1 /* 2 * Copyright (C) 2007 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.app; 18 19 import static android.annotation.Dimension.DP; 20 import static android.graphics.drawable.Icon.TYPE_URI; 21 import static android.graphics.drawable.Icon.TYPE_URI_ADAPTIVE_BITMAP; 22 23 import static java.util.Objects.requireNonNull; 24 25 import android.annotation.ColorInt; 26 import android.annotation.ColorRes; 27 import android.annotation.DimenRes; 28 import android.annotation.Dimension; 29 import android.annotation.DrawableRes; 30 import android.annotation.IdRes; 31 import android.annotation.IntDef; 32 import android.annotation.NonNull; 33 import android.annotation.Nullable; 34 import android.annotation.RequiresPermission; 35 import android.annotation.SdkConstant; 36 import android.annotation.SdkConstant.SdkConstantType; 37 import android.annotation.StringRes; 38 import android.annotation.StyleableRes; 39 import android.annotation.SuppressLint; 40 import android.annotation.SystemApi; 41 import android.annotation.TestApi; 42 import android.compat.annotation.UnsupportedAppUsage; 43 import android.content.Context; 44 import android.content.Intent; 45 import android.content.LocusId; 46 import android.content.pm.ApplicationInfo; 47 import android.content.pm.PackageManager; 48 import android.content.pm.PackageManager.NameNotFoundException; 49 import android.content.pm.ShortcutInfo; 50 import android.content.res.ColorStateList; 51 import android.content.res.Configuration; 52 import android.content.res.Resources; 53 import android.content.res.TypedArray; 54 import android.graphics.Bitmap; 55 import android.graphics.Canvas; 56 import android.graphics.Color; 57 import android.graphics.PorterDuff; 58 import android.graphics.drawable.Drawable; 59 import android.graphics.drawable.Icon; 60 import android.media.AudioAttributes; 61 import android.media.AudioManager; 62 import android.media.PlayerBase; 63 import android.media.session.MediaSession; 64 import android.net.Uri; 65 import android.os.BadParcelableException; 66 import android.os.Build; 67 import android.os.Bundle; 68 import android.os.IBinder; 69 import android.os.Parcel; 70 import android.os.Parcelable; 71 import android.os.SystemClock; 72 import android.os.SystemProperties; 73 import android.os.UserHandle; 74 import android.provider.Settings; 75 import android.text.BidiFormatter; 76 import android.text.SpannableStringBuilder; 77 import android.text.Spanned; 78 import android.text.TextUtils; 79 import android.text.style.AbsoluteSizeSpan; 80 import android.text.style.CharacterStyle; 81 import android.text.style.ForegroundColorSpan; 82 import android.text.style.RelativeSizeSpan; 83 import android.text.style.TextAppearanceSpan; 84 import android.util.ArraySet; 85 import android.util.Log; 86 import android.util.Pair; 87 import android.util.SparseArray; 88 import android.util.TypedValue; 89 import android.util.proto.ProtoOutputStream; 90 import android.view.ContextThemeWrapper; 91 import android.view.Gravity; 92 import android.view.View; 93 import android.view.contentcapture.ContentCaptureContext; 94 import android.widget.ProgressBar; 95 import android.widget.RemoteViews; 96 97 import com.android.internal.R; 98 import com.android.internal.annotations.VisibleForTesting; 99 import com.android.internal.graphics.ColorUtils; 100 import com.android.internal.util.ArrayUtils; 101 import com.android.internal.util.ContrastColorUtil; 102 103 import java.lang.annotation.Retention; 104 import java.lang.annotation.RetentionPolicy; 105 import java.lang.reflect.Array; 106 import java.lang.reflect.Constructor; 107 import java.util.ArrayList; 108 import java.util.Arrays; 109 import java.util.Collections; 110 import java.util.List; 111 import java.util.Objects; 112 import java.util.Set; 113 import java.util.function.Consumer; 114 115 /** 116 * A class that represents how a persistent notification is to be presented to 117 * the user using the {@link android.app.NotificationManager}. 118 * 119 * <p>The {@link Notification.Builder Notification.Builder} has been added to make it 120 * easier to construct Notifications.</p> 121 * 122 * <div class="special reference"> 123 * <h3>Developer Guides</h3> 124 * <p>For a guide to creating notifications, read the 125 * <a href="{@docRoot}guide/topics/ui/notifiers/notifications.html">Status Bar Notifications</a> 126 * developer guide.</p> 127 * </div> 128 */ 129 public class Notification implements Parcelable 130 { 131 private static final String TAG = "Notification"; 132 133 /** 134 * @hide 135 */ 136 @Retention(RetentionPolicy.SOURCE) 137 @IntDef({ 138 FOREGROUND_SERVICE_DEFAULT, 139 FOREGROUND_SERVICE_IMMEDIATE, 140 FOREGROUND_SERVICE_DEFERRED 141 }) 142 public @interface ServiceNotificationPolicy {}; 143 144 /** 145 * If the Notification associated with starting a foreground service has been 146 * built using setForegroundServiceBehavior() with this behavior, display of 147 * the notification will usually be suppressed for a short time to avoid visual 148 * disturbances to the user. 149 * @see Notification.Builder#setForegroundServiceBehavior(int) 150 * @see #FOREGROUND_SERVICE_IMMEDIATE 151 * @see #FOREGROUND_SERVICE_DEFERRED 152 */ 153 public static final @ServiceNotificationPolicy int FOREGROUND_SERVICE_DEFAULT = 0; 154 155 /** 156 * If the Notification associated with starting a foreground service has been 157 * built using setForegroundServiceBehavior() with this behavior, display of 158 * the notification will be immediate even if the default behavior would be 159 * to defer visibility for a short time. 160 * @see Notification.Builder#setForegroundServiceBehavior(int) 161 * @see #FOREGROUND_SERVICE_DEFAULT 162 * @see #FOREGROUND_SERVICE_DEFERRED 163 */ 164 public static final @ServiceNotificationPolicy int FOREGROUND_SERVICE_IMMEDIATE = 1; 165 166 /** 167 * If the Notification associated with starting a foreground service has been 168 * built using setForegroundServiceBehavior() with this behavior, display of 169 * the notification will usually be suppressed for a short time to avoid visual 170 * disturbances to the user. 171 * @see Notification.Builder#setForegroundServiceBehavior(int) 172 * @see #FOREGROUND_SERVICE_DEFAULT 173 * @see #FOREGROUND_SERVICE_IMMEDIATE 174 */ 175 public static final @ServiceNotificationPolicy int FOREGROUND_SERVICE_DEFERRED = 2; 176 177 @ServiceNotificationPolicy 178 private int mFgsDeferBehavior; 179 180 /** 181 * An activity that provides a user interface for adjusting notification preferences for its 182 * containing application. 183 */ 184 @SdkConstant(SdkConstantType.INTENT_CATEGORY) 185 public static final String INTENT_CATEGORY_NOTIFICATION_PREFERENCES 186 = "android.intent.category.NOTIFICATION_PREFERENCES"; 187 188 /** 189 * Optional extra for {@link #INTENT_CATEGORY_NOTIFICATION_PREFERENCES}. If provided, will 190 * contain a {@link NotificationChannel#getId() channel id} that can be used to narrow down 191 * what settings should be shown in the target app. 192 */ 193 public static final String EXTRA_CHANNEL_ID = "android.intent.extra.CHANNEL_ID"; 194 195 /** 196 * Optional extra for {@link #INTENT_CATEGORY_NOTIFICATION_PREFERENCES}. If provided, will 197 * contain a {@link NotificationChannelGroup#getId() group id} that can be used to narrow down 198 * what settings should be shown in the target app. 199 */ 200 public static final String EXTRA_CHANNEL_GROUP_ID = "android.intent.extra.CHANNEL_GROUP_ID"; 201 202 /** 203 * Optional extra for {@link #INTENT_CATEGORY_NOTIFICATION_PREFERENCES}. If provided, will 204 * contain the tag provided to {@link NotificationManager#notify(String, int, Notification)} 205 * that can be used to narrow down what settings should be shown in the target app. 206 */ 207 public static final String EXTRA_NOTIFICATION_TAG = "android.intent.extra.NOTIFICATION_TAG"; 208 209 /** 210 * Optional extra for {@link #INTENT_CATEGORY_NOTIFICATION_PREFERENCES}. If provided, will 211 * contain the id provided to {@link NotificationManager#notify(String, int, Notification)} 212 * that can be used to narrow down what settings should be shown in the target app. 213 */ 214 public static final String EXTRA_NOTIFICATION_ID = "android.intent.extra.NOTIFICATION_ID"; 215 216 /** 217 * Use all default values (where applicable). 218 */ 219 public static final int DEFAULT_ALL = ~0; 220 221 /** 222 * Use the default notification sound. This will ignore any given 223 * {@link #sound}. 224 * 225 * <p> 226 * A notification that is noisy is more likely to be presented as a heads-up notification. 227 * </p> 228 * 229 * @see #defaults 230 */ 231 232 public static final int DEFAULT_SOUND = 1; 233 234 /** 235 * Use the default notification vibrate. This will ignore any given 236 * {@link #vibrate}. Using phone vibration requires the 237 * {@link android.Manifest.permission#VIBRATE VIBRATE} permission. 238 * 239 * <p> 240 * A notification that vibrates is more likely to be presented as a heads-up notification. 241 * </p> 242 * 243 * @see #defaults 244 */ 245 246 public static final int DEFAULT_VIBRATE = 2; 247 248 /** 249 * Use the default notification lights. This will ignore the 250 * {@link #FLAG_SHOW_LIGHTS} bit, and {@link #ledARGB}, {@link #ledOffMS}, or 251 * {@link #ledOnMS}. 252 * 253 * @see #defaults 254 */ 255 256 public static final int DEFAULT_LIGHTS = 4; 257 258 /** 259 * Maximum length of CharSequences accepted by Builder and friends. 260 * 261 * <p> 262 * Avoids spamming the system with overly large strings such as full e-mails. 263 */ 264 private static final int MAX_CHARSEQUENCE_LENGTH = 1024; 265 266 /** 267 * Maximum entries of reply text that are accepted by Builder and friends. 268 */ 269 private static final int MAX_REPLY_HISTORY = 5; 270 271 /** 272 * Maximum aspect ratio of the large icon. 16:9 273 */ 274 private static final float MAX_LARGE_ICON_ASPECT_RATIO = 16f / 9f; 275 276 /** 277 * Maximum number of (generic) action buttons in a notification (contextual action buttons are 278 * handled separately). 279 * @hide 280 */ 281 public static final int MAX_ACTION_BUTTONS = 3; 282 283 /** 284 * If the notification contained an unsent draft for a RemoteInput when the user clicked on it, 285 * we're adding the draft as a String extra to the {@link #contentIntent} using this key. 286 * 287 * <p>Apps may use this extra to prepopulate text fields in the app, where the user usually 288 * sends messages.</p> 289 */ 290 public static final String EXTRA_REMOTE_INPUT_DRAFT = "android.remoteInputDraft"; 291 292 /** 293 * A timestamp related to this notification, in milliseconds since the epoch. 294 * 295 * Default value: {@link System#currentTimeMillis() Now}. 296 * 297 * Choose a timestamp that will be most relevant to the user. For most finite events, this 298 * corresponds to the time the event happened (or will happen, in the case of events that have 299 * yet to occur but about which the user is being informed). Indefinite events should be 300 * timestamped according to when the activity began. 301 * 302 * Some examples: 303 * 304 * <ul> 305 * <li>Notification of a new chat message should be stamped when the message was received.</li> 306 * <li>Notification of an ongoing file download (with a progress bar, for example) should be stamped when the download started.</li> 307 * <li>Notification of a completed file download should be stamped when the download finished.</li> 308 * <li>Notification of an upcoming meeting should be stamped with the time the meeting will begin (that is, in the future).</li> 309 * <li>Notification of an ongoing stopwatch (increasing timer) should be stamped with the watch's start time. 310 * <li>Notification of an ongoing countdown timer should be stamped with the timer's end time. 311 * </ul> 312 * 313 * For apps targeting {@link android.os.Build.VERSION_CODES#N} and above, this time is not shown 314 * anymore by default and must be opted into by using 315 * {@link android.app.Notification.Builder#setShowWhen(boolean)} 316 */ 317 public long when; 318 319 /** 320 * The creation time of the notification 321 */ 322 private long creationTime; 323 324 /** 325 * The resource id of a drawable to use as the icon in the status bar. 326 * 327 * @deprecated Use {@link Builder#setSmallIcon(Icon)} instead. 328 */ 329 @Deprecated 330 @DrawableRes 331 public int icon; 332 333 /** 334 * If the icon in the status bar is to have more than one level, you can set this. Otherwise, 335 * leave it at its default value of 0. 336 * 337 * @see android.widget.ImageView#setImageLevel 338 * @see android.graphics.drawable.Drawable#setLevel 339 */ 340 public int iconLevel; 341 342 /** 343 * The number of events that this notification represents. For example, in a new mail 344 * notification, this could be the number of unread messages. 345 * 346 * The system may or may not use this field to modify the appearance of the notification. 347 * Starting with {@link android.os.Build.VERSION_CODES#O}, the number may be displayed as a 348 * badge icon in Launchers that support badging. 349 */ 350 public int number = 0; 351 352 /** 353 * The intent to execute when the expanded status entry is clicked. If 354 * this is an activity, it must include the 355 * {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK} flag, which requires 356 * that you take care of task management as described in the 357 * <a href="{@docRoot}guide/topics/fundamentals/tasks-and-back-stack.html">Tasks and Back 358 * Stack</a> document. In particular, make sure to read the notification section 359 * <a href="{@docRoot}guide/topics/ui/notifiers/notifications.html#HandlingNotifications">Handling 360 * Notifications</a> for the correct ways to launch an application from a 361 * notification. 362 */ 363 public PendingIntent contentIntent; 364 365 /** 366 * The intent to execute when the notification is explicitly dismissed by the user, either with 367 * the "Clear All" button or by swiping it away individually. 368 * 369 * This probably shouldn't be launching an activity since several of those will be sent 370 * at the same time. 371 */ 372 public PendingIntent deleteIntent; 373 374 /** 375 * An intent to launch instead of posting the notification to the status bar. 376 * 377 * <p> 378 * The system UI may choose to display a heads-up notification, instead of 379 * launching this intent, while the user is using the device. 380 * </p> 381 * 382 * @see Notification.Builder#setFullScreenIntent 383 */ 384 public PendingIntent fullScreenIntent; 385 386 /** 387 * Text that summarizes this notification for accessibility services. 388 * 389 * As of the L release, this text is no longer shown on screen, but it is still useful to 390 * accessibility services (where it serves as an audible announcement of the notification's 391 * appearance). 392 * 393 * @see #tickerView 394 */ 395 public CharSequence tickerText; 396 397 /** 398 * Formerly, a view showing the {@link #tickerText}. 399 * 400 * No longer displayed in the status bar as of API 21. 401 */ 402 @Deprecated 403 public RemoteViews tickerView; 404 405 /** 406 * The view that will represent this notification in the notification list (which is pulled 407 * down from the status bar). 408 * 409 * As of N, this field may be null. The notification view is determined by the inputs 410 * to {@link Notification.Builder}; a custom RemoteViews can optionally be 411 * supplied with {@link Notification.Builder#setCustomContentView(RemoteViews)}. 412 */ 413 @Deprecated 414 public RemoteViews contentView; 415 416 /** 417 * A large-format version of {@link #contentView}, giving the Notification an 418 * opportunity to show more detail. The system UI may choose to show this 419 * instead of the normal content view at its discretion. 420 * 421 * As of N, this field may be null. The expanded notification view is determined by the 422 * inputs to {@link Notification.Builder}; a custom RemoteViews can optionally be 423 * supplied with {@link Notification.Builder#setCustomBigContentView(RemoteViews)}. 424 */ 425 @Deprecated 426 public RemoteViews bigContentView; 427 428 429 /** 430 * A medium-format version of {@link #contentView}, providing the Notification an 431 * opportunity to add action buttons to contentView. At its discretion, the system UI may 432 * choose to show this as a heads-up notification, which will pop up so the user can see 433 * it without leaving their current activity. 434 * 435 * As of N, this field may be null. The heads-up notification view is determined by the 436 * inputs to {@link Notification.Builder}; a custom RemoteViews can optionally be 437 * supplied with {@link Notification.Builder#setCustomHeadsUpContentView(RemoteViews)}. 438 */ 439 @Deprecated 440 public RemoteViews headsUpContentView; 441 442 private boolean mUsesStandardHeader; 443 444 private static final ArraySet<Integer> STANDARD_LAYOUTS = new ArraySet<>(); 445 static { 446 STANDARD_LAYOUTS.add(R.layout.notification_template_material_base); 447 STANDARD_LAYOUTS.add(R.layout.notification_template_material_heads_up_base); 448 STANDARD_LAYOUTS.add(R.layout.notification_template_material_big_base); 449 STANDARD_LAYOUTS.add(R.layout.notification_template_material_big_picture); 450 STANDARD_LAYOUTS.add(R.layout.notification_template_material_big_text); 451 STANDARD_LAYOUTS.add(R.layout.notification_template_material_inbox); 452 STANDARD_LAYOUTS.add(R.layout.notification_template_material_messaging); 453 STANDARD_LAYOUTS.add(R.layout.notification_template_material_big_messaging); 454 STANDARD_LAYOUTS.add(R.layout.notification_template_material_conversation); 455 STANDARD_LAYOUTS.add(R.layout.notification_template_material_media); 456 STANDARD_LAYOUTS.add(R.layout.notification_template_material_big_media); 457 STANDARD_LAYOUTS.add(R.layout.notification_template_material_call); 458 STANDARD_LAYOUTS.add(R.layout.notification_template_material_big_call); 459 STANDARD_LAYOUTS.add(R.layout.notification_template_header); 460 } 461 462 /** 463 * A large bitmap to be shown in the notification content area. 464 * 465 * @deprecated Use {@link Builder#setLargeIcon(Icon)} instead. 466 */ 467 @Deprecated 468 public Bitmap largeIcon; 469 470 /** 471 * The sound to play. 472 * 473 * <p> 474 * A notification that is noisy is more likely to be presented as a heads-up notification. 475 * </p> 476 * 477 * <p> 478 * To play the default notification sound, see {@link #defaults}. 479 * </p> 480 * @deprecated use {@link NotificationChannel#getSound()}. 481 */ 482 @Deprecated 483 public Uri sound; 484 485 /** 486 * Use this constant as the value for audioStreamType to request that 487 * the default stream type for notifications be used. Currently the 488 * default stream type is {@link AudioManager#STREAM_NOTIFICATION}. 489 * 490 * @deprecated Use {@link NotificationChannel#getAudioAttributes()} instead. 491 */ 492 @Deprecated 493 public static final int STREAM_DEFAULT = -1; 494 495 /** 496 * The audio stream type to use when playing the sound. 497 * Should be one of the STREAM_ constants from 498 * {@link android.media.AudioManager}. 499 * 500 * @deprecated Use {@link #audioAttributes} instead. 501 */ 502 @Deprecated 503 public int audioStreamType = STREAM_DEFAULT; 504 505 /** 506 * The default value of {@link #audioAttributes}. 507 */ 508 public static final AudioAttributes AUDIO_ATTRIBUTES_DEFAULT = new AudioAttributes.Builder() 509 .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) 510 .setUsage(AudioAttributes.USAGE_NOTIFICATION) 511 .build(); 512 513 /** 514 * The {@link AudioAttributes audio attributes} to use when playing the sound. 515 * 516 * @deprecated use {@link NotificationChannel#getAudioAttributes()} instead. 517 */ 518 @Deprecated 519 public AudioAttributes audioAttributes = AUDIO_ATTRIBUTES_DEFAULT; 520 521 /** 522 * The pattern with which to vibrate. 523 * 524 * <p> 525 * To vibrate the default pattern, see {@link #defaults}. 526 * </p> 527 * 528 * @see android.os.Vibrator#vibrate(long[],int) 529 * @deprecated use {@link NotificationChannel#getVibrationPattern()}. 530 */ 531 @Deprecated 532 public long[] vibrate; 533 534 /** 535 * The color of the led. The hardware will do its best approximation. 536 * 537 * @see #FLAG_SHOW_LIGHTS 538 * @see #flags 539 * @deprecated use {@link NotificationChannel#shouldShowLights()}. 540 */ 541 @ColorInt 542 @Deprecated 543 public int ledARGB; 544 545 /** 546 * The number of milliseconds for the LED to be on while it's flashing. 547 * The hardware will do its best approximation. 548 * 549 * @see #FLAG_SHOW_LIGHTS 550 * @see #flags 551 * @deprecated use {@link NotificationChannel#shouldShowLights()}. 552 */ 553 @Deprecated 554 public int ledOnMS; 555 556 /** 557 * The number of milliseconds for the LED to be off while it's flashing. 558 * The hardware will do its best approximation. 559 * 560 * @see #FLAG_SHOW_LIGHTS 561 * @see #flags 562 * 563 * @deprecated use {@link NotificationChannel#shouldShowLights()}. 564 */ 565 @Deprecated 566 public int ledOffMS; 567 568 /** 569 * Specifies which values should be taken from the defaults. 570 * <p> 571 * To set, OR the desired from {@link #DEFAULT_SOUND}, 572 * {@link #DEFAULT_VIBRATE}, {@link #DEFAULT_LIGHTS}. For all default 573 * values, use {@link #DEFAULT_ALL}. 574 * </p> 575 * 576 * @deprecated use {@link NotificationChannel#getSound()} and 577 * {@link NotificationChannel#shouldShowLights()} and 578 * {@link NotificationChannel#shouldVibrate()}. 579 */ 580 @Deprecated 581 public int defaults; 582 583 /** 584 * Bit to be bitwise-ored into the {@link #flags} field that should be 585 * set if you want the LED on for this notification. 586 * <ul> 587 * <li>To turn the LED off, pass 0 in the alpha channel for colorARGB 588 * or 0 for both ledOnMS and ledOffMS.</li> 589 * <li>To turn the LED on, pass 1 for ledOnMS and 0 for ledOffMS.</li> 590 * <li>To flash the LED, pass the number of milliseconds that it should 591 * be on and off to ledOnMS and ledOffMS.</li> 592 * </ul> 593 * <p> 594 * Since hardware varies, you are not guaranteed that any of the values 595 * you pass are honored exactly. Use the system defaults if possible 596 * because they will be set to values that work on any given hardware. 597 * <p> 598 * The alpha channel must be set for forward compatibility. 599 * 600 * @deprecated use {@link NotificationChannel#shouldShowLights()}. 601 */ 602 @Deprecated 603 public static final int FLAG_SHOW_LIGHTS = 0x00000001; 604 605 /** 606 * Bit to be bitwise-ored into the {@link #flags} field that should be 607 * set if this notification is in reference to something that is ongoing, 608 * like a phone call. It should not be set if this notification is in 609 * reference to something that happened at a particular point in time, 610 * like a missed phone call. 611 */ 612 public static final int FLAG_ONGOING_EVENT = 0x00000002; 613 614 /** 615 * Bit to be bitwise-ored into the {@link #flags} field that if set, 616 * the audio will be repeated until the notification is 617 * cancelled or the notification window is opened. 618 */ 619 public static final int FLAG_INSISTENT = 0x00000004; 620 621 /** 622 * Bit to be bitwise-ored into the {@link #flags} field that should be 623 * set if you would only like the sound, vibrate and ticker to be played 624 * if the notification was not already showing. 625 */ 626 public static final int FLAG_ONLY_ALERT_ONCE = 0x00000008; 627 628 /** 629 * Bit to be bitwise-ored into the {@link #flags} field that should be 630 * set if the notification should be canceled when it is clicked by the 631 * user. 632 */ 633 public static final int FLAG_AUTO_CANCEL = 0x00000010; 634 635 /** 636 * Bit to be bitwise-ored into the {@link #flags} field that should be 637 * set if the notification should not be canceled when the user clicks 638 * the Clear all button. 639 */ 640 public static final int FLAG_NO_CLEAR = 0x00000020; 641 642 /** 643 * Bit to be bitwise-ored into the {@link #flags} field that should be 644 * set if this notification represents a currently running service. This 645 * will normally be set for you by {@link Service#startForeground}. 646 */ 647 public static final int FLAG_FOREGROUND_SERVICE = 0x00000040; 648 649 /** 650 * Obsolete flag indicating high-priority notifications; use the priority field instead. 651 * 652 * @deprecated Use {@link #priority} with a positive value. 653 */ 654 @Deprecated 655 public static final int FLAG_HIGH_PRIORITY = 0x00000080; 656 657 /** 658 * Bit to be bitswise-ored into the {@link #flags} field that should be 659 * set if this notification is relevant to the current device only 660 * and it is not recommended that it bridge to other devices. 661 */ 662 public static final int FLAG_LOCAL_ONLY = 0x00000100; 663 664 /** 665 * Bit to be bitswise-ored into the {@link #flags} field that should be 666 * set if this notification is the group summary for a group of notifications. 667 * Grouped notifications may display in a cluster or stack on devices which 668 * support such rendering. Requires a group key also be set using {@link Builder#setGroup}. 669 */ 670 public static final int FLAG_GROUP_SUMMARY = 0x00000200; 671 672 /** 673 * Bit to be bitswise-ored into the {@link #flags} field that should be 674 * set if this notification is the group summary for an auto-group of notifications. 675 * 676 * @hide 677 */ 678 @SystemApi 679 public static final int FLAG_AUTOGROUP_SUMMARY = 0x00000400; 680 681 /** 682 * @hide 683 */ 684 public static final int FLAG_CAN_COLORIZE = 0x00000800; 685 686 /** 687 * Bit to be bitswised-ored into the {@link #flags} field that should be 688 * set by the system if this notification is showing as a bubble. 689 * 690 * Applications cannot set this flag directly; they should instead call 691 * {@link Notification.Builder#setBubbleMetadata(BubbleMetadata)} to 692 * request that a notification be displayed as a bubble, and then check 693 * this flag to see whether that request was honored by the system. 694 */ 695 public static final int FLAG_BUBBLE = 0x00001000; 696 697 private static final List<Class<? extends Style>> PLATFORM_STYLE_CLASSES = Arrays.asList( 698 BigTextStyle.class, BigPictureStyle.class, InboxStyle.class, MediaStyle.class, 699 DecoratedCustomViewStyle.class, DecoratedMediaCustomViewStyle.class, 700 MessagingStyle.class, CallStyle.class); 701 702 /** @hide */ 703 @IntDef(flag = true, prefix = { "FLAG_" }, value = {FLAG_SHOW_LIGHTS, FLAG_ONGOING_EVENT, 704 FLAG_INSISTENT, FLAG_ONLY_ALERT_ONCE, 705 FLAG_AUTO_CANCEL, FLAG_NO_CLEAR, FLAG_FOREGROUND_SERVICE, FLAG_HIGH_PRIORITY, 706 FLAG_LOCAL_ONLY, FLAG_GROUP_SUMMARY, FLAG_AUTOGROUP_SUMMARY, FLAG_BUBBLE}) 707 @Retention(RetentionPolicy.SOURCE) 708 public @interface NotificationFlags{}; 709 710 public int flags; 711 712 /** @hide */ 713 @IntDef(prefix = { "PRIORITY_" }, value = { 714 PRIORITY_DEFAULT, 715 PRIORITY_LOW, 716 PRIORITY_MIN, 717 PRIORITY_HIGH, 718 PRIORITY_MAX 719 }) 720 @Retention(RetentionPolicy.SOURCE) 721 public @interface Priority {} 722 723 /** 724 * Default notification {@link #priority}. If your application does not prioritize its own 725 * notifications, use this value for all notifications. 726 * 727 * @deprecated use {@link NotificationManager#IMPORTANCE_DEFAULT} instead. 728 */ 729 @Deprecated 730 public static final int PRIORITY_DEFAULT = 0; 731 732 /** 733 * Lower {@link #priority}, for items that are less important. The UI may choose to show these 734 * items smaller, or at a different position in the list, compared with your app's 735 * {@link #PRIORITY_DEFAULT} items. 736 * 737 * @deprecated use {@link NotificationManager#IMPORTANCE_LOW} instead. 738 */ 739 @Deprecated 740 public static final int PRIORITY_LOW = -1; 741 742 /** 743 * Lowest {@link #priority}; these items might not be shown to the user except under special 744 * circumstances, such as detailed notification logs. 745 * 746 * @deprecated use {@link NotificationManager#IMPORTANCE_MIN} instead. 747 */ 748 @Deprecated 749 public static final int PRIORITY_MIN = -2; 750 751 /** 752 * Higher {@link #priority}, for more important notifications or alerts. The UI may choose to 753 * show these items larger, or at a different position in notification lists, compared with 754 * your app's {@link #PRIORITY_DEFAULT} items. 755 * 756 * @deprecated use {@link NotificationManager#IMPORTANCE_HIGH} instead. 757 */ 758 @Deprecated 759 public static final int PRIORITY_HIGH = 1; 760 761 /** 762 * Highest {@link #priority}, for your application's most important items that require the 763 * user's prompt attention or input. 764 * 765 * @deprecated use {@link NotificationManager#IMPORTANCE_HIGH} instead. 766 */ 767 @Deprecated 768 public static final int PRIORITY_MAX = 2; 769 770 /** 771 * Relative priority for this notification. 772 * 773 * Priority is an indication of how much of the user's valuable attention should be consumed by 774 * this notification. Low-priority notifications may be hidden from the user in certain 775 * situations, while the user might be interrupted for a higher-priority notification. The 776 * system will make a determination about how to interpret this priority when presenting 777 * the notification. 778 * 779 * <p> 780 * A notification that is at least {@link #PRIORITY_HIGH} is more likely to be presented 781 * as a heads-up notification. 782 * </p> 783 * 784 * @deprecated use {@link NotificationChannel#getImportance()} instead. 785 */ 786 @Priority 787 @Deprecated 788 public int priority; 789 790 /** 791 * Accent color (an ARGB integer like the constants in {@link android.graphics.Color}) 792 * to be applied by the standard Style templates when presenting this notification. 793 * 794 * The current template design constructs a colorful header image by overlaying the 795 * {@link #icon} image (stenciled in white) atop a field of this color. Alpha components are 796 * ignored. 797 */ 798 @ColorInt 799 public int color = COLOR_DEFAULT; 800 801 /** 802 * Special value of {@link #color} telling the system not to decorate this notification with 803 * any special color but instead use default colors when presenting this notification. 804 */ 805 @ColorInt 806 public static final int COLOR_DEFAULT = 0; // AKA Color.TRANSPARENT 807 808 /** 809 * Special value of {@link #color} used as a place holder for an invalid color. 810 * @hide 811 */ 812 @ColorInt 813 public static final int COLOR_INVALID = 1; 814 815 /** 816 * Sphere of visibility of this notification, which affects how and when the SystemUI reveals 817 * the notification's presence and contents in untrusted situations (namely, on the secure 818 * lockscreen). 819 * 820 * The default level, {@link #VISIBILITY_PRIVATE}, behaves exactly as notifications have always 821 * done on Android: The notification's {@link #icon} and {@link #tickerText} (if available) are 822 * shown in all situations, but the contents are only available if the device is unlocked for 823 * the appropriate user. 824 * 825 * A more permissive policy can be expressed by {@link #VISIBILITY_PUBLIC}; such a notification 826 * can be read even in an "insecure" context (that is, above a secure lockscreen). 827 * To modify the public version of this notification—for example, to redact some portions—see 828 * {@link Builder#setPublicVersion(Notification)}. 829 * 830 * Finally, a notification can be made {@link #VISIBILITY_SECRET}, which will suppress its icon 831 * and ticker until the user has bypassed the lockscreen. 832 */ 833 public @Visibility int visibility; 834 835 /** @hide */ 836 @IntDef(prefix = { "VISIBILITY_" }, value = { 837 VISIBILITY_PUBLIC, 838 VISIBILITY_PRIVATE, 839 VISIBILITY_SECRET, 840 }) 841 @Retention(RetentionPolicy.SOURCE) 842 public @interface Visibility {} 843 844 /** 845 * Notification visibility: Show this notification in its entirety on all lockscreens. 846 * 847 * {@see #visibility} 848 */ 849 public static final int VISIBILITY_PUBLIC = 1; 850 851 /** 852 * Notification visibility: Show this notification on all lockscreens, but conceal sensitive or 853 * private information on secure lockscreens. 854 * 855 * {@see #visibility} 856 */ 857 public static final int VISIBILITY_PRIVATE = 0; 858 859 /** 860 * Notification visibility: Do not reveal any part of this notification on a secure lockscreen. 861 * 862 * {@see #visibility} 863 */ 864 public static final int VISIBILITY_SECRET = -1; 865 866 /** 867 * @hide 868 */ 869 @IntDef(prefix = "VISIBILITY_", value = { 870 VISIBILITY_PUBLIC, 871 VISIBILITY_PRIVATE, 872 VISIBILITY_SECRET, 873 NotificationManager.VISIBILITY_NO_OVERRIDE 874 }) 875 public @interface NotificationVisibilityOverride{}; 876 877 /** 878 * Notification category: incoming call (voice or video) or similar synchronous communication request. 879 */ 880 public static final String CATEGORY_CALL = "call"; 881 882 /** 883 * Notification category: map turn-by-turn navigation. 884 */ 885 public static final String CATEGORY_NAVIGATION = "navigation"; 886 887 /** 888 * Notification category: incoming direct message (SMS, instant message, etc.). 889 */ 890 public static final String CATEGORY_MESSAGE = "msg"; 891 892 /** 893 * Notification category: asynchronous bulk message (email). 894 */ 895 public static final String CATEGORY_EMAIL = "email"; 896 897 /** 898 * Notification category: calendar event. 899 */ 900 public static final String CATEGORY_EVENT = "event"; 901 902 /** 903 * Notification category: promotion or advertisement. 904 */ 905 public static final String CATEGORY_PROMO = "promo"; 906 907 /** 908 * Notification category: alarm or timer. 909 */ 910 public static final String CATEGORY_ALARM = "alarm"; 911 912 /** 913 * Notification category: progress of a long-running background operation. 914 */ 915 public static final String CATEGORY_PROGRESS = "progress"; 916 917 /** 918 * Notification category: social network or sharing update. 919 */ 920 public static final String CATEGORY_SOCIAL = "social"; 921 922 /** 923 * Notification category: error in background operation or authentication status. 924 */ 925 public static final String CATEGORY_ERROR = "err"; 926 927 /** 928 * Notification category: media transport control for playback. 929 */ 930 public static final String CATEGORY_TRANSPORT = "transport"; 931 932 /** 933 * Notification category: system or device status update. Reserved for system use. 934 */ 935 public static final String CATEGORY_SYSTEM = "sys"; 936 937 /** 938 * Notification category: indication of running background service. 939 */ 940 public static final String CATEGORY_SERVICE = "service"; 941 942 /** 943 * Notification category: a specific, timely recommendation for a single thing. 944 * For example, a news app might want to recommend a news story it believes the user will 945 * want to read next. 946 */ 947 public static final String CATEGORY_RECOMMENDATION = "recommendation"; 948 949 /** 950 * Notification category: ongoing information about device or contextual status. 951 */ 952 public static final String CATEGORY_STATUS = "status"; 953 954 /** 955 * Notification category: user-scheduled reminder. 956 */ 957 public static final String CATEGORY_REMINDER = "reminder"; 958 959 /** 960 * Notification category: extreme car emergencies. 961 * @hide 962 */ 963 @SystemApi 964 public static final String CATEGORY_CAR_EMERGENCY = "car_emergency"; 965 966 /** 967 * Notification category: car warnings. 968 * @hide 969 */ 970 @SystemApi 971 public static final String CATEGORY_CAR_WARNING = "car_warning"; 972 973 /** 974 * Notification category: general car system information. 975 * @hide 976 */ 977 @SystemApi 978 public static final String CATEGORY_CAR_INFORMATION = "car_information"; 979 980 /** 981 * Notification category: tracking a user's workout. 982 */ 983 public static final String CATEGORY_WORKOUT = "workout"; 984 985 /** 986 * Notification category: temporarily sharing location. 987 */ 988 public static final String CATEGORY_LOCATION_SHARING = "location_sharing"; 989 990 /** 991 * Notification category: running stopwatch. 992 */ 993 public static final String CATEGORY_STOPWATCH = "stopwatch"; 994 995 /** 996 * Notification category: missed call. 997 */ 998 public static final String CATEGORY_MISSED_CALL = "missed_call"; 999 1000 /** 1001 * One of the predefined notification categories (see the <code>CATEGORY_*</code> constants) 1002 * that best describes this Notification. May be used by the system for ranking and filtering. 1003 */ 1004 public String category; 1005 1006 @UnsupportedAppUsage 1007 private String mGroupKey; 1008 1009 /** 1010 * Get the key used to group this notification into a cluster or stack 1011 * with other notifications on devices which support such rendering. 1012 */ getGroup()1013 public String getGroup() { 1014 return mGroupKey; 1015 } 1016 1017 private String mSortKey; 1018 1019 /** 1020 * Get a sort key that orders this notification among other notifications from the 1021 * same package. This can be useful if an external sort was already applied and an app 1022 * would like to preserve this. Notifications will be sorted lexicographically using this 1023 * value, although providing different priorities in addition to providing sort key may 1024 * cause this value to be ignored. 1025 * 1026 * <p>This sort key can also be used to order members of a notification group. See 1027 * {@link Builder#setGroup}. 1028 * 1029 * @see String#compareTo(String) 1030 */ getSortKey()1031 public String getSortKey() { 1032 return mSortKey; 1033 } 1034 1035 /** 1036 * Additional semantic data to be carried around with this Notification. 1037 * <p> 1038 * The extras keys defined here are intended to capture the original inputs to {@link Builder} 1039 * APIs, and are intended to be used by 1040 * {@link android.service.notification.NotificationListenerService} implementations to extract 1041 * detailed information from notification objects. 1042 */ 1043 public Bundle extras = new Bundle(); 1044 1045 /** 1046 * All pending intents in the notification as the system needs to be able to access them but 1047 * touching the extras bundle in the system process is not safe because the bundle may contain 1048 * custom parcelable objects. 1049 * 1050 * @hide 1051 */ 1052 @UnsupportedAppUsage 1053 public ArraySet<PendingIntent> allPendingIntents; 1054 1055 /** 1056 * Token identifying the notification that is applying doze/bgcheck allowlisting to the 1057 * pending intents inside of it, so only those will get the behavior. 1058 * 1059 * @hide 1060 */ 1061 private IBinder mAllowlistToken; 1062 1063 /** 1064 * Must be set by a process to start associating tokens with Notification objects 1065 * coming in to it. This is set by NotificationManagerService. 1066 * 1067 * @hide 1068 */ 1069 static public IBinder processAllowlistToken; 1070 1071 /** 1072 * {@link #extras} key: this is the title of the notification, 1073 * as supplied to {@link Builder#setContentTitle(CharSequence)}. 1074 */ 1075 public static final String EXTRA_TITLE = "android.title"; 1076 1077 /** 1078 * {@link #extras} key: this is the title of the notification when shown in expanded form, 1079 * e.g. as supplied to {@link BigTextStyle#setBigContentTitle(CharSequence)}. 1080 */ 1081 public static final String EXTRA_TITLE_BIG = EXTRA_TITLE + ".big"; 1082 1083 /** 1084 * {@link #extras} key: this is the main text payload, as supplied to 1085 * {@link Builder#setContentText(CharSequence)}. 1086 */ 1087 public static final String EXTRA_TEXT = "android.text"; 1088 1089 /** 1090 * {@link #extras} key: this is a third line of text, as supplied to 1091 * {@link Builder#setSubText(CharSequence)}. 1092 */ 1093 public static final String EXTRA_SUB_TEXT = "android.subText"; 1094 1095 /** 1096 * {@link #extras} key: this is the remote input history, as supplied to 1097 * {@link Builder#setRemoteInputHistory(CharSequence[])}. 1098 * 1099 * Apps can fill this through {@link Builder#setRemoteInputHistory(CharSequence[])} 1100 * with the most recent inputs that have been sent through a {@link RemoteInput} of this 1101 * Notification and are expected to clear it once the it is no longer relevant (e.g. for chat 1102 * notifications once the other party has responded). 1103 * 1104 * The extra with this key is of type CharSequence[] and contains the most recent entry at 1105 * the 0 index, the second most recent at the 1 index, etc. 1106 * 1107 * @see Builder#setRemoteInputHistory(CharSequence[]) 1108 */ 1109 public static final String EXTRA_REMOTE_INPUT_HISTORY = "android.remoteInputHistory"; 1110 1111 1112 /** 1113 * {@link #extras} key: this is a remote input history which can include media messages 1114 * in addition to text, as supplied to 1115 * {@link Builder#setRemoteInputHistory(RemoteInputHistoryItem[])} or 1116 * {@link Builder#setRemoteInputHistory(CharSequence[])}. 1117 * 1118 * SystemUI can populate this through 1119 * {@link Builder#setRemoteInputHistory(RemoteInputHistoryItem[])} with the most recent inputs 1120 * that have been sent through a {@link RemoteInput} of this Notification. These items can 1121 * represent either media content (specified by a URI and a MIME type) or a text message 1122 * (described by a CharSequence). 1123 * 1124 * To maintain compatibility, this can also be set by apps with 1125 * {@link Builder#setRemoteInputHistory(CharSequence[])}, which will create a 1126 * {@link RemoteInputHistoryItem} for each of the provided text-only messages. 1127 * 1128 * The extra with this key is of type {@link RemoteInputHistoryItem[]} and contains the most 1129 * recent entry at the 0 index, the second most recent at the 1 index, etc. 1130 * 1131 * @see Builder#setRemoteInputHistory(RemoteInputHistoryItem[]) 1132 * @hide 1133 */ 1134 public static final String EXTRA_REMOTE_INPUT_HISTORY_ITEMS = "android.remoteInputHistoryItems"; 1135 1136 /** 1137 * {@link #extras} key: boolean as supplied to 1138 * {@link Builder#setShowRemoteInputSpinner(boolean)}. 1139 * 1140 * If set to true, then the view displaying the remote input history from 1141 * {@link Builder#setRemoteInputHistory(CharSequence[])} will have a progress spinner. 1142 * 1143 * @see Builder#setShowRemoteInputSpinner(boolean) 1144 * @hide 1145 */ 1146 public static final String EXTRA_SHOW_REMOTE_INPUT_SPINNER = "android.remoteInputSpinner"; 1147 1148 /** 1149 * {@link #extras} key: boolean as supplied to 1150 * {@link Builder#setHideSmartReplies(boolean)}. 1151 * 1152 * If set to true, then any smart reply buttons will be hidden. 1153 * 1154 * @see Builder#setHideSmartReplies(boolean) 1155 * @hide 1156 */ 1157 public static final String EXTRA_HIDE_SMART_REPLIES = "android.hideSmartReplies"; 1158 1159 /** 1160 * {@link #extras} key: this is a small piece of additional text as supplied to 1161 * {@link Builder#setContentInfo(CharSequence)}. 1162 */ 1163 public static final String EXTRA_INFO_TEXT = "android.infoText"; 1164 1165 /** 1166 * {@link #extras} key: this is a line of summary information intended to be shown 1167 * alongside expanded notifications, as supplied to (e.g.) 1168 * {@link BigTextStyle#setSummaryText(CharSequence)}. 1169 */ 1170 public static final String EXTRA_SUMMARY_TEXT = "android.summaryText"; 1171 1172 /** 1173 * {@link #extras} key: this is the longer text shown in the big form of a 1174 * {@link BigTextStyle} notification, as supplied to 1175 * {@link BigTextStyle#bigText(CharSequence)}. 1176 */ 1177 public static final String EXTRA_BIG_TEXT = "android.bigText"; 1178 1179 /** 1180 * {@link #extras} key: this is the resource ID of the notification's main small icon, as 1181 * supplied to {@link Builder#setSmallIcon(int)}. 1182 * 1183 * @deprecated Use {@link #getSmallIcon()}, which supports a wider variety of icon sources. 1184 */ 1185 @Deprecated 1186 public static final String EXTRA_SMALL_ICON = "android.icon"; 1187 1188 /** 1189 * {@link #extras} key: this is a bitmap to be used instead of the small icon when showing the 1190 * notification payload, as 1191 * supplied to {@link Builder#setLargeIcon(android.graphics.Bitmap)}. 1192 * 1193 * @deprecated Use {@link #getLargeIcon()}, which supports a wider variety of icon sources. 1194 */ 1195 @Deprecated 1196 public static final String EXTRA_LARGE_ICON = "android.largeIcon"; 1197 1198 /** 1199 * {@link #extras} key: this is a bitmap to be used instead of the one from 1200 * {@link Builder#setLargeIcon(android.graphics.Bitmap)} when the notification is 1201 * shown in its expanded form, as supplied to 1202 * {@link BigPictureStyle#bigLargeIcon(android.graphics.Bitmap)}. 1203 */ 1204 public static final String EXTRA_LARGE_ICON_BIG = EXTRA_LARGE_ICON + ".big"; 1205 1206 /** 1207 * {@link #extras} key: this is the progress value supplied to 1208 * {@link Builder#setProgress(int, int, boolean)}. 1209 */ 1210 public static final String EXTRA_PROGRESS = "android.progress"; 1211 1212 /** 1213 * {@link #extras} key: this is the maximum value supplied to 1214 * {@link Builder#setProgress(int, int, boolean)}. 1215 */ 1216 public static final String EXTRA_PROGRESS_MAX = "android.progressMax"; 1217 1218 /** 1219 * {@link #extras} key: whether the progress bar is indeterminate, supplied to 1220 * {@link Builder#setProgress(int, int, boolean)}. 1221 */ 1222 public static final String EXTRA_PROGRESS_INDETERMINATE = "android.progressIndeterminate"; 1223 1224 /** 1225 * {@link #extras} key: whether {@link #when} should be shown as a count-up timer (specifically 1226 * a {@link android.widget.Chronometer}) instead of a timestamp, as supplied to 1227 * {@link Builder#setUsesChronometer(boolean)}. 1228 */ 1229 public static final String EXTRA_SHOW_CHRONOMETER = "android.showChronometer"; 1230 1231 /** 1232 * {@link #extras} key: whether the chronometer set on the notification should count down 1233 * instead of counting up. Is only relevant if key {@link #EXTRA_SHOW_CHRONOMETER} is present. 1234 * This extra is a boolean. The default is false. 1235 */ 1236 public static final String EXTRA_CHRONOMETER_COUNT_DOWN = "android.chronometerCountDown"; 1237 1238 /** 1239 * {@link #extras} key: whether {@link #when} should be shown, 1240 * as supplied to {@link Builder#setShowWhen(boolean)}. 1241 */ 1242 public static final String EXTRA_SHOW_WHEN = "android.showWhen"; 1243 1244 /** 1245 * {@link #extras} key: this is a bitmap to be shown in {@link BigPictureStyle} expanded 1246 * notifications, supplied to {@link BigPictureStyle#bigPicture(android.graphics.Bitmap)}. 1247 */ 1248 public static final String EXTRA_PICTURE = "android.picture"; 1249 1250 /** 1251 * {@link #extras} key: this is an {@link Icon} of an image to be 1252 * shown in {@link BigPictureStyle} expanded notifications, supplied to 1253 * {@link BigPictureStyle#bigPicture(Icon)}. 1254 */ 1255 public static final String EXTRA_PICTURE_ICON = "android.pictureIcon"; 1256 1257 /** 1258 * {@link #extras} key: this is a content description of the big picture supplied from 1259 * {@link BigPictureStyle#bigPicture(Bitmap)}, supplied to 1260 * {@link BigPictureStyle#setContentDescription(CharSequence)}. 1261 */ 1262 public static final String EXTRA_PICTURE_CONTENT_DESCRIPTION = 1263 "android.pictureContentDescription"; 1264 1265 /** 1266 * {@link #extras} key: this is a boolean to indicate that the 1267 * {@link BigPictureStyle#bigPicture(Bitmap) big picture} is to be shown in the collapsed state 1268 * of a {@link BigPictureStyle} notification. This will replace a 1269 * {@link Builder#setLargeIcon(Icon) large icon} in that state if one was provided. 1270 */ 1271 public static final String EXTRA_SHOW_BIG_PICTURE_WHEN_COLLAPSED = 1272 "android.showBigPictureWhenCollapsed"; 1273 1274 /** 1275 * {@link #extras} key: An array of CharSequences to show in {@link InboxStyle} expanded 1276 * notifications, each of which was supplied to {@link InboxStyle#addLine(CharSequence)}. 1277 */ 1278 public static final String EXTRA_TEXT_LINES = "android.textLines"; 1279 1280 /** 1281 * {@link #extras} key: A string representing the name of the specific 1282 * {@link android.app.Notification.Style} used to create this notification. 1283 */ 1284 public static final String EXTRA_TEMPLATE = "android.template"; 1285 1286 /** 1287 * {@link #extras} key: A String array containing the people that this notification relates to, 1288 * each of which was supplied to {@link Builder#addPerson(String)}. 1289 * 1290 * @deprecated the actual objects are now in {@link #EXTRA_PEOPLE_LIST} 1291 */ 1292 public static final String EXTRA_PEOPLE = "android.people"; 1293 1294 /** 1295 * {@link #extras} key: An arrayList of {@link Person} objects containing the people that 1296 * this notification relates to. 1297 */ 1298 public static final String EXTRA_PEOPLE_LIST = "android.people.list"; 1299 1300 /** 1301 * Allow certain system-generated notifications to appear before the device is provisioned. 1302 * Only available to notifications coming from the android package. 1303 * @hide 1304 */ 1305 @SystemApi 1306 @RequiresPermission(android.Manifest.permission.NOTIFICATION_DURING_SETUP) 1307 public static final String EXTRA_ALLOW_DURING_SETUP = "android.allowDuringSetup"; 1308 1309 /** 1310 * {@link #extras} key: 1311 * flat {@link String} representation of a {@link android.content.ContentUris content URI} 1312 * pointing to an image that can be displayed in the background when the notification is 1313 * selected. Used on television platforms. The URI must point to an image stream suitable for 1314 * passing into {@link android.graphics.BitmapFactory#decodeStream(java.io.InputStream) 1315 * BitmapFactory.decodeStream}; all other content types will be ignored. 1316 */ 1317 public static final String EXTRA_BACKGROUND_IMAGE_URI = "android.backgroundImageUri"; 1318 1319 /** 1320 * {@link #extras} key: A 1321 * {@link android.media.session.MediaSession.Token} associated with a 1322 * {@link android.app.Notification.MediaStyle} notification. 1323 */ 1324 public static final String EXTRA_MEDIA_SESSION = "android.mediaSession"; 1325 1326 /** 1327 * {@link #extras} key: the indices of actions to be shown in the compact view, 1328 * as supplied to (e.g.) {@link MediaStyle#setShowActionsInCompactView(int...)}. 1329 */ 1330 public static final String EXTRA_COMPACT_ACTIONS = "android.compactActions"; 1331 1332 /** 1333 * {@link #extras} key: the username to be displayed for all messages sent by the user including 1334 * direct replies 1335 * {@link android.app.Notification.MessagingStyle} notification. This extra is a 1336 * {@link CharSequence} 1337 * 1338 * @deprecated use {@link #EXTRA_MESSAGING_PERSON} 1339 */ 1340 public static final String EXTRA_SELF_DISPLAY_NAME = "android.selfDisplayName"; 1341 1342 /** 1343 * {@link #extras} key: the person to be displayed for all messages sent by the user including 1344 * direct replies 1345 * {@link android.app.Notification.MessagingStyle} notification. This extra is a 1346 * {@link Person} 1347 */ 1348 public static final String EXTRA_MESSAGING_PERSON = "android.messagingUser"; 1349 1350 /** 1351 * {@link #extras} key: a {@link CharSequence} to be displayed as the title to a conversation 1352 * represented by a {@link android.app.Notification.MessagingStyle} 1353 */ 1354 public static final String EXTRA_CONVERSATION_TITLE = "android.conversationTitle"; 1355 1356 /** @hide */ 1357 public static final String EXTRA_CONVERSATION_ICON = "android.conversationIcon"; 1358 1359 /** @hide */ 1360 public static final String EXTRA_CONVERSATION_UNREAD_MESSAGE_COUNT = 1361 "android.conversationUnreadMessageCount"; 1362 1363 /** 1364 * {@link #extras} key: an array of {@link android.app.Notification.MessagingStyle.Message} 1365 * bundles provided by a 1366 * {@link android.app.Notification.MessagingStyle} notification. This extra is a parcelable 1367 * array of bundles. 1368 */ 1369 public static final String EXTRA_MESSAGES = "android.messages"; 1370 1371 /** 1372 * {@link #extras} key: an array of 1373 * {@link android.app.Notification.MessagingStyle#addHistoricMessage historic} 1374 * {@link android.app.Notification.MessagingStyle.Message} bundles provided by a 1375 * {@link android.app.Notification.MessagingStyle} notification. This extra is a parcelable 1376 * array of bundles. 1377 */ 1378 public static final String EXTRA_HISTORIC_MESSAGES = "android.messages.historic"; 1379 1380 /** 1381 * {@link #extras} key: whether the {@link android.app.Notification.MessagingStyle} notification 1382 * represents a group conversation. 1383 */ 1384 public static final String EXTRA_IS_GROUP_CONVERSATION = "android.isGroupConversation"; 1385 1386 /** 1387 * {@link #extras} key: the type of call represented by the 1388 * {@link android.app.Notification.CallStyle} notification. This extra is an int. 1389 * @hide 1390 */ 1391 public static final String EXTRA_CALL_TYPE = "android.callType"; 1392 1393 /** 1394 * {@link #extras} key: whether the {@link android.app.Notification.CallStyle} notification 1395 * is for a call that will activate video when answered. This extra is a boolean. 1396 */ 1397 public static final String EXTRA_CALL_IS_VIDEO = "android.callIsVideo"; 1398 1399 /** 1400 * {@link #extras} key: the person to be displayed as calling for the 1401 * {@link android.app.Notification.CallStyle} notification. This extra is a {@link Person}. 1402 */ 1403 public static final String EXTRA_CALL_PERSON = "android.callPerson"; 1404 1405 /** 1406 * {@link #extras} key: the icon to be displayed as a verification status of the caller on a 1407 * {@link android.app.Notification.CallStyle} notification. This extra is an {@link Icon}. 1408 */ 1409 public static final String EXTRA_VERIFICATION_ICON = "android.verificationIcon"; 1410 1411 /** 1412 * {@link #extras} key: the text to be displayed as a verification status of the caller on a 1413 * {@link android.app.Notification.CallStyle} notification. This extra is a 1414 * {@link CharSequence}. 1415 */ 1416 public static final String EXTRA_VERIFICATION_TEXT = "android.verificationText"; 1417 1418 /** 1419 * {@link #extras} key: the intent to be sent when the users answers a 1420 * {@link android.app.Notification.CallStyle} notification. This extra is a 1421 * {@link PendingIntent}. 1422 */ 1423 public static final String EXTRA_ANSWER_INTENT = "android.answerIntent"; 1424 1425 /** 1426 * {@link #extras} key: the intent to be sent when the users declines a 1427 * {@link android.app.Notification.CallStyle} notification. This extra is a 1428 * {@link PendingIntent}. 1429 */ 1430 public static final String EXTRA_DECLINE_INTENT = "android.declineIntent"; 1431 1432 /** 1433 * {@link #extras} key: the intent to be sent when the users hangs up a 1434 * {@link android.app.Notification.CallStyle} notification. This extra is a 1435 * {@link PendingIntent}. 1436 */ 1437 public static final String EXTRA_HANG_UP_INTENT = "android.hangUpIntent"; 1438 1439 /** 1440 * {@link #extras} key: the color used as a hint for the Answer action button of a 1441 * {@link android.app.Notification.CallStyle} notification. This extra is a {@link ColorInt}. 1442 */ 1443 public static final String EXTRA_ANSWER_COLOR = "android.answerColor"; 1444 1445 /** 1446 * {@link #extras} key: the color used as a hint for the Decline or Hang Up action button of a 1447 * {@link android.app.Notification.CallStyle} notification. This extra is a {@link ColorInt}. 1448 */ 1449 public static final String EXTRA_DECLINE_COLOR = "android.declineColor"; 1450 1451 /** 1452 * {@link #extras} key: whether the notification should be colorized as 1453 * supplied to {@link Builder#setColorized(boolean)}. 1454 */ 1455 public static final String EXTRA_COLORIZED = "android.colorized"; 1456 1457 /** 1458 * @hide 1459 */ 1460 public static final String EXTRA_BUILDER_APPLICATION_INFO = "android.appInfo"; 1461 1462 /** 1463 * @hide 1464 */ 1465 public static final String EXTRA_CONTAINS_CUSTOM_VIEW = "android.contains.customView"; 1466 1467 /** 1468 * @hide 1469 */ 1470 public static final String EXTRA_REDUCED_IMAGES = "android.reduced.images"; 1471 1472 /** 1473 * {@link #extras} key: the audio contents of this notification. 1474 * 1475 * This is for use when rendering the notification on an audio-focused interface; 1476 * the audio contents are a complete sound sample that contains the contents/body of the 1477 * notification. This may be used in substitute of a Text-to-Speech reading of the 1478 * notification. For example if the notification represents a voice message this should point 1479 * to the audio of that message. 1480 * 1481 * The data stored under this key should be a String representation of a Uri that contains the 1482 * audio contents in one of the following formats: WAV, PCM 16-bit, AMR-WB. 1483 * 1484 * This extra is unnecessary if you are using {@code MessagingStyle} since each {@code Message} 1485 * has a field for holding data URI. That field can be used for audio. 1486 * See {@code Message#setData}. 1487 * 1488 * Example usage: 1489 * <pre> 1490 * {@code 1491 * Notification.Builder myBuilder = (build your Notification as normal); 1492 * myBuilder.getExtras().putString(EXTRA_AUDIO_CONTENTS_URI, myAudioUri.toString()); 1493 * } 1494 * </pre> 1495 */ 1496 public static final String EXTRA_AUDIO_CONTENTS_URI = "android.audioContents"; 1497 1498 /** @hide */ 1499 @SystemApi 1500 @RequiresPermission(android.Manifest.permission.SUBSTITUTE_NOTIFICATION_APP_NAME) 1501 public static final String EXTRA_SUBSTITUTE_APP_NAME = "android.substName"; 1502 1503 /** 1504 * This is set on the notifications shown by system_server about apps running foreground 1505 * services. It indicates that the notification should be shown 1506 * only if any of the given apps do not already have a properly tagged 1507 * {@link #FLAG_FOREGROUND_SERVICE} notification currently visible to the user. 1508 * This is a string array of all package names of the apps. 1509 * @hide 1510 */ 1511 public static final String EXTRA_FOREGROUND_APPS = "android.foregroundApps"; 1512 1513 @UnsupportedAppUsage 1514 private Icon mSmallIcon; 1515 @UnsupportedAppUsage 1516 private Icon mLargeIcon; 1517 1518 @UnsupportedAppUsage 1519 private String mChannelId; 1520 private long mTimeout; 1521 1522 private String mShortcutId; 1523 private LocusId mLocusId; 1524 private CharSequence mSettingsText; 1525 1526 private BubbleMetadata mBubbleMetadata; 1527 1528 /** @hide */ 1529 @IntDef(prefix = { "GROUP_ALERT_" }, value = { 1530 GROUP_ALERT_ALL, GROUP_ALERT_CHILDREN, GROUP_ALERT_SUMMARY 1531 }) 1532 @Retention(RetentionPolicy.SOURCE) 1533 public @interface GroupAlertBehavior {} 1534 1535 /** 1536 * Constant for {@link Builder#setGroupAlertBehavior(int)}, meaning that all notifications in a 1537 * group with sound or vibration ought to make sound or vibrate (respectively), so this 1538 * notification will not be muted when it is in a group. 1539 */ 1540 public static final int GROUP_ALERT_ALL = 0; 1541 1542 /** 1543 * Constant for {@link Builder#setGroupAlertBehavior(int)}, meaning that all children 1544 * notification in a group should be silenced (no sound or vibration) even if they are posted 1545 * to a {@link NotificationChannel} that has sound and/or vibration. Use this constant to 1546 * mute this notification if this notification is a group child. This must be applied to all 1547 * children notifications you want to mute. 1548 * 1549 * <p> For example, you might want to use this constant if you post a number of children 1550 * notifications at once (say, after a periodic sync), and only need to notify the user 1551 * audibly once. 1552 */ 1553 public static final int GROUP_ALERT_SUMMARY = 1; 1554 1555 /** 1556 * Constant for {@link Builder#setGroupAlertBehavior(int)}, meaning that the summary 1557 * notification in a group should be silenced (no sound or vibration) even if they are 1558 * posted to a {@link NotificationChannel} that has sound and/or vibration. Use this constant 1559 * to mute this notification if this notification is a group summary. 1560 * 1561 * <p>For example, you might want to use this constant if only the children notifications 1562 * in your group have content and the summary is only used to visually group notifications 1563 * rather than to alert the user that new information is available. 1564 */ 1565 public static final int GROUP_ALERT_CHILDREN = 2; 1566 1567 private int mGroupAlertBehavior = GROUP_ALERT_ALL; 1568 1569 /** 1570 * If this notification is being shown as a badge, always show as a number. 1571 */ 1572 public static final int BADGE_ICON_NONE = 0; 1573 1574 /** 1575 * If this notification is being shown as a badge, use the {@link #getSmallIcon()} to 1576 * represent this notification. 1577 */ 1578 public static final int BADGE_ICON_SMALL = 1; 1579 1580 /** 1581 * If this notification is being shown as a badge, use the {@link #getLargeIcon()} to 1582 * represent this notification. 1583 */ 1584 public static final int BADGE_ICON_LARGE = 2; 1585 private int mBadgeIcon = BADGE_ICON_NONE; 1586 1587 /** 1588 * Determines whether the platform can generate contextual actions for a notification. 1589 */ 1590 private boolean mAllowSystemGeneratedContextualActions = true; 1591 1592 /** 1593 * Structure to encapsulate a named action that can be shown as part of this notification. 1594 * It must include an icon, a label, and a {@link PendingIntent} to be fired when the action is 1595 * selected by the user. 1596 * <p> 1597 * Apps should use {@link Notification.Builder#addAction(int, CharSequence, PendingIntent)} 1598 * or {@link Notification.Builder#addAction(Notification.Action)} 1599 * to attach actions. 1600 * <p> 1601 * As of Android {@link android.os.Build.VERSION_CODES#S}, apps targeting API level {@link 1602 * android.os.Build.VERSION_CODES#S} or higher won't be able to start activities while 1603 * processing broadcast receivers or services in response to notification action clicks. To 1604 * launch an activity in those cases, provide a {@link PendingIntent} for the activity itself. 1605 */ 1606 public static class Action implements Parcelable { 1607 /** 1608 * {@link #extras} key: Keys to a {@link Parcelable} {@link ArrayList} of 1609 * {@link RemoteInput}s. 1610 * 1611 * This is intended for {@link RemoteInput}s that only accept data, meaning 1612 * {@link RemoteInput#getAllowFreeFormInput} is false, {@link RemoteInput#getChoices} 1613 * is null or empty, and {@link RemoteInput#getAllowedDataTypes} is non-null and not 1614 * empty. These {@link RemoteInput}s will be ignored by devices that do not 1615 * support non-text-based {@link RemoteInput}s. See {@link Builder#build}. 1616 * 1617 * You can test if a RemoteInput matches these constraints using 1618 * {@link RemoteInput#isDataOnly}. 1619 */ 1620 private static final String EXTRA_DATA_ONLY_INPUTS = "android.extra.DATA_ONLY_INPUTS"; 1621 1622 /** 1623 * {@link }: No semantic action defined. 1624 */ 1625 public static final int SEMANTIC_ACTION_NONE = 0; 1626 1627 /** 1628 * {@code SemanticAction}: Reply to a conversation, chat, group, or wherever replies 1629 * may be appropriate. 1630 */ 1631 public static final int SEMANTIC_ACTION_REPLY = 1; 1632 1633 /** 1634 * {@code SemanticAction}: Mark content as read. 1635 */ 1636 public static final int SEMANTIC_ACTION_MARK_AS_READ = 2; 1637 1638 /** 1639 * {@code SemanticAction}: Mark content as unread. 1640 */ 1641 public static final int SEMANTIC_ACTION_MARK_AS_UNREAD = 3; 1642 1643 /** 1644 * {@code SemanticAction}: Delete the content associated with the notification. This 1645 * could mean deleting an email, message, etc. 1646 */ 1647 public static final int SEMANTIC_ACTION_DELETE = 4; 1648 1649 /** 1650 * {@code SemanticAction}: Archive the content associated with the notification. This 1651 * could mean archiving an email, message, etc. 1652 */ 1653 public static final int SEMANTIC_ACTION_ARCHIVE = 5; 1654 1655 /** 1656 * {@code SemanticAction}: Mute the content associated with the notification. This could 1657 * mean silencing a conversation or currently playing media. 1658 */ 1659 public static final int SEMANTIC_ACTION_MUTE = 6; 1660 1661 /** 1662 * {@code SemanticAction}: Unmute the content associated with the notification. This could 1663 * mean un-silencing a conversation or currently playing media. 1664 */ 1665 public static final int SEMANTIC_ACTION_UNMUTE = 7; 1666 1667 /** 1668 * {@code SemanticAction}: Mark content with a thumbs up. 1669 */ 1670 public static final int SEMANTIC_ACTION_THUMBS_UP = 8; 1671 1672 /** 1673 * {@code SemanticAction}: Mark content with a thumbs down. 1674 */ 1675 public static final int SEMANTIC_ACTION_THUMBS_DOWN = 9; 1676 1677 /** 1678 * {@code SemanticAction}: Call a contact, group, etc. 1679 */ 1680 public static final int SEMANTIC_ACTION_CALL = 10; 1681 1682 /** 1683 * {@code SemanticAction}: Mark the conversation associated with the notification as a 1684 * priority. Note that this is only for use by the notification assistant services. The 1685 * type will be ignored for actions an app adds to its own notifications. 1686 * @hide 1687 */ 1688 @SystemApi 1689 public static final int SEMANTIC_ACTION_MARK_CONVERSATION_AS_PRIORITY = 11; 1690 1691 /** 1692 * {@code SemanticAction}: Mark content as a potential phishing attempt. 1693 * Note that this is only for use by the notification assistant services. The type will 1694 * be ignored for actions an app adds to its own notifications. 1695 * @hide 1696 */ 1697 @SystemApi 1698 public static final int SEMANTIC_ACTION_CONVERSATION_IS_PHISHING = 12; 1699 1700 private final Bundle mExtras; 1701 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) 1702 private Icon mIcon; 1703 private final RemoteInput[] mRemoteInputs; 1704 private boolean mAllowGeneratedReplies = true; 1705 private final @SemanticAction int mSemanticAction; 1706 private final boolean mIsContextual; 1707 private boolean mAuthenticationRequired; 1708 1709 /** 1710 * Small icon representing the action. 1711 * 1712 * @deprecated Use {@link Action#getIcon()} instead. 1713 */ 1714 @Deprecated 1715 public int icon; 1716 1717 /** 1718 * Title of the action. 1719 */ 1720 public CharSequence title; 1721 1722 /** 1723 * Intent to send when the user invokes this action. May be null, in which case the action 1724 * may be rendered in a disabled presentation by the system UI. 1725 */ 1726 public PendingIntent actionIntent; 1727 Action(Parcel in)1728 private Action(Parcel in) { 1729 if (in.readInt() != 0) { 1730 mIcon = Icon.CREATOR.createFromParcel(in); 1731 if (mIcon.getType() == Icon.TYPE_RESOURCE) { 1732 icon = mIcon.getResId(); 1733 } 1734 } 1735 title = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); 1736 if (in.readInt() == 1) { 1737 actionIntent = PendingIntent.CREATOR.createFromParcel(in); 1738 } 1739 mExtras = Bundle.setDefusable(in.readBundle(), true); 1740 mRemoteInputs = in.createTypedArray(RemoteInput.CREATOR); 1741 mAllowGeneratedReplies = in.readInt() == 1; 1742 mSemanticAction = in.readInt(); 1743 mIsContextual = in.readInt() == 1; 1744 mAuthenticationRequired = in.readInt() == 1; 1745 } 1746 1747 /** 1748 * @deprecated Use {@link android.app.Notification.Action.Builder}. 1749 */ 1750 @Deprecated Action(int icon, CharSequence title, PendingIntent intent)1751 public Action(int icon, CharSequence title, PendingIntent intent) { 1752 this(Icon.createWithResource("", icon), title, intent, new Bundle(), null, true, 1753 SEMANTIC_ACTION_NONE, false /* isContextual */, false /* requireAuth */); 1754 } 1755 1756 /** Keep in sync with {@link Notification.Action.Builder#Builder(Action)}! */ Action(Icon icon, CharSequence title, PendingIntent intent, Bundle extras, RemoteInput[] remoteInputs, boolean allowGeneratedReplies, @SemanticAction int semanticAction, boolean isContextual, boolean requireAuth)1757 private Action(Icon icon, CharSequence title, PendingIntent intent, Bundle extras, 1758 RemoteInput[] remoteInputs, boolean allowGeneratedReplies, 1759 @SemanticAction int semanticAction, boolean isContextual, 1760 boolean requireAuth) { 1761 this.mIcon = icon; 1762 if (icon != null && icon.getType() == Icon.TYPE_RESOURCE) { 1763 this.icon = icon.getResId(); 1764 } 1765 this.title = title; 1766 this.actionIntent = intent; 1767 this.mExtras = extras != null ? extras : new Bundle(); 1768 this.mRemoteInputs = remoteInputs; 1769 this.mAllowGeneratedReplies = allowGeneratedReplies; 1770 this.mSemanticAction = semanticAction; 1771 this.mIsContextual = isContextual; 1772 this.mAuthenticationRequired = requireAuth; 1773 } 1774 1775 /** 1776 * Return an icon representing the action. 1777 */ getIcon()1778 public Icon getIcon() { 1779 if (mIcon == null && icon != 0) { 1780 // you snuck an icon in here without using the builder; let's try to keep it 1781 mIcon = Icon.createWithResource("", icon); 1782 } 1783 return mIcon; 1784 } 1785 1786 /** 1787 * Get additional metadata carried around with this Action. 1788 */ getExtras()1789 public Bundle getExtras() { 1790 return mExtras; 1791 } 1792 1793 /** 1794 * Return whether the platform should automatically generate possible replies for this 1795 * {@link Action} 1796 */ getAllowGeneratedReplies()1797 public boolean getAllowGeneratedReplies() { 1798 return mAllowGeneratedReplies; 1799 } 1800 1801 /** 1802 * Get the list of inputs to be collected from the user when this action is sent. 1803 * May return null if no remote inputs were added. Only returns inputs which accept 1804 * a text input. For inputs which only accept data use {@link #getDataOnlyRemoteInputs}. 1805 */ getRemoteInputs()1806 public RemoteInput[] getRemoteInputs() { 1807 return mRemoteInputs; 1808 } 1809 1810 /** 1811 * Returns the {@code SemanticAction} associated with this {@link Action}. A 1812 * {@code SemanticAction} denotes what an {@link Action}'s {@link PendingIntent} will do 1813 * (eg. reply, mark as read, delete, etc). 1814 */ getSemanticAction()1815 public @SemanticAction int getSemanticAction() { 1816 return mSemanticAction; 1817 } 1818 1819 /** 1820 * Returns whether this is a contextual Action, i.e. whether the action is dependent on the 1821 * notification message body. An example of a contextual action could be an action opening a 1822 * map application with an address shown in the notification. 1823 */ isContextual()1824 public boolean isContextual() { 1825 return mIsContextual; 1826 } 1827 1828 /** 1829 * Get the list of inputs to be collected from the user that ONLY accept data when this 1830 * action is sent. These remote inputs are guaranteed to return true on a call to 1831 * {@link RemoteInput#isDataOnly}. 1832 * 1833 * Returns null if there are no data-only remote inputs. 1834 * 1835 * This method exists so that legacy RemoteInput collectors that pre-date the addition 1836 * of non-textual RemoteInputs do not access these remote inputs. 1837 */ getDataOnlyRemoteInputs()1838 public RemoteInput[] getDataOnlyRemoteInputs() { 1839 return getParcelableArrayFromBundle(mExtras, EXTRA_DATA_ONLY_INPUTS, RemoteInput.class); 1840 } 1841 1842 /** 1843 * Returns whether the OS should only send this action's {@link PendingIntent} on an 1844 * unlocked device. 1845 * 1846 * If the device is locked when the action is invoked, the OS should show the keyguard and 1847 * require successful authentication before invoking the intent. 1848 */ isAuthenticationRequired()1849 public boolean isAuthenticationRequired() { 1850 return mAuthenticationRequired; 1851 } 1852 1853 /** 1854 * Builder class for {@link Action} objects. 1855 */ 1856 public static final class Builder { 1857 @Nullable private final Icon mIcon; 1858 @Nullable private final CharSequence mTitle; 1859 @Nullable private final PendingIntent mIntent; 1860 private boolean mAllowGeneratedReplies = true; 1861 @NonNull private final Bundle mExtras; 1862 @Nullable private ArrayList<RemoteInput> mRemoteInputs; 1863 private @SemanticAction int mSemanticAction; 1864 private boolean mIsContextual; 1865 private boolean mAuthenticationRequired; 1866 1867 /** 1868 * Construct a new builder for {@link Action} object. 1869 * @param icon icon to show for this action 1870 * @param title the title of the action 1871 * @param intent the {@link PendingIntent} to fire when users trigger this action 1872 */ 1873 @Deprecated Builder(int icon, CharSequence title, PendingIntent intent)1874 public Builder(int icon, CharSequence title, PendingIntent intent) { 1875 this(Icon.createWithResource("", icon), title, intent); 1876 } 1877 1878 /** 1879 * Construct a new builder for {@link Action} object. 1880 * 1881 * <p>As of Android {@link android.os.Build.VERSION_CODES#S}, apps targeting API level 1882 * {@link android.os.Build.VERSION_CODES#S} or higher won't be able to start activities 1883 * while processing broadcast receivers or services in response to notification action 1884 * clicks. To launch an activity in those cases, provide a {@link PendingIntent} for the 1885 * activity itself. 1886 * 1887 * @param icon icon to show for this action 1888 * @param title the title of the action 1889 * @param intent the {@link PendingIntent} to fire when users trigger this action 1890 */ Builder(Icon icon, CharSequence title, PendingIntent intent)1891 public Builder(Icon icon, CharSequence title, PendingIntent intent) { 1892 this(icon, title, intent, new Bundle(), null, true, SEMANTIC_ACTION_NONE, false); 1893 } 1894 1895 /** 1896 * Construct a new builder for {@link Action} object using the fields from an 1897 * {@link Action}. 1898 * @param action the action to read fields from. 1899 */ Builder(Action action)1900 public Builder(Action action) { 1901 this(action.getIcon(), action.title, action.actionIntent, 1902 new Bundle(action.mExtras), action.getRemoteInputs(), 1903 action.getAllowGeneratedReplies(), action.getSemanticAction(), 1904 action.isAuthenticationRequired()); 1905 } 1906 Builder(@ullable Icon icon, @Nullable CharSequence title, @Nullable PendingIntent intent, @NonNull Bundle extras, @Nullable RemoteInput[] remoteInputs, boolean allowGeneratedReplies, @SemanticAction int semanticAction, boolean authRequired)1907 private Builder(@Nullable Icon icon, @Nullable CharSequence title, 1908 @Nullable PendingIntent intent, @NonNull Bundle extras, 1909 @Nullable RemoteInput[] remoteInputs, boolean allowGeneratedReplies, 1910 @SemanticAction int semanticAction, boolean authRequired) { 1911 mIcon = icon; 1912 mTitle = title; 1913 mIntent = intent; 1914 mExtras = extras; 1915 if (remoteInputs != null) { 1916 mRemoteInputs = new ArrayList<>(remoteInputs.length); 1917 Collections.addAll(mRemoteInputs, remoteInputs); 1918 } 1919 mAllowGeneratedReplies = allowGeneratedReplies; 1920 mSemanticAction = semanticAction; 1921 mAuthenticationRequired = authRequired; 1922 } 1923 1924 /** 1925 * Merge additional metadata into this builder. 1926 * 1927 * <p>Values within the Bundle will replace existing extras values in this Builder. 1928 * 1929 * @see Notification.Action#extras 1930 */ 1931 @NonNull addExtras(Bundle extras)1932 public Builder addExtras(Bundle extras) { 1933 if (extras != null) { 1934 mExtras.putAll(extras); 1935 } 1936 return this; 1937 } 1938 1939 /** 1940 * Get the metadata Bundle used by this Builder. 1941 * 1942 * <p>The returned Bundle is shared with this Builder. 1943 */ 1944 @NonNull getExtras()1945 public Bundle getExtras() { 1946 return mExtras; 1947 } 1948 1949 /** 1950 * Add an input to be collected from the user when this action is sent. 1951 * Response values can be retrieved from the fired intent by using the 1952 * {@link RemoteInput#getResultsFromIntent} function. 1953 * @param remoteInput a {@link RemoteInput} to add to the action 1954 * @return this object for method chaining 1955 */ 1956 @NonNull addRemoteInput(RemoteInput remoteInput)1957 public Builder addRemoteInput(RemoteInput remoteInput) { 1958 if (mRemoteInputs == null) { 1959 mRemoteInputs = new ArrayList<RemoteInput>(); 1960 } 1961 mRemoteInputs.add(remoteInput); 1962 return this; 1963 } 1964 1965 /** 1966 * Set whether the platform should automatically generate possible replies to add to 1967 * {@link RemoteInput#getChoices()}. If the {@link Action} doesn't have a 1968 * {@link RemoteInput}, this has no effect. 1969 * @param allowGeneratedReplies {@code true} to allow generated replies, {@code false} 1970 * otherwise 1971 * @return this object for method chaining 1972 * The default value is {@code true} 1973 */ 1974 @NonNull setAllowGeneratedReplies(boolean allowGeneratedReplies)1975 public Builder setAllowGeneratedReplies(boolean allowGeneratedReplies) { 1976 mAllowGeneratedReplies = allowGeneratedReplies; 1977 return this; 1978 } 1979 1980 /** 1981 * Sets the {@code SemanticAction} for this {@link Action}. A 1982 * {@code SemanticAction} denotes what an {@link Action}'s 1983 * {@link PendingIntent} will do (eg. reply, mark as read, delete, etc). 1984 * @param semanticAction a SemanticAction defined within {@link Action} with 1985 * {@code SEMANTIC_ACTION_} prefixes 1986 * @return this object for method chaining 1987 */ 1988 @NonNull setSemanticAction(@emanticAction int semanticAction)1989 public Builder setSemanticAction(@SemanticAction int semanticAction) { 1990 mSemanticAction = semanticAction; 1991 return this; 1992 } 1993 1994 /** 1995 * Sets whether this {@link Action} is a contextual action, i.e. whether the action is 1996 * dependent on the notification message body. An example of a contextual action could 1997 * be an action opening a map application with an address shown in the notification. 1998 */ 1999 @NonNull setContextual(boolean isContextual)2000 public Builder setContextual(boolean isContextual) { 2001 mIsContextual = isContextual; 2002 return this; 2003 } 2004 2005 /** 2006 * Apply an extender to this action builder. Extenders may be used to add 2007 * metadata or change options on this builder. 2008 */ 2009 @NonNull extend(Extender extender)2010 public Builder extend(Extender extender) { 2011 extender.extend(this); 2012 return this; 2013 } 2014 2015 /** 2016 * Sets whether the OS should only send this action's {@link PendingIntent} on an 2017 * unlocked device. 2018 * 2019 * If this is true and the device is locked when the action is invoked, the OS will 2020 * show the keyguard and require successful authentication before invoking the intent. 2021 * If this is false and the device is locked, the OS will decide whether authentication 2022 * should be required. 2023 */ 2024 @NonNull setAuthenticationRequired(boolean authenticationRequired)2025 public Builder setAuthenticationRequired(boolean authenticationRequired) { 2026 mAuthenticationRequired = authenticationRequired; 2027 return this; 2028 } 2029 2030 /** 2031 * Throws an NPE if we are building a contextual action missing one of the fields 2032 * necessary to display the action. 2033 */ checkContextualActionNullFields()2034 private void checkContextualActionNullFields() { 2035 if (!mIsContextual) return; 2036 2037 if (mIcon == null) { 2038 throw new NullPointerException("Contextual Actions must contain a valid icon"); 2039 } 2040 2041 if (mIntent == null) { 2042 throw new NullPointerException( 2043 "Contextual Actions must contain a valid PendingIntent"); 2044 } 2045 } 2046 2047 /** 2048 * Combine all of the options that have been set and return a new {@link Action} 2049 * object. 2050 * @return the built action 2051 */ 2052 @NonNull build()2053 public Action build() { 2054 checkContextualActionNullFields(); 2055 2056 ArrayList<RemoteInput> dataOnlyInputs = new ArrayList<>(); 2057 RemoteInput[] previousDataInputs = getParcelableArrayFromBundle( 2058 mExtras, EXTRA_DATA_ONLY_INPUTS, RemoteInput.class); 2059 if (previousDataInputs != null) { 2060 for (RemoteInput input : previousDataInputs) { 2061 dataOnlyInputs.add(input); 2062 } 2063 } 2064 List<RemoteInput> textInputs = new ArrayList<>(); 2065 if (mRemoteInputs != null) { 2066 for (RemoteInput input : mRemoteInputs) { 2067 if (input.isDataOnly()) { 2068 dataOnlyInputs.add(input); 2069 } else { 2070 textInputs.add(input); 2071 } 2072 } 2073 } 2074 if (!dataOnlyInputs.isEmpty()) { 2075 RemoteInput[] dataInputsArr = 2076 dataOnlyInputs.toArray(new RemoteInput[dataOnlyInputs.size()]); 2077 mExtras.putParcelableArray(EXTRA_DATA_ONLY_INPUTS, dataInputsArr); 2078 } 2079 RemoteInput[] textInputsArr = textInputs.isEmpty() 2080 ? null : textInputs.toArray(new RemoteInput[textInputs.size()]); 2081 return new Action(mIcon, mTitle, mIntent, mExtras, textInputsArr, 2082 mAllowGeneratedReplies, mSemanticAction, mIsContextual, 2083 mAuthenticationRequired); 2084 } 2085 } 2086 2087 @Override clone()2088 public Action clone() { 2089 return new Action( 2090 getIcon(), 2091 title, 2092 actionIntent, // safe to alias 2093 mExtras == null ? new Bundle() : new Bundle(mExtras), 2094 getRemoteInputs(), 2095 getAllowGeneratedReplies(), 2096 getSemanticAction(), 2097 isContextual(), 2098 isAuthenticationRequired()); 2099 } 2100 2101 @Override describeContents()2102 public int describeContents() { 2103 return 0; 2104 } 2105 2106 @Override writeToParcel(Parcel out, int flags)2107 public void writeToParcel(Parcel out, int flags) { 2108 final Icon ic = getIcon(); 2109 if (ic != null) { 2110 out.writeInt(1); 2111 ic.writeToParcel(out, 0); 2112 } else { 2113 out.writeInt(0); 2114 } 2115 TextUtils.writeToParcel(title, out, flags); 2116 if (actionIntent != null) { 2117 out.writeInt(1); 2118 actionIntent.writeToParcel(out, flags); 2119 } else { 2120 out.writeInt(0); 2121 } 2122 out.writeBundle(mExtras); 2123 out.writeTypedArray(mRemoteInputs, flags); 2124 out.writeInt(mAllowGeneratedReplies ? 1 : 0); 2125 out.writeInt(mSemanticAction); 2126 out.writeInt(mIsContextual ? 1 : 0); 2127 out.writeInt(mAuthenticationRequired ? 1 : 0); 2128 } 2129 2130 public static final @android.annotation.NonNull Parcelable.Creator<Action> CREATOR = 2131 new Parcelable.Creator<Action>() { 2132 public Action createFromParcel(Parcel in) { 2133 return new Action(in); 2134 } 2135 public Action[] newArray(int size) { 2136 return new Action[size]; 2137 } 2138 }; 2139 2140 /** 2141 * Extender interface for use with {@link Builder#extend}. Extenders may be used to add 2142 * metadata or change options on an action builder. 2143 */ 2144 public interface Extender { 2145 /** 2146 * Apply this extender to a notification action builder. 2147 * @param builder the builder to be modified. 2148 * @return the build object for chaining. 2149 */ extend(Builder builder)2150 public Builder extend(Builder builder); 2151 } 2152 2153 /** 2154 * Wearable extender for notification actions. To add extensions to an action, 2155 * create a new {@link android.app.Notification.Action.WearableExtender} object using 2156 * the {@code WearableExtender()} constructor and apply it to a 2157 * {@link android.app.Notification.Action.Builder} using 2158 * {@link android.app.Notification.Action.Builder#extend}. 2159 * 2160 * <pre class="prettyprint"> 2161 * Notification.Action action = new Notification.Action.Builder( 2162 * R.drawable.archive_all, "Archive all", actionIntent) 2163 * .extend(new Notification.Action.WearableExtender() 2164 * .setAvailableOffline(false)) 2165 * .build();</pre> 2166 */ 2167 public static final class WearableExtender implements Extender { 2168 /** Notification action extra which contains wearable extensions */ 2169 private static final String EXTRA_WEARABLE_EXTENSIONS = "android.wearable.EXTENSIONS"; 2170 2171 // Keys within EXTRA_WEARABLE_EXTENSIONS for wearable options. 2172 private static final String KEY_FLAGS = "flags"; 2173 private static final String KEY_IN_PROGRESS_LABEL = "inProgressLabel"; 2174 private static final String KEY_CONFIRM_LABEL = "confirmLabel"; 2175 private static final String KEY_CANCEL_LABEL = "cancelLabel"; 2176 2177 // Flags bitwise-ored to mFlags 2178 private static final int FLAG_AVAILABLE_OFFLINE = 0x1; 2179 private static final int FLAG_HINT_LAUNCHES_ACTIVITY = 1 << 1; 2180 private static final int FLAG_HINT_DISPLAY_INLINE = 1 << 2; 2181 2182 // Default value for flags integer 2183 private static final int DEFAULT_FLAGS = FLAG_AVAILABLE_OFFLINE; 2184 2185 private int mFlags = DEFAULT_FLAGS; 2186 2187 private CharSequence mInProgressLabel; 2188 private CharSequence mConfirmLabel; 2189 private CharSequence mCancelLabel; 2190 2191 /** 2192 * Create a {@link android.app.Notification.Action.WearableExtender} with default 2193 * options. 2194 */ WearableExtender()2195 public WearableExtender() { 2196 } 2197 2198 /** 2199 * Create a {@link android.app.Notification.Action.WearableExtender} by reading 2200 * wearable options present in an existing notification action. 2201 * @param action the notification action to inspect. 2202 */ WearableExtender(Action action)2203 public WearableExtender(Action action) { 2204 Bundle wearableBundle = action.getExtras().getBundle(EXTRA_WEARABLE_EXTENSIONS); 2205 if (wearableBundle != null) { 2206 mFlags = wearableBundle.getInt(KEY_FLAGS, DEFAULT_FLAGS); 2207 mInProgressLabel = wearableBundle.getCharSequence(KEY_IN_PROGRESS_LABEL); 2208 mConfirmLabel = wearableBundle.getCharSequence(KEY_CONFIRM_LABEL); 2209 mCancelLabel = wearableBundle.getCharSequence(KEY_CANCEL_LABEL); 2210 } 2211 } 2212 2213 /** 2214 * Apply wearable extensions to a notification action that is being built. This is 2215 * typically called by the {@link android.app.Notification.Action.Builder#extend} 2216 * method of {@link android.app.Notification.Action.Builder}. 2217 */ 2218 @Override extend(Action.Builder builder)2219 public Action.Builder extend(Action.Builder builder) { 2220 Bundle wearableBundle = new Bundle(); 2221 2222 if (mFlags != DEFAULT_FLAGS) { 2223 wearableBundle.putInt(KEY_FLAGS, mFlags); 2224 } 2225 if (mInProgressLabel != null) { 2226 wearableBundle.putCharSequence(KEY_IN_PROGRESS_LABEL, mInProgressLabel); 2227 } 2228 if (mConfirmLabel != null) { 2229 wearableBundle.putCharSequence(KEY_CONFIRM_LABEL, mConfirmLabel); 2230 } 2231 if (mCancelLabel != null) { 2232 wearableBundle.putCharSequence(KEY_CANCEL_LABEL, mCancelLabel); 2233 } 2234 2235 builder.getExtras().putBundle(EXTRA_WEARABLE_EXTENSIONS, wearableBundle); 2236 return builder; 2237 } 2238 2239 @Override clone()2240 public WearableExtender clone() { 2241 WearableExtender that = new WearableExtender(); 2242 that.mFlags = this.mFlags; 2243 that.mInProgressLabel = this.mInProgressLabel; 2244 that.mConfirmLabel = this.mConfirmLabel; 2245 that.mCancelLabel = this.mCancelLabel; 2246 return that; 2247 } 2248 2249 /** 2250 * Set whether this action is available when the wearable device is not connected to 2251 * a companion device. The user can still trigger this action when the wearable device is 2252 * offline, but a visual hint will indicate that the action may not be available. 2253 * Defaults to true. 2254 */ setAvailableOffline(boolean availableOffline)2255 public WearableExtender setAvailableOffline(boolean availableOffline) { 2256 setFlag(FLAG_AVAILABLE_OFFLINE, availableOffline); 2257 return this; 2258 } 2259 2260 /** 2261 * Get whether this action is available when the wearable device is not connected to 2262 * a companion device. The user can still trigger this action when the wearable device is 2263 * offline, but a visual hint will indicate that the action may not be available. 2264 * Defaults to true. 2265 */ isAvailableOffline()2266 public boolean isAvailableOffline() { 2267 return (mFlags & FLAG_AVAILABLE_OFFLINE) != 0; 2268 } 2269 setFlag(int mask, boolean value)2270 private void setFlag(int mask, boolean value) { 2271 if (value) { 2272 mFlags |= mask; 2273 } else { 2274 mFlags &= ~mask; 2275 } 2276 } 2277 2278 /** 2279 * Set a label to display while the wearable is preparing to automatically execute the 2280 * action. This is usually a 'ing' verb ending in ellipsis like "Sending..." 2281 * 2282 * @param label the label to display while the action is being prepared to execute 2283 * @return this object for method chaining 2284 */ 2285 @Deprecated setInProgressLabel(CharSequence label)2286 public WearableExtender setInProgressLabel(CharSequence label) { 2287 mInProgressLabel = label; 2288 return this; 2289 } 2290 2291 /** 2292 * Get the label to display while the wearable is preparing to automatically execute 2293 * the action. This is usually a 'ing' verb ending in ellipsis like "Sending..." 2294 * 2295 * @return the label to display while the action is being prepared to execute 2296 */ 2297 @Deprecated getInProgressLabel()2298 public CharSequence getInProgressLabel() { 2299 return mInProgressLabel; 2300 } 2301 2302 /** 2303 * Set a label to display to confirm that the action should be executed. 2304 * This is usually an imperative verb like "Send". 2305 * 2306 * @param label the label to confirm the action should be executed 2307 * @return this object for method chaining 2308 */ 2309 @Deprecated setConfirmLabel(CharSequence label)2310 public WearableExtender setConfirmLabel(CharSequence label) { 2311 mConfirmLabel = label; 2312 return this; 2313 } 2314 2315 /** 2316 * Get the label to display to confirm that the action should be executed. 2317 * This is usually an imperative verb like "Send". 2318 * 2319 * @return the label to confirm the action should be executed 2320 */ 2321 @Deprecated getConfirmLabel()2322 public CharSequence getConfirmLabel() { 2323 return mConfirmLabel; 2324 } 2325 2326 /** 2327 * Set a label to display to cancel the action. 2328 * This is usually an imperative verb, like "Cancel". 2329 * 2330 * @param label the label to display to cancel the action 2331 * @return this object for method chaining 2332 */ 2333 @Deprecated setCancelLabel(CharSequence label)2334 public WearableExtender setCancelLabel(CharSequence label) { 2335 mCancelLabel = label; 2336 return this; 2337 } 2338 2339 /** 2340 * Get the label to display to cancel the action. 2341 * This is usually an imperative verb like "Cancel". 2342 * 2343 * @return the label to display to cancel the action 2344 */ 2345 @Deprecated getCancelLabel()2346 public CharSequence getCancelLabel() { 2347 return mCancelLabel; 2348 } 2349 2350 /** 2351 * Set a hint that this Action will launch an {@link Activity} directly, telling the 2352 * platform that it can generate the appropriate transitions. 2353 * @param hintLaunchesActivity {@code true} if the content intent will launch 2354 * an activity and transitions should be generated, false otherwise. 2355 * @return this object for method chaining 2356 */ setHintLaunchesActivity( boolean hintLaunchesActivity)2357 public WearableExtender setHintLaunchesActivity( 2358 boolean hintLaunchesActivity) { 2359 setFlag(FLAG_HINT_LAUNCHES_ACTIVITY, hintLaunchesActivity); 2360 return this; 2361 } 2362 2363 /** 2364 * Get a hint that this Action will launch an {@link Activity} directly, telling the 2365 * platform that it can generate the appropriate transitions 2366 * @return {@code true} if the content intent will launch an activity and transitions 2367 * should be generated, false otherwise. The default value is {@code false} if this was 2368 * never set. 2369 */ getHintLaunchesActivity()2370 public boolean getHintLaunchesActivity() { 2371 return (mFlags & FLAG_HINT_LAUNCHES_ACTIVITY) != 0; 2372 } 2373 2374 /** 2375 * Set a hint that this Action should be displayed inline. 2376 * 2377 * @param hintDisplayInline {@code true} if action should be displayed inline, false 2378 * otherwise 2379 * @return this object for method chaining 2380 */ setHintDisplayActionInline( boolean hintDisplayInline)2381 public WearableExtender setHintDisplayActionInline( 2382 boolean hintDisplayInline) { 2383 setFlag(FLAG_HINT_DISPLAY_INLINE, hintDisplayInline); 2384 return this; 2385 } 2386 2387 /** 2388 * Get a hint that this Action should be displayed inline. 2389 * 2390 * @return {@code true} if the Action should be displayed inline, {@code false} 2391 * otherwise. The default value is {@code false} if this was never set. 2392 */ getHintDisplayActionInline()2393 public boolean getHintDisplayActionInline() { 2394 return (mFlags & FLAG_HINT_DISPLAY_INLINE) != 0; 2395 } 2396 } 2397 2398 /** 2399 * Provides meaning to an {@link Action} that hints at what the associated 2400 * {@link PendingIntent} will do. For example, an {@link Action} with a 2401 * {@link PendingIntent} that replies to a text message notification may have the 2402 * {@link #SEMANTIC_ACTION_REPLY} {@code SemanticAction} set within it. 2403 * 2404 * @hide 2405 */ 2406 @IntDef(prefix = { "SEMANTIC_ACTION_" }, value = { 2407 SEMANTIC_ACTION_NONE, 2408 SEMANTIC_ACTION_REPLY, 2409 SEMANTIC_ACTION_MARK_AS_READ, 2410 SEMANTIC_ACTION_MARK_AS_UNREAD, 2411 SEMANTIC_ACTION_DELETE, 2412 SEMANTIC_ACTION_ARCHIVE, 2413 SEMANTIC_ACTION_MUTE, 2414 SEMANTIC_ACTION_UNMUTE, 2415 SEMANTIC_ACTION_THUMBS_UP, 2416 SEMANTIC_ACTION_THUMBS_DOWN, 2417 SEMANTIC_ACTION_CALL, 2418 SEMANTIC_ACTION_MARK_CONVERSATION_AS_PRIORITY, 2419 SEMANTIC_ACTION_CONVERSATION_IS_PHISHING 2420 }) 2421 @Retention(RetentionPolicy.SOURCE) 2422 public @interface SemanticAction {} 2423 } 2424 2425 /** 2426 * Array of all {@link Action} structures attached to this notification by 2427 * {@link Builder#addAction(int, CharSequence, PendingIntent)}. Mostly useful for instances of 2428 * {@link android.service.notification.NotificationListenerService} that provide an alternative 2429 * interface for invoking actions. 2430 */ 2431 public Action[] actions; 2432 2433 /** 2434 * Replacement version of this notification whose content will be shown 2435 * in an insecure context such as atop a secure keyguard. See {@link #visibility} 2436 * and {@link #VISIBILITY_PUBLIC}. 2437 */ 2438 public Notification publicVersion; 2439 2440 /** 2441 * Constructs a Notification object with default values. 2442 * You might want to consider using {@link Builder} instead. 2443 */ Notification()2444 public Notification() 2445 { 2446 this.when = System.currentTimeMillis(); 2447 this.creationTime = System.currentTimeMillis(); 2448 this.priority = PRIORITY_DEFAULT; 2449 } 2450 2451 /** 2452 * @hide 2453 */ 2454 @UnsupportedAppUsage Notification(Context context, int icon, CharSequence tickerText, long when, CharSequence contentTitle, CharSequence contentText, Intent contentIntent)2455 public Notification(Context context, int icon, CharSequence tickerText, long when, 2456 CharSequence contentTitle, CharSequence contentText, Intent contentIntent) 2457 { 2458 new Builder(context) 2459 .setWhen(when) 2460 .setSmallIcon(icon) 2461 .setTicker(tickerText) 2462 .setContentTitle(contentTitle) 2463 .setContentText(contentText) 2464 .setContentIntent(PendingIntent.getActivity( 2465 context, 0, contentIntent, PendingIntent.FLAG_MUTABLE)) 2466 .buildInto(this); 2467 } 2468 2469 /** 2470 * Constructs a Notification object with the information needed to 2471 * have a status bar icon without the standard expanded view. 2472 * 2473 * @param icon The resource id of the icon to put in the status bar. 2474 * @param tickerText The text that flows by in the status bar when the notification first 2475 * activates. 2476 * @param when The time to show in the time field. In the System.currentTimeMillis 2477 * timebase. 2478 * 2479 * @deprecated Use {@link Builder} instead. 2480 */ 2481 @Deprecated Notification(int icon, CharSequence tickerText, long when)2482 public Notification(int icon, CharSequence tickerText, long when) 2483 { 2484 this.icon = icon; 2485 this.tickerText = tickerText; 2486 this.when = when; 2487 this.creationTime = System.currentTimeMillis(); 2488 } 2489 2490 /** 2491 * Unflatten the notification from a parcel. 2492 */ 2493 @SuppressWarnings("unchecked") Notification(Parcel parcel)2494 public Notification(Parcel parcel) { 2495 // IMPORTANT: Add unmarshaling code in readFromParcel as the pending 2496 // intents in extras are always written as the last entry. 2497 readFromParcelImpl(parcel); 2498 // Must be read last! 2499 allPendingIntents = (ArraySet<PendingIntent>) parcel.readArraySet(null); 2500 } 2501 readFromParcelImpl(Parcel parcel)2502 private void readFromParcelImpl(Parcel parcel) 2503 { 2504 int version = parcel.readInt(); 2505 2506 mAllowlistToken = parcel.readStrongBinder(); 2507 if (mAllowlistToken == null) { 2508 mAllowlistToken = processAllowlistToken; 2509 } 2510 // Propagate this token to all pending intents that are unmarshalled from the parcel. 2511 parcel.setClassCookie(PendingIntent.class, mAllowlistToken); 2512 2513 when = parcel.readLong(); 2514 creationTime = parcel.readLong(); 2515 if (parcel.readInt() != 0) { 2516 mSmallIcon = Icon.CREATOR.createFromParcel(parcel); 2517 if (mSmallIcon.getType() == Icon.TYPE_RESOURCE) { 2518 icon = mSmallIcon.getResId(); 2519 } 2520 } 2521 number = parcel.readInt(); 2522 if (parcel.readInt() != 0) { 2523 contentIntent = PendingIntent.CREATOR.createFromParcel(parcel); 2524 } 2525 if (parcel.readInt() != 0) { 2526 deleteIntent = PendingIntent.CREATOR.createFromParcel(parcel); 2527 } 2528 if (parcel.readInt() != 0) { 2529 tickerText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel); 2530 } 2531 if (parcel.readInt() != 0) { 2532 tickerView = RemoteViews.CREATOR.createFromParcel(parcel); 2533 } 2534 if (parcel.readInt() != 0) { 2535 contentView = RemoteViews.CREATOR.createFromParcel(parcel); 2536 } 2537 if (parcel.readInt() != 0) { 2538 mLargeIcon = Icon.CREATOR.createFromParcel(parcel); 2539 } 2540 defaults = parcel.readInt(); 2541 flags = parcel.readInt(); 2542 if (parcel.readInt() != 0) { 2543 sound = Uri.CREATOR.createFromParcel(parcel); 2544 } 2545 2546 audioStreamType = parcel.readInt(); 2547 if (parcel.readInt() != 0) { 2548 audioAttributes = AudioAttributes.CREATOR.createFromParcel(parcel); 2549 } 2550 vibrate = parcel.createLongArray(); 2551 ledARGB = parcel.readInt(); 2552 ledOnMS = parcel.readInt(); 2553 ledOffMS = parcel.readInt(); 2554 iconLevel = parcel.readInt(); 2555 2556 if (parcel.readInt() != 0) { 2557 fullScreenIntent = PendingIntent.CREATOR.createFromParcel(parcel); 2558 } 2559 2560 priority = parcel.readInt(); 2561 2562 category = parcel.readString8(); 2563 2564 mGroupKey = parcel.readString8(); 2565 2566 mSortKey = parcel.readString8(); 2567 2568 extras = Bundle.setDefusable(parcel.readBundle(), true); // may be null 2569 fixDuplicateExtras(); 2570 2571 actions = parcel.createTypedArray(Action.CREATOR); // may be null 2572 2573 if (parcel.readInt() != 0) { 2574 bigContentView = RemoteViews.CREATOR.createFromParcel(parcel); 2575 } 2576 2577 if (parcel.readInt() != 0) { 2578 headsUpContentView = RemoteViews.CREATOR.createFromParcel(parcel); 2579 } 2580 2581 visibility = parcel.readInt(); 2582 2583 if (parcel.readInt() != 0) { 2584 publicVersion = Notification.CREATOR.createFromParcel(parcel); 2585 } 2586 2587 color = parcel.readInt(); 2588 2589 if (parcel.readInt() != 0) { 2590 mChannelId = parcel.readString8(); 2591 } 2592 mTimeout = parcel.readLong(); 2593 2594 if (parcel.readInt() != 0) { 2595 mShortcutId = parcel.readString8(); 2596 } 2597 2598 if (parcel.readInt() != 0) { 2599 mLocusId = LocusId.CREATOR.createFromParcel(parcel); 2600 } 2601 2602 mBadgeIcon = parcel.readInt(); 2603 2604 if (parcel.readInt() != 0) { 2605 mSettingsText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel); 2606 } 2607 2608 mGroupAlertBehavior = parcel.readInt(); 2609 if (parcel.readInt() != 0) { 2610 mBubbleMetadata = BubbleMetadata.CREATOR.createFromParcel(parcel); 2611 } 2612 2613 mAllowSystemGeneratedContextualActions = parcel.readBoolean(); 2614 2615 mFgsDeferBehavior = parcel.readInt(); 2616 } 2617 2618 @Override clone()2619 public Notification clone() { 2620 Notification that = new Notification(); 2621 cloneInto(that, true); 2622 return that; 2623 } 2624 2625 /** 2626 * Copy all (or if heavy is false, all except Bitmaps and RemoteViews) members 2627 * of this into that. 2628 * @hide 2629 */ cloneInto(Notification that, boolean heavy)2630 public void cloneInto(Notification that, boolean heavy) { 2631 that.mAllowlistToken = this.mAllowlistToken; 2632 that.when = this.when; 2633 that.creationTime = this.creationTime; 2634 that.mSmallIcon = this.mSmallIcon; 2635 that.number = this.number; 2636 2637 // PendingIntents are global, so there's no reason (or way) to clone them. 2638 that.contentIntent = this.contentIntent; 2639 that.deleteIntent = this.deleteIntent; 2640 that.fullScreenIntent = this.fullScreenIntent; 2641 2642 if (this.tickerText != null) { 2643 that.tickerText = this.tickerText.toString(); 2644 } 2645 if (heavy && this.tickerView != null) { 2646 that.tickerView = this.tickerView.clone(); 2647 } 2648 if (heavy && this.contentView != null) { 2649 that.contentView = this.contentView.clone(); 2650 } 2651 if (heavy && this.mLargeIcon != null) { 2652 that.mLargeIcon = this.mLargeIcon; 2653 } 2654 that.iconLevel = this.iconLevel; 2655 that.sound = this.sound; // android.net.Uri is immutable 2656 that.audioStreamType = this.audioStreamType; 2657 if (this.audioAttributes != null) { 2658 that.audioAttributes = new AudioAttributes.Builder(this.audioAttributes).build(); 2659 } 2660 2661 final long[] vibrate = this.vibrate; 2662 if (vibrate != null) { 2663 final int N = vibrate.length; 2664 final long[] vib = that.vibrate = new long[N]; 2665 System.arraycopy(vibrate, 0, vib, 0, N); 2666 } 2667 2668 that.ledARGB = this.ledARGB; 2669 that.ledOnMS = this.ledOnMS; 2670 that.ledOffMS = this.ledOffMS; 2671 that.defaults = this.defaults; 2672 2673 that.flags = this.flags; 2674 2675 that.priority = this.priority; 2676 2677 that.category = this.category; 2678 2679 that.mGroupKey = this.mGroupKey; 2680 2681 that.mSortKey = this.mSortKey; 2682 2683 if (this.extras != null) { 2684 try { 2685 that.extras = new Bundle(this.extras); 2686 // will unparcel 2687 that.extras.size(); 2688 } catch (BadParcelableException e) { 2689 Log.e(TAG, "could not unparcel extras from notification: " + this, e); 2690 that.extras = null; 2691 } 2692 } 2693 2694 if (!ArrayUtils.isEmpty(allPendingIntents)) { 2695 that.allPendingIntents = new ArraySet<>(allPendingIntents); 2696 } 2697 2698 if (this.actions != null) { 2699 that.actions = new Action[this.actions.length]; 2700 for(int i=0; i<this.actions.length; i++) { 2701 if ( this.actions[i] != null) { 2702 that.actions[i] = this.actions[i].clone(); 2703 } 2704 } 2705 } 2706 2707 if (heavy && this.bigContentView != null) { 2708 that.bigContentView = this.bigContentView.clone(); 2709 } 2710 2711 if (heavy && this.headsUpContentView != null) { 2712 that.headsUpContentView = this.headsUpContentView.clone(); 2713 } 2714 2715 that.visibility = this.visibility; 2716 2717 if (this.publicVersion != null) { 2718 that.publicVersion = new Notification(); 2719 this.publicVersion.cloneInto(that.publicVersion, heavy); 2720 } 2721 2722 that.color = this.color; 2723 2724 that.mChannelId = this.mChannelId; 2725 that.mTimeout = this.mTimeout; 2726 that.mShortcutId = this.mShortcutId; 2727 that.mLocusId = this.mLocusId; 2728 that.mBadgeIcon = this.mBadgeIcon; 2729 that.mSettingsText = this.mSettingsText; 2730 that.mGroupAlertBehavior = this.mGroupAlertBehavior; 2731 that.mFgsDeferBehavior = this.mFgsDeferBehavior; 2732 that.mBubbleMetadata = this.mBubbleMetadata; 2733 that.mAllowSystemGeneratedContextualActions = this.mAllowSystemGeneratedContextualActions; 2734 2735 if (!heavy) { 2736 that.lightenPayload(); // will clean out extras 2737 } 2738 } 2739 visitIconUri(@onNull Consumer<Uri> visitor, @Nullable Icon icon)2740 private static void visitIconUri(@NonNull Consumer<Uri> visitor, @Nullable Icon icon) { 2741 if (icon == null) return; 2742 final int iconType = icon.getType(); 2743 if (iconType == TYPE_URI || iconType == TYPE_URI_ADAPTIVE_BITMAP) { 2744 visitor.accept(icon.getUri()); 2745 } 2746 } 2747 2748 /** 2749 * Note all {@link Uri} that are referenced internally, with the expectation 2750 * that Uri permission grants will need to be issued to ensure the recipient 2751 * of this object is able to render its contents. 2752 * 2753 * @hide 2754 */ visitUris(@onNull Consumer<Uri> visitor)2755 public void visitUris(@NonNull Consumer<Uri> visitor) { 2756 visitor.accept(sound); 2757 2758 if (tickerView != null) tickerView.visitUris(visitor); 2759 if (contentView != null) contentView.visitUris(visitor); 2760 if (bigContentView != null) bigContentView.visitUris(visitor); 2761 if (headsUpContentView != null) headsUpContentView.visitUris(visitor); 2762 2763 visitIconUri(visitor, mSmallIcon); 2764 visitIconUri(visitor, mLargeIcon); 2765 2766 if (actions != null) { 2767 for (Action action : actions) { 2768 visitIconUri(visitor, action.getIcon()); 2769 } 2770 } 2771 2772 if (extras != null) { 2773 visitIconUri(visitor, extras.getParcelable(EXTRA_LARGE_ICON_BIG)); 2774 visitIconUri(visitor, extras.getParcelable(EXTRA_PICTURE_ICON)); 2775 2776 // NOTE: The documentation of EXTRA_AUDIO_CONTENTS_URI explicitly says that it is a 2777 // String representation of a Uri, but the previous implementation (and unit test) of 2778 // this method has always treated it as a Uri object. Given the inconsistency, 2779 // supporting both going forward is the safest choice. 2780 Object audioContentsUri = extras.get(EXTRA_AUDIO_CONTENTS_URI); 2781 if (audioContentsUri instanceof Uri) { 2782 visitor.accept((Uri) audioContentsUri); 2783 } else if (audioContentsUri instanceof String) { 2784 visitor.accept(Uri.parse((String) audioContentsUri)); 2785 } 2786 2787 if (extras.containsKey(EXTRA_BACKGROUND_IMAGE_URI)) { 2788 visitor.accept(Uri.parse(extras.getString(EXTRA_BACKGROUND_IMAGE_URI))); 2789 } 2790 2791 ArrayList<Person> people = extras.getParcelableArrayList(EXTRA_PEOPLE_LIST); 2792 if (people != null && !people.isEmpty()) { 2793 for (Person p : people) { 2794 visitor.accept(p.getIconUri()); 2795 } 2796 } 2797 2798 final Person person = extras.getParcelable(EXTRA_MESSAGING_PERSON); 2799 if (person != null) { 2800 visitor.accept(person.getIconUri()); 2801 } 2802 } 2803 2804 if (isStyle(MessagingStyle.class) && extras != null) { 2805 final Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES); 2806 if (!ArrayUtils.isEmpty(messages)) { 2807 for (MessagingStyle.Message message : MessagingStyle.Message 2808 .getMessagesFromBundleArray(messages)) { 2809 visitor.accept(message.getDataUri()); 2810 2811 Person senderPerson = message.getSenderPerson(); 2812 if (senderPerson != null) { 2813 visitor.accept(senderPerson.getIconUri()); 2814 } 2815 } 2816 } 2817 2818 final Parcelable[] historic = extras.getParcelableArray(EXTRA_HISTORIC_MESSAGES); 2819 if (!ArrayUtils.isEmpty(historic)) { 2820 for (MessagingStyle.Message message : MessagingStyle.Message 2821 .getMessagesFromBundleArray(historic)) { 2822 visitor.accept(message.getDataUri()); 2823 2824 Person senderPerson = message.getSenderPerson(); 2825 if (senderPerson != null) { 2826 visitor.accept(senderPerson.getIconUri()); 2827 } 2828 } 2829 } 2830 } 2831 2832 if (mBubbleMetadata != null) { 2833 visitIconUri(visitor, mBubbleMetadata.getIcon()); 2834 } 2835 } 2836 2837 /** 2838 * Removes heavyweight parts of the Notification object for archival or for sending to 2839 * listeners when the full contents are not necessary. 2840 * @hide 2841 */ lightenPayload()2842 public final void lightenPayload() { 2843 tickerView = null; 2844 contentView = null; 2845 bigContentView = null; 2846 headsUpContentView = null; 2847 mLargeIcon = null; 2848 if (extras != null && !extras.isEmpty()) { 2849 final Set<String> keyset = extras.keySet(); 2850 final int N = keyset.size(); 2851 final String[] keys = keyset.toArray(new String[N]); 2852 for (int i=0; i<N; i++) { 2853 final String key = keys[i]; 2854 if (TvExtender.EXTRA_TV_EXTENDER.equals(key)) { 2855 continue; 2856 } 2857 final Object obj = extras.get(key); 2858 if (obj != null && 2859 ( obj instanceof Parcelable 2860 || obj instanceof Parcelable[] 2861 || obj instanceof SparseArray 2862 || obj instanceof ArrayList)) { 2863 extras.remove(key); 2864 } 2865 } 2866 } 2867 } 2868 2869 /** 2870 * Make sure this CharSequence is safe to put into a bundle, which basically 2871 * means it had better not be some custom Parcelable implementation. 2872 * @hide 2873 */ safeCharSequence(CharSequence cs)2874 public static CharSequence safeCharSequence(CharSequence cs) { 2875 if (cs == null) return cs; 2876 if (cs.length() > MAX_CHARSEQUENCE_LENGTH) { 2877 cs = cs.subSequence(0, MAX_CHARSEQUENCE_LENGTH); 2878 } 2879 if (cs instanceof Parcelable) { 2880 Log.e(TAG, "warning: " + cs.getClass().getCanonicalName() 2881 + " instance is a custom Parcelable and not allowed in Notification"); 2882 return cs.toString(); 2883 } 2884 return removeTextSizeSpans(cs); 2885 } 2886 removeTextSizeSpans(CharSequence charSequence)2887 private static CharSequence removeTextSizeSpans(CharSequence charSequence) { 2888 if (charSequence instanceof Spanned) { 2889 Spanned ss = (Spanned) charSequence; 2890 Object[] spans = ss.getSpans(0, ss.length(), Object.class); 2891 SpannableStringBuilder builder = new SpannableStringBuilder(ss.toString()); 2892 for (Object span : spans) { 2893 Object resultSpan = span; 2894 if (resultSpan instanceof CharacterStyle) { 2895 resultSpan = ((CharacterStyle) span).getUnderlying(); 2896 } 2897 if (resultSpan instanceof TextAppearanceSpan) { 2898 TextAppearanceSpan originalSpan = (TextAppearanceSpan) resultSpan; 2899 resultSpan = new TextAppearanceSpan( 2900 originalSpan.getFamily(), 2901 originalSpan.getTextStyle(), 2902 -1, 2903 originalSpan.getTextColor(), 2904 originalSpan.getLinkTextColor()); 2905 } else if (resultSpan instanceof RelativeSizeSpan 2906 || resultSpan instanceof AbsoluteSizeSpan) { 2907 continue; 2908 } else { 2909 resultSpan = span; 2910 } 2911 builder.setSpan(resultSpan, ss.getSpanStart(span), ss.getSpanEnd(span), 2912 ss.getSpanFlags(span)); 2913 } 2914 return builder; 2915 } 2916 return charSequence; 2917 } 2918 describeContents()2919 public int describeContents() { 2920 return 0; 2921 } 2922 2923 /** 2924 * Flatten this notification into a parcel. 2925 */ writeToParcel(Parcel parcel, int flags)2926 public void writeToParcel(Parcel parcel, int flags) { 2927 // We need to mark all pending intents getting into the notification 2928 // system as being put there to later allow the notification ranker 2929 // to launch them and by doing so add the app to the battery saver white 2930 // list for a short period of time. The problem is that the system 2931 // cannot look into the extras as there may be parcelables there that 2932 // the platform does not know how to handle. To go around that we have 2933 // an explicit list of the pending intents in the extras bundle. 2934 final boolean collectPendingIntents = (allPendingIntents == null); 2935 if (collectPendingIntents) { 2936 PendingIntent.setOnMarshaledListener( 2937 (PendingIntent intent, Parcel out, int outFlags) -> { 2938 if (parcel == out) { 2939 synchronized (this) { 2940 if (allPendingIntents == null) { 2941 allPendingIntents = new ArraySet<>(); 2942 } 2943 allPendingIntents.add(intent); 2944 } 2945 } 2946 }); 2947 } 2948 try { 2949 // IMPORTANT: Add marshaling code in writeToParcelImpl as we 2950 // want to intercept all pending events written to the parcel. 2951 writeToParcelImpl(parcel, flags); 2952 synchronized (this) { 2953 // Must be written last! 2954 parcel.writeArraySet(allPendingIntents); 2955 } 2956 } finally { 2957 if (collectPendingIntents) { 2958 PendingIntent.setOnMarshaledListener(null); 2959 } 2960 } 2961 } 2962 writeToParcelImpl(Parcel parcel, int flags)2963 private void writeToParcelImpl(Parcel parcel, int flags) { 2964 parcel.writeInt(1); 2965 2966 parcel.writeStrongBinder(mAllowlistToken); 2967 parcel.writeLong(when); 2968 parcel.writeLong(creationTime); 2969 if (mSmallIcon == null && icon != 0) { 2970 // you snuck an icon in here without using the builder; let's try to keep it 2971 mSmallIcon = Icon.createWithResource("", icon); 2972 } 2973 if (mSmallIcon != null) { 2974 parcel.writeInt(1); 2975 mSmallIcon.writeToParcel(parcel, 0); 2976 } else { 2977 parcel.writeInt(0); 2978 } 2979 parcel.writeInt(number); 2980 if (contentIntent != null) { 2981 parcel.writeInt(1); 2982 contentIntent.writeToParcel(parcel, 0); 2983 } else { 2984 parcel.writeInt(0); 2985 } 2986 if (deleteIntent != null) { 2987 parcel.writeInt(1); 2988 deleteIntent.writeToParcel(parcel, 0); 2989 } else { 2990 parcel.writeInt(0); 2991 } 2992 if (tickerText != null) { 2993 parcel.writeInt(1); 2994 TextUtils.writeToParcel(tickerText, parcel, flags); 2995 } else { 2996 parcel.writeInt(0); 2997 } 2998 if (tickerView != null) { 2999 parcel.writeInt(1); 3000 tickerView.writeToParcel(parcel, 0); 3001 } else { 3002 parcel.writeInt(0); 3003 } 3004 if (contentView != null) { 3005 parcel.writeInt(1); 3006 contentView.writeToParcel(parcel, 0); 3007 } else { 3008 parcel.writeInt(0); 3009 } 3010 if (mLargeIcon == null && largeIcon != null) { 3011 // you snuck an icon in here without using the builder; let's try to keep it 3012 mLargeIcon = Icon.createWithBitmap(largeIcon); 3013 } 3014 if (mLargeIcon != null) { 3015 parcel.writeInt(1); 3016 mLargeIcon.writeToParcel(parcel, 0); 3017 } else { 3018 parcel.writeInt(0); 3019 } 3020 3021 parcel.writeInt(defaults); 3022 parcel.writeInt(this.flags); 3023 3024 if (sound != null) { 3025 parcel.writeInt(1); 3026 sound.writeToParcel(parcel, 0); 3027 } else { 3028 parcel.writeInt(0); 3029 } 3030 parcel.writeInt(audioStreamType); 3031 3032 if (audioAttributes != null) { 3033 parcel.writeInt(1); 3034 audioAttributes.writeToParcel(parcel, 0); 3035 } else { 3036 parcel.writeInt(0); 3037 } 3038 3039 parcel.writeLongArray(vibrate); 3040 parcel.writeInt(ledARGB); 3041 parcel.writeInt(ledOnMS); 3042 parcel.writeInt(ledOffMS); 3043 parcel.writeInt(iconLevel); 3044 3045 if (fullScreenIntent != null) { 3046 parcel.writeInt(1); 3047 fullScreenIntent.writeToParcel(parcel, 0); 3048 } else { 3049 parcel.writeInt(0); 3050 } 3051 3052 parcel.writeInt(priority); 3053 3054 parcel.writeString8(category); 3055 3056 parcel.writeString8(mGroupKey); 3057 3058 parcel.writeString8(mSortKey); 3059 3060 parcel.writeBundle(extras); // null ok 3061 3062 parcel.writeTypedArray(actions, 0); // null ok 3063 3064 if (bigContentView != null) { 3065 parcel.writeInt(1); 3066 bigContentView.writeToParcel(parcel, 0); 3067 } else { 3068 parcel.writeInt(0); 3069 } 3070 3071 if (headsUpContentView != null) { 3072 parcel.writeInt(1); 3073 headsUpContentView.writeToParcel(parcel, 0); 3074 } else { 3075 parcel.writeInt(0); 3076 } 3077 3078 parcel.writeInt(visibility); 3079 3080 if (publicVersion != null) { 3081 parcel.writeInt(1); 3082 publicVersion.writeToParcel(parcel, 0); 3083 } else { 3084 parcel.writeInt(0); 3085 } 3086 3087 parcel.writeInt(color); 3088 3089 if (mChannelId != null) { 3090 parcel.writeInt(1); 3091 parcel.writeString8(mChannelId); 3092 } else { 3093 parcel.writeInt(0); 3094 } 3095 parcel.writeLong(mTimeout); 3096 3097 if (mShortcutId != null) { 3098 parcel.writeInt(1); 3099 parcel.writeString8(mShortcutId); 3100 } else { 3101 parcel.writeInt(0); 3102 } 3103 3104 if (mLocusId != null) { 3105 parcel.writeInt(1); 3106 mLocusId.writeToParcel(parcel, 0); 3107 } else { 3108 parcel.writeInt(0); 3109 } 3110 3111 parcel.writeInt(mBadgeIcon); 3112 3113 if (mSettingsText != null) { 3114 parcel.writeInt(1); 3115 TextUtils.writeToParcel(mSettingsText, parcel, flags); 3116 } else { 3117 parcel.writeInt(0); 3118 } 3119 3120 parcel.writeInt(mGroupAlertBehavior); 3121 3122 if (mBubbleMetadata != null) { 3123 parcel.writeInt(1); 3124 mBubbleMetadata.writeToParcel(parcel, 0); 3125 } else { 3126 parcel.writeInt(0); 3127 } 3128 3129 parcel.writeBoolean(mAllowSystemGeneratedContextualActions); 3130 3131 parcel.writeInt(mFgsDeferBehavior); 3132 3133 // mUsesStandardHeader is not written because it should be recomputed in listeners 3134 } 3135 3136 /** 3137 * Parcelable.Creator that instantiates Notification objects 3138 */ 3139 public static final @android.annotation.NonNull Parcelable.Creator<Notification> CREATOR 3140 = new Parcelable.Creator<Notification>() 3141 { 3142 public Notification createFromParcel(Parcel parcel) 3143 { 3144 return new Notification(parcel); 3145 } 3146 3147 public Notification[] newArray(int size) 3148 { 3149 return new Notification[size]; 3150 } 3151 }; 3152 3153 /** 3154 * @hide 3155 */ areActionsVisiblyDifferent(Notification first, Notification second)3156 public static boolean areActionsVisiblyDifferent(Notification first, Notification second) { 3157 Notification.Action[] firstAs = first.actions; 3158 Notification.Action[] secondAs = second.actions; 3159 if (firstAs == null && secondAs != null || firstAs != null && secondAs == null) { 3160 return true; 3161 } 3162 if (firstAs != null && secondAs != null) { 3163 if (firstAs.length != secondAs.length) { 3164 return true; 3165 } 3166 for (int i = 0; i < firstAs.length; i++) { 3167 if (!Objects.equals(String.valueOf(firstAs[i].title), 3168 String.valueOf(secondAs[i].title))) { 3169 return true; 3170 } 3171 RemoteInput[] firstRs = firstAs[i].getRemoteInputs(); 3172 RemoteInput[] secondRs = secondAs[i].getRemoteInputs(); 3173 if (firstRs == null) { 3174 firstRs = new RemoteInput[0]; 3175 } 3176 if (secondRs == null) { 3177 secondRs = new RemoteInput[0]; 3178 } 3179 if (firstRs.length != secondRs.length) { 3180 return true; 3181 } 3182 for (int j = 0; j < firstRs.length; j++) { 3183 if (!Objects.equals(String.valueOf(firstRs[j].getLabel()), 3184 String.valueOf(secondRs[j].getLabel()))) { 3185 return true; 3186 } 3187 } 3188 } 3189 } 3190 return false; 3191 } 3192 3193 /** 3194 * @hide 3195 */ areStyledNotificationsVisiblyDifferent(Builder first, Builder second)3196 public static boolean areStyledNotificationsVisiblyDifferent(Builder first, Builder second) { 3197 if (first.getStyle() == null) { 3198 return second.getStyle() != null; 3199 } 3200 if (second.getStyle() == null) { 3201 return true; 3202 } 3203 return first.getStyle().areNotificationsVisiblyDifferent(second.getStyle()); 3204 } 3205 3206 /** 3207 * @hide 3208 */ areRemoteViewsChanged(Builder first, Builder second)3209 public static boolean areRemoteViewsChanged(Builder first, Builder second) { 3210 if (!Objects.equals(first.usesStandardHeader(), second.usesStandardHeader())) { 3211 return true; 3212 } 3213 3214 if (areRemoteViewsChanged(first.mN.contentView, second.mN.contentView)) { 3215 return true; 3216 } 3217 if (areRemoteViewsChanged(first.mN.bigContentView, second.mN.bigContentView)) { 3218 return true; 3219 } 3220 if (areRemoteViewsChanged(first.mN.headsUpContentView, second.mN.headsUpContentView)) { 3221 return true; 3222 } 3223 3224 return false; 3225 } 3226 areRemoteViewsChanged(RemoteViews first, RemoteViews second)3227 private static boolean areRemoteViewsChanged(RemoteViews first, RemoteViews second) { 3228 if (first == null && second == null) { 3229 return false; 3230 } 3231 if (first == null && second != null || first != null && second == null) { 3232 return true; 3233 } 3234 3235 if (!Objects.equals(first.getLayoutId(), second.getLayoutId())) { 3236 return true; 3237 } 3238 3239 if (!Objects.equals(first.getSequenceNumber(), second.getSequenceNumber())) { 3240 return true; 3241 } 3242 3243 return false; 3244 } 3245 3246 /** 3247 * Parcelling creates multiple copies of objects in {@code extras}. Fix them. 3248 * <p> 3249 * For backwards compatibility {@code extras} holds some references to "real" member data such 3250 * as {@link getLargeIcon()} which is mirrored by {@link #EXTRA_LARGE_ICON}. This is mostly 3251 * fine as long as the object stays in one process. 3252 * <p> 3253 * However, once the notification goes into a parcel each reference gets marshalled separately, 3254 * wasting memory. Especially with large images on Auto and TV, this is worth fixing. 3255 */ fixDuplicateExtras()3256 private void fixDuplicateExtras() { 3257 if (extras != null) { 3258 fixDuplicateExtra(mLargeIcon, EXTRA_LARGE_ICON); 3259 } 3260 } 3261 3262 /** 3263 * If we find an extra that's exactly the same as one of the "real" fields but refers to a 3264 * separate object, replace it with the field's version to avoid holding duplicate copies. 3265 */ fixDuplicateExtra(@ullable Parcelable original, @NonNull String extraName)3266 private void fixDuplicateExtra(@Nullable Parcelable original, @NonNull String extraName) { 3267 if (original != null && extras.getParcelable(extraName) != null) { 3268 extras.putParcelable(extraName, original); 3269 } 3270 } 3271 3272 /** 3273 * Sets the {@link #contentView} field to be a view with the standard "Latest Event" 3274 * layout. 3275 * 3276 * <p>Uses the {@link #icon} and {@link #when} fields to set the icon and time fields 3277 * in the view.</p> 3278 * @param context The context for your application / activity. 3279 * @param contentTitle The title that goes in the expanded entry. 3280 * @param contentText The text that goes in the expanded entry. 3281 * @param contentIntent The intent to launch when the user clicks the expanded notification. 3282 * If this is an activity, it must include the 3283 * {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK} flag, which requires 3284 * that you take care of task management as described in the 3285 * <a href="{@docRoot}guide/topics/fundamentals/tasks-and-back-stack.html">Tasks and Back 3286 * Stack</a> document. 3287 * 3288 * @deprecated Use {@link Builder} instead. 3289 * @removed 3290 */ 3291 @Deprecated setLatestEventInfo(Context context, CharSequence contentTitle, CharSequence contentText, PendingIntent contentIntent)3292 public void setLatestEventInfo(Context context, 3293 CharSequence contentTitle, CharSequence contentText, PendingIntent contentIntent) { 3294 if (context.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1){ 3295 Log.e(TAG, "setLatestEventInfo() is deprecated and you should feel deprecated.", 3296 new Throwable()); 3297 } 3298 3299 if (context.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N) { 3300 extras.putBoolean(EXTRA_SHOW_WHEN, true); 3301 } 3302 3303 // ensure that any information already set directly is preserved 3304 final Notification.Builder builder = new Notification.Builder(context, this); 3305 3306 // now apply the latestEventInfo fields 3307 if (contentTitle != null) { 3308 builder.setContentTitle(contentTitle); 3309 } 3310 if (contentText != null) { 3311 builder.setContentText(contentText); 3312 } 3313 builder.setContentIntent(contentIntent); 3314 3315 builder.build(); // callers expect this notification to be ready to use 3316 } 3317 3318 /** 3319 * Sets the token used for background operations for the pending intents associated with this 3320 * notification. 3321 * 3322 * This token is automatically set during deserialization for you, you usually won't need to 3323 * call this unless you want to change the existing token, if any. 3324 * 3325 * @hide 3326 */ setAllowlistToken(@ullable IBinder token)3327 public void setAllowlistToken(@Nullable IBinder token) { 3328 mAllowlistToken = token; 3329 } 3330 3331 /** 3332 * @hide 3333 */ addFieldsFromContext(Context context, Notification notification)3334 public static void addFieldsFromContext(Context context, Notification notification) { 3335 addFieldsFromContext(context.getApplicationInfo(), notification); 3336 } 3337 3338 /** 3339 * @hide 3340 */ addFieldsFromContext(ApplicationInfo ai, Notification notification)3341 public static void addFieldsFromContext(ApplicationInfo ai, Notification notification) { 3342 notification.extras.putParcelable(EXTRA_BUILDER_APPLICATION_INFO, ai); 3343 } 3344 3345 /** 3346 * @hide 3347 */ dumpDebug(ProtoOutputStream proto, long fieldId)3348 public void dumpDebug(ProtoOutputStream proto, long fieldId) { 3349 long token = proto.start(fieldId); 3350 proto.write(NotificationProto.CHANNEL_ID, getChannelId()); 3351 proto.write(NotificationProto.HAS_TICKER_TEXT, this.tickerText != null); 3352 proto.write(NotificationProto.FLAGS, this.flags); 3353 proto.write(NotificationProto.COLOR, this.color); 3354 proto.write(NotificationProto.CATEGORY, this.category); 3355 proto.write(NotificationProto.GROUP_KEY, this.mGroupKey); 3356 proto.write(NotificationProto.SORT_KEY, this.mSortKey); 3357 if (this.actions != null) { 3358 proto.write(NotificationProto.ACTION_LENGTH, this.actions.length); 3359 } 3360 if (this.visibility >= VISIBILITY_SECRET && this.visibility <= VISIBILITY_PUBLIC) { 3361 proto.write(NotificationProto.VISIBILITY, this.visibility); 3362 } 3363 if (publicVersion != null) { 3364 publicVersion.dumpDebug(proto, NotificationProto.PUBLIC_VERSION); 3365 } 3366 proto.end(token); 3367 } 3368 3369 @Override toString()3370 public String toString() { 3371 StringBuilder sb = new StringBuilder(); 3372 sb.append("Notification(channel="); 3373 sb.append(getChannelId()); 3374 sb.append(" shortcut="); 3375 sb.append(getShortcutId()); 3376 sb.append(" contentView="); 3377 if (contentView != null) { 3378 sb.append(contentView.getPackage()); 3379 sb.append("/0x"); 3380 sb.append(Integer.toHexString(contentView.getLayoutId())); 3381 } else { 3382 sb.append("null"); 3383 } 3384 sb.append(" vibrate="); 3385 if ((this.defaults & DEFAULT_VIBRATE) != 0) { 3386 sb.append("default"); 3387 } else if (this.vibrate != null) { 3388 int N = this.vibrate.length-1; 3389 sb.append("["); 3390 for (int i=0; i<N; i++) { 3391 sb.append(this.vibrate[i]); 3392 sb.append(','); 3393 } 3394 if (N != -1) { 3395 sb.append(this.vibrate[N]); 3396 } 3397 sb.append("]"); 3398 } else { 3399 sb.append("null"); 3400 } 3401 sb.append(" sound="); 3402 if ((this.defaults & DEFAULT_SOUND) != 0) { 3403 sb.append("default"); 3404 } else if (this.sound != null) { 3405 sb.append(this.sound.toString()); 3406 } else { 3407 sb.append("null"); 3408 } 3409 if (this.tickerText != null) { 3410 sb.append(" tick"); 3411 } 3412 sb.append(" defaults=0x"); 3413 sb.append(Integer.toHexString(this.defaults)); 3414 sb.append(" flags=0x"); 3415 sb.append(Integer.toHexString(this.flags)); 3416 sb.append(String.format(" color=0x%08x", this.color)); 3417 if (this.category != null) { 3418 sb.append(" category="); 3419 sb.append(this.category); 3420 } 3421 if (this.mGroupKey != null) { 3422 sb.append(" groupKey="); 3423 sb.append(this.mGroupKey); 3424 } 3425 if (this.mSortKey != null) { 3426 sb.append(" sortKey="); 3427 sb.append(this.mSortKey); 3428 } 3429 if (actions != null) { 3430 sb.append(" actions="); 3431 sb.append(actions.length); 3432 } 3433 sb.append(" vis="); 3434 sb.append(visibilityToString(this.visibility)); 3435 if (this.publicVersion != null) { 3436 sb.append(" publicVersion="); 3437 sb.append(publicVersion.toString()); 3438 } 3439 if (this.mLocusId != null) { 3440 sb.append(" locusId="); 3441 sb.append(this.mLocusId); // LocusId.toString() is PII safe. 3442 } 3443 sb.append(")"); 3444 return sb.toString(); 3445 } 3446 3447 /** 3448 * {@hide} 3449 */ visibilityToString(int vis)3450 public static String visibilityToString(int vis) { 3451 switch (vis) { 3452 case VISIBILITY_PRIVATE: 3453 return "PRIVATE"; 3454 case VISIBILITY_PUBLIC: 3455 return "PUBLIC"; 3456 case VISIBILITY_SECRET: 3457 return "SECRET"; 3458 default: 3459 return "UNKNOWN(" + String.valueOf(vis) + ")"; 3460 } 3461 } 3462 3463 /** 3464 * {@hide} 3465 */ priorityToString(@riority int pri)3466 public static String priorityToString(@Priority int pri) { 3467 switch (pri) { 3468 case PRIORITY_MIN: 3469 return "MIN"; 3470 case PRIORITY_LOW: 3471 return "LOW"; 3472 case PRIORITY_DEFAULT: 3473 return "DEFAULT"; 3474 case PRIORITY_HIGH: 3475 return "HIGH"; 3476 case PRIORITY_MAX: 3477 return "MAX"; 3478 default: 3479 return "UNKNOWN(" + String.valueOf(pri) + ")"; 3480 } 3481 } 3482 3483 /** 3484 * @hide 3485 */ hasCompletedProgress()3486 public boolean hasCompletedProgress() { 3487 // not a progress notification; can't be complete 3488 if (!extras.containsKey(EXTRA_PROGRESS) 3489 || !extras.containsKey(EXTRA_PROGRESS_MAX)) { 3490 return false; 3491 } 3492 // many apps use max 0 for 'indeterminate'; not complete 3493 if (extras.getInt(EXTRA_PROGRESS_MAX) == 0) { 3494 return false; 3495 } 3496 return extras.getInt(EXTRA_PROGRESS) == extras.getInt(EXTRA_PROGRESS_MAX); 3497 } 3498 3499 /** @removed */ 3500 @Deprecated getChannel()3501 public String getChannel() { 3502 return mChannelId; 3503 } 3504 3505 /** 3506 * Returns the id of the channel this notification posts to. 3507 */ getChannelId()3508 public String getChannelId() { 3509 return mChannelId; 3510 } 3511 3512 /** @removed */ 3513 @Deprecated getTimeout()3514 public long getTimeout() { 3515 return mTimeout; 3516 } 3517 3518 /** 3519 * Returns the duration from posting after which this notification should be canceled by the 3520 * system, if it's not canceled already. 3521 */ getTimeoutAfter()3522 public long getTimeoutAfter() { 3523 return mTimeout; 3524 } 3525 3526 /** 3527 * Returns what icon should be shown for this notification if it is being displayed in a 3528 * Launcher that supports badging. Will be one of {@link #BADGE_ICON_NONE}, 3529 * {@link #BADGE_ICON_SMALL}, or {@link #BADGE_ICON_LARGE}. 3530 */ getBadgeIconType()3531 public int getBadgeIconType() { 3532 return mBadgeIcon; 3533 } 3534 3535 /** 3536 * Returns the {@link ShortcutInfo#getId() id} that this notification supersedes, if any. 3537 * 3538 * <p>Used by some Launchers that display notification content to hide shortcuts that duplicate 3539 * notifications. 3540 */ getShortcutId()3541 public String getShortcutId() { 3542 return mShortcutId; 3543 } 3544 3545 /** 3546 * Gets the {@link LocusId} associated with this notification. 3547 * 3548 * <p>Used by the device's intelligence services to correlate objects (such as 3549 * {@link ShortcutInfo} and {@link ContentCaptureContext}) that are correlated. 3550 */ 3551 @Nullable getLocusId()3552 public LocusId getLocusId() { 3553 return mLocusId; 3554 } 3555 3556 /** 3557 * Returns the settings text provided to {@link Builder#setSettingsText(CharSequence)}. 3558 */ getSettingsText()3559 public CharSequence getSettingsText() { 3560 return mSettingsText; 3561 } 3562 3563 /** 3564 * Returns which type of notifications in a group are responsible for audibly alerting the 3565 * user. See {@link #GROUP_ALERT_ALL}, {@link #GROUP_ALERT_CHILDREN}, 3566 * {@link #GROUP_ALERT_SUMMARY}. 3567 */ getGroupAlertBehavior()3568 public @GroupAlertBehavior int getGroupAlertBehavior() { 3569 return mGroupAlertBehavior; 3570 } 3571 3572 /** 3573 * Returns the bubble metadata that will be used to display app content in a floating window 3574 * over the existing foreground activity. 3575 */ 3576 @Nullable getBubbleMetadata()3577 public BubbleMetadata getBubbleMetadata() { 3578 return mBubbleMetadata; 3579 } 3580 3581 /** 3582 * Sets the {@link BubbleMetadata} for this notification. 3583 * @hide 3584 */ setBubbleMetadata(BubbleMetadata data)3585 public void setBubbleMetadata(BubbleMetadata data) { 3586 mBubbleMetadata = data; 3587 } 3588 3589 /** 3590 * Returns whether the platform is allowed (by the app developer) to generate contextual actions 3591 * for this notification. 3592 */ getAllowSystemGeneratedContextualActions()3593 public boolean getAllowSystemGeneratedContextualActions() { 3594 return mAllowSystemGeneratedContextualActions; 3595 } 3596 3597 /** 3598 * The small icon representing this notification in the status bar and content view. 3599 * 3600 * @return the small icon representing this notification. 3601 * 3602 * @see Builder#getSmallIcon() 3603 * @see Builder#setSmallIcon(Icon) 3604 */ getSmallIcon()3605 public Icon getSmallIcon() { 3606 return mSmallIcon; 3607 } 3608 3609 /** 3610 * Used when notifying to clean up legacy small icons. 3611 * @hide 3612 */ 3613 @UnsupportedAppUsage setSmallIcon(Icon icon)3614 public void setSmallIcon(Icon icon) { 3615 mSmallIcon = icon; 3616 } 3617 3618 /** 3619 * The large icon shown in this notification's content view. 3620 * @see Builder#getLargeIcon() 3621 * @see Builder#setLargeIcon(Icon) 3622 */ getLargeIcon()3623 public Icon getLargeIcon() { 3624 return mLargeIcon; 3625 } 3626 3627 /** 3628 * @hide 3629 */ 3630 @UnsupportedAppUsage isGroupSummary()3631 public boolean isGroupSummary() { 3632 return mGroupKey != null && (flags & FLAG_GROUP_SUMMARY) != 0; 3633 } 3634 3635 /** 3636 * @hide 3637 */ 3638 @UnsupportedAppUsage isGroupChild()3639 public boolean isGroupChild() { 3640 return mGroupKey != null && (flags & FLAG_GROUP_SUMMARY) == 0; 3641 } 3642 3643 /** 3644 * @hide 3645 */ suppressAlertingDueToGrouping()3646 public boolean suppressAlertingDueToGrouping() { 3647 if (isGroupSummary() 3648 && getGroupAlertBehavior() == Notification.GROUP_ALERT_CHILDREN) { 3649 return true; 3650 } else if (isGroupChild() 3651 && getGroupAlertBehavior() == Notification.GROUP_ALERT_SUMMARY) { 3652 return true; 3653 } 3654 return false; 3655 } 3656 3657 3658 /** 3659 * Finds and returns a remote input and its corresponding action. 3660 * 3661 * @param requiresFreeform requires the remoteinput to allow freeform or not. 3662 * @return the result pair, {@code null} if no result is found. 3663 */ 3664 @Nullable findRemoteInputActionPair(boolean requiresFreeform)3665 public Pair<RemoteInput, Action> findRemoteInputActionPair(boolean requiresFreeform) { 3666 if (actions == null) { 3667 return null; 3668 } 3669 for (Notification.Action action : actions) { 3670 if (action.getRemoteInputs() == null) { 3671 continue; 3672 } 3673 RemoteInput resultRemoteInput = null; 3674 for (RemoteInput remoteInput : action.getRemoteInputs()) { 3675 if (remoteInput.getAllowFreeFormInput() || !requiresFreeform) { 3676 resultRemoteInput = remoteInput; 3677 } 3678 } 3679 if (resultRemoteInput != null) { 3680 return Pair.create(resultRemoteInput, action); 3681 } 3682 } 3683 return null; 3684 } 3685 3686 /** 3687 * Returns the actions that are contextual (that is, suggested because of the content of the 3688 * notification) out of the actions in this notification. 3689 */ getContextualActions()3690 public @NonNull List<Notification.Action> getContextualActions() { 3691 if (actions == null) return Collections.emptyList(); 3692 3693 List<Notification.Action> contextualActions = new ArrayList<>(); 3694 for (Notification.Action action : actions) { 3695 if (action.isContextual()) { 3696 contextualActions.add(action); 3697 } 3698 } 3699 return contextualActions; 3700 } 3701 3702 /** 3703 * Builder class for {@link Notification} objects. 3704 * 3705 * Provides a convenient way to set the various fields of a {@link Notification} and generate 3706 * content views using the platform's notification layout template. If your app supports 3707 * versions of Android as old as API level 4, you can instead use 3708 * {@link android.support.v4.app.NotificationCompat.Builder NotificationCompat.Builder}, 3709 * available in the <a href="{@docRoot}tools/extras/support-library.html">Android Support 3710 * library</a>. 3711 * 3712 * <p>Example: 3713 * 3714 * <pre class="prettyprint"> 3715 * Notification noti = new Notification.Builder(mContext) 3716 * .setContentTitle("New mail from " + sender.toString()) 3717 * .setContentText(subject) 3718 * .setSmallIcon(R.drawable.new_mail) 3719 * .setLargeIcon(aBitmap) 3720 * .build(); 3721 * </pre> 3722 */ 3723 public static class Builder { 3724 /** 3725 * @hide 3726 */ 3727 public static final String EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT = 3728 "android.rebuild.contentViewActionCount"; 3729 /** 3730 * @hide 3731 */ 3732 public static final String EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT 3733 = "android.rebuild.bigViewActionCount"; 3734 /** 3735 * @hide 3736 */ 3737 public static final String EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT 3738 = "android.rebuild.hudViewActionCount"; 3739 3740 private static final boolean USE_ONLY_TITLE_IN_LOW_PRIORITY_SUMMARY = 3741 SystemProperties.getBoolean("notifications.only_title", true); 3742 3743 /** 3744 * The lightness difference that has to be added to the primary text color to obtain the 3745 * secondary text color when the background is light. 3746 */ 3747 private static final int LIGHTNESS_TEXT_DIFFERENCE_LIGHT = 20; 3748 3749 /** 3750 * The lightness difference that has to be added to the primary text color to obtain the 3751 * secondary text color when the background is dark. 3752 * A bit less then the above value, since it looks better on dark backgrounds. 3753 */ 3754 private static final int LIGHTNESS_TEXT_DIFFERENCE_DARK = -10; 3755 3756 private Context mContext; 3757 private Notification mN; 3758 private Bundle mUserExtras = new Bundle(); 3759 private Style mStyle; 3760 @UnsupportedAppUsage 3761 private ArrayList<Action> mActions = new ArrayList<>(MAX_ACTION_BUTTONS); 3762 private ArrayList<Person> mPersonList = new ArrayList<>(); 3763 private ContrastColorUtil mColorUtil; 3764 private boolean mIsLegacy; 3765 private boolean mIsLegacyInitialized; 3766 3767 /** 3768 * Caches an instance of StandardTemplateParams. Note that this may have been used before, 3769 * so make sure to call {@link StandardTemplateParams#reset()} before using it. 3770 */ 3771 StandardTemplateParams mParams = new StandardTemplateParams(); 3772 Colors mColors = new Colors(); 3773 3774 private boolean mTintActionButtons; 3775 private boolean mInNightMode; 3776 3777 /** 3778 * Constructs a new Builder with the defaults: 3779 * 3780 * @param context 3781 * A {@link Context} that will be used by the Builder to construct the 3782 * RemoteViews. The Context will not be held past the lifetime of this Builder 3783 * object. 3784 * @param channelId 3785 * The constructed Notification will be posted on this 3786 * {@link NotificationChannel}. To use a NotificationChannel, it must first be 3787 * created using {@link NotificationManager#createNotificationChannel}. 3788 */ Builder(Context context, String channelId)3789 public Builder(Context context, String channelId) { 3790 this(context, (Notification) null); 3791 mN.mChannelId = channelId; 3792 } 3793 3794 /** 3795 * @deprecated use {@link #Builder(Context, String)} 3796 * instead. All posted Notifications must specify a NotificationChannel Id. 3797 */ 3798 @Deprecated Builder(Context context)3799 public Builder(Context context) { 3800 this(context, (Notification) null); 3801 } 3802 3803 /** 3804 * @hide 3805 */ Builder(Context context, Notification toAdopt)3806 public Builder(Context context, Notification toAdopt) { 3807 mContext = context; 3808 Resources res = mContext.getResources(); 3809 mTintActionButtons = res.getBoolean(R.bool.config_tintNotificationActionButtons); 3810 3811 if (res.getBoolean(R.bool.config_enableNightMode)) { 3812 Configuration currentConfig = res.getConfiguration(); 3813 mInNightMode = (currentConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK) 3814 == Configuration.UI_MODE_NIGHT_YES; 3815 } 3816 3817 if (toAdopt == null) { 3818 mN = new Notification(); 3819 if (context.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N) { 3820 mN.extras.putBoolean(EXTRA_SHOW_WHEN, true); 3821 } 3822 mN.priority = PRIORITY_DEFAULT; 3823 mN.visibility = VISIBILITY_PRIVATE; 3824 } else { 3825 mN = toAdopt; 3826 if (mN.actions != null) { 3827 Collections.addAll(mActions, mN.actions); 3828 } 3829 3830 if (mN.extras.containsKey(EXTRA_PEOPLE_LIST)) { 3831 ArrayList<Person> people = mN.extras.getParcelableArrayList(EXTRA_PEOPLE_LIST); 3832 mPersonList.addAll(people); 3833 } 3834 3835 if (mN.getSmallIcon() == null && mN.icon != 0) { 3836 setSmallIcon(mN.icon); 3837 } 3838 3839 if (mN.getLargeIcon() == null && mN.largeIcon != null) { 3840 setLargeIcon(mN.largeIcon); 3841 } 3842 3843 String templateClass = mN.extras.getString(EXTRA_TEMPLATE); 3844 if (!TextUtils.isEmpty(templateClass)) { 3845 final Class<? extends Style> styleClass 3846 = getNotificationStyleClass(templateClass); 3847 if (styleClass == null) { 3848 Log.d(TAG, "Unknown style class: " + templateClass); 3849 } else { 3850 try { 3851 final Constructor<? extends Style> ctor = 3852 styleClass.getDeclaredConstructor(); 3853 ctor.setAccessible(true); 3854 final Style style = ctor.newInstance(); 3855 style.restoreFromExtras(mN.extras); 3856 3857 if (style != null) { 3858 setStyle(style); 3859 } 3860 } catch (Throwable t) { 3861 Log.e(TAG, "Could not create Style", t); 3862 } 3863 } 3864 } 3865 } 3866 } 3867 getColorUtil()3868 private ContrastColorUtil getColorUtil() { 3869 if (mColorUtil == null) { 3870 mColorUtil = ContrastColorUtil.getInstance(mContext); 3871 } 3872 return mColorUtil; 3873 } 3874 3875 /** 3876 * From Android 11, messaging notifications (those that use {@link MessagingStyle}) that 3877 * use this method to link to a published long-lived sharing shortcut may appear in a 3878 * dedicated Conversation section of the shade and may show configuration options that 3879 * are unique to conversations. This behavior should be reserved for person to person(s) 3880 * conversations where there is a likely social obligation for an individual to respond. 3881 * <p> 3882 * For example, the following are some examples of notifications that belong in the 3883 * conversation space: 3884 * <ul> 3885 * <li>1:1 conversations between two individuals</li> 3886 * <li>Group conversations between individuals where everyone can contribute</li> 3887 * </ul> 3888 * And the following are some examples of notifications that do not belong in the 3889 * conversation space: 3890 * <ul> 3891 * <li>Advertisements from a bot (even if personal and contextualized)</li> 3892 * <li>Engagement notifications from a bot</li> 3893 * <li>Directional conversations where there is an active speaker and many passive 3894 * individuals</li> 3895 * <li>Stream / posting updates from other individuals</li> 3896 * <li>Email, document comments, or other conversation types that are not real-time</li> 3897 * </ul> 3898 * </p> 3899 * 3900 * <p> 3901 * Additionally, this method can be used for all types of notifications to mark this 3902 * notification as duplicative of a Launcher shortcut. Launchers that show badges or 3903 * notification content may then suppress the shortcut in favor of the content of this 3904 * notification. 3905 * <p> 3906 * If this notification has {@link BubbleMetadata} attached that was created with 3907 * a shortcutId a check will be performed to ensure the shortcutId supplied to bubble 3908 * metadata matches the shortcutId set here, if one was set. If the shortcutId's were 3909 * specified but do not match, an exception is thrown. 3910 * 3911 * @param shortcutId the {@link ShortcutInfo#getId() id} of the shortcut this notification 3912 * is linked to 3913 * 3914 * @see BubbleMetadata.Builder#Builder(String) 3915 */ 3916 @NonNull setShortcutId(String shortcutId)3917 public Builder setShortcutId(String shortcutId) { 3918 mN.mShortcutId = shortcutId; 3919 return this; 3920 } 3921 3922 /** 3923 * Sets the {@link LocusId} associated with this notification. 3924 * 3925 * <p>This method should be called when the {@link LocusId} is used in other places (such 3926 * as {@link ShortcutInfo} and {@link ContentCaptureContext}) so the device's intelligence 3927 * services can correlate them. 3928 */ 3929 @NonNull setLocusId(@ullable LocusId locusId)3930 public Builder setLocusId(@Nullable LocusId locusId) { 3931 mN.mLocusId = locusId; 3932 return this; 3933 } 3934 3935 /** 3936 * Sets which icon to display as a badge for this notification. 3937 * 3938 * Must be one of {@link #BADGE_ICON_NONE}, {@link #BADGE_ICON_SMALL}, 3939 * {@link #BADGE_ICON_LARGE}. 3940 * 3941 * Note: This value might be ignored, for launchers that don't support badge icons. 3942 */ 3943 @NonNull setBadgeIconType(int icon)3944 public Builder setBadgeIconType(int icon) { 3945 mN.mBadgeIcon = icon; 3946 return this; 3947 } 3948 3949 /** 3950 * Sets the group alert behavior for this notification. Use this method to mute this 3951 * notification if alerts for this notification's group should be handled by a different 3952 * notification. This is only applicable for notifications that belong to a 3953 * {@link #setGroup(String) group}. This must be called on all notifications you want to 3954 * mute. For example, if you want only the summary of your group to make noise, all 3955 * children in the group should have the group alert behavior {@link #GROUP_ALERT_SUMMARY}. 3956 * 3957 * <p> The default value is {@link #GROUP_ALERT_ALL}.</p> 3958 */ 3959 @NonNull setGroupAlertBehavior(@roupAlertBehavior int groupAlertBehavior)3960 public Builder setGroupAlertBehavior(@GroupAlertBehavior int groupAlertBehavior) { 3961 mN.mGroupAlertBehavior = groupAlertBehavior; 3962 return this; 3963 } 3964 3965 /** 3966 * Sets the {@link BubbleMetadata} that will be used to display app content in a floating 3967 * window over the existing foreground activity. 3968 * 3969 * <p>This data will be ignored unless the notification is posted to a channel that 3970 * allows {@link NotificationChannel#canBubble() bubbles}.</p> 3971 * 3972 * <p>Notifications allowed to bubble that have valid bubble metadata will display in 3973 * collapsed state outside of the notification shade on unlocked devices. When a user 3974 * interacts with the collapsed state, the bubble intent will be invoked and displayed.</p> 3975 */ 3976 @NonNull setBubbleMetadata(@ullable BubbleMetadata data)3977 public Builder setBubbleMetadata(@Nullable BubbleMetadata data) { 3978 mN.mBubbleMetadata = data; 3979 return this; 3980 } 3981 3982 /** @removed */ 3983 @Deprecated setChannel(String channelId)3984 public Builder setChannel(String channelId) { 3985 mN.mChannelId = channelId; 3986 return this; 3987 } 3988 3989 /** 3990 * Specifies the channel the notification should be delivered on. 3991 */ 3992 @NonNull setChannelId(String channelId)3993 public Builder setChannelId(String channelId) { 3994 mN.mChannelId = channelId; 3995 return this; 3996 } 3997 3998 /** @removed */ 3999 @Deprecated setTimeout(long durationMs)4000 public Builder setTimeout(long durationMs) { 4001 mN.mTimeout = durationMs; 4002 return this; 4003 } 4004 4005 /** 4006 * Specifies a duration in milliseconds after which this notification should be canceled, 4007 * if it is not already canceled. 4008 */ 4009 @NonNull setTimeoutAfter(long durationMs)4010 public Builder setTimeoutAfter(long durationMs) { 4011 mN.mTimeout = durationMs; 4012 return this; 4013 } 4014 4015 /** 4016 * Add a timestamp pertaining to the notification (usually the time the event occurred). 4017 * 4018 * For apps targeting {@link android.os.Build.VERSION_CODES#N} and above, this time is not 4019 * shown anymore by default and must be opted into by using 4020 * {@link android.app.Notification.Builder#setShowWhen(boolean)} 4021 * 4022 * @see Notification#when 4023 */ 4024 @NonNull setWhen(long when)4025 public Builder setWhen(long when) { 4026 mN.when = when; 4027 return this; 4028 } 4029 4030 /** 4031 * Control whether the timestamp set with {@link #setWhen(long) setWhen} is shown 4032 * in the content view. 4033 * For apps targeting {@link android.os.Build.VERSION_CODES#N} and above, this defaults to 4034 * {@code false}. For earlier apps, the default is {@code true}. 4035 */ 4036 @NonNull setShowWhen(boolean show)4037 public Builder setShowWhen(boolean show) { 4038 mN.extras.putBoolean(EXTRA_SHOW_WHEN, show); 4039 return this; 4040 } 4041 4042 /** 4043 * Show the {@link Notification#when} field as a stopwatch. 4044 * 4045 * Instead of presenting <code>when</code> as a timestamp, the notification will show an 4046 * automatically updating display of the minutes and seconds since <code>when</code>. 4047 * 4048 * Useful when showing an elapsed time (like an ongoing phone call). 4049 * 4050 * The counter can also be set to count down to <code>when</code> when using 4051 * {@link #setChronometerCountDown(boolean)}. 4052 * 4053 * @see android.widget.Chronometer 4054 * @see Notification#when 4055 * @see #setChronometerCountDown(boolean) 4056 */ 4057 @NonNull setUsesChronometer(boolean b)4058 public Builder setUsesChronometer(boolean b) { 4059 mN.extras.putBoolean(EXTRA_SHOW_CHRONOMETER, b); 4060 return this; 4061 } 4062 4063 /** 4064 * Sets the Chronometer to count down instead of counting up. 4065 * 4066 * <p>This is only relevant if {@link #setUsesChronometer(boolean)} has been set to true. 4067 * If it isn't set the chronometer will count up. 4068 * 4069 * @see #setUsesChronometer(boolean) 4070 */ 4071 @NonNull setChronometerCountDown(boolean countDown)4072 public Builder setChronometerCountDown(boolean countDown) { 4073 mN.extras.putBoolean(EXTRA_CHRONOMETER_COUNT_DOWN, countDown); 4074 return this; 4075 } 4076 4077 /** 4078 * Set the small icon resource, which will be used to represent the notification in the 4079 * status bar. 4080 * 4081 4082 * The platform template for the expanded view will draw this icon in the left, unless a 4083 * {@link #setLargeIcon(Bitmap) large icon} has also been specified, in which case the small 4084 * icon will be moved to the right-hand side. 4085 * 4086 4087 * @param icon 4088 * A resource ID in the application's package of the drawable to use. 4089 * @see Notification#icon 4090 */ 4091 @NonNull setSmallIcon(@rawableRes int icon)4092 public Builder setSmallIcon(@DrawableRes int icon) { 4093 return setSmallIcon(icon != 0 4094 ? Icon.createWithResource(mContext, icon) 4095 : null); 4096 } 4097 4098 /** 4099 * A variant of {@link #setSmallIcon(int) setSmallIcon(int)} that takes an additional 4100 * level parameter for when the icon is a {@link android.graphics.drawable.LevelListDrawable 4101 * LevelListDrawable}. 4102 * 4103 * @param icon A resource ID in the application's package of the drawable to use. 4104 * @param level The level to use for the icon. 4105 * 4106 * @see Notification#icon 4107 * @see Notification#iconLevel 4108 */ 4109 @NonNull setSmallIcon(@rawableRes int icon, int level)4110 public Builder setSmallIcon(@DrawableRes int icon, int level) { 4111 mN.iconLevel = level; 4112 return setSmallIcon(icon); 4113 } 4114 4115 /** 4116 * Set the small icon, which will be used to represent the notification in the 4117 * status bar and content view (unless overridden there by a 4118 * {@link #setLargeIcon(Bitmap) large icon}). 4119 * 4120 * @param icon An Icon object to use. 4121 * @see Notification#icon 4122 */ 4123 @NonNull setSmallIcon(Icon icon)4124 public Builder setSmallIcon(Icon icon) { 4125 mN.setSmallIcon(icon); 4126 if (icon != null && icon.getType() == Icon.TYPE_RESOURCE) { 4127 mN.icon = icon.getResId(); 4128 } 4129 return this; 4130 } 4131 4132 /** 4133 * Set the first line of text in the platform notification template. 4134 */ 4135 @NonNull setContentTitle(CharSequence title)4136 public Builder setContentTitle(CharSequence title) { 4137 mN.extras.putCharSequence(EXTRA_TITLE, safeCharSequence(title)); 4138 return this; 4139 } 4140 4141 /** 4142 * Set the second line of text in the platform notification template. 4143 */ 4144 @NonNull setContentText(CharSequence text)4145 public Builder setContentText(CharSequence text) { 4146 mN.extras.putCharSequence(EXTRA_TEXT, safeCharSequence(text)); 4147 return this; 4148 } 4149 4150 /** 4151 * This provides some additional information that is displayed in the notification. No 4152 * guarantees are given where exactly it is displayed. 4153 * 4154 * <p>This information should only be provided if it provides an essential 4155 * benefit to the understanding of the notification. The more text you provide the 4156 * less readable it becomes. For example, an email client should only provide the account 4157 * name here if more than one email account has been added.</p> 4158 * 4159 * <p>As of {@link android.os.Build.VERSION_CODES#N} this information is displayed in the 4160 * notification header area. 4161 * 4162 * On Android versions before {@link android.os.Build.VERSION_CODES#N} 4163 * this will be shown in the third line of text in the platform notification template. 4164 * You should not be using {@link #setProgress(int, int, boolean)} at the 4165 * same time on those versions; they occupy the same place. 4166 * </p> 4167 */ 4168 @NonNull setSubText(CharSequence text)4169 public Builder setSubText(CharSequence text) { 4170 mN.extras.putCharSequence(EXTRA_SUB_TEXT, safeCharSequence(text)); 4171 return this; 4172 } 4173 4174 /** 4175 * Provides text that will appear as a link to your application's settings. 4176 * 4177 * <p>This text does not appear within notification {@link Style templates} but may 4178 * appear when the user uses an affordance to learn more about the notification. 4179 * Additionally, this text will not appear unless you provide a valid link target by 4180 * handling {@link #INTENT_CATEGORY_NOTIFICATION_PREFERENCES}. 4181 * 4182 * <p>This text is meant to be concise description about what the user can customize 4183 * when they click on this link. The recommended maximum length is 40 characters. 4184 * @param text 4185 * @return 4186 */ 4187 @NonNull setSettingsText(CharSequence text)4188 public Builder setSettingsText(CharSequence text) { 4189 mN.mSettingsText = safeCharSequence(text); 4190 return this; 4191 } 4192 4193 /** 4194 * Set the remote input history. 4195 * 4196 * This should be set to the most recent inputs that have been sent 4197 * through a {@link RemoteInput} of this Notification and cleared once the it is no 4198 * longer relevant (e.g. for chat notifications once the other party has responded). 4199 * 4200 * The most recent input must be stored at the 0 index, the second most recent at the 4201 * 1 index, etc. Note that the system will limit both how far back the inputs will be shown 4202 * and how much of each individual input is shown. 4203 * 4204 * <p>Note: The reply text will only be shown on notifications that have least one action 4205 * with a {@code RemoteInput}.</p> 4206 */ 4207 @NonNull setRemoteInputHistory(CharSequence[] text)4208 public Builder setRemoteInputHistory(CharSequence[] text) { 4209 if (text == null) { 4210 mN.extras.putCharSequenceArray(EXTRA_REMOTE_INPUT_HISTORY, null); 4211 } else { 4212 final int itemCount = Math.min(MAX_REPLY_HISTORY, text.length); 4213 CharSequence[] safe = new CharSequence[itemCount]; 4214 RemoteInputHistoryItem[] items = new RemoteInputHistoryItem[itemCount]; 4215 for (int i = 0; i < itemCount; i++) { 4216 safe[i] = safeCharSequence(text[i]); 4217 items[i] = new RemoteInputHistoryItem(text[i]); 4218 } 4219 mN.extras.putCharSequenceArray(EXTRA_REMOTE_INPUT_HISTORY, safe); 4220 4221 // Also add these messages as structured history items. 4222 mN.extras.putParcelableArray(EXTRA_REMOTE_INPUT_HISTORY_ITEMS, items); 4223 } 4224 return this; 4225 } 4226 4227 /** 4228 * Set the remote input history, with support for embedding URIs and mime types for 4229 * images and other media. 4230 * @hide 4231 */ 4232 @NonNull setRemoteInputHistory(RemoteInputHistoryItem[] items)4233 public Builder setRemoteInputHistory(RemoteInputHistoryItem[] items) { 4234 if (items == null) { 4235 mN.extras.putParcelableArray(EXTRA_REMOTE_INPUT_HISTORY_ITEMS, null); 4236 } else { 4237 final int itemCount = Math.min(MAX_REPLY_HISTORY, items.length); 4238 RemoteInputHistoryItem[] history = new RemoteInputHistoryItem[itemCount]; 4239 for (int i = 0; i < itemCount; i++) { 4240 history[i] = items[i]; 4241 } 4242 mN.extras.putParcelableArray(EXTRA_REMOTE_INPUT_HISTORY_ITEMS, history); 4243 } 4244 return this; 4245 } 4246 4247 /** 4248 * Sets whether remote history entries view should have a spinner. 4249 * @hide 4250 */ 4251 @NonNull setShowRemoteInputSpinner(boolean showSpinner)4252 public Builder setShowRemoteInputSpinner(boolean showSpinner) { 4253 mN.extras.putBoolean(EXTRA_SHOW_REMOTE_INPUT_SPINNER, showSpinner); 4254 return this; 4255 } 4256 4257 /** 4258 * Sets whether smart reply buttons should be hidden. 4259 * @hide 4260 */ 4261 @NonNull setHideSmartReplies(boolean hideSmartReplies)4262 public Builder setHideSmartReplies(boolean hideSmartReplies) { 4263 mN.extras.putBoolean(EXTRA_HIDE_SMART_REPLIES, hideSmartReplies); 4264 return this; 4265 } 4266 4267 /** 4268 * Sets the number of items this notification represents. May be displayed as a badge count 4269 * for Launchers that support badging. 4270 */ 4271 @NonNull setNumber(int number)4272 public Builder setNumber(int number) { 4273 mN.number = number; 4274 return this; 4275 } 4276 4277 /** 4278 * A small piece of additional information pertaining to this notification. 4279 * 4280 * The platform template will draw this on the last line of the notification, at the far 4281 * right (to the right of a smallIcon if it has been placed there). 4282 * 4283 * @deprecated use {@link #setSubText(CharSequence)} instead to set a text in the header. 4284 * For legacy apps targeting a version below {@link android.os.Build.VERSION_CODES#N} this 4285 * field will still show up, but the subtext will take precedence. 4286 */ 4287 @Deprecated setContentInfo(CharSequence info)4288 public Builder setContentInfo(CharSequence info) { 4289 mN.extras.putCharSequence(EXTRA_INFO_TEXT, safeCharSequence(info)); 4290 return this; 4291 } 4292 4293 /** 4294 * Set the progress this notification represents. 4295 * 4296 * The platform template will represent this using a {@link ProgressBar}. 4297 */ 4298 @NonNull setProgress(int max, int progress, boolean indeterminate)4299 public Builder setProgress(int max, int progress, boolean indeterminate) { 4300 mN.extras.putInt(EXTRA_PROGRESS, progress); 4301 mN.extras.putInt(EXTRA_PROGRESS_MAX, max); 4302 mN.extras.putBoolean(EXTRA_PROGRESS_INDETERMINATE, indeterminate); 4303 return this; 4304 } 4305 4306 /** 4307 * Supply a custom RemoteViews to use instead of the platform template. 4308 * 4309 * Use {@link #setCustomContentView(RemoteViews)} instead. 4310 */ 4311 @Deprecated setContent(RemoteViews views)4312 public Builder setContent(RemoteViews views) { 4313 return setCustomContentView(views); 4314 } 4315 4316 /** 4317 * Supply custom RemoteViews to use instead of the platform template. 4318 * 4319 * This will override the layout that would otherwise be constructed by this Builder 4320 * object. 4321 */ 4322 @NonNull setCustomContentView(RemoteViews contentView)4323 public Builder setCustomContentView(RemoteViews contentView) { 4324 mN.contentView = contentView; 4325 return this; 4326 } 4327 4328 /** 4329 * Supply custom RemoteViews to use instead of the platform template in the expanded form. 4330 * 4331 * This will override the expanded layout that would otherwise be constructed by this 4332 * Builder object. 4333 */ 4334 @NonNull setCustomBigContentView(RemoteViews contentView)4335 public Builder setCustomBigContentView(RemoteViews contentView) { 4336 mN.bigContentView = contentView; 4337 return this; 4338 } 4339 4340 /** 4341 * Supply custom RemoteViews to use instead of the platform template in the heads up dialog. 4342 * 4343 * This will override the heads-up layout that would otherwise be constructed by this 4344 * Builder object. 4345 */ 4346 @NonNull setCustomHeadsUpContentView(RemoteViews contentView)4347 public Builder setCustomHeadsUpContentView(RemoteViews contentView) { 4348 mN.headsUpContentView = contentView; 4349 return this; 4350 } 4351 4352 /** 4353 * Supply a {@link PendingIntent} to be sent when the notification is clicked. 4354 * 4355 * <p>As of Android {@link android.os.Build.VERSION_CODES#S}, apps targeting API level 4356 * {@link android.os.Build.VERSION_CODES#S} or higher won't be able to start activities 4357 * while processing broadcast receivers or services in response to notification clicks. To 4358 * launch an activity in those cases, provide a {@link PendingIntent} for the activity 4359 * itself. 4360 * 4361 * <p>As of {@link android.os.Build.VERSION_CODES#HONEYCOMB}, if this field is unset and you 4362 * have specified a custom RemoteViews with {@link #setContent(RemoteViews)}, you can use 4363 * {@link RemoteViews#setOnClickPendingIntent RemoteViews.setOnClickPendingIntent(int,PendingIntent)} 4364 * to assign PendingIntents to individual views in that custom layout (i.e., to create 4365 * clickable buttons inside the notification view). 4366 * 4367 * @see Notification#contentIntent Notification.contentIntent 4368 */ 4369 @NonNull setContentIntent(PendingIntent intent)4370 public Builder setContentIntent(PendingIntent intent) { 4371 mN.contentIntent = intent; 4372 return this; 4373 } 4374 4375 /** 4376 * Supply a {@link PendingIntent} to send when the notification is cleared explicitly by the user. 4377 * 4378 * @see Notification#deleteIntent 4379 */ 4380 @NonNull setDeleteIntent(PendingIntent intent)4381 public Builder setDeleteIntent(PendingIntent intent) { 4382 mN.deleteIntent = intent; 4383 return this; 4384 } 4385 4386 /** 4387 * An intent to launch instead of posting the notification to the status bar. 4388 * Only for use with extremely high-priority notifications demanding the user's 4389 * <strong>immediate</strong> attention, such as an incoming phone call or 4390 * alarm clock that the user has explicitly set to a particular time. 4391 * If this facility is used for something else, please give the user an option 4392 * to turn it off and use a normal notification, as this can be extremely 4393 * disruptive. 4394 * 4395 * <p> 4396 * The system UI may choose to display a heads-up notification, instead of 4397 * launching this intent, while the user is using the device. 4398 * </p> 4399 * <p>Apps targeting {@link Build.VERSION_CODES#Q} and above will have to request 4400 * a permission ({@link android.Manifest.permission#USE_FULL_SCREEN_INTENT}) in order to 4401 * use full screen intents.</p> 4402 * 4403 * @param intent The pending intent to launch. 4404 * @param highPriority Passing true will cause this notification to be sent 4405 * even if other notifications are suppressed. 4406 * 4407 * @see Notification#fullScreenIntent 4408 */ 4409 @NonNull setFullScreenIntent(PendingIntent intent, boolean highPriority)4410 public Builder setFullScreenIntent(PendingIntent intent, boolean highPriority) { 4411 mN.fullScreenIntent = intent; 4412 setFlag(FLAG_HIGH_PRIORITY, highPriority); 4413 return this; 4414 } 4415 4416 /** 4417 * Set the "ticker" text which is sent to accessibility services. 4418 * 4419 * @see Notification#tickerText 4420 */ 4421 @NonNull setTicker(CharSequence tickerText)4422 public Builder setTicker(CharSequence tickerText) { 4423 mN.tickerText = safeCharSequence(tickerText); 4424 return this; 4425 } 4426 4427 /** 4428 * Obsolete version of {@link #setTicker(CharSequence)}. 4429 * 4430 */ 4431 @Deprecated setTicker(CharSequence tickerText, RemoteViews views)4432 public Builder setTicker(CharSequence tickerText, RemoteViews views) { 4433 setTicker(tickerText); 4434 // views is ignored 4435 return this; 4436 } 4437 4438 /** 4439 * Add a large icon to the notification content view. 4440 * 4441 * In the platform template, this image will be shown either on the right of the 4442 * notification, with an aspect ratio of up to 16:9, or (when the notification is grouped) 4443 * on the left in place of the {@link #setSmallIcon(Icon) small icon}. 4444 */ 4445 @NonNull setLargeIcon(Bitmap b)4446 public Builder setLargeIcon(Bitmap b) { 4447 return setLargeIcon(b != null ? Icon.createWithBitmap(b) : null); 4448 } 4449 4450 /** 4451 * Add a large icon to the notification content view. 4452 * 4453 * In the platform template, this image will be shown either on the right of the 4454 * notification, with an aspect ratio of up to 16:9, or (when the notification is grouped) 4455 * on the left in place of the {@link #setSmallIcon(Icon) small icon}. 4456 */ 4457 @NonNull setLargeIcon(Icon icon)4458 public Builder setLargeIcon(Icon icon) { 4459 mN.mLargeIcon = icon; 4460 mN.extras.putParcelable(EXTRA_LARGE_ICON, icon); 4461 return this; 4462 } 4463 4464 /** 4465 * Set the sound to play. 4466 * 4467 * It will be played using the {@link #AUDIO_ATTRIBUTES_DEFAULT default audio attributes} 4468 * for notifications. 4469 * 4470 * @deprecated use {@link NotificationChannel#setSound(Uri, AudioAttributes)} instead. 4471 */ 4472 @Deprecated setSound(Uri sound)4473 public Builder setSound(Uri sound) { 4474 mN.sound = sound; 4475 mN.audioAttributes = AUDIO_ATTRIBUTES_DEFAULT; 4476 return this; 4477 } 4478 4479 /** 4480 * Set the sound to play, along with a specific stream on which to play it. 4481 * 4482 * See {@link android.media.AudioManager} for the <code>STREAM_</code> constants. 4483 * 4484 * @deprecated use {@link NotificationChannel#setSound(Uri, AudioAttributes)}. 4485 */ 4486 @Deprecated setSound(Uri sound, int streamType)4487 public Builder setSound(Uri sound, int streamType) { 4488 PlayerBase.deprecateStreamTypeForPlayback(streamType, "Notification", "setSound()"); 4489 mN.sound = sound; 4490 mN.audioStreamType = streamType; 4491 return this; 4492 } 4493 4494 /** 4495 * Set the sound to play, along with specific {@link AudioAttributes audio attributes} to 4496 * use during playback. 4497 * 4498 * @deprecated use {@link NotificationChannel#setSound(Uri, AudioAttributes)} instead. 4499 * @see Notification#sound 4500 */ 4501 @Deprecated setSound(Uri sound, AudioAttributes audioAttributes)4502 public Builder setSound(Uri sound, AudioAttributes audioAttributes) { 4503 mN.sound = sound; 4504 mN.audioAttributes = audioAttributes; 4505 return this; 4506 } 4507 4508 /** 4509 * Set the vibration pattern to use. 4510 * 4511 * See {@link android.os.Vibrator#vibrate(long[], int)} for a discussion of the 4512 * <code>pattern</code> parameter. 4513 * 4514 * <p> 4515 * A notification that vibrates is more likely to be presented as a heads-up notification. 4516 * </p> 4517 * 4518 * @deprecated use {@link NotificationChannel#setVibrationPattern(long[])} instead. 4519 * @see Notification#vibrate 4520 */ 4521 @Deprecated setVibrate(long[] pattern)4522 public Builder setVibrate(long[] pattern) { 4523 mN.vibrate = pattern; 4524 return this; 4525 } 4526 4527 /** 4528 * Set the desired color for the indicator LED on the device, as well as the 4529 * blink duty cycle (specified in milliseconds). 4530 * 4531 4532 * Not all devices will honor all (or even any) of these values. 4533 * 4534 * @deprecated use {@link NotificationChannel#enableLights(boolean)} instead. 4535 * @see Notification#ledARGB 4536 * @see Notification#ledOnMS 4537 * @see Notification#ledOffMS 4538 */ 4539 @Deprecated setLights(@olorInt int argb, int onMs, int offMs)4540 public Builder setLights(@ColorInt int argb, int onMs, int offMs) { 4541 mN.ledARGB = argb; 4542 mN.ledOnMS = onMs; 4543 mN.ledOffMS = offMs; 4544 if (onMs != 0 || offMs != 0) { 4545 mN.flags |= FLAG_SHOW_LIGHTS; 4546 } 4547 return this; 4548 } 4549 4550 /** 4551 * Set whether this is an "ongoing" notification. 4552 * 4553 4554 * Ongoing notifications cannot be dismissed by the user, so your application or service 4555 * must take care of canceling them. 4556 * 4557 4558 * They are typically used to indicate a background task that the user is actively engaged 4559 * with (e.g., playing music) or is pending in some way and therefore occupying the device 4560 * (e.g., a file download, sync operation, active network connection). 4561 * 4562 4563 * @see Notification#FLAG_ONGOING_EVENT 4564 */ 4565 @NonNull setOngoing(boolean ongoing)4566 public Builder setOngoing(boolean ongoing) { 4567 setFlag(FLAG_ONGOING_EVENT, ongoing); 4568 return this; 4569 } 4570 4571 /** 4572 * Set whether this notification should be colorized. When set, the color set with 4573 * {@link #setColor(int)} will be used as the background color of this notification. 4574 * <p> 4575 * This should only be used for high priority ongoing tasks like navigation, an ongoing 4576 * call, or other similarly high-priority events for the user. 4577 * <p> 4578 * For most styles, the coloring will only be applied if the notification is for a 4579 * foreground service notification. 4580 * However, for {@link MediaStyle} and {@link DecoratedMediaCustomViewStyle} notifications 4581 * that have a media session attached there is no such requirement. 4582 * 4583 * @see #setColor(int) 4584 * @see MediaStyle#setMediaSession(MediaSession.Token) 4585 */ 4586 @NonNull setColorized(boolean colorize)4587 public Builder setColorized(boolean colorize) { 4588 mN.extras.putBoolean(EXTRA_COLORIZED, colorize); 4589 return this; 4590 } 4591 4592 /** 4593 * Set this flag if you would only like the sound, vibrate 4594 * and ticker to be played if the notification is not already showing. 4595 * 4596 * @see Notification#FLAG_ONLY_ALERT_ONCE 4597 */ 4598 @NonNull setOnlyAlertOnce(boolean onlyAlertOnce)4599 public Builder setOnlyAlertOnce(boolean onlyAlertOnce) { 4600 setFlag(FLAG_ONLY_ALERT_ONCE, onlyAlertOnce); 4601 return this; 4602 } 4603 4604 /** 4605 * Specify a desired visibility policy for a Notification associated with a 4606 * foreground service. By default, the system can choose to defer 4607 * visibility of the notification for a short time after the service is 4608 * started. Pass 4609 * {@link Notification#FOREGROUND_SERVICE_IMMEDIATE FOREGROUND_SERVICE_IMMEDIATE} 4610 * to this method in order to guarantee that visibility is never deferred. Pass 4611 * {@link Notification#FOREGROUND_SERVICE_DEFERRED FOREGROUND_SERVICE_DEFERRED} 4612 * to request that visibility is deferred whenever possible. 4613 * 4614 * <p class="note">Note that deferred visibility is not guaranteed. There 4615 * may be some circumstances under which the system will show the foreground 4616 * service's associated Notification immediately even when the app has used 4617 * this method to explicitly request deferred display.</p> 4618 * @param behavior One of 4619 * {@link Notification#FOREGROUND_SERVICE_DEFAULT FOREGROUND_SERVICE_DEFAULT}, 4620 * {@link Notification#FOREGROUND_SERVICE_IMMEDIATE FOREGROUND_SERVICE_IMMEDIATE}, 4621 * or {@link Notification#FOREGROUND_SERVICE_DEFERRED FOREGROUND_SERVICE_DEFERRED} 4622 * @return 4623 */ 4624 @NonNull setForegroundServiceBehavior(@erviceNotificationPolicy int behavior)4625 public Builder setForegroundServiceBehavior(@ServiceNotificationPolicy int behavior) { 4626 mN.mFgsDeferBehavior = behavior; 4627 return this; 4628 } 4629 4630 /** 4631 * Make this notification automatically dismissed when the user touches it. 4632 * 4633 * @see Notification#FLAG_AUTO_CANCEL 4634 */ 4635 @NonNull setAutoCancel(boolean autoCancel)4636 public Builder setAutoCancel(boolean autoCancel) { 4637 setFlag(FLAG_AUTO_CANCEL, autoCancel); 4638 return this; 4639 } 4640 4641 /** 4642 * Set whether or not this notification should not bridge to other devices. 4643 * 4644 * <p>Some notifications can be bridged to other devices for remote display. 4645 * This hint can be set to recommend this notification not be bridged. 4646 */ 4647 @NonNull setLocalOnly(boolean localOnly)4648 public Builder setLocalOnly(boolean localOnly) { 4649 setFlag(FLAG_LOCAL_ONLY, localOnly); 4650 return this; 4651 } 4652 4653 /** 4654 * Set which notification properties will be inherited from system defaults. 4655 * <p> 4656 * The value should be one or more of the following fields combined with 4657 * bitwise-or: 4658 * {@link #DEFAULT_SOUND}, {@link #DEFAULT_VIBRATE}, {@link #DEFAULT_LIGHTS}. 4659 * <p> 4660 * For all default values, use {@link #DEFAULT_ALL}. 4661 * 4662 * @deprecated use {@link NotificationChannel#enableVibration(boolean)} and 4663 * {@link NotificationChannel#enableLights(boolean)} and 4664 * {@link NotificationChannel#setSound(Uri, AudioAttributes)} instead. 4665 */ 4666 @Deprecated setDefaults(int defaults)4667 public Builder setDefaults(int defaults) { 4668 mN.defaults = defaults; 4669 return this; 4670 } 4671 4672 /** 4673 * Set the priority of this notification. 4674 * 4675 * @see Notification#priority 4676 * @deprecated use {@link NotificationChannel#setImportance(int)} instead. 4677 */ 4678 @Deprecated setPriority(@riority int pri)4679 public Builder setPriority(@Priority int pri) { 4680 mN.priority = pri; 4681 return this; 4682 } 4683 4684 /** 4685 * Set the notification category. 4686 * 4687 * @see Notification#category 4688 */ 4689 @NonNull setCategory(String category)4690 public Builder setCategory(String category) { 4691 mN.category = category; 4692 return this; 4693 } 4694 4695 /** 4696 * Add a person that is relevant to this notification. 4697 * 4698 * <P> 4699 * Depending on user preferences, this annotation may allow the notification to pass 4700 * through interruption filters, if this notification is of category {@link #CATEGORY_CALL} 4701 * or {@link #CATEGORY_MESSAGE}. The addition of people may also cause this notification to 4702 * appear more prominently in the user interface. 4703 * </P> 4704 * 4705 * <P> 4706 * The person should be specified by the {@code String} representation of a 4707 * {@link android.provider.ContactsContract.Contacts#CONTENT_LOOKUP_URI}. 4708 * </P> 4709 * 4710 * <P>The system will also attempt to resolve {@code mailto:} and {@code tel:} schema 4711 * URIs. The path part of these URIs must exist in the contacts database, in the 4712 * appropriate column, or the reference will be discarded as invalid. Telephone schema 4713 * URIs will be resolved by {@link android.provider.ContactsContract.PhoneLookup}. 4714 * It is also possible to provide a URI with the schema {@code name:} in order to uniquely 4715 * identify a person without an entry in the contacts database. 4716 * </P> 4717 * 4718 * @param uri A URI for the person. 4719 * @see Notification#EXTRA_PEOPLE 4720 * @deprecated use {@link #addPerson(Person)} 4721 */ addPerson(String uri)4722 public Builder addPerson(String uri) { 4723 addPerson(new Person.Builder().setUri(uri).build()); 4724 return this; 4725 } 4726 4727 /** 4728 * Add a person that is relevant to this notification. 4729 * 4730 * <P> 4731 * Depending on user preferences, this annotation may allow the notification to pass 4732 * through interruption filters, if this notification is of category {@link #CATEGORY_CALL} 4733 * or {@link #CATEGORY_MESSAGE}. The addition of people may also cause this notification to 4734 * appear more prominently in the user interface. 4735 * </P> 4736 * 4737 * <P> 4738 * A person should usually contain a uri in order to benefit from the ranking boost. 4739 * However, even if no uri is provided, it's beneficial to provide other people in the 4740 * notification, such that listeners and voice only devices can announce and handle them 4741 * properly. 4742 * </P> 4743 * 4744 * @param person the person to add. 4745 * @see Notification#EXTRA_PEOPLE_LIST 4746 */ 4747 @NonNull addPerson(Person person)4748 public Builder addPerson(Person person) { 4749 mPersonList.add(person); 4750 return this; 4751 } 4752 4753 /** 4754 * Set this notification to be part of a group of notifications sharing the same key. 4755 * Grouped notifications may display in a cluster or stack on devices which 4756 * support such rendering. 4757 * 4758 * <p>To make this notification the summary for its group, also call 4759 * {@link #setGroupSummary}. A sort order can be specified for group members by using 4760 * {@link #setSortKey}. 4761 * @param groupKey The group key of the group. 4762 * @return this object for method chaining 4763 */ 4764 @NonNull setGroup(String groupKey)4765 public Builder setGroup(String groupKey) { 4766 mN.mGroupKey = groupKey; 4767 return this; 4768 } 4769 4770 /** 4771 * Set this notification to be the group summary for a group of notifications. 4772 * Grouped notifications may display in a cluster or stack on devices which 4773 * support such rendering. If thereRequires a group key also be set using {@link #setGroup}. 4774 * The group summary may be suppressed if too few notifications are included in the group. 4775 * @param isGroupSummary Whether this notification should be a group summary. 4776 * @return this object for method chaining 4777 */ 4778 @NonNull setGroupSummary(boolean isGroupSummary)4779 public Builder setGroupSummary(boolean isGroupSummary) { 4780 setFlag(FLAG_GROUP_SUMMARY, isGroupSummary); 4781 return this; 4782 } 4783 4784 /** 4785 * Set a sort key that orders this notification among other notifications from the 4786 * same package. This can be useful if an external sort was already applied and an app 4787 * would like to preserve this. Notifications will be sorted lexicographically using this 4788 * value, although providing different priorities in addition to providing sort key may 4789 * cause this value to be ignored. 4790 * 4791 * <p>This sort key can also be used to order members of a notification group. See 4792 * {@link #setGroup}. 4793 * 4794 * @see String#compareTo(String) 4795 */ 4796 @NonNull setSortKey(String sortKey)4797 public Builder setSortKey(String sortKey) { 4798 mN.mSortKey = sortKey; 4799 return this; 4800 } 4801 4802 /** 4803 * Merge additional metadata into this notification. 4804 * 4805 * <p>Values within the Bundle will replace existing extras values in this Builder. 4806 * 4807 * @see Notification#extras 4808 */ 4809 @NonNull addExtras(Bundle extras)4810 public Builder addExtras(Bundle extras) { 4811 if (extras != null) { 4812 mUserExtras.putAll(extras); 4813 } 4814 return this; 4815 } 4816 4817 /** 4818 * Set metadata for this notification. 4819 * 4820 * <p>A reference to the Bundle is held for the lifetime of this Builder, and the Bundle's 4821 * current contents are copied into the Notification each time {@link #build()} is 4822 * called. 4823 * 4824 * <p>Replaces any existing extras values with those from the provided Bundle. 4825 * Use {@link #addExtras} to merge in metadata instead. 4826 * 4827 * @see Notification#extras 4828 */ 4829 @NonNull setExtras(Bundle extras)4830 public Builder setExtras(Bundle extras) { 4831 if (extras != null) { 4832 mUserExtras = extras; 4833 } 4834 return this; 4835 } 4836 4837 /** 4838 * Get the current metadata Bundle used by this notification Builder. 4839 * 4840 * <p>The returned Bundle is shared with this Builder. 4841 * 4842 * <p>The current contents of this Bundle are copied into the Notification each time 4843 * {@link #build()} is called. 4844 * 4845 * @see Notification#extras 4846 */ getExtras()4847 public Bundle getExtras() { 4848 return mUserExtras; 4849 } 4850 getAllExtras()4851 private Bundle getAllExtras() { 4852 final Bundle saveExtras = (Bundle) mUserExtras.clone(); 4853 saveExtras.putAll(mN.extras); 4854 return saveExtras; 4855 } 4856 4857 /** 4858 * Add an action to this notification. Actions are typically displayed by 4859 * the system as a button adjacent to the notification content. 4860 * <p> 4861 * Every action must have an icon (32dp square and matching the 4862 * <a href="{@docRoot}design/style/iconography.html#action-bar">Holo 4863 * Dark action bar</a> visual style), a textual label, and a {@link PendingIntent}. 4864 * <p> 4865 * A notification in its expanded form can display up to 3 actions, from left to right in 4866 * the order they were added. Actions will not be displayed when the notification is 4867 * collapsed, however, so be sure that any essential functions may be accessed by the user 4868 * in some other way (for example, in the Activity pointed to by {@link #contentIntent}). 4869 * <p> 4870 * As of Android {@link android.os.Build.VERSION_CODES#S}, apps targeting API level 4871 * {@link android.os.Build.VERSION_CODES#S} or higher won't be able to start activities 4872 * while processing broadcast receivers or services in response to notification action 4873 * clicks. To launch an activity in those cases, provide a {@link PendingIntent} to the 4874 * activity itself. 4875 * 4876 * @param icon Resource ID of a drawable that represents the action. 4877 * @param title Text describing the action. 4878 * @param intent PendingIntent to be fired when the action is invoked. 4879 * 4880 * @deprecated Use {@link #addAction(Action)} instead. 4881 */ 4882 @Deprecated addAction(int icon, CharSequence title, PendingIntent intent)4883 public Builder addAction(int icon, CharSequence title, PendingIntent intent) { 4884 mActions.add(new Action(icon, safeCharSequence(title), intent)); 4885 return this; 4886 } 4887 4888 /** 4889 * Add an action to this notification. Actions are typically displayed by 4890 * the system as a button adjacent to the notification content. 4891 * <p> 4892 * Every action must have an icon (32dp square and matching the 4893 * <a href="{@docRoot}design/style/iconography.html#action-bar">Holo 4894 * Dark action bar</a> visual style), a textual label, and a {@link PendingIntent}. 4895 * <p> 4896 * A notification in its expanded form can display up to 3 actions, from left to right in 4897 * the order they were added. Actions will not be displayed when the notification is 4898 * collapsed, however, so be sure that any essential functions may be accessed by the user 4899 * in some other way (for example, in the Activity pointed to by {@link #contentIntent}). 4900 * 4901 * @param action The action to add. 4902 */ 4903 @NonNull addAction(Action action)4904 public Builder addAction(Action action) { 4905 if (action != null) { 4906 mActions.add(action); 4907 } 4908 return this; 4909 } 4910 4911 /** 4912 * Alter the complete list of actions attached to this notification. 4913 * @see #addAction(Action). 4914 * 4915 * @param actions 4916 * @return 4917 */ 4918 @NonNull setActions(Action... actions)4919 public Builder setActions(Action... actions) { 4920 mActions.clear(); 4921 for (int i = 0; i < actions.length; i++) { 4922 if (actions[i] != null) { 4923 mActions.add(actions[i]); 4924 } 4925 } 4926 return this; 4927 } 4928 4929 /** 4930 * Add a rich notification style to be applied at build time. 4931 * 4932 * @param style Object responsible for modifying the notification style. 4933 */ 4934 @NonNull setStyle(Style style)4935 public Builder setStyle(Style style) { 4936 if (mStyle != style) { 4937 mStyle = style; 4938 if (mStyle != null) { 4939 mStyle.setBuilder(this); 4940 mN.extras.putString(EXTRA_TEMPLATE, style.getClass().getName()); 4941 } else { 4942 mN.extras.remove(EXTRA_TEMPLATE); 4943 } 4944 } 4945 return this; 4946 } 4947 4948 /** 4949 * Returns the style set by {@link #setStyle(Style)}. 4950 */ getStyle()4951 public Style getStyle() { 4952 return mStyle; 4953 } 4954 4955 /** 4956 * Specify the value of {@link #visibility}. 4957 * 4958 * @return The same Builder. 4959 */ 4960 @NonNull setVisibility(@isibility int visibility)4961 public Builder setVisibility(@Visibility int visibility) { 4962 mN.visibility = visibility; 4963 return this; 4964 } 4965 4966 /** 4967 * Supply a replacement Notification whose contents should be shown in insecure contexts 4968 * (i.e. atop the secure lockscreen). See {@link #visibility} and {@link #VISIBILITY_PUBLIC}. 4969 * @param n A replacement notification, presumably with some or all info redacted. 4970 * @return The same Builder. 4971 */ 4972 @NonNull setPublicVersion(Notification n)4973 public Builder setPublicVersion(Notification n) { 4974 if (n != null) { 4975 mN.publicVersion = new Notification(); 4976 n.cloneInto(mN.publicVersion, /*heavy=*/ true); 4977 } else { 4978 mN.publicVersion = null; 4979 } 4980 return this; 4981 } 4982 4983 /** 4984 * Apply an extender to this notification builder. Extenders may be used to add 4985 * metadata or change options on this builder. 4986 */ 4987 @NonNull extend(Extender extender)4988 public Builder extend(Extender extender) { 4989 extender.extend(this); 4990 return this; 4991 } 4992 4993 /** 4994 * Set the value for a notification flag 4995 * 4996 * @param mask Bit mask of the flag 4997 * @param value Status (on/off) of the flag 4998 * 4999 * @return The same Builder. 5000 */ 5001 @NonNull setFlag(@otificationFlags int mask, boolean value)5002 public Builder setFlag(@NotificationFlags int mask, boolean value) { 5003 if (value) { 5004 mN.flags |= mask; 5005 } else { 5006 mN.flags &= ~mask; 5007 } 5008 return this; 5009 } 5010 5011 /** 5012 * Sets {@link Notification#color}. 5013 * 5014 * @param argb The accent color to use 5015 * 5016 * @return The same Builder. 5017 */ 5018 @NonNull setColor(@olorInt int argb)5019 public Builder setColor(@ColorInt int argb) { 5020 mN.color = argb; 5021 sanitizeColor(); 5022 return this; 5023 } 5024 bindPhishingAlertIcon(RemoteViews contentView, StandardTemplateParams p)5025 private void bindPhishingAlertIcon(RemoteViews contentView, StandardTemplateParams p) { 5026 contentView.setDrawableTint( 5027 R.id.phishing_alert, 5028 false /* targetBackground */, 5029 getColors(p).getErrorColor(), 5030 PorterDuff.Mode.SRC_ATOP); 5031 } 5032 getProfileBadgeDrawable()5033 private Drawable getProfileBadgeDrawable() { 5034 if (mContext.getUserId() == UserHandle.USER_SYSTEM) { 5035 // This user can never be a badged profile, 5036 // and also includes USER_ALL system notifications. 5037 return null; 5038 } 5039 // Note: This assumes that the current user can read the profile badge of the 5040 // originating user. 5041 return mContext.getPackageManager().getUserBadgeForDensityNoBackground( 5042 new UserHandle(mContext.getUserId()), 0); 5043 } 5044 getProfileBadge()5045 private Bitmap getProfileBadge() { 5046 Drawable badge = getProfileBadgeDrawable(); 5047 if (badge == null) { 5048 return null; 5049 } 5050 final int size = mContext.getResources().getDimensionPixelSize( 5051 R.dimen.notification_badge_size); 5052 Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); 5053 Canvas canvas = new Canvas(bitmap); 5054 badge.setBounds(0, 0, size, size); 5055 badge.draw(canvas); 5056 return bitmap; 5057 } 5058 bindProfileBadge(RemoteViews contentView, StandardTemplateParams p)5059 private void bindProfileBadge(RemoteViews contentView, StandardTemplateParams p) { 5060 Bitmap profileBadge = getProfileBadge(); 5061 5062 if (profileBadge != null) { 5063 contentView.setImageViewBitmap(R.id.profile_badge, profileBadge); 5064 contentView.setViewVisibility(R.id.profile_badge, View.VISIBLE); 5065 if (isBackgroundColorized(p)) { 5066 contentView.setDrawableTint(R.id.profile_badge, false, 5067 getPrimaryTextColor(p), PorterDuff.Mode.SRC_ATOP); 5068 } 5069 } 5070 } 5071 bindAlertedIcon(RemoteViews contentView, StandardTemplateParams p)5072 private void bindAlertedIcon(RemoteViews contentView, StandardTemplateParams p) { 5073 contentView.setDrawableTint( 5074 R.id.alerted_icon, 5075 false /* targetBackground */, 5076 getColors(p).getSecondaryTextColor(), 5077 PorterDuff.Mode.SRC_IN); 5078 } 5079 5080 /** 5081 * @hide 5082 */ usesStandardHeader()5083 public boolean usesStandardHeader() { 5084 if (mN.mUsesStandardHeader) { 5085 return true; 5086 } 5087 if (mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.N) { 5088 if (mN.contentView == null && mN.bigContentView == null) { 5089 return true; 5090 } 5091 } 5092 boolean contentViewUsesHeader = mN.contentView == null 5093 || STANDARD_LAYOUTS.contains(mN.contentView.getLayoutId()); 5094 boolean bigContentViewUsesHeader = mN.bigContentView == null 5095 || STANDARD_LAYOUTS.contains(mN.bigContentView.getLayoutId()); 5096 return contentViewUsesHeader && bigContentViewUsesHeader; 5097 } 5098 resetStandardTemplate(RemoteViews contentView)5099 private void resetStandardTemplate(RemoteViews contentView) { 5100 resetNotificationHeader(contentView); 5101 contentView.setViewVisibility(R.id.right_icon, View.GONE); 5102 contentView.setViewVisibility(R.id.title, View.GONE); 5103 contentView.setTextViewText(R.id.title, null); 5104 contentView.setViewVisibility(R.id.text, View.GONE); 5105 contentView.setTextViewText(R.id.text, null); 5106 } 5107 5108 /** 5109 * Resets the notification header to its original state 5110 */ resetNotificationHeader(RemoteViews contentView)5111 private void resetNotificationHeader(RemoteViews contentView) { 5112 // Small icon doesn't need to be reset, as it's always set. Resetting would prevent 5113 // re-using the drawable when the notification is updated. 5114 contentView.setBoolean(R.id.expand_button, "setExpanded", false); 5115 contentView.setViewVisibility(R.id.app_name_text, View.GONE); 5116 contentView.setTextViewText(R.id.app_name_text, null); 5117 contentView.setViewVisibility(R.id.chronometer, View.GONE); 5118 contentView.setViewVisibility(R.id.header_text, View.GONE); 5119 contentView.setTextViewText(R.id.header_text, null); 5120 contentView.setViewVisibility(R.id.header_text_secondary, View.GONE); 5121 contentView.setTextViewText(R.id.header_text_secondary, null); 5122 contentView.setViewVisibility(R.id.header_text_divider, View.GONE); 5123 contentView.setViewVisibility(R.id.header_text_secondary_divider, View.GONE); 5124 contentView.setViewVisibility(R.id.time_divider, View.GONE); 5125 contentView.setViewVisibility(R.id.time, View.GONE); 5126 contentView.setImageViewIcon(R.id.profile_badge, null); 5127 contentView.setViewVisibility(R.id.profile_badge, View.GONE); 5128 mN.mUsesStandardHeader = false; 5129 } 5130 applyStandardTemplate(int resId, StandardTemplateParams p, TemplateBindResult result)5131 private RemoteViews applyStandardTemplate(int resId, StandardTemplateParams p, 5132 TemplateBindResult result) { 5133 p.headerless(resId == getBaseLayoutResource() 5134 || resId == getHeadsUpBaseLayoutResource() 5135 || resId == getMessagingLayoutResource() 5136 || resId == R.layout.notification_template_material_media); 5137 RemoteViews contentView = new BuilderRemoteViews(mContext.getApplicationInfo(), resId); 5138 5139 resetStandardTemplate(contentView); 5140 5141 final Bundle ex = mN.extras; 5142 updateBackgroundColor(contentView, p); 5143 bindNotificationHeader(contentView, p); 5144 bindLargeIconAndApplyMargin(contentView, p, result); 5145 boolean showProgress = handleProgressBar(contentView, ex, p); 5146 boolean hasSecondLine = showProgress; 5147 if (p.hasTitle()) { 5148 contentView.setViewVisibility(p.mTitleViewId, View.VISIBLE); 5149 contentView.setTextViewText(p.mTitleViewId, processTextSpans(p.title)); 5150 setTextViewColorPrimary(contentView, p.mTitleViewId, p); 5151 } else if (p.mTitleViewId != R.id.title) { 5152 // This alternate title view ID is not cleared by resetStandardTemplate 5153 contentView.setViewVisibility(p.mTitleViewId, View.GONE); 5154 contentView.setTextViewText(p.mTitleViewId, null); 5155 } 5156 if (p.text != null && p.text.length() != 0 5157 && (!showProgress || p.mAllowTextWithProgress)) { 5158 contentView.setViewVisibility(p.mTextViewId, View.VISIBLE); 5159 contentView.setTextViewText(p.mTextViewId, processTextSpans(p.text)); 5160 setTextViewColorSecondary(contentView, p.mTextViewId, p); 5161 hasSecondLine = true; 5162 } else if (p.mTextViewId != R.id.text) { 5163 // This alternate text view ID is not cleared by resetStandardTemplate 5164 contentView.setViewVisibility(p.mTextViewId, View.GONE); 5165 contentView.setTextViewText(p.mTextViewId, null); 5166 } 5167 setHeaderlessVerticalMargins(contentView, p, hasSecondLine); 5168 5169 return contentView; 5170 } 5171 setHeaderlessVerticalMargins(RemoteViews contentView, StandardTemplateParams p, boolean hasSecondLine)5172 private static void setHeaderlessVerticalMargins(RemoteViews contentView, 5173 StandardTemplateParams p, boolean hasSecondLine) { 5174 if (!p.mHeaderless) { 5175 return; 5176 } 5177 int marginDimen = hasSecondLine 5178 ? R.dimen.notification_headerless_margin_twoline 5179 : R.dimen.notification_headerless_margin_oneline; 5180 contentView.setViewLayoutMarginDimen(R.id.notification_headerless_view_column, 5181 RemoteViews.MARGIN_TOP, marginDimen); 5182 contentView.setViewLayoutMarginDimen(R.id.notification_headerless_view_column, 5183 RemoteViews.MARGIN_BOTTOM, marginDimen); 5184 } 5185 processTextSpans(CharSequence text)5186 private CharSequence processTextSpans(CharSequence text) { 5187 if (mInNightMode) { 5188 return ContrastColorUtil.clearColorSpans(text); 5189 } 5190 return text; 5191 } 5192 setTextViewColorPrimary(RemoteViews contentView, @IdRes int id, StandardTemplateParams p)5193 private void setTextViewColorPrimary(RemoteViews contentView, @IdRes int id, 5194 StandardTemplateParams p) { 5195 contentView.setTextColor(id, getPrimaryTextColor(p)); 5196 } 5197 5198 /** 5199 * @param p the template params to inflate this with 5200 * @return the primary text color 5201 * @hide 5202 */ 5203 @VisibleForTesting getPrimaryTextColor(StandardTemplateParams p)5204 public @ColorInt int getPrimaryTextColor(StandardTemplateParams p) { 5205 return getColors(p).getPrimaryTextColor(); 5206 } 5207 5208 /** 5209 * @param p the template params to inflate this with 5210 * @return the secondary text color 5211 * @hide 5212 */ 5213 @VisibleForTesting getSecondaryTextColor(StandardTemplateParams p)5214 public @ColorInt int getSecondaryTextColor(StandardTemplateParams p) { 5215 return getColors(p).getSecondaryTextColor(); 5216 } 5217 setTextViewColorSecondary(RemoteViews contentView, @IdRes int id, StandardTemplateParams p)5218 private void setTextViewColorSecondary(RemoteViews contentView, @IdRes int id, 5219 StandardTemplateParams p) { 5220 contentView.setTextColor(id, getSecondaryTextColor(p)); 5221 } 5222 getColors(StandardTemplateParams p)5223 private Colors getColors(StandardTemplateParams p) { 5224 mColors.resolvePalette(mContext, mN.color, isBackgroundColorized(p), mInNightMode); 5225 return mColors; 5226 } 5227 updateBackgroundColor(RemoteViews contentView, StandardTemplateParams p)5228 private void updateBackgroundColor(RemoteViews contentView, 5229 StandardTemplateParams p) { 5230 if (isBackgroundColorized(p)) { 5231 contentView.setInt(R.id.status_bar_latest_event_content, "setBackgroundColor", 5232 getBackgroundColor(p)); 5233 } else { 5234 // Clear it! 5235 contentView.setInt(R.id.status_bar_latest_event_content, "setBackgroundResource", 5236 0); 5237 } 5238 } 5239 handleProgressBar(RemoteViews contentView, Bundle ex, StandardTemplateParams p)5240 private boolean handleProgressBar(RemoteViews contentView, Bundle ex, 5241 StandardTemplateParams p) { 5242 final int max = ex.getInt(EXTRA_PROGRESS_MAX, 0); 5243 final int progress = ex.getInt(EXTRA_PROGRESS, 0); 5244 final boolean ind = ex.getBoolean(EXTRA_PROGRESS_INDETERMINATE); 5245 if (!p.mHideProgress && (max != 0 || ind)) { 5246 contentView.setViewVisibility(com.android.internal.R.id.progress, View.VISIBLE); 5247 contentView.setProgressBar(R.id.progress, max, progress, ind); 5248 contentView.setProgressBackgroundTintList(R.id.progress, 5249 mContext.getColorStateList(R.color.notification_progress_background_color)); 5250 ColorStateList progressTint = ColorStateList.valueOf(getPrimaryAccentColor(p)); 5251 contentView.setProgressTintList(R.id.progress, progressTint); 5252 contentView.setProgressIndeterminateTintList(R.id.progress, progressTint); 5253 return true; 5254 } else { 5255 contentView.setViewVisibility(R.id.progress, View.GONE); 5256 return false; 5257 } 5258 } 5259 bindLargeIconAndApplyMargin(RemoteViews contentView, @NonNull StandardTemplateParams p, @Nullable TemplateBindResult result)5260 private void bindLargeIconAndApplyMargin(RemoteViews contentView, 5261 @NonNull StandardTemplateParams p, 5262 @Nullable TemplateBindResult result) { 5263 if (result == null) { 5264 result = new TemplateBindResult(); 5265 } 5266 bindLargeIcon(contentView, p, result); 5267 if (!p.mHeaderless) { 5268 // views in states with a header (big states) 5269 result.mHeadingExtraMarginSet.applyToView(contentView, R.id.notification_header); 5270 result.mTitleMarginSet.applyToView(contentView, R.id.title); 5271 // If there is no title, the text (or big_text) needs to wrap around the image 5272 result.mTitleMarginSet.applyToView(contentView, p.mTextViewId); 5273 contentView.setInt(p.mTextViewId, "setNumIndentLines", p.hasTitle() ? 0 : 1); 5274 } 5275 } 5276 5277 // This code is executed on behalf of other apps' notifications, sometimes even by 3p apps, 5278 // a use case that is not supported by the Compat Framework library. Workarounds to resolve 5279 // the change's state in NotificationManagerService were very complex. These behavior 5280 // changes are entirely visual, and should otherwise be undetectable by apps. 5281 @SuppressWarnings("AndroidFrameworkCompatChange") calculateRightIconDimens(Icon rightIcon, boolean isPromotedPicture, @NonNull TemplateBindResult result)5282 private void calculateRightIconDimens(Icon rightIcon, boolean isPromotedPicture, 5283 @NonNull TemplateBindResult result) { 5284 final Resources resources = mContext.getResources(); 5285 final float density = resources.getDisplayMetrics().density; 5286 final float iconMarginDp = resources.getDimension( 5287 R.dimen.notification_right_icon_content_margin) / density; 5288 final float contentMarginDp = resources.getDimension( 5289 R.dimen.notification_content_margin_end) / density; 5290 final float expanderSizeDp = resources.getDimension( 5291 R.dimen.notification_header_expand_icon_size) / density - contentMarginDp; 5292 final float viewHeightDp = resources.getDimension( 5293 R.dimen.notification_right_icon_size) / density; 5294 float viewWidthDp = viewHeightDp; // icons are 1:1 by default 5295 if (rightIcon != null && (isPromotedPicture 5296 || mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.S)) { 5297 Drawable drawable = rightIcon.loadDrawable(mContext); 5298 if (drawable != null) { 5299 int iconWidth = drawable.getIntrinsicWidth(); 5300 int iconHeight = drawable.getIntrinsicHeight(); 5301 if (iconWidth > iconHeight && iconHeight > 0) { 5302 final float maxViewWidthDp = viewHeightDp * MAX_LARGE_ICON_ASPECT_RATIO; 5303 viewWidthDp = Math.min(viewHeightDp * iconWidth / iconHeight, 5304 maxViewWidthDp); 5305 } 5306 } 5307 } 5308 final float extraMarginEndDpIfVisible = viewWidthDp + iconMarginDp; 5309 result.setRightIconState(rightIcon != null /* visible */, viewWidthDp, 5310 viewHeightDp, extraMarginEndDpIfVisible, expanderSizeDp); 5311 } 5312 5313 /** 5314 * Bind the large icon. 5315 */ bindLargeIcon(RemoteViews contentView, @NonNull StandardTemplateParams p, @NonNull TemplateBindResult result)5316 private void bindLargeIcon(RemoteViews contentView, @NonNull StandardTemplateParams p, 5317 @NonNull TemplateBindResult result) { 5318 if (mN.mLargeIcon == null && mN.largeIcon != null) { 5319 mN.mLargeIcon = Icon.createWithBitmap(mN.largeIcon); 5320 } 5321 5322 // Determine the left and right icons 5323 Icon leftIcon = p.mHideLeftIcon ? null : mN.mLargeIcon; 5324 Icon rightIcon = p.mHideRightIcon ? null 5325 : (p.mPromotedPicture != null ? p.mPromotedPicture : mN.mLargeIcon); 5326 5327 // Apply the left icon (without duplicating the bitmap) 5328 if (leftIcon != rightIcon || leftIcon == null) { 5329 // If the leftIcon is explicitly hidden or different from the rightIcon, then set it 5330 // explicitly and make sure it won't take the right_icon drawable. 5331 contentView.setImageViewIcon(R.id.left_icon, leftIcon); 5332 contentView.setIntTag(R.id.left_icon, R.id.tag_uses_right_icon_drawable, 0); 5333 } else { 5334 // If the leftIcon equals the rightIcon, just set the flag to use the right_icon 5335 // drawable. This avoids the view having two copies of the same bitmap. 5336 contentView.setIntTag(R.id.left_icon, R.id.tag_uses_right_icon_drawable, 1); 5337 } 5338 5339 // Always calculate dimens to populate `result` for the GONE case 5340 boolean isPromotedPicture = p.mPromotedPicture != null; 5341 calculateRightIconDimens(rightIcon, isPromotedPicture, result); 5342 5343 // Bind the right icon 5344 if (rightIcon != null) { 5345 contentView.setViewLayoutWidth(R.id.right_icon, 5346 result.mRightIconWidthDp, TypedValue.COMPLEX_UNIT_DIP); 5347 contentView.setViewLayoutHeight(R.id.right_icon, 5348 result.mRightIconHeightDp, TypedValue.COMPLEX_UNIT_DIP); 5349 contentView.setViewVisibility(R.id.right_icon, View.VISIBLE); 5350 contentView.setImageViewIcon(R.id.right_icon, rightIcon); 5351 contentView.setIntTag(R.id.right_icon, R.id.tag_keep_when_showing_left_icon, 5352 isPromotedPicture ? 1 : 0); 5353 processLargeLegacyIcon(rightIcon, contentView, p); 5354 } else { 5355 // The "reset" doesn't clear the drawable, so we do it here. This clear is 5356 // important because the presence of a drawable in this view (regardless of the 5357 // visibility) is used by NotificationGroupingUtil to set the visibility. 5358 contentView.setImageViewIcon(R.id.right_icon, null); 5359 contentView.setIntTag(R.id.right_icon, R.id.tag_keep_when_showing_left_icon, 0); 5360 } 5361 } 5362 bindNotificationHeader(RemoteViews contentView, StandardTemplateParams p)5363 private void bindNotificationHeader(RemoteViews contentView, StandardTemplateParams p) { 5364 bindSmallIcon(contentView, p); 5365 // Populate text left-to-right so that separators are only shown between strings 5366 boolean hasTextToLeft = bindHeaderAppName(contentView, p, false /* force */); 5367 hasTextToLeft |= bindHeaderTextSecondary(contentView, p, hasTextToLeft); 5368 hasTextToLeft |= bindHeaderText(contentView, p, hasTextToLeft); 5369 if (!hasTextToLeft) { 5370 // If there's still no text, force add the app name so there is some text. 5371 hasTextToLeft |= bindHeaderAppName(contentView, p, true /* force */); 5372 } 5373 bindHeaderChronometerAndTime(contentView, p, hasTextToLeft); 5374 bindPhishingAlertIcon(contentView, p); 5375 bindProfileBadge(contentView, p); 5376 bindAlertedIcon(contentView, p); 5377 bindExpandButton(contentView, p); 5378 mN.mUsesStandardHeader = true; 5379 } 5380 bindExpandButton(RemoteViews contentView, StandardTemplateParams p)5381 private void bindExpandButton(RemoteViews contentView, StandardTemplateParams p) { 5382 // set default colors 5383 int bgColor = getBackgroundColor(p); 5384 int pillColor = Colors.flattenAlpha(getColors(p).getProtectionColor(), bgColor); 5385 int textColor = Colors.flattenAlpha(getPrimaryTextColor(p), pillColor); 5386 contentView.setInt(R.id.expand_button, "setDefaultTextColor", textColor); 5387 contentView.setInt(R.id.expand_button, "setDefaultPillColor", pillColor); 5388 // Use different highlighted colors for conversations' unread count 5389 if (p.mHighlightExpander) { 5390 pillColor = Colors.flattenAlpha(getPrimaryAccentColor(p), bgColor); 5391 textColor = Colors.flattenAlpha(bgColor, pillColor); 5392 } 5393 contentView.setInt(R.id.expand_button, "setHighlightTextColor", textColor); 5394 contentView.setInt(R.id.expand_button, "setHighlightPillColor", pillColor); 5395 } 5396 bindHeaderChronometerAndTime(RemoteViews contentView, StandardTemplateParams p, boolean hasTextToLeft)5397 private void bindHeaderChronometerAndTime(RemoteViews contentView, 5398 StandardTemplateParams p, boolean hasTextToLeft) { 5399 if (!p.mHideTime && showsTimeOrChronometer()) { 5400 if (hasTextToLeft) { 5401 contentView.setViewVisibility(R.id.time_divider, View.VISIBLE); 5402 setTextViewColorSecondary(contentView, R.id.time_divider, p); 5403 } 5404 if (mN.extras.getBoolean(EXTRA_SHOW_CHRONOMETER)) { 5405 contentView.setViewVisibility(R.id.chronometer, View.VISIBLE); 5406 contentView.setLong(R.id.chronometer, "setBase", 5407 mN.when + (SystemClock.elapsedRealtime() - System.currentTimeMillis())); 5408 contentView.setBoolean(R.id.chronometer, "setStarted", true); 5409 boolean countsDown = mN.extras.getBoolean(EXTRA_CHRONOMETER_COUNT_DOWN); 5410 contentView.setChronometerCountDown(R.id.chronometer, countsDown); 5411 setTextViewColorSecondary(contentView, R.id.chronometer, p); 5412 } else { 5413 contentView.setViewVisibility(R.id.time, View.VISIBLE); 5414 contentView.setLong(R.id.time, "setTime", mN.when); 5415 setTextViewColorSecondary(contentView, R.id.time, p); 5416 } 5417 } else { 5418 // We still want a time to be set but gone, such that we can show and hide it 5419 // on demand in case it's a child notification without anything in the header 5420 contentView.setLong(R.id.time, "setTime", mN.when != 0 ? mN.when : mN.creationTime); 5421 setTextViewColorSecondary(contentView, R.id.time, p); 5422 } 5423 } 5424 5425 /** 5426 * @return true if the header text will be visible 5427 */ bindHeaderText(RemoteViews contentView, StandardTemplateParams p, boolean hasTextToLeft)5428 private boolean bindHeaderText(RemoteViews contentView, StandardTemplateParams p, 5429 boolean hasTextToLeft) { 5430 if (p.mHideSubText) { 5431 return false; 5432 } 5433 CharSequence summaryText = p.summaryText; 5434 if (summaryText == null && mStyle != null && mStyle.mSummaryTextSet 5435 && mStyle.hasSummaryInHeader()) { 5436 summaryText = mStyle.mSummaryText; 5437 } 5438 if (summaryText == null 5439 && mContext.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N 5440 && mN.extras.getCharSequence(EXTRA_INFO_TEXT) != null) { 5441 summaryText = mN.extras.getCharSequence(EXTRA_INFO_TEXT); 5442 } 5443 if (!TextUtils.isEmpty(summaryText)) { 5444 // TODO: Remove the span entirely to only have the string with propper formating. 5445 contentView.setTextViewText(R.id.header_text, processTextSpans( 5446 processLegacyText(summaryText))); 5447 setTextViewColorSecondary(contentView, R.id.header_text, p); 5448 contentView.setViewVisibility(R.id.header_text, View.VISIBLE); 5449 if (hasTextToLeft) { 5450 contentView.setViewVisibility(R.id.header_text_divider, View.VISIBLE); 5451 setTextViewColorSecondary(contentView, R.id.header_text_divider, p); 5452 } 5453 return true; 5454 } 5455 return false; 5456 } 5457 5458 /** 5459 * @return true if the secondary header text will be visible 5460 */ bindHeaderTextSecondary(RemoteViews contentView, StandardTemplateParams p, boolean hasTextToLeft)5461 private boolean bindHeaderTextSecondary(RemoteViews contentView, StandardTemplateParams p, 5462 boolean hasTextToLeft) { 5463 if (p.mHideSubText) { 5464 return false; 5465 } 5466 if (!TextUtils.isEmpty(p.headerTextSecondary)) { 5467 contentView.setTextViewText(R.id.header_text_secondary, processTextSpans( 5468 processLegacyText(p.headerTextSecondary))); 5469 setTextViewColorSecondary(contentView, R.id.header_text_secondary, p); 5470 contentView.setViewVisibility(R.id.header_text_secondary, View.VISIBLE); 5471 if (hasTextToLeft) { 5472 contentView.setViewVisibility(R.id.header_text_secondary_divider, View.VISIBLE); 5473 setTextViewColorSecondary(contentView, R.id.header_text_secondary_divider, p); 5474 } 5475 return true; 5476 } 5477 return false; 5478 } 5479 5480 /** 5481 * @hide 5482 */ 5483 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) loadHeaderAppName()5484 public String loadHeaderAppName() { 5485 CharSequence name = null; 5486 final PackageManager pm = mContext.getPackageManager(); 5487 if (mN.extras.containsKey(EXTRA_SUBSTITUTE_APP_NAME)) { 5488 // only system packages which lump together a bunch of unrelated stuff 5489 // may substitute a different name to make the purpose of the 5490 // notification more clear. the correct package label should always 5491 // be accessible via SystemUI. 5492 final String pkg = mContext.getPackageName(); 5493 final String subName = mN.extras.getString(EXTRA_SUBSTITUTE_APP_NAME); 5494 if (PackageManager.PERMISSION_GRANTED == pm.checkPermission( 5495 android.Manifest.permission.SUBSTITUTE_NOTIFICATION_APP_NAME, pkg)) { 5496 name = subName; 5497 } else { 5498 Log.w(TAG, "warning: pkg " 5499 + pkg + " attempting to substitute app name '" + subName 5500 + "' without holding perm " 5501 + android.Manifest.permission.SUBSTITUTE_NOTIFICATION_APP_NAME); 5502 } 5503 } 5504 if (TextUtils.isEmpty(name)) { 5505 name = pm.getApplicationLabel(mContext.getApplicationInfo()); 5506 } 5507 if (TextUtils.isEmpty(name)) { 5508 // still nothing? 5509 return null; 5510 } 5511 5512 return String.valueOf(name); 5513 } 5514 5515 /** 5516 * @return true if the app name will be visible 5517 */ bindHeaderAppName(RemoteViews contentView, StandardTemplateParams p, boolean force)5518 private boolean bindHeaderAppName(RemoteViews contentView, StandardTemplateParams p, 5519 boolean force) { 5520 if (p.mViewType == StandardTemplateParams.VIEW_TYPE_MINIMIZED && !force) { 5521 // unless the force flag is set, don't show the app name in the minimized state. 5522 return false; 5523 } 5524 if (p.mHeaderless && p.hasTitle()) { 5525 // the headerless template will have the TITLE in this position; return true to 5526 // keep the divider visible between that title and the next text element. 5527 return true; 5528 } 5529 if (p.mHideAppName) { 5530 // The app name is being hidden, so we definitely want to return here. 5531 // Assume that there is a title which will replace it in the header. 5532 return p.hasTitle(); 5533 } 5534 contentView.setViewVisibility(R.id.app_name_text, View.VISIBLE); 5535 contentView.setTextViewText(R.id.app_name_text, loadHeaderAppName()); 5536 contentView.setTextColor(R.id.app_name_text, getSecondaryTextColor(p)); 5537 return true; 5538 } 5539 5540 /** 5541 * Determines if the notification should be colorized *for the purposes of applying colors*. 5542 * If this is the minimized view of a colorized notification, or if the app did not provide 5543 * a color to colorize with, this will return false so that internal coloring logic can 5544 * still render the notification normally. 5545 */ isBackgroundColorized(StandardTemplateParams p)5546 private boolean isBackgroundColorized(StandardTemplateParams p) { 5547 return p.allowColorization && mN.color != COLOR_DEFAULT && mN.isColorized(); 5548 } 5549 isCallActionColorCustomizable()5550 private boolean isCallActionColorCustomizable() { 5551 // NOTE: this doesn't need to check StandardTemplateParams.allowColorization because 5552 // that is only used for disallowing colorization of headers for the minimized state, 5553 // and neither of those conditions applies when showing actions. 5554 // Not requiring StandardTemplateParams as an argument simplifies the creation process. 5555 return mN.color != COLOR_DEFAULT && mN.isColorized() 5556 && mContext.getResources().getBoolean( 5557 R.bool.config_callNotificationActionColorsRequireColorized); 5558 } 5559 bindSmallIcon(RemoteViews contentView, StandardTemplateParams p)5560 private void bindSmallIcon(RemoteViews contentView, StandardTemplateParams p) { 5561 if (mN.mSmallIcon == null && mN.icon != 0) { 5562 mN.mSmallIcon = Icon.createWithResource(mContext, mN.icon); 5563 } 5564 contentView.setImageViewIcon(R.id.icon, mN.mSmallIcon); 5565 contentView.setInt(R.id.icon, "setImageLevel", mN.iconLevel); 5566 processSmallIconColor(mN.mSmallIcon, contentView, p); 5567 } 5568 5569 /** 5570 * @return true if the built notification will show the time or the chronometer; false 5571 * otherwise 5572 */ showsTimeOrChronometer()5573 private boolean showsTimeOrChronometer() { 5574 return mN.showsTime() || mN.showsChronometer(); 5575 } 5576 resetStandardTemplateWithActions(RemoteViews big)5577 private void resetStandardTemplateWithActions(RemoteViews big) { 5578 // actions_container is only reset when there are no actions to avoid focus issues with 5579 // remote inputs. 5580 big.setViewVisibility(R.id.actions, View.GONE); 5581 big.removeAllViews(R.id.actions); 5582 5583 big.setViewVisibility(R.id.notification_material_reply_container, View.GONE); 5584 big.setTextViewText(R.id.notification_material_reply_text_1, null); 5585 big.setViewVisibility(R.id.notification_material_reply_text_1_container, View.GONE); 5586 big.setViewVisibility(R.id.notification_material_reply_progress, View.GONE); 5587 5588 big.setViewVisibility(R.id.notification_material_reply_text_2, View.GONE); 5589 big.setTextViewText(R.id.notification_material_reply_text_2, null); 5590 big.setViewVisibility(R.id.notification_material_reply_text_3, View.GONE); 5591 big.setTextViewText(R.id.notification_material_reply_text_3, null); 5592 5593 // This may get erased by bindSnoozeAction 5594 big.setViewLayoutMarginDimen(R.id.notification_action_list_margin_target, 5595 RemoteViews.MARGIN_BOTTOM, R.dimen.notification_content_margin); 5596 } 5597 bindSnoozeAction(RemoteViews big, StandardTemplateParams p)5598 private void bindSnoozeAction(RemoteViews big, StandardTemplateParams p) { 5599 boolean hideSnoozeButton = mN.isForegroundService() || mN.fullScreenIntent != null 5600 || isBackgroundColorized(p) 5601 || p.mViewType != StandardTemplateParams.VIEW_TYPE_BIG; 5602 big.setBoolean(R.id.snooze_button, "setEnabled", !hideSnoozeButton); 5603 if (hideSnoozeButton) { 5604 // Only hide; NotificationContentView will show it when it adds the click listener 5605 big.setViewVisibility(R.id.snooze_button, View.GONE); 5606 } 5607 5608 final boolean snoozeEnabled = !hideSnoozeButton 5609 && mContext.getContentResolver() != null 5610 && isSnoozeSettingEnabled(); 5611 if (snoozeEnabled) { 5612 big.setViewLayoutMarginDimen(R.id.notification_action_list_margin_target, 5613 RemoteViews.MARGIN_BOTTOM, 0); 5614 } 5615 } 5616 isSnoozeSettingEnabled()5617 private boolean isSnoozeSettingEnabled() { 5618 try { 5619 return Settings.Secure.getInt(mContext.getContentResolver(), 5620 Settings.Secure.SHOW_NOTIFICATION_SNOOZE, 0) == 1; 5621 } catch (SecurityException ex) { 5622 // Most 3p apps can't access this snooze setting, so their NotificationListeners 5623 // would be unable to create notification views if we propagated this exception. 5624 return false; 5625 } 5626 } 5627 5628 /** 5629 * Returns the actions that are not contextual. 5630 */ getNonContextualActions()5631 private @NonNull List<Notification.Action> getNonContextualActions() { 5632 if (mActions == null) return Collections.emptyList(); 5633 List<Notification.Action> standardActions = new ArrayList<>(); 5634 for (Notification.Action action : mActions) { 5635 if (!action.isContextual()) { 5636 standardActions.add(action); 5637 } 5638 } 5639 return standardActions; 5640 } 5641 applyStandardTemplateWithActions(int layoutId, StandardTemplateParams p, TemplateBindResult result)5642 private RemoteViews applyStandardTemplateWithActions(int layoutId, 5643 StandardTemplateParams p, TemplateBindResult result) { 5644 RemoteViews big = applyStandardTemplate(layoutId, p, result); 5645 5646 resetStandardTemplateWithActions(big); 5647 bindSnoozeAction(big, p); 5648 // color the snooze and bubble actions with the theme color 5649 ColorStateList actionColor = ColorStateList.valueOf(getStandardActionColor(p)); 5650 big.setColorStateList(R.id.snooze_button, "setImageTintList", actionColor); 5651 big.setColorStateList(R.id.bubble_button, "setImageTintList", actionColor); 5652 5653 boolean validRemoteInput = false; 5654 5655 // In the UI, contextual actions appear separately from the standard actions, so we 5656 // filter them out here. 5657 List<Notification.Action> nonContextualActions = getNonContextualActions(); 5658 5659 int numActions = Math.min(nonContextualActions.size(), MAX_ACTION_BUTTONS); 5660 boolean emphazisedMode = mN.fullScreenIntent != null || p.mCallStyleActions; 5661 if (p.mCallStyleActions) { 5662 // Clear view padding to allow buttons to start on the left edge. 5663 // This must be done before 'setEmphasizedMode' which sets top/bottom margins. 5664 big.setViewPadding(R.id.actions, 0, 0, 0, 0); 5665 // Add an optional indent that will make buttons start at the correct column when 5666 // there is enough space to do so (and fall back to the left edge if not). 5667 big.setInt(R.id.actions, "setCollapsibleIndentDimen", 5668 R.dimen.call_notification_collapsible_indent); 5669 } 5670 big.setBoolean(R.id.actions, "setEmphasizedMode", emphazisedMode); 5671 if (numActions > 0 && !p.mHideActions) { 5672 big.setViewVisibility(R.id.actions_container, View.VISIBLE); 5673 big.setViewVisibility(R.id.actions, View.VISIBLE); 5674 big.setViewLayoutMarginDimen(R.id.notification_action_list_margin_target, 5675 RemoteViews.MARGIN_BOTTOM, 0); 5676 for (int i = 0; i < numActions; i++) { 5677 Action action = nonContextualActions.get(i); 5678 5679 boolean actionHasValidInput = hasValidRemoteInput(action); 5680 validRemoteInput |= actionHasValidInput; 5681 5682 final RemoteViews button = generateActionButton(action, emphazisedMode, p); 5683 if (actionHasValidInput && !emphazisedMode) { 5684 // Clear the drawable 5685 button.setInt(R.id.action0, "setBackgroundResource", 0); 5686 } 5687 if (emphazisedMode && i > 0) { 5688 // Clear start margin from non-first buttons to reduce the gap between them. 5689 // (8dp remaining gap is from all buttons' standard 4dp inset). 5690 button.setViewLayoutMarginDimen(R.id.action0, RemoteViews.MARGIN_START, 0); 5691 } 5692 big.addView(R.id.actions, button); 5693 } 5694 } else { 5695 big.setViewVisibility(R.id.actions_container, View.GONE); 5696 } 5697 5698 RemoteInputHistoryItem[] replyText = getParcelableArrayFromBundle( 5699 mN.extras, EXTRA_REMOTE_INPUT_HISTORY_ITEMS, RemoteInputHistoryItem.class); 5700 if (validRemoteInput && replyText != null && replyText.length > 0 5701 && !TextUtils.isEmpty(replyText[0].getText()) 5702 && p.maxRemoteInputHistory > 0) { 5703 boolean showSpinner = mN.extras.getBoolean(EXTRA_SHOW_REMOTE_INPUT_SPINNER); 5704 big.setViewVisibility(R.id.notification_material_reply_container, View.VISIBLE); 5705 big.setViewVisibility(R.id.notification_material_reply_text_1_container, 5706 View.VISIBLE); 5707 big.setTextViewText(R.id.notification_material_reply_text_1, 5708 processTextSpans(replyText[0].getText())); 5709 setTextViewColorSecondary(big, R.id.notification_material_reply_text_1, p); 5710 big.setViewVisibility(R.id.notification_material_reply_progress, 5711 showSpinner ? View.VISIBLE : View.GONE); 5712 big.setProgressIndeterminateTintList( 5713 R.id.notification_material_reply_progress, 5714 ColorStateList.valueOf(getPrimaryAccentColor(p))); 5715 5716 if (replyText.length > 1 && !TextUtils.isEmpty(replyText[1].getText()) 5717 && p.maxRemoteInputHistory > 1) { 5718 big.setViewVisibility(R.id.notification_material_reply_text_2, View.VISIBLE); 5719 big.setTextViewText(R.id.notification_material_reply_text_2, 5720 processTextSpans(replyText[1].getText())); 5721 setTextViewColorSecondary(big, R.id.notification_material_reply_text_2, p); 5722 5723 if (replyText.length > 2 && !TextUtils.isEmpty(replyText[2].getText()) 5724 && p.maxRemoteInputHistory > 2) { 5725 big.setViewVisibility( 5726 R.id.notification_material_reply_text_3, View.VISIBLE); 5727 big.setTextViewText(R.id.notification_material_reply_text_3, 5728 processTextSpans(replyText[2].getText())); 5729 setTextViewColorSecondary(big, R.id.notification_material_reply_text_3, p); 5730 } 5731 } 5732 } 5733 5734 return big; 5735 } 5736 hasValidRemoteInput(Action action)5737 private boolean hasValidRemoteInput(Action action) { 5738 if (TextUtils.isEmpty(action.title) || action.actionIntent == null) { 5739 // Weird actions 5740 return false; 5741 } 5742 5743 RemoteInput[] remoteInputs = action.getRemoteInputs(); 5744 if (remoteInputs == null) { 5745 return false; 5746 } 5747 5748 for (RemoteInput r : remoteInputs) { 5749 CharSequence[] choices = r.getChoices(); 5750 if (r.getAllowFreeFormInput() || (choices != null && choices.length != 0)) { 5751 return true; 5752 } 5753 } 5754 return false; 5755 } 5756 5757 /** 5758 * Construct a RemoteViews for the final 1U notification layout. In order: 5759 * 1. Custom contentView from the caller 5760 * 2. Style's proposed content view 5761 * 3. Standard template view 5762 */ createContentView()5763 public RemoteViews createContentView() { 5764 return createContentView(false /* increasedheight */ ); 5765 } 5766 5767 // This code is executed on behalf of other apps' notifications, sometimes even by 3p apps, 5768 // a use case that is not supported by the Compat Framework library. Workarounds to resolve 5769 // the change's state in NotificationManagerService were very complex. While it's possible 5770 // apps can detect the change, it's most likely that the changes will simply result in 5771 // visual regressions. 5772 @SuppressWarnings("AndroidFrameworkCompatChange") fullyCustomViewRequiresDecoration(boolean fromStyle)5773 private boolean fullyCustomViewRequiresDecoration(boolean fromStyle) { 5774 // Custom views which come from a platform style class are safe, and thus do not need to 5775 // be wrapped. Any subclass of those styles has the opportunity to make arbitrary 5776 // changes to the RemoteViews, and thus can't be trusted as a fully vetted view. 5777 if (fromStyle && PLATFORM_STYLE_CLASSES.contains(mStyle.getClass())) { 5778 return false; 5779 } 5780 return mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.S; 5781 } 5782 minimallyDecoratedContentView(@onNull RemoteViews customContent)5783 private RemoteViews minimallyDecoratedContentView(@NonNull RemoteViews customContent) { 5784 StandardTemplateParams p = mParams.reset() 5785 .viewType(StandardTemplateParams.VIEW_TYPE_NORMAL) 5786 .decorationType(StandardTemplateParams.DECORATION_MINIMAL) 5787 .fillTextsFrom(this); 5788 TemplateBindResult result = new TemplateBindResult(); 5789 RemoteViews standard = applyStandardTemplate(getBaseLayoutResource(), p, result); 5790 buildCustomContentIntoTemplate(mContext, standard, customContent, 5791 p, result); 5792 return standard; 5793 } 5794 minimallyDecoratedBigContentView(@onNull RemoteViews customContent)5795 private RemoteViews minimallyDecoratedBigContentView(@NonNull RemoteViews customContent) { 5796 StandardTemplateParams p = mParams.reset() 5797 .viewType(StandardTemplateParams.VIEW_TYPE_BIG) 5798 .decorationType(StandardTemplateParams.DECORATION_MINIMAL) 5799 .fillTextsFrom(this); 5800 TemplateBindResult result = new TemplateBindResult(); 5801 RemoteViews standard = applyStandardTemplateWithActions(getBigBaseLayoutResource(), 5802 p, result); 5803 buildCustomContentIntoTemplate(mContext, standard, customContent, 5804 p, result); 5805 return standard; 5806 } 5807 minimallyDecoratedHeadsUpContentView( @onNull RemoteViews customContent)5808 private RemoteViews minimallyDecoratedHeadsUpContentView( 5809 @NonNull RemoteViews customContent) { 5810 StandardTemplateParams p = mParams.reset() 5811 .viewType(StandardTemplateParams.VIEW_TYPE_HEADS_UP) 5812 .decorationType(StandardTemplateParams.DECORATION_MINIMAL) 5813 .fillTextsFrom(this); 5814 TemplateBindResult result = new TemplateBindResult(); 5815 RemoteViews standard = applyStandardTemplateWithActions(getHeadsUpBaseLayoutResource(), 5816 p, result); 5817 buildCustomContentIntoTemplate(mContext, standard, customContent, 5818 p, result); 5819 return standard; 5820 } 5821 5822 /** 5823 * Construct a RemoteViews for the smaller content view. 5824 * 5825 * @param increasedHeight true if this layout be created with an increased height. Some 5826 * styles may support showing more then just that basic 1U size 5827 * and the system may decide to render important notifications 5828 * slightly bigger even when collapsed. 5829 * 5830 * @hide 5831 */ createContentView(boolean increasedHeight)5832 public RemoteViews createContentView(boolean increasedHeight) { 5833 if (useExistingRemoteView(mN.contentView)) { 5834 return fullyCustomViewRequiresDecoration(false /* fromStyle */) 5835 ? minimallyDecoratedContentView(mN.contentView) : mN.contentView; 5836 } else if (mStyle != null) { 5837 final RemoteViews styleView = mStyle.makeContentView(increasedHeight); 5838 if (styleView != null) { 5839 return fullyCustomViewRequiresDecoration(true /* fromStyle */) 5840 ? minimallyDecoratedContentView(styleView) : styleView; 5841 } 5842 } 5843 StandardTemplateParams p = mParams.reset() 5844 .viewType(StandardTemplateParams.VIEW_TYPE_NORMAL) 5845 .fillTextsFrom(this); 5846 return applyStandardTemplate(getBaseLayoutResource(), p, null /* result */); 5847 } 5848 useExistingRemoteView(RemoteViews customContent)5849 private boolean useExistingRemoteView(RemoteViews customContent) { 5850 if (customContent == null) { 5851 return false; 5852 } 5853 if (styleDisplaysCustomViewInline()) { 5854 // the provided custom view is intended to be wrapped by the style. 5855 return false; 5856 } 5857 if (fullyCustomViewRequiresDecoration(false) 5858 && STANDARD_LAYOUTS.contains(customContent.getLayoutId())) { 5859 // If the app's custom views are objects returned from Builder.create*ContentView() 5860 // then the app is most likely attempting to spoof the user. Even if they are not, 5861 // the result would be broken (b/189189308) so we will ignore it. 5862 Log.w(TAG, "For apps targeting S, a custom content view that is a modified " 5863 + "version of any standard layout is disallowed."); 5864 return false; 5865 } 5866 return true; 5867 } 5868 5869 /** 5870 * Construct a RemoteViews for the final big notification layout. 5871 */ createBigContentView()5872 public RemoteViews createBigContentView() { 5873 RemoteViews result = null; 5874 if (useExistingRemoteView(mN.bigContentView)) { 5875 return fullyCustomViewRequiresDecoration(false /* fromStyle */) 5876 ? minimallyDecoratedBigContentView(mN.bigContentView) : mN.bigContentView; 5877 } 5878 if (mStyle != null) { 5879 result = mStyle.makeBigContentView(); 5880 if (fullyCustomViewRequiresDecoration(true /* fromStyle */)) { 5881 result = minimallyDecoratedBigContentView(result); 5882 } 5883 } 5884 if (result == null) { 5885 if (bigContentViewRequired()) { 5886 StandardTemplateParams p = mParams.reset() 5887 .viewType(StandardTemplateParams.VIEW_TYPE_BIG) 5888 .allowTextWithProgress(true) 5889 .fillTextsFrom(this); 5890 result = applyStandardTemplateWithActions(getBigBaseLayoutResource(), p, 5891 null /* result */); 5892 } 5893 } 5894 makeHeaderExpanded(result); 5895 return result; 5896 } 5897 5898 // This code is executed on behalf of other apps' notifications, sometimes even by 3p apps, 5899 // a use case that is not supported by the Compat Framework library. Workarounds to resolve 5900 // the change's state in NotificationManagerService were very complex. While it's possible 5901 // apps can detect the change, it's most likely that the changes will simply result in 5902 // visual regressions. 5903 @SuppressWarnings("AndroidFrameworkCompatChange") bigContentViewRequired()5904 private boolean bigContentViewRequired() { 5905 if (mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.S) { 5906 return true; 5907 } 5908 // Notifications with contentView and without a bigContentView, style, or actions would 5909 // not have an expanded state before S, so showing the standard template expanded state 5910 // usually looks wrong, so we keep it simple and don't show the expanded state. 5911 boolean exempt = mN.contentView != null && mN.bigContentView == null 5912 && mStyle == null && mActions.size() == 0; 5913 return !exempt; 5914 } 5915 5916 /** 5917 * Construct a RemoteViews for the final notification header only. This will not be 5918 * colorized. 5919 * 5920 * @hide 5921 */ makeNotificationGroupHeader()5922 public RemoteViews makeNotificationGroupHeader() { 5923 return makeNotificationHeader(mParams.reset() 5924 .viewType(StandardTemplateParams.VIEW_TYPE_GROUP_HEADER) 5925 .fillTextsFrom(this)); 5926 } 5927 5928 /** 5929 * Construct a RemoteViews for the final notification header only. This will not be 5930 * colorized. 5931 * 5932 * @param p the template params to inflate this with 5933 */ makeNotificationHeader(StandardTemplateParams p)5934 private RemoteViews makeNotificationHeader(StandardTemplateParams p) { 5935 // Headers on their own are never colorized 5936 p.disallowColorization(); 5937 RemoteViews header = new BuilderRemoteViews(mContext.getApplicationInfo(), 5938 R.layout.notification_template_header); 5939 resetNotificationHeader(header); 5940 bindNotificationHeader(header, p); 5941 return header; 5942 } 5943 5944 /** 5945 * Construct a RemoteViews for the ambient version of the notification. 5946 * 5947 * @hide 5948 */ makeAmbientNotification()5949 public RemoteViews makeAmbientNotification() { 5950 RemoteViews headsUpContentView = createHeadsUpContentView(false /* increasedHeight */); 5951 if (headsUpContentView != null) { 5952 return headsUpContentView; 5953 } 5954 return createContentView(); 5955 } 5956 5957 /** 5958 * Adapt the Notification header if this view is used as an expanded view. 5959 * 5960 * @hide 5961 */ makeHeaderExpanded(RemoteViews result)5962 public static void makeHeaderExpanded(RemoteViews result) { 5963 if (result != null) { 5964 result.setBoolean(R.id.expand_button, "setExpanded", true); 5965 } 5966 } 5967 5968 /** 5969 * Construct a RemoteViews for the final heads-up notification layout. 5970 * 5971 * @param increasedHeight true if this layout be created with an increased height. Some 5972 * styles may support showing more then just that basic 1U size 5973 * and the system may decide to render important notifications 5974 * slightly bigger even when collapsed. 5975 * 5976 * @hide 5977 */ createHeadsUpContentView(boolean increasedHeight)5978 public RemoteViews createHeadsUpContentView(boolean increasedHeight) { 5979 if (useExistingRemoteView(mN.headsUpContentView)) { 5980 return fullyCustomViewRequiresDecoration(false /* fromStyle */) 5981 ? minimallyDecoratedHeadsUpContentView(mN.headsUpContentView) 5982 : mN.headsUpContentView; 5983 } else if (mStyle != null) { 5984 final RemoteViews styleView = mStyle.makeHeadsUpContentView(increasedHeight); 5985 if (styleView != null) { 5986 return fullyCustomViewRequiresDecoration(true /* fromStyle */) 5987 ? minimallyDecoratedHeadsUpContentView(styleView) : styleView; 5988 } 5989 } else if (mActions.size() == 0) { 5990 return null; 5991 } 5992 5993 // We only want at most a single remote input history to be shown here, otherwise 5994 // the content would become squished. 5995 StandardTemplateParams p = mParams.reset() 5996 .viewType(StandardTemplateParams.VIEW_TYPE_HEADS_UP) 5997 .fillTextsFrom(this) 5998 .setMaxRemoteInputHistory(1); 5999 return applyStandardTemplateWithActions(getHeadsUpBaseLayoutResource(), p, 6000 null /* result */); 6001 } 6002 6003 /** 6004 * Construct a RemoteViews for the final heads-up notification layout. 6005 */ createHeadsUpContentView()6006 public RemoteViews createHeadsUpContentView() { 6007 return createHeadsUpContentView(false /* useIncreasedHeight */); 6008 } 6009 6010 /** 6011 * Construct a RemoteViews for the display in public contexts like on the lockscreen. 6012 * 6013 * @param isLowPriority is this notification low priority 6014 * @hide 6015 */ 6016 @UnsupportedAppUsage makePublicContentView(boolean isLowPriority)6017 public RemoteViews makePublicContentView(boolean isLowPriority) { 6018 if (mN.publicVersion != null) { 6019 final Builder builder = recoverBuilder(mContext, mN.publicVersion); 6020 return builder.createContentView(); 6021 } 6022 Bundle savedBundle = mN.extras; 6023 Style style = mStyle; 6024 mStyle = null; 6025 Icon largeIcon = mN.mLargeIcon; 6026 mN.mLargeIcon = null; 6027 Bitmap largeIconLegacy = mN.largeIcon; 6028 mN.largeIcon = null; 6029 ArrayList<Action> actions = mActions; 6030 mActions = new ArrayList<>(); 6031 Bundle publicExtras = new Bundle(); 6032 publicExtras.putBoolean(EXTRA_SHOW_WHEN, 6033 savedBundle.getBoolean(EXTRA_SHOW_WHEN)); 6034 publicExtras.putBoolean(EXTRA_SHOW_CHRONOMETER, 6035 savedBundle.getBoolean(EXTRA_SHOW_CHRONOMETER)); 6036 publicExtras.putBoolean(EXTRA_CHRONOMETER_COUNT_DOWN, 6037 savedBundle.getBoolean(EXTRA_CHRONOMETER_COUNT_DOWN)); 6038 String appName = savedBundle.getString(EXTRA_SUBSTITUTE_APP_NAME); 6039 if (appName != null) { 6040 publicExtras.putString(EXTRA_SUBSTITUTE_APP_NAME, appName); 6041 } 6042 mN.extras = publicExtras; 6043 RemoteViews view; 6044 StandardTemplateParams params = mParams.reset() 6045 .viewType(StandardTemplateParams.VIEW_TYPE_PUBLIC) 6046 .fillTextsFrom(this); 6047 if (isLowPriority) { 6048 params.highlightExpander(false); 6049 } 6050 view = makeNotificationHeader(params); 6051 view.setBoolean(R.id.notification_header, "setExpandOnlyOnButton", true); 6052 mN.extras = savedBundle; 6053 mN.mLargeIcon = largeIcon; 6054 mN.largeIcon = largeIconLegacy; 6055 mActions = actions; 6056 mStyle = style; 6057 return view; 6058 } 6059 6060 /** 6061 * Construct a content view for the display when low - priority 6062 * 6063 * @param useRegularSubtext uses the normal subtext set if there is one available. Otherwise 6064 * a new subtext is created consisting of the content of the 6065 * notification. 6066 * @hide 6067 */ makeLowPriorityContentView(boolean useRegularSubtext)6068 public RemoteViews makeLowPriorityContentView(boolean useRegularSubtext) { 6069 StandardTemplateParams p = mParams.reset() 6070 .viewType(StandardTemplateParams.VIEW_TYPE_MINIMIZED) 6071 .highlightExpander(false) 6072 .fillTextsFrom(this); 6073 if (!useRegularSubtext || TextUtils.isEmpty(p.summaryText)) { 6074 p.summaryText(createSummaryText()); 6075 } 6076 RemoteViews header = makeNotificationHeader(p); 6077 header.setBoolean(R.id.notification_header, "setAcceptAllTouches", true); 6078 // The low priority header has no app name and shows the text 6079 header.setBoolean(R.id.notification_header, "styleTextAsTitle", true); 6080 return header; 6081 } 6082 createSummaryText()6083 private CharSequence createSummaryText() { 6084 CharSequence titleText = mN.extras.getCharSequence(Notification.EXTRA_TITLE); 6085 if (USE_ONLY_TITLE_IN_LOW_PRIORITY_SUMMARY) { 6086 return titleText; 6087 } 6088 SpannableStringBuilder summary = new SpannableStringBuilder(); 6089 if (titleText == null) { 6090 titleText = mN.extras.getCharSequence(Notification.EXTRA_TITLE_BIG); 6091 } 6092 BidiFormatter bidi = BidiFormatter.getInstance(); 6093 if (titleText != null) { 6094 summary.append(bidi.unicodeWrap(titleText)); 6095 } 6096 CharSequence contentText = mN.extras.getCharSequence(Notification.EXTRA_TEXT); 6097 if (titleText != null && contentText != null) { 6098 summary.append(bidi.unicodeWrap(mContext.getText( 6099 R.string.notification_header_divider_symbol_with_spaces))); 6100 } 6101 if (contentText != null) { 6102 summary.append(bidi.unicodeWrap(contentText)); 6103 } 6104 return summary; 6105 } 6106 generateActionButton(Action action, boolean emphasizedMode, StandardTemplateParams p)6107 private RemoteViews generateActionButton(Action action, boolean emphasizedMode, 6108 StandardTemplateParams p) { 6109 final boolean tombstone = (action.actionIntent == null); 6110 RemoteViews button = new BuilderRemoteViews(mContext.getApplicationInfo(), 6111 emphasizedMode ? getEmphasizedActionLayoutResource() 6112 : tombstone ? getActionTombstoneLayoutResource() 6113 : getActionLayoutResource()); 6114 if (!tombstone) { 6115 button.setOnClickPendingIntent(R.id.action0, action.actionIntent); 6116 } 6117 button.setContentDescription(R.id.action0, action.title); 6118 if (action.mRemoteInputs != null) { 6119 button.setRemoteInputs(R.id.action0, action.mRemoteInputs); 6120 } 6121 if (emphasizedMode) { 6122 // change the background bgColor 6123 CharSequence title = action.title; 6124 ColorStateList[] outResultColor = new ColorStateList[1]; 6125 int buttonFillColor = getColors(p).getSecondaryAccentColor(); 6126 if (isLegacy()) { 6127 title = ContrastColorUtil.clearColorSpans(title); 6128 } else { 6129 int notifBackgroundColor = getColors(p).getBackgroundColor(); 6130 title = ensureColorSpanContrast(title, notifBackgroundColor, outResultColor); 6131 } 6132 button.setTextViewText(R.id.action0, processTextSpans(title)); 6133 boolean hasColorOverride = outResultColor[0] != null; 6134 if (hasColorOverride) { 6135 // There's a span spanning the full text, let's take it and use it as the 6136 // background color 6137 buttonFillColor = outResultColor[0].getDefaultColor(); 6138 } 6139 final int textColor = ContrastColorUtil.resolvePrimaryColor(mContext, 6140 buttonFillColor, mInNightMode); 6141 button.setTextColor(R.id.action0, textColor); 6142 // We only want about 20% alpha for the ripple 6143 final int rippleColor = (textColor & 0x00ffffff) | 0x33000000; 6144 button.setColorStateList(R.id.action0, "setRippleColor", 6145 ColorStateList.valueOf(rippleColor)); 6146 button.setColorStateList(R.id.action0, "setButtonBackground", 6147 ColorStateList.valueOf(buttonFillColor)); 6148 if (p.mCallStyleActions) { 6149 button.setImageViewIcon(R.id.action0, action.getIcon()); 6150 boolean priority = action.getExtras().getBoolean(CallStyle.KEY_ACTION_PRIORITY); 6151 button.setBoolean(R.id.action0, "setIsPriority", priority); 6152 int minWidthDimen = 6153 priority ? R.dimen.call_notification_system_action_min_width : 0; 6154 button.setIntDimen(R.id.action0, "setMinimumWidth", minWidthDimen); 6155 } 6156 } else { 6157 button.setTextViewText(R.id.action0, processTextSpans( 6158 processLegacyText(action.title))); 6159 button.setTextColor(R.id.action0, getStandardActionColor(p)); 6160 } 6161 // CallStyle notifications add action buttons which don't actually exist in mActions, 6162 // so we have to omit the index in that case. 6163 int actionIndex = mActions.indexOf(action); 6164 if (actionIndex != -1) { 6165 button.setIntTag(R.id.action0, R.id.notification_action_index_tag, actionIndex); 6166 } 6167 return button; 6168 } 6169 6170 /** 6171 * Ensures contrast on color spans against a background color. also returns the color of the 6172 * text if a span was found that spans over the whole text. 6173 * 6174 * @param charSequence the charSequence on which the spans are 6175 * @param background the background color to ensure the contrast against 6176 * @param outResultColor an array in which a color will be returned as the first element if 6177 * there exists a full length color span. 6178 * @return the contrasted charSequence 6179 */ ensureColorSpanContrast(CharSequence charSequence, int background, ColorStateList[] outResultColor)6180 private static CharSequence ensureColorSpanContrast(CharSequence charSequence, 6181 int background, ColorStateList[] outResultColor) { 6182 if (charSequence instanceof Spanned) { 6183 Spanned ss = (Spanned) charSequence; 6184 Object[] spans = ss.getSpans(0, ss.length(), Object.class); 6185 SpannableStringBuilder builder = new SpannableStringBuilder(ss.toString()); 6186 for (Object span : spans) { 6187 Object resultSpan = span; 6188 int spanStart = ss.getSpanStart(span); 6189 int spanEnd = ss.getSpanEnd(span); 6190 boolean fullLength = (spanEnd - spanStart) == charSequence.length(); 6191 if (resultSpan instanceof CharacterStyle) { 6192 resultSpan = ((CharacterStyle) span).getUnderlying(); 6193 } 6194 if (resultSpan instanceof TextAppearanceSpan) { 6195 TextAppearanceSpan originalSpan = (TextAppearanceSpan) resultSpan; 6196 ColorStateList textColor = originalSpan.getTextColor(); 6197 if (textColor != null) { 6198 int[] colors = textColor.getColors(); 6199 int[] newColors = new int[colors.length]; 6200 for (int i = 0; i < newColors.length; i++) { 6201 boolean isBgDark = isColorDark(background); 6202 newColors[i] = ContrastColorUtil.ensureLargeTextContrast( 6203 colors[i], background, isBgDark); 6204 } 6205 textColor = new ColorStateList(textColor.getStates().clone(), 6206 newColors); 6207 if (fullLength) { 6208 outResultColor[0] = textColor; 6209 // Let's drop the color from the span 6210 textColor = null; 6211 } 6212 resultSpan = new TextAppearanceSpan( 6213 originalSpan.getFamily(), 6214 originalSpan.getTextStyle(), 6215 originalSpan.getTextSize(), 6216 textColor, 6217 originalSpan.getLinkTextColor()); 6218 } 6219 } else if (resultSpan instanceof ForegroundColorSpan) { 6220 ForegroundColorSpan originalSpan = (ForegroundColorSpan) resultSpan; 6221 int foregroundColor = originalSpan.getForegroundColor(); 6222 boolean isBgDark = isColorDark(background); 6223 foregroundColor = ContrastColorUtil.ensureLargeTextContrast( 6224 foregroundColor, background, isBgDark); 6225 if (fullLength) { 6226 outResultColor[0] = ColorStateList.valueOf(foregroundColor); 6227 resultSpan = null; 6228 } else { 6229 resultSpan = new ForegroundColorSpan(foregroundColor); 6230 } 6231 } else { 6232 resultSpan = span; 6233 } 6234 if (resultSpan != null) { 6235 builder.setSpan(resultSpan, spanStart, spanEnd, ss.getSpanFlags(span)); 6236 } 6237 } 6238 return builder; 6239 } 6240 return charSequence; 6241 } 6242 6243 /** 6244 * Determines if the color is light or dark. Specifically, this is using the same metric as 6245 * {@link ContrastColorUtil#resolvePrimaryColor(Context, int, boolean)} and peers so that 6246 * the direction of color shift is consistent. 6247 * 6248 * @param color the color to check 6249 * @return true if the color has higher contrast with white than black 6250 */ isColorDark(int color)6251 private static boolean isColorDark(int color) { 6252 // as per ContrastColorUtil.shouldUseDark, this uses the color contrast midpoint. 6253 return ContrastColorUtil.calculateLuminance(color) <= 0.17912878474; 6254 } 6255 6256 /** 6257 * @return Whether we are currently building a notification from a legacy (an app that 6258 * doesn't create material notifications by itself) app. 6259 */ isLegacy()6260 private boolean isLegacy() { 6261 if (!mIsLegacyInitialized) { 6262 mIsLegacy = mContext.getApplicationInfo().targetSdkVersion 6263 < Build.VERSION_CODES.LOLLIPOP; 6264 mIsLegacyInitialized = true; 6265 } 6266 return mIsLegacy; 6267 } 6268 6269 private CharSequence processLegacyText(CharSequence charSequence) { 6270 boolean isAlreadyLightText = isLegacy() || textColorsNeedInversion(); 6271 if (isAlreadyLightText) { 6272 return getColorUtil().invertCharSequenceColors(charSequence); 6273 } else { 6274 return charSequence; 6275 } 6276 } 6277 6278 /** 6279 * Apply any necessary colors to the small icon 6280 */ 6281 private void processSmallIconColor(Icon smallIcon, RemoteViews contentView, 6282 StandardTemplateParams p) { 6283 boolean colorable = !isLegacy() || getColorUtil().isGrayscaleIcon(mContext, smallIcon); 6284 int color = getSmallIconColor(p); 6285 contentView.setInt(R.id.icon, "setBackgroundColor", 6286 getBackgroundColor(p)); 6287 contentView.setInt(R.id.icon, "setOriginalIconColor", 6288 colorable ? color : COLOR_INVALID); 6289 } 6290 6291 /** 6292 * Make the largeIcon dark if it's a fake smallIcon (that is, 6293 * if it's grayscale). 6294 */ 6295 // TODO: also check bounds, transparency, that sort of thing. 6296 private void processLargeLegacyIcon(Icon largeIcon, RemoteViews contentView, 6297 StandardTemplateParams p) { 6298 if (largeIcon != null && isLegacy() 6299 && getColorUtil().isGrayscaleIcon(mContext, largeIcon)) { 6300 // resolve color will fall back to the default when legacy 6301 int color = getSmallIconColor(p); 6302 contentView.setInt(R.id.icon, "setOriginalIconColor", color); 6303 } 6304 } 6305 6306 private void sanitizeColor() { 6307 if (mN.color != COLOR_DEFAULT) { 6308 mN.color |= 0xFF000000; // no alpha for custom colors 6309 } 6310 } 6311 6312 /** 6313 * Gets the standard action button color 6314 */ 6315 private @ColorInt int getStandardActionColor(Notification.StandardTemplateParams p) { 6316 return mTintActionButtons || isBackgroundColorized(p) 6317 ? getPrimaryAccentColor(p) : getSecondaryTextColor(p); 6318 } 6319 6320 /** 6321 * Gets the foreground color of the small icon. If the notification is colorized, this 6322 * is the primary text color, otherwise it's the contrast-adjusted app-provided color. 6323 */ 6324 private @ColorInt int getSmallIconColor(StandardTemplateParams p) { 6325 return getColors(p).getContrastColor(); 6326 } 6327 6328 /** @return the theme's accent color for colored UI elements. */ 6329 private @ColorInt int getPrimaryAccentColor(StandardTemplateParams p) { 6330 return getColors(p).getPrimaryAccentColor(); 6331 } 6332 6333 /** 6334 * Apply the unstyled operations and return a new {@link Notification} object. 6335 * @hide 6336 */ 6337 @NonNull 6338 public Notification buildUnstyled() { 6339 if (mActions.size() > 0) { 6340 mN.actions = new Action[mActions.size()]; 6341 mActions.toArray(mN.actions); 6342 } 6343 if (!mPersonList.isEmpty()) { 6344 mN.extras.putParcelableArrayList(EXTRA_PEOPLE_LIST, mPersonList); 6345 } 6346 if (mN.bigContentView != null || mN.contentView != null 6347 || mN.headsUpContentView != null) { 6348 mN.extras.putBoolean(EXTRA_CONTAINS_CUSTOM_VIEW, true); 6349 } 6350 return mN; 6351 } 6352 6353 /** 6354 * Creates a Builder from an existing notification so further changes can be made. 6355 * @param context The context for your application / activity. 6356 * @param n The notification to create a Builder from. 6357 */ 6358 @NonNull recoverBuilder(Context context, Notification n)6359 public static Notification.Builder recoverBuilder(Context context, Notification n) { 6360 // Re-create notification context so we can access app resources. 6361 ApplicationInfo applicationInfo = n.extras.getParcelable( 6362 EXTRA_BUILDER_APPLICATION_INFO); 6363 Context builderContext; 6364 if (applicationInfo != null) { 6365 try { 6366 builderContext = context.createApplicationContext(applicationInfo, 6367 Context.CONTEXT_RESTRICTED); 6368 } catch (NameNotFoundException e) { 6369 Log.e(TAG, "ApplicationInfo " + applicationInfo + " not found"); 6370 builderContext = context; // try with our context 6371 } 6372 } else { 6373 builderContext = context; // try with given context 6374 } 6375 6376 return new Builder(builderContext, n); 6377 } 6378 6379 /** 6380 * Determines whether the platform can generate contextual actions for a notification. 6381 * By default this is true. 6382 */ 6383 @NonNull setAllowSystemGeneratedContextualActions(boolean allowed)6384 public Builder setAllowSystemGeneratedContextualActions(boolean allowed) { 6385 mN.mAllowSystemGeneratedContextualActions = allowed; 6386 return this; 6387 } 6388 6389 /** 6390 * @deprecated Use {@link #build()} instead. 6391 */ 6392 @Deprecated getNotification()6393 public Notification getNotification() { 6394 return build(); 6395 } 6396 6397 /** 6398 * Combine all of the options that have been set and return a new {@link Notification} 6399 * object. 6400 * 6401 * If this notification has {@link BubbleMetadata} attached that was created with 6402 * a shortcutId a check will be performed to ensure the shortcutId supplied to bubble 6403 * metadata matches the shortcutId set on the notification builder, if one was set. 6404 * If the shortcutId's were specified but do not match, an exception is thrown here. 6405 * 6406 * @see BubbleMetadata.Builder#Builder(String) 6407 * @see #setShortcutId(String) 6408 */ 6409 @NonNull build()6410 public Notification build() { 6411 // Check shortcut id matches 6412 if (mN.mShortcutId != null 6413 && mN.mBubbleMetadata != null 6414 && mN.mBubbleMetadata.getShortcutId() != null 6415 && !mN.mShortcutId.equals(mN.mBubbleMetadata.getShortcutId())) { 6416 throw new IllegalArgumentException( 6417 "Notification and BubbleMetadata shortcut id's don't match," 6418 + " notification: " + mN.mShortcutId 6419 + " vs bubble: " + mN.mBubbleMetadata.getShortcutId()); 6420 } 6421 6422 // first, add any extras from the calling code 6423 if (mUserExtras != null) { 6424 mN.extras = getAllExtras(); 6425 } 6426 6427 mN.creationTime = System.currentTimeMillis(); 6428 6429 // lazy stuff from mContext; see comment in Builder(Context, Notification) 6430 Notification.addFieldsFromContext(mContext, mN); 6431 6432 buildUnstyled(); 6433 6434 if (mStyle != null) { 6435 mStyle.reduceImageSizes(mContext); 6436 mStyle.purgeResources(); 6437 mStyle.validate(mContext); 6438 mStyle.buildStyled(mN); 6439 } 6440 mN.reduceImageSizes(mContext); 6441 6442 if (mContext.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N 6443 && !styleDisplaysCustomViewInline()) { 6444 if (mN.contentView == null) { 6445 mN.contentView = createContentView(); 6446 mN.extras.putInt(EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT, 6447 mN.contentView.getSequenceNumber()); 6448 } 6449 if (mN.bigContentView == null) { 6450 mN.bigContentView = createBigContentView(); 6451 if (mN.bigContentView != null) { 6452 mN.extras.putInt(EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT, 6453 mN.bigContentView.getSequenceNumber()); 6454 } 6455 } 6456 if (mN.headsUpContentView == null) { 6457 mN.headsUpContentView = createHeadsUpContentView(); 6458 if (mN.headsUpContentView != null) { 6459 mN.extras.putInt(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT, 6460 mN.headsUpContentView.getSequenceNumber()); 6461 } 6462 } 6463 } 6464 6465 if ((mN.defaults & DEFAULT_LIGHTS) != 0) { 6466 mN.flags |= FLAG_SHOW_LIGHTS; 6467 } 6468 6469 mN.allPendingIntents = null; 6470 6471 return mN; 6472 } 6473 styleDisplaysCustomViewInline()6474 private boolean styleDisplaysCustomViewInline() { 6475 return mStyle != null && mStyle.displayCustomViewInline(); 6476 } 6477 6478 /** 6479 * Apply this Builder to an existing {@link Notification} object. 6480 * 6481 * @hide 6482 */ 6483 @NonNull buildInto(@onNull Notification n)6484 public Notification buildInto(@NonNull Notification n) { 6485 build().cloneInto(n, true); 6486 return n; 6487 } 6488 6489 /** 6490 * Removes RemoteViews that were created for compatibility from {@param n}, if they did not 6491 * change. 6492 * 6493 * @return {@param n}, if no stripping is needed, otherwise a stripped clone of {@param n}. 6494 * 6495 * @hide 6496 */ maybeCloneStrippedForDelivery(Notification n)6497 public static Notification maybeCloneStrippedForDelivery(Notification n) { 6498 String templateClass = n.extras.getString(EXTRA_TEMPLATE); 6499 6500 // Only strip views for known Styles because we won't know how to 6501 // re-create them otherwise. 6502 if (!TextUtils.isEmpty(templateClass) 6503 && getNotificationStyleClass(templateClass) == null) { 6504 return n; 6505 } 6506 6507 // Only strip unmodified BuilderRemoteViews. 6508 boolean stripContentView = n.contentView instanceof BuilderRemoteViews && 6509 n.extras.getInt(EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT, -1) == 6510 n.contentView.getSequenceNumber(); 6511 boolean stripBigContentView = n.bigContentView instanceof BuilderRemoteViews && 6512 n.extras.getInt(EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT, -1) == 6513 n.bigContentView.getSequenceNumber(); 6514 boolean stripHeadsUpContentView = n.headsUpContentView instanceof BuilderRemoteViews && 6515 n.extras.getInt(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT, -1) == 6516 n.headsUpContentView.getSequenceNumber(); 6517 6518 // Nothing to do here, no need to clone. 6519 if (!stripContentView && !stripBigContentView && !stripHeadsUpContentView) { 6520 return n; 6521 } 6522 6523 Notification clone = n.clone(); 6524 if (stripContentView) { 6525 clone.contentView = null; 6526 clone.extras.remove(EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT); 6527 } 6528 if (stripBigContentView) { 6529 clone.bigContentView = null; 6530 clone.extras.remove(EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT); 6531 } 6532 if (stripHeadsUpContentView) { 6533 clone.headsUpContentView = null; 6534 clone.extras.remove(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT); 6535 } 6536 return clone; 6537 } 6538 6539 @UnsupportedAppUsage getBaseLayoutResource()6540 private int getBaseLayoutResource() { 6541 return R.layout.notification_template_material_base; 6542 } 6543 getHeadsUpBaseLayoutResource()6544 private int getHeadsUpBaseLayoutResource() { 6545 return R.layout.notification_template_material_heads_up_base; 6546 } 6547 getBigBaseLayoutResource()6548 private int getBigBaseLayoutResource() { 6549 return R.layout.notification_template_material_big_base; 6550 } 6551 getBigPictureLayoutResource()6552 private int getBigPictureLayoutResource() { 6553 return R.layout.notification_template_material_big_picture; 6554 } 6555 getBigTextLayoutResource()6556 private int getBigTextLayoutResource() { 6557 return R.layout.notification_template_material_big_text; 6558 } 6559 getInboxLayoutResource()6560 private int getInboxLayoutResource() { 6561 return R.layout.notification_template_material_inbox; 6562 } 6563 getMessagingLayoutResource()6564 private int getMessagingLayoutResource() { 6565 return R.layout.notification_template_material_messaging; 6566 } 6567 getBigMessagingLayoutResource()6568 private int getBigMessagingLayoutResource() { 6569 return R.layout.notification_template_material_big_messaging; 6570 } 6571 getConversationLayoutResource()6572 private int getConversationLayoutResource() { 6573 return R.layout.notification_template_material_conversation; 6574 } 6575 getActionLayoutResource()6576 private int getActionLayoutResource() { 6577 return R.layout.notification_material_action; 6578 } 6579 getEmphasizedActionLayoutResource()6580 private int getEmphasizedActionLayoutResource() { 6581 return R.layout.notification_material_action_emphasized; 6582 } 6583 getActionTombstoneLayoutResource()6584 private int getActionTombstoneLayoutResource() { 6585 return R.layout.notification_material_action_tombstone; 6586 } 6587 getBackgroundColor(StandardTemplateParams p)6588 private @ColorInt int getBackgroundColor(StandardTemplateParams p) { 6589 return getColors(p).getBackgroundColor(); 6590 } 6591 textColorsNeedInversion()6592 private boolean textColorsNeedInversion() { 6593 if (mStyle == null || !MediaStyle.class.equals(mStyle.getClass())) { 6594 return false; 6595 } 6596 int targetSdkVersion = mContext.getApplicationInfo().targetSdkVersion; 6597 return targetSdkVersion > Build.VERSION_CODES.M 6598 && targetSdkVersion < Build.VERSION_CODES.O; 6599 } 6600 6601 /** 6602 * Get the text that should be displayed in the statusBar when heads upped. This is 6603 * usually just the app name, but may be different depending on the style. 6604 * 6605 * @param publicMode If true, return a text that is safe to display in public. 6606 * 6607 * @hide 6608 */ getHeadsUpStatusBarText(boolean publicMode)6609 public CharSequence getHeadsUpStatusBarText(boolean publicMode) { 6610 if (mStyle != null && !publicMode) { 6611 CharSequence text = mStyle.getHeadsUpStatusBarText(); 6612 if (!TextUtils.isEmpty(text)) { 6613 return text; 6614 } 6615 } 6616 return loadHeaderAppName(); 6617 } 6618 6619 /** 6620 * @return if this builder uses a template 6621 * 6622 * @hide 6623 */ usesTemplate()6624 public boolean usesTemplate() { 6625 return (mN.contentView == null && mN.headsUpContentView == null 6626 && mN.bigContentView == null) 6627 || styleDisplaysCustomViewInline(); 6628 } 6629 } 6630 6631 /** 6632 * Reduces the image sizes to conform to a maximum allowed size. This also processes all custom 6633 * remote views. 6634 * 6635 * @hide 6636 */ reduceImageSizes(Context context)6637 void reduceImageSizes(Context context) { 6638 if (extras.getBoolean(EXTRA_REDUCED_IMAGES)) { 6639 return; 6640 } 6641 boolean isLowRam = ActivityManager.isLowRamDeviceStatic(); 6642 if (mLargeIcon != null || largeIcon != null) { 6643 Resources resources = context.getResources(); 6644 Class<? extends Style> style = getNotificationStyle(); 6645 int maxSize = resources.getDimensionPixelSize(isLowRam 6646 ? R.dimen.notification_right_icon_size_low_ram 6647 : R.dimen.notification_right_icon_size); 6648 if (mLargeIcon != null) { 6649 mLargeIcon.scaleDownIfNecessary(maxSize, maxSize); 6650 } 6651 if (largeIcon != null) { 6652 largeIcon = Icon.scaleDownIfNecessary(largeIcon, maxSize, maxSize); 6653 } 6654 } 6655 reduceImageSizesForRemoteView(contentView, context, isLowRam); 6656 reduceImageSizesForRemoteView(headsUpContentView, context, isLowRam); 6657 reduceImageSizesForRemoteView(bigContentView, context, isLowRam); 6658 extras.putBoolean(EXTRA_REDUCED_IMAGES, true); 6659 } 6660 reduceImageSizesForRemoteView(RemoteViews remoteView, Context context, boolean isLowRam)6661 private void reduceImageSizesForRemoteView(RemoteViews remoteView, Context context, 6662 boolean isLowRam) { 6663 if (remoteView != null) { 6664 Resources resources = context.getResources(); 6665 int maxWidth = resources.getDimensionPixelSize(isLowRam 6666 ? R.dimen.notification_custom_view_max_image_width_low_ram 6667 : R.dimen.notification_custom_view_max_image_width); 6668 int maxHeight = resources.getDimensionPixelSize(isLowRam 6669 ? R.dimen.notification_custom_view_max_image_height_low_ram 6670 : R.dimen.notification_custom_view_max_image_height); 6671 remoteView.reduceImageSizes(maxWidth, maxHeight); 6672 } 6673 } 6674 6675 /** 6676 * @return whether this notification is a foreground service notification 6677 * @hide 6678 */ isForegroundService()6679 public boolean isForegroundService() { 6680 return (flags & Notification.FLAG_FOREGROUND_SERVICE) != 0; 6681 } 6682 6683 /** 6684 * Describe whether this notification's content such that it should always display 6685 * immediately when tied to a foreground service, even if the system might generally 6686 * avoid showing the notifications for short-lived foreground service lifetimes. 6687 * 6688 * Immediate visibility of the Notification is indicated when: 6689 * <ul> 6690 * <li>The app specifically indicated it with 6691 * {@link Notification.Builder#setForegroundServiceBehavior(int) 6692 * setForegroundServiceBehavior(BEHAVIOR_IMMEDIATE_DISPLAY)}</li> 6693 * <li>It is a media notification or has an associated media session</li> 6694 * <li>It is a call or navigation notification</li> 6695 * <li>It provides additional action affordances</li> 6696 * </ul> 6697 * 6698 * If the app has specified 6699 * {@code NotificationBuilder.setForegroundServiceBehavior(BEHAVIOR_DEFERRED_DISPLAY)} 6700 * then this method will return {@code false} and notification visibility will be 6701 * deferred following the service's transition to the foreground state even in the 6702 * circumstances described above. 6703 * 6704 * @return whether this notification should be displayed immediately when 6705 * its associated service transitions to the foreground state 6706 * @hide 6707 */ 6708 @TestApi shouldShowForegroundImmediately()6709 public boolean shouldShowForegroundImmediately() { 6710 // Has the app demanded immediate display? 6711 if (mFgsDeferBehavior == FOREGROUND_SERVICE_IMMEDIATE) { 6712 return true; 6713 } 6714 6715 // Has the app demanded deferred display? 6716 if (mFgsDeferBehavior == FOREGROUND_SERVICE_DEFERRED) { 6717 return false; 6718 } 6719 6720 // We show these sorts of notifications immediately in the absence of 6721 // any explicit app declaration 6722 if (isMediaNotification() || hasMediaSession() 6723 || CATEGORY_CALL.equals(category) 6724 || CATEGORY_NAVIGATION.equals(category) 6725 || (actions != null && actions.length > 0)) { 6726 return true; 6727 } 6728 6729 // No extenuating circumstances: defer visibility 6730 return false; 6731 } 6732 6733 /** 6734 * Has forced deferral for FGS purposes been specified? 6735 * @hide 6736 */ isForegroundDisplayForceDeferred()6737 public boolean isForegroundDisplayForceDeferred() { 6738 return FOREGROUND_SERVICE_DEFERRED == mFgsDeferBehavior; 6739 } 6740 6741 /** 6742 * @return whether this notification has a media session attached 6743 * @hide 6744 */ hasMediaSession()6745 public boolean hasMediaSession() { 6746 return extras.getParcelable(Notification.EXTRA_MEDIA_SESSION) != null; 6747 } 6748 6749 /** 6750 * @return the style class of this notification 6751 * @hide 6752 */ getNotificationStyle()6753 public Class<? extends Notification.Style> getNotificationStyle() { 6754 String templateClass = extras.getString(Notification.EXTRA_TEMPLATE); 6755 6756 if (!TextUtils.isEmpty(templateClass)) { 6757 return Notification.getNotificationStyleClass(templateClass); 6758 } 6759 return null; 6760 } 6761 6762 /** 6763 * @return whether the style of this notification is the one provided 6764 * @hide 6765 */ isStyle(@onNull Class<? extends Style> styleClass)6766 public boolean isStyle(@NonNull Class<? extends Style> styleClass) { 6767 String templateClass = extras.getString(Notification.EXTRA_TEMPLATE); 6768 return Objects.equals(templateClass, styleClass.getName()); 6769 } 6770 6771 /** 6772 * @return true if this notification is colorized *for the purposes of ranking*. If the 6773 * {@link #color} is {@link #COLOR_DEFAULT} this will be true, even though the actual 6774 * appearance of the notification may not be "colorized". 6775 * 6776 * @hide 6777 */ isColorized()6778 public boolean isColorized() { 6779 return extras.getBoolean(EXTRA_COLORIZED) 6780 && (hasColorizedPermission() || isForegroundService()); 6781 } 6782 6783 /** 6784 * Returns whether an app can colorize due to the android.permission.USE_COLORIZED_NOTIFICATIONS 6785 * permission. The permission is checked when a notification is enqueued. 6786 */ hasColorizedPermission()6787 private boolean hasColorizedPermission() { 6788 return (flags & Notification.FLAG_CAN_COLORIZE) != 0; 6789 } 6790 6791 /** 6792 * @return true if this is a media notification 6793 * 6794 * @hide 6795 */ isMediaNotification()6796 public boolean isMediaNotification() { 6797 Class<? extends Style> style = getNotificationStyle(); 6798 if (MediaStyle.class.equals(style)) { 6799 return true; 6800 } else if (DecoratedMediaCustomViewStyle.class.equals(style)) { 6801 return true; 6802 } 6803 return false; 6804 } 6805 6806 /** 6807 * @return true if this notification is showing as a bubble 6808 * 6809 * @hide 6810 */ isBubbleNotification()6811 public boolean isBubbleNotification() { 6812 return (flags & Notification.FLAG_BUBBLE) != 0; 6813 } 6814 hasLargeIcon()6815 private boolean hasLargeIcon() { 6816 return mLargeIcon != null || largeIcon != null; 6817 } 6818 6819 /** 6820 * @return true if the notification will show the time; false otherwise 6821 * @hide 6822 */ showsTime()6823 public boolean showsTime() { 6824 return when != 0 && extras.getBoolean(EXTRA_SHOW_WHEN); 6825 } 6826 6827 /** 6828 * @return true if the notification will show a chronometer; false otherwise 6829 * @hide 6830 */ showsChronometer()6831 public boolean showsChronometer() { 6832 return when != 0 && extras.getBoolean(EXTRA_SHOW_CHRONOMETER); 6833 } 6834 6835 /** 6836 * @return true if the notification has image 6837 */ hasImage()6838 public boolean hasImage() { 6839 if (isStyle(MessagingStyle.class) && extras != null) { 6840 final Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES); 6841 if (!ArrayUtils.isEmpty(messages)) { 6842 for (MessagingStyle.Message m : MessagingStyle.Message 6843 .getMessagesFromBundleArray(messages)) { 6844 if (m.getDataUri() != null 6845 && m.getDataMimeType() != null 6846 && m.getDataMimeType().startsWith("image/")) { 6847 return true; 6848 } 6849 } 6850 } 6851 } else if (hasLargeIcon()) { 6852 return true; 6853 } else if (extras.containsKey(EXTRA_BACKGROUND_IMAGE_URI)) { 6854 return true; 6855 } 6856 return false; 6857 } 6858 6859 6860 /** 6861 * @removed 6862 */ 6863 @SystemApi getNotificationStyleClass(String templateClass)6864 public static Class<? extends Style> getNotificationStyleClass(String templateClass) { 6865 for (Class<? extends Style> innerClass : PLATFORM_STYLE_CLASSES) { 6866 if (templateClass.equals(innerClass.getName())) { 6867 return innerClass; 6868 } 6869 } 6870 return null; 6871 } 6872 buildCustomContentIntoTemplate(@onNull Context context, @NonNull RemoteViews template, @Nullable RemoteViews customContent, @NonNull StandardTemplateParams p, @NonNull TemplateBindResult result)6873 private static void buildCustomContentIntoTemplate(@NonNull Context context, 6874 @NonNull RemoteViews template, @Nullable RemoteViews customContent, 6875 @NonNull StandardTemplateParams p, @NonNull TemplateBindResult result) { 6876 int childIndex = -1; 6877 if (customContent != null) { 6878 // Need to clone customContent before adding, because otherwise it can no longer be 6879 // parceled independently of remoteViews. 6880 customContent = customContent.clone(); 6881 if (p.mHeaderless) { 6882 template.removeFromParent(R.id.notification_top_line); 6883 // We do not know how many lines ar emote view has, so we presume it has 2; this 6884 // ensures that we don't under-pad the content, which could lead to abuse, at the 6885 // cost of making single-line custom content over-padded. 6886 Builder.setHeaderlessVerticalMargins(template, p, true /* hasSecondLine */); 6887 } else { 6888 // also update the end margin to account for the large icon or expander 6889 Resources resources = context.getResources(); 6890 result.mTitleMarginSet.applyToView(template, R.id.notification_main_column, 6891 resources.getDimension(R.dimen.notification_content_margin_end) 6892 / resources.getDisplayMetrics().density); 6893 } 6894 template.removeAllViewsExceptId(R.id.notification_main_column, R.id.progress); 6895 template.addView(R.id.notification_main_column, customContent, 0 /* index */); 6896 template.addFlags(RemoteViews.FLAG_REAPPLY_DISALLOWED); 6897 childIndex = 0; 6898 } 6899 template.setIntTag(R.id.notification_main_column, 6900 com.android.internal.R.id.notification_custom_view_index_tag, 6901 childIndex); 6902 } 6903 6904 /** 6905 * An object that can apply a rich notification style to a {@link Notification.Builder} 6906 * object. 6907 */ 6908 public static abstract class Style { 6909 6910 /** 6911 * The number of items allowed simulatanously in the remote input history. 6912 * @hide 6913 */ 6914 static final int MAX_REMOTE_INPUT_HISTORY_LINES = 3; 6915 private CharSequence mBigContentTitle; 6916 6917 /** 6918 * @hide 6919 */ 6920 protected CharSequence mSummaryText = null; 6921 6922 /** 6923 * @hide 6924 */ 6925 protected boolean mSummaryTextSet = false; 6926 6927 protected Builder mBuilder; 6928 6929 /** 6930 * Overrides ContentTitle in the big form of the template. 6931 * This defaults to the value passed to setContentTitle(). 6932 */ internalSetBigContentTitle(CharSequence title)6933 protected void internalSetBigContentTitle(CharSequence title) { 6934 mBigContentTitle = title; 6935 } 6936 6937 /** 6938 * Set the first line of text after the detail section in the big form of the template. 6939 */ internalSetSummaryText(CharSequence cs)6940 protected void internalSetSummaryText(CharSequence cs) { 6941 mSummaryText = cs; 6942 mSummaryTextSet = true; 6943 } 6944 setBuilder(Builder builder)6945 public void setBuilder(Builder builder) { 6946 if (mBuilder != builder) { 6947 mBuilder = builder; 6948 if (mBuilder != null) { 6949 mBuilder.setStyle(this); 6950 } 6951 } 6952 } 6953 checkBuilder()6954 protected void checkBuilder() { 6955 if (mBuilder == null) { 6956 throw new IllegalArgumentException("Style requires a valid Builder object"); 6957 } 6958 } 6959 getStandardView(int layoutId)6960 protected RemoteViews getStandardView(int layoutId) { 6961 // TODO(jeffdq): set the view type based on the layout resource? 6962 StandardTemplateParams p = mBuilder.mParams.reset() 6963 .viewType(StandardTemplateParams.VIEW_TYPE_UNSPECIFIED) 6964 .fillTextsFrom(mBuilder); 6965 return getStandardView(layoutId, p, null); 6966 } 6967 6968 6969 /** 6970 * Get the standard view for this style. 6971 * 6972 * @param layoutId The layout id to use. 6973 * @param p the params for this inflation. 6974 * @param result The result where template bind information is saved. 6975 * @return A remoteView for this style. 6976 * @hide 6977 */ getStandardView(int layoutId, StandardTemplateParams p, TemplateBindResult result)6978 protected RemoteViews getStandardView(int layoutId, StandardTemplateParams p, 6979 TemplateBindResult result) { 6980 checkBuilder(); 6981 6982 if (mBigContentTitle != null) { 6983 p.title = mBigContentTitle; 6984 } 6985 6986 return mBuilder.applyStandardTemplateWithActions(layoutId, p, result); 6987 } 6988 6989 /** 6990 * Construct a Style-specific RemoteViews for the collapsed notification layout. 6991 * The default implementation has nothing additional to add. 6992 * 6993 * @param increasedHeight true if this layout be created with an increased height. 6994 * @hide 6995 */ makeContentView(boolean increasedHeight)6996 public RemoteViews makeContentView(boolean increasedHeight) { 6997 return null; 6998 } 6999 7000 /** 7001 * Construct a Style-specific RemoteViews for the final big notification layout. 7002 * @hide 7003 */ makeBigContentView()7004 public RemoteViews makeBigContentView() { 7005 return null; 7006 } 7007 7008 /** 7009 * Construct a Style-specific RemoteViews for the final HUN layout. 7010 * 7011 * @param increasedHeight true if this layout be created with an increased height. 7012 * @hide 7013 */ makeHeadsUpContentView(boolean increasedHeight)7014 public RemoteViews makeHeadsUpContentView(boolean increasedHeight) { 7015 return null; 7016 } 7017 7018 /** 7019 * Apply any style-specific extras to this notification before shipping it out. 7020 * @hide 7021 */ addExtras(Bundle extras)7022 public void addExtras(Bundle extras) { 7023 if (mSummaryTextSet) { 7024 extras.putCharSequence(EXTRA_SUMMARY_TEXT, mSummaryText); 7025 } 7026 if (mBigContentTitle != null) { 7027 extras.putCharSequence(EXTRA_TITLE_BIG, mBigContentTitle); 7028 } 7029 extras.putString(EXTRA_TEMPLATE, this.getClass().getName()); 7030 } 7031 7032 /** 7033 * Reconstruct the internal state of this Style object from extras. 7034 * @hide 7035 */ restoreFromExtras(Bundle extras)7036 protected void restoreFromExtras(Bundle extras) { 7037 if (extras.containsKey(EXTRA_SUMMARY_TEXT)) { 7038 mSummaryText = extras.getCharSequence(EXTRA_SUMMARY_TEXT); 7039 mSummaryTextSet = true; 7040 } 7041 if (extras.containsKey(EXTRA_TITLE_BIG)) { 7042 mBigContentTitle = extras.getCharSequence(EXTRA_TITLE_BIG); 7043 } 7044 } 7045 7046 7047 /** 7048 * @hide 7049 */ buildStyled(Notification wip)7050 public Notification buildStyled(Notification wip) { 7051 addExtras(wip.extras); 7052 return wip; 7053 } 7054 7055 /** 7056 * @hide 7057 */ purgeResources()7058 public void purgeResources() {} 7059 7060 /** 7061 * Calls {@link android.app.Notification.Builder#build()} on the Builder this Style is 7062 * attached to. 7063 * 7064 * @return the fully constructed Notification. 7065 */ build()7066 public Notification build() { 7067 checkBuilder(); 7068 return mBuilder.build(); 7069 } 7070 7071 /** 7072 * @hide 7073 * @return Whether we should put the summary be put into the notification header 7074 */ hasSummaryInHeader()7075 public boolean hasSummaryInHeader() { 7076 return true; 7077 } 7078 7079 /** 7080 * @hide 7081 * @return Whether custom content views are displayed inline in the style 7082 */ displayCustomViewInline()7083 public boolean displayCustomViewInline() { 7084 return false; 7085 } 7086 7087 /** 7088 * Reduces the image sizes contained in this style. 7089 * 7090 * @hide 7091 */ reduceImageSizes(Context context)7092 public void reduceImageSizes(Context context) { 7093 } 7094 7095 /** 7096 * Validate that this style was properly composed. This is called at build time. 7097 * @hide 7098 */ validate(Context context)7099 public void validate(Context context) { 7100 } 7101 7102 /** 7103 * @hide 7104 */ areNotificationsVisiblyDifferent(Style other)7105 public abstract boolean areNotificationsVisiblyDifferent(Style other); 7106 7107 /** 7108 * @return the text that should be displayed in the statusBar when heads-upped. 7109 * If {@code null} is returned, the default implementation will be used. 7110 * 7111 * @hide 7112 */ getHeadsUpStatusBarText()7113 public CharSequence getHeadsUpStatusBarText() { 7114 return null; 7115 } 7116 } 7117 7118 /** 7119 * Helper class for generating large-format notifications that include a large image attachment. 7120 * 7121 * Here's how you'd set the <code>BigPictureStyle</code> on a notification: 7122 * <pre class="prettyprint"> 7123 * Notification notif = new Notification.Builder(mContext) 7124 * .setContentTitle("New photo from " + sender.toString()) 7125 * .setContentText(subject) 7126 * .setSmallIcon(R.drawable.new_post) 7127 * .setLargeIcon(aBitmap) 7128 * .setStyle(new Notification.BigPictureStyle() 7129 * .bigPicture(aBigBitmap)) 7130 * .build(); 7131 * </pre> 7132 * 7133 * @see Notification#bigContentView 7134 */ 7135 public static class BigPictureStyle extends Style { 7136 private Icon mPictureIcon; 7137 private Icon mBigLargeIcon; 7138 private boolean mBigLargeIconSet = false; 7139 private CharSequence mPictureContentDescription; 7140 private boolean mShowBigPictureWhenCollapsed; 7141 BigPictureStyle()7142 public BigPictureStyle() { 7143 } 7144 7145 /** 7146 * @deprecated use {@code BigPictureStyle()}. 7147 */ 7148 @Deprecated BigPictureStyle(Builder builder)7149 public BigPictureStyle(Builder builder) { 7150 setBuilder(builder); 7151 } 7152 7153 /** 7154 * Overrides ContentTitle in the big form of the template. 7155 * This defaults to the value passed to setContentTitle(). 7156 */ 7157 @NonNull setBigContentTitle(@ullable CharSequence title)7158 public BigPictureStyle setBigContentTitle(@Nullable CharSequence title) { 7159 internalSetBigContentTitle(safeCharSequence(title)); 7160 return this; 7161 } 7162 7163 /** 7164 * Set the first line of text after the detail section in the big form of the template. 7165 */ 7166 @NonNull setSummaryText(@ullable CharSequence cs)7167 public BigPictureStyle setSummaryText(@Nullable CharSequence cs) { 7168 internalSetSummaryText(safeCharSequence(cs)); 7169 return this; 7170 } 7171 7172 /** 7173 * Set the content description of the big picture. 7174 */ 7175 @NonNull setContentDescription( @ullable CharSequence contentDescription)7176 public BigPictureStyle setContentDescription( 7177 @Nullable CharSequence contentDescription) { 7178 mPictureContentDescription = contentDescription; 7179 return this; 7180 } 7181 7182 /** 7183 * @hide 7184 */ 7185 @Nullable getBigPicture()7186 public Icon getBigPicture() { 7187 if (mPictureIcon != null) { 7188 return mPictureIcon; 7189 } 7190 return null; 7191 } 7192 7193 /** 7194 * Provide the bitmap to be used as the payload for the BigPicture notification. 7195 */ 7196 @NonNull bigPicture(@ullable Bitmap b)7197 public BigPictureStyle bigPicture(@Nullable Bitmap b) { 7198 mPictureIcon = b == null ? null : Icon.createWithBitmap(b); 7199 return this; 7200 } 7201 7202 /** 7203 * Provide the content Uri to be used as the payload for the BigPicture notification. 7204 */ 7205 @NonNull bigPicture(@ullable Icon icon)7206 public BigPictureStyle bigPicture(@Nullable Icon icon) { 7207 mPictureIcon = icon; 7208 return this; 7209 } 7210 7211 /** 7212 * When set, the {@link #bigPicture(Bitmap) big picture} of this style will be promoted and 7213 * shown in place of the {@link Builder#setLargeIcon(Icon) large icon} in the collapsed 7214 * state of this notification. 7215 */ 7216 @NonNull showBigPictureWhenCollapsed(boolean show)7217 public BigPictureStyle showBigPictureWhenCollapsed(boolean show) { 7218 mShowBigPictureWhenCollapsed = show; 7219 return this; 7220 } 7221 7222 /** 7223 * Override the large icon when the big notification is shown. 7224 */ 7225 @NonNull bigLargeIcon(@ullable Bitmap b)7226 public BigPictureStyle bigLargeIcon(@Nullable Bitmap b) { 7227 return bigLargeIcon(b != null ? Icon.createWithBitmap(b) : null); 7228 } 7229 7230 /** 7231 * Override the large icon when the big notification is shown. 7232 */ 7233 @NonNull bigLargeIcon(@ullable Icon icon)7234 public BigPictureStyle bigLargeIcon(@Nullable Icon icon) { 7235 mBigLargeIconSet = true; 7236 mBigLargeIcon = icon; 7237 return this; 7238 } 7239 7240 /** @hide */ 7241 public static final int MIN_ASHMEM_BITMAP_SIZE = 128 * (1 << 10); 7242 7243 /** 7244 * @hide 7245 */ 7246 @Override purgeResources()7247 public void purgeResources() { 7248 super.purgeResources(); 7249 if (mPictureIcon != null) { 7250 mPictureIcon.convertToAshmem(); 7251 } 7252 if (mBigLargeIcon != null) { 7253 mBigLargeIcon.convertToAshmem(); 7254 } 7255 } 7256 7257 /** 7258 * @hide 7259 */ 7260 @Override reduceImageSizes(Context context)7261 public void reduceImageSizes(Context context) { 7262 super.reduceImageSizes(context); 7263 Resources resources = context.getResources(); 7264 boolean isLowRam = ActivityManager.isLowRamDeviceStatic(); 7265 if (mPictureIcon != null) { 7266 int maxPictureWidth = resources.getDimensionPixelSize(isLowRam 7267 ? R.dimen.notification_big_picture_max_height_low_ram 7268 : R.dimen.notification_big_picture_max_height); 7269 int maxPictureHeight = resources.getDimensionPixelSize(isLowRam 7270 ? R.dimen.notification_big_picture_max_width_low_ram 7271 : R.dimen.notification_big_picture_max_width); 7272 mPictureIcon.scaleDownIfNecessary(maxPictureWidth, maxPictureHeight); 7273 } 7274 if (mBigLargeIcon != null) { 7275 int rightIconSize = resources.getDimensionPixelSize(isLowRam 7276 ? R.dimen.notification_right_icon_size_low_ram 7277 : R.dimen.notification_right_icon_size); 7278 mBigLargeIcon.scaleDownIfNecessary(rightIconSize, rightIconSize); 7279 } 7280 } 7281 7282 /** 7283 * @hide 7284 */ 7285 @Override makeContentView(boolean increasedHeight)7286 public RemoteViews makeContentView(boolean increasedHeight) { 7287 if (mPictureIcon == null || !mShowBigPictureWhenCollapsed) { 7288 return super.makeContentView(increasedHeight); 7289 } 7290 7291 StandardTemplateParams p = mBuilder.mParams.reset() 7292 .viewType(StandardTemplateParams.VIEW_TYPE_NORMAL) 7293 .fillTextsFrom(mBuilder) 7294 .promotedPicture(mPictureIcon); 7295 return getStandardView(mBuilder.getBaseLayoutResource(), p, null /* result */); 7296 } 7297 7298 /** 7299 * @hide 7300 */ 7301 @Override makeHeadsUpContentView(boolean increasedHeight)7302 public RemoteViews makeHeadsUpContentView(boolean increasedHeight) { 7303 if (mPictureIcon == null || !mShowBigPictureWhenCollapsed) { 7304 return super.makeHeadsUpContentView(increasedHeight); 7305 } 7306 7307 StandardTemplateParams p = mBuilder.mParams.reset() 7308 .viewType(StandardTemplateParams.VIEW_TYPE_HEADS_UP) 7309 .fillTextsFrom(mBuilder) 7310 .promotedPicture(mPictureIcon); 7311 return getStandardView(mBuilder.getHeadsUpBaseLayoutResource(), p, null /* result */); 7312 } 7313 7314 /** 7315 * @hide 7316 */ makeBigContentView()7317 public RemoteViews makeBigContentView() { 7318 // Replace mN.mLargeIcon with mBigLargeIcon if mBigLargeIconSet 7319 // This covers the following cases: 7320 // 1. mBigLargeIconSet -> mBigLargeIcon (null or non-null) applies, overrides 7321 // mN.mLargeIcon 7322 // 2. !mBigLargeIconSet -> mN.mLargeIcon applies 7323 Icon oldLargeIcon = null; 7324 Bitmap largeIconLegacy = null; 7325 if (mBigLargeIconSet) { 7326 oldLargeIcon = mBuilder.mN.mLargeIcon; 7327 mBuilder.mN.mLargeIcon = mBigLargeIcon; 7328 // The legacy largeIcon might not allow us to clear the image, as it's taken in 7329 // replacement if the other one is null. Because we're restoring these legacy icons 7330 // for old listeners, this is in general non-null. 7331 largeIconLegacy = mBuilder.mN.largeIcon; 7332 mBuilder.mN.largeIcon = null; 7333 } 7334 7335 StandardTemplateParams p = mBuilder.mParams.reset() 7336 .viewType(StandardTemplateParams.VIEW_TYPE_BIG).fillTextsFrom(mBuilder); 7337 RemoteViews contentView = getStandardView(mBuilder.getBigPictureLayoutResource(), 7338 p, null /* result */); 7339 if (mSummaryTextSet) { 7340 contentView.setTextViewText(R.id.text, mBuilder.processTextSpans( 7341 mBuilder.processLegacyText(mSummaryText))); 7342 mBuilder.setTextViewColorSecondary(contentView, R.id.text, p); 7343 contentView.setViewVisibility(R.id.text, View.VISIBLE); 7344 } 7345 7346 if (mBigLargeIconSet) { 7347 mBuilder.mN.mLargeIcon = oldLargeIcon; 7348 mBuilder.mN.largeIcon = largeIconLegacy; 7349 } 7350 7351 contentView.setImageViewIcon(R.id.big_picture, mPictureIcon); 7352 7353 if (mPictureContentDescription != null) { 7354 contentView.setContentDescription(R.id.big_picture, mPictureContentDescription); 7355 } 7356 7357 return contentView; 7358 } 7359 7360 /** 7361 * @hide 7362 */ addExtras(Bundle extras)7363 public void addExtras(Bundle extras) { 7364 super.addExtras(extras); 7365 7366 if (mBigLargeIconSet) { 7367 extras.putParcelable(EXTRA_LARGE_ICON_BIG, mBigLargeIcon); 7368 } 7369 if (mPictureContentDescription != null) { 7370 extras.putCharSequence(EXTRA_PICTURE_CONTENT_DESCRIPTION, 7371 mPictureContentDescription); 7372 } 7373 extras.putBoolean(EXTRA_SHOW_BIG_PICTURE_WHEN_COLLAPSED, mShowBigPictureWhenCollapsed); 7374 7375 // If the icon contains a bitmap, use the old extra so that listeners which look for 7376 // that extra can still find the picture. Don't include the new extra in that case, 7377 // to avoid duplicating data. 7378 if (mPictureIcon != null && mPictureIcon.getType() == Icon.TYPE_BITMAP) { 7379 extras.putParcelable(EXTRA_PICTURE, mPictureIcon.getBitmap()); 7380 extras.putParcelable(EXTRA_PICTURE_ICON, null); 7381 } else { 7382 extras.putParcelable(EXTRA_PICTURE, null); 7383 extras.putParcelable(EXTRA_PICTURE_ICON, mPictureIcon); 7384 } 7385 } 7386 7387 /** 7388 * @hide 7389 */ 7390 @Override restoreFromExtras(Bundle extras)7391 protected void restoreFromExtras(Bundle extras) { 7392 super.restoreFromExtras(extras); 7393 7394 if (extras.containsKey(EXTRA_LARGE_ICON_BIG)) { 7395 mBigLargeIconSet = true; 7396 mBigLargeIcon = extras.getParcelable(EXTRA_LARGE_ICON_BIG); 7397 } 7398 7399 if (extras.containsKey(EXTRA_PICTURE_CONTENT_DESCRIPTION)) { 7400 mPictureContentDescription = 7401 extras.getCharSequence(EXTRA_PICTURE_CONTENT_DESCRIPTION); 7402 } 7403 7404 mShowBigPictureWhenCollapsed = extras.getBoolean(EXTRA_SHOW_BIG_PICTURE_WHEN_COLLAPSED); 7405 7406 mPictureIcon = getPictureIcon(extras); 7407 } 7408 7409 /** @hide */ 7410 @Nullable getPictureIcon(@ullable Bundle extras)7411 public static Icon getPictureIcon(@Nullable Bundle extras) { 7412 if (extras == null) return null; 7413 // When this style adds a picture, we only add one of the keys. If both were added, 7414 // it would most likely be a legacy app trying to override the picture in some way. 7415 // Because of that case it's better to give precedence to the legacy field. 7416 Bitmap bitmapPicture = extras.getParcelable(EXTRA_PICTURE); 7417 if (bitmapPicture != null) { 7418 return Icon.createWithBitmap(bitmapPicture); 7419 } else { 7420 return extras.getParcelable(EXTRA_PICTURE_ICON); 7421 } 7422 } 7423 7424 /** 7425 * @hide 7426 */ 7427 @Override hasSummaryInHeader()7428 public boolean hasSummaryInHeader() { 7429 return false; 7430 } 7431 7432 /** 7433 * @hide 7434 * Note that we aren't actually comparing the contents of the bitmaps here, so this 7435 * is only doing a cursory inspection. Bitmaps of equal size will appear the same. 7436 */ 7437 @Override areNotificationsVisiblyDifferent(Style other)7438 public boolean areNotificationsVisiblyDifferent(Style other) { 7439 if (other == null || getClass() != other.getClass()) { 7440 return true; 7441 } 7442 BigPictureStyle otherS = (BigPictureStyle) other; 7443 return areIconsObviouslyDifferent(getBigPicture(), otherS.getBigPicture()); 7444 } 7445 areIconsObviouslyDifferent(Icon a, Icon b)7446 private static boolean areIconsObviouslyDifferent(Icon a, Icon b) { 7447 if (a == b) { 7448 return false; 7449 } 7450 if (a == null || b == null) { 7451 return true; 7452 } 7453 if (a.sameAs(b)) { 7454 return false; 7455 } 7456 final int aType = a.getType(); 7457 if (aType != b.getType()) { 7458 return true; 7459 } 7460 if (aType == Icon.TYPE_BITMAP || aType == Icon.TYPE_ADAPTIVE_BITMAP) { 7461 final Bitmap aBitmap = a.getBitmap(); 7462 final Bitmap bBitmap = b.getBitmap(); 7463 return aBitmap.getWidth() != bBitmap.getWidth() 7464 || aBitmap.getHeight() != bBitmap.getHeight() 7465 || aBitmap.getConfig() != bBitmap.getConfig() 7466 || aBitmap.getGenerationId() != bBitmap.getGenerationId(); 7467 } 7468 return true; 7469 } 7470 } 7471 7472 /** 7473 * Helper class for generating large-format notifications that include a lot of text. 7474 * 7475 * Here's how you'd set the <code>BigTextStyle</code> on a notification: 7476 * <pre class="prettyprint"> 7477 * Notification notif = new Notification.Builder(mContext) 7478 * .setContentTitle("New mail from " + sender.toString()) 7479 * .setContentText(subject) 7480 * .setSmallIcon(R.drawable.new_mail) 7481 * .setLargeIcon(aBitmap) 7482 * .setStyle(new Notification.BigTextStyle() 7483 * .bigText(aVeryLongString)) 7484 * .build(); 7485 * </pre> 7486 * 7487 * @see Notification#bigContentView 7488 */ 7489 public static class BigTextStyle extends Style { 7490 7491 private CharSequence mBigText; 7492 BigTextStyle()7493 public BigTextStyle() { 7494 } 7495 7496 /** 7497 * @deprecated use {@code BigTextStyle()}. 7498 */ 7499 @Deprecated BigTextStyle(Builder builder)7500 public BigTextStyle(Builder builder) { 7501 setBuilder(builder); 7502 } 7503 7504 /** 7505 * Overrides ContentTitle in the big form of the template. 7506 * This defaults to the value passed to setContentTitle(). 7507 */ setBigContentTitle(CharSequence title)7508 public BigTextStyle setBigContentTitle(CharSequence title) { 7509 internalSetBigContentTitle(safeCharSequence(title)); 7510 return this; 7511 } 7512 7513 /** 7514 * Set the first line of text after the detail section in the big form of the template. 7515 */ setSummaryText(CharSequence cs)7516 public BigTextStyle setSummaryText(CharSequence cs) { 7517 internalSetSummaryText(safeCharSequence(cs)); 7518 return this; 7519 } 7520 7521 /** 7522 * Provide the longer text to be displayed in the big form of the 7523 * template in place of the content text. 7524 */ bigText(CharSequence cs)7525 public BigTextStyle bigText(CharSequence cs) { 7526 mBigText = safeCharSequence(cs); 7527 return this; 7528 } 7529 7530 /** 7531 * @hide 7532 */ getBigText()7533 public CharSequence getBigText() { 7534 return mBigText; 7535 } 7536 7537 /** 7538 * @hide 7539 */ addExtras(Bundle extras)7540 public void addExtras(Bundle extras) { 7541 super.addExtras(extras); 7542 7543 extras.putCharSequence(EXTRA_BIG_TEXT, mBigText); 7544 } 7545 7546 /** 7547 * @hide 7548 */ 7549 @Override restoreFromExtras(Bundle extras)7550 protected void restoreFromExtras(Bundle extras) { 7551 super.restoreFromExtras(extras); 7552 7553 mBigText = extras.getCharSequence(EXTRA_BIG_TEXT); 7554 } 7555 7556 /** 7557 * @param increasedHeight true if this layout be created with an increased height. 7558 * 7559 * @hide 7560 */ 7561 @Override makeContentView(boolean increasedHeight)7562 public RemoteViews makeContentView(boolean increasedHeight) { 7563 if (increasedHeight) { 7564 ArrayList<Action> originalActions = mBuilder.mActions; 7565 mBuilder.mActions = new ArrayList<>(); 7566 RemoteViews remoteViews = makeBigContentView(); 7567 mBuilder.mActions = originalActions; 7568 return remoteViews; 7569 } 7570 return super.makeContentView(increasedHeight); 7571 } 7572 7573 /** 7574 * @hide 7575 */ 7576 @Override makeHeadsUpContentView(boolean increasedHeight)7577 public RemoteViews makeHeadsUpContentView(boolean increasedHeight) { 7578 if (increasedHeight && mBuilder.mActions.size() > 0) { 7579 // TODO(b/163626038): pass VIEW_TYPE_HEADS_UP? 7580 return makeBigContentView(); 7581 } 7582 return super.makeHeadsUpContentView(increasedHeight); 7583 } 7584 7585 /** 7586 * @hide 7587 */ makeBigContentView()7588 public RemoteViews makeBigContentView() { 7589 StandardTemplateParams p = mBuilder.mParams.reset() 7590 .viewType(StandardTemplateParams.VIEW_TYPE_BIG) 7591 .allowTextWithProgress(true) 7592 .textViewId(R.id.big_text) 7593 .fillTextsFrom(mBuilder); 7594 7595 // Replace the text with the big text, but only if the big text is not empty. 7596 CharSequence bigTextText = mBuilder.processLegacyText(mBigText); 7597 if (!TextUtils.isEmpty(bigTextText)) { 7598 p.text(bigTextText); 7599 } 7600 7601 return getStandardView(mBuilder.getBigTextLayoutResource(), p, null /* result */); 7602 } 7603 7604 /** 7605 * @hide 7606 * Spans are ignored when comparing text for visual difference. 7607 */ 7608 @Override areNotificationsVisiblyDifferent(Style other)7609 public boolean areNotificationsVisiblyDifferent(Style other) { 7610 if (other == null || getClass() != other.getClass()) { 7611 return true; 7612 } 7613 BigTextStyle newS = (BigTextStyle) other; 7614 return !Objects.equals(String.valueOf(getBigText()), String.valueOf(newS.getBigText())); 7615 } 7616 7617 } 7618 7619 /** 7620 * Helper class for generating large-format notifications that include multiple back-and-forth 7621 * messages of varying types between any number of people. 7622 * 7623 * <p> 7624 * If the platform does not provide large-format notifications, this method has no effect. The 7625 * user will always see the normal notification view. 7626 * 7627 * <p> 7628 * If the app is targeting Android P and above, it is required to use the {@link Person} 7629 * class in order to get an optimal rendering of the notification and its avatars. For 7630 * conversations involving multiple people, the app should also make sure that it marks the 7631 * conversation as a group with {@link #setGroupConversation(boolean)}. 7632 * 7633 * <p> 7634 * This class is a "rebuilder": It attaches to a Builder object and modifies its behavior. 7635 * Here's an example of how this may be used: 7636 * <pre class="prettyprint"> 7637 * 7638 * Person user = new Person.Builder().setIcon(userIcon).setName(userName).build(); 7639 * MessagingStyle style = new MessagingStyle(user) 7640 * .addMessage(messages[1].getText(), messages[1].getTime(), messages[1].getPerson()) 7641 * .addMessage(messages[2].getText(), messages[2].getTime(), messages[2].getPerson()) 7642 * .setGroupConversation(hasMultiplePeople()); 7643 * 7644 * Notification noti = new Notification.Builder() 7645 * .setContentTitle("2 new messages with " + sender.toString()) 7646 * .setContentText(subject) 7647 * .setSmallIcon(R.drawable.new_message) 7648 * .setLargeIcon(aBitmap) 7649 * .setStyle(style) 7650 * .build(); 7651 * </pre> 7652 */ 7653 public static class MessagingStyle extends Style { 7654 7655 /** 7656 * The maximum number of messages that will be retained in the Notification itself (the 7657 * number displayed is up to the platform). 7658 */ 7659 public static final int MAXIMUM_RETAINED_MESSAGES = 25; 7660 7661 7662 /** @hide */ 7663 public static final int CONVERSATION_TYPE_LEGACY = 0; 7664 /** @hide */ 7665 public static final int CONVERSATION_TYPE_NORMAL = 1; 7666 /** @hide */ 7667 public static final int CONVERSATION_TYPE_IMPORTANT = 2; 7668 7669 /** @hide */ 7670 @IntDef(prefix = {"CONVERSATION_TYPE_"}, value = { 7671 CONVERSATION_TYPE_LEGACY, CONVERSATION_TYPE_NORMAL, CONVERSATION_TYPE_IMPORTANT 7672 }) 7673 @Retention(RetentionPolicy.SOURCE) 7674 public @interface ConversationType {} 7675 7676 @NonNull Person mUser; 7677 @Nullable CharSequence mConversationTitle; 7678 @Nullable Icon mShortcutIcon; 7679 List<Message> mMessages = new ArrayList<>(); 7680 List<Message> mHistoricMessages = new ArrayList<>(); 7681 boolean mIsGroupConversation; 7682 @ConversationType int mConversationType = CONVERSATION_TYPE_LEGACY; 7683 int mUnreadMessageCount; 7684 MessagingStyle()7685 MessagingStyle() { 7686 } 7687 7688 /** 7689 * @param userDisplayName Required - the name to be displayed for any replies sent by the 7690 * user before the posting app reposts the notification with those messages after they've 7691 * been actually sent and in previous messages sent by the user added in 7692 * {@link #addMessage(Notification.MessagingStyle.Message)} 7693 * 7694 * @deprecated use {@code MessagingStyle(Person)} 7695 */ MessagingStyle(@onNull CharSequence userDisplayName)7696 public MessagingStyle(@NonNull CharSequence userDisplayName) { 7697 this(new Person.Builder().setName(userDisplayName).build()); 7698 } 7699 7700 /** 7701 * @param user Required - The person displayed for any messages that are sent by the 7702 * user. Any messages added with {@link #addMessage(Notification.MessagingStyle.Message)} 7703 * who don't have a Person associated with it will be displayed as if they were sent 7704 * by this user. The user also needs to have a valid name associated with it, which will 7705 * be enforced starting in Android P. 7706 */ MessagingStyle(@onNull Person user)7707 public MessagingStyle(@NonNull Person user) { 7708 mUser = user; 7709 } 7710 7711 /** 7712 * Validate that this style was properly composed. This is called at build time. 7713 * @hide 7714 */ 7715 @Override validate(Context context)7716 public void validate(Context context) { 7717 super.validate(context); 7718 if (context.getApplicationInfo().targetSdkVersion 7719 >= Build.VERSION_CODES.P && (mUser == null || mUser.getName() == null)) { 7720 throw new RuntimeException("User must be valid and have a name."); 7721 } 7722 } 7723 7724 /** 7725 * @return the text that should be displayed in the statusBar when heads upped. 7726 * If {@code null} is returned, the default implementation will be used. 7727 * 7728 * @hide 7729 */ 7730 @Override getHeadsUpStatusBarText()7731 public CharSequence getHeadsUpStatusBarText() { 7732 CharSequence conversationTitle = !TextUtils.isEmpty(super.mBigContentTitle) 7733 ? super.mBigContentTitle 7734 : mConversationTitle; 7735 if (mConversationType == CONVERSATION_TYPE_LEGACY 7736 && !TextUtils.isEmpty(conversationTitle) && !hasOnlyWhiteSpaceSenders()) { 7737 return conversationTitle; 7738 } 7739 return null; 7740 } 7741 7742 /** 7743 * @return the user to be displayed for any replies sent by the user 7744 */ 7745 @NonNull getUser()7746 public Person getUser() { 7747 return mUser; 7748 } 7749 7750 /** 7751 * Returns the name to be displayed for any replies sent by the user 7752 * 7753 * @deprecated use {@link #getUser()} instead 7754 */ getUserDisplayName()7755 public CharSequence getUserDisplayName() { 7756 return mUser.getName(); 7757 } 7758 7759 /** 7760 * Sets the title to be displayed on this conversation. May be set to {@code null}. 7761 * 7762 * <p>Starting in {@link Build.VERSION_CODES#R, this conversation title will be ignored if a 7763 * valid shortcutId is added via {@link Notification.Builder#setShortcutId(String)}. In this 7764 * case, {@link ShortcutInfo#getLongLabel()} (or, if missing, 7765 * {@link ShortcutInfo#getShortLabel()}) will be shown as the conversation title 7766 * instead. 7767 * 7768 * <p>This API's behavior was changed in SDK version {@link Build.VERSION_CODES#P}. If your 7769 * application's target version is less than {@link Build.VERSION_CODES#P}, setting a 7770 * conversation title to a non-null value will make {@link #isGroupConversation()} return 7771 * {@code true} and passing {@code null} will make it return {@code false}. In 7772 * {@link Build.VERSION_CODES#P} and beyond, use {@link #setGroupConversation(boolean)} 7773 * to set group conversation status. 7774 * 7775 * @param conversationTitle Title displayed for this conversation 7776 * @return this object for method chaining 7777 */ setConversationTitle(@ullable CharSequence conversationTitle)7778 public MessagingStyle setConversationTitle(@Nullable CharSequence conversationTitle) { 7779 mConversationTitle = conversationTitle; 7780 return this; 7781 } 7782 7783 /** 7784 * Return the title to be displayed on this conversation. May return {@code null}. 7785 */ 7786 @Nullable getConversationTitle()7787 public CharSequence getConversationTitle() { 7788 return mConversationTitle; 7789 } 7790 7791 /** 7792 * Sets the icon to be displayed on the conversation, derived from the shortcutId. 7793 * 7794 * @hide 7795 */ setShortcutIcon(@ullable Icon conversationIcon)7796 public MessagingStyle setShortcutIcon(@Nullable Icon conversationIcon) { 7797 mShortcutIcon = conversationIcon; 7798 return this; 7799 } 7800 7801 /** 7802 * Return the icon to be displayed on this conversation, derived from the shortcutId. May 7803 * return {@code null}. 7804 * 7805 * @hide 7806 */ 7807 @Nullable getShortcutIcon()7808 public Icon getShortcutIcon() { 7809 return mShortcutIcon; 7810 } 7811 7812 /** 7813 * Sets the conversation type of this MessageStyle notification. 7814 * {@link #CONVERSATION_TYPE_LEGACY} will use the "older" layout from pre-R, 7815 * {@link #CONVERSATION_TYPE_NORMAL} will use the new "conversation" layout, and 7816 * {@link #CONVERSATION_TYPE_IMPORTANT} will add additional "important" treatments. 7817 * 7818 * @hide 7819 */ setConversationType(@onversationType int conversationType)7820 public MessagingStyle setConversationType(@ConversationType int conversationType) { 7821 mConversationType = conversationType; 7822 return this; 7823 } 7824 7825 /** @hide */ 7826 @ConversationType getConversationType()7827 public int getConversationType() { 7828 return mConversationType; 7829 } 7830 7831 /** @hide */ getUnreadMessageCount()7832 public int getUnreadMessageCount() { 7833 return mUnreadMessageCount; 7834 } 7835 7836 /** @hide */ setUnreadMessageCount(int unreadMessageCount)7837 public MessagingStyle setUnreadMessageCount(int unreadMessageCount) { 7838 mUnreadMessageCount = unreadMessageCount; 7839 return this; 7840 } 7841 7842 /** 7843 * Adds a message for display by this notification. Convenience call for a simple 7844 * {@link Message} in {@link #addMessage(Notification.MessagingStyle.Message)}. 7845 * @param text A {@link CharSequence} to be displayed as the message content 7846 * @param timestamp Time at which the message arrived 7847 * @param sender A {@link CharSequence} to be used for displaying the name of the 7848 * sender. Should be <code>null</code> for messages by the current user, in which case 7849 * the platform will insert {@link #getUserDisplayName()}. 7850 * Should be unique amongst all individuals in the conversation, and should be 7851 * consistent during re-posts of the notification. 7852 * 7853 * @see Message#Message(CharSequence, long, CharSequence) 7854 * 7855 * @return this object for method chaining 7856 * 7857 * @deprecated use {@link #addMessage(CharSequence, long, Person)} 7858 */ addMessage(CharSequence text, long timestamp, CharSequence sender)7859 public MessagingStyle addMessage(CharSequence text, long timestamp, CharSequence sender) { 7860 return addMessage(text, timestamp, 7861 sender == null ? null : new Person.Builder().setName(sender).build()); 7862 } 7863 7864 /** 7865 * Adds a message for display by this notification. Convenience call for a simple 7866 * {@link Message} in {@link #addMessage(Notification.MessagingStyle.Message)}. 7867 * @param text A {@link CharSequence} to be displayed as the message content 7868 * @param timestamp Time at which the message arrived 7869 * @param sender The {@link Person} who sent the message. 7870 * Should be <code>null</code> for messages by the current user, in which case 7871 * the platform will insert the user set in {@code MessagingStyle(Person)}. 7872 * 7873 * @see Message#Message(CharSequence, long, CharSequence) 7874 * 7875 * @return this object for method chaining 7876 */ addMessage(@onNull CharSequence text, long timestamp, @Nullable Person sender)7877 public MessagingStyle addMessage(@NonNull CharSequence text, long timestamp, 7878 @Nullable Person sender) { 7879 return addMessage(new Message(text, timestamp, sender)); 7880 } 7881 7882 /** 7883 * Adds a {@link Message} for display in this notification. 7884 * 7885 * <p>The messages should be added in chronologic order, i.e. the oldest first, 7886 * the newest last. 7887 * 7888 * @param message The {@link Message} to be displayed 7889 * @return this object for method chaining 7890 */ addMessage(Message message)7891 public MessagingStyle addMessage(Message message) { 7892 mMessages.add(message); 7893 if (mMessages.size() > MAXIMUM_RETAINED_MESSAGES) { 7894 mMessages.remove(0); 7895 } 7896 return this; 7897 } 7898 7899 /** 7900 * Adds a {@link Message} for historic context in this notification. 7901 * 7902 * <p>Messages should be added as historic if they are not the main subject of the 7903 * notification but may give context to a conversation. The system may choose to present 7904 * them only when relevant, e.g. when replying to a message through a {@link RemoteInput}. 7905 * 7906 * <p>The messages should be added in chronologic order, i.e. the oldest first, 7907 * the newest last. 7908 * 7909 * @param message The historic {@link Message} to be added 7910 * @return this object for method chaining 7911 */ addHistoricMessage(Message message)7912 public MessagingStyle addHistoricMessage(Message message) { 7913 mHistoricMessages.add(message); 7914 if (mHistoricMessages.size() > MAXIMUM_RETAINED_MESSAGES) { 7915 mHistoricMessages.remove(0); 7916 } 7917 return this; 7918 } 7919 7920 /** 7921 * Gets the list of {@code Message} objects that represent the notification 7922 */ getMessages()7923 public List<Message> getMessages() { 7924 return mMessages; 7925 } 7926 7927 /** 7928 * Gets the list of historic {@code Message}s in the notification. 7929 */ getHistoricMessages()7930 public List<Message> getHistoricMessages() { 7931 return mHistoricMessages; 7932 } 7933 7934 /** 7935 * Sets whether this conversation notification represents a group. If the app is targeting 7936 * Android P, this is required if the app wants to display the largeIcon set with 7937 * {@link Notification.Builder#setLargeIcon(Bitmap)}, otherwise it will be hidden. 7938 * 7939 * @param isGroupConversation {@code true} if the conversation represents a group, 7940 * {@code false} otherwise. 7941 * @return this object for method chaining 7942 */ setGroupConversation(boolean isGroupConversation)7943 public MessagingStyle setGroupConversation(boolean isGroupConversation) { 7944 mIsGroupConversation = isGroupConversation; 7945 return this; 7946 } 7947 7948 /** 7949 * Returns {@code true} if this notification represents a group conversation, otherwise 7950 * {@code false}. 7951 * 7952 * <p> If the application that generated this {@link MessagingStyle} targets an SDK version 7953 * less than {@link Build.VERSION_CODES#P}, this method becomes dependent on whether or 7954 * not the conversation title is set; returning {@code true} if the conversation title is 7955 * a non-null value, or {@code false} otherwise. From {@link Build.VERSION_CODES#P} forward, 7956 * this method returns what's set by {@link #setGroupConversation(boolean)} allowing for 7957 * named, non-group conversations. 7958 * 7959 * @see #setConversationTitle(CharSequence) 7960 */ isGroupConversation()7961 public boolean isGroupConversation() { 7962 // When target SDK version is < P, a non-null conversation title dictates if this is 7963 // as group conversation. 7964 if (mBuilder != null 7965 && mBuilder.mContext.getApplicationInfo().targetSdkVersion 7966 < Build.VERSION_CODES.P) { 7967 return mConversationTitle != null; 7968 } 7969 7970 return mIsGroupConversation; 7971 } 7972 7973 /** 7974 * @hide 7975 */ 7976 @Override addExtras(Bundle extras)7977 public void addExtras(Bundle extras) { 7978 super.addExtras(extras); 7979 if (mUser != null) { 7980 // For legacy usages 7981 extras.putCharSequence(EXTRA_SELF_DISPLAY_NAME, mUser.getName()); 7982 extras.putParcelable(EXTRA_MESSAGING_PERSON, mUser); 7983 } 7984 if (mConversationTitle != null) { 7985 extras.putCharSequence(EXTRA_CONVERSATION_TITLE, mConversationTitle); 7986 } 7987 if (!mMessages.isEmpty()) { extras.putParcelableArray(EXTRA_MESSAGES, 7988 Message.getBundleArrayForMessages(mMessages)); 7989 } 7990 if (!mHistoricMessages.isEmpty()) { extras.putParcelableArray(EXTRA_HISTORIC_MESSAGES, 7991 Message.getBundleArrayForMessages(mHistoricMessages)); 7992 } 7993 if (mShortcutIcon != null) { 7994 extras.putParcelable(EXTRA_CONVERSATION_ICON, mShortcutIcon); 7995 } 7996 extras.putInt(EXTRA_CONVERSATION_UNREAD_MESSAGE_COUNT, mUnreadMessageCount); 7997 7998 fixTitleAndTextExtras(extras); 7999 extras.putBoolean(EXTRA_IS_GROUP_CONVERSATION, mIsGroupConversation); 8000 } 8001 fixTitleAndTextExtras(Bundle extras)8002 private void fixTitleAndTextExtras(Bundle extras) { 8003 Message m = findLatestIncomingMessage(); 8004 CharSequence text = (m == null) ? null : m.mText; 8005 CharSequence sender = m == null ? null 8006 : m.mSender == null || TextUtils.isEmpty(m.mSender.getName()) 8007 ? mUser.getName() : m.mSender.getName(); 8008 CharSequence title; 8009 if (!TextUtils.isEmpty(mConversationTitle)) { 8010 if (!TextUtils.isEmpty(sender)) { 8011 BidiFormatter bidi = BidiFormatter.getInstance(); 8012 title = mBuilder.mContext.getString( 8013 com.android.internal.R.string.notification_messaging_title_template, 8014 bidi.unicodeWrap(mConversationTitle), bidi.unicodeWrap(sender)); 8015 } else { 8016 title = mConversationTitle; 8017 } 8018 } else { 8019 title = sender; 8020 } 8021 8022 if (title != null) { 8023 extras.putCharSequence(EXTRA_TITLE, title); 8024 } 8025 if (text != null) { 8026 extras.putCharSequence(EXTRA_TEXT, text); 8027 } 8028 } 8029 8030 /** 8031 * @hide 8032 */ 8033 @Override restoreFromExtras(Bundle extras)8034 protected void restoreFromExtras(Bundle extras) { 8035 super.restoreFromExtras(extras); 8036 8037 mUser = extras.getParcelable(EXTRA_MESSAGING_PERSON); 8038 if (mUser == null) { 8039 CharSequence displayName = extras.getCharSequence(EXTRA_SELF_DISPLAY_NAME); 8040 mUser = new Person.Builder().setName(displayName).build(); 8041 } 8042 mConversationTitle = extras.getCharSequence(EXTRA_CONVERSATION_TITLE); 8043 Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES); 8044 mMessages = Message.getMessagesFromBundleArray(messages); 8045 Parcelable[] histMessages = extras.getParcelableArray(EXTRA_HISTORIC_MESSAGES); 8046 mHistoricMessages = Message.getMessagesFromBundleArray(histMessages); 8047 mIsGroupConversation = extras.getBoolean(EXTRA_IS_GROUP_CONVERSATION); 8048 mUnreadMessageCount = extras.getInt(EXTRA_CONVERSATION_UNREAD_MESSAGE_COUNT); 8049 mShortcutIcon = extras.getParcelable(EXTRA_CONVERSATION_ICON); 8050 } 8051 8052 /** 8053 * @hide 8054 */ 8055 @Override makeContentView(boolean increasedHeight)8056 public RemoteViews makeContentView(boolean increasedHeight) { 8057 // All messaging templates contain the actions 8058 ArrayList<Action> originalActions = mBuilder.mActions; 8059 try { 8060 mBuilder.mActions = new ArrayList<>(); 8061 return makeMessagingView(StandardTemplateParams.VIEW_TYPE_NORMAL); 8062 } finally { 8063 mBuilder.mActions = originalActions; 8064 } 8065 } 8066 8067 /** 8068 * @hide 8069 * Spans are ignored when comparing text for visual difference. 8070 */ 8071 @Override areNotificationsVisiblyDifferent(Style other)8072 public boolean areNotificationsVisiblyDifferent(Style other) { 8073 if (other == null || getClass() != other.getClass()) { 8074 return true; 8075 } 8076 MessagingStyle newS = (MessagingStyle) other; 8077 List<MessagingStyle.Message> oldMs = getMessages(); 8078 List<MessagingStyle.Message> newMs = newS.getMessages(); 8079 8080 if (oldMs == null || newMs == null) { 8081 newMs = new ArrayList<>(); 8082 } 8083 8084 int n = oldMs.size(); 8085 if (n != newMs.size()) { 8086 return true; 8087 } 8088 for (int i = 0; i < n; i++) { 8089 MessagingStyle.Message oldM = oldMs.get(i); 8090 MessagingStyle.Message newM = newMs.get(i); 8091 if (!Objects.equals( 8092 String.valueOf(oldM.getText()), 8093 String.valueOf(newM.getText()))) { 8094 return true; 8095 } 8096 if (!Objects.equals(oldM.getDataUri(), newM.getDataUri())) { 8097 return true; 8098 } 8099 String oldSender = String.valueOf(oldM.getSenderPerson() == null 8100 ? oldM.getSender() 8101 : oldM.getSenderPerson().getName()); 8102 String newSender = String.valueOf(newM.getSenderPerson() == null 8103 ? newM.getSender() 8104 : newM.getSenderPerson().getName()); 8105 if (!Objects.equals(oldSender, newSender)) { 8106 return true; 8107 } 8108 8109 String oldKey = oldM.getSenderPerson() == null 8110 ? null : oldM.getSenderPerson().getKey(); 8111 String newKey = newM.getSenderPerson() == null 8112 ? null : newM.getSenderPerson().getKey(); 8113 if (!Objects.equals(oldKey, newKey)) { 8114 return true; 8115 } 8116 // Other fields (like timestamp) intentionally excluded 8117 } 8118 return false; 8119 } 8120 findLatestIncomingMessage()8121 private Message findLatestIncomingMessage() { 8122 return findLatestIncomingMessage(mMessages); 8123 } 8124 8125 /** 8126 * @hide 8127 */ 8128 @Nullable findLatestIncomingMessage( List<Message> messages)8129 public static Message findLatestIncomingMessage( 8130 List<Message> messages) { 8131 for (int i = messages.size() - 1; i >= 0; i--) { 8132 Message m = messages.get(i); 8133 // Incoming messages have a non-empty sender. 8134 if (m.mSender != null && !TextUtils.isEmpty(m.mSender.getName())) { 8135 return m; 8136 } 8137 } 8138 if (!messages.isEmpty()) { 8139 // No incoming messages, fall back to outgoing message 8140 return messages.get(messages.size() - 1); 8141 } 8142 return null; 8143 } 8144 8145 /** 8146 * @hide 8147 */ 8148 @Override makeBigContentView()8149 public RemoteViews makeBigContentView() { 8150 return makeMessagingView(StandardTemplateParams.VIEW_TYPE_BIG); 8151 } 8152 8153 /** 8154 * Create a messaging layout. 8155 * 8156 * @param viewType one of StandardTemplateParams.VIEW_TYPE_NORMAL, VIEW_TYPE_BIG, 8157 * VIEW_TYPE_HEADS_UP 8158 * @return the created remoteView. 8159 */ 8160 @NonNull makeMessagingView(int viewType)8161 private RemoteViews makeMessagingView(int viewType) { 8162 boolean isCollapsed = viewType != StandardTemplateParams.VIEW_TYPE_BIG; 8163 boolean hideRightIcons = viewType != StandardTemplateParams.VIEW_TYPE_NORMAL; 8164 boolean isConversationLayout = mConversationType != CONVERSATION_TYPE_LEGACY; 8165 boolean isImportantConversation = mConversationType == CONVERSATION_TYPE_IMPORTANT; 8166 boolean isHeaderless = !isConversationLayout && isCollapsed; 8167 8168 CharSequence conversationTitle = !TextUtils.isEmpty(super.mBigContentTitle) 8169 ? super.mBigContentTitle 8170 : mConversationTitle; 8171 boolean atLeastP = mBuilder.mContext.getApplicationInfo().targetSdkVersion 8172 >= Build.VERSION_CODES.P; 8173 boolean isOneToOne; 8174 CharSequence nameReplacement = null; 8175 if (!atLeastP) { 8176 isOneToOne = TextUtils.isEmpty(conversationTitle); 8177 if (hasOnlyWhiteSpaceSenders()) { 8178 isOneToOne = true; 8179 nameReplacement = conversationTitle; 8180 conversationTitle = null; 8181 } 8182 } else { 8183 isOneToOne = !isGroupConversation(); 8184 } 8185 if (isHeaderless && isOneToOne && TextUtils.isEmpty(conversationTitle)) { 8186 conversationTitle = getOtherPersonName(); 8187 } 8188 8189 Icon largeIcon = mBuilder.mN.mLargeIcon; 8190 TemplateBindResult bindResult = new TemplateBindResult(); 8191 StandardTemplateParams p = mBuilder.mParams.reset() 8192 .viewType(viewType) 8193 .highlightExpander(isConversationLayout) 8194 .hideProgress(true) 8195 .title(isHeaderless ? conversationTitle : null) 8196 .text(null) 8197 .hideLeftIcon(isOneToOne) 8198 .hideRightIcon(hideRightIcons || isOneToOne) 8199 .headerTextSecondary(isHeaderless ? null : conversationTitle); 8200 RemoteViews contentView = mBuilder.applyStandardTemplateWithActions( 8201 isConversationLayout 8202 ? mBuilder.getConversationLayoutResource() 8203 : isCollapsed 8204 ? mBuilder.getMessagingLayoutResource() 8205 : mBuilder.getBigMessagingLayoutResource(), 8206 p, 8207 bindResult); 8208 if (isConversationLayout) { 8209 mBuilder.setTextViewColorPrimary(contentView, R.id.conversation_text, p); 8210 mBuilder.setTextViewColorSecondary(contentView, R.id.app_name_divider, p); 8211 } 8212 8213 addExtras(mBuilder.mN.extras); 8214 contentView.setInt(R.id.status_bar_latest_event_content, "setLayoutColor", 8215 mBuilder.getSmallIconColor(p)); 8216 contentView.setInt(R.id.status_bar_latest_event_content, "setSenderTextColor", 8217 mBuilder.getPrimaryTextColor(p)); 8218 contentView.setInt(R.id.status_bar_latest_event_content, "setMessageTextColor", 8219 mBuilder.getSecondaryTextColor(p)); 8220 contentView.setInt(R.id.status_bar_latest_event_content, 8221 "setNotificationBackgroundColor", 8222 mBuilder.getBackgroundColor(p)); 8223 contentView.setBoolean(R.id.status_bar_latest_event_content, "setIsCollapsed", 8224 isCollapsed); 8225 contentView.setIcon(R.id.status_bar_latest_event_content, "setAvatarReplacement", 8226 mBuilder.mN.mLargeIcon); 8227 contentView.setCharSequence(R.id.status_bar_latest_event_content, "setNameReplacement", 8228 nameReplacement); 8229 contentView.setBoolean(R.id.status_bar_latest_event_content, "setIsOneToOne", 8230 isOneToOne); 8231 contentView.setCharSequence(R.id.status_bar_latest_event_content, 8232 "setConversationTitle", conversationTitle); 8233 if (isConversationLayout) { 8234 contentView.setIcon(R.id.status_bar_latest_event_content, 8235 "setShortcutIcon", mShortcutIcon); 8236 contentView.setBoolean(R.id.status_bar_latest_event_content, 8237 "setIsImportantConversation", isImportantConversation); 8238 } 8239 if (isHeaderless) { 8240 // Collapsed legacy messaging style has a 1-line limit. 8241 contentView.setInt(R.id.notification_messaging, "setMaxDisplayedLines", 1); 8242 } 8243 contentView.setIcon(R.id.status_bar_latest_event_content, "setLargeIcon", 8244 largeIcon); 8245 contentView.setBundle(R.id.status_bar_latest_event_content, "setData", 8246 mBuilder.mN.extras); 8247 return contentView; 8248 } 8249 getKey(Person person)8250 private CharSequence getKey(Person person) { 8251 return person == null ? null 8252 : person.getKey() == null ? person.getName() : person.getKey(); 8253 } 8254 getOtherPersonName()8255 private CharSequence getOtherPersonName() { 8256 CharSequence userKey = getKey(mUser); 8257 for (int i = mMessages.size() - 1; i >= 0; i--) { 8258 Person sender = mMessages.get(i).getSenderPerson(); 8259 if (sender != null && !TextUtils.equals(userKey, getKey(sender))) { 8260 return sender.getName(); 8261 } 8262 } 8263 return null; 8264 } 8265 hasOnlyWhiteSpaceSenders()8266 private boolean hasOnlyWhiteSpaceSenders() { 8267 for (int i = 0; i < mMessages.size(); i++) { 8268 Message m = mMessages.get(i); 8269 Person sender = m.getSenderPerson(); 8270 if (sender != null && !isWhiteSpace(sender.getName())) { 8271 return false; 8272 } 8273 } 8274 return true; 8275 } 8276 isWhiteSpace(CharSequence sender)8277 private boolean isWhiteSpace(CharSequence sender) { 8278 if (TextUtils.isEmpty(sender)) { 8279 return true; 8280 } 8281 if (sender.toString().matches("^\\s*$")) { 8282 return true; 8283 } 8284 // Let's check if we only have 0 whitespace chars. Some apps did this as a workaround 8285 // For the presentation that we had. 8286 for (int i = 0; i < sender.length(); i++) { 8287 char c = sender.charAt(i); 8288 if (c != '\u200B') { 8289 return false; 8290 } 8291 } 8292 return true; 8293 } 8294 8295 /** 8296 * @hide 8297 */ 8298 @Override makeHeadsUpContentView(boolean increasedHeight)8299 public RemoteViews makeHeadsUpContentView(boolean increasedHeight) { 8300 return makeMessagingView(StandardTemplateParams.VIEW_TYPE_HEADS_UP); 8301 } 8302 8303 public static final class Message { 8304 /** @hide */ 8305 public static final String KEY_TEXT = "text"; 8306 static final String KEY_TIMESTAMP = "time"; 8307 static final String KEY_SENDER = "sender"; 8308 static final String KEY_SENDER_PERSON = "sender_person"; 8309 static final String KEY_DATA_MIME_TYPE = "type"; 8310 static final String KEY_DATA_URI= "uri"; 8311 static final String KEY_EXTRAS_BUNDLE = "extras"; 8312 static final String KEY_REMOTE_INPUT_HISTORY = "remote_input_history"; 8313 8314 private final CharSequence mText; 8315 private final long mTimestamp; 8316 @Nullable 8317 private final Person mSender; 8318 /** True if this message was generated from the extra 8319 * {@link Notification#EXTRA_REMOTE_INPUT_HISTORY_ITEMS} 8320 */ 8321 private final boolean mRemoteInputHistory; 8322 8323 private Bundle mExtras = new Bundle(); 8324 private String mDataMimeType; 8325 private Uri mDataUri; 8326 8327 /** 8328 * Constructor 8329 * @param text A {@link CharSequence} to be displayed as the message content 8330 * @param timestamp Time at which the message arrived 8331 * @param sender A {@link CharSequence} to be used for displaying the name of the 8332 * sender. Should be <code>null</code> for messages by the current user, in which case 8333 * the platform will insert {@link MessagingStyle#getUserDisplayName()}. 8334 * Should be unique amongst all individuals in the conversation, and should be 8335 * consistent during re-posts of the notification. 8336 * 8337 * @deprecated use {@code Message(CharSequence, long, Person)} 8338 */ Message(CharSequence text, long timestamp, CharSequence sender)8339 public Message(CharSequence text, long timestamp, CharSequence sender){ 8340 this(text, timestamp, sender == null ? null 8341 : new Person.Builder().setName(sender).build()); 8342 } 8343 8344 /** 8345 * Constructor 8346 * @param text A {@link CharSequence} to be displayed as the message content 8347 * @param timestamp Time at which the message arrived 8348 * @param sender The {@link Person} who sent the message. 8349 * Should be <code>null</code> for messages by the current user, in which case 8350 * the platform will insert the user set in {@code MessagingStyle(Person)}. 8351 * <p> 8352 * The person provided should contain an Icon, set with 8353 * {@link Person.Builder#setIcon(Icon)} and also have a name provided 8354 * with {@link Person.Builder#setName(CharSequence)}. If multiple users have the same 8355 * name, consider providing a key with {@link Person.Builder#setKey(String)} in order 8356 * to differentiate between the different users. 8357 * </p> 8358 */ Message(@onNull CharSequence text, long timestamp, @Nullable Person sender)8359 public Message(@NonNull CharSequence text, long timestamp, @Nullable Person sender) { 8360 this(text, timestamp, sender, false /* remoteHistory */); 8361 } 8362 8363 /** 8364 * Constructor 8365 * @param text A {@link CharSequence} to be displayed as the message content 8366 * @param timestamp Time at which the message arrived 8367 * @param sender The {@link Person} who sent the message. 8368 * Should be <code>null</code> for messages by the current user, in which case 8369 * the platform will insert the user set in {@code MessagingStyle(Person)}. 8370 * @param remoteInputHistory True if the messages was generated from the extra 8371 * {@link Notification#EXTRA_REMOTE_INPUT_HISTORY_ITEMS}. 8372 * <p> 8373 * The person provided should contain an Icon, set with 8374 * {@link Person.Builder#setIcon(Icon)} and also have a name provided 8375 * with {@link Person.Builder#setName(CharSequence)}. If multiple users have the same 8376 * name, consider providing a key with {@link Person.Builder#setKey(String)} in order 8377 * to differentiate between the different users. 8378 * </p> 8379 * @hide 8380 */ Message(@onNull CharSequence text, long timestamp, @Nullable Person sender, boolean remoteInputHistory)8381 public Message(@NonNull CharSequence text, long timestamp, @Nullable Person sender, 8382 boolean remoteInputHistory) { 8383 mText = safeCharSequence(text); 8384 mTimestamp = timestamp; 8385 mSender = sender; 8386 mRemoteInputHistory = remoteInputHistory; 8387 } 8388 8389 /** 8390 * Sets a binary blob of data and an associated MIME type for a message. In the case 8391 * where the platform doesn't support the MIME type, the original text provided in the 8392 * constructor will be used. 8393 * @param dataMimeType The MIME type of the content. See 8394 * <a href="{@docRoot}notifications/messaging.html"> for the list of supported MIME 8395 * types on Android and Android Wear. 8396 * @param dataUri The uri containing the content whose type is given by the MIME type. 8397 * <p class="note"> 8398 * <ol> 8399 * <li>Notification Listeners including the System UI need permission to access the 8400 * data the Uri points to. The recommended ways to do this are:</li> 8401 * <li>Store the data in your own ContentProvider, making sure that other apps have 8402 * the correct permission to access your provider. The preferred mechanism for 8403 * providing access is to use per-URI permissions which are temporary and only 8404 * grant access to the receiving application. An easy way to create a 8405 * ContentProvider like this is to use the FileProvider helper class.</li> 8406 * <li>Use the system MediaStore. The MediaStore is primarily aimed at video, audio 8407 * and image MIME types, however beginning with Android 3.0 (API level 11) it can 8408 * also store non-media types (see MediaStore.Files for more info). Files can be 8409 * inserted into the MediaStore using scanFile() after which a content:// style 8410 * Uri suitable for sharing is passed to the provided onScanCompleted() callback. 8411 * Note that once added to the system MediaStore the content is accessible to any 8412 * app on the device.</li> 8413 * </ol> 8414 * @return this object for method chaining 8415 */ setData(String dataMimeType, Uri dataUri)8416 public Message setData(String dataMimeType, Uri dataUri) { 8417 mDataMimeType = dataMimeType; 8418 mDataUri = dataUri; 8419 return this; 8420 } 8421 8422 /** 8423 * Get the text to be used for this message, or the fallback text if a type and content 8424 * Uri have been set 8425 */ getText()8426 public CharSequence getText() { 8427 return mText; 8428 } 8429 8430 /** 8431 * Get the time at which this message arrived 8432 */ getTimestamp()8433 public long getTimestamp() { 8434 return mTimestamp; 8435 } 8436 8437 /** 8438 * Get the extras Bundle for this message. 8439 */ getExtras()8440 public Bundle getExtras() { 8441 return mExtras; 8442 } 8443 8444 /** 8445 * Get the text used to display the contact's name in the messaging experience 8446 * 8447 * @deprecated use {@link #getSenderPerson()} 8448 */ getSender()8449 public CharSequence getSender() { 8450 return mSender == null ? null : mSender.getName(); 8451 } 8452 8453 /** 8454 * Get the sender associated with this message. 8455 */ 8456 @Nullable getSenderPerson()8457 public Person getSenderPerson() { 8458 return mSender; 8459 } 8460 8461 /** 8462 * Get the MIME type of the data pointed to by the Uri 8463 */ getDataMimeType()8464 public String getDataMimeType() { 8465 return mDataMimeType; 8466 } 8467 8468 /** 8469 * Get the Uri pointing to the content of the message. Can be null, in which case 8470 * {@see #getText()} is used. 8471 */ getDataUri()8472 public Uri getDataUri() { 8473 return mDataUri; 8474 } 8475 8476 /** 8477 * @return True if the message was generated from 8478 * {@link Notification#EXTRA_REMOTE_INPUT_HISTORY_ITEMS}. 8479 * @hide 8480 */ isRemoteInputHistory()8481 public boolean isRemoteInputHistory() { 8482 return mRemoteInputHistory; 8483 } 8484 8485 /** 8486 * @hide 8487 */ 8488 @VisibleForTesting toBundle()8489 public Bundle toBundle() { 8490 Bundle bundle = new Bundle(); 8491 if (mText != null) { 8492 bundle.putCharSequence(KEY_TEXT, mText); 8493 } 8494 bundle.putLong(KEY_TIMESTAMP, mTimestamp); 8495 if (mSender != null) { 8496 // Legacy listeners need this 8497 bundle.putCharSequence(KEY_SENDER, safeCharSequence(mSender.getName())); 8498 bundle.putParcelable(KEY_SENDER_PERSON, mSender); 8499 } 8500 if (mDataMimeType != null) { 8501 bundle.putString(KEY_DATA_MIME_TYPE, mDataMimeType); 8502 } 8503 if (mDataUri != null) { 8504 bundle.putParcelable(KEY_DATA_URI, mDataUri); 8505 } 8506 if (mExtras != null) { 8507 bundle.putBundle(KEY_EXTRAS_BUNDLE, mExtras); 8508 } 8509 if (mRemoteInputHistory) { 8510 bundle.putBoolean(KEY_REMOTE_INPUT_HISTORY, mRemoteInputHistory); 8511 } 8512 return bundle; 8513 } 8514 getBundleArrayForMessages(List<Message> messages)8515 static Bundle[] getBundleArrayForMessages(List<Message> messages) { 8516 Bundle[] bundles = new Bundle[messages.size()]; 8517 final int N = messages.size(); 8518 for (int i = 0; i < N; i++) { 8519 bundles[i] = messages.get(i).toBundle(); 8520 } 8521 return bundles; 8522 } 8523 8524 /** 8525 * Returns a list of messages read from the given bundle list, e.g. 8526 * {@link #EXTRA_MESSAGES} or {@link #EXTRA_HISTORIC_MESSAGES}. 8527 */ 8528 @NonNull getMessagesFromBundleArray(@ullable Parcelable[] bundles)8529 public static List<Message> getMessagesFromBundleArray(@Nullable Parcelable[] bundles) { 8530 if (bundles == null) { 8531 return new ArrayList<>(); 8532 } 8533 List<Message> messages = new ArrayList<>(bundles.length); 8534 for (int i = 0; i < bundles.length; i++) { 8535 if (bundles[i] instanceof Bundle) { 8536 Message message = getMessageFromBundle((Bundle)bundles[i]); 8537 if (message != null) { 8538 messages.add(message); 8539 } 8540 } 8541 } 8542 return messages; 8543 } 8544 8545 /** 8546 * Returns the message that is stored in the bundle (e.g. one of the values in the lists 8547 * in {@link #EXTRA_MESSAGES} or {@link #EXTRA_HISTORIC_MESSAGES}) or null if the 8548 * message couldn't be resolved. 8549 * @hide 8550 */ 8551 @Nullable getMessageFromBundle(@onNull Bundle bundle)8552 public static Message getMessageFromBundle(@NonNull Bundle bundle) { 8553 try { 8554 if (!bundle.containsKey(KEY_TEXT) || !bundle.containsKey(KEY_TIMESTAMP)) { 8555 return null; 8556 } else { 8557 8558 Person senderPerson = bundle.getParcelable(KEY_SENDER_PERSON); 8559 if (senderPerson == null) { 8560 // Legacy apps that use compat don't actually provide the sender objects 8561 // We need to fix the compat version to provide people / use 8562 // the native api instead 8563 CharSequence senderName = bundle.getCharSequence(KEY_SENDER); 8564 if (senderName != null) { 8565 senderPerson = new Person.Builder().setName(senderName).build(); 8566 } 8567 } 8568 Message message = new Message(bundle.getCharSequence(KEY_TEXT), 8569 bundle.getLong(KEY_TIMESTAMP), 8570 senderPerson, 8571 bundle.getBoolean(KEY_REMOTE_INPUT_HISTORY, false)); 8572 if (bundle.containsKey(KEY_DATA_MIME_TYPE) && 8573 bundle.containsKey(KEY_DATA_URI)) { 8574 message.setData(bundle.getString(KEY_DATA_MIME_TYPE), 8575 (Uri) bundle.getParcelable(KEY_DATA_URI)); 8576 } 8577 if (bundle.containsKey(KEY_EXTRAS_BUNDLE)) { 8578 message.getExtras().putAll(bundle.getBundle(KEY_EXTRAS_BUNDLE)); 8579 } 8580 return message; 8581 } 8582 } catch (ClassCastException e) { 8583 return null; 8584 } 8585 } 8586 } 8587 } 8588 8589 /** 8590 * Helper class for generating large-format notifications that include a list of (up to 5) strings. 8591 * 8592 * Here's how you'd set the <code>InboxStyle</code> on a notification: 8593 * <pre class="prettyprint"> 8594 * Notification notif = new Notification.Builder(mContext) 8595 * .setContentTitle("5 New mails from " + sender.toString()) 8596 * .setContentText(subject) 8597 * .setSmallIcon(R.drawable.new_mail) 8598 * .setLargeIcon(aBitmap) 8599 * .setStyle(new Notification.InboxStyle() 8600 * .addLine(str1) 8601 * .addLine(str2) 8602 * .setContentTitle("") 8603 * .setSummaryText("+3 more")) 8604 * .build(); 8605 * </pre> 8606 * 8607 * @see Notification#bigContentView 8608 */ 8609 public static class InboxStyle extends Style { 8610 8611 /** 8612 * The number of lines of remote input history allowed until we start reducing lines. 8613 */ 8614 private static final int NUMBER_OF_HISTORY_ALLOWED_UNTIL_REDUCTION = 1; 8615 private ArrayList<CharSequence> mTexts = new ArrayList<CharSequence>(5); 8616 InboxStyle()8617 public InboxStyle() { 8618 } 8619 8620 /** 8621 * @deprecated use {@code InboxStyle()}. 8622 */ 8623 @Deprecated InboxStyle(Builder builder)8624 public InboxStyle(Builder builder) { 8625 setBuilder(builder); 8626 } 8627 8628 /** 8629 * Overrides ContentTitle in the big form of the template. 8630 * This defaults to the value passed to setContentTitle(). 8631 */ setBigContentTitle(CharSequence title)8632 public InboxStyle setBigContentTitle(CharSequence title) { 8633 internalSetBigContentTitle(safeCharSequence(title)); 8634 return this; 8635 } 8636 8637 /** 8638 * Set the first line of text after the detail section in the big form of the template. 8639 */ setSummaryText(CharSequence cs)8640 public InboxStyle setSummaryText(CharSequence cs) { 8641 internalSetSummaryText(safeCharSequence(cs)); 8642 return this; 8643 } 8644 8645 /** 8646 * Append a line to the digest section of the Inbox notification. 8647 */ addLine(CharSequence cs)8648 public InboxStyle addLine(CharSequence cs) { 8649 mTexts.add(safeCharSequence(cs)); 8650 return this; 8651 } 8652 8653 /** 8654 * @hide 8655 */ getLines()8656 public ArrayList<CharSequence> getLines() { 8657 return mTexts; 8658 } 8659 8660 /** 8661 * @hide 8662 */ addExtras(Bundle extras)8663 public void addExtras(Bundle extras) { 8664 super.addExtras(extras); 8665 8666 CharSequence[] a = new CharSequence[mTexts.size()]; 8667 extras.putCharSequenceArray(EXTRA_TEXT_LINES, mTexts.toArray(a)); 8668 } 8669 8670 /** 8671 * @hide 8672 */ 8673 @Override restoreFromExtras(Bundle extras)8674 protected void restoreFromExtras(Bundle extras) { 8675 super.restoreFromExtras(extras); 8676 8677 mTexts.clear(); 8678 if (extras.containsKey(EXTRA_TEXT_LINES)) { 8679 Collections.addAll(mTexts, extras.getCharSequenceArray(EXTRA_TEXT_LINES)); 8680 } 8681 } 8682 8683 /** 8684 * @hide 8685 */ makeBigContentView()8686 public RemoteViews makeBigContentView() { 8687 StandardTemplateParams p = mBuilder.mParams.reset() 8688 .viewType(StandardTemplateParams.VIEW_TYPE_BIG) 8689 .fillTextsFrom(mBuilder).text(null); 8690 TemplateBindResult result = new TemplateBindResult(); 8691 RemoteViews contentView = getStandardView(mBuilder.getInboxLayoutResource(), p, result); 8692 8693 int[] rowIds = {R.id.inbox_text0, R.id.inbox_text1, R.id.inbox_text2, R.id.inbox_text3, 8694 R.id.inbox_text4, R.id.inbox_text5, R.id.inbox_text6}; 8695 8696 // Make sure all rows are gone in case we reuse a view. 8697 for (int rowId : rowIds) { 8698 contentView.setViewVisibility(rowId, View.GONE); 8699 } 8700 8701 int i=0; 8702 int topPadding = mBuilder.mContext.getResources().getDimensionPixelSize( 8703 R.dimen.notification_inbox_item_top_padding); 8704 boolean first = true; 8705 int onlyViewId = 0; 8706 int maxRows = rowIds.length; 8707 if (mBuilder.mActions.size() > 0) { 8708 maxRows--; 8709 } 8710 RemoteInputHistoryItem[] remoteInputHistory = getParcelableArrayFromBundle( 8711 mBuilder.mN.extras, EXTRA_REMOTE_INPUT_HISTORY_ITEMS, 8712 RemoteInputHistoryItem.class); 8713 if (remoteInputHistory != null 8714 && remoteInputHistory.length > NUMBER_OF_HISTORY_ALLOWED_UNTIL_REDUCTION) { 8715 // Let's remove some messages to make room for the remote input history. 8716 // 1 is always able to fit, but let's remove them if they are 2 or 3 8717 int numRemoteInputs = Math.min(remoteInputHistory.length, 8718 MAX_REMOTE_INPUT_HISTORY_LINES); 8719 int totalNumRows = mTexts.size() + numRemoteInputs 8720 - NUMBER_OF_HISTORY_ALLOWED_UNTIL_REDUCTION; 8721 if (totalNumRows > maxRows) { 8722 int overflow = totalNumRows - maxRows; 8723 if (mTexts.size() > maxRows) { 8724 // Heuristic: if the Texts don't fit anyway, we'll rather drop the last 8725 // few messages, even with the remote input 8726 maxRows -= overflow; 8727 } else { 8728 // otherwise we drop the first messages 8729 i = overflow; 8730 } 8731 } 8732 } 8733 while (i < mTexts.size() && i < maxRows) { 8734 CharSequence str = mTexts.get(i); 8735 if (!TextUtils.isEmpty(str)) { 8736 contentView.setViewVisibility(rowIds[i], View.VISIBLE); 8737 contentView.setTextViewText(rowIds[i], 8738 mBuilder.processTextSpans(mBuilder.processLegacyText(str))); 8739 mBuilder.setTextViewColorSecondary(contentView, rowIds[i], p); 8740 contentView.setViewPadding(rowIds[i], 0, topPadding, 0, 0); 8741 if (first) { 8742 onlyViewId = rowIds[i]; 8743 } else { 8744 onlyViewId = 0; 8745 } 8746 first = false; 8747 } 8748 i++; 8749 } 8750 if (onlyViewId != 0) { 8751 // We only have 1 entry, lets make it look like the normal Text of a Bigtext 8752 topPadding = mBuilder.mContext.getResources().getDimensionPixelSize( 8753 R.dimen.notification_text_margin_top); 8754 contentView.setViewPadding(onlyViewId, 0, topPadding, 0, 0); 8755 } 8756 8757 return contentView; 8758 } 8759 8760 /** 8761 * @hide 8762 */ 8763 @Override areNotificationsVisiblyDifferent(Style other)8764 public boolean areNotificationsVisiblyDifferent(Style other) { 8765 if (other == null || getClass() != other.getClass()) { 8766 return true; 8767 } 8768 InboxStyle newS = (InboxStyle) other; 8769 8770 final ArrayList<CharSequence> myLines = getLines(); 8771 final ArrayList<CharSequence> newLines = newS.getLines(); 8772 final int n = myLines.size(); 8773 if (n != newLines.size()) { 8774 return true; 8775 } 8776 8777 for (int i = 0; i < n; i++) { 8778 if (!Objects.equals( 8779 String.valueOf(myLines.get(i)), 8780 String.valueOf(newLines.get(i)))) { 8781 return true; 8782 } 8783 } 8784 return false; 8785 } 8786 } 8787 8788 /** 8789 * Notification style for media playback notifications. 8790 * 8791 * In the expanded form, {@link Notification#bigContentView}, up to 5 8792 * {@link Notification.Action}s specified with 8793 * {@link Notification.Builder#addAction(Action) addAction} will be 8794 * shown as icon-only pushbuttons, suitable for transport controls. The Bitmap given to 8795 * {@link Notification.Builder#setLargeIcon(android.graphics.Bitmap) setLargeIcon()} will be 8796 * treated as album artwork. 8797 * <p> 8798 * Unlike the other styles provided here, MediaStyle can also modify the standard-size 8799 * {@link Notification#contentView}; by providing action indices to 8800 * {@link #setShowActionsInCompactView(int...)} you can promote up to 3 actions to be displayed 8801 * in the standard view alongside the usual content. 8802 * <p> 8803 * Notifications created with MediaStyle will have their category set to 8804 * {@link Notification#CATEGORY_TRANSPORT CATEGORY_TRANSPORT} unless you set a different 8805 * category using {@link Notification.Builder#setCategory(String) setCategory()}. 8806 * <p> 8807 * Finally, if you attach a {@link android.media.session.MediaSession.Token} using 8808 * {@link android.app.Notification.MediaStyle#setMediaSession(MediaSession.Token)}, 8809 * the System UI can identify this as a notification representing an active media session 8810 * and respond accordingly (by showing album artwork in the lockscreen, for example). 8811 * 8812 * <p> 8813 * Starting at {@link android.os.Build.VERSION_CODES#O Android O} any notification that has a 8814 * media session attached with {@link #setMediaSession(MediaSession.Token)} will be colorized. 8815 * You can opt-out of this behavior by using {@link Notification.Builder#setColorized(boolean)}. 8816 * <p> 8817 * 8818 * To use this style with your Notification, feed it to 8819 * {@link Notification.Builder#setStyle(android.app.Notification.Style)} like so: 8820 * <pre class="prettyprint"> 8821 * Notification noti = new Notification.Builder() 8822 * .setSmallIcon(R.drawable.ic_stat_player) 8823 * .setContentTitle("Track title") 8824 * .setContentText("Artist - Album") 8825 * .setLargeIcon(albumArtBitmap)) 8826 * .setStyle(<b>new Notification.MediaStyle()</b> 8827 * .setMediaSession(mySession)) 8828 * .build(); 8829 * </pre> 8830 * 8831 * @see Notification#bigContentView 8832 * @see Notification.Builder#setColorized(boolean) 8833 */ 8834 public static class MediaStyle extends Style { 8835 // Changing max media buttons requires also changing templates 8836 // (notification_template_material_media and notification_template_material_big_media). 8837 static final int MAX_MEDIA_BUTTONS_IN_COMPACT = 3; 8838 static final int MAX_MEDIA_BUTTONS = 5; 8839 @IdRes private static final int[] MEDIA_BUTTON_IDS = { 8840 R.id.action0, 8841 R.id.action1, 8842 R.id.action2, 8843 R.id.action3, 8844 R.id.action4, 8845 }; 8846 8847 private int[] mActionsToShowInCompact = null; 8848 private MediaSession.Token mToken; 8849 MediaStyle()8850 public MediaStyle() { 8851 } 8852 8853 /** 8854 * @deprecated use {@code MediaStyle()}. 8855 */ 8856 @Deprecated MediaStyle(Builder builder)8857 public MediaStyle(Builder builder) { 8858 setBuilder(builder); 8859 } 8860 8861 /** 8862 * Request up to 3 actions (by index in the order of addition) to be shown in the compact 8863 * notification view. 8864 * 8865 * @param actions the indices of the actions to show in the compact notification view 8866 */ setShowActionsInCompactView(int...actions)8867 public MediaStyle setShowActionsInCompactView(int...actions) { 8868 mActionsToShowInCompact = actions; 8869 return this; 8870 } 8871 8872 /** 8873 * Attach a {@link android.media.session.MediaSession.Token} to this Notification 8874 * to provide additional playback information and control to the SystemUI. 8875 */ setMediaSession(MediaSession.Token token)8876 public MediaStyle setMediaSession(MediaSession.Token token) { 8877 mToken = token; 8878 return this; 8879 } 8880 8881 /** 8882 * @hide 8883 */ 8884 @Override 8885 @UnsupportedAppUsage buildStyled(Notification wip)8886 public Notification buildStyled(Notification wip) { 8887 super.buildStyled(wip); 8888 if (wip.category == null) { 8889 wip.category = Notification.CATEGORY_TRANSPORT; 8890 } 8891 return wip; 8892 } 8893 8894 /** 8895 * @hide 8896 */ 8897 @Override makeContentView(boolean increasedHeight)8898 public RemoteViews makeContentView(boolean increasedHeight) { 8899 return makeMediaContentView(null /* customContent */); 8900 } 8901 8902 /** 8903 * @hide 8904 */ 8905 @Override makeBigContentView()8906 public RemoteViews makeBigContentView() { 8907 return makeMediaBigContentView(null /* customContent */); 8908 } 8909 8910 /** 8911 * @hide 8912 */ 8913 @Override makeHeadsUpContentView(boolean increasedHeight)8914 public RemoteViews makeHeadsUpContentView(boolean increasedHeight) { 8915 return makeMediaContentView(null /* customContent */); 8916 } 8917 8918 /** @hide */ 8919 @Override addExtras(Bundle extras)8920 public void addExtras(Bundle extras) { 8921 super.addExtras(extras); 8922 8923 if (mToken != null) { 8924 extras.putParcelable(EXTRA_MEDIA_SESSION, mToken); 8925 } 8926 if (mActionsToShowInCompact != null) { 8927 extras.putIntArray(EXTRA_COMPACT_ACTIONS, mActionsToShowInCompact); 8928 } 8929 } 8930 8931 /** 8932 * @hide 8933 */ 8934 @Override restoreFromExtras(Bundle extras)8935 protected void restoreFromExtras(Bundle extras) { 8936 super.restoreFromExtras(extras); 8937 8938 if (extras.containsKey(EXTRA_MEDIA_SESSION)) { 8939 mToken = extras.getParcelable(EXTRA_MEDIA_SESSION); 8940 } 8941 if (extras.containsKey(EXTRA_COMPACT_ACTIONS)) { 8942 mActionsToShowInCompact = extras.getIntArray(EXTRA_COMPACT_ACTIONS); 8943 } 8944 } 8945 8946 /** 8947 * @hide 8948 */ 8949 @Override areNotificationsVisiblyDifferent(Style other)8950 public boolean areNotificationsVisiblyDifferent(Style other) { 8951 if (other == null || getClass() != other.getClass()) { 8952 return true; 8953 } 8954 // All fields to compare are on the Notification object 8955 return false; 8956 } 8957 bindMediaActionButton(RemoteViews container, @IdRes int buttonId, Action action, StandardTemplateParams p)8958 private void bindMediaActionButton(RemoteViews container, @IdRes int buttonId, 8959 Action action, StandardTemplateParams p) { 8960 final boolean tombstone = (action.actionIntent == null); 8961 container.setViewVisibility(buttonId, View.VISIBLE); 8962 container.setImageViewIcon(buttonId, action.getIcon()); 8963 8964 // If the action buttons should not be tinted, then just use the default 8965 // notification color. Otherwise, just use the passed-in color. 8966 int tintColor = mBuilder.getStandardActionColor(p); 8967 8968 container.setDrawableTint(buttonId, false, tintColor, 8969 PorterDuff.Mode.SRC_ATOP); 8970 8971 int rippleAlpha = mBuilder.getColors(p).getRippleAlpha(); 8972 int rippleColor = Color.argb(rippleAlpha, Color.red(tintColor), Color.green(tintColor), 8973 Color.blue(tintColor)); 8974 container.setRippleDrawableColor(buttonId, ColorStateList.valueOf(rippleColor)); 8975 8976 if (!tombstone) { 8977 container.setOnClickPendingIntent(buttonId, action.actionIntent); 8978 } 8979 container.setContentDescription(buttonId, action.title); 8980 } 8981 8982 /** @hide */ makeMediaContentView(@ullable RemoteViews customContent)8983 protected RemoteViews makeMediaContentView(@Nullable RemoteViews customContent) { 8984 final int numActions = mBuilder.mActions.size(); 8985 final int numActionsToShow = Math.min(mActionsToShowInCompact == null 8986 ? 0 : mActionsToShowInCompact.length, MAX_MEDIA_BUTTONS_IN_COMPACT); 8987 if (numActionsToShow > numActions) { 8988 throw new IllegalArgumentException(String.format( 8989 "setShowActionsInCompactView: action %d out of bounds (max %d)", 8990 numActions, numActions - 1)); 8991 } 8992 8993 StandardTemplateParams p = mBuilder.mParams.reset() 8994 .viewType(StandardTemplateParams.VIEW_TYPE_NORMAL) 8995 .hideTime(numActionsToShow > 1) // hide if actions wider than a right icon 8996 .hideSubText(numActionsToShow > 1) // hide if actions wider than a right icon 8997 .hideLeftIcon(false) // allow large icon on left when grouped 8998 .hideRightIcon(numActionsToShow > 0) // right icon or actions; not both 8999 .hideProgress(true) 9000 .fillTextsFrom(mBuilder); 9001 TemplateBindResult result = new TemplateBindResult(); 9002 RemoteViews template = mBuilder.applyStandardTemplate( 9003 R.layout.notification_template_material_media, p, 9004 null /* result */); 9005 9006 for (int i = 0; i < MAX_MEDIA_BUTTONS_IN_COMPACT; i++) { 9007 if (i < numActionsToShow) { 9008 final Action action = mBuilder.mActions.get(mActionsToShowInCompact[i]); 9009 bindMediaActionButton(template, MEDIA_BUTTON_IDS[i], action, p); 9010 } else { 9011 template.setViewVisibility(MEDIA_BUTTON_IDS[i], View.GONE); 9012 } 9013 } 9014 // Prevent a swooping expand animation when there are no actions 9015 boolean hasActions = numActionsToShow != 0; 9016 template.setViewVisibility(R.id.media_actions, hasActions ? View.VISIBLE : View.GONE); 9017 9018 // Add custom view if provided by subclass. 9019 buildCustomContentIntoTemplate(mBuilder.mContext, template, customContent, p, result); 9020 return template; 9021 } 9022 9023 /** @hide */ makeMediaBigContentView(@ullable RemoteViews customContent)9024 protected RemoteViews makeMediaBigContentView(@Nullable RemoteViews customContent) { 9025 final int actionCount = Math.min(mBuilder.mActions.size(), MAX_MEDIA_BUTTONS); 9026 StandardTemplateParams p = mBuilder.mParams.reset() 9027 .viewType(StandardTemplateParams.VIEW_TYPE_BIG) 9028 .hideProgress(true) 9029 .fillTextsFrom(mBuilder); 9030 TemplateBindResult result = new TemplateBindResult(); 9031 RemoteViews template = mBuilder.applyStandardTemplate( 9032 R.layout.notification_template_material_big_media, p , result); 9033 9034 for (int i = 0; i < MAX_MEDIA_BUTTONS; i++) { 9035 if (i < actionCount) { 9036 bindMediaActionButton(template, 9037 MEDIA_BUTTON_IDS[i], mBuilder.mActions.get(i), p); 9038 } else { 9039 template.setViewVisibility(MEDIA_BUTTON_IDS[i], View.GONE); 9040 } 9041 } 9042 buildCustomContentIntoTemplate(mBuilder.mContext, template, customContent, p, result); 9043 return template; 9044 } 9045 } 9046 9047 /** 9048 * Helper class for generating large-format notifications that include a large image attachment. 9049 * 9050 * Here's how you'd set the <code>CallStyle</code> on a notification: 9051 * <pre class="prettyprint"> 9052 * Notification notif = new Notification.Builder(mContext) 9053 * .setSmallIcon(R.drawable.new_post) 9054 * .setStyle(Notification.CallStyle.forIncomingCall(caller, declineIntent, answerIntent)) 9055 * .build(); 9056 * </pre> 9057 */ 9058 public static class CallStyle extends Style { 9059 /** @hide */ 9060 public static final int CALL_TYPE_INCOMING = 1; 9061 /** @hide */ 9062 public static final int CALL_TYPE_ONGOING = 2; 9063 /** @hide */ 9064 public static final int CALL_TYPE_SCREENING = 3; 9065 9066 /** 9067 * This is a key used privately on the action.extras to give spacing priority 9068 * to the required call actions 9069 */ 9070 private static final String KEY_ACTION_PRIORITY = "key_action_priority"; 9071 9072 private int mCallType; 9073 private Person mPerson; 9074 private PendingIntent mAnswerIntent; 9075 private PendingIntent mDeclineIntent; 9076 private PendingIntent mHangUpIntent; 9077 private boolean mIsVideo; 9078 private Integer mAnswerButtonColor; 9079 private Integer mDeclineButtonColor; 9080 private Icon mVerificationIcon; 9081 private CharSequence mVerificationText; 9082 CallStyle()9083 CallStyle() { 9084 } 9085 9086 /** 9087 * Create a CallStyle for an incoming call. 9088 * This notification will have a decline and an answer action, will allow a single 9089 * custom {@link Builder#addAction(Action) action}, and will have a default 9090 * {@link Builder#setContentText(CharSequence) content text} for an incoming call. 9091 * 9092 * @param person The person displayed as the caller. 9093 * The person also needs to have a non-empty name associated with it. 9094 * @param declineIntent The intent to be sent when the user taps the decline action 9095 * @param answerIntent The intent to be sent when the user taps the answer action 9096 */ 9097 @NonNull forIncomingCall(@onNull Person person, @NonNull PendingIntent declineIntent, @NonNull PendingIntent answerIntent)9098 public static CallStyle forIncomingCall(@NonNull Person person, 9099 @NonNull PendingIntent declineIntent, @NonNull PendingIntent answerIntent) { 9100 return new CallStyle(CALL_TYPE_INCOMING, person, 9101 null /* hangUpIntent */, 9102 requireNonNull(declineIntent, "declineIntent is required"), 9103 requireNonNull(answerIntent, "answerIntent is required") 9104 ); 9105 } 9106 9107 /** 9108 * Create a CallStyle for an ongoing call. 9109 * This notification will have a hang up action, will allow up to two 9110 * custom {@link Builder#addAction(Action) actions}, and will have a default 9111 * {@link Builder#setContentText(CharSequence) content text} for an ongoing call. 9112 * 9113 * @param person The person displayed as being on the other end of the call. 9114 * The person also needs to have a non-empty name associated with it. 9115 * @param hangUpIntent The intent to be sent when the user taps the hang up action 9116 */ 9117 @NonNull forOngoingCall(@onNull Person person, @NonNull PendingIntent hangUpIntent)9118 public static CallStyle forOngoingCall(@NonNull Person person, 9119 @NonNull PendingIntent hangUpIntent) { 9120 return new CallStyle(CALL_TYPE_ONGOING, person, 9121 requireNonNull(hangUpIntent, "hangUpIntent is required"), 9122 null /* declineIntent */, 9123 null /* answerIntent */ 9124 ); 9125 } 9126 9127 /** 9128 * Create a CallStyle for a call that is being screened. 9129 * This notification will have a hang up and an answer action, will allow a single 9130 * custom {@link Builder#addAction(Action) action}, and will have a default 9131 * {@link Builder#setContentText(CharSequence) content text} for a call that is being 9132 * screened. 9133 * 9134 * @param person The person displayed as the caller. 9135 * The person also needs to have a non-empty name associated with it. 9136 * @param hangUpIntent The intent to be sent when the user taps the hang up action 9137 * @param answerIntent The intent to be sent when the user taps the answer action 9138 */ 9139 @NonNull forScreeningCall(@onNull Person person, @NonNull PendingIntent hangUpIntent, @NonNull PendingIntent answerIntent)9140 public static CallStyle forScreeningCall(@NonNull Person person, 9141 @NonNull PendingIntent hangUpIntent, @NonNull PendingIntent answerIntent) { 9142 return new CallStyle(CALL_TYPE_SCREENING, person, 9143 requireNonNull(hangUpIntent, "hangUpIntent is required"), 9144 null /* declineIntent */, 9145 requireNonNull(answerIntent, "answerIntent is required") 9146 ); 9147 } 9148 9149 /** 9150 * @param person The person displayed for the incoming call. 9151 * The user also needs to have a non-empty name associated with it. 9152 * @param hangUpIntent The intent to be sent when the user taps the hang up action 9153 * @param declineIntent The intent to be sent when the user taps the decline action 9154 * @param answerIntent The intent to be sent when the user taps the answer action 9155 */ CallStyle(int callType, @NonNull Person person, @Nullable PendingIntent hangUpIntent, @Nullable PendingIntent declineIntent, @Nullable PendingIntent answerIntent)9156 private CallStyle(int callType, @NonNull Person person, 9157 @Nullable PendingIntent hangUpIntent, @Nullable PendingIntent declineIntent, 9158 @Nullable PendingIntent answerIntent) { 9159 if (person == null || TextUtils.isEmpty(person.getName())) { 9160 throw new IllegalArgumentException("person must have a non-empty a name"); 9161 } 9162 mCallType = callType; 9163 mPerson = person; 9164 mAnswerIntent = answerIntent; 9165 mDeclineIntent = declineIntent; 9166 mHangUpIntent = hangUpIntent; 9167 } 9168 9169 /** 9170 * Sets whether the call is a video call, which may affect the icons or text used on the 9171 * required action buttons. 9172 */ 9173 @NonNull setIsVideo(boolean isVideo)9174 public CallStyle setIsVideo(boolean isVideo) { 9175 mIsVideo = isVideo; 9176 return this; 9177 } 9178 9179 /** 9180 * Optional icon to be displayed with {@link #setVerificationText(CharSequence) text} 9181 * as a verification status of the caller. 9182 */ 9183 @NonNull setVerificationIcon(@ullable Icon verificationIcon)9184 public CallStyle setVerificationIcon(@Nullable Icon verificationIcon) { 9185 mVerificationIcon = verificationIcon; 9186 return this; 9187 } 9188 9189 /** 9190 * Optional text to be displayed with an {@link #setVerificationIcon(Icon) icon} 9191 * as a verification status of the caller. 9192 */ 9193 @NonNull setVerificationText(@ullable CharSequence verificationText)9194 public CallStyle setVerificationText(@Nullable CharSequence verificationText) { 9195 mVerificationText = safeCharSequence(verificationText); 9196 return this; 9197 } 9198 9199 /** 9200 * Optional color to be used as a hint for the Answer action button's color. 9201 * The system may change this color to ensure sufficient contrast with the background. 9202 * The system may choose to disregard this hint if the notification is not colorized. 9203 */ 9204 @NonNull setAnswerButtonColorHint(@olorInt int color)9205 public CallStyle setAnswerButtonColorHint(@ColorInt int color) { 9206 mAnswerButtonColor = color; 9207 return this; 9208 } 9209 9210 /** 9211 * Optional color to be used as a hint for the Decline or Hang Up action button's color. 9212 * The system may change this color to ensure sufficient contrast with the background. 9213 * The system may choose to disregard this hint if the notification is not colorized. 9214 */ 9215 @NonNull setDeclineButtonColorHint(@olorInt int color)9216 public CallStyle setDeclineButtonColorHint(@ColorInt int color) { 9217 mDeclineButtonColor = color; 9218 return this; 9219 } 9220 9221 /** @hide */ 9222 @Override buildStyled(Notification wip)9223 public Notification buildStyled(Notification wip) { 9224 wip = super.buildStyled(wip); 9225 // ensure that the actions in the builder and notification are corrected. 9226 mBuilder.mActions = getActionsListWithSystemActions(); 9227 wip.actions = new Action[mBuilder.mActions.size()]; 9228 mBuilder.mActions.toArray(wip.actions); 9229 return wip; 9230 } 9231 9232 /** 9233 * @hide 9234 */ displayCustomViewInline()9235 public boolean displayCustomViewInline() { 9236 // This is a lie; True is returned to make sure that the custom view is not used 9237 // instead of the template, but it will not actually be included. 9238 return true; 9239 } 9240 9241 /** 9242 * @hide 9243 */ 9244 @Override purgeResources()9245 public void purgeResources() { 9246 super.purgeResources(); 9247 if (mVerificationIcon != null) { 9248 mVerificationIcon.convertToAshmem(); 9249 } 9250 } 9251 9252 /** 9253 * @hide 9254 */ 9255 @Override reduceImageSizes(Context context)9256 public void reduceImageSizes(Context context) { 9257 super.reduceImageSizes(context); 9258 if (mVerificationIcon != null) { 9259 int rightIconSize = context.getResources().getDimensionPixelSize( 9260 ActivityManager.isLowRamDeviceStatic() 9261 ? R.dimen.notification_right_icon_size_low_ram 9262 : R.dimen.notification_right_icon_size); 9263 mVerificationIcon.scaleDownIfNecessary(rightIconSize, rightIconSize); 9264 } 9265 } 9266 9267 /** 9268 * @hide 9269 */ 9270 @Override makeContentView(boolean increasedHeight)9271 public RemoteViews makeContentView(boolean increasedHeight) { 9272 return makeCallLayout(StandardTemplateParams.VIEW_TYPE_NORMAL); 9273 } 9274 9275 /** 9276 * @hide 9277 */ 9278 @Override makeHeadsUpContentView(boolean increasedHeight)9279 public RemoteViews makeHeadsUpContentView(boolean increasedHeight) { 9280 return makeCallLayout(StandardTemplateParams.VIEW_TYPE_HEADS_UP); 9281 } 9282 9283 /** 9284 * @hide 9285 */ makeBigContentView()9286 public RemoteViews makeBigContentView() { 9287 return makeCallLayout(StandardTemplateParams.VIEW_TYPE_BIG); 9288 } 9289 9290 @NonNull makeNegativeAction()9291 private Action makeNegativeAction() { 9292 if (mDeclineIntent == null) { 9293 return makeAction(R.drawable.ic_call_decline, 9294 R.string.call_notification_hang_up_action, 9295 mDeclineButtonColor, R.color.call_notification_decline_color, 9296 mHangUpIntent); 9297 } else { 9298 return makeAction(R.drawable.ic_call_decline, 9299 R.string.call_notification_decline_action, 9300 mDeclineButtonColor, R.color.call_notification_decline_color, 9301 mDeclineIntent); 9302 } 9303 } 9304 9305 @Nullable makeAnswerAction()9306 private Action makeAnswerAction() { 9307 return mAnswerIntent == null ? null : makeAction( 9308 mIsVideo ? R.drawable.ic_call_answer_video : R.drawable.ic_call_answer, 9309 mIsVideo ? R.string.call_notification_answer_video_action 9310 : R.string.call_notification_answer_action, 9311 mAnswerButtonColor, R.color.call_notification_answer_color, 9312 mAnswerIntent); 9313 } 9314 9315 @NonNull makeAction(@rawableRes int icon, @StringRes int title, @ColorInt Integer colorInt, @ColorRes int defaultColorRes, PendingIntent intent)9316 private Action makeAction(@DrawableRes int icon, @StringRes int title, 9317 @ColorInt Integer colorInt, @ColorRes int defaultColorRes, PendingIntent intent) { 9318 if (colorInt == null || !mBuilder.isCallActionColorCustomizable()) { 9319 colorInt = mBuilder.mContext.getColor(defaultColorRes); 9320 } 9321 Action action = new Action.Builder(Icon.createWithResource("", icon), 9322 new SpannableStringBuilder().append(mBuilder.mContext.getString(title), 9323 new ForegroundColorSpan(colorInt), 9324 SpannableStringBuilder.SPAN_INCLUSIVE_INCLUSIVE), 9325 intent).build(); 9326 action.getExtras().putBoolean(KEY_ACTION_PRIORITY, true); 9327 return action; 9328 } 9329 isActionAddedByCallStyle(Action action)9330 private boolean isActionAddedByCallStyle(Action action) { 9331 // This is an internal extra added by the style to these actions. If an app were to add 9332 // this extra to the action themselves, the action would be dropped. :shrug: 9333 return action != null && action.getExtras().getBoolean(KEY_ACTION_PRIORITY); 9334 } 9335 9336 /** 9337 * Gets the actions list for the call with the answer/decline/hangUp actions inserted in 9338 * the correct place. This returns the correct result even if the system actions have 9339 * already been added, and even if more actions were added since then. 9340 * @hide 9341 */ 9342 @NonNull getActionsListWithSystemActions()9343 public ArrayList<Action> getActionsListWithSystemActions() { 9344 // Define the system actions we expect to see 9345 final Action negativeAction = makeNegativeAction(); 9346 final Action answerAction = makeAnswerAction(); 9347 // Sort the expected actions into the correct order: 9348 // * If there's no answer action, put the hang up / decline action at the end 9349 // * Otherwise put the answer action at the end, and put the decline action at start. 9350 final Action firstAction = answerAction == null ? null : negativeAction; 9351 final Action lastAction = answerAction == null ? negativeAction : answerAction; 9352 9353 // Start creating the result list. 9354 int nonContextualActionSlotsRemaining = MAX_ACTION_BUTTONS; 9355 ArrayList<Action> resultActions = new ArrayList<>(MAX_ACTION_BUTTONS); 9356 if (firstAction != null) { 9357 resultActions.add(firstAction); 9358 --nonContextualActionSlotsRemaining; 9359 } 9360 9361 // Copy actions into the new list, correcting system actions. 9362 if (mBuilder.mActions != null) { 9363 for (Notification.Action action : mBuilder.mActions) { 9364 if (action.isContextual()) { 9365 // Always include all contextual actions 9366 resultActions.add(action); 9367 } else if (isActionAddedByCallStyle(action)) { 9368 // Drop any old versions of system actions 9369 } else { 9370 // Copy non-contextual actions; decrement the remaining action slots. 9371 resultActions.add(action); 9372 --nonContextualActionSlotsRemaining; 9373 } 9374 // If there's exactly one action slot left, fill it with the lastAction. 9375 if (nonContextualActionSlotsRemaining == 1) { 9376 resultActions.add(lastAction); 9377 --nonContextualActionSlotsRemaining; 9378 } 9379 } 9380 } 9381 // If there are any action slots left, the lastAction still needs to be added. 9382 if (nonContextualActionSlotsRemaining >= 1) { 9383 resultActions.add(lastAction); 9384 } 9385 return resultActions; 9386 } 9387 makeCallLayout(int viewType)9388 private RemoteViews makeCallLayout(int viewType) { 9389 final boolean isCollapsed = viewType == StandardTemplateParams.VIEW_TYPE_NORMAL; 9390 Bundle extras = mBuilder.mN.extras; 9391 CharSequence title = mPerson != null ? mPerson.getName() : null; 9392 CharSequence text = mBuilder.processLegacyText(extras.getCharSequence(EXTRA_TEXT)); 9393 if (text == null) { 9394 text = getDefaultText(); 9395 } 9396 9397 // Bind standard template 9398 StandardTemplateParams p = mBuilder.mParams.reset() 9399 .viewType(viewType) 9400 .callStyleActions(true) 9401 .allowTextWithProgress(true) 9402 .hideLeftIcon(true) 9403 .hideRightIcon(true) 9404 .hideAppName(isCollapsed) 9405 .titleViewId(R.id.conversation_text) 9406 .title(title) 9407 .text(text) 9408 .summaryText(mBuilder.processLegacyText(mVerificationText)); 9409 mBuilder.mActions = getActionsListWithSystemActions(); 9410 final RemoteViews contentView; 9411 if (isCollapsed) { 9412 contentView = mBuilder.applyStandardTemplate( 9413 R.layout.notification_template_material_call, p, null /* result */); 9414 } else { 9415 contentView = mBuilder.applyStandardTemplateWithActions( 9416 R.layout.notification_template_material_big_call, p, null /* result */); 9417 } 9418 9419 // Bind some extra conversation-specific header fields. 9420 if (!p.mHideAppName) { 9421 mBuilder.setTextViewColorSecondary(contentView, R.id.app_name_divider, p); 9422 contentView.setViewVisibility(R.id.app_name_divider, View.VISIBLE); 9423 } 9424 bindCallerVerification(contentView, p); 9425 9426 // Bind some custom CallLayout properties 9427 contentView.setInt(R.id.status_bar_latest_event_content, "setLayoutColor", 9428 mBuilder.getSmallIconColor(p)); 9429 contentView.setInt(R.id.status_bar_latest_event_content, 9430 "setNotificationBackgroundColor", mBuilder.getBackgroundColor(p)); 9431 contentView.setIcon(R.id.status_bar_latest_event_content, "setLargeIcon", 9432 mBuilder.mN.mLargeIcon); 9433 contentView.setBundle(R.id.status_bar_latest_event_content, "setData", 9434 mBuilder.mN.extras); 9435 9436 return contentView; 9437 } 9438 bindCallerVerification(RemoteViews contentView, StandardTemplateParams p)9439 private void bindCallerVerification(RemoteViews contentView, StandardTemplateParams p) { 9440 String iconContentDescription = null; 9441 boolean showDivider = true; 9442 if (mVerificationIcon != null) { 9443 contentView.setImageViewIcon(R.id.verification_icon, mVerificationIcon); 9444 contentView.setDrawableTint(R.id.verification_icon, false /* targetBackground */, 9445 mBuilder.getSecondaryTextColor(p), PorterDuff.Mode.SRC_ATOP); 9446 contentView.setViewVisibility(R.id.verification_icon, View.VISIBLE); 9447 iconContentDescription = mBuilder.mContext.getString( 9448 R.string.notification_verified_content_description); 9449 showDivider = false; // the icon replaces the divider 9450 } else { 9451 contentView.setViewVisibility(R.id.verification_icon, View.GONE); 9452 } 9453 if (!TextUtils.isEmpty(mVerificationText)) { 9454 contentView.setTextViewText(R.id.verification_text, mVerificationText); 9455 mBuilder.setTextViewColorSecondary(contentView, R.id.verification_text, p); 9456 contentView.setViewVisibility(R.id.verification_text, View.VISIBLE); 9457 iconContentDescription = null; // let the app's text take precedence 9458 } else { 9459 contentView.setViewVisibility(R.id.verification_text, View.GONE); 9460 showDivider = false; // no divider if no text 9461 } 9462 contentView.setContentDescription(R.id.verification_icon, iconContentDescription); 9463 if (showDivider) { 9464 contentView.setViewVisibility(R.id.verification_divider, View.VISIBLE); 9465 mBuilder.setTextViewColorSecondary(contentView, R.id.verification_divider, p); 9466 } else { 9467 contentView.setViewVisibility(R.id.verification_divider, View.GONE); 9468 } 9469 } 9470 9471 @Nullable getDefaultText()9472 private String getDefaultText() { 9473 switch (mCallType) { 9474 case CALL_TYPE_INCOMING: 9475 return mBuilder.mContext.getString(R.string.call_notification_incoming_text); 9476 case CALL_TYPE_ONGOING: 9477 return mBuilder.mContext.getString(R.string.call_notification_ongoing_text); 9478 case CALL_TYPE_SCREENING: 9479 return mBuilder.mContext.getString(R.string.call_notification_screening_text); 9480 } 9481 return null; 9482 } 9483 9484 /** 9485 * @hide 9486 */ addExtras(Bundle extras)9487 public void addExtras(Bundle extras) { 9488 super.addExtras(extras); 9489 extras.putInt(EXTRA_CALL_TYPE, mCallType); 9490 extras.putBoolean(EXTRA_CALL_IS_VIDEO, mIsVideo); 9491 extras.putParcelable(EXTRA_CALL_PERSON, mPerson); 9492 if (mVerificationIcon != null) { 9493 extras.putParcelable(EXTRA_VERIFICATION_ICON, mVerificationIcon); 9494 } 9495 if (mVerificationText != null) { 9496 extras.putCharSequence(EXTRA_VERIFICATION_TEXT, mVerificationText); 9497 } 9498 if (mAnswerIntent != null) { 9499 extras.putParcelable(EXTRA_ANSWER_INTENT, mAnswerIntent); 9500 } 9501 if (mDeclineIntent != null) { 9502 extras.putParcelable(EXTRA_DECLINE_INTENT, mDeclineIntent); 9503 } 9504 if (mHangUpIntent != null) { 9505 extras.putParcelable(EXTRA_HANG_UP_INTENT, mHangUpIntent); 9506 } 9507 if (mAnswerButtonColor != null) { 9508 extras.putInt(EXTRA_ANSWER_COLOR, mAnswerButtonColor); 9509 } 9510 if (mDeclineButtonColor != null) { 9511 extras.putInt(EXTRA_DECLINE_COLOR, mDeclineButtonColor); 9512 } 9513 fixTitleAndTextExtras(extras); 9514 } 9515 fixTitleAndTextExtras(Bundle extras)9516 private void fixTitleAndTextExtras(Bundle extras) { 9517 CharSequence sender = mPerson != null ? mPerson.getName() : null; 9518 if (sender != null) { 9519 extras.putCharSequence(EXTRA_TITLE, sender); 9520 } 9521 if (extras.getCharSequence(EXTRA_TEXT) == null) { 9522 extras.putCharSequence(EXTRA_TEXT, getDefaultText()); 9523 } 9524 } 9525 9526 /** 9527 * @hide 9528 */ 9529 @Override restoreFromExtras(Bundle extras)9530 protected void restoreFromExtras(Bundle extras) { 9531 super.restoreFromExtras(extras); 9532 mCallType = extras.getInt(EXTRA_CALL_TYPE); 9533 mIsVideo = extras.getBoolean(EXTRA_CALL_IS_VIDEO); 9534 mPerson = extras.getParcelable(EXTRA_CALL_PERSON); 9535 mVerificationIcon = extras.getParcelable(EXTRA_VERIFICATION_ICON); 9536 mVerificationText = extras.getCharSequence(EXTRA_VERIFICATION_TEXT); 9537 mAnswerIntent = extras.getParcelable(EXTRA_ANSWER_INTENT); 9538 mDeclineIntent = extras.getParcelable(EXTRA_DECLINE_INTENT); 9539 mHangUpIntent = extras.getParcelable(EXTRA_HANG_UP_INTENT); 9540 mAnswerButtonColor = extras.containsKey(EXTRA_ANSWER_COLOR) 9541 ? extras.getInt(EXTRA_ANSWER_COLOR) : null; 9542 mDeclineButtonColor = extras.containsKey(EXTRA_DECLINE_COLOR) 9543 ? extras.getInt(EXTRA_DECLINE_COLOR) : null; 9544 } 9545 9546 /** 9547 * @hide 9548 */ 9549 @Override hasSummaryInHeader()9550 public boolean hasSummaryInHeader() { 9551 return false; 9552 } 9553 9554 /** 9555 * @hide 9556 */ 9557 @Override areNotificationsVisiblyDifferent(Style other)9558 public boolean areNotificationsVisiblyDifferent(Style other) { 9559 if (other == null || getClass() != other.getClass()) { 9560 return true; 9561 } 9562 CallStyle otherS = (CallStyle) other; 9563 return !Objects.equals(mCallType, otherS.mCallType) 9564 || !Objects.equals(mPerson, otherS.mPerson) 9565 || !Objects.equals(mVerificationText, otherS.mVerificationText); 9566 } 9567 } 9568 9569 /** 9570 * Notification style for custom views that are decorated by the system 9571 * 9572 * <p>Instead of providing a notification that is completely custom, a developer can set this 9573 * style and still obtain system decorations like the notification header with the expand 9574 * affordance and actions. 9575 * 9576 * <p>Use {@link android.app.Notification.Builder#setCustomContentView(RemoteViews)}, 9577 * {@link android.app.Notification.Builder#setCustomBigContentView(RemoteViews)} and 9578 * {@link android.app.Notification.Builder#setCustomHeadsUpContentView(RemoteViews)} to set the 9579 * corresponding custom views to display. 9580 * 9581 * To use this style with your Notification, feed it to 9582 * {@link Notification.Builder#setStyle(android.app.Notification.Style)} like so: 9583 * <pre class="prettyprint"> 9584 * Notification noti = new Notification.Builder() 9585 * .setSmallIcon(R.drawable.ic_stat_player) 9586 * .setLargeIcon(albumArtBitmap)) 9587 * .setCustomContentView(contentView); 9588 * .setStyle(<b>new Notification.DecoratedCustomViewStyle()</b>) 9589 * .build(); 9590 * </pre> 9591 */ 9592 public static class DecoratedCustomViewStyle extends Style { 9593 DecoratedCustomViewStyle()9594 public DecoratedCustomViewStyle() { 9595 } 9596 9597 /** 9598 * @hide 9599 */ displayCustomViewInline()9600 public boolean displayCustomViewInline() { 9601 return true; 9602 } 9603 9604 /** 9605 * @hide 9606 */ 9607 @Override makeContentView(boolean increasedHeight)9608 public RemoteViews makeContentView(boolean increasedHeight) { 9609 return makeStandardTemplateWithCustomContent(mBuilder.mN.contentView); 9610 } 9611 9612 /** 9613 * @hide 9614 */ 9615 @Override makeBigContentView()9616 public RemoteViews makeBigContentView() { 9617 return makeDecoratedBigContentView(); 9618 } 9619 9620 /** 9621 * @hide 9622 */ 9623 @Override makeHeadsUpContentView(boolean increasedHeight)9624 public RemoteViews makeHeadsUpContentView(boolean increasedHeight) { 9625 return makeDecoratedHeadsUpContentView(); 9626 } 9627 makeDecoratedHeadsUpContentView()9628 private RemoteViews makeDecoratedHeadsUpContentView() { 9629 RemoteViews headsUpContentView = mBuilder.mN.headsUpContentView == null 9630 ? mBuilder.mN.contentView 9631 : mBuilder.mN.headsUpContentView; 9632 if (headsUpContentView == null) { 9633 return null; // no custom view; use the default behavior 9634 } 9635 if (mBuilder.mActions.size() == 0) { 9636 return makeStandardTemplateWithCustomContent(headsUpContentView); 9637 } 9638 TemplateBindResult result = new TemplateBindResult(); 9639 StandardTemplateParams p = mBuilder.mParams.reset() 9640 .viewType(StandardTemplateParams.VIEW_TYPE_HEADS_UP) 9641 .decorationType(StandardTemplateParams.DECORATION_PARTIAL) 9642 .fillTextsFrom(mBuilder); 9643 RemoteViews remoteViews = mBuilder.applyStandardTemplateWithActions( 9644 mBuilder.getHeadsUpBaseLayoutResource(), p, result); 9645 buildCustomContentIntoTemplate(mBuilder.mContext, remoteViews, headsUpContentView, 9646 p, result); 9647 return remoteViews; 9648 } 9649 makeStandardTemplateWithCustomContent(RemoteViews customContent)9650 private RemoteViews makeStandardTemplateWithCustomContent(RemoteViews customContent) { 9651 if (customContent == null) { 9652 return null; // no custom view; use the default behavior 9653 } 9654 TemplateBindResult result = new TemplateBindResult(); 9655 StandardTemplateParams p = mBuilder.mParams.reset() 9656 .viewType(StandardTemplateParams.VIEW_TYPE_NORMAL) 9657 .decorationType(StandardTemplateParams.DECORATION_PARTIAL) 9658 .fillTextsFrom(mBuilder); 9659 RemoteViews remoteViews = mBuilder.applyStandardTemplate( 9660 mBuilder.getBaseLayoutResource(), p, result); 9661 buildCustomContentIntoTemplate(mBuilder.mContext, remoteViews, customContent, 9662 p, result); 9663 return remoteViews; 9664 } 9665 makeDecoratedBigContentView()9666 private RemoteViews makeDecoratedBigContentView() { 9667 RemoteViews bigContentView = mBuilder.mN.bigContentView == null 9668 ? mBuilder.mN.contentView 9669 : mBuilder.mN.bigContentView; 9670 if (bigContentView == null) { 9671 return null; // no custom view; use the default behavior 9672 } 9673 TemplateBindResult result = new TemplateBindResult(); 9674 StandardTemplateParams p = mBuilder.mParams.reset() 9675 .viewType(StandardTemplateParams.VIEW_TYPE_BIG) 9676 .decorationType(StandardTemplateParams.DECORATION_PARTIAL) 9677 .fillTextsFrom(mBuilder); 9678 RemoteViews remoteViews = mBuilder.applyStandardTemplateWithActions( 9679 mBuilder.getBigBaseLayoutResource(), p, result); 9680 buildCustomContentIntoTemplate(mBuilder.mContext, remoteViews, bigContentView, 9681 p, result); 9682 return remoteViews; 9683 } 9684 9685 /** 9686 * @hide 9687 */ 9688 @Override areNotificationsVisiblyDifferent(Style other)9689 public boolean areNotificationsVisiblyDifferent(Style other) { 9690 if (other == null || getClass() != other.getClass()) { 9691 return true; 9692 } 9693 // Comparison done for all custom RemoteViews, independent of style 9694 return false; 9695 } 9696 } 9697 9698 /** 9699 * Notification style for media custom views that are decorated by the system 9700 * 9701 * <p>Instead of providing a media notification that is completely custom, a developer can set 9702 * this style and still obtain system decorations like the notification header with the expand 9703 * affordance and actions. 9704 * 9705 * <p>Use {@link android.app.Notification.Builder#setCustomContentView(RemoteViews)}, 9706 * {@link android.app.Notification.Builder#setCustomBigContentView(RemoteViews)} and 9707 * {@link android.app.Notification.Builder#setCustomHeadsUpContentView(RemoteViews)} to set the 9708 * corresponding custom views to display. 9709 * <p> 9710 * Contrary to {@link MediaStyle} a developer has to opt-in to the colorizing of the 9711 * notification by using {@link Notification.Builder#setColorized(boolean)}. 9712 * <p> 9713 * To use this style with your Notification, feed it to 9714 * {@link Notification.Builder#setStyle(android.app.Notification.Style)} like so: 9715 * <pre class="prettyprint"> 9716 * Notification noti = new Notification.Builder() 9717 * .setSmallIcon(R.drawable.ic_stat_player) 9718 * .setLargeIcon(albumArtBitmap)) 9719 * .setCustomContentView(contentView); 9720 * .setStyle(<b>new Notification.DecoratedMediaCustomViewStyle()</b> 9721 * .setMediaSession(mySession)) 9722 * .build(); 9723 * </pre> 9724 * 9725 * @see android.app.Notification.DecoratedCustomViewStyle 9726 * @see android.app.Notification.MediaStyle 9727 */ 9728 public static class DecoratedMediaCustomViewStyle extends MediaStyle { 9729 DecoratedMediaCustomViewStyle()9730 public DecoratedMediaCustomViewStyle() { 9731 } 9732 9733 /** 9734 * @hide 9735 */ displayCustomViewInline()9736 public boolean displayCustomViewInline() { 9737 return true; 9738 } 9739 9740 /** 9741 * @hide 9742 */ 9743 @Override makeContentView(boolean increasedHeight)9744 public RemoteViews makeContentView(boolean increasedHeight) { 9745 return makeMediaContentView(mBuilder.mN.contentView); 9746 } 9747 9748 /** 9749 * @hide 9750 */ 9751 @Override makeBigContentView()9752 public RemoteViews makeBigContentView() { 9753 RemoteViews customContent = mBuilder.mN.bigContentView != null 9754 ? mBuilder.mN.bigContentView 9755 : mBuilder.mN.contentView; 9756 return makeMediaBigContentView(customContent); 9757 } 9758 9759 /** 9760 * @hide 9761 */ 9762 @Override makeHeadsUpContentView(boolean increasedHeight)9763 public RemoteViews makeHeadsUpContentView(boolean increasedHeight) { 9764 RemoteViews customContent = mBuilder.mN.headsUpContentView != null 9765 ? mBuilder.mN.headsUpContentView 9766 : mBuilder.mN.contentView; 9767 return makeMediaBigContentView(customContent); 9768 } 9769 9770 /** 9771 * @hide 9772 */ 9773 @Override areNotificationsVisiblyDifferent(Style other)9774 public boolean areNotificationsVisiblyDifferent(Style other) { 9775 if (other == null || getClass() != other.getClass()) { 9776 return true; 9777 } 9778 // Comparison done for all custom RemoteViews, independent of style 9779 return false; 9780 } 9781 } 9782 9783 /** 9784 * Encapsulates the information needed to display a notification as a bubble. 9785 * 9786 * <p>A bubble is used to display app content in a floating window over the existing 9787 * foreground activity. A bubble has a collapsed state represented by an icon and an 9788 * expanded state that displays an activity. These may be defined via 9789 * {@link Builder#Builder(PendingIntent, Icon)} or they may 9790 * be defined via an existing shortcut using {@link Builder#Builder(String)}. 9791 * </p> 9792 * 9793 * <b>Notifications with a valid and allowed bubble will display in collapsed state 9794 * outside of the notification shade on unlocked devices. When a user interacts with the 9795 * collapsed bubble, the bubble activity will be invoked and displayed.</b> 9796 * 9797 * @see Notification.Builder#setBubbleMetadata(BubbleMetadata) 9798 */ 9799 public static final class BubbleMetadata implements Parcelable { 9800 9801 private PendingIntent mPendingIntent; 9802 private PendingIntent mDeleteIntent; 9803 private Icon mIcon; 9804 private int mDesiredHeight; 9805 @DimenRes private int mDesiredHeightResId; 9806 private int mFlags; 9807 private String mShortcutId; 9808 9809 /** 9810 * If set and the app creating the bubble is in the foreground, the bubble will be posted 9811 * in its expanded state. 9812 * 9813 * <p>This flag has no effect if the app posting the bubble is not in the foreground. 9814 * The app is considered foreground if it is visible and on the screen, note that 9815 * a foreground service does not qualify. 9816 * </p> 9817 * 9818 * <p>Generally this flag should only be set if the user has performed an action to request 9819 * or create a bubble.</p> 9820 * 9821 * @hide 9822 */ 9823 public static final int FLAG_AUTO_EXPAND_BUBBLE = 0x00000001; 9824 9825 /** 9826 * Indicates whether the notification associated with the bubble is being visually 9827 * suppressed from the notification shade. When <code>true</code> the notification is 9828 * hidden, when <code>false</code> the notification shows as normal. 9829 * 9830 * <p>Apps sending bubbles may set this flag so that the bubble is posted <b>without</b> 9831 * the associated notification in the notification shade.</p> 9832 * 9833 * <p>Generally this flag should only be set by the app if the user has performed an 9834 * action to request or create a bubble, or if the user has seen the content in the 9835 * notification and the notification is no longer relevant. </p> 9836 * 9837 * <p>The system will also update this flag with <code>true</code> to hide the notification 9838 * from the user once the bubble has been expanded. </p> 9839 * 9840 * @hide 9841 */ 9842 public static final int FLAG_SUPPRESS_NOTIFICATION = 0x00000002; 9843 9844 /** 9845 * Indicates whether the bubble should be visually suppressed from the bubble stack if the 9846 * user is viewing the same content outside of the bubble. For example, the user has a 9847 * bubble with Alice and then opens up the main app and navigates to Alice's page. 9848 * 9849 * @hide 9850 */ 9851 public static final int FLAG_SUPPRESSABLE_BUBBLE = 0x00000004; 9852 9853 /** 9854 * Indicates whether the bubble is visually suppressed from the bubble stack. 9855 * 9856 * @hide 9857 */ 9858 public static final int FLAG_SUPPRESS_BUBBLE = 0x00000008; 9859 BubbleMetadata(PendingIntent expandIntent, PendingIntent deleteIntent, Icon icon, int height, @DimenRes int heightResId, String shortcutId)9860 private BubbleMetadata(PendingIntent expandIntent, PendingIntent deleteIntent, 9861 Icon icon, int height, @DimenRes int heightResId, String shortcutId) { 9862 mPendingIntent = expandIntent; 9863 mIcon = icon; 9864 mDesiredHeight = height; 9865 mDesiredHeightResId = heightResId; 9866 mDeleteIntent = deleteIntent; 9867 mShortcutId = shortcutId; 9868 } 9869 BubbleMetadata(Parcel in)9870 private BubbleMetadata(Parcel in) { 9871 if (in.readInt() != 0) { 9872 mPendingIntent = PendingIntent.CREATOR.createFromParcel(in); 9873 } 9874 if (in.readInt() != 0) { 9875 mIcon = Icon.CREATOR.createFromParcel(in); 9876 } 9877 mDesiredHeight = in.readInt(); 9878 mFlags = in.readInt(); 9879 if (in.readInt() != 0) { 9880 mDeleteIntent = PendingIntent.CREATOR.createFromParcel(in); 9881 } 9882 mDesiredHeightResId = in.readInt(); 9883 if (in.readInt() != 0) { 9884 mShortcutId = in.readString8(); 9885 } 9886 } 9887 9888 /** 9889 * @return the shortcut id used for this bubble if created via 9890 * {@link Builder#Builder(String)} or null if created 9891 * via {@link Builder#Builder(PendingIntent, Icon)}. 9892 */ 9893 @Nullable getShortcutId()9894 public String getShortcutId() { 9895 return mShortcutId; 9896 } 9897 9898 /** 9899 * @return the pending intent used to populate the floating window for this bubble, or 9900 * null if this bubble is created via {@link Builder#Builder(String)}. 9901 */ 9902 @SuppressLint("InvalidNullConversion") 9903 @Nullable getIntent()9904 public PendingIntent getIntent() { 9905 return mPendingIntent; 9906 } 9907 9908 /** 9909 * @deprecated use {@link #getIntent()} instead. 9910 * @removed Removed from the R SDK but was never publicly stable. 9911 */ 9912 @Nullable 9913 @Deprecated getBubbleIntent()9914 public PendingIntent getBubbleIntent() { 9915 return mPendingIntent; 9916 } 9917 9918 /** 9919 * @return the pending intent to send when the bubble is dismissed by a user, if one exists. 9920 */ 9921 @Nullable getDeleteIntent()9922 public PendingIntent getDeleteIntent() { 9923 return mDeleteIntent; 9924 } 9925 9926 /** 9927 * @return the icon that will be displayed for this bubble when it is collapsed, or null 9928 * if the bubble is created via {@link Builder#Builder(String)}. 9929 */ 9930 @SuppressLint("InvalidNullConversion") 9931 @Nullable getIcon()9932 public Icon getIcon() { 9933 return mIcon; 9934 } 9935 9936 /** 9937 * @deprecated use {@link #getIcon()} instead. 9938 * @removed Removed from the R SDK but was never publicly stable. 9939 */ 9940 @Nullable 9941 @Deprecated getBubbleIcon()9942 public Icon getBubbleIcon() { 9943 return mIcon; 9944 } 9945 9946 /** 9947 * @return the ideal height, in DPs, for the floating window that app content defined by 9948 * {@link #getIntent()} for this bubble. A value of 0 indicates a desired height has 9949 * not been set. 9950 */ 9951 @Dimension(unit = DP) getDesiredHeight()9952 public int getDesiredHeight() { 9953 return mDesiredHeight; 9954 } 9955 9956 /** 9957 * @return the resId of ideal height for the floating window that app content defined by 9958 * {@link #getIntent()} for this bubble. A value of 0 indicates a res value has not 9959 * been provided for the desired height. 9960 */ 9961 @DimenRes getDesiredHeightResId()9962 public int getDesiredHeightResId() { 9963 return mDesiredHeightResId; 9964 } 9965 9966 /** 9967 * @return whether this bubble should auto expand when it is posted. 9968 * 9969 * @see BubbleMetadata.Builder#setAutoExpandBubble(boolean) 9970 */ getAutoExpandBubble()9971 public boolean getAutoExpandBubble() { 9972 return (mFlags & FLAG_AUTO_EXPAND_BUBBLE) != 0; 9973 } 9974 9975 /** 9976 * Indicates whether the notification associated with the bubble is being visually 9977 * suppressed from the notification shade. When <code>true</code> the notification is 9978 * hidden, when <code>false</code> the notification shows as normal. 9979 * 9980 * <p>Apps sending bubbles may set this flag so that the bubble is posted <b>without</b> 9981 * the associated notification in the notification shade.</p> 9982 * 9983 * <p>Generally the app should only set this flag if the user has performed an 9984 * action to request or create a bubble, or if the user has seen the content in the 9985 * notification and the notification is no longer relevant. </p> 9986 * 9987 * <p>The system will update this flag with <code>true</code> to hide the notification 9988 * from the user once the bubble has been expanded.</p> 9989 * 9990 * @return whether this bubble should suppress the notification when it is posted. 9991 * 9992 * @see BubbleMetadata.Builder#setSuppressNotification(boolean) 9993 */ isNotificationSuppressed()9994 public boolean isNotificationSuppressed() { 9995 return (mFlags & FLAG_SUPPRESS_NOTIFICATION) != 0; 9996 } 9997 9998 /** 9999 * Indicates whether the bubble should be visually suppressed from the bubble stack if the 10000 * user is viewing the same content outside of the bubble. For example, the user has a 10001 * bubble with Alice and then opens up the main app and navigates to Alice's page. 10002 * 10003 * To match the activity and the bubble notification, the bubble notification should 10004 * have a locus id set that matches a locus id set on the activity. 10005 * 10006 * @return whether this bubble should be suppressed when the same content is visible 10007 * outside of the bubble. 10008 * 10009 * @see BubbleMetadata.Builder#setSuppressableBubble(boolean) 10010 */ isBubbleSuppressable()10011 public boolean isBubbleSuppressable() { 10012 return (mFlags & FLAG_SUPPRESSABLE_BUBBLE) != 0; 10013 } 10014 10015 /** 10016 * Indicates whether the bubble is currently visually suppressed from the bubble stack. 10017 * 10018 * @see BubbleMetadata.Builder#setSuppressableBubble(boolean) 10019 */ isBubbleSuppressed()10020 public boolean isBubbleSuppressed() { 10021 return (mFlags & FLAG_SUPPRESS_BUBBLE) != 0; 10022 } 10023 10024 /** 10025 * Sets whether the notification associated with the bubble is being visually 10026 * suppressed from the notification shade. When <code>true</code> the notification is 10027 * hidden, when <code>false</code> the notification shows as normal. 10028 * 10029 * @hide 10030 */ setSuppressNotification(boolean suppressed)10031 public void setSuppressNotification(boolean suppressed) { 10032 if (suppressed) { 10033 mFlags |= Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION; 10034 } else { 10035 mFlags &= ~Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION; 10036 } 10037 } 10038 10039 /** 10040 * Sets whether the bubble should be visually suppressed from the bubble stack if the 10041 * user is viewing the same content outside of the bubble. For example, the user has a 10042 * bubble with Alice and then opens up the main app and navigates to Alice's page. 10043 * 10044 * @hide 10045 */ setSuppressBubble(boolean suppressed)10046 public void setSuppressBubble(boolean suppressed) { 10047 if (suppressed) { 10048 mFlags |= Notification.BubbleMetadata.FLAG_SUPPRESS_BUBBLE; 10049 } else { 10050 mFlags &= ~Notification.BubbleMetadata.FLAG_SUPPRESS_BUBBLE; 10051 } 10052 } 10053 10054 /** 10055 * @hide 10056 */ setFlags(int flags)10057 public void setFlags(int flags) { 10058 mFlags = flags; 10059 } 10060 10061 /** 10062 * @hide 10063 */ getFlags()10064 public int getFlags() { 10065 return mFlags; 10066 } 10067 10068 public static final @android.annotation.NonNull Parcelable.Creator<BubbleMetadata> CREATOR = 10069 new Parcelable.Creator<BubbleMetadata>() { 10070 10071 @Override 10072 public BubbleMetadata createFromParcel(Parcel source) { 10073 return new BubbleMetadata(source); 10074 } 10075 10076 @Override 10077 public BubbleMetadata[] newArray(int size) { 10078 return new BubbleMetadata[size]; 10079 } 10080 }; 10081 10082 @Override describeContents()10083 public int describeContents() { 10084 return 0; 10085 } 10086 10087 @Override writeToParcel(Parcel out, int flags)10088 public void writeToParcel(Parcel out, int flags) { 10089 out.writeInt(mPendingIntent != null ? 1 : 0); 10090 if (mPendingIntent != null) { 10091 mPendingIntent.writeToParcel(out, 0); 10092 } 10093 out.writeInt(mIcon != null ? 1 : 0); 10094 if (mIcon != null) { 10095 mIcon.writeToParcel(out, 0); 10096 } 10097 out.writeInt(mDesiredHeight); 10098 out.writeInt(mFlags); 10099 out.writeInt(mDeleteIntent != null ? 1 : 0); 10100 if (mDeleteIntent != null) { 10101 mDeleteIntent.writeToParcel(out, 0); 10102 } 10103 out.writeInt(mDesiredHeightResId); 10104 out.writeInt(TextUtils.isEmpty(mShortcutId) ? 0 : 1); 10105 if (!TextUtils.isEmpty(mShortcutId)) { 10106 out.writeString8(mShortcutId); 10107 } 10108 } 10109 10110 /** 10111 * Builder to construct a {@link BubbleMetadata} object. 10112 */ 10113 public static final class Builder { 10114 10115 private PendingIntent mPendingIntent; 10116 private Icon mIcon; 10117 private int mDesiredHeight; 10118 @DimenRes private int mDesiredHeightResId; 10119 private int mFlags; 10120 private PendingIntent mDeleteIntent; 10121 private String mShortcutId; 10122 10123 /** 10124 * @deprecated use {@link Builder#Builder(String)} for a bubble created via a 10125 * {@link ShortcutInfo} or {@link Builder#Builder(PendingIntent, Icon)} for a bubble 10126 * created via a {@link PendingIntent}. 10127 */ 10128 @Deprecated Builder()10129 public Builder() { 10130 } 10131 10132 /** 10133 * Creates a {@link BubbleMetadata.Builder} based on a {@link ShortcutInfo}. To create 10134 * a shortcut bubble, ensure that the shortcut associated with the provided 10135 * {@param shortcutId} is published as a dynamic shortcut that was built with 10136 * {@link ShortcutInfo.Builder#setLongLived(boolean)} being true, otherwise your 10137 * notification will not be able to bubble. 10138 * 10139 * <p>The shortcut icon will be used to represent the bubble when it is collapsed.</p> 10140 * 10141 * <p>The shortcut activity will be used when the bubble is expanded. This will display 10142 * the shortcut activity in a floating window over the existing foreground activity.</p> 10143 * 10144 * <p>When the activity is launched from a bubble, 10145 * {@link Activity#isLaunchedFromBubble()} will return with {@code true}. 10146 * </p> 10147 * 10148 * <p>If the shortcut has not been published when the bubble notification is sent, 10149 * no bubble will be produced. If the shortcut is deleted while the bubble is active, 10150 * the bubble will be removed.</p> 10151 * 10152 * @throws NullPointerException if shortcutId is null. 10153 * 10154 * @see ShortcutInfo 10155 * @see ShortcutInfo.Builder#setLongLived(boolean) 10156 * @see android.content.pm.ShortcutManager#addDynamicShortcuts(List) 10157 */ Builder(@onNull String shortcutId)10158 public Builder(@NonNull String shortcutId) { 10159 if (TextUtils.isEmpty(shortcutId)) { 10160 throw new NullPointerException("Bubble requires a non-null shortcut id"); 10161 } 10162 mShortcutId = shortcutId; 10163 } 10164 10165 /** 10166 * Creates a {@link BubbleMetadata.Builder} based on the provided intent and icon. 10167 * 10168 * <p>The icon will be used to represent the bubble when it is collapsed. An icon 10169 * should be representative of the content within the bubble. If your app produces 10170 * multiple bubbles, the icon should be unique for each of them.</p> 10171 * 10172 * <p>The intent that will be used when the bubble is expanded. This will display the 10173 * app content in a floating window over the existing foreground activity. The intent 10174 * should point to a resizable activity. </p> 10175 * 10176 * <p>When the activity is launched from a bubble, 10177 * {@link Activity#isLaunchedFromBubble()} will return with {@code true}. 10178 * </p> 10179 * 10180 * Note that the pending intent used here requires PendingIntent.FLAG_MUTABLE. 10181 * 10182 * @throws NullPointerException if intent is null. 10183 * @throws NullPointerException if icon is null. 10184 */ Builder(@onNull PendingIntent intent, @NonNull Icon icon)10185 public Builder(@NonNull PendingIntent intent, @NonNull Icon icon) { 10186 if (intent == null) { 10187 throw new NullPointerException("Bubble requires non-null pending intent"); 10188 } 10189 if (icon == null) { 10190 throw new NullPointerException("Bubbles require non-null icon"); 10191 } 10192 if (icon.getType() != TYPE_URI_ADAPTIVE_BITMAP 10193 && icon.getType() != TYPE_URI) { 10194 Log.w(TAG, "Bubbles work best with icons of TYPE_URI or " 10195 + "TYPE_URI_ADAPTIVE_BITMAP. " 10196 + "In the future, using an icon of this type will be required."); 10197 } 10198 mPendingIntent = intent; 10199 mIcon = icon; 10200 } 10201 10202 /** 10203 * @deprecated use {@link Builder#Builder(String)} instead. 10204 * @removed Removed from the R SDK but was never publicly stable. 10205 */ 10206 @NonNull 10207 @Deprecated createShortcutBubble(@onNull String shortcutId)10208 public BubbleMetadata.Builder createShortcutBubble(@NonNull String shortcutId) { 10209 if (!TextUtils.isEmpty(shortcutId)) { 10210 // If shortcut id is set, we don't use these if they were previously set. 10211 mPendingIntent = null; 10212 mIcon = null; 10213 } 10214 mShortcutId = shortcutId; 10215 return this; 10216 } 10217 10218 /** 10219 * @deprecated use {@link Builder#Builder(PendingIntent, Icon)} instead. 10220 * @removed Removed from the R SDK but was never publicly stable. 10221 */ 10222 @NonNull 10223 @Deprecated createIntentBubble(@onNull PendingIntent intent, @NonNull Icon icon)10224 public BubbleMetadata.Builder createIntentBubble(@NonNull PendingIntent intent, 10225 @NonNull Icon icon) { 10226 if (intent == null) { 10227 throw new IllegalArgumentException("Bubble requires non-null pending intent"); 10228 } 10229 if (icon == null) { 10230 throw new IllegalArgumentException("Bubbles require non-null icon"); 10231 } 10232 if (icon.getType() != TYPE_URI_ADAPTIVE_BITMAP 10233 && icon.getType() != TYPE_URI) { 10234 Log.w(TAG, "Bubbles work best with icons of TYPE_URI or " 10235 + "TYPE_URI_ADAPTIVE_BITMAP. " 10236 + "In the future, using an icon of this type will be required."); 10237 } 10238 mShortcutId = null; 10239 mPendingIntent = intent; 10240 mIcon = icon; 10241 return this; 10242 } 10243 10244 /** 10245 * Sets the intent for the bubble. 10246 * 10247 * <p>The intent that will be used when the bubble is expanded. This will display the 10248 * app content in a floating window over the existing foreground activity. The intent 10249 * should point to a resizable activity. </p> 10250 * 10251 * @throws NullPointerException if intent is null. 10252 * @throws IllegalStateException if this builder was created via 10253 * {@link Builder#Builder(String)}. 10254 */ 10255 @NonNull setIntent(@onNull PendingIntent intent)10256 public BubbleMetadata.Builder setIntent(@NonNull PendingIntent intent) { 10257 if (mShortcutId != null) { 10258 throw new IllegalStateException("Created as a shortcut bubble, cannot set a " 10259 + "PendingIntent. Consider using " 10260 + "BubbleMetadata.Builder(PendingIntent,Icon) instead."); 10261 } 10262 if (intent == null) { 10263 throw new NullPointerException("Bubble requires non-null pending intent"); 10264 } 10265 mPendingIntent = intent; 10266 return this; 10267 } 10268 10269 /** 10270 * Sets the icon for the bubble. Can only be used if the bubble was created 10271 * via {@link Builder#Builder(PendingIntent, Icon)}. 10272 * 10273 * <p>The icon will be used to represent the bubble when it is collapsed. An icon 10274 * should be representative of the content within the bubble. If your app produces 10275 * multiple bubbles, the icon should be unique for each of them.</p> 10276 * 10277 * <p>It is recommended to use an {@link Icon} of type {@link Icon#TYPE_URI} 10278 * or {@link Icon#TYPE_URI_ADAPTIVE_BITMAP}</p> 10279 * 10280 * @throws NullPointerException if icon is null. 10281 * @throws IllegalStateException if this builder was created via 10282 * {@link Builder#Builder(String)}. 10283 */ 10284 @NonNull setIcon(@onNull Icon icon)10285 public BubbleMetadata.Builder setIcon(@NonNull Icon icon) { 10286 if (mShortcutId != null) { 10287 throw new IllegalStateException("Created as a shortcut bubble, cannot set an " 10288 + "Icon. Consider using " 10289 + "BubbleMetadata.Builder(PendingIntent,Icon) instead."); 10290 } 10291 if (icon == null) { 10292 throw new NullPointerException("Bubbles require non-null icon"); 10293 } 10294 if (icon.getType() != TYPE_URI_ADAPTIVE_BITMAP 10295 && icon.getType() != TYPE_URI) { 10296 Log.w(TAG, "Bubbles work best with icons of TYPE_URI or " 10297 + "TYPE_URI_ADAPTIVE_BITMAP. " 10298 + "In the future, using an icon of this type will be required."); 10299 } 10300 mIcon = icon; 10301 return this; 10302 } 10303 10304 /** 10305 * Sets the desired height in DPs for the expanded content of the bubble. 10306 * 10307 * <p>This height may not be respected if there is not enough space on the screen or if 10308 * the provided height is too small to be useful.</p> 10309 * 10310 * <p>If {@link #setDesiredHeightResId(int)} was previously called on this builder, the 10311 * previous value set will be cleared after calling this method, and this value will 10312 * be used instead.</p> 10313 * 10314 * <p>A desired height (in DPs or via resID) is optional.</p> 10315 * 10316 * @see #setDesiredHeightResId(int) 10317 */ 10318 @NonNull setDesiredHeight(@imensionunit = DP) int height)10319 public BubbleMetadata.Builder setDesiredHeight(@Dimension(unit = DP) int height) { 10320 mDesiredHeight = Math.max(height, 0); 10321 mDesiredHeightResId = 0; 10322 return this; 10323 } 10324 10325 10326 /** 10327 * Sets the desired height via resId for the expanded content of the bubble. 10328 * 10329 * <p>This height may not be respected if there is not enough space on the screen or if 10330 * the provided height is too small to be useful.</p> 10331 * 10332 * <p>If {@link #setDesiredHeight(int)} was previously called on this builder, the 10333 * previous value set will be cleared after calling this method, and this value will 10334 * be used instead.</p> 10335 * 10336 * <p>A desired height (in DPs or via resID) is optional.</p> 10337 * 10338 * @see #setDesiredHeight(int) 10339 */ 10340 @NonNull setDesiredHeightResId(@imenRes int heightResId)10341 public BubbleMetadata.Builder setDesiredHeightResId(@DimenRes int heightResId) { 10342 mDesiredHeightResId = heightResId; 10343 mDesiredHeight = 0; 10344 return this; 10345 } 10346 10347 /** 10348 * Sets whether the bubble will be posted in its expanded state. 10349 * 10350 * <p>This flag has no effect if the app posting the bubble is not in the foreground. 10351 * The app is considered foreground if it is visible and on the screen, note that 10352 * a foreground service does not qualify. 10353 * </p> 10354 * 10355 * <p>Generally, this flag should only be set if the user has performed an action to 10356 * request or create a bubble.</p> 10357 * 10358 * <p>Setting this flag is optional; it defaults to false.</p> 10359 */ 10360 @NonNull setAutoExpandBubble(boolean shouldExpand)10361 public BubbleMetadata.Builder setAutoExpandBubble(boolean shouldExpand) { 10362 setFlag(FLAG_AUTO_EXPAND_BUBBLE, shouldExpand); 10363 return this; 10364 } 10365 10366 /** 10367 * Sets whether the bubble will be posted <b>without</b> the associated notification in 10368 * the notification shade. 10369 * 10370 * <p>Generally, this flag should only be set if the user has performed an action to 10371 * request or create a bubble, or if the user has seen the content in the notification 10372 * and the notification is no longer relevant.</p> 10373 * 10374 * <p>Setting this flag is optional; it defaults to false.</p> 10375 */ 10376 @NonNull setSuppressNotification(boolean shouldSuppressNotif)10377 public BubbleMetadata.Builder setSuppressNotification(boolean shouldSuppressNotif) { 10378 setFlag(FLAG_SUPPRESS_NOTIFICATION, shouldSuppressNotif); 10379 return this; 10380 } 10381 10382 /** 10383 * Indicates whether the bubble should be visually suppressed from the bubble stack if 10384 * the user is viewing the same content outside of the bubble. For example, the user has 10385 * a bubble with Alice and then opens up the main app and navigates to Alice's page. 10386 * 10387 * To match the activity and the bubble notification, the bubble notification should 10388 * have a locus id set that matches a locus id set on the activity. 10389 * 10390 * {@link Notification.Builder#setLocusId(LocusId)} 10391 * {@link Activity#setLocusContext(LocusId, Bundle)} 10392 */ 10393 @NonNull setSuppressableBubble(boolean suppressBubble)10394 public BubbleMetadata.Builder setSuppressableBubble(boolean suppressBubble) { 10395 setFlag(FLAG_SUPPRESSABLE_BUBBLE, suppressBubble); 10396 return this; 10397 } 10398 10399 /** 10400 * Sets an intent to send when this bubble is explicitly removed by the user. 10401 * 10402 * <p>Setting a delete intent is optional.</p> 10403 */ 10404 @NonNull setDeleteIntent(@ullable PendingIntent deleteIntent)10405 public BubbleMetadata.Builder setDeleteIntent(@Nullable PendingIntent deleteIntent) { 10406 mDeleteIntent = deleteIntent; 10407 return this; 10408 } 10409 10410 /** 10411 * Creates the {@link BubbleMetadata} defined by this builder. 10412 * 10413 * @throws NullPointerException if required elements have not been set. 10414 */ 10415 @NonNull build()10416 public BubbleMetadata build() { 10417 if (mShortcutId == null && mPendingIntent == null) { 10418 throw new NullPointerException( 10419 "Must supply pending intent or shortcut to bubble"); 10420 } 10421 if (mShortcutId == null && mIcon == null) { 10422 throw new NullPointerException( 10423 "Must supply an icon or shortcut for the bubble"); 10424 } 10425 BubbleMetadata data = new BubbleMetadata(mPendingIntent, mDeleteIntent, 10426 mIcon, mDesiredHeight, mDesiredHeightResId, mShortcutId); 10427 data.setFlags(mFlags); 10428 return data; 10429 } 10430 10431 /** 10432 * @hide 10433 */ setFlag(int mask, boolean value)10434 public BubbleMetadata.Builder setFlag(int mask, boolean value) { 10435 if (value) { 10436 mFlags |= mask; 10437 } else { 10438 mFlags &= ~mask; 10439 } 10440 return this; 10441 } 10442 } 10443 } 10444 10445 10446 // When adding a new Style subclass here, don't forget to update 10447 // Builder.getNotificationStyleClass. 10448 10449 /** 10450 * Extender interface for use with {@link Builder#extend}. Extenders may be used to add 10451 * metadata or change options on a notification builder. 10452 */ 10453 public interface Extender { 10454 /** 10455 * Apply this extender to a notification builder. 10456 * @param builder the builder to be modified. 10457 * @return the build object for chaining. 10458 */ extend(Builder builder)10459 public Builder extend(Builder builder); 10460 } 10461 10462 /** 10463 * Helper class to add wearable extensions to notifications. 10464 * <p class="note"> See 10465 * <a href="{@docRoot}wear/notifications/creating.html">Creating Notifications 10466 * for Android Wear</a> for more information on how to use this class. 10467 * <p> 10468 * To create a notification with wearable extensions: 10469 * <ol> 10470 * <li>Create a {@link android.app.Notification.Builder}, setting any desired 10471 * properties. 10472 * <li>Create a {@link android.app.Notification.WearableExtender}. 10473 * <li>Set wearable-specific properties using the 10474 * {@code add} and {@code set} methods of {@link android.app.Notification.WearableExtender}. 10475 * <li>Call {@link android.app.Notification.Builder#extend} to apply the extensions to a 10476 * notification. 10477 * <li>Post the notification to the notification system with the 10478 * {@code NotificationManager.notify(...)} methods. 10479 * </ol> 10480 * 10481 * <pre class="prettyprint"> 10482 * Notification notif = new Notification.Builder(mContext) 10483 * .setContentTitle("New mail from " + sender.toString()) 10484 * .setContentText(subject) 10485 * .setSmallIcon(R.drawable.new_mail) 10486 * .extend(new Notification.WearableExtender() 10487 * .setContentIcon(R.drawable.new_mail)) 10488 * .build(); 10489 * NotificationManager notificationManger = 10490 * (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); 10491 * notificationManger.notify(0, notif);</pre> 10492 * 10493 * <p>Wearable extensions can be accessed on an existing notification by using the 10494 * {@code WearableExtender(Notification)} constructor, 10495 * and then using the {@code get} methods to access values. 10496 * 10497 * <pre class="prettyprint"> 10498 * Notification.WearableExtender wearableExtender = new Notification.WearableExtender( 10499 * notification); 10500 * List<Notification> pages = wearableExtender.getPages();</pre> 10501 */ 10502 public static final class WearableExtender implements Extender { 10503 /** 10504 * Sentinel value for an action index that is unset. 10505 */ 10506 public static final int UNSET_ACTION_INDEX = -1; 10507 10508 /** 10509 * Size value for use with {@link #setCustomSizePreset} to show this notification with 10510 * default sizing. 10511 * <p>For custom display notifications created using {@link #setDisplayIntent}, 10512 * the default is {@link #SIZE_MEDIUM}. All other notifications size automatically based 10513 * on their content. 10514 * 10515 * @deprecated Display intents are no longer supported. 10516 */ 10517 @Deprecated 10518 public static final int SIZE_DEFAULT = 0; 10519 10520 /** 10521 * Size value for use with {@link #setCustomSizePreset} to show this notification 10522 * with an extra small size. 10523 * <p>This value is only applicable for custom display notifications created using 10524 * {@link #setDisplayIntent}. 10525 * 10526 * @deprecated Display intents are no longer supported. 10527 */ 10528 @Deprecated 10529 public static final int SIZE_XSMALL = 1; 10530 10531 /** 10532 * Size value for use with {@link #setCustomSizePreset} to show this notification 10533 * with a small size. 10534 * <p>This value is only applicable for custom display notifications created using 10535 * {@link #setDisplayIntent}. 10536 * 10537 * @deprecated Display intents are no longer supported. 10538 */ 10539 @Deprecated 10540 public static final int SIZE_SMALL = 2; 10541 10542 /** 10543 * Size value for use with {@link #setCustomSizePreset} to show this notification 10544 * with a medium size. 10545 * <p>This value is only applicable for custom display notifications created using 10546 * {@link #setDisplayIntent}. 10547 * 10548 * @deprecated Display intents are no longer supported. 10549 */ 10550 @Deprecated 10551 public static final int SIZE_MEDIUM = 3; 10552 10553 /** 10554 * Size value for use with {@link #setCustomSizePreset} to show this notification 10555 * with a large size. 10556 * <p>This value is only applicable for custom display notifications created using 10557 * {@link #setDisplayIntent}. 10558 * 10559 * @deprecated Display intents are no longer supported. 10560 */ 10561 @Deprecated 10562 public static final int SIZE_LARGE = 4; 10563 10564 /** 10565 * Size value for use with {@link #setCustomSizePreset} to show this notification 10566 * full screen. 10567 * <p>This value is only applicable for custom display notifications created using 10568 * {@link #setDisplayIntent}. 10569 * 10570 * @deprecated Display intents are no longer supported. 10571 */ 10572 @Deprecated 10573 public static final int SIZE_FULL_SCREEN = 5; 10574 10575 /** 10576 * Sentinel value for use with {@link #setHintScreenTimeout} to keep the screen on for a 10577 * short amount of time when this notification is displayed on the screen. This 10578 * is the default value. 10579 * 10580 * @deprecated This feature is no longer supported. 10581 */ 10582 @Deprecated 10583 public static final int SCREEN_TIMEOUT_SHORT = 0; 10584 10585 /** 10586 * Sentinel value for use with {@link #setHintScreenTimeout} to keep the screen on 10587 * for a longer amount of time when this notification is displayed on the screen. 10588 * 10589 * @deprecated This feature is no longer supported. 10590 */ 10591 @Deprecated 10592 public static final int SCREEN_TIMEOUT_LONG = -1; 10593 10594 /** Notification extra which contains wearable extensions */ 10595 private static final String EXTRA_WEARABLE_EXTENSIONS = "android.wearable.EXTENSIONS"; 10596 10597 // Keys within EXTRA_WEARABLE_EXTENSIONS for wearable options. 10598 private static final String KEY_ACTIONS = "actions"; 10599 private static final String KEY_FLAGS = "flags"; 10600 private static final String KEY_DISPLAY_INTENT = "displayIntent"; 10601 private static final String KEY_PAGES = "pages"; 10602 private static final String KEY_BACKGROUND = "background"; 10603 private static final String KEY_CONTENT_ICON = "contentIcon"; 10604 private static final String KEY_CONTENT_ICON_GRAVITY = "contentIconGravity"; 10605 private static final String KEY_CONTENT_ACTION_INDEX = "contentActionIndex"; 10606 private static final String KEY_CUSTOM_SIZE_PRESET = "customSizePreset"; 10607 private static final String KEY_CUSTOM_CONTENT_HEIGHT = "customContentHeight"; 10608 private static final String KEY_GRAVITY = "gravity"; 10609 private static final String KEY_HINT_SCREEN_TIMEOUT = "hintScreenTimeout"; 10610 private static final String KEY_DISMISSAL_ID = "dismissalId"; 10611 private static final String KEY_BRIDGE_TAG = "bridgeTag"; 10612 10613 // Flags bitwise-ored to mFlags 10614 private static final int FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE = 0x1; 10615 private static final int FLAG_HINT_HIDE_ICON = 1 << 1; 10616 private static final int FLAG_HINT_SHOW_BACKGROUND_ONLY = 1 << 2; 10617 private static final int FLAG_START_SCROLL_BOTTOM = 1 << 3; 10618 private static final int FLAG_HINT_AVOID_BACKGROUND_CLIPPING = 1 << 4; 10619 private static final int FLAG_BIG_PICTURE_AMBIENT = 1 << 5; 10620 private static final int FLAG_HINT_CONTENT_INTENT_LAUNCHES_ACTIVITY = 1 << 6; 10621 10622 // Default value for flags integer 10623 private static final int DEFAULT_FLAGS = FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE; 10624 10625 private static final int DEFAULT_CONTENT_ICON_GRAVITY = Gravity.END; 10626 private static final int DEFAULT_GRAVITY = Gravity.BOTTOM; 10627 10628 private ArrayList<Action> mActions = new ArrayList<Action>(); 10629 private int mFlags = DEFAULT_FLAGS; 10630 private PendingIntent mDisplayIntent; 10631 private ArrayList<Notification> mPages = new ArrayList<Notification>(); 10632 private Bitmap mBackground; 10633 private int mContentIcon; 10634 private int mContentIconGravity = DEFAULT_CONTENT_ICON_GRAVITY; 10635 private int mContentActionIndex = UNSET_ACTION_INDEX; 10636 private int mCustomSizePreset = SIZE_DEFAULT; 10637 private int mCustomContentHeight; 10638 private int mGravity = DEFAULT_GRAVITY; 10639 private int mHintScreenTimeout; 10640 private String mDismissalId; 10641 private String mBridgeTag; 10642 10643 /** 10644 * Create a {@link android.app.Notification.WearableExtender} with default 10645 * options. 10646 */ WearableExtender()10647 public WearableExtender() { 10648 } 10649 WearableExtender(Notification notif)10650 public WearableExtender(Notification notif) { 10651 Bundle wearableBundle = notif.extras.getBundle(EXTRA_WEARABLE_EXTENSIONS); 10652 if (wearableBundle != null) { 10653 List<Action> actions = wearableBundle.getParcelableArrayList(KEY_ACTIONS); 10654 if (actions != null) { 10655 mActions.addAll(actions); 10656 } 10657 10658 mFlags = wearableBundle.getInt(KEY_FLAGS, DEFAULT_FLAGS); 10659 mDisplayIntent = wearableBundle.getParcelable(KEY_DISPLAY_INTENT); 10660 10661 Notification[] pages = getParcelableArrayFromBundle( 10662 wearableBundle, KEY_PAGES, Notification.class); 10663 if (pages != null) { 10664 Collections.addAll(mPages, pages); 10665 } 10666 10667 mBackground = wearableBundle.getParcelable(KEY_BACKGROUND); 10668 mContentIcon = wearableBundle.getInt(KEY_CONTENT_ICON); 10669 mContentIconGravity = wearableBundle.getInt(KEY_CONTENT_ICON_GRAVITY, 10670 DEFAULT_CONTENT_ICON_GRAVITY); 10671 mContentActionIndex = wearableBundle.getInt(KEY_CONTENT_ACTION_INDEX, 10672 UNSET_ACTION_INDEX); 10673 mCustomSizePreset = wearableBundle.getInt(KEY_CUSTOM_SIZE_PRESET, 10674 SIZE_DEFAULT); 10675 mCustomContentHeight = wearableBundle.getInt(KEY_CUSTOM_CONTENT_HEIGHT); 10676 mGravity = wearableBundle.getInt(KEY_GRAVITY, DEFAULT_GRAVITY); 10677 mHintScreenTimeout = wearableBundle.getInt(KEY_HINT_SCREEN_TIMEOUT); 10678 mDismissalId = wearableBundle.getString(KEY_DISMISSAL_ID); 10679 mBridgeTag = wearableBundle.getString(KEY_BRIDGE_TAG); 10680 } 10681 } 10682 10683 /** 10684 * Apply wearable extensions to a notification that is being built. This is typically 10685 * called by the {@link android.app.Notification.Builder#extend} method of 10686 * {@link android.app.Notification.Builder}. 10687 */ 10688 @Override extend(Notification.Builder builder)10689 public Notification.Builder extend(Notification.Builder builder) { 10690 Bundle wearableBundle = new Bundle(); 10691 10692 if (!mActions.isEmpty()) { 10693 wearableBundle.putParcelableArrayList(KEY_ACTIONS, mActions); 10694 } 10695 if (mFlags != DEFAULT_FLAGS) { 10696 wearableBundle.putInt(KEY_FLAGS, mFlags); 10697 } 10698 if (mDisplayIntent != null) { 10699 wearableBundle.putParcelable(KEY_DISPLAY_INTENT, mDisplayIntent); 10700 } 10701 if (!mPages.isEmpty()) { 10702 wearableBundle.putParcelableArray(KEY_PAGES, mPages.toArray( 10703 new Notification[mPages.size()])); 10704 } 10705 if (mBackground != null) { 10706 wearableBundle.putParcelable(KEY_BACKGROUND, mBackground); 10707 } 10708 if (mContentIcon != 0) { 10709 wearableBundle.putInt(KEY_CONTENT_ICON, mContentIcon); 10710 } 10711 if (mContentIconGravity != DEFAULT_CONTENT_ICON_GRAVITY) { 10712 wearableBundle.putInt(KEY_CONTENT_ICON_GRAVITY, mContentIconGravity); 10713 } 10714 if (mContentActionIndex != UNSET_ACTION_INDEX) { 10715 wearableBundle.putInt(KEY_CONTENT_ACTION_INDEX, 10716 mContentActionIndex); 10717 } 10718 if (mCustomSizePreset != SIZE_DEFAULT) { 10719 wearableBundle.putInt(KEY_CUSTOM_SIZE_PRESET, mCustomSizePreset); 10720 } 10721 if (mCustomContentHeight != 0) { 10722 wearableBundle.putInt(KEY_CUSTOM_CONTENT_HEIGHT, mCustomContentHeight); 10723 } 10724 if (mGravity != DEFAULT_GRAVITY) { 10725 wearableBundle.putInt(KEY_GRAVITY, mGravity); 10726 } 10727 if (mHintScreenTimeout != 0) { 10728 wearableBundle.putInt(KEY_HINT_SCREEN_TIMEOUT, mHintScreenTimeout); 10729 } 10730 if (mDismissalId != null) { 10731 wearableBundle.putString(KEY_DISMISSAL_ID, mDismissalId); 10732 } 10733 if (mBridgeTag != null) { 10734 wearableBundle.putString(KEY_BRIDGE_TAG, mBridgeTag); 10735 } 10736 10737 builder.getExtras().putBundle(EXTRA_WEARABLE_EXTENSIONS, wearableBundle); 10738 return builder; 10739 } 10740 10741 @Override clone()10742 public WearableExtender clone() { 10743 WearableExtender that = new WearableExtender(); 10744 that.mActions = new ArrayList<Action>(this.mActions); 10745 that.mFlags = this.mFlags; 10746 that.mDisplayIntent = this.mDisplayIntent; 10747 that.mPages = new ArrayList<Notification>(this.mPages); 10748 that.mBackground = this.mBackground; 10749 that.mContentIcon = this.mContentIcon; 10750 that.mContentIconGravity = this.mContentIconGravity; 10751 that.mContentActionIndex = this.mContentActionIndex; 10752 that.mCustomSizePreset = this.mCustomSizePreset; 10753 that.mCustomContentHeight = this.mCustomContentHeight; 10754 that.mGravity = this.mGravity; 10755 that.mHintScreenTimeout = this.mHintScreenTimeout; 10756 that.mDismissalId = this.mDismissalId; 10757 that.mBridgeTag = this.mBridgeTag; 10758 return that; 10759 } 10760 10761 /** 10762 * Add a wearable action to this notification. 10763 * 10764 * <p>When wearable actions are added using this method, the set of actions that 10765 * show on a wearable device splits from devices that only show actions added 10766 * using {@link android.app.Notification.Builder#addAction}. This allows for customization 10767 * of which actions display on different devices. 10768 * 10769 * @param action the action to add to this notification 10770 * @return this object for method chaining 10771 * @see android.app.Notification.Action 10772 */ addAction(Action action)10773 public WearableExtender addAction(Action action) { 10774 mActions.add(action); 10775 return this; 10776 } 10777 10778 /** 10779 * Adds wearable actions to this notification. 10780 * 10781 * <p>When wearable actions are added using this method, the set of actions that 10782 * show on a wearable device splits from devices that only show actions added 10783 * using {@link android.app.Notification.Builder#addAction}. This allows for customization 10784 * of which actions display on different devices. 10785 * 10786 * @param actions the actions to add to this notification 10787 * @return this object for method chaining 10788 * @see android.app.Notification.Action 10789 */ addActions(List<Action> actions)10790 public WearableExtender addActions(List<Action> actions) { 10791 mActions.addAll(actions); 10792 return this; 10793 } 10794 10795 /** 10796 * Clear all wearable actions present on this builder. 10797 * @return this object for method chaining. 10798 * @see #addAction 10799 */ clearActions()10800 public WearableExtender clearActions() { 10801 mActions.clear(); 10802 return this; 10803 } 10804 10805 /** 10806 * Get the wearable actions present on this notification. 10807 */ getActions()10808 public List<Action> getActions() { 10809 return mActions; 10810 } 10811 10812 /** 10813 * Set an intent to launch inside of an activity view when displaying 10814 * this notification. The {@link PendingIntent} provided should be for an activity. 10815 * 10816 * <pre class="prettyprint"> 10817 * Intent displayIntent = new Intent(context, MyDisplayActivity.class); 10818 * PendingIntent displayPendingIntent = PendingIntent.getActivity(context, 10819 * 0, displayIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED); 10820 * Notification notif = new Notification.Builder(context) 10821 * .extend(new Notification.WearableExtender() 10822 * .setDisplayIntent(displayPendingIntent) 10823 * .setCustomSizePreset(Notification.WearableExtender.SIZE_MEDIUM)) 10824 * .build();</pre> 10825 * 10826 * <p>The activity to launch needs to allow embedding, must be exported, and 10827 * should have an empty task affinity. It is also recommended to use the device 10828 * default light theme. 10829 * 10830 * <p>Example AndroidManifest.xml entry: 10831 * <pre class="prettyprint"> 10832 * <activity android:name="com.example.MyDisplayActivity" 10833 * android:exported="true" 10834 * android:allowEmbedded="true" 10835 * android:taskAffinity="" 10836 * android:theme="@android:style/Theme.DeviceDefault.Light" /></pre> 10837 * 10838 * @param intent the {@link PendingIntent} for an activity 10839 * @return this object for method chaining 10840 * @see android.app.Notification.WearableExtender#getDisplayIntent 10841 * @deprecated Display intents are no longer supported. 10842 */ 10843 @Deprecated setDisplayIntent(PendingIntent intent)10844 public WearableExtender setDisplayIntent(PendingIntent intent) { 10845 mDisplayIntent = intent; 10846 return this; 10847 } 10848 10849 /** 10850 * Get the intent to launch inside of an activity view when displaying this 10851 * notification. This {@code PendingIntent} should be for an activity. 10852 * 10853 * @deprecated Display intents are no longer supported. 10854 */ 10855 @Deprecated getDisplayIntent()10856 public PendingIntent getDisplayIntent() { 10857 return mDisplayIntent; 10858 } 10859 10860 /** 10861 * Add an additional page of content to display with this notification. The current 10862 * notification forms the first page, and pages added using this function form 10863 * subsequent pages. This field can be used to separate a notification into multiple 10864 * sections. 10865 * 10866 * @param page the notification to add as another page 10867 * @return this object for method chaining 10868 * @see android.app.Notification.WearableExtender#getPages 10869 * @deprecated Multiple content pages are no longer supported. 10870 */ 10871 @Deprecated addPage(Notification page)10872 public WearableExtender addPage(Notification page) { 10873 mPages.add(page); 10874 return this; 10875 } 10876 10877 /** 10878 * Add additional pages of content to display with this notification. The current 10879 * notification forms the first page, and pages added using this function form 10880 * subsequent pages. This field can be used to separate a notification into multiple 10881 * sections. 10882 * 10883 * @param pages a list of notifications 10884 * @return this object for method chaining 10885 * @see android.app.Notification.WearableExtender#getPages 10886 * @deprecated Multiple content pages are no longer supported. 10887 */ 10888 @Deprecated addPages(List<Notification> pages)10889 public WearableExtender addPages(List<Notification> pages) { 10890 mPages.addAll(pages); 10891 return this; 10892 } 10893 10894 /** 10895 * Clear all additional pages present on this builder. 10896 * @return this object for method chaining. 10897 * @see #addPage 10898 * @deprecated Multiple content pages are no longer supported. 10899 */ 10900 @Deprecated clearPages()10901 public WearableExtender clearPages() { 10902 mPages.clear(); 10903 return this; 10904 } 10905 10906 /** 10907 * Get the array of additional pages of content for displaying this notification. The 10908 * current notification forms the first page, and elements within this array form 10909 * subsequent pages. This field can be used to separate a notification into multiple 10910 * sections. 10911 * @return the pages for this notification 10912 * @deprecated Multiple content pages are no longer supported. 10913 */ 10914 @Deprecated getPages()10915 public List<Notification> getPages() { 10916 return mPages; 10917 } 10918 10919 /** 10920 * Set a background image to be displayed behind the notification content. 10921 * Contrary to the {@link android.app.Notification.BigPictureStyle}, this background 10922 * will work with any notification style. 10923 * 10924 * @param background the background bitmap 10925 * @return this object for method chaining 10926 * @see android.app.Notification.WearableExtender#getBackground 10927 * @deprecated Background images are no longer supported. 10928 */ 10929 @Deprecated setBackground(Bitmap background)10930 public WearableExtender setBackground(Bitmap background) { 10931 mBackground = background; 10932 return this; 10933 } 10934 10935 /** 10936 * Get a background image to be displayed behind the notification content. 10937 * Contrary to the {@link android.app.Notification.BigPictureStyle}, this background 10938 * will work with any notification style. 10939 * 10940 * @return the background image 10941 * @see android.app.Notification.WearableExtender#setBackground 10942 * @deprecated Background images are no longer supported. 10943 */ 10944 @Deprecated getBackground()10945 public Bitmap getBackground() { 10946 return mBackground; 10947 } 10948 10949 /** 10950 * Set an icon that goes with the content of this notification. 10951 */ 10952 @Deprecated setContentIcon(int icon)10953 public WearableExtender setContentIcon(int icon) { 10954 mContentIcon = icon; 10955 return this; 10956 } 10957 10958 /** 10959 * Get an icon that goes with the content of this notification. 10960 */ 10961 @Deprecated getContentIcon()10962 public int getContentIcon() { 10963 return mContentIcon; 10964 } 10965 10966 /** 10967 * Set the gravity that the content icon should have within the notification display. 10968 * Supported values include {@link android.view.Gravity#START} and 10969 * {@link android.view.Gravity#END}. The default value is {@link android.view.Gravity#END}. 10970 * @see #setContentIcon 10971 */ 10972 @Deprecated setContentIconGravity(int contentIconGravity)10973 public WearableExtender setContentIconGravity(int contentIconGravity) { 10974 mContentIconGravity = contentIconGravity; 10975 return this; 10976 } 10977 10978 /** 10979 * Get the gravity that the content icon should have within the notification display. 10980 * Supported values include {@link android.view.Gravity#START} and 10981 * {@link android.view.Gravity#END}. The default value is {@link android.view.Gravity#END}. 10982 * @see #getContentIcon 10983 */ 10984 @Deprecated getContentIconGravity()10985 public int getContentIconGravity() { 10986 return mContentIconGravity; 10987 } 10988 10989 /** 10990 * Set an action from this notification's actions as the primary action. If the action has a 10991 * {@link RemoteInput} associated with it, shortcuts to the options for that input are shown 10992 * directly on the notification. 10993 * 10994 * @param actionIndex The index of the primary action. 10995 * If wearable actions were added to the main notification, this index 10996 * will apply to that list, otherwise it will apply to the regular 10997 * actions list. 10998 */ setContentAction(int actionIndex)10999 public WearableExtender setContentAction(int actionIndex) { 11000 mContentActionIndex = actionIndex; 11001 return this; 11002 } 11003 11004 /** 11005 * Get the index of the notification action, if any, that was specified as the primary 11006 * action. 11007 * 11008 * <p>If wearable specific actions were added to the main notification, this index will 11009 * apply to that list, otherwise it will apply to the regular actions list. 11010 * 11011 * @return the action index or {@link #UNSET_ACTION_INDEX} if no action was selected. 11012 */ getContentAction()11013 public int getContentAction() { 11014 return mContentActionIndex; 11015 } 11016 11017 /** 11018 * Set the gravity that this notification should have within the available viewport space. 11019 * Supported values include {@link android.view.Gravity#TOP}, 11020 * {@link android.view.Gravity#CENTER_VERTICAL} and {@link android.view.Gravity#BOTTOM}. 11021 * The default value is {@link android.view.Gravity#BOTTOM}. 11022 */ 11023 @Deprecated setGravity(int gravity)11024 public WearableExtender setGravity(int gravity) { 11025 mGravity = gravity; 11026 return this; 11027 } 11028 11029 /** 11030 * Get the gravity that this notification should have within the available viewport space. 11031 * Supported values include {@link android.view.Gravity#TOP}, 11032 * {@link android.view.Gravity#CENTER_VERTICAL} and {@link android.view.Gravity#BOTTOM}. 11033 * The default value is {@link android.view.Gravity#BOTTOM}. 11034 */ 11035 @Deprecated getGravity()11036 public int getGravity() { 11037 return mGravity; 11038 } 11039 11040 /** 11041 * Set the custom size preset for the display of this notification out of the available 11042 * presets found in {@link android.app.Notification.WearableExtender}, e.g. 11043 * {@link #SIZE_LARGE}. 11044 * <p>Some custom size presets are only applicable for custom display notifications created 11045 * using {@link android.app.Notification.WearableExtender#setDisplayIntent}. Check the 11046 * documentation for the preset in question. See also 11047 * {@link #setCustomContentHeight} and {@link #getCustomSizePreset}. 11048 */ 11049 @Deprecated setCustomSizePreset(int sizePreset)11050 public WearableExtender setCustomSizePreset(int sizePreset) { 11051 mCustomSizePreset = sizePreset; 11052 return this; 11053 } 11054 11055 /** 11056 * Get the custom size preset for the display of this notification out of the available 11057 * presets found in {@link android.app.Notification.WearableExtender}, e.g. 11058 * {@link #SIZE_LARGE}. 11059 * <p>Some custom size presets are only applicable for custom display notifications created 11060 * using {@link #setDisplayIntent}. Check the documentation for the preset in question. 11061 * See also {@link #setCustomContentHeight} and {@link #setCustomSizePreset}. 11062 */ 11063 @Deprecated getCustomSizePreset()11064 public int getCustomSizePreset() { 11065 return mCustomSizePreset; 11066 } 11067 11068 /** 11069 * Set the custom height in pixels for the display of this notification's content. 11070 * <p>This option is only available for custom display notifications created 11071 * using {@link android.app.Notification.WearableExtender#setDisplayIntent}. See also 11072 * {@link android.app.Notification.WearableExtender#setCustomSizePreset} and 11073 * {@link #getCustomContentHeight}. 11074 */ 11075 @Deprecated setCustomContentHeight(int height)11076 public WearableExtender setCustomContentHeight(int height) { 11077 mCustomContentHeight = height; 11078 return this; 11079 } 11080 11081 /** 11082 * Get the custom height in pixels for the display of this notification's content. 11083 * <p>This option is only available for custom display notifications created 11084 * using {@link #setDisplayIntent}. See also {@link #setCustomSizePreset} and 11085 * {@link #setCustomContentHeight}. 11086 */ 11087 @Deprecated getCustomContentHeight()11088 public int getCustomContentHeight() { 11089 return mCustomContentHeight; 11090 } 11091 11092 /** 11093 * Set whether the scrolling position for the contents of this notification should start 11094 * at the bottom of the contents instead of the top when the contents are too long to 11095 * display within the screen. Default is false (start scroll at the top). 11096 */ setStartScrollBottom(boolean startScrollBottom)11097 public WearableExtender setStartScrollBottom(boolean startScrollBottom) { 11098 setFlag(FLAG_START_SCROLL_BOTTOM, startScrollBottom); 11099 return this; 11100 } 11101 11102 /** 11103 * Get whether the scrolling position for the contents of this notification should start 11104 * at the bottom of the contents instead of the top when the contents are too long to 11105 * display within the screen. Default is false (start scroll at the top). 11106 */ getStartScrollBottom()11107 public boolean getStartScrollBottom() { 11108 return (mFlags & FLAG_START_SCROLL_BOTTOM) != 0; 11109 } 11110 11111 /** 11112 * Set whether the content intent is available when the wearable device is not connected 11113 * to a companion device. The user can still trigger this intent when the wearable device 11114 * is offline, but a visual hint will indicate that the content intent may not be available. 11115 * Defaults to true. 11116 */ setContentIntentAvailableOffline( boolean contentIntentAvailableOffline)11117 public WearableExtender setContentIntentAvailableOffline( 11118 boolean contentIntentAvailableOffline) { 11119 setFlag(FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE, contentIntentAvailableOffline); 11120 return this; 11121 } 11122 11123 /** 11124 * Get whether the content intent is available when the wearable device is not connected 11125 * to a companion device. The user can still trigger this intent when the wearable device 11126 * is offline, but a visual hint will indicate that the content intent may not be available. 11127 * Defaults to true. 11128 */ getContentIntentAvailableOffline()11129 public boolean getContentIntentAvailableOffline() { 11130 return (mFlags & FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE) != 0; 11131 } 11132 11133 /** 11134 * Set a hint that this notification's icon should not be displayed. 11135 * @param hintHideIcon {@code true} to hide the icon, {@code false} otherwise. 11136 * @return this object for method chaining 11137 */ 11138 @Deprecated setHintHideIcon(boolean hintHideIcon)11139 public WearableExtender setHintHideIcon(boolean hintHideIcon) { 11140 setFlag(FLAG_HINT_HIDE_ICON, hintHideIcon); 11141 return this; 11142 } 11143 11144 /** 11145 * Get a hint that this notification's icon should not be displayed. 11146 * @return {@code true} if this icon should not be displayed, false otherwise. 11147 * The default value is {@code false} if this was never set. 11148 */ 11149 @Deprecated getHintHideIcon()11150 public boolean getHintHideIcon() { 11151 return (mFlags & FLAG_HINT_HIDE_ICON) != 0; 11152 } 11153 11154 /** 11155 * Set a visual hint that only the background image of this notification should be 11156 * displayed, and other semantic content should be hidden. This hint is only applicable 11157 * to sub-pages added using {@link #addPage}. 11158 */ 11159 @Deprecated setHintShowBackgroundOnly(boolean hintShowBackgroundOnly)11160 public WearableExtender setHintShowBackgroundOnly(boolean hintShowBackgroundOnly) { 11161 setFlag(FLAG_HINT_SHOW_BACKGROUND_ONLY, hintShowBackgroundOnly); 11162 return this; 11163 } 11164 11165 /** 11166 * Get a visual hint that only the background image of this notification should be 11167 * displayed, and other semantic content should be hidden. This hint is only applicable 11168 * to sub-pages added using {@link android.app.Notification.WearableExtender#addPage}. 11169 */ 11170 @Deprecated getHintShowBackgroundOnly()11171 public boolean getHintShowBackgroundOnly() { 11172 return (mFlags & FLAG_HINT_SHOW_BACKGROUND_ONLY) != 0; 11173 } 11174 11175 /** 11176 * Set a hint that this notification's background should not be clipped if possible, 11177 * and should instead be resized to fully display on the screen, retaining the aspect 11178 * ratio of the image. This can be useful for images like barcodes or qr codes. 11179 * @param hintAvoidBackgroundClipping {@code true} to avoid clipping if possible. 11180 * @return this object for method chaining 11181 */ 11182 @Deprecated setHintAvoidBackgroundClipping( boolean hintAvoidBackgroundClipping)11183 public WearableExtender setHintAvoidBackgroundClipping( 11184 boolean hintAvoidBackgroundClipping) { 11185 setFlag(FLAG_HINT_AVOID_BACKGROUND_CLIPPING, hintAvoidBackgroundClipping); 11186 return this; 11187 } 11188 11189 /** 11190 * Get a hint that this notification's background should not be clipped if possible, 11191 * and should instead be resized to fully display on the screen, retaining the aspect 11192 * ratio of the image. This can be useful for images like barcodes or qr codes. 11193 * @return {@code true} if it's ok if the background is clipped on the screen, false 11194 * otherwise. The default value is {@code false} if this was never set. 11195 */ 11196 @Deprecated getHintAvoidBackgroundClipping()11197 public boolean getHintAvoidBackgroundClipping() { 11198 return (mFlags & FLAG_HINT_AVOID_BACKGROUND_CLIPPING) != 0; 11199 } 11200 11201 /** 11202 * Set a hint that the screen should remain on for at least this duration when 11203 * this notification is displayed on the screen. 11204 * @param timeout The requested screen timeout in milliseconds. Can also be either 11205 * {@link #SCREEN_TIMEOUT_SHORT} or {@link #SCREEN_TIMEOUT_LONG}. 11206 * @return this object for method chaining 11207 */ 11208 @Deprecated setHintScreenTimeout(int timeout)11209 public WearableExtender setHintScreenTimeout(int timeout) { 11210 mHintScreenTimeout = timeout; 11211 return this; 11212 } 11213 11214 /** 11215 * Get the duration, in milliseconds, that the screen should remain on for 11216 * when this notification is displayed. 11217 * @return the duration in milliseconds if > 0, or either one of the sentinel values 11218 * {@link #SCREEN_TIMEOUT_SHORT} or {@link #SCREEN_TIMEOUT_LONG}. 11219 */ 11220 @Deprecated getHintScreenTimeout()11221 public int getHintScreenTimeout() { 11222 return mHintScreenTimeout; 11223 } 11224 11225 /** 11226 * Set a hint that this notification's {@link BigPictureStyle} (if present) should be 11227 * converted to low-bit and displayed in ambient mode, especially useful for barcodes and 11228 * qr codes, as well as other simple black-and-white tickets. 11229 * @param hintAmbientBigPicture {@code true} to enable converstion and ambient. 11230 * @return this object for method chaining 11231 * @deprecated This feature is no longer supported. 11232 */ 11233 @Deprecated setHintAmbientBigPicture(boolean hintAmbientBigPicture)11234 public WearableExtender setHintAmbientBigPicture(boolean hintAmbientBigPicture) { 11235 setFlag(FLAG_BIG_PICTURE_AMBIENT, hintAmbientBigPicture); 11236 return this; 11237 } 11238 11239 /** 11240 * Get a hint that this notification's {@link BigPictureStyle} (if present) should be 11241 * converted to low-bit and displayed in ambient mode, especially useful for barcodes and 11242 * qr codes, as well as other simple black-and-white tickets. 11243 * @return {@code true} if it should be displayed in ambient, false otherwise 11244 * otherwise. The default value is {@code false} if this was never set. 11245 * @deprecated This feature is no longer supported. 11246 */ 11247 @Deprecated getHintAmbientBigPicture()11248 public boolean getHintAmbientBigPicture() { 11249 return (mFlags & FLAG_BIG_PICTURE_AMBIENT) != 0; 11250 } 11251 11252 /** 11253 * Set a hint that this notification's content intent will launch an {@link Activity} 11254 * directly, telling the platform that it can generate the appropriate transitions. 11255 * @param hintContentIntentLaunchesActivity {@code true} if the content intent will launch 11256 * an activity and transitions should be generated, false otherwise. 11257 * @return this object for method chaining 11258 */ setHintContentIntentLaunchesActivity( boolean hintContentIntentLaunchesActivity)11259 public WearableExtender setHintContentIntentLaunchesActivity( 11260 boolean hintContentIntentLaunchesActivity) { 11261 setFlag(FLAG_HINT_CONTENT_INTENT_LAUNCHES_ACTIVITY, hintContentIntentLaunchesActivity); 11262 return this; 11263 } 11264 11265 /** 11266 * Get a hint that this notification's content intent will launch an {@link Activity} 11267 * directly, telling the platform that it can generate the appropriate transitions 11268 * @return {@code true} if the content intent will launch an activity and transitions should 11269 * be generated, false otherwise. The default value is {@code false} if this was never set. 11270 */ getHintContentIntentLaunchesActivity()11271 public boolean getHintContentIntentLaunchesActivity() { 11272 return (mFlags & FLAG_HINT_CONTENT_INTENT_LAUNCHES_ACTIVITY) != 0; 11273 } 11274 11275 /** 11276 * Sets the dismissal id for this notification. If a notification is posted with a 11277 * dismissal id, then when that notification is canceled, notifications on other wearables 11278 * and the paired Android phone having that same dismissal id will also be canceled. See 11279 * <a href="{@docRoot}wear/notifications/index.html">Adding Wearable Features to 11280 * Notifications</a> for more information. 11281 * @param dismissalId the dismissal id of the notification. 11282 * @return this object for method chaining 11283 */ setDismissalId(String dismissalId)11284 public WearableExtender setDismissalId(String dismissalId) { 11285 mDismissalId = dismissalId; 11286 return this; 11287 } 11288 11289 /** 11290 * Returns the dismissal id of the notification. 11291 * @return the dismissal id of the notification or null if it has not been set. 11292 */ getDismissalId()11293 public String getDismissalId() { 11294 return mDismissalId; 11295 } 11296 11297 /** 11298 * Sets a bridge tag for this notification. A bridge tag can be set for notifications 11299 * posted from a phone to provide finer-grained control on what notifications are bridged 11300 * to wearables. See <a href="{@docRoot}wear/notifications/index.html">Adding Wearable 11301 * Features to Notifications</a> for more information. 11302 * @param bridgeTag the bridge tag of the notification. 11303 * @return this object for method chaining 11304 */ setBridgeTag(String bridgeTag)11305 public WearableExtender setBridgeTag(String bridgeTag) { 11306 mBridgeTag = bridgeTag; 11307 return this; 11308 } 11309 11310 /** 11311 * Returns the bridge tag of the notification. 11312 * @return the bridge tag or null if not present. 11313 */ getBridgeTag()11314 public String getBridgeTag() { 11315 return mBridgeTag; 11316 } 11317 setFlag(int mask, boolean value)11318 private void setFlag(int mask, boolean value) { 11319 if (value) { 11320 mFlags |= mask; 11321 } else { 11322 mFlags &= ~mask; 11323 } 11324 } 11325 } 11326 11327 /** 11328 * <p>Helper class to add Android Auto extensions to notifications. To create a notification 11329 * with car extensions: 11330 * 11331 * <ol> 11332 * <li>Create an {@link Notification.Builder}, setting any desired 11333 * properties. 11334 * <li>Create a {@link CarExtender}. 11335 * <li>Set car-specific properties using the {@code add} and {@code set} methods of 11336 * {@link CarExtender}. 11337 * <li>Call {@link Notification.Builder#extend(Notification.Extender)} 11338 * to apply the extensions to a notification. 11339 * </ol> 11340 * 11341 * <pre class="prettyprint"> 11342 * Notification notification = new Notification.Builder(context) 11343 * ... 11344 * .extend(new CarExtender() 11345 * .set*(...)) 11346 * .build(); 11347 * </pre> 11348 * 11349 * <p>Car extensions can be accessed on an existing notification by using the 11350 * {@code CarExtender(Notification)} constructor, and then using the {@code get} methods 11351 * to access values. 11352 */ 11353 public static final class CarExtender implements Extender { 11354 private static final String TAG = "CarExtender"; 11355 11356 private static final String EXTRA_CAR_EXTENDER = "android.car.EXTENSIONS"; 11357 private static final String EXTRA_LARGE_ICON = "large_icon"; 11358 private static final String EXTRA_CONVERSATION = "car_conversation"; 11359 private static final String EXTRA_COLOR = "app_color"; 11360 11361 private Bitmap mLargeIcon; 11362 private UnreadConversation mUnreadConversation; 11363 private int mColor = Notification.COLOR_DEFAULT; 11364 11365 /** 11366 * Create a {@link CarExtender} with default options. 11367 */ CarExtender()11368 public CarExtender() { 11369 } 11370 11371 /** 11372 * Create a {@link CarExtender} from the CarExtender options of an existing Notification. 11373 * 11374 * @param notif The notification from which to copy options. 11375 */ CarExtender(Notification notif)11376 public CarExtender(Notification notif) { 11377 Bundle carBundle = notif.extras == null ? 11378 null : notif.extras.getBundle(EXTRA_CAR_EXTENDER); 11379 if (carBundle != null) { 11380 mLargeIcon = carBundle.getParcelable(EXTRA_LARGE_ICON); 11381 mColor = carBundle.getInt(EXTRA_COLOR, Notification.COLOR_DEFAULT); 11382 11383 Bundle b = carBundle.getBundle(EXTRA_CONVERSATION); 11384 mUnreadConversation = UnreadConversation.getUnreadConversationFromBundle(b); 11385 } 11386 } 11387 11388 /** 11389 * Apply car extensions to a notification that is being built. This is typically called by 11390 * the {@link Notification.Builder#extend(Notification.Extender)} 11391 * method of {@link Notification.Builder}. 11392 */ 11393 @Override extend(Notification.Builder builder)11394 public Notification.Builder extend(Notification.Builder builder) { 11395 Bundle carExtensions = new Bundle(); 11396 11397 if (mLargeIcon != null) { 11398 carExtensions.putParcelable(EXTRA_LARGE_ICON, mLargeIcon); 11399 } 11400 if (mColor != Notification.COLOR_DEFAULT) { 11401 carExtensions.putInt(EXTRA_COLOR, mColor); 11402 } 11403 11404 if (mUnreadConversation != null) { 11405 Bundle b = mUnreadConversation.getBundleForUnreadConversation(); 11406 carExtensions.putBundle(EXTRA_CONVERSATION, b); 11407 } 11408 11409 builder.getExtras().putBundle(EXTRA_CAR_EXTENDER, carExtensions); 11410 return builder; 11411 } 11412 11413 /** 11414 * Sets the accent color to use when Android Auto presents the notification. 11415 * 11416 * Android Auto uses the color set with {@link Notification.Builder#setColor(int)} 11417 * to accent the displayed notification. However, not all colors are acceptable in an 11418 * automotive setting. This method can be used to override the color provided in the 11419 * notification in such a situation. 11420 */ setColor(@olorInt int color)11421 public CarExtender setColor(@ColorInt int color) { 11422 mColor = color; 11423 return this; 11424 } 11425 11426 /** 11427 * Gets the accent color. 11428 * 11429 * @see #setColor 11430 */ 11431 @ColorInt getColor()11432 public int getColor() { 11433 return mColor; 11434 } 11435 11436 /** 11437 * Sets the large icon of the car notification. 11438 * 11439 * If no large icon is set in the extender, Android Auto will display the icon 11440 * specified by {@link Notification.Builder#setLargeIcon(android.graphics.Bitmap)} 11441 * 11442 * @param largeIcon The large icon to use in the car notification. 11443 * @return This object for method chaining. 11444 */ setLargeIcon(Bitmap largeIcon)11445 public CarExtender setLargeIcon(Bitmap largeIcon) { 11446 mLargeIcon = largeIcon; 11447 return this; 11448 } 11449 11450 /** 11451 * Gets the large icon used in this car notification, or null if no icon has been set. 11452 * 11453 * @return The large icon for the car notification. 11454 * @see CarExtender#setLargeIcon 11455 */ getLargeIcon()11456 public Bitmap getLargeIcon() { 11457 return mLargeIcon; 11458 } 11459 11460 /** 11461 * Sets the unread conversation in a message notification. 11462 * 11463 * @param unreadConversation The unread part of the conversation this notification conveys. 11464 * @return This object for method chaining. 11465 */ setUnreadConversation(UnreadConversation unreadConversation)11466 public CarExtender setUnreadConversation(UnreadConversation unreadConversation) { 11467 mUnreadConversation = unreadConversation; 11468 return this; 11469 } 11470 11471 /** 11472 * Returns the unread conversation conveyed by this notification. 11473 * @see #setUnreadConversation(UnreadConversation) 11474 */ getUnreadConversation()11475 public UnreadConversation getUnreadConversation() { 11476 return mUnreadConversation; 11477 } 11478 11479 /** 11480 * A class which holds the unread messages from a conversation. 11481 */ 11482 public static class UnreadConversation { 11483 private static final String KEY_AUTHOR = "author"; 11484 private static final String KEY_TEXT = "text"; 11485 private static final String KEY_MESSAGES = "messages"; 11486 private static final String KEY_REMOTE_INPUT = "remote_input"; 11487 private static final String KEY_ON_REPLY = "on_reply"; 11488 private static final String KEY_ON_READ = "on_read"; 11489 private static final String KEY_PARTICIPANTS = "participants"; 11490 private static final String KEY_TIMESTAMP = "timestamp"; 11491 11492 private final String[] mMessages; 11493 private final RemoteInput mRemoteInput; 11494 private final PendingIntent mReplyPendingIntent; 11495 private final PendingIntent mReadPendingIntent; 11496 private final String[] mParticipants; 11497 private final long mLatestTimestamp; 11498 UnreadConversation(String[] messages, RemoteInput remoteInput, PendingIntent replyPendingIntent, PendingIntent readPendingIntent, String[] participants, long latestTimestamp)11499 UnreadConversation(String[] messages, RemoteInput remoteInput, 11500 PendingIntent replyPendingIntent, PendingIntent readPendingIntent, 11501 String[] participants, long latestTimestamp) { 11502 mMessages = messages; 11503 mRemoteInput = remoteInput; 11504 mReadPendingIntent = readPendingIntent; 11505 mReplyPendingIntent = replyPendingIntent; 11506 mParticipants = participants; 11507 mLatestTimestamp = latestTimestamp; 11508 } 11509 11510 /** 11511 * Gets the list of messages conveyed by this notification. 11512 */ getMessages()11513 public String[] getMessages() { 11514 return mMessages; 11515 } 11516 11517 /** 11518 * Gets the remote input that will be used to convey the response to a message list, or 11519 * null if no such remote input exists. 11520 */ getRemoteInput()11521 public RemoteInput getRemoteInput() { 11522 return mRemoteInput; 11523 } 11524 11525 /** 11526 * Gets the pending intent that will be triggered when the user replies to this 11527 * notification. 11528 */ getReplyPendingIntent()11529 public PendingIntent getReplyPendingIntent() { 11530 return mReplyPendingIntent; 11531 } 11532 11533 /** 11534 * Gets the pending intent that Android Auto will send after it reads aloud all messages 11535 * in this object's message list. 11536 */ getReadPendingIntent()11537 public PendingIntent getReadPendingIntent() { 11538 return mReadPendingIntent; 11539 } 11540 11541 /** 11542 * Gets the participants in the conversation. 11543 */ getParticipants()11544 public String[] getParticipants() { 11545 return mParticipants; 11546 } 11547 11548 /** 11549 * Gets the firs participant in the conversation. 11550 */ getParticipant()11551 public String getParticipant() { 11552 return mParticipants.length > 0 ? mParticipants[0] : null; 11553 } 11554 11555 /** 11556 * Gets the timestamp of the conversation. 11557 */ getLatestTimestamp()11558 public long getLatestTimestamp() { 11559 return mLatestTimestamp; 11560 } 11561 getBundleForUnreadConversation()11562 Bundle getBundleForUnreadConversation() { 11563 Bundle b = new Bundle(); 11564 String author = null; 11565 if (mParticipants != null && mParticipants.length > 1) { 11566 author = mParticipants[0]; 11567 } 11568 Parcelable[] messages = new Parcelable[mMessages.length]; 11569 for (int i = 0; i < messages.length; i++) { 11570 Bundle m = new Bundle(); 11571 m.putString(KEY_TEXT, mMessages[i]); 11572 m.putString(KEY_AUTHOR, author); 11573 messages[i] = m; 11574 } 11575 b.putParcelableArray(KEY_MESSAGES, messages); 11576 if (mRemoteInput != null) { 11577 b.putParcelable(KEY_REMOTE_INPUT, mRemoteInput); 11578 } 11579 b.putParcelable(KEY_ON_REPLY, mReplyPendingIntent); 11580 b.putParcelable(KEY_ON_READ, mReadPendingIntent); 11581 b.putStringArray(KEY_PARTICIPANTS, mParticipants); 11582 b.putLong(KEY_TIMESTAMP, mLatestTimestamp); 11583 return b; 11584 } 11585 getUnreadConversationFromBundle(Bundle b)11586 static UnreadConversation getUnreadConversationFromBundle(Bundle b) { 11587 if (b == null) { 11588 return null; 11589 } 11590 Parcelable[] parcelableMessages = b.getParcelableArray(KEY_MESSAGES); 11591 String[] messages = null; 11592 if (parcelableMessages != null) { 11593 String[] tmp = new String[parcelableMessages.length]; 11594 boolean success = true; 11595 for (int i = 0; i < tmp.length; i++) { 11596 if (!(parcelableMessages[i] instanceof Bundle)) { 11597 success = false; 11598 break; 11599 } 11600 tmp[i] = ((Bundle) parcelableMessages[i]).getString(KEY_TEXT); 11601 if (tmp[i] == null) { 11602 success = false; 11603 break; 11604 } 11605 } 11606 if (success) { 11607 messages = tmp; 11608 } else { 11609 return null; 11610 } 11611 } 11612 11613 PendingIntent onRead = b.getParcelable(KEY_ON_READ); 11614 PendingIntent onReply = b.getParcelable(KEY_ON_REPLY); 11615 11616 RemoteInput remoteInput = b.getParcelable(KEY_REMOTE_INPUT); 11617 11618 String[] participants = b.getStringArray(KEY_PARTICIPANTS); 11619 if (participants == null || participants.length != 1) { 11620 return null; 11621 } 11622 11623 return new UnreadConversation(messages, 11624 remoteInput, 11625 onReply, 11626 onRead, 11627 participants, b.getLong(KEY_TIMESTAMP)); 11628 } 11629 }; 11630 11631 /** 11632 * Builder class for {@link CarExtender.UnreadConversation} objects. 11633 */ 11634 public static class Builder { 11635 private final List<String> mMessages = new ArrayList<String>(); 11636 private final String mParticipant; 11637 private RemoteInput mRemoteInput; 11638 private PendingIntent mReadPendingIntent; 11639 private PendingIntent mReplyPendingIntent; 11640 private long mLatestTimestamp; 11641 11642 /** 11643 * Constructs a new builder for {@link CarExtender.UnreadConversation}. 11644 * 11645 * @param name The name of the other participant in the conversation. 11646 */ Builder(String name)11647 public Builder(String name) { 11648 mParticipant = name; 11649 } 11650 11651 /** 11652 * Appends a new unread message to the list of messages for this conversation. 11653 * 11654 * The messages should be added from oldest to newest. 11655 * 11656 * @param message The text of the new unread message. 11657 * @return This object for method chaining. 11658 */ addMessage(String message)11659 public Builder addMessage(String message) { 11660 mMessages.add(message); 11661 return this; 11662 } 11663 11664 /** 11665 * Sets the pending intent and remote input which will convey the reply to this 11666 * notification. 11667 * 11668 * @param pendingIntent The pending intent which will be triggered on a reply. 11669 * @param remoteInput The remote input parcelable which will carry the reply. 11670 * @return This object for method chaining. 11671 * 11672 * @see CarExtender.UnreadConversation#getRemoteInput 11673 * @see CarExtender.UnreadConversation#getReplyPendingIntent 11674 */ setReplyAction( PendingIntent pendingIntent, RemoteInput remoteInput)11675 public Builder setReplyAction( 11676 PendingIntent pendingIntent, RemoteInput remoteInput) { 11677 mRemoteInput = remoteInput; 11678 mReplyPendingIntent = pendingIntent; 11679 11680 return this; 11681 } 11682 11683 /** 11684 * Sets the pending intent that will be sent once the messages in this notification 11685 * are read. 11686 * 11687 * @param pendingIntent The pending intent to use. 11688 * @return This object for method chaining. 11689 */ setReadPendingIntent(PendingIntent pendingIntent)11690 public Builder setReadPendingIntent(PendingIntent pendingIntent) { 11691 mReadPendingIntent = pendingIntent; 11692 return this; 11693 } 11694 11695 /** 11696 * Sets the timestamp of the most recent message in an unread conversation. 11697 * 11698 * If a messaging notification has been posted by your application and has not 11699 * yet been cancelled, posting a later notification with the same id and tag 11700 * but without a newer timestamp may result in Android Auto not displaying a 11701 * heads up notification for the later notification. 11702 * 11703 * @param timestamp The timestamp of the most recent message in the conversation. 11704 * @return This object for method chaining. 11705 */ setLatestTimestamp(long timestamp)11706 public Builder setLatestTimestamp(long timestamp) { 11707 mLatestTimestamp = timestamp; 11708 return this; 11709 } 11710 11711 /** 11712 * Builds a new unread conversation object. 11713 * 11714 * @return The new unread conversation object. 11715 */ build()11716 public UnreadConversation build() { 11717 String[] messages = mMessages.toArray(new String[mMessages.size()]); 11718 String[] participants = { mParticipant }; 11719 return new UnreadConversation(messages, mRemoteInput, mReplyPendingIntent, 11720 mReadPendingIntent, participants, mLatestTimestamp); 11721 } 11722 } 11723 } 11724 11725 /** 11726 * <p>Helper class to add Android TV extensions to notifications. To create a notification 11727 * with a TV extension: 11728 * 11729 * <ol> 11730 * <li>Create an {@link Notification.Builder}, setting any desired properties. 11731 * <li>Create a {@link TvExtender}. 11732 * <li>Set TV-specific properties using the {@code set} methods of 11733 * {@link TvExtender}. 11734 * <li>Call {@link Notification.Builder#extend(Notification.Extender)} 11735 * to apply the extension to a notification. 11736 * </ol> 11737 * 11738 * <pre class="prettyprint"> 11739 * Notification notification = new Notification.Builder(context) 11740 * ... 11741 * .extend(new TvExtender() 11742 * .set*(...)) 11743 * .build(); 11744 * </pre> 11745 * 11746 * <p>TV extensions can be accessed on an existing notification by using the 11747 * {@code TvExtender(Notification)} constructor, and then using the {@code get} methods 11748 * to access values. 11749 * 11750 * @hide 11751 */ 11752 @SystemApi 11753 public static final class TvExtender implements Extender { 11754 private static final String TAG = "TvExtender"; 11755 11756 private static final String EXTRA_TV_EXTENDER = "android.tv.EXTENSIONS"; 11757 private static final String EXTRA_FLAGS = "flags"; 11758 private static final String EXTRA_CONTENT_INTENT = "content_intent"; 11759 private static final String EXTRA_DELETE_INTENT = "delete_intent"; 11760 private static final String EXTRA_CHANNEL_ID = "channel_id"; 11761 private static final String EXTRA_SUPPRESS_SHOW_OVER_APPS = "suppressShowOverApps"; 11762 11763 // Flags bitwise-ored to mFlags 11764 private static final int FLAG_AVAILABLE_ON_TV = 0x1; 11765 11766 private int mFlags; 11767 private String mChannelId; 11768 private PendingIntent mContentIntent; 11769 private PendingIntent mDeleteIntent; 11770 private boolean mSuppressShowOverApps; 11771 11772 /** 11773 * Create a {@link TvExtender} with default options. 11774 */ TvExtender()11775 public TvExtender() { 11776 mFlags = FLAG_AVAILABLE_ON_TV; 11777 } 11778 11779 /** 11780 * Create a {@link TvExtender} from the TvExtender options of an existing Notification. 11781 * 11782 * @param notif The notification from which to copy options. 11783 */ TvExtender(Notification notif)11784 public TvExtender(Notification notif) { 11785 Bundle bundle = notif.extras == null ? 11786 null : notif.extras.getBundle(EXTRA_TV_EXTENDER); 11787 if (bundle != null) { 11788 mFlags = bundle.getInt(EXTRA_FLAGS); 11789 mChannelId = bundle.getString(EXTRA_CHANNEL_ID); 11790 mSuppressShowOverApps = bundle.getBoolean(EXTRA_SUPPRESS_SHOW_OVER_APPS); 11791 mContentIntent = bundle.getParcelable(EXTRA_CONTENT_INTENT); 11792 mDeleteIntent = bundle.getParcelable(EXTRA_DELETE_INTENT); 11793 } 11794 } 11795 11796 /** 11797 * Apply a TV extension to a notification that is being built. This is typically called by 11798 * the {@link Notification.Builder#extend(Notification.Extender)} 11799 * method of {@link Notification.Builder}. 11800 */ 11801 @Override extend(Notification.Builder builder)11802 public Notification.Builder extend(Notification.Builder builder) { 11803 Bundle bundle = new Bundle(); 11804 11805 bundle.putInt(EXTRA_FLAGS, mFlags); 11806 bundle.putString(EXTRA_CHANNEL_ID, mChannelId); 11807 bundle.putBoolean(EXTRA_SUPPRESS_SHOW_OVER_APPS, mSuppressShowOverApps); 11808 if (mContentIntent != null) { 11809 bundle.putParcelable(EXTRA_CONTENT_INTENT, mContentIntent); 11810 } 11811 11812 if (mDeleteIntent != null) { 11813 bundle.putParcelable(EXTRA_DELETE_INTENT, mDeleteIntent); 11814 } 11815 11816 builder.getExtras().putBundle(EXTRA_TV_EXTENDER, bundle); 11817 return builder; 11818 } 11819 11820 /** 11821 * Returns true if this notification should be shown on TV. This method return true 11822 * if the notification was extended with a TvExtender. 11823 */ isAvailableOnTv()11824 public boolean isAvailableOnTv() { 11825 return (mFlags & FLAG_AVAILABLE_ON_TV) != 0; 11826 } 11827 11828 /** 11829 * Specifies the channel the notification should be delivered on when shown on TV. 11830 * It can be different from the channel that the notification is delivered to when 11831 * posting on a non-TV device. 11832 */ setChannel(String channelId)11833 public TvExtender setChannel(String channelId) { 11834 mChannelId = channelId; 11835 return this; 11836 } 11837 11838 /** 11839 * Specifies the channel the notification should be delivered on when shown on TV. 11840 * It can be different from the channel that the notification is delivered to when 11841 * posting on a non-TV device. 11842 */ setChannelId(String channelId)11843 public TvExtender setChannelId(String channelId) { 11844 mChannelId = channelId; 11845 return this; 11846 } 11847 11848 /** @removed */ 11849 @Deprecated getChannel()11850 public String getChannel() { 11851 return mChannelId; 11852 } 11853 11854 /** 11855 * Returns the id of the channel this notification posts to on TV. 11856 */ getChannelId()11857 public String getChannelId() { 11858 return mChannelId; 11859 } 11860 11861 /** 11862 * Supplies a {@link PendingIntent} to be sent when the notification is selected on TV. 11863 * If provided, it is used instead of the content intent specified 11864 * at the level of Notification. 11865 */ setContentIntent(PendingIntent intent)11866 public TvExtender setContentIntent(PendingIntent intent) { 11867 mContentIntent = intent; 11868 return this; 11869 } 11870 11871 /** 11872 * Returns the TV-specific content intent. If this method returns null, the 11873 * main content intent on the notification should be used. 11874 * 11875 * @see {@link Notification#contentIntent} 11876 */ getContentIntent()11877 public PendingIntent getContentIntent() { 11878 return mContentIntent; 11879 } 11880 11881 /** 11882 * Supplies a {@link PendingIntent} to send when the notification is cleared explicitly 11883 * by the user on TV. If provided, it is used instead of the delete intent specified 11884 * at the level of Notification. 11885 */ setDeleteIntent(PendingIntent intent)11886 public TvExtender setDeleteIntent(PendingIntent intent) { 11887 mDeleteIntent = intent; 11888 return this; 11889 } 11890 11891 /** 11892 * Returns the TV-specific delete intent. If this method returns null, the 11893 * main delete intent on the notification should be used. 11894 * 11895 * @see {@link Notification#deleteIntent} 11896 */ getDeleteIntent()11897 public PendingIntent getDeleteIntent() { 11898 return mDeleteIntent; 11899 } 11900 11901 /** 11902 * Specifies whether this notification should suppress showing a message over top of apps 11903 * outside of the launcher. 11904 */ setSuppressShowOverApps(boolean suppress)11905 public TvExtender setSuppressShowOverApps(boolean suppress) { 11906 mSuppressShowOverApps = suppress; 11907 return this; 11908 } 11909 11910 /** 11911 * Returns true if this notification should not show messages over top of apps 11912 * outside of the launcher. 11913 */ getSuppressShowOverApps()11914 public boolean getSuppressShowOverApps() { 11915 return mSuppressShowOverApps; 11916 } 11917 } 11918 11919 /** 11920 * Get an array of Parcelable objects from a parcelable array bundle field. 11921 * Update the bundle to have a typed array so fetches in the future don't need 11922 * to do an array copy. 11923 */ 11924 @Nullable getParcelableArrayFromBundle( Bundle bundle, String key, Class<T> itemClass)11925 private static <T extends Parcelable> T[] getParcelableArrayFromBundle( 11926 Bundle bundle, String key, Class<T> itemClass) { 11927 final Parcelable[] array = bundle.getParcelableArray(key); 11928 final Class<?> arrayClass = Array.newInstance(itemClass, 0).getClass(); 11929 if (arrayClass.isInstance(array) || array == null) { 11930 return (T[]) array; 11931 } 11932 final T[] typedArray = (T[]) Array.newInstance(itemClass, array.length); 11933 for (int i = 0; i < array.length; i++) { 11934 typedArray[i] = (T) array[i]; 11935 } 11936 bundle.putParcelableArray(key, typedArray); 11937 return typedArray; 11938 } 11939 11940 private static class BuilderRemoteViews extends RemoteViews { BuilderRemoteViews(Parcel parcel)11941 public BuilderRemoteViews(Parcel parcel) { 11942 super(parcel); 11943 } 11944 BuilderRemoteViews(ApplicationInfo appInfo, int layoutId)11945 public BuilderRemoteViews(ApplicationInfo appInfo, int layoutId) { 11946 super(appInfo, layoutId); 11947 } 11948 11949 @Override clone()11950 public BuilderRemoteViews clone() { 11951 Parcel p = Parcel.obtain(); 11952 writeToParcel(p, 0); 11953 p.setDataPosition(0); 11954 BuilderRemoteViews brv = new BuilderRemoteViews(p); 11955 p.recycle(); 11956 return brv; 11957 } 11958 11959 /** 11960 * Override and return true, since {@link RemoteViews#onLoadClass(Class)} is not overridden. 11961 * 11962 * @see RemoteViews#shouldUseStaticFilter() 11963 */ 11964 @Override shouldUseStaticFilter()11965 protected boolean shouldUseStaticFilter() { 11966 return true; 11967 } 11968 } 11969 11970 /** 11971 * A result object where information about the template that was created is saved. 11972 */ 11973 private static class TemplateBindResult { 11974 boolean mRightIconVisible; 11975 float mRightIconWidthDp; 11976 float mRightIconHeightDp; 11977 11978 /** 11979 * The margin end that needs to be added to the heading so that it won't overlap 11980 * with the large icon. This value includes the space required to accommodate the large 11981 * icon, but should be added to the space needed to accommodate the expander. This does 11982 * not include the 16dp content margin that all notification views must have. 11983 */ 11984 public final MarginSet mHeadingExtraMarginSet = new MarginSet(); 11985 11986 /** 11987 * The margin end that needs to be added to the heading so that it won't overlap 11988 * with the large icon. This value includes the space required to accommodate the large 11989 * icon as well as the expander. This does not include the 16dp content margin that all 11990 * notification views must have. 11991 */ 11992 public final MarginSet mHeadingFullMarginSet = new MarginSet(); 11993 11994 /** 11995 * The margin end that needs to be added to the title text of the big state 11996 * so that it won't overlap with the large icon, but assuming the text can run under 11997 * the expander when that icon is not visible. 11998 */ 11999 public final MarginSet mTitleMarginSet = new MarginSet(); 12000 setRightIconState(boolean visible, float widthDp, float heightDp, float marginEndDpIfVisible, float expanderSizeDp)12001 public void setRightIconState(boolean visible, float widthDp, float heightDp, 12002 float marginEndDpIfVisible, float expanderSizeDp) { 12003 mRightIconVisible = visible; 12004 mRightIconWidthDp = widthDp; 12005 mRightIconHeightDp = heightDp; 12006 mHeadingExtraMarginSet.setValues(0, marginEndDpIfVisible); 12007 mHeadingFullMarginSet.setValues(expanderSizeDp, marginEndDpIfVisible + expanderSizeDp); 12008 mTitleMarginSet.setValues(0, marginEndDpIfVisible + expanderSizeDp); 12009 } 12010 12011 /** 12012 * This contains the end margins for a view when the right icon is visible or not. These 12013 * values are both needed so that NotificationGroupingUtil can 'move' the right_icon to the 12014 * left_icon and adjust the margins, and to undo that change as well. 12015 */ 12016 private class MarginSet { 12017 private float mValueIfGone; 12018 private float mValueIfVisible; 12019 setValues(float valueIfGone, float valueIfVisible)12020 public void setValues(float valueIfGone, float valueIfVisible) { 12021 mValueIfGone = valueIfGone; 12022 mValueIfVisible = valueIfVisible; 12023 } 12024 applyToView(@onNull RemoteViews views, @IdRes int viewId)12025 public void applyToView(@NonNull RemoteViews views, @IdRes int viewId) { 12026 applyToView(views, viewId, 0); 12027 } 12028 applyToView(@onNull RemoteViews views, @IdRes int viewId, float extraMarginDp)12029 public void applyToView(@NonNull RemoteViews views, @IdRes int viewId, 12030 float extraMarginDp) { 12031 final float marginEndDp = getDpValue() + extraMarginDp; 12032 if (viewId == R.id.notification_header) { 12033 views.setFloat(R.id.notification_header, 12034 "setTopLineExtraMarginEndDp", marginEndDp); 12035 } else if (viewId == R.id.text || viewId == R.id.big_text) { 12036 if (mValueIfGone != 0) { 12037 throw new RuntimeException("Programming error: `text` and `big_text` use " 12038 + "ImageFloatingTextView which can either show a margin or not; " 12039 + "thus mValueIfGone must be 0, but it was " + mValueIfGone); 12040 } 12041 // Note that the caller must set "setNumIndentLines" to a positive int in order 12042 // for this margin to do anything at all. 12043 views.setFloat(viewId, "setImageEndMarginDp", mValueIfVisible); 12044 views.setBoolean(viewId, "setHasImage", mRightIconVisible); 12045 // Apply just the *extra* margin as the view layout margin; this will be 12046 // unchanged depending on the visibility of the image, but it means that the 12047 // extra margin applies to *every* line of text instead of just indented lines. 12048 views.setViewLayoutMargin(viewId, RemoteViews.MARGIN_END, 12049 extraMarginDp, TypedValue.COMPLEX_UNIT_DIP); 12050 } else { 12051 views.setViewLayoutMargin(viewId, RemoteViews.MARGIN_END, 12052 marginEndDp, TypedValue.COMPLEX_UNIT_DIP); 12053 } 12054 if (mRightIconVisible) { 12055 views.setIntTag(viewId, R.id.tag_margin_end_when_icon_visible, 12056 TypedValue.createComplexDimension( 12057 mValueIfVisible + extraMarginDp, TypedValue.COMPLEX_UNIT_DIP)); 12058 views.setIntTag(viewId, R.id.tag_margin_end_when_icon_gone, 12059 TypedValue.createComplexDimension( 12060 mValueIfGone + extraMarginDp, TypedValue.COMPLEX_UNIT_DIP)); 12061 } 12062 } 12063 getDpValue()12064 public float getDpValue() { 12065 return mRightIconVisible ? mValueIfVisible : mValueIfGone; 12066 } 12067 } 12068 } 12069 12070 private static class StandardTemplateParams { 12071 /** 12072 * Notifications will be minimally decorated with ONLY an icon and expander: 12073 * <li>A large icon is never shown. 12074 * <li>A progress bar is never shown. 12075 * <li>The expanded and heads up states do not show actions, even if provided. 12076 */ 12077 public static final int DECORATION_MINIMAL = 1; 12078 12079 /** 12080 * Notifications will be partially decorated with AT LEAST an icon and expander: 12081 * <li>A large icon is shown if provided. 12082 * <li>A progress bar is shown if provided and enough space remains below the content. 12083 * <li>Actions are shown in the expanded and heads up states. 12084 */ 12085 public static final int DECORATION_PARTIAL = 2; 12086 12087 public static int VIEW_TYPE_UNSPECIFIED = 0; 12088 public static int VIEW_TYPE_NORMAL = 1; 12089 public static int VIEW_TYPE_BIG = 2; 12090 public static int VIEW_TYPE_HEADS_UP = 3; 12091 public static int VIEW_TYPE_MINIMIZED = 4; // header only for minimized state 12092 public static int VIEW_TYPE_PUBLIC = 5; // header only for automatic public version 12093 public static int VIEW_TYPE_GROUP_HEADER = 6; // header only for top of group 12094 12095 int mViewType = VIEW_TYPE_UNSPECIFIED; 12096 boolean mHeaderless; 12097 boolean mHideAppName; 12098 boolean mHideTitle; 12099 boolean mHideSubText; 12100 boolean mHideTime; 12101 boolean mHideActions; 12102 boolean mHideProgress; 12103 boolean mHideSnoozeButton; 12104 boolean mHideLeftIcon; 12105 boolean mHideRightIcon; 12106 Icon mPromotedPicture; 12107 boolean mCallStyleActions; 12108 boolean mAllowTextWithProgress; 12109 int mTitleViewId; 12110 int mTextViewId; 12111 CharSequence title; 12112 CharSequence text; 12113 CharSequence headerTextSecondary; 12114 CharSequence summaryText; 12115 int maxRemoteInputHistory = Style.MAX_REMOTE_INPUT_HISTORY_LINES; 12116 boolean allowColorization = true; 12117 boolean mHighlightExpander = false; 12118 reset()12119 final StandardTemplateParams reset() { 12120 mViewType = VIEW_TYPE_UNSPECIFIED; 12121 mHeaderless = false; 12122 mHideAppName = false; 12123 mHideTitle = false; 12124 mHideSubText = false; 12125 mHideTime = false; 12126 mHideActions = false; 12127 mHideProgress = false; 12128 mHideSnoozeButton = false; 12129 mHideLeftIcon = false; 12130 mHideRightIcon = false; 12131 mPromotedPicture = null; 12132 mCallStyleActions = false; 12133 mAllowTextWithProgress = false; 12134 mTitleViewId = R.id.title; 12135 mTextViewId = R.id.text; 12136 title = null; 12137 text = null; 12138 summaryText = null; 12139 headerTextSecondary = null; 12140 maxRemoteInputHistory = Style.MAX_REMOTE_INPUT_HISTORY_LINES; 12141 allowColorization = true; 12142 mHighlightExpander = false; 12143 return this; 12144 } 12145 hasTitle()12146 final boolean hasTitle() { 12147 return !TextUtils.isEmpty(title) && !mHideTitle; 12148 } 12149 viewType(int viewType)12150 final StandardTemplateParams viewType(int viewType) { 12151 mViewType = viewType; 12152 return this; 12153 } 12154 headerless(boolean headerless)12155 public StandardTemplateParams headerless(boolean headerless) { 12156 mHeaderless = headerless; 12157 return this; 12158 } 12159 hideAppName(boolean hideAppName)12160 public StandardTemplateParams hideAppName(boolean hideAppName) { 12161 mHideAppName = hideAppName; 12162 return this; 12163 } 12164 hideSubText(boolean hideSubText)12165 public StandardTemplateParams hideSubText(boolean hideSubText) { 12166 mHideSubText = hideSubText; 12167 return this; 12168 } 12169 hideTime(boolean hideTime)12170 public StandardTemplateParams hideTime(boolean hideTime) { 12171 mHideTime = hideTime; 12172 return this; 12173 } 12174 hideActions(boolean hideActions)12175 final StandardTemplateParams hideActions(boolean hideActions) { 12176 this.mHideActions = hideActions; 12177 return this; 12178 } 12179 hideProgress(boolean hideProgress)12180 final StandardTemplateParams hideProgress(boolean hideProgress) { 12181 this.mHideProgress = hideProgress; 12182 return this; 12183 } 12184 hideTitle(boolean hideTitle)12185 final StandardTemplateParams hideTitle(boolean hideTitle) { 12186 this.mHideTitle = hideTitle; 12187 return this; 12188 } 12189 callStyleActions(boolean callStyleActions)12190 final StandardTemplateParams callStyleActions(boolean callStyleActions) { 12191 this.mCallStyleActions = callStyleActions; 12192 return this; 12193 } 12194 allowTextWithProgress(boolean allowTextWithProgress)12195 final StandardTemplateParams allowTextWithProgress(boolean allowTextWithProgress) { 12196 this.mAllowTextWithProgress = allowTextWithProgress; 12197 return this; 12198 } 12199 hideSnoozeButton(boolean hideSnoozeButton)12200 final StandardTemplateParams hideSnoozeButton(boolean hideSnoozeButton) { 12201 this.mHideSnoozeButton = hideSnoozeButton; 12202 return this; 12203 } 12204 promotedPicture(Icon promotedPicture)12205 final StandardTemplateParams promotedPicture(Icon promotedPicture) { 12206 this.mPromotedPicture = promotedPicture; 12207 return this; 12208 } 12209 titleViewId(int titleViewId)12210 public StandardTemplateParams titleViewId(int titleViewId) { 12211 mTitleViewId = titleViewId; 12212 return this; 12213 } 12214 textViewId(int textViewId)12215 public StandardTemplateParams textViewId(int textViewId) { 12216 mTextViewId = textViewId; 12217 return this; 12218 } 12219 title(CharSequence title)12220 final StandardTemplateParams title(CharSequence title) { 12221 this.title = title; 12222 return this; 12223 } 12224 text(CharSequence text)12225 final StandardTemplateParams text(CharSequence text) { 12226 this.text = text; 12227 return this; 12228 } 12229 summaryText(CharSequence text)12230 final StandardTemplateParams summaryText(CharSequence text) { 12231 this.summaryText = text; 12232 return this; 12233 } 12234 headerTextSecondary(CharSequence text)12235 final StandardTemplateParams headerTextSecondary(CharSequence text) { 12236 this.headerTextSecondary = text; 12237 return this; 12238 } 12239 12240 hideLeftIcon(boolean hideLeftIcon)12241 final StandardTemplateParams hideLeftIcon(boolean hideLeftIcon) { 12242 this.mHideLeftIcon = hideLeftIcon; 12243 return this; 12244 } 12245 hideRightIcon(boolean hideRightIcon)12246 final StandardTemplateParams hideRightIcon(boolean hideRightIcon) { 12247 this.mHideRightIcon = hideRightIcon; 12248 return this; 12249 } 12250 disallowColorization()12251 final StandardTemplateParams disallowColorization() { 12252 this.allowColorization = false; 12253 return this; 12254 } 12255 highlightExpander(boolean highlight)12256 final StandardTemplateParams highlightExpander(boolean highlight) { 12257 this.mHighlightExpander = highlight; 12258 return this; 12259 } 12260 fillTextsFrom(Builder b)12261 final StandardTemplateParams fillTextsFrom(Builder b) { 12262 Bundle extras = b.mN.extras; 12263 this.title = b.processLegacyText(extras.getCharSequence(EXTRA_TITLE)); 12264 this.text = b.processLegacyText(extras.getCharSequence(EXTRA_TEXT)); 12265 this.summaryText = extras.getCharSequence(EXTRA_SUB_TEXT); 12266 return this; 12267 } 12268 12269 /** 12270 * Set the maximum lines of remote input history lines allowed. 12271 * @param maxRemoteInputHistory The number of lines. 12272 * @return The builder for method chaining. 12273 */ setMaxRemoteInputHistory(int maxRemoteInputHistory)12274 public StandardTemplateParams setMaxRemoteInputHistory(int maxRemoteInputHistory) { 12275 this.maxRemoteInputHistory = maxRemoteInputHistory; 12276 return this; 12277 } 12278 decorationType(int decorationType)12279 public StandardTemplateParams decorationType(int decorationType) { 12280 hideTitle(true); 12281 // Minimally decorated custom views do not show certain pieces of chrome that have 12282 // always been shown when using DecoratedCustomViewStyle. 12283 boolean hideOtherFields = decorationType <= DECORATION_MINIMAL; 12284 hideLeftIcon(false); // The left icon decoration is better than showing nothing. 12285 hideRightIcon(hideOtherFields); 12286 hideProgress(hideOtherFields); 12287 hideActions(hideOtherFields); 12288 return this; 12289 } 12290 } 12291 12292 /** 12293 * A utility which stores and calculates the palette of colors used to color notifications. 12294 * @hide 12295 */ 12296 @VisibleForTesting 12297 public static class Colors { 12298 private int mPaletteIsForRawColor = COLOR_INVALID; 12299 private boolean mPaletteIsForColorized = false; 12300 private boolean mPaletteIsForNightMode = false; 12301 // The following colors are the palette 12302 private int mBackgroundColor = COLOR_INVALID; 12303 private int mProtectionColor = COLOR_INVALID; 12304 private int mPrimaryTextColor = COLOR_INVALID; 12305 private int mSecondaryTextColor = COLOR_INVALID; 12306 private int mPrimaryAccentColor = COLOR_INVALID; 12307 private int mSecondaryAccentColor = COLOR_INVALID; 12308 private int mErrorColor = COLOR_INVALID; 12309 private int mContrastColor = COLOR_INVALID; 12310 private int mRippleAlpha = 0x33; 12311 12312 /** 12313 * A utility for obtaining a TypedArray of the given DayNight-styled attributes, which 12314 * returns null when the context is a mock with no theme. 12315 * 12316 * NOTE: Calling this method is expensive, as creating a new ContextThemeWrapper 12317 * instances can allocate as much as 5MB of memory, so its important to call this method 12318 * only when necessary, getting as many attributes as possible from each call. 12319 * 12320 * @see Resources.Theme#obtainStyledAttributes(int[]) 12321 */ 12322 @Nullable obtainDayNightAttributes(@onNull Context ctx, @NonNull @StyleableRes int[] attrs)12323 private static TypedArray obtainDayNightAttributes(@NonNull Context ctx, 12324 @NonNull @StyleableRes int[] attrs) { 12325 // when testing, the mock context may have no theme 12326 if (ctx.getTheme() == null) { 12327 return null; 12328 } 12329 Resources.Theme theme = new ContextThemeWrapper(ctx, 12330 R.style.Theme_DeviceDefault_DayNight).getTheme(); 12331 return theme.obtainStyledAttributes(attrs); 12332 } 12333 12334 /** A null-safe wrapper of TypedArray.getColor because mocks return null */ getColor(@ullable TypedArray ta, int index, @ColorInt int defValue)12335 private static @ColorInt int getColor(@Nullable TypedArray ta, int index, 12336 @ColorInt int defValue) { 12337 return ta == null ? defValue : ta.getColor(index, defValue); 12338 } 12339 12340 /** 12341 * Resolve the palette. If the inputs have not changed, this will be a no-op. 12342 * This does not handle invalidating the resolved colors when the context itself changes, 12343 * because that case does not happen in the current notification inflation pipeline; we will 12344 * recreate a new builder (and thus a new palette) when reinflating notifications for a new 12345 * theme (admittedly, we do the same for night mode, but that's easy to check). 12346 * 12347 * @param ctx the builder context. 12348 * @param rawColor the notification's color; may be COLOR_DEFAULT, but may never have alpha. 12349 * @param isColorized whether the notification is colorized. 12350 * @param nightMode whether the UI is in night mode. 12351 */ resolvePalette(Context ctx, int rawColor, boolean isColorized, boolean nightMode)12352 public void resolvePalette(Context ctx, int rawColor, 12353 boolean isColorized, boolean nightMode) { 12354 if (mPaletteIsForRawColor == rawColor 12355 && mPaletteIsForColorized == isColorized 12356 && mPaletteIsForNightMode == nightMode) { 12357 return; 12358 } 12359 mPaletteIsForRawColor = rawColor; 12360 mPaletteIsForColorized = isColorized; 12361 mPaletteIsForNightMode = nightMode; 12362 12363 if (isColorized) { 12364 if (rawColor == COLOR_DEFAULT) { 12365 int[] attrs = {R.attr.colorAccentTertiary}; 12366 try (TypedArray ta = obtainDayNightAttributes(ctx, attrs)) { 12367 mBackgroundColor = getColor(ta, 0, Color.WHITE); 12368 } 12369 } else { 12370 mBackgroundColor = rawColor; 12371 } 12372 mProtectionColor = COLOR_INVALID; // filled in at the end 12373 mPrimaryTextColor = ContrastColorUtil.findAlphaToMeetContrast( 12374 ContrastColorUtil.resolvePrimaryColor(ctx, mBackgroundColor, nightMode), 12375 mBackgroundColor, 4.5); 12376 mSecondaryTextColor = ContrastColorUtil.findAlphaToMeetContrast( 12377 ContrastColorUtil.resolveSecondaryColor(ctx, mBackgroundColor, nightMode), 12378 mBackgroundColor, 4.5); 12379 mContrastColor = mPrimaryTextColor; 12380 mPrimaryAccentColor = mPrimaryTextColor; 12381 mSecondaryAccentColor = mSecondaryTextColor; 12382 mErrorColor = mPrimaryTextColor; 12383 mRippleAlpha = 0x33; 12384 } else { 12385 int[] attrs = { 12386 R.attr.colorSurface, 12387 R.attr.colorBackgroundFloating, 12388 R.attr.textColorPrimary, 12389 R.attr.textColorSecondary, 12390 R.attr.colorAccent, 12391 R.attr.colorAccentSecondary, 12392 R.attr.colorError, 12393 R.attr.colorControlHighlight 12394 }; 12395 try (TypedArray ta = obtainDayNightAttributes(ctx, attrs)) { 12396 mBackgroundColor = getColor(ta, 0, nightMode ? Color.BLACK : Color.WHITE); 12397 mProtectionColor = getColor(ta, 1, COLOR_INVALID); 12398 mPrimaryTextColor = getColor(ta, 2, COLOR_INVALID); 12399 mSecondaryTextColor = getColor(ta, 3, COLOR_INVALID); 12400 mPrimaryAccentColor = getColor(ta, 4, COLOR_INVALID); 12401 mSecondaryAccentColor = getColor(ta, 5, COLOR_INVALID); 12402 mErrorColor = getColor(ta, 6, COLOR_INVALID); 12403 mRippleAlpha = Color.alpha(getColor(ta, 7, 0x33ffffff)); 12404 } 12405 mContrastColor = calculateContrastColor(ctx, rawColor, mPrimaryAccentColor, 12406 mBackgroundColor, nightMode); 12407 12408 // make sure every color has a valid value 12409 if (mPrimaryTextColor == COLOR_INVALID) { 12410 mPrimaryTextColor = ContrastColorUtil.resolvePrimaryColor( 12411 ctx, mBackgroundColor, nightMode); 12412 } 12413 if (mSecondaryTextColor == COLOR_INVALID) { 12414 mSecondaryTextColor = ContrastColorUtil.resolveSecondaryColor( 12415 ctx, mBackgroundColor, nightMode); 12416 } 12417 if (mPrimaryAccentColor == COLOR_INVALID) { 12418 mPrimaryAccentColor = mContrastColor; 12419 } 12420 if (mSecondaryAccentColor == COLOR_INVALID) { 12421 mSecondaryAccentColor = mContrastColor; 12422 } 12423 if (mErrorColor == COLOR_INVALID) { 12424 mErrorColor = mPrimaryTextColor; 12425 } 12426 } 12427 // make sure every color has a valid value 12428 if (mProtectionColor == COLOR_INVALID) { 12429 mProtectionColor = ColorUtils.blendARGB(mPrimaryTextColor, mBackgroundColor, 0.8f); 12430 } 12431 } 12432 12433 /** calculates the contrast color for the non-colorized notifications */ calculateContrastColor(Context ctx, @ColorInt int rawColor, @ColorInt int accentColor, @ColorInt int backgroundColor, boolean nightMode)12434 private static @ColorInt int calculateContrastColor(Context ctx, @ColorInt int rawColor, 12435 @ColorInt int accentColor, @ColorInt int backgroundColor, boolean nightMode) { 12436 int color; 12437 if (rawColor == COLOR_DEFAULT) { 12438 color = accentColor; 12439 if (color == COLOR_INVALID) { 12440 color = ContrastColorUtil.resolveDefaultColor(ctx, backgroundColor, nightMode); 12441 } 12442 } else { 12443 color = ContrastColorUtil.resolveContrastColor(ctx, rawColor, backgroundColor, 12444 nightMode); 12445 } 12446 return flattenAlpha(color, backgroundColor); 12447 } 12448 12449 /** remove any alpha by manually blending it with the given background. */ flattenAlpha(@olorInt int color, @ColorInt int background)12450 private static @ColorInt int flattenAlpha(@ColorInt int color, @ColorInt int background) { 12451 return Color.alpha(color) == 0xff ? color 12452 : ContrastColorUtil.compositeColors(color, background); 12453 } 12454 12455 /** @return the notification's background color */ getBackgroundColor()12456 public @ColorInt int getBackgroundColor() { 12457 return mBackgroundColor; 12458 } 12459 12460 /** 12461 * @return the "surface protection" color from the theme, 12462 * or a variant of the normal background color when colorized. 12463 */ getProtectionColor()12464 public @ColorInt int getProtectionColor() { 12465 return mProtectionColor; 12466 } 12467 12468 /** @return the color for the most prominent text */ getPrimaryTextColor()12469 public @ColorInt int getPrimaryTextColor() { 12470 return mPrimaryTextColor; 12471 } 12472 12473 /** @return the color for less prominent text */ getSecondaryTextColor()12474 public @ColorInt int getSecondaryTextColor() { 12475 return mSecondaryTextColor; 12476 } 12477 12478 /** @return the theme's accent color for colored UI elements. */ getPrimaryAccentColor()12479 public @ColorInt int getPrimaryAccentColor() { 12480 return mPrimaryAccentColor; 12481 } 12482 12483 /** @return the theme's secondary accent color for colored UI elements. */ getSecondaryAccentColor()12484 public @ColorInt int getSecondaryAccentColor() { 12485 return mSecondaryAccentColor; 12486 } 12487 12488 /** 12489 * @return the contrast-adjusted version of the color provided by the app, or the 12490 * primary text color when colorized. 12491 */ getContrastColor()12492 public @ColorInt int getContrastColor() { 12493 return mContrastColor; 12494 } 12495 12496 /** @return the theme's error color, or the primary text color when colorized. */ getErrorColor()12497 public @ColorInt int getErrorColor() { 12498 return mErrorColor; 12499 } 12500 12501 /** @return the alpha component of the current theme's control highlight color. */ getRippleAlpha()12502 public int getRippleAlpha() { 12503 return mRippleAlpha; 12504 } 12505 } 12506 } 12507