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 com.android.internal.util.ContrastColorUtil.satisfiesTextContrast; 24 25 import android.annotation.ColorInt; 26 import android.annotation.DimenRes; 27 import android.annotation.Dimension; 28 import android.annotation.DrawableRes; 29 import android.annotation.IdRes; 30 import android.annotation.IntDef; 31 import android.annotation.NonNull; 32 import android.annotation.Nullable; 33 import android.annotation.RequiresPermission; 34 import android.annotation.SdkConstant; 35 import android.annotation.SdkConstant.SdkConstantType; 36 import android.annotation.SuppressLint; 37 import android.annotation.SystemApi; 38 import android.compat.annotation.UnsupportedAppUsage; 39 import android.content.Context; 40 import android.content.Intent; 41 import android.content.LocusId; 42 import android.content.pm.ApplicationInfo; 43 import android.content.pm.PackageManager; 44 import android.content.pm.PackageManager.NameNotFoundException; 45 import android.content.pm.ShortcutInfo; 46 import android.content.res.ColorStateList; 47 import android.content.res.Configuration; 48 import android.content.res.Resources; 49 import android.content.res.TypedArray; 50 import android.graphics.Bitmap; 51 import android.graphics.Canvas; 52 import android.graphics.Color; 53 import android.graphics.PorterDuff; 54 import android.graphics.drawable.Drawable; 55 import android.graphics.drawable.Icon; 56 import android.media.AudioAttributes; 57 import android.media.AudioManager; 58 import android.media.PlayerBase; 59 import android.media.session.MediaSession; 60 import android.net.Uri; 61 import android.os.BadParcelableException; 62 import android.os.Build; 63 import android.os.Bundle; 64 import android.os.IBinder; 65 import android.os.Parcel; 66 import android.os.Parcelable; 67 import android.os.SystemClock; 68 import android.os.SystemProperties; 69 import android.os.UserHandle; 70 import android.text.BidiFormatter; 71 import android.text.SpannableStringBuilder; 72 import android.text.Spanned; 73 import android.text.TextUtils; 74 import android.text.style.AbsoluteSizeSpan; 75 import android.text.style.CharacterStyle; 76 import android.text.style.ForegroundColorSpan; 77 import android.text.style.RelativeSizeSpan; 78 import android.text.style.TextAppearanceSpan; 79 import android.util.ArraySet; 80 import android.util.Log; 81 import android.util.Pair; 82 import android.util.SparseArray; 83 import android.util.proto.ProtoOutputStream; 84 import android.view.Gravity; 85 import android.view.NotificationHeaderView; 86 import android.view.View; 87 import android.view.ViewGroup; 88 import android.view.contentcapture.ContentCaptureContext; 89 import android.widget.ProgressBar; 90 import android.widget.RemoteViews; 91 92 import com.android.internal.R; 93 import com.android.internal.annotations.VisibleForTesting; 94 import com.android.internal.util.ArrayUtils; 95 import com.android.internal.util.ContrastColorUtil; 96 97 import java.lang.annotation.Retention; 98 import java.lang.annotation.RetentionPolicy; 99 import java.lang.reflect.Array; 100 import java.lang.reflect.Constructor; 101 import java.util.ArrayList; 102 import java.util.Collections; 103 import java.util.List; 104 import java.util.Objects; 105 import java.util.Set; 106 import java.util.function.Consumer; 107 108 /** 109 * A class that represents how a persistent notification is to be presented to 110 * the user using the {@link android.app.NotificationManager}. 111 * 112 * <p>The {@link Notification.Builder Notification.Builder} has been added to make it 113 * easier to construct Notifications.</p> 114 * 115 * <div class="special reference"> 116 * <h3>Developer Guides</h3> 117 * <p>For a guide to creating notifications, read the 118 * <a href="{@docRoot}guide/topics/ui/notifiers/notifications.html">Status Bar Notifications</a> 119 * developer guide.</p> 120 * </div> 121 */ 122 public class Notification implements Parcelable 123 { 124 private static final String TAG = "Notification"; 125 126 /** 127 * An activity that provides a user interface for adjusting notification preferences for its 128 * containing application. 129 */ 130 @SdkConstant(SdkConstantType.INTENT_CATEGORY) 131 public static final String INTENT_CATEGORY_NOTIFICATION_PREFERENCES 132 = "android.intent.category.NOTIFICATION_PREFERENCES"; 133 134 /** 135 * Optional extra for {@link #INTENT_CATEGORY_NOTIFICATION_PREFERENCES}. If provided, will 136 * contain a {@link NotificationChannel#getId() channel id} that can be used to narrow down 137 * what settings should be shown in the target app. 138 */ 139 public static final String EXTRA_CHANNEL_ID = "android.intent.extra.CHANNEL_ID"; 140 141 /** 142 * Optional extra for {@link #INTENT_CATEGORY_NOTIFICATION_PREFERENCES}. If provided, will 143 * contain a {@link NotificationChannelGroup#getId() group id} that can be used to narrow down 144 * what settings should be shown in the target app. 145 */ 146 public static final String EXTRA_CHANNEL_GROUP_ID = "android.intent.extra.CHANNEL_GROUP_ID"; 147 148 /** 149 * Optional extra for {@link #INTENT_CATEGORY_NOTIFICATION_PREFERENCES}. If provided, will 150 * contain the tag provided to {@link NotificationManager#notify(String, int, Notification)} 151 * that can be used to narrow down what settings should be shown in the target app. 152 */ 153 public static final String EXTRA_NOTIFICATION_TAG = "android.intent.extra.NOTIFICATION_TAG"; 154 155 /** 156 * Optional extra for {@link #INTENT_CATEGORY_NOTIFICATION_PREFERENCES}. If provided, will 157 * contain the id provided to {@link NotificationManager#notify(String, int, Notification)} 158 * that can be used to narrow down what settings should be shown in the target app. 159 */ 160 public static final String EXTRA_NOTIFICATION_ID = "android.intent.extra.NOTIFICATION_ID"; 161 162 /** 163 * Use all default values (where applicable). 164 */ 165 public static final int DEFAULT_ALL = ~0; 166 167 /** 168 * Use the default notification sound. This will ignore any given 169 * {@link #sound}. 170 * 171 * <p> 172 * A notification that is noisy is more likely to be presented as a heads-up notification. 173 * </p> 174 * 175 * @see #defaults 176 */ 177 178 public static final int DEFAULT_SOUND = 1; 179 180 /** 181 * Use the default notification vibrate. This will ignore any given 182 * {@link #vibrate}. Using phone vibration requires the 183 * {@link android.Manifest.permission#VIBRATE VIBRATE} permission. 184 * 185 * <p> 186 * A notification that vibrates is more likely to be presented as a heads-up notification. 187 * </p> 188 * 189 * @see #defaults 190 */ 191 192 public static final int DEFAULT_VIBRATE = 2; 193 194 /** 195 * Use the default notification lights. This will ignore the 196 * {@link #FLAG_SHOW_LIGHTS} bit, and {@link #ledARGB}, {@link #ledOffMS}, or 197 * {@link #ledOnMS}. 198 * 199 * @see #defaults 200 */ 201 202 public static final int DEFAULT_LIGHTS = 4; 203 204 /** 205 * Maximum length of CharSequences accepted by Builder and friends. 206 * 207 * <p> 208 * Avoids spamming the system with overly large strings such as full e-mails. 209 */ 210 private static final int MAX_CHARSEQUENCE_LENGTH = 5 * 1024; 211 212 /** 213 * Maximum entries of reply text that are accepted by Builder and friends. 214 */ 215 private static final int MAX_REPLY_HISTORY = 5; 216 217 /** 218 * Maximum number of (generic) action buttons in a notification (contextual action buttons are 219 * handled separately). 220 * @hide 221 */ 222 public static final int MAX_ACTION_BUTTONS = 3; 223 224 /** 225 * If the notification contained an unsent draft for a RemoteInput when the user clicked on it, 226 * we're adding the draft as a String extra to the {@link #contentIntent} using this key. 227 * 228 * <p>Apps may use this extra to prepopulate text fields in the app, where the user usually 229 * sends messages.</p> 230 */ 231 public static final String EXTRA_REMOTE_INPUT_DRAFT = "android.remoteInputDraft"; 232 233 /** 234 * A timestamp related to this notification, in milliseconds since the epoch. 235 * 236 * Default value: {@link System#currentTimeMillis() Now}. 237 * 238 * Choose a timestamp that will be most relevant to the user. For most finite events, this 239 * corresponds to the time the event happened (or will happen, in the case of events that have 240 * yet to occur but about which the user is being informed). Indefinite events should be 241 * timestamped according to when the activity began. 242 * 243 * Some examples: 244 * 245 * <ul> 246 * <li>Notification of a new chat message should be stamped when the message was received.</li> 247 * <li>Notification of an ongoing file download (with a progress bar, for example) should be stamped when the download started.</li> 248 * <li>Notification of a completed file download should be stamped when the download finished.</li> 249 * <li>Notification of an upcoming meeting should be stamped with the time the meeting will begin (that is, in the future).</li> 250 * <li>Notification of an ongoing stopwatch (increasing timer) should be stamped with the watch's start time. 251 * <li>Notification of an ongoing countdown timer should be stamped with the timer's end time. 252 * </ul> 253 * 254 * For apps targeting {@link android.os.Build.VERSION_CODES#N} and above, this time is not shown 255 * anymore by default and must be opted into by using 256 * {@link android.app.Notification.Builder#setShowWhen(boolean)} 257 */ 258 public long when; 259 260 /** 261 * The creation time of the notification 262 */ 263 private long creationTime; 264 265 /** 266 * The resource id of a drawable to use as the icon in the status bar. 267 * 268 * @deprecated Use {@link Builder#setSmallIcon(Icon)} instead. 269 */ 270 @Deprecated 271 @DrawableRes 272 public int icon; 273 274 /** 275 * If the icon in the status bar is to have more than one level, you can set this. Otherwise, 276 * leave it at its default value of 0. 277 * 278 * @see android.widget.ImageView#setImageLevel 279 * @see android.graphics.drawable.Drawable#setLevel 280 */ 281 public int iconLevel; 282 283 /** 284 * The number of events that this notification represents. For example, in a new mail 285 * notification, this could be the number of unread messages. 286 * 287 * The system may or may not use this field to modify the appearance of the notification. 288 * Starting with {@link android.os.Build.VERSION_CODES#O}, the number may be displayed as a 289 * badge icon in Launchers that support badging. 290 */ 291 public int number = 0; 292 293 /** 294 * The intent to execute when the expanded status entry is clicked. If 295 * this is an activity, it must include the 296 * {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK} flag, which requires 297 * that you take care of task management as described in the 298 * <a href="{@docRoot}guide/topics/fundamentals/tasks-and-back-stack.html">Tasks and Back 299 * Stack</a> document. In particular, make sure to read the notification section 300 * <a href="{@docRoot}guide/topics/ui/notifiers/notifications.html#HandlingNotifications">Handling 301 * Notifications</a> for the correct ways to launch an application from a 302 * notification. 303 */ 304 public PendingIntent contentIntent; 305 306 /** 307 * The intent to execute when the notification is explicitly dismissed by the user, either with 308 * the "Clear All" button or by swiping it away individually. 309 * 310 * This probably shouldn't be launching an activity since several of those will be sent 311 * at the same time. 312 */ 313 public PendingIntent deleteIntent; 314 315 /** 316 * An intent to launch instead of posting the notification to the status bar. 317 * 318 * <p> 319 * The system UI may choose to display a heads-up notification, instead of 320 * launching this intent, while the user is using the device. 321 * </p> 322 * 323 * @see Notification.Builder#setFullScreenIntent 324 */ 325 public PendingIntent fullScreenIntent; 326 327 /** 328 * Text that summarizes this notification for accessibility services. 329 * 330 * As of the L release, this text is no longer shown on screen, but it is still useful to 331 * accessibility services (where it serves as an audible announcement of the notification's 332 * appearance). 333 * 334 * @see #tickerView 335 */ 336 public CharSequence tickerText; 337 338 /** 339 * Formerly, a view showing the {@link #tickerText}. 340 * 341 * No longer displayed in the status bar as of API 21. 342 */ 343 @Deprecated 344 public RemoteViews tickerView; 345 346 /** 347 * The view that will represent this notification in the notification list (which is pulled 348 * down from the status bar). 349 * 350 * As of N, this field may be null. The notification view is determined by the inputs 351 * to {@link Notification.Builder}; a custom RemoteViews can optionally be 352 * supplied with {@link Notification.Builder#setCustomContentView(RemoteViews)}. 353 */ 354 @Deprecated 355 public RemoteViews contentView; 356 357 /** 358 * A large-format version of {@link #contentView}, giving the Notification an 359 * opportunity to show more detail. The system UI may choose to show this 360 * instead of the normal content view at its discretion. 361 * 362 * As of N, this field may be null. The expanded notification view is determined by the 363 * inputs to {@link Notification.Builder}; a custom RemoteViews can optionally be 364 * supplied with {@link Notification.Builder#setCustomBigContentView(RemoteViews)}. 365 */ 366 @Deprecated 367 public RemoteViews bigContentView; 368 369 370 /** 371 * A medium-format version of {@link #contentView}, providing the Notification an 372 * opportunity to add action buttons to contentView. At its discretion, the system UI may 373 * choose to show this as a heads-up notification, which will pop up so the user can see 374 * it without leaving their current activity. 375 * 376 * As of N, this field may be null. The heads-up notification view is determined by the 377 * inputs to {@link Notification.Builder}; a custom RemoteViews can optionally be 378 * supplied with {@link Notification.Builder#setCustomHeadsUpContentView(RemoteViews)}. 379 */ 380 @Deprecated 381 public RemoteViews headsUpContentView; 382 383 private boolean mUsesStandardHeader; 384 385 private static final ArraySet<Integer> STANDARD_LAYOUTS = new ArraySet<>(); 386 static { 387 STANDARD_LAYOUTS.add(R.layout.notification_template_material_base); 388 STANDARD_LAYOUTS.add(R.layout.notification_template_material_big_base); 389 STANDARD_LAYOUTS.add(R.layout.notification_template_material_big_picture); 390 STANDARD_LAYOUTS.add(R.layout.notification_template_material_big_text); 391 STANDARD_LAYOUTS.add(R.layout.notification_template_material_inbox); 392 STANDARD_LAYOUTS.add(R.layout.notification_template_material_messaging); 393 STANDARD_LAYOUTS.add(R.layout.notification_template_material_conversation); 394 STANDARD_LAYOUTS.add(R.layout.notification_template_material_media); 395 STANDARD_LAYOUTS.add(R.layout.notification_template_material_big_media); 396 STANDARD_LAYOUTS.add(R.layout.notification_template_header); 397 } 398 399 /** 400 * A large bitmap to be shown in the notification content area. 401 * 402 * @deprecated Use {@link Builder#setLargeIcon(Icon)} instead. 403 */ 404 @Deprecated 405 public Bitmap largeIcon; 406 407 /** 408 * The sound to play. 409 * 410 * <p> 411 * A notification that is noisy is more likely to be presented as a heads-up notification. 412 * </p> 413 * 414 * <p> 415 * To play the default notification sound, see {@link #defaults}. 416 * </p> 417 * @deprecated use {@link NotificationChannel#getSound()}. 418 */ 419 @Deprecated 420 public Uri sound; 421 422 /** 423 * Use this constant as the value for audioStreamType to request that 424 * the default stream type for notifications be used. Currently the 425 * default stream type is {@link AudioManager#STREAM_NOTIFICATION}. 426 * 427 * @deprecated Use {@link NotificationChannel#getAudioAttributes()} instead. 428 */ 429 @Deprecated 430 public static final int STREAM_DEFAULT = -1; 431 432 /** 433 * The audio stream type to use when playing the sound. 434 * Should be one of the STREAM_ constants from 435 * {@link android.media.AudioManager}. 436 * 437 * @deprecated Use {@link #audioAttributes} instead. 438 */ 439 @Deprecated 440 public int audioStreamType = STREAM_DEFAULT; 441 442 /** 443 * The default value of {@link #audioAttributes}. 444 */ 445 public static final AudioAttributes AUDIO_ATTRIBUTES_DEFAULT = new AudioAttributes.Builder() 446 .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) 447 .setUsage(AudioAttributes.USAGE_NOTIFICATION) 448 .build(); 449 450 /** 451 * The {@link AudioAttributes audio attributes} to use when playing the sound. 452 * 453 * @deprecated use {@link NotificationChannel#getAudioAttributes()} instead. 454 */ 455 @Deprecated 456 public AudioAttributes audioAttributes = AUDIO_ATTRIBUTES_DEFAULT; 457 458 /** 459 * The pattern with which to vibrate. 460 * 461 * <p> 462 * To vibrate the default pattern, see {@link #defaults}. 463 * </p> 464 * 465 * @see android.os.Vibrator#vibrate(long[],int) 466 * @deprecated use {@link NotificationChannel#getVibrationPattern()}. 467 */ 468 @Deprecated 469 public long[] vibrate; 470 471 /** 472 * The color of the led. The hardware will do its best approximation. 473 * 474 * @see #FLAG_SHOW_LIGHTS 475 * @see #flags 476 * @deprecated use {@link NotificationChannel#shouldShowLights()}. 477 */ 478 @ColorInt 479 @Deprecated 480 public int ledARGB; 481 482 /** 483 * The number of milliseconds for the LED to be on while it's flashing. 484 * The hardware will do its best approximation. 485 * 486 * @see #FLAG_SHOW_LIGHTS 487 * @see #flags 488 * @deprecated use {@link NotificationChannel#shouldShowLights()}. 489 */ 490 @Deprecated 491 public int ledOnMS; 492 493 /** 494 * The number of milliseconds for the LED to be off while it's flashing. 495 * The hardware will do its best approximation. 496 * 497 * @see #FLAG_SHOW_LIGHTS 498 * @see #flags 499 * 500 * @deprecated use {@link NotificationChannel#shouldShowLights()}. 501 */ 502 @Deprecated 503 public int ledOffMS; 504 505 /** 506 * Specifies which values should be taken from the defaults. 507 * <p> 508 * To set, OR the desired from {@link #DEFAULT_SOUND}, 509 * {@link #DEFAULT_VIBRATE}, {@link #DEFAULT_LIGHTS}. For all default 510 * values, use {@link #DEFAULT_ALL}. 511 * </p> 512 * 513 * @deprecated use {@link NotificationChannel#getSound()} and 514 * {@link NotificationChannel#shouldShowLights()} and 515 * {@link NotificationChannel#shouldVibrate()}. 516 */ 517 @Deprecated 518 public int defaults; 519 520 /** 521 * Bit to be bitwise-ored into the {@link #flags} field that should be 522 * set if you want the LED on for this notification. 523 * <ul> 524 * <li>To turn the LED off, pass 0 in the alpha channel for colorARGB 525 * or 0 for both ledOnMS and ledOffMS.</li> 526 * <li>To turn the LED on, pass 1 for ledOnMS and 0 for ledOffMS.</li> 527 * <li>To flash the LED, pass the number of milliseconds that it should 528 * be on and off to ledOnMS and ledOffMS.</li> 529 * </ul> 530 * <p> 531 * Since hardware varies, you are not guaranteed that any of the values 532 * you pass are honored exactly. Use the system defaults if possible 533 * because they will be set to values that work on any given hardware. 534 * <p> 535 * The alpha channel must be set for forward compatibility. 536 * 537 * @deprecated use {@link NotificationChannel#shouldShowLights()}. 538 */ 539 @Deprecated 540 public static final int FLAG_SHOW_LIGHTS = 0x00000001; 541 542 /** 543 * Bit to be bitwise-ored into the {@link #flags} field that should be 544 * set if this notification is in reference to something that is ongoing, 545 * like a phone call. It should not be set if this notification is in 546 * reference to something that happened at a particular point in time, 547 * like a missed phone call. 548 */ 549 public static final int FLAG_ONGOING_EVENT = 0x00000002; 550 551 /** 552 * Bit to be bitwise-ored into the {@link #flags} field that if set, 553 * the audio will be repeated until the notification is 554 * cancelled or the notification window is opened. 555 */ 556 public static final int FLAG_INSISTENT = 0x00000004; 557 558 /** 559 * Bit to be bitwise-ored into the {@link #flags} field that should be 560 * set if you would only like the sound, vibrate and ticker to be played 561 * if the notification was not already showing. 562 */ 563 public static final int FLAG_ONLY_ALERT_ONCE = 0x00000008; 564 565 /** 566 * Bit to be bitwise-ored into the {@link #flags} field that should be 567 * set if the notification should be canceled when it is clicked by the 568 * user. 569 */ 570 public static final int FLAG_AUTO_CANCEL = 0x00000010; 571 572 /** 573 * Bit to be bitwise-ored into the {@link #flags} field that should be 574 * set if the notification should not be canceled when the user clicks 575 * the Clear all button. 576 */ 577 public static final int FLAG_NO_CLEAR = 0x00000020; 578 579 /** 580 * Bit to be bitwise-ored into the {@link #flags} field that should be 581 * set if this notification represents a currently running service. This 582 * will normally be set for you by {@link Service#startForeground}. 583 */ 584 public static final int FLAG_FOREGROUND_SERVICE = 0x00000040; 585 586 /** 587 * Obsolete flag indicating high-priority notifications; use the priority field instead. 588 * 589 * @deprecated Use {@link #priority} with a positive value. 590 */ 591 @Deprecated 592 public static final int FLAG_HIGH_PRIORITY = 0x00000080; 593 594 /** 595 * Bit to be bitswise-ored into the {@link #flags} field that should be 596 * set if this notification is relevant to the current device only 597 * and it is not recommended that it bridge to other devices. 598 */ 599 public static final int FLAG_LOCAL_ONLY = 0x00000100; 600 601 /** 602 * Bit to be bitswise-ored into the {@link #flags} field that should be 603 * set if this notification is the group summary for a group of notifications. 604 * Grouped notifications may display in a cluster or stack on devices which 605 * support such rendering. Requires a group key also be set using {@link Builder#setGroup}. 606 */ 607 public static final int FLAG_GROUP_SUMMARY = 0x00000200; 608 609 /** 610 * Bit to be bitswise-ored into the {@link #flags} field that should be 611 * set if this notification is the group summary for an auto-group of notifications. 612 * 613 * @hide 614 */ 615 @SystemApi 616 public static final int FLAG_AUTOGROUP_SUMMARY = 0x00000400; 617 618 /** 619 * @hide 620 */ 621 public static final int FLAG_CAN_COLORIZE = 0x00000800; 622 623 /** 624 * Bit to be bitswised-ored into the {@link #flags} field that should be 625 * set by the system if this notification is showing as a bubble. 626 * 627 * Applications cannot set this flag directly; they should instead call 628 * {@link Notification.Builder#setBubbleMetadata(BubbleMetadata)} to 629 * request that a notification be displayed as a bubble, and then check 630 * this flag to see whether that request was honored by the system. 631 */ 632 public static final int FLAG_BUBBLE = 0x00001000; 633 634 /** @hide */ 635 @IntDef({FLAG_SHOW_LIGHTS, FLAG_ONGOING_EVENT, FLAG_INSISTENT, FLAG_ONLY_ALERT_ONCE, 636 FLAG_AUTO_CANCEL, FLAG_NO_CLEAR, FLAG_FOREGROUND_SERVICE, FLAG_HIGH_PRIORITY, 637 FLAG_LOCAL_ONLY, FLAG_GROUP_SUMMARY, FLAG_AUTOGROUP_SUMMARY, FLAG_BUBBLE}) 638 @Retention(RetentionPolicy.SOURCE) 639 public @interface NotificationFlags{}; 640 641 public int flags; 642 643 /** @hide */ 644 @IntDef(prefix = { "PRIORITY_" }, value = { 645 PRIORITY_DEFAULT, 646 PRIORITY_LOW, 647 PRIORITY_MIN, 648 PRIORITY_HIGH, 649 PRIORITY_MAX 650 }) 651 @Retention(RetentionPolicy.SOURCE) 652 public @interface Priority {} 653 654 /** 655 * Default notification {@link #priority}. If your application does not prioritize its own 656 * notifications, use this value for all notifications. 657 * 658 * @deprecated use {@link NotificationManager#IMPORTANCE_DEFAULT} instead. 659 */ 660 @Deprecated 661 public static final int PRIORITY_DEFAULT = 0; 662 663 /** 664 * Lower {@link #priority}, for items that are less important. The UI may choose to show these 665 * items smaller, or at a different position in the list, compared with your app's 666 * {@link #PRIORITY_DEFAULT} items. 667 * 668 * @deprecated use {@link NotificationManager#IMPORTANCE_LOW} instead. 669 */ 670 @Deprecated 671 public static final int PRIORITY_LOW = -1; 672 673 /** 674 * Lowest {@link #priority}; these items might not be shown to the user except under special 675 * circumstances, such as detailed notification logs. 676 * 677 * @deprecated use {@link NotificationManager#IMPORTANCE_MIN} instead. 678 */ 679 @Deprecated 680 public static final int PRIORITY_MIN = -2; 681 682 /** 683 * Higher {@link #priority}, for more important notifications or alerts. The UI may choose to 684 * show these items larger, or at a different position in notification lists, compared with 685 * your app's {@link #PRIORITY_DEFAULT} items. 686 * 687 * @deprecated use {@link NotificationManager#IMPORTANCE_HIGH} instead. 688 */ 689 @Deprecated 690 public static final int PRIORITY_HIGH = 1; 691 692 /** 693 * Highest {@link #priority}, for your application's most important items that require the 694 * user's prompt attention or input. 695 * 696 * @deprecated use {@link NotificationManager#IMPORTANCE_HIGH} instead. 697 */ 698 @Deprecated 699 public static final int PRIORITY_MAX = 2; 700 701 /** 702 * Relative priority for this notification. 703 * 704 * Priority is an indication of how much of the user's valuable attention should be consumed by 705 * this notification. Low-priority notifications may be hidden from the user in certain 706 * situations, while the user might be interrupted for a higher-priority notification. The 707 * system will make a determination about how to interpret this priority when presenting 708 * the notification. 709 * 710 * <p> 711 * A notification that is at least {@link #PRIORITY_HIGH} is more likely to be presented 712 * as a heads-up notification. 713 * </p> 714 * 715 * @deprecated use {@link NotificationChannel#getImportance()} instead. 716 */ 717 @Priority 718 @Deprecated 719 public int priority; 720 721 /** 722 * Accent color (an ARGB integer like the constants in {@link android.graphics.Color}) 723 * to be applied by the standard Style templates when presenting this notification. 724 * 725 * The current template design constructs a colorful header image by overlaying the 726 * {@link #icon} image (stenciled in white) atop a field of this color. Alpha components are 727 * ignored. 728 */ 729 @ColorInt 730 public int color = COLOR_DEFAULT; 731 732 /** 733 * Special value of {@link #color} telling the system not to decorate this notification with 734 * any special color but instead use default colors when presenting this notification. 735 */ 736 @ColorInt 737 public static final int COLOR_DEFAULT = 0; // AKA Color.TRANSPARENT 738 739 /** 740 * Special value of {@link #color} used as a place holder for an invalid color. 741 * @hide 742 */ 743 @ColorInt 744 public static final int COLOR_INVALID = 1; 745 746 /** 747 * Sphere of visibility of this notification, which affects how and when the SystemUI reveals 748 * the notification's presence and contents in untrusted situations (namely, on the secure 749 * lockscreen). 750 * 751 * The default level, {@link #VISIBILITY_PRIVATE}, behaves exactly as notifications have always 752 * done on Android: The notification's {@link #icon} and {@link #tickerText} (if available) are 753 * shown in all situations, but the contents are only available if the device is unlocked for 754 * the appropriate user. 755 * 756 * A more permissive policy can be expressed by {@link #VISIBILITY_PUBLIC}; such a notification 757 * can be read even in an "insecure" context (that is, above a secure lockscreen). 758 * To modify the public version of this notification—for example, to redact some portions—see 759 * {@link Builder#setPublicVersion(Notification)}. 760 * 761 * Finally, a notification can be made {@link #VISIBILITY_SECRET}, which will suppress its icon 762 * and ticker until the user has bypassed the lockscreen. 763 */ 764 public @Visibility int visibility; 765 766 /** @hide */ 767 @IntDef(prefix = { "VISIBILITY_" }, value = { 768 VISIBILITY_PUBLIC, 769 VISIBILITY_PRIVATE, 770 VISIBILITY_SECRET, 771 }) 772 @Retention(RetentionPolicy.SOURCE) 773 public @interface Visibility {} 774 775 /** 776 * Notification visibility: Show this notification in its entirety on all lockscreens. 777 * 778 * {@see #visibility} 779 */ 780 public static final int VISIBILITY_PUBLIC = 1; 781 782 /** 783 * Notification visibility: Show this notification on all lockscreens, but conceal sensitive or 784 * private information on secure lockscreens. 785 * 786 * {@see #visibility} 787 */ 788 public static final int VISIBILITY_PRIVATE = 0; 789 790 /** 791 * Notification visibility: Do not reveal any part of this notification on a secure lockscreen. 792 * 793 * {@see #visibility} 794 */ 795 public static final int VISIBILITY_SECRET = -1; 796 797 /** 798 * Notification category: incoming call (voice or video) or similar synchronous communication request. 799 */ 800 public static final String CATEGORY_CALL = "call"; 801 802 /** 803 * Notification category: map turn-by-turn navigation. 804 */ 805 public static final String CATEGORY_NAVIGATION = "navigation"; 806 807 /** 808 * Notification category: incoming direct message (SMS, instant message, etc.). 809 */ 810 public static final String CATEGORY_MESSAGE = "msg"; 811 812 /** 813 * Notification category: asynchronous bulk message (email). 814 */ 815 public static final String CATEGORY_EMAIL = "email"; 816 817 /** 818 * Notification category: calendar event. 819 */ 820 public static final String CATEGORY_EVENT = "event"; 821 822 /** 823 * Notification category: promotion or advertisement. 824 */ 825 public static final String CATEGORY_PROMO = "promo"; 826 827 /** 828 * Notification category: alarm or timer. 829 */ 830 public static final String CATEGORY_ALARM = "alarm"; 831 832 /** 833 * Notification category: progress of a long-running background operation. 834 */ 835 public static final String CATEGORY_PROGRESS = "progress"; 836 837 /** 838 * Notification category: social network or sharing update. 839 */ 840 public static final String CATEGORY_SOCIAL = "social"; 841 842 /** 843 * Notification category: error in background operation or authentication status. 844 */ 845 public static final String CATEGORY_ERROR = "err"; 846 847 /** 848 * Notification category: media transport control for playback. 849 */ 850 public static final String CATEGORY_TRANSPORT = "transport"; 851 852 /** 853 * Notification category: system or device status update. Reserved for system use. 854 */ 855 public static final String CATEGORY_SYSTEM = "sys"; 856 857 /** 858 * Notification category: indication of running background service. 859 */ 860 public static final String CATEGORY_SERVICE = "service"; 861 862 /** 863 * Notification category: a specific, timely recommendation for a single thing. 864 * For example, a news app might want to recommend a news story it believes the user will 865 * want to read next. 866 */ 867 public static final String CATEGORY_RECOMMENDATION = "recommendation"; 868 869 /** 870 * Notification category: ongoing information about device or contextual status. 871 */ 872 public static final String CATEGORY_STATUS = "status"; 873 874 /** 875 * Notification category: user-scheduled reminder. 876 */ 877 public static final String CATEGORY_REMINDER = "reminder"; 878 879 /** 880 * Notification category: extreme car emergencies. 881 * @hide 882 */ 883 @SystemApi 884 public static final String CATEGORY_CAR_EMERGENCY = "car_emergency"; 885 886 /** 887 * Notification category: car warnings. 888 * @hide 889 */ 890 @SystemApi 891 public static final String CATEGORY_CAR_WARNING = "car_warning"; 892 893 /** 894 * Notification category: general car system information. 895 * @hide 896 */ 897 @SystemApi 898 public static final String CATEGORY_CAR_INFORMATION = "car_information"; 899 900 /** 901 * One of the predefined notification categories (see the <code>CATEGORY_*</code> constants) 902 * that best describes this Notification. May be used by the system for ranking and filtering. 903 */ 904 public String category; 905 906 @UnsupportedAppUsage 907 private String mGroupKey; 908 909 /** 910 * Get the key used to group this notification into a cluster or stack 911 * with other notifications on devices which support such rendering. 912 */ getGroup()913 public String getGroup() { 914 return mGroupKey; 915 } 916 917 private String mSortKey; 918 919 /** 920 * Get a sort key that orders this notification among other notifications from the 921 * same package. This can be useful if an external sort was already applied and an app 922 * would like to preserve this. Notifications will be sorted lexicographically using this 923 * value, although providing different priorities in addition to providing sort key may 924 * cause this value to be ignored. 925 * 926 * <p>This sort key can also be used to order members of a notification group. See 927 * {@link Builder#setGroup}. 928 * 929 * @see String#compareTo(String) 930 */ getSortKey()931 public String getSortKey() { 932 return mSortKey; 933 } 934 935 /** 936 * Additional semantic data to be carried around with this Notification. 937 * <p> 938 * The extras keys defined here are intended to capture the original inputs to {@link Builder} 939 * APIs, and are intended to be used by 940 * {@link android.service.notification.NotificationListenerService} implementations to extract 941 * detailed information from notification objects. 942 */ 943 public Bundle extras = new Bundle(); 944 945 /** 946 * All pending intents in the notification as the system needs to be able to access them but 947 * touching the extras bundle in the system process is not safe because the bundle may contain 948 * custom parcelable objects. 949 * 950 * @hide 951 */ 952 @UnsupportedAppUsage 953 public ArraySet<PendingIntent> allPendingIntents; 954 955 /** 956 * Token identifying the notification that is applying doze/bgcheck whitelisting to the 957 * pending intents inside of it, so only those will get the behavior. 958 * 959 * @hide 960 */ 961 private IBinder mWhitelistToken; 962 963 /** 964 * Must be set by a process to start associating tokens with Notification objects 965 * coming in to it. This is set by NotificationManagerService. 966 * 967 * @hide 968 */ 969 static public IBinder processWhitelistToken; 970 971 /** 972 * {@link #extras} key: this is the title of the notification, 973 * as supplied to {@link Builder#setContentTitle(CharSequence)}. 974 */ 975 public static final String EXTRA_TITLE = "android.title"; 976 977 /** 978 * {@link #extras} key: this is the title of the notification when shown in expanded form, 979 * e.g. as supplied to {@link BigTextStyle#setBigContentTitle(CharSequence)}. 980 */ 981 public static final String EXTRA_TITLE_BIG = EXTRA_TITLE + ".big"; 982 983 /** 984 * {@link #extras} key: this is the main text payload, as supplied to 985 * {@link Builder#setContentText(CharSequence)}. 986 */ 987 public static final String EXTRA_TEXT = "android.text"; 988 989 /** 990 * {@link #extras} key: this is a third line of text, as supplied to 991 * {@link Builder#setSubText(CharSequence)}. 992 */ 993 public static final String EXTRA_SUB_TEXT = "android.subText"; 994 995 /** 996 * {@link #extras} key: this is the remote input history, as supplied to 997 * {@link Builder#setRemoteInputHistory(CharSequence[])}. 998 * 999 * Apps can fill this through {@link Builder#setRemoteInputHistory(CharSequence[])} 1000 * with the most recent inputs that have been sent through a {@link RemoteInput} of this 1001 * Notification and are expected to clear it once the it is no longer relevant (e.g. for chat 1002 * notifications once the other party has responded). 1003 * 1004 * The extra with this key is of type CharSequence[] and contains the most recent entry at 1005 * the 0 index, the second most recent at the 1 index, etc. 1006 * 1007 * @see Builder#setRemoteInputHistory(CharSequence[]) 1008 */ 1009 public static final String EXTRA_REMOTE_INPUT_HISTORY = "android.remoteInputHistory"; 1010 1011 1012 /** 1013 * {@link #extras} key: this is a remote input history which can include media messages 1014 * in addition to text, as supplied to 1015 * {@link Builder#setRemoteInputHistory(RemoteInputHistoryItem[])} or 1016 * {@link Builder#setRemoteInputHistory(CharSequence[])}. 1017 * 1018 * SystemUI can populate this through 1019 * {@link Builder#setRemoteInputHistory(RemoteInputHistoryItem[])} with the most recent inputs 1020 * that have been sent through a {@link RemoteInput} of this Notification. These items can 1021 * represent either media content (specified by a URI and a MIME type) or a text message 1022 * (described by a CharSequence). 1023 * 1024 * To maintain compatibility, this can also be set by apps with 1025 * {@link Builder#setRemoteInputHistory(CharSequence[])}, which will create a 1026 * {@link RemoteInputHistoryItem} for each of the provided text-only messages. 1027 * 1028 * The extra with this key is of type {@link RemoteInputHistoryItem[]} and contains the most 1029 * recent entry at the 0 index, the second most recent at the 1 index, etc. 1030 * 1031 * @see Builder#setRemoteInputHistory(RemoteInputHistoryItem[]) 1032 * @hide 1033 */ 1034 public static final String EXTRA_REMOTE_INPUT_HISTORY_ITEMS = "android.remoteInputHistoryItems"; 1035 1036 /** 1037 * {@link #extras} key: boolean as supplied to 1038 * {@link Builder#setShowRemoteInputSpinner(boolean)}. 1039 * 1040 * If set to true, then the view displaying the remote input history from 1041 * {@link Builder#setRemoteInputHistory(CharSequence[])} will have a progress spinner. 1042 * 1043 * @see Builder#setShowRemoteInputSpinner(boolean) 1044 * @hide 1045 */ 1046 public static final String EXTRA_SHOW_REMOTE_INPUT_SPINNER = "android.remoteInputSpinner"; 1047 1048 /** 1049 * {@link #extras} key: boolean as supplied to 1050 * {@link Builder#setHideSmartReplies(boolean)}. 1051 * 1052 * If set to true, then any smart reply buttons will be hidden. 1053 * 1054 * @see Builder#setHideSmartReplies(boolean) 1055 * @hide 1056 */ 1057 public static final String EXTRA_HIDE_SMART_REPLIES = "android.hideSmartReplies"; 1058 1059 /** 1060 * {@link #extras} key: this is a small piece of additional text as supplied to 1061 * {@link Builder#setContentInfo(CharSequence)}. 1062 */ 1063 public static final String EXTRA_INFO_TEXT = "android.infoText"; 1064 1065 /** 1066 * {@link #extras} key: this is a line of summary information intended to be shown 1067 * alongside expanded notifications, as supplied to (e.g.) 1068 * {@link BigTextStyle#setSummaryText(CharSequence)}. 1069 */ 1070 public static final String EXTRA_SUMMARY_TEXT = "android.summaryText"; 1071 1072 /** 1073 * {@link #extras} key: this is the longer text shown in the big form of a 1074 * {@link BigTextStyle} notification, as supplied to 1075 * {@link BigTextStyle#bigText(CharSequence)}. 1076 */ 1077 public static final String EXTRA_BIG_TEXT = "android.bigText"; 1078 1079 /** 1080 * {@link #extras} key: this is the resource ID of the notification's main small icon, as 1081 * supplied to {@link Builder#setSmallIcon(int)}. 1082 * 1083 * @deprecated Use {@link #getSmallIcon()}, which supports a wider variety of icon sources. 1084 */ 1085 @Deprecated 1086 public static final String EXTRA_SMALL_ICON = "android.icon"; 1087 1088 /** 1089 * {@link #extras} key: this is a bitmap to be used instead of the small icon when showing the 1090 * notification payload, as 1091 * supplied to {@link Builder#setLargeIcon(android.graphics.Bitmap)}. 1092 * 1093 * @deprecated Use {@link #getLargeIcon()}, which supports a wider variety of icon sources. 1094 */ 1095 @Deprecated 1096 public static final String EXTRA_LARGE_ICON = "android.largeIcon"; 1097 1098 /** 1099 * {@link #extras} key: this is a bitmap to be used instead of the one from 1100 * {@link Builder#setLargeIcon(android.graphics.Bitmap)} when the notification is 1101 * shown in its expanded form, as supplied to 1102 * {@link BigPictureStyle#bigLargeIcon(android.graphics.Bitmap)}. 1103 */ 1104 public static final String EXTRA_LARGE_ICON_BIG = EXTRA_LARGE_ICON + ".big"; 1105 1106 /** 1107 * {@link #extras} key: this is the progress value supplied to 1108 * {@link Builder#setProgress(int, int, boolean)}. 1109 */ 1110 public static final String EXTRA_PROGRESS = "android.progress"; 1111 1112 /** 1113 * {@link #extras} key: this is the maximum value supplied to 1114 * {@link Builder#setProgress(int, int, boolean)}. 1115 */ 1116 public static final String EXTRA_PROGRESS_MAX = "android.progressMax"; 1117 1118 /** 1119 * {@link #extras} key: whether the progress bar is indeterminate, supplied to 1120 * {@link Builder#setProgress(int, int, boolean)}. 1121 */ 1122 public static final String EXTRA_PROGRESS_INDETERMINATE = "android.progressIndeterminate"; 1123 1124 /** 1125 * {@link #extras} key: whether {@link #when} should be shown as a count-up timer (specifically 1126 * a {@link android.widget.Chronometer}) instead of a timestamp, as supplied to 1127 * {@link Builder#setUsesChronometer(boolean)}. 1128 */ 1129 public static final String EXTRA_SHOW_CHRONOMETER = "android.showChronometer"; 1130 1131 /** 1132 * {@link #extras} key: whether the chronometer set on the notification should count down 1133 * instead of counting up. Is only relevant if key {@link #EXTRA_SHOW_CHRONOMETER} is present. 1134 * This extra is a boolean. The default is false. 1135 */ 1136 public static final String EXTRA_CHRONOMETER_COUNT_DOWN = "android.chronometerCountDown"; 1137 1138 /** 1139 * {@link #extras} key: whether {@link #when} should be shown, 1140 * as supplied to {@link Builder#setShowWhen(boolean)}. 1141 */ 1142 public static final String EXTRA_SHOW_WHEN = "android.showWhen"; 1143 1144 /** 1145 * {@link #extras} key: this is a bitmap to be shown in {@link BigPictureStyle} expanded 1146 * notifications, supplied to {@link BigPictureStyle#bigPicture(android.graphics.Bitmap)}. 1147 */ 1148 public static final String EXTRA_PICTURE = "android.picture"; 1149 1150 /** 1151 * {@link #extras} key: An array of CharSequences to show in {@link InboxStyle} expanded 1152 * notifications, each of which was supplied to {@link InboxStyle#addLine(CharSequence)}. 1153 */ 1154 public static final String EXTRA_TEXT_LINES = "android.textLines"; 1155 1156 /** 1157 * {@link #extras} key: A string representing the name of the specific 1158 * {@link android.app.Notification.Style} used to create this notification. 1159 */ 1160 public static final String EXTRA_TEMPLATE = "android.template"; 1161 1162 /** 1163 * {@link #extras} key: A String array containing the people that this notification relates to, 1164 * each of which was supplied to {@link Builder#addPerson(String)}. 1165 * 1166 * @deprecated the actual objects are now in {@link #EXTRA_PEOPLE_LIST} 1167 */ 1168 public static final String EXTRA_PEOPLE = "android.people"; 1169 1170 /** 1171 * {@link #extras} key: An arrayList of {@link Person} objects containing the people that 1172 * this notification relates to. 1173 */ 1174 public static final String EXTRA_PEOPLE_LIST = "android.people.list"; 1175 1176 /** 1177 * Allow certain system-generated notifications to appear before the device is provisioned. 1178 * Only available to notifications coming from the android package. 1179 * @hide 1180 */ 1181 @SystemApi 1182 @RequiresPermission(android.Manifest.permission.NOTIFICATION_DURING_SETUP) 1183 public static final String EXTRA_ALLOW_DURING_SETUP = "android.allowDuringSetup"; 1184 1185 /** 1186 * {@link #extras} key: 1187 * flat {@link String} representation of a {@link android.content.ContentUris content URI} 1188 * pointing to an image that can be displayed in the background when the notification is 1189 * selected. Used on television platforms. The URI must point to an image stream suitable for 1190 * passing into {@link android.graphics.BitmapFactory#decodeStream(java.io.InputStream) 1191 * BitmapFactory.decodeStream}; all other content types will be ignored. 1192 */ 1193 public static final String EXTRA_BACKGROUND_IMAGE_URI = "android.backgroundImageUri"; 1194 1195 /** 1196 * {@link #extras} key: A 1197 * {@link android.media.session.MediaSession.Token} associated with a 1198 * {@link android.app.Notification.MediaStyle} notification. 1199 */ 1200 public static final String EXTRA_MEDIA_SESSION = "android.mediaSession"; 1201 1202 /** 1203 * {@link #extras} key: the indices of actions to be shown in the compact view, 1204 * as supplied to (e.g.) {@link MediaStyle#setShowActionsInCompactView(int...)}. 1205 */ 1206 public static final String EXTRA_COMPACT_ACTIONS = "android.compactActions"; 1207 1208 /** 1209 * {@link #extras} key: the username to be displayed for all messages sent by the user including 1210 * direct replies 1211 * {@link android.app.Notification.MessagingStyle} notification. This extra is a 1212 * {@link CharSequence} 1213 * 1214 * @deprecated use {@link #EXTRA_MESSAGING_PERSON} 1215 */ 1216 public static final String EXTRA_SELF_DISPLAY_NAME = "android.selfDisplayName"; 1217 1218 /** 1219 * {@link #extras} key: the person to be displayed for all messages sent by the user including 1220 * direct replies 1221 * {@link android.app.Notification.MessagingStyle} notification. This extra is a 1222 * {@link Person} 1223 */ 1224 public static final String EXTRA_MESSAGING_PERSON = "android.messagingUser"; 1225 1226 /** 1227 * {@link #extras} key: a {@link CharSequence} to be displayed as the title to a conversation 1228 * represented by a {@link android.app.Notification.MessagingStyle} 1229 */ 1230 public static final String EXTRA_CONVERSATION_TITLE = "android.conversationTitle"; 1231 1232 /** @hide */ 1233 public static final String EXTRA_CONVERSATION_ICON = "android.conversationIcon"; 1234 1235 /** @hide */ 1236 public static final String EXTRA_CONVERSATION_UNREAD_MESSAGE_COUNT = 1237 "android.conversationUnreadMessageCount"; 1238 1239 /** 1240 * {@link #extras} key: an array of {@link android.app.Notification.MessagingStyle.Message} 1241 * bundles provided by a 1242 * {@link android.app.Notification.MessagingStyle} notification. This extra is a parcelable 1243 * array of bundles. 1244 */ 1245 public static final String EXTRA_MESSAGES = "android.messages"; 1246 1247 /** 1248 * {@link #extras} key: an array of 1249 * {@link android.app.Notification.MessagingStyle#addHistoricMessage historic} 1250 * {@link android.app.Notification.MessagingStyle.Message} bundles provided by a 1251 * {@link android.app.Notification.MessagingStyle} notification. This extra is a parcelable 1252 * array of bundles. 1253 */ 1254 public static final String EXTRA_HISTORIC_MESSAGES = "android.messages.historic"; 1255 1256 /** 1257 * {@link #extras} key: whether the {@link android.app.Notification.MessagingStyle} notification 1258 * represents a group conversation. 1259 */ 1260 public static final String EXTRA_IS_GROUP_CONVERSATION = "android.isGroupConversation"; 1261 1262 /** 1263 * {@link #extras} key: whether the notification should be colorized as 1264 * supplied to {@link Builder#setColorized(boolean)}. 1265 */ 1266 public static final String EXTRA_COLORIZED = "android.colorized"; 1267 1268 /** 1269 * @hide 1270 */ 1271 public static final String EXTRA_BUILDER_APPLICATION_INFO = "android.appInfo"; 1272 1273 /** 1274 * @hide 1275 */ 1276 public static final String EXTRA_CONTAINS_CUSTOM_VIEW = "android.contains.customView"; 1277 1278 /** 1279 * @hide 1280 */ 1281 public static final String EXTRA_REDUCED_IMAGES = "android.reduced.images"; 1282 1283 /** 1284 * {@link #extras} key: the audio contents of this notification. 1285 * 1286 * This is for use when rendering the notification on an audio-focused interface; 1287 * the audio contents are a complete sound sample that contains the contents/body of the 1288 * notification. This may be used in substitute of a Text-to-Speech reading of the 1289 * notification. For example if the notification represents a voice message this should point 1290 * to the audio of that message. 1291 * 1292 * The data stored under this key should be a String representation of a Uri that contains the 1293 * audio contents in one of the following formats: WAV, PCM 16-bit, AMR-WB. 1294 * 1295 * This extra is unnecessary if you are using {@code MessagingStyle} since each {@code Message} 1296 * has a field for holding data URI. That field can be used for audio. 1297 * See {@code Message#setData}. 1298 * 1299 * Example usage: 1300 * <pre> 1301 * {@code 1302 * Notification.Builder myBuilder = (build your Notification as normal); 1303 * myBuilder.getExtras().putString(EXTRA_AUDIO_CONTENTS_URI, myAudioUri.toString()); 1304 * } 1305 * </pre> 1306 */ 1307 public static final String EXTRA_AUDIO_CONTENTS_URI = "android.audioContents"; 1308 1309 /** @hide */ 1310 @SystemApi 1311 @RequiresPermission(android.Manifest.permission.SUBSTITUTE_NOTIFICATION_APP_NAME) 1312 public static final String EXTRA_SUBSTITUTE_APP_NAME = "android.substName"; 1313 1314 /** 1315 * This is set on the notifications shown by system_server about apps running foreground 1316 * services. It indicates that the notification should be shown 1317 * only if any of the given apps do not already have a properly tagged 1318 * {@link #FLAG_FOREGROUND_SERVICE} notification currently visible to the user. 1319 * This is a string array of all package names of the apps. 1320 * @hide 1321 */ 1322 public static final String EXTRA_FOREGROUND_APPS = "android.foregroundApps"; 1323 1324 @UnsupportedAppUsage 1325 private Icon mSmallIcon; 1326 @UnsupportedAppUsage 1327 private Icon mLargeIcon; 1328 1329 @UnsupportedAppUsage 1330 private String mChannelId; 1331 private long mTimeout; 1332 1333 private String mShortcutId; 1334 private LocusId mLocusId; 1335 private CharSequence mSettingsText; 1336 1337 private BubbleMetadata mBubbleMetadata; 1338 1339 /** @hide */ 1340 @IntDef(prefix = { "GROUP_ALERT_" }, value = { 1341 GROUP_ALERT_ALL, GROUP_ALERT_CHILDREN, GROUP_ALERT_SUMMARY 1342 }) 1343 @Retention(RetentionPolicy.SOURCE) 1344 public @interface GroupAlertBehavior {} 1345 1346 /** 1347 * Constant for {@link Builder#setGroupAlertBehavior(int)}, meaning that all notifications in a 1348 * group with sound or vibration ought to make sound or vibrate (respectively), so this 1349 * notification will not be muted when it is in a group. 1350 */ 1351 public static final int GROUP_ALERT_ALL = 0; 1352 1353 /** 1354 * Constant for {@link Builder#setGroupAlertBehavior(int)}, meaning that all children 1355 * notification in a group should be silenced (no sound or vibration) even if they are posted 1356 * to a {@link NotificationChannel} that has sound and/or vibration. Use this constant to 1357 * mute this notification if this notification is a group child. This must be applied to all 1358 * children notifications you want to mute. 1359 * 1360 * <p> For example, you might want to use this constant if you post a number of children 1361 * notifications at once (say, after a periodic sync), and only need to notify the user 1362 * audibly once. 1363 */ 1364 public static final int GROUP_ALERT_SUMMARY = 1; 1365 1366 /** 1367 * Constant for {@link Builder#setGroupAlertBehavior(int)}, meaning that the summary 1368 * notification in a group should be silenced (no sound or vibration) even if they are 1369 * posted to a {@link NotificationChannel} that has sound and/or vibration. Use this constant 1370 * to mute this notification if this notification is a group summary. 1371 * 1372 * <p>For example, you might want to use this constant if only the children notifications 1373 * in your group have content and the summary is only used to visually group notifications 1374 * rather than to alert the user that new information is available. 1375 */ 1376 public static final int GROUP_ALERT_CHILDREN = 2; 1377 1378 private int mGroupAlertBehavior = GROUP_ALERT_ALL; 1379 1380 /** 1381 * If this notification is being shown as a badge, always show as a number. 1382 */ 1383 public static final int BADGE_ICON_NONE = 0; 1384 1385 /** 1386 * If this notification is being shown as a badge, use the {@link #getSmallIcon()} to 1387 * represent this notification. 1388 */ 1389 public static final int BADGE_ICON_SMALL = 1; 1390 1391 /** 1392 * If this notification is being shown as a badge, use the {@link #getLargeIcon()} to 1393 * represent this notification. 1394 */ 1395 public static final int BADGE_ICON_LARGE = 2; 1396 private int mBadgeIcon = BADGE_ICON_NONE; 1397 1398 /** 1399 * Determines whether the platform can generate contextual actions for a notification. 1400 */ 1401 private boolean mAllowSystemGeneratedContextualActions = true; 1402 1403 /** 1404 * Structure to encapsulate a named action that can be shown as part of this notification. 1405 * It must include an icon, a label, and a {@link PendingIntent} to be fired when the action is 1406 * selected by the user. 1407 * <p> 1408 * Apps should use {@link Notification.Builder#addAction(int, CharSequence, PendingIntent)} 1409 * or {@link Notification.Builder#addAction(Notification.Action)} 1410 * to attach actions. 1411 */ 1412 public static class Action implements Parcelable { 1413 /** 1414 * {@link #extras} key: Keys to a {@link Parcelable} {@link ArrayList} of 1415 * {@link RemoteInput}s. 1416 * 1417 * This is intended for {@link RemoteInput}s that only accept data, meaning 1418 * {@link RemoteInput#getAllowFreeFormInput} is false, {@link RemoteInput#getChoices} 1419 * is null or empty, and {@link RemoteInput#getAllowedDataTypes} is non-null and not 1420 * empty. These {@link RemoteInput}s will be ignored by devices that do not 1421 * support non-text-based {@link RemoteInput}s. See {@link Builder#build}. 1422 * 1423 * You can test if a RemoteInput matches these constraints using 1424 * {@link RemoteInput#isDataOnly}. 1425 */ 1426 private static final String EXTRA_DATA_ONLY_INPUTS = "android.extra.DATA_ONLY_INPUTS"; 1427 1428 /** 1429 * {@link }: No semantic action defined. 1430 */ 1431 public static final int SEMANTIC_ACTION_NONE = 0; 1432 1433 /** 1434 * {@code SemanticAction}: Reply to a conversation, chat, group, or wherever replies 1435 * may be appropriate. 1436 */ 1437 public static final int SEMANTIC_ACTION_REPLY = 1; 1438 1439 /** 1440 * {@code SemanticAction}: Mark content as read. 1441 */ 1442 public static final int SEMANTIC_ACTION_MARK_AS_READ = 2; 1443 1444 /** 1445 * {@code SemanticAction}: Mark content as unread. 1446 */ 1447 public static final int SEMANTIC_ACTION_MARK_AS_UNREAD = 3; 1448 1449 /** 1450 * {@code SemanticAction}: Delete the content associated with the notification. This 1451 * could mean deleting an email, message, etc. 1452 */ 1453 public static final int SEMANTIC_ACTION_DELETE = 4; 1454 1455 /** 1456 * {@code SemanticAction}: Archive the content associated with the notification. This 1457 * could mean archiving an email, message, etc. 1458 */ 1459 public static final int SEMANTIC_ACTION_ARCHIVE = 5; 1460 1461 /** 1462 * {@code SemanticAction}: Mute the content associated with the notification. This could 1463 * mean silencing a conversation or currently playing media. 1464 */ 1465 public static final int SEMANTIC_ACTION_MUTE = 6; 1466 1467 /** 1468 * {@code SemanticAction}: Unmute the content associated with the notification. This could 1469 * mean un-silencing a conversation or currently playing media. 1470 */ 1471 public static final int SEMANTIC_ACTION_UNMUTE = 7; 1472 1473 /** 1474 * {@code SemanticAction}: Mark content with a thumbs up. 1475 */ 1476 public static final int SEMANTIC_ACTION_THUMBS_UP = 8; 1477 1478 /** 1479 * {@code SemanticAction}: Mark content with a thumbs down. 1480 */ 1481 public static final int SEMANTIC_ACTION_THUMBS_DOWN = 9; 1482 1483 /** 1484 * {@code SemanticAction}: Call a contact, group, etc. 1485 */ 1486 public static final int SEMANTIC_ACTION_CALL = 10; 1487 1488 private final Bundle mExtras; 1489 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) 1490 private Icon mIcon; 1491 private final RemoteInput[] mRemoteInputs; 1492 private boolean mAllowGeneratedReplies = true; 1493 private final @SemanticAction int mSemanticAction; 1494 private final boolean mIsContextual; 1495 1496 /** 1497 * Small icon representing the action. 1498 * 1499 * @deprecated Use {@link Action#getIcon()} instead. 1500 */ 1501 @Deprecated 1502 public int icon; 1503 1504 /** 1505 * Title of the action. 1506 */ 1507 public CharSequence title; 1508 1509 /** 1510 * Intent to send when the user invokes this action. May be null, in which case the action 1511 * may be rendered in a disabled presentation by the system UI. 1512 */ 1513 public PendingIntent actionIntent; 1514 Action(Parcel in)1515 private Action(Parcel in) { 1516 if (in.readInt() != 0) { 1517 mIcon = Icon.CREATOR.createFromParcel(in); 1518 if (mIcon.getType() == Icon.TYPE_RESOURCE) { 1519 icon = mIcon.getResId(); 1520 } 1521 } 1522 title = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); 1523 if (in.readInt() == 1) { 1524 actionIntent = PendingIntent.CREATOR.createFromParcel(in); 1525 } 1526 mExtras = Bundle.setDefusable(in.readBundle(), true); 1527 mRemoteInputs = in.createTypedArray(RemoteInput.CREATOR); 1528 mAllowGeneratedReplies = in.readInt() == 1; 1529 mSemanticAction = in.readInt(); 1530 mIsContextual = in.readInt() == 1; 1531 } 1532 1533 /** 1534 * @deprecated Use {@link android.app.Notification.Action.Builder}. 1535 */ 1536 @Deprecated Action(int icon, CharSequence title, PendingIntent intent)1537 public Action(int icon, CharSequence title, PendingIntent intent) { 1538 this(Icon.createWithResource("", icon), title, intent, new Bundle(), null, true, 1539 SEMANTIC_ACTION_NONE, false /* isContextual */); 1540 } 1541 1542 /** 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)1543 private Action(Icon icon, CharSequence title, PendingIntent intent, Bundle extras, 1544 RemoteInput[] remoteInputs, boolean allowGeneratedReplies, 1545 @SemanticAction int semanticAction, boolean isContextual) { 1546 this.mIcon = icon; 1547 if (icon != null && icon.getType() == Icon.TYPE_RESOURCE) { 1548 this.icon = icon.getResId(); 1549 } 1550 this.title = title; 1551 this.actionIntent = intent; 1552 this.mExtras = extras != null ? extras : new Bundle(); 1553 this.mRemoteInputs = remoteInputs; 1554 this.mAllowGeneratedReplies = allowGeneratedReplies; 1555 this.mSemanticAction = semanticAction; 1556 this.mIsContextual = isContextual; 1557 } 1558 1559 /** 1560 * Return an icon representing the action. 1561 */ getIcon()1562 public Icon getIcon() { 1563 if (mIcon == null && icon != 0) { 1564 // you snuck an icon in here without using the builder; let's try to keep it 1565 mIcon = Icon.createWithResource("", icon); 1566 } 1567 return mIcon; 1568 } 1569 1570 /** 1571 * Get additional metadata carried around with this Action. 1572 */ getExtras()1573 public Bundle getExtras() { 1574 return mExtras; 1575 } 1576 1577 /** 1578 * Return whether the platform should automatically generate possible replies for this 1579 * {@link Action} 1580 */ getAllowGeneratedReplies()1581 public boolean getAllowGeneratedReplies() { 1582 return mAllowGeneratedReplies; 1583 } 1584 1585 /** 1586 * Get the list of inputs to be collected from the user when this action is sent. 1587 * May return null if no remote inputs were added. Only returns inputs which accept 1588 * a text input. For inputs which only accept data use {@link #getDataOnlyRemoteInputs}. 1589 */ getRemoteInputs()1590 public RemoteInput[] getRemoteInputs() { 1591 return mRemoteInputs; 1592 } 1593 1594 /** 1595 * Returns the {@code SemanticAction} associated with this {@link Action}. A 1596 * {@code SemanticAction} denotes what an {@link Action}'s {@link PendingIntent} will do 1597 * (eg. reply, mark as read, delete, etc). 1598 */ getSemanticAction()1599 public @SemanticAction int getSemanticAction() { 1600 return mSemanticAction; 1601 } 1602 1603 /** 1604 * Returns whether this is a contextual Action, i.e. whether the action is dependent on the 1605 * notification message body. An example of a contextual action could be an action opening a 1606 * map application with an address shown in the notification. 1607 */ isContextual()1608 public boolean isContextual() { 1609 return mIsContextual; 1610 } 1611 1612 /** 1613 * Get the list of inputs to be collected from the user that ONLY accept data when this 1614 * action is sent. These remote inputs are guaranteed to return true on a call to 1615 * {@link RemoteInput#isDataOnly}. 1616 * 1617 * Returns null if there are no data-only remote inputs. 1618 * 1619 * This method exists so that legacy RemoteInput collectors that pre-date the addition 1620 * of non-textual RemoteInputs do not access these remote inputs. 1621 */ getDataOnlyRemoteInputs()1622 public RemoteInput[] getDataOnlyRemoteInputs() { 1623 return getParcelableArrayFromBundle(mExtras, EXTRA_DATA_ONLY_INPUTS, RemoteInput.class); 1624 } 1625 1626 /** 1627 * Builder class for {@link Action} objects. 1628 */ 1629 public static final class Builder { 1630 @Nullable private final Icon mIcon; 1631 @Nullable private final CharSequence mTitle; 1632 @Nullable private final PendingIntent mIntent; 1633 private boolean mAllowGeneratedReplies = true; 1634 @NonNull private final Bundle mExtras; 1635 @Nullable private ArrayList<RemoteInput> mRemoteInputs; 1636 private @SemanticAction int mSemanticAction; 1637 private boolean mIsContextual; 1638 1639 /** 1640 * Construct a new builder for {@link Action} object. 1641 * @param icon icon to show for this action 1642 * @param title the title of the action 1643 * @param intent the {@link PendingIntent} to fire when users trigger this action 1644 */ 1645 @Deprecated Builder(int icon, CharSequence title, PendingIntent intent)1646 public Builder(int icon, CharSequence title, PendingIntent intent) { 1647 this(Icon.createWithResource("", icon), title, intent); 1648 } 1649 1650 /** 1651 * Construct a new builder for {@link Action} object. 1652 * @param icon icon to show for this action 1653 * @param title the title of the action 1654 * @param intent the {@link PendingIntent} to fire when users trigger this action 1655 */ Builder(Icon icon, CharSequence title, PendingIntent intent)1656 public Builder(Icon icon, CharSequence title, PendingIntent intent) { 1657 this(icon, title, intent, new Bundle(), null, true, SEMANTIC_ACTION_NONE); 1658 } 1659 1660 /** 1661 * Construct a new builder for {@link Action} object using the fields from an 1662 * {@link Action}. 1663 * @param action the action to read fields from. 1664 */ Builder(Action action)1665 public Builder(Action action) { 1666 this(action.getIcon(), action.title, action.actionIntent, 1667 new Bundle(action.mExtras), action.getRemoteInputs(), 1668 action.getAllowGeneratedReplies(), action.getSemanticAction()); 1669 } 1670 Builder(@ullable Icon icon, @Nullable CharSequence title, @Nullable PendingIntent intent, @NonNull Bundle extras, @Nullable RemoteInput[] remoteInputs, boolean allowGeneratedReplies, @SemanticAction int semanticAction)1671 private Builder(@Nullable Icon icon, @Nullable CharSequence title, 1672 @Nullable PendingIntent intent, @NonNull Bundle extras, 1673 @Nullable RemoteInput[] remoteInputs, boolean allowGeneratedReplies, 1674 @SemanticAction int semanticAction) { 1675 mIcon = icon; 1676 mTitle = title; 1677 mIntent = intent; 1678 mExtras = extras; 1679 if (remoteInputs != null) { 1680 mRemoteInputs = new ArrayList<RemoteInput>(remoteInputs.length); 1681 Collections.addAll(mRemoteInputs, remoteInputs); 1682 } 1683 mAllowGeneratedReplies = allowGeneratedReplies; 1684 mSemanticAction = semanticAction; 1685 } 1686 1687 /** 1688 * Merge additional metadata into this builder. 1689 * 1690 * <p>Values within the Bundle will replace existing extras values in this Builder. 1691 * 1692 * @see Notification.Action#extras 1693 */ 1694 @NonNull addExtras(Bundle extras)1695 public Builder addExtras(Bundle extras) { 1696 if (extras != null) { 1697 mExtras.putAll(extras); 1698 } 1699 return this; 1700 } 1701 1702 /** 1703 * Get the metadata Bundle used by this Builder. 1704 * 1705 * <p>The returned Bundle is shared with this Builder. 1706 */ 1707 @NonNull getExtras()1708 public Bundle getExtras() { 1709 return mExtras; 1710 } 1711 1712 /** 1713 * Add an input to be collected from the user when this action is sent. 1714 * Response values can be retrieved from the fired intent by using the 1715 * {@link RemoteInput#getResultsFromIntent} function. 1716 * @param remoteInput a {@link RemoteInput} to add to the action 1717 * @return this object for method chaining 1718 */ 1719 @NonNull addRemoteInput(RemoteInput remoteInput)1720 public Builder addRemoteInput(RemoteInput remoteInput) { 1721 if (mRemoteInputs == null) { 1722 mRemoteInputs = new ArrayList<RemoteInput>(); 1723 } 1724 mRemoteInputs.add(remoteInput); 1725 return this; 1726 } 1727 1728 /** 1729 * Set whether the platform should automatically generate possible replies to add to 1730 * {@link RemoteInput#getChoices()}. If the {@link Action} doesn't have a 1731 * {@link RemoteInput}, this has no effect. 1732 * @param allowGeneratedReplies {@code true} to allow generated replies, {@code false} 1733 * otherwise 1734 * @return this object for method chaining 1735 * The default value is {@code true} 1736 */ 1737 @NonNull setAllowGeneratedReplies(boolean allowGeneratedReplies)1738 public Builder setAllowGeneratedReplies(boolean allowGeneratedReplies) { 1739 mAllowGeneratedReplies = allowGeneratedReplies; 1740 return this; 1741 } 1742 1743 /** 1744 * Sets the {@code SemanticAction} for this {@link Action}. A 1745 * {@code SemanticAction} denotes what an {@link Action}'s 1746 * {@link PendingIntent} will do (eg. reply, mark as read, delete, etc). 1747 * @param semanticAction a SemanticAction defined within {@link Action} with 1748 * {@code SEMANTIC_ACTION_} prefixes 1749 * @return this object for method chaining 1750 */ 1751 @NonNull setSemanticAction(@emanticAction int semanticAction)1752 public Builder setSemanticAction(@SemanticAction int semanticAction) { 1753 mSemanticAction = semanticAction; 1754 return this; 1755 } 1756 1757 /** 1758 * Sets whether this {@link Action} is a contextual action, i.e. whether the action is 1759 * dependent on the notification message body. An example of a contextual action could 1760 * be an action opening a map application with an address shown in the notification. 1761 */ 1762 @NonNull setContextual(boolean isContextual)1763 public Builder setContextual(boolean isContextual) { 1764 mIsContextual = isContextual; 1765 return this; 1766 } 1767 1768 /** 1769 * Apply an extender to this action builder. Extenders may be used to add 1770 * metadata or change options on this builder. 1771 */ 1772 @NonNull extend(Extender extender)1773 public Builder extend(Extender extender) { 1774 extender.extend(this); 1775 return this; 1776 } 1777 1778 /** 1779 * Throws an NPE if we are building a contextual action missing one of the fields 1780 * necessary to display the action. 1781 */ checkContextualActionNullFields()1782 private void checkContextualActionNullFields() { 1783 if (!mIsContextual) return; 1784 1785 if (mIcon == null) { 1786 throw new NullPointerException("Contextual Actions must contain a valid icon"); 1787 } 1788 1789 if (mIntent == null) { 1790 throw new NullPointerException( 1791 "Contextual Actions must contain a valid PendingIntent"); 1792 } 1793 } 1794 1795 /** 1796 * Combine all of the options that have been set and return a new {@link Action} 1797 * object. 1798 * @return the built action 1799 */ 1800 @NonNull build()1801 public Action build() { 1802 checkContextualActionNullFields(); 1803 1804 ArrayList<RemoteInput> dataOnlyInputs = new ArrayList<>(); 1805 RemoteInput[] previousDataInputs = getParcelableArrayFromBundle( 1806 mExtras, EXTRA_DATA_ONLY_INPUTS, RemoteInput.class); 1807 if (previousDataInputs != null) { 1808 for (RemoteInput input : previousDataInputs) { 1809 dataOnlyInputs.add(input); 1810 } 1811 } 1812 List<RemoteInput> textInputs = new ArrayList<>(); 1813 if (mRemoteInputs != null) { 1814 for (RemoteInput input : mRemoteInputs) { 1815 if (input.isDataOnly()) { 1816 dataOnlyInputs.add(input); 1817 } else { 1818 textInputs.add(input); 1819 } 1820 } 1821 } 1822 if (!dataOnlyInputs.isEmpty()) { 1823 RemoteInput[] dataInputsArr = 1824 dataOnlyInputs.toArray(new RemoteInput[dataOnlyInputs.size()]); 1825 mExtras.putParcelableArray(EXTRA_DATA_ONLY_INPUTS, dataInputsArr); 1826 } 1827 RemoteInput[] textInputsArr = textInputs.isEmpty() 1828 ? null : textInputs.toArray(new RemoteInput[textInputs.size()]); 1829 return new Action(mIcon, mTitle, mIntent, mExtras, textInputsArr, 1830 mAllowGeneratedReplies, mSemanticAction, mIsContextual); 1831 } 1832 } 1833 1834 @Override clone()1835 public Action clone() { 1836 return new Action( 1837 getIcon(), 1838 title, 1839 actionIntent, // safe to alias 1840 mExtras == null ? new Bundle() : new Bundle(mExtras), 1841 getRemoteInputs(), 1842 getAllowGeneratedReplies(), 1843 getSemanticAction(), 1844 isContextual()); 1845 } 1846 1847 @Override describeContents()1848 public int describeContents() { 1849 return 0; 1850 } 1851 1852 @Override writeToParcel(Parcel out, int flags)1853 public void writeToParcel(Parcel out, int flags) { 1854 final Icon ic = getIcon(); 1855 if (ic != null) { 1856 out.writeInt(1); 1857 ic.writeToParcel(out, 0); 1858 } else { 1859 out.writeInt(0); 1860 } 1861 TextUtils.writeToParcel(title, out, flags); 1862 if (actionIntent != null) { 1863 out.writeInt(1); 1864 actionIntent.writeToParcel(out, flags); 1865 } else { 1866 out.writeInt(0); 1867 } 1868 out.writeBundle(mExtras); 1869 out.writeTypedArray(mRemoteInputs, flags); 1870 out.writeInt(mAllowGeneratedReplies ? 1 : 0); 1871 out.writeInt(mSemanticAction); 1872 out.writeInt(mIsContextual ? 1 : 0); 1873 } 1874 1875 public static final @android.annotation.NonNull Parcelable.Creator<Action> CREATOR = 1876 new Parcelable.Creator<Action>() { 1877 public Action createFromParcel(Parcel in) { 1878 return new Action(in); 1879 } 1880 public Action[] newArray(int size) { 1881 return new Action[size]; 1882 } 1883 }; 1884 1885 /** 1886 * Extender interface for use with {@link Builder#extend}. Extenders may be used to add 1887 * metadata or change options on an action builder. 1888 */ 1889 public interface Extender { 1890 /** 1891 * Apply this extender to a notification action builder. 1892 * @param builder the builder to be modified. 1893 * @return the build object for chaining. 1894 */ extend(Builder builder)1895 public Builder extend(Builder builder); 1896 } 1897 1898 /** 1899 * Wearable extender for notification actions. To add extensions to an action, 1900 * create a new {@link android.app.Notification.Action.WearableExtender} object using 1901 * the {@code WearableExtender()} constructor and apply it to a 1902 * {@link android.app.Notification.Action.Builder} using 1903 * {@link android.app.Notification.Action.Builder#extend}. 1904 * 1905 * <pre class="prettyprint"> 1906 * Notification.Action action = new Notification.Action.Builder( 1907 * R.drawable.archive_all, "Archive all", actionIntent) 1908 * .extend(new Notification.Action.WearableExtender() 1909 * .setAvailableOffline(false)) 1910 * .build();</pre> 1911 */ 1912 public static final class WearableExtender implements Extender { 1913 /** Notification action extra which contains wearable extensions */ 1914 private static final String EXTRA_WEARABLE_EXTENSIONS = "android.wearable.EXTENSIONS"; 1915 1916 // Keys within EXTRA_WEARABLE_EXTENSIONS for wearable options. 1917 private static final String KEY_FLAGS = "flags"; 1918 private static final String KEY_IN_PROGRESS_LABEL = "inProgressLabel"; 1919 private static final String KEY_CONFIRM_LABEL = "confirmLabel"; 1920 private static final String KEY_CANCEL_LABEL = "cancelLabel"; 1921 1922 // Flags bitwise-ored to mFlags 1923 private static final int FLAG_AVAILABLE_OFFLINE = 0x1; 1924 private static final int FLAG_HINT_LAUNCHES_ACTIVITY = 1 << 1; 1925 private static final int FLAG_HINT_DISPLAY_INLINE = 1 << 2; 1926 1927 // Default value for flags integer 1928 private static final int DEFAULT_FLAGS = FLAG_AVAILABLE_OFFLINE; 1929 1930 private int mFlags = DEFAULT_FLAGS; 1931 1932 private CharSequence mInProgressLabel; 1933 private CharSequence mConfirmLabel; 1934 private CharSequence mCancelLabel; 1935 1936 /** 1937 * Create a {@link android.app.Notification.Action.WearableExtender} with default 1938 * options. 1939 */ WearableExtender()1940 public WearableExtender() { 1941 } 1942 1943 /** 1944 * Create a {@link android.app.Notification.Action.WearableExtender} by reading 1945 * wearable options present in an existing notification action. 1946 * @param action the notification action to inspect. 1947 */ WearableExtender(Action action)1948 public WearableExtender(Action action) { 1949 Bundle wearableBundle = action.getExtras().getBundle(EXTRA_WEARABLE_EXTENSIONS); 1950 if (wearableBundle != null) { 1951 mFlags = wearableBundle.getInt(KEY_FLAGS, DEFAULT_FLAGS); 1952 mInProgressLabel = wearableBundle.getCharSequence(KEY_IN_PROGRESS_LABEL); 1953 mConfirmLabel = wearableBundle.getCharSequence(KEY_CONFIRM_LABEL); 1954 mCancelLabel = wearableBundle.getCharSequence(KEY_CANCEL_LABEL); 1955 } 1956 } 1957 1958 /** 1959 * Apply wearable extensions to a notification action that is being built. This is 1960 * typically called by the {@link android.app.Notification.Action.Builder#extend} 1961 * method of {@link android.app.Notification.Action.Builder}. 1962 */ 1963 @Override extend(Action.Builder builder)1964 public Action.Builder extend(Action.Builder builder) { 1965 Bundle wearableBundle = new Bundle(); 1966 1967 if (mFlags != DEFAULT_FLAGS) { 1968 wearableBundle.putInt(KEY_FLAGS, mFlags); 1969 } 1970 if (mInProgressLabel != null) { 1971 wearableBundle.putCharSequence(KEY_IN_PROGRESS_LABEL, mInProgressLabel); 1972 } 1973 if (mConfirmLabel != null) { 1974 wearableBundle.putCharSequence(KEY_CONFIRM_LABEL, mConfirmLabel); 1975 } 1976 if (mCancelLabel != null) { 1977 wearableBundle.putCharSequence(KEY_CANCEL_LABEL, mCancelLabel); 1978 } 1979 1980 builder.getExtras().putBundle(EXTRA_WEARABLE_EXTENSIONS, wearableBundle); 1981 return builder; 1982 } 1983 1984 @Override clone()1985 public WearableExtender clone() { 1986 WearableExtender that = new WearableExtender(); 1987 that.mFlags = this.mFlags; 1988 that.mInProgressLabel = this.mInProgressLabel; 1989 that.mConfirmLabel = this.mConfirmLabel; 1990 that.mCancelLabel = this.mCancelLabel; 1991 return that; 1992 } 1993 1994 /** 1995 * Set whether this action is available when the wearable device is not connected to 1996 * a companion device. The user can still trigger this action when the wearable device is 1997 * offline, but a visual hint will indicate that the action may not be available. 1998 * Defaults to true. 1999 */ setAvailableOffline(boolean availableOffline)2000 public WearableExtender setAvailableOffline(boolean availableOffline) { 2001 setFlag(FLAG_AVAILABLE_OFFLINE, availableOffline); 2002 return this; 2003 } 2004 2005 /** 2006 * Get whether this action is available when the wearable device is not connected to 2007 * a companion device. The user can still trigger this action when the wearable device is 2008 * offline, but a visual hint will indicate that the action may not be available. 2009 * Defaults to true. 2010 */ isAvailableOffline()2011 public boolean isAvailableOffline() { 2012 return (mFlags & FLAG_AVAILABLE_OFFLINE) != 0; 2013 } 2014 setFlag(int mask, boolean value)2015 private void setFlag(int mask, boolean value) { 2016 if (value) { 2017 mFlags |= mask; 2018 } else { 2019 mFlags &= ~mask; 2020 } 2021 } 2022 2023 /** 2024 * Set a label to display while the wearable is preparing to automatically execute the 2025 * action. This is usually a 'ing' verb ending in ellipsis like "Sending..." 2026 * 2027 * @param label the label to display while the action is being prepared to execute 2028 * @return this object for method chaining 2029 */ 2030 @Deprecated setInProgressLabel(CharSequence label)2031 public WearableExtender setInProgressLabel(CharSequence label) { 2032 mInProgressLabel = label; 2033 return this; 2034 } 2035 2036 /** 2037 * Get the label to display while the wearable is preparing to automatically execute 2038 * the action. This is usually a 'ing' verb ending in ellipsis like "Sending..." 2039 * 2040 * @return the label to display while the action is being prepared to execute 2041 */ 2042 @Deprecated getInProgressLabel()2043 public CharSequence getInProgressLabel() { 2044 return mInProgressLabel; 2045 } 2046 2047 /** 2048 * Set a label to display to confirm that the action should be executed. 2049 * This is usually an imperative verb like "Send". 2050 * 2051 * @param label the label to confirm the action should be executed 2052 * @return this object for method chaining 2053 */ 2054 @Deprecated setConfirmLabel(CharSequence label)2055 public WearableExtender setConfirmLabel(CharSequence label) { 2056 mConfirmLabel = label; 2057 return this; 2058 } 2059 2060 /** 2061 * Get the label to display to confirm that the action should be executed. 2062 * This is usually an imperative verb like "Send". 2063 * 2064 * @return the label to confirm the action should be executed 2065 */ 2066 @Deprecated getConfirmLabel()2067 public CharSequence getConfirmLabel() { 2068 return mConfirmLabel; 2069 } 2070 2071 /** 2072 * Set a label to display to cancel the action. 2073 * This is usually an imperative verb, like "Cancel". 2074 * 2075 * @param label the label to display to cancel the action 2076 * @return this object for method chaining 2077 */ 2078 @Deprecated setCancelLabel(CharSequence label)2079 public WearableExtender setCancelLabel(CharSequence label) { 2080 mCancelLabel = label; 2081 return this; 2082 } 2083 2084 /** 2085 * Get the label to display to cancel the action. 2086 * This is usually an imperative verb like "Cancel". 2087 * 2088 * @return the label to display to cancel the action 2089 */ 2090 @Deprecated getCancelLabel()2091 public CharSequence getCancelLabel() { 2092 return mCancelLabel; 2093 } 2094 2095 /** 2096 * Set a hint that this Action will launch an {@link Activity} directly, telling the 2097 * platform that it can generate the appropriate transitions. 2098 * @param hintLaunchesActivity {@code true} if the content intent will launch 2099 * an activity and transitions should be generated, false otherwise. 2100 * @return this object for method chaining 2101 */ setHintLaunchesActivity( boolean hintLaunchesActivity)2102 public WearableExtender setHintLaunchesActivity( 2103 boolean hintLaunchesActivity) { 2104 setFlag(FLAG_HINT_LAUNCHES_ACTIVITY, hintLaunchesActivity); 2105 return this; 2106 } 2107 2108 /** 2109 * Get a hint that this Action will launch an {@link Activity} directly, telling the 2110 * platform that it can generate the appropriate transitions 2111 * @return {@code true} if the content intent will launch an activity and transitions 2112 * should be generated, false otherwise. The default value is {@code false} if this was 2113 * never set. 2114 */ getHintLaunchesActivity()2115 public boolean getHintLaunchesActivity() { 2116 return (mFlags & FLAG_HINT_LAUNCHES_ACTIVITY) != 0; 2117 } 2118 2119 /** 2120 * Set a hint that this Action should be displayed inline. 2121 * 2122 * @param hintDisplayInline {@code true} if action should be displayed inline, false 2123 * otherwise 2124 * @return this object for method chaining 2125 */ setHintDisplayActionInline( boolean hintDisplayInline)2126 public WearableExtender setHintDisplayActionInline( 2127 boolean hintDisplayInline) { 2128 setFlag(FLAG_HINT_DISPLAY_INLINE, hintDisplayInline); 2129 return this; 2130 } 2131 2132 /** 2133 * Get a hint that this Action should be displayed inline. 2134 * 2135 * @return {@code true} if the Action should be displayed inline, {@code false} 2136 * otherwise. The default value is {@code false} if this was never set. 2137 */ getHintDisplayActionInline()2138 public boolean getHintDisplayActionInline() { 2139 return (mFlags & FLAG_HINT_DISPLAY_INLINE) != 0; 2140 } 2141 } 2142 2143 /** 2144 * Provides meaning to an {@link Action} that hints at what the associated 2145 * {@link PendingIntent} will do. For example, an {@link Action} with a 2146 * {@link PendingIntent} that replies to a text message notification may have the 2147 * {@link #SEMANTIC_ACTION_REPLY} {@code SemanticAction} set within it. 2148 * 2149 * @hide 2150 */ 2151 @IntDef(prefix = { "SEMANTIC_ACTION_" }, value = { 2152 SEMANTIC_ACTION_NONE, 2153 SEMANTIC_ACTION_REPLY, 2154 SEMANTIC_ACTION_MARK_AS_READ, 2155 SEMANTIC_ACTION_MARK_AS_UNREAD, 2156 SEMANTIC_ACTION_DELETE, 2157 SEMANTIC_ACTION_ARCHIVE, 2158 SEMANTIC_ACTION_MUTE, 2159 SEMANTIC_ACTION_UNMUTE, 2160 SEMANTIC_ACTION_THUMBS_UP, 2161 SEMANTIC_ACTION_THUMBS_DOWN, 2162 SEMANTIC_ACTION_CALL 2163 }) 2164 @Retention(RetentionPolicy.SOURCE) 2165 public @interface SemanticAction {} 2166 } 2167 2168 /** 2169 * Array of all {@link Action} structures attached to this notification by 2170 * {@link Builder#addAction(int, CharSequence, PendingIntent)}. Mostly useful for instances of 2171 * {@link android.service.notification.NotificationListenerService} that provide an alternative 2172 * interface for invoking actions. 2173 */ 2174 public Action[] actions; 2175 2176 /** 2177 * Replacement version of this notification whose content will be shown 2178 * in an insecure context such as atop a secure keyguard. See {@link #visibility} 2179 * and {@link #VISIBILITY_PUBLIC}. 2180 */ 2181 public Notification publicVersion; 2182 2183 /** 2184 * Constructs a Notification object with default values. 2185 * You might want to consider using {@link Builder} instead. 2186 */ Notification()2187 public Notification() 2188 { 2189 this.when = System.currentTimeMillis(); 2190 this.creationTime = System.currentTimeMillis(); 2191 this.priority = PRIORITY_DEFAULT; 2192 } 2193 2194 /** 2195 * @hide 2196 */ 2197 @UnsupportedAppUsage Notification(Context context, int icon, CharSequence tickerText, long when, CharSequence contentTitle, CharSequence contentText, Intent contentIntent)2198 public Notification(Context context, int icon, CharSequence tickerText, long when, 2199 CharSequence contentTitle, CharSequence contentText, Intent contentIntent) 2200 { 2201 new Builder(context) 2202 .setWhen(when) 2203 .setSmallIcon(icon) 2204 .setTicker(tickerText) 2205 .setContentTitle(contentTitle) 2206 .setContentText(contentText) 2207 .setContentIntent(PendingIntent.getActivity(context, 0, contentIntent, 0)) 2208 .buildInto(this); 2209 } 2210 2211 /** 2212 * Constructs a Notification object with the information needed to 2213 * have a status bar icon without the standard expanded view. 2214 * 2215 * @param icon The resource id of the icon to put in the status bar. 2216 * @param tickerText The text that flows by in the status bar when the notification first 2217 * activates. 2218 * @param when The time to show in the time field. In the System.currentTimeMillis 2219 * timebase. 2220 * 2221 * @deprecated Use {@link Builder} instead. 2222 */ 2223 @Deprecated Notification(int icon, CharSequence tickerText, long when)2224 public Notification(int icon, CharSequence tickerText, long when) 2225 { 2226 this.icon = icon; 2227 this.tickerText = tickerText; 2228 this.when = when; 2229 this.creationTime = System.currentTimeMillis(); 2230 } 2231 2232 /** 2233 * Unflatten the notification from a parcel. 2234 */ 2235 @SuppressWarnings("unchecked") Notification(Parcel parcel)2236 public Notification(Parcel parcel) { 2237 // IMPORTANT: Add unmarshaling code in readFromParcel as the pending 2238 // intents in extras are always written as the last entry. 2239 readFromParcelImpl(parcel); 2240 // Must be read last! 2241 allPendingIntents = (ArraySet<PendingIntent>) parcel.readArraySet(null); 2242 } 2243 readFromParcelImpl(Parcel parcel)2244 private void readFromParcelImpl(Parcel parcel) 2245 { 2246 int version = parcel.readInt(); 2247 2248 mWhitelistToken = parcel.readStrongBinder(); 2249 if (mWhitelistToken == null) { 2250 mWhitelistToken = processWhitelistToken; 2251 } 2252 // Propagate this token to all pending intents that are unmarshalled from the parcel. 2253 parcel.setClassCookie(PendingIntent.class, mWhitelistToken); 2254 2255 when = parcel.readLong(); 2256 creationTime = parcel.readLong(); 2257 if (parcel.readInt() != 0) { 2258 mSmallIcon = Icon.CREATOR.createFromParcel(parcel); 2259 if (mSmallIcon.getType() == Icon.TYPE_RESOURCE) { 2260 icon = mSmallIcon.getResId(); 2261 } 2262 } 2263 number = parcel.readInt(); 2264 if (parcel.readInt() != 0) { 2265 contentIntent = PendingIntent.CREATOR.createFromParcel(parcel); 2266 } 2267 if (parcel.readInt() != 0) { 2268 deleteIntent = PendingIntent.CREATOR.createFromParcel(parcel); 2269 } 2270 if (parcel.readInt() != 0) { 2271 tickerText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel); 2272 } 2273 if (parcel.readInt() != 0) { 2274 tickerView = RemoteViews.CREATOR.createFromParcel(parcel); 2275 } 2276 if (parcel.readInt() != 0) { 2277 contentView = RemoteViews.CREATOR.createFromParcel(parcel); 2278 } 2279 if (parcel.readInt() != 0) { 2280 mLargeIcon = Icon.CREATOR.createFromParcel(parcel); 2281 } 2282 defaults = parcel.readInt(); 2283 flags = parcel.readInt(); 2284 if (parcel.readInt() != 0) { 2285 sound = Uri.CREATOR.createFromParcel(parcel); 2286 } 2287 2288 audioStreamType = parcel.readInt(); 2289 if (parcel.readInt() != 0) { 2290 audioAttributes = AudioAttributes.CREATOR.createFromParcel(parcel); 2291 } 2292 vibrate = parcel.createLongArray(); 2293 ledARGB = parcel.readInt(); 2294 ledOnMS = parcel.readInt(); 2295 ledOffMS = parcel.readInt(); 2296 iconLevel = parcel.readInt(); 2297 2298 if (parcel.readInt() != 0) { 2299 fullScreenIntent = PendingIntent.CREATOR.createFromParcel(parcel); 2300 } 2301 2302 priority = parcel.readInt(); 2303 2304 category = parcel.readString8(); 2305 2306 mGroupKey = parcel.readString8(); 2307 2308 mSortKey = parcel.readString8(); 2309 2310 extras = Bundle.setDefusable(parcel.readBundle(), true); // may be null 2311 fixDuplicateExtras(); 2312 2313 actions = parcel.createTypedArray(Action.CREATOR); // may be null 2314 2315 if (parcel.readInt() != 0) { 2316 bigContentView = RemoteViews.CREATOR.createFromParcel(parcel); 2317 } 2318 2319 if (parcel.readInt() != 0) { 2320 headsUpContentView = RemoteViews.CREATOR.createFromParcel(parcel); 2321 } 2322 2323 visibility = parcel.readInt(); 2324 2325 if (parcel.readInt() != 0) { 2326 publicVersion = Notification.CREATOR.createFromParcel(parcel); 2327 } 2328 2329 color = parcel.readInt(); 2330 2331 if (parcel.readInt() != 0) { 2332 mChannelId = parcel.readString8(); 2333 } 2334 mTimeout = parcel.readLong(); 2335 2336 if (parcel.readInt() != 0) { 2337 mShortcutId = parcel.readString8(); 2338 } 2339 2340 if (parcel.readInt() != 0) { 2341 mLocusId = LocusId.CREATOR.createFromParcel(parcel); 2342 } 2343 2344 mBadgeIcon = parcel.readInt(); 2345 2346 if (parcel.readInt() != 0) { 2347 mSettingsText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel); 2348 } 2349 2350 mGroupAlertBehavior = parcel.readInt(); 2351 if (parcel.readInt() != 0) { 2352 mBubbleMetadata = BubbleMetadata.CREATOR.createFromParcel(parcel); 2353 } 2354 2355 mAllowSystemGeneratedContextualActions = parcel.readBoolean(); 2356 } 2357 2358 @Override clone()2359 public Notification clone() { 2360 Notification that = new Notification(); 2361 cloneInto(that, true); 2362 return that; 2363 } 2364 2365 /** 2366 * Copy all (or if heavy is false, all except Bitmaps and RemoteViews) members 2367 * of this into that. 2368 * @hide 2369 */ cloneInto(Notification that, boolean heavy)2370 public void cloneInto(Notification that, boolean heavy) { 2371 that.mWhitelistToken = this.mWhitelistToken; 2372 that.when = this.when; 2373 that.creationTime = this.creationTime; 2374 that.mSmallIcon = this.mSmallIcon; 2375 that.number = this.number; 2376 2377 // PendingIntents are global, so there's no reason (or way) to clone them. 2378 that.contentIntent = this.contentIntent; 2379 that.deleteIntent = this.deleteIntent; 2380 that.fullScreenIntent = this.fullScreenIntent; 2381 2382 if (this.tickerText != null) { 2383 that.tickerText = this.tickerText.toString(); 2384 } 2385 if (heavy && this.tickerView != null) { 2386 that.tickerView = this.tickerView.clone(); 2387 } 2388 if (heavy && this.contentView != null) { 2389 that.contentView = this.contentView.clone(); 2390 } 2391 if (heavy && this.mLargeIcon != null) { 2392 that.mLargeIcon = this.mLargeIcon; 2393 } 2394 that.iconLevel = this.iconLevel; 2395 that.sound = this.sound; // android.net.Uri is immutable 2396 that.audioStreamType = this.audioStreamType; 2397 if (this.audioAttributes != null) { 2398 that.audioAttributes = new AudioAttributes.Builder(this.audioAttributes).build(); 2399 } 2400 2401 final long[] vibrate = this.vibrate; 2402 if (vibrate != null) { 2403 final int N = vibrate.length; 2404 final long[] vib = that.vibrate = new long[N]; 2405 System.arraycopy(vibrate, 0, vib, 0, N); 2406 } 2407 2408 that.ledARGB = this.ledARGB; 2409 that.ledOnMS = this.ledOnMS; 2410 that.ledOffMS = this.ledOffMS; 2411 that.defaults = this.defaults; 2412 2413 that.flags = this.flags; 2414 2415 that.priority = this.priority; 2416 2417 that.category = this.category; 2418 2419 that.mGroupKey = this.mGroupKey; 2420 2421 that.mSortKey = this.mSortKey; 2422 2423 if (this.extras != null) { 2424 try { 2425 that.extras = new Bundle(this.extras); 2426 // will unparcel 2427 that.extras.size(); 2428 } catch (BadParcelableException e) { 2429 Log.e(TAG, "could not unparcel extras from notification: " + this, e); 2430 that.extras = null; 2431 } 2432 } 2433 2434 if (!ArrayUtils.isEmpty(allPendingIntents)) { 2435 that.allPendingIntents = new ArraySet<>(allPendingIntents); 2436 } 2437 2438 if (this.actions != null) { 2439 that.actions = new Action[this.actions.length]; 2440 for(int i=0; i<this.actions.length; i++) { 2441 if ( this.actions[i] != null) { 2442 that.actions[i] = this.actions[i].clone(); 2443 } 2444 } 2445 } 2446 2447 if (heavy && this.bigContentView != null) { 2448 that.bigContentView = this.bigContentView.clone(); 2449 } 2450 2451 if (heavy && this.headsUpContentView != null) { 2452 that.headsUpContentView = this.headsUpContentView.clone(); 2453 } 2454 2455 that.visibility = this.visibility; 2456 2457 if (this.publicVersion != null) { 2458 that.publicVersion = new Notification(); 2459 this.publicVersion.cloneInto(that.publicVersion, heavy); 2460 } 2461 2462 that.color = this.color; 2463 2464 that.mChannelId = this.mChannelId; 2465 that.mTimeout = this.mTimeout; 2466 that.mShortcutId = this.mShortcutId; 2467 that.mLocusId = this.mLocusId; 2468 that.mBadgeIcon = this.mBadgeIcon; 2469 that.mSettingsText = this.mSettingsText; 2470 that.mGroupAlertBehavior = this.mGroupAlertBehavior; 2471 that.mBubbleMetadata = this.mBubbleMetadata; 2472 that.mAllowSystemGeneratedContextualActions = this.mAllowSystemGeneratedContextualActions; 2473 2474 if (!heavy) { 2475 that.lightenPayload(); // will clean out extras 2476 } 2477 } 2478 2479 /** 2480 * Note all {@link Uri} that are referenced internally, with the expectation 2481 * that Uri permission grants will need to be issued to ensure the recipient 2482 * of this object is able to render its contents. 2483 * 2484 * @hide 2485 */ visitUris(@onNull Consumer<Uri> visitor)2486 public void visitUris(@NonNull Consumer<Uri> visitor) { 2487 visitor.accept(sound); 2488 2489 if (tickerView != null) tickerView.visitUris(visitor); 2490 if (contentView != null) contentView.visitUris(visitor); 2491 if (bigContentView != null) bigContentView.visitUris(visitor); 2492 if (headsUpContentView != null) headsUpContentView.visitUris(visitor); 2493 2494 if (extras != null) { 2495 visitor.accept(extras.getParcelable(EXTRA_AUDIO_CONTENTS_URI)); 2496 if (extras.containsKey(EXTRA_BACKGROUND_IMAGE_URI)) { 2497 visitor.accept(Uri.parse(extras.getString(EXTRA_BACKGROUND_IMAGE_URI))); 2498 } 2499 2500 ArrayList<Person> people = extras.getParcelableArrayList(EXTRA_PEOPLE_LIST); 2501 if (people != null && !people.isEmpty()) { 2502 for (Person p : people) { 2503 if (p.getIconUri() != null) { 2504 visitor.accept(p.getIconUri()); 2505 } 2506 } 2507 } 2508 2509 final Person person = extras.getParcelable(EXTRA_MESSAGING_PERSON); 2510 if (person != null && person.getIconUri() != null) { 2511 visitor.accept(person.getIconUri()); 2512 } 2513 } 2514 2515 if (MessagingStyle.class.equals(getNotificationStyle()) && extras != null) { 2516 final Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES); 2517 if (!ArrayUtils.isEmpty(messages)) { 2518 for (MessagingStyle.Message message : MessagingStyle.Message 2519 .getMessagesFromBundleArray(messages)) { 2520 visitor.accept(message.getDataUri()); 2521 2522 Person senderPerson = message.getSenderPerson(); 2523 if (senderPerson != null && senderPerson.getIconUri() != null) { 2524 visitor.accept(senderPerson.getIconUri()); 2525 } 2526 } 2527 } 2528 2529 final Parcelable[] historic = extras.getParcelableArray(EXTRA_HISTORIC_MESSAGES); 2530 if (!ArrayUtils.isEmpty(historic)) { 2531 for (MessagingStyle.Message message : MessagingStyle.Message 2532 .getMessagesFromBundleArray(historic)) { 2533 visitor.accept(message.getDataUri()); 2534 2535 Person senderPerson = message.getSenderPerson(); 2536 if (senderPerson != null && senderPerson.getIconUri() != null) { 2537 visitor.accept(senderPerson.getIconUri()); 2538 } 2539 } 2540 } 2541 } 2542 2543 if (mBubbleMetadata != null && mBubbleMetadata.getIcon() != null) { 2544 final Icon icon = mBubbleMetadata.getIcon(); 2545 final int iconType = icon.getType(); 2546 if (iconType == TYPE_URI_ADAPTIVE_BITMAP || iconType == TYPE_URI) { 2547 visitor.accept(icon.getUri()); 2548 } 2549 } 2550 } 2551 2552 /** 2553 * Removes heavyweight parts of the Notification object for archival or for sending to 2554 * listeners when the full contents are not necessary. 2555 * @hide 2556 */ lightenPayload()2557 public final void lightenPayload() { 2558 tickerView = null; 2559 contentView = null; 2560 bigContentView = null; 2561 headsUpContentView = null; 2562 mLargeIcon = null; 2563 if (extras != null && !extras.isEmpty()) { 2564 final Set<String> keyset = extras.keySet(); 2565 final int N = keyset.size(); 2566 final String[] keys = keyset.toArray(new String[N]); 2567 for (int i=0; i<N; i++) { 2568 final String key = keys[i]; 2569 if (TvExtender.EXTRA_TV_EXTENDER.equals(key)) { 2570 continue; 2571 } 2572 final Object obj = extras.get(key); 2573 if (obj != null && 2574 ( obj instanceof Parcelable 2575 || obj instanceof Parcelable[] 2576 || obj instanceof SparseArray 2577 || obj instanceof ArrayList)) { 2578 extras.remove(key); 2579 } 2580 } 2581 } 2582 } 2583 2584 /** 2585 * Make sure this CharSequence is safe to put into a bundle, which basically 2586 * means it had better not be some custom Parcelable implementation. 2587 * @hide 2588 */ safeCharSequence(CharSequence cs)2589 public static CharSequence safeCharSequence(CharSequence cs) { 2590 if (cs == null) return cs; 2591 if (cs.length() > MAX_CHARSEQUENCE_LENGTH) { 2592 cs = cs.subSequence(0, MAX_CHARSEQUENCE_LENGTH); 2593 } 2594 if (cs instanceof Parcelable) { 2595 Log.e(TAG, "warning: " + cs.getClass().getCanonicalName() 2596 + " instance is a custom Parcelable and not allowed in Notification"); 2597 return cs.toString(); 2598 } 2599 return removeTextSizeSpans(cs); 2600 } 2601 removeTextSizeSpans(CharSequence charSequence)2602 private static CharSequence removeTextSizeSpans(CharSequence charSequence) { 2603 if (charSequence instanceof Spanned) { 2604 Spanned ss = (Spanned) charSequence; 2605 Object[] spans = ss.getSpans(0, ss.length(), Object.class); 2606 SpannableStringBuilder builder = new SpannableStringBuilder(ss.toString()); 2607 for (Object span : spans) { 2608 Object resultSpan = span; 2609 if (resultSpan instanceof CharacterStyle) { 2610 resultSpan = ((CharacterStyle) span).getUnderlying(); 2611 } 2612 if (resultSpan instanceof TextAppearanceSpan) { 2613 TextAppearanceSpan originalSpan = (TextAppearanceSpan) resultSpan; 2614 resultSpan = new TextAppearanceSpan( 2615 originalSpan.getFamily(), 2616 originalSpan.getTextStyle(), 2617 -1, 2618 originalSpan.getTextColor(), 2619 originalSpan.getLinkTextColor()); 2620 } else if (resultSpan instanceof RelativeSizeSpan 2621 || resultSpan instanceof AbsoluteSizeSpan) { 2622 continue; 2623 } else { 2624 resultSpan = span; 2625 } 2626 builder.setSpan(resultSpan, ss.getSpanStart(span), ss.getSpanEnd(span), 2627 ss.getSpanFlags(span)); 2628 } 2629 return builder; 2630 } 2631 return charSequence; 2632 } 2633 describeContents()2634 public int describeContents() { 2635 return 0; 2636 } 2637 2638 /** 2639 * Flatten this notification into a parcel. 2640 */ writeToParcel(Parcel parcel, int flags)2641 public void writeToParcel(Parcel parcel, int flags) { 2642 // We need to mark all pending intents getting into the notification 2643 // system as being put there to later allow the notification ranker 2644 // to launch them and by doing so add the app to the battery saver white 2645 // list for a short period of time. The problem is that the system 2646 // cannot look into the extras as there may be parcelables there that 2647 // the platform does not know how to handle. To go around that we have 2648 // an explicit list of the pending intents in the extras bundle. 2649 final boolean collectPendingIntents = (allPendingIntents == null); 2650 if (collectPendingIntents) { 2651 PendingIntent.setOnMarshaledListener( 2652 (PendingIntent intent, Parcel out, int outFlags) -> { 2653 if (parcel == out) { 2654 synchronized (this) { 2655 if (allPendingIntents == null) { 2656 allPendingIntents = new ArraySet<>(); 2657 } 2658 allPendingIntents.add(intent); 2659 } 2660 } 2661 }); 2662 } 2663 try { 2664 // IMPORTANT: Add marshaling code in writeToParcelImpl as we 2665 // want to intercept all pending events written to the parcel. 2666 writeToParcelImpl(parcel, flags); 2667 synchronized (this) { 2668 // Must be written last! 2669 parcel.writeArraySet(allPendingIntents); 2670 } 2671 } finally { 2672 if (collectPendingIntents) { 2673 PendingIntent.setOnMarshaledListener(null); 2674 } 2675 } 2676 } 2677 writeToParcelImpl(Parcel parcel, int flags)2678 private void writeToParcelImpl(Parcel parcel, int flags) { 2679 parcel.writeInt(1); 2680 2681 parcel.writeStrongBinder(mWhitelistToken); 2682 parcel.writeLong(when); 2683 parcel.writeLong(creationTime); 2684 if (mSmallIcon == null && icon != 0) { 2685 // you snuck an icon in here without using the builder; let's try to keep it 2686 mSmallIcon = Icon.createWithResource("", icon); 2687 } 2688 if (mSmallIcon != null) { 2689 parcel.writeInt(1); 2690 mSmallIcon.writeToParcel(parcel, 0); 2691 } else { 2692 parcel.writeInt(0); 2693 } 2694 parcel.writeInt(number); 2695 if (contentIntent != null) { 2696 parcel.writeInt(1); 2697 contentIntent.writeToParcel(parcel, 0); 2698 } else { 2699 parcel.writeInt(0); 2700 } 2701 if (deleteIntent != null) { 2702 parcel.writeInt(1); 2703 deleteIntent.writeToParcel(parcel, 0); 2704 } else { 2705 parcel.writeInt(0); 2706 } 2707 if (tickerText != null) { 2708 parcel.writeInt(1); 2709 TextUtils.writeToParcel(tickerText, parcel, flags); 2710 } else { 2711 parcel.writeInt(0); 2712 } 2713 if (tickerView != null) { 2714 parcel.writeInt(1); 2715 tickerView.writeToParcel(parcel, 0); 2716 } else { 2717 parcel.writeInt(0); 2718 } 2719 if (contentView != null) { 2720 parcel.writeInt(1); 2721 contentView.writeToParcel(parcel, 0); 2722 } else { 2723 parcel.writeInt(0); 2724 } 2725 if (mLargeIcon == null && largeIcon != null) { 2726 // you snuck an icon in here without using the builder; let's try to keep it 2727 mLargeIcon = Icon.createWithBitmap(largeIcon); 2728 } 2729 if (mLargeIcon != null) { 2730 parcel.writeInt(1); 2731 mLargeIcon.writeToParcel(parcel, 0); 2732 } else { 2733 parcel.writeInt(0); 2734 } 2735 2736 parcel.writeInt(defaults); 2737 parcel.writeInt(this.flags); 2738 2739 if (sound != null) { 2740 parcel.writeInt(1); 2741 sound.writeToParcel(parcel, 0); 2742 } else { 2743 parcel.writeInt(0); 2744 } 2745 parcel.writeInt(audioStreamType); 2746 2747 if (audioAttributes != null) { 2748 parcel.writeInt(1); 2749 audioAttributes.writeToParcel(parcel, 0); 2750 } else { 2751 parcel.writeInt(0); 2752 } 2753 2754 parcel.writeLongArray(vibrate); 2755 parcel.writeInt(ledARGB); 2756 parcel.writeInt(ledOnMS); 2757 parcel.writeInt(ledOffMS); 2758 parcel.writeInt(iconLevel); 2759 2760 if (fullScreenIntent != null) { 2761 parcel.writeInt(1); 2762 fullScreenIntent.writeToParcel(parcel, 0); 2763 } else { 2764 parcel.writeInt(0); 2765 } 2766 2767 parcel.writeInt(priority); 2768 2769 parcel.writeString8(category); 2770 2771 parcel.writeString8(mGroupKey); 2772 2773 parcel.writeString8(mSortKey); 2774 2775 parcel.writeBundle(extras); // null ok 2776 2777 parcel.writeTypedArray(actions, 0); // null ok 2778 2779 if (bigContentView != null) { 2780 parcel.writeInt(1); 2781 bigContentView.writeToParcel(parcel, 0); 2782 } else { 2783 parcel.writeInt(0); 2784 } 2785 2786 if (headsUpContentView != null) { 2787 parcel.writeInt(1); 2788 headsUpContentView.writeToParcel(parcel, 0); 2789 } else { 2790 parcel.writeInt(0); 2791 } 2792 2793 parcel.writeInt(visibility); 2794 2795 if (publicVersion != null) { 2796 parcel.writeInt(1); 2797 publicVersion.writeToParcel(parcel, 0); 2798 } else { 2799 parcel.writeInt(0); 2800 } 2801 2802 parcel.writeInt(color); 2803 2804 if (mChannelId != null) { 2805 parcel.writeInt(1); 2806 parcel.writeString8(mChannelId); 2807 } else { 2808 parcel.writeInt(0); 2809 } 2810 parcel.writeLong(mTimeout); 2811 2812 if (mShortcutId != null) { 2813 parcel.writeInt(1); 2814 parcel.writeString8(mShortcutId); 2815 } else { 2816 parcel.writeInt(0); 2817 } 2818 2819 if (mLocusId != null) { 2820 parcel.writeInt(1); 2821 mLocusId.writeToParcel(parcel, 0); 2822 } else { 2823 parcel.writeInt(0); 2824 } 2825 2826 parcel.writeInt(mBadgeIcon); 2827 2828 if (mSettingsText != null) { 2829 parcel.writeInt(1); 2830 TextUtils.writeToParcel(mSettingsText, parcel, flags); 2831 } else { 2832 parcel.writeInt(0); 2833 } 2834 2835 parcel.writeInt(mGroupAlertBehavior); 2836 2837 if (mBubbleMetadata != null) { 2838 parcel.writeInt(1); 2839 mBubbleMetadata.writeToParcel(parcel, 0); 2840 } else { 2841 parcel.writeInt(0); 2842 } 2843 2844 parcel.writeBoolean(mAllowSystemGeneratedContextualActions); 2845 2846 // mUsesStandardHeader is not written because it should be recomputed in listeners 2847 } 2848 2849 /** 2850 * Parcelable.Creator that instantiates Notification objects 2851 */ 2852 public static final @android.annotation.NonNull Parcelable.Creator<Notification> CREATOR 2853 = new Parcelable.Creator<Notification>() 2854 { 2855 public Notification createFromParcel(Parcel parcel) 2856 { 2857 return new Notification(parcel); 2858 } 2859 2860 public Notification[] newArray(int size) 2861 { 2862 return new Notification[size]; 2863 } 2864 }; 2865 2866 /** 2867 * @hide 2868 */ areActionsVisiblyDifferent(Notification first, Notification second)2869 public static boolean areActionsVisiblyDifferent(Notification first, Notification second) { 2870 Notification.Action[] firstAs = first.actions; 2871 Notification.Action[] secondAs = second.actions; 2872 if (firstAs == null && secondAs != null || firstAs != null && secondAs == null) { 2873 return true; 2874 } 2875 if (firstAs != null && secondAs != null) { 2876 if (firstAs.length != secondAs.length) { 2877 return true; 2878 } 2879 for (int i = 0; i < firstAs.length; i++) { 2880 if (!Objects.equals(String.valueOf(firstAs[i].title), 2881 String.valueOf(secondAs[i].title))) { 2882 return true; 2883 } 2884 RemoteInput[] firstRs = firstAs[i].getRemoteInputs(); 2885 RemoteInput[] secondRs = secondAs[i].getRemoteInputs(); 2886 if (firstRs == null) { 2887 firstRs = new RemoteInput[0]; 2888 } 2889 if (secondRs == null) { 2890 secondRs = new RemoteInput[0]; 2891 } 2892 if (firstRs.length != secondRs.length) { 2893 return true; 2894 } 2895 for (int j = 0; j < firstRs.length; j++) { 2896 if (!Objects.equals(String.valueOf(firstRs[j].getLabel()), 2897 String.valueOf(secondRs[j].getLabel()))) { 2898 return true; 2899 } 2900 } 2901 } 2902 } 2903 return false; 2904 } 2905 2906 /** 2907 * @hide 2908 */ areStyledNotificationsVisiblyDifferent(Builder first, Builder second)2909 public static boolean areStyledNotificationsVisiblyDifferent(Builder first, Builder second) { 2910 if (first.getStyle() == null) { 2911 return second.getStyle() != null; 2912 } 2913 if (second.getStyle() == null) { 2914 return true; 2915 } 2916 return first.getStyle().areNotificationsVisiblyDifferent(second.getStyle()); 2917 } 2918 2919 /** 2920 * @hide 2921 */ areRemoteViewsChanged(Builder first, Builder second)2922 public static boolean areRemoteViewsChanged(Builder first, Builder second) { 2923 if (!Objects.equals(first.usesStandardHeader(), second.usesStandardHeader())) { 2924 return true; 2925 } 2926 2927 if (areRemoteViewsChanged(first.mN.contentView, second.mN.contentView)) { 2928 return true; 2929 } 2930 if (areRemoteViewsChanged(first.mN.bigContentView, second.mN.bigContentView)) { 2931 return true; 2932 } 2933 if (areRemoteViewsChanged(first.mN.headsUpContentView, second.mN.headsUpContentView)) { 2934 return true; 2935 } 2936 2937 return false; 2938 } 2939 areRemoteViewsChanged(RemoteViews first, RemoteViews second)2940 private static boolean areRemoteViewsChanged(RemoteViews first, RemoteViews second) { 2941 if (first == null && second == null) { 2942 return false; 2943 } 2944 if (first == null && second != null || first != null && second == null) { 2945 return true; 2946 } 2947 2948 if (!Objects.equals(first.getLayoutId(), second.getLayoutId())) { 2949 return true; 2950 } 2951 2952 if (!Objects.equals(first.getSequenceNumber(), second.getSequenceNumber())) { 2953 return true; 2954 } 2955 2956 return false; 2957 } 2958 2959 /** 2960 * Parcelling creates multiple copies of objects in {@code extras}. Fix them. 2961 * <p> 2962 * For backwards compatibility {@code extras} holds some references to "real" member data such 2963 * as {@link getLargeIcon()} which is mirrored by {@link #EXTRA_LARGE_ICON}. This is mostly 2964 * fine as long as the object stays in one process. 2965 * <p> 2966 * However, once the notification goes into a parcel each reference gets marshalled separately, 2967 * wasting memory. Especially with large images on Auto and TV, this is worth fixing. 2968 */ fixDuplicateExtras()2969 private void fixDuplicateExtras() { 2970 if (extras != null) { 2971 fixDuplicateExtra(mLargeIcon, EXTRA_LARGE_ICON); 2972 } 2973 } 2974 2975 /** 2976 * If we find an extra that's exactly the same as one of the "real" fields but refers to a 2977 * separate object, replace it with the field's version to avoid holding duplicate copies. 2978 */ fixDuplicateExtra(@ullable Parcelable original, @NonNull String extraName)2979 private void fixDuplicateExtra(@Nullable Parcelable original, @NonNull String extraName) { 2980 if (original != null && extras.getParcelable(extraName) != null) { 2981 extras.putParcelable(extraName, original); 2982 } 2983 } 2984 2985 /** 2986 * Sets the {@link #contentView} field to be a view with the standard "Latest Event" 2987 * layout. 2988 * 2989 * <p>Uses the {@link #icon} and {@link #when} fields to set the icon and time fields 2990 * in the view.</p> 2991 * @param context The context for your application / activity. 2992 * @param contentTitle The title that goes in the expanded entry. 2993 * @param contentText The text that goes in the expanded entry. 2994 * @param contentIntent The intent to launch when the user clicks the expanded notification. 2995 * If this is an activity, it must include the 2996 * {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK} flag, which requires 2997 * that you take care of task management as described in the 2998 * <a href="{@docRoot}guide/topics/fundamentals/tasks-and-back-stack.html">Tasks and Back 2999 * Stack</a> document. 3000 * 3001 * @deprecated Use {@link Builder} instead. 3002 * @removed 3003 */ 3004 @Deprecated setLatestEventInfo(Context context, CharSequence contentTitle, CharSequence contentText, PendingIntent contentIntent)3005 public void setLatestEventInfo(Context context, 3006 CharSequence contentTitle, CharSequence contentText, PendingIntent contentIntent) { 3007 if (context.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1){ 3008 Log.e(TAG, "setLatestEventInfo() is deprecated and you should feel deprecated.", 3009 new Throwable()); 3010 } 3011 3012 if (context.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N) { 3013 extras.putBoolean(EXTRA_SHOW_WHEN, true); 3014 } 3015 3016 // ensure that any information already set directly is preserved 3017 final Notification.Builder builder = new Notification.Builder(context, this); 3018 3019 // now apply the latestEventInfo fields 3020 if (contentTitle != null) { 3021 builder.setContentTitle(contentTitle); 3022 } 3023 if (contentText != null) { 3024 builder.setContentText(contentText); 3025 } 3026 builder.setContentIntent(contentIntent); 3027 3028 builder.build(); // callers expect this notification to be ready to use 3029 } 3030 3031 /** 3032 * @hide 3033 */ addFieldsFromContext(Context context, Notification notification)3034 public static void addFieldsFromContext(Context context, Notification notification) { 3035 addFieldsFromContext(context.getApplicationInfo(), notification); 3036 } 3037 3038 /** 3039 * @hide 3040 */ addFieldsFromContext(ApplicationInfo ai, Notification notification)3041 public static void addFieldsFromContext(ApplicationInfo ai, Notification notification) { 3042 notification.extras.putParcelable(EXTRA_BUILDER_APPLICATION_INFO, ai); 3043 } 3044 3045 /** 3046 * @hide 3047 */ dumpDebug(ProtoOutputStream proto, long fieldId)3048 public void dumpDebug(ProtoOutputStream proto, long fieldId) { 3049 long token = proto.start(fieldId); 3050 proto.write(NotificationProto.CHANNEL_ID, getChannelId()); 3051 proto.write(NotificationProto.HAS_TICKER_TEXT, this.tickerText != null); 3052 proto.write(NotificationProto.FLAGS, this.flags); 3053 proto.write(NotificationProto.COLOR, this.color); 3054 proto.write(NotificationProto.CATEGORY, this.category); 3055 proto.write(NotificationProto.GROUP_KEY, this.mGroupKey); 3056 proto.write(NotificationProto.SORT_KEY, this.mSortKey); 3057 if (this.actions != null) { 3058 proto.write(NotificationProto.ACTION_LENGTH, this.actions.length); 3059 } 3060 if (this.visibility >= VISIBILITY_SECRET && this.visibility <= VISIBILITY_PUBLIC) { 3061 proto.write(NotificationProto.VISIBILITY, this.visibility); 3062 } 3063 if (publicVersion != null) { 3064 publicVersion.dumpDebug(proto, NotificationProto.PUBLIC_VERSION); 3065 } 3066 proto.end(token); 3067 } 3068 3069 @Override toString()3070 public String toString() { 3071 StringBuilder sb = new StringBuilder(); 3072 sb.append("Notification(channel="); 3073 sb.append(getChannelId()); 3074 sb.append(" shortcut="); 3075 sb.append(getShortcutId()); 3076 sb.append(" contentView="); 3077 if (contentView != null) { 3078 sb.append(contentView.getPackage()); 3079 sb.append("/0x"); 3080 sb.append(Integer.toHexString(contentView.getLayoutId())); 3081 } else { 3082 sb.append("null"); 3083 } 3084 sb.append(" vibrate="); 3085 if ((this.defaults & DEFAULT_VIBRATE) != 0) { 3086 sb.append("default"); 3087 } else if (this.vibrate != null) { 3088 int N = this.vibrate.length-1; 3089 sb.append("["); 3090 for (int i=0; i<N; i++) { 3091 sb.append(this.vibrate[i]); 3092 sb.append(','); 3093 } 3094 if (N != -1) { 3095 sb.append(this.vibrate[N]); 3096 } 3097 sb.append("]"); 3098 } else { 3099 sb.append("null"); 3100 } 3101 sb.append(" sound="); 3102 if ((this.defaults & DEFAULT_SOUND) != 0) { 3103 sb.append("default"); 3104 } else if (this.sound != null) { 3105 sb.append(this.sound.toString()); 3106 } else { 3107 sb.append("null"); 3108 } 3109 if (this.tickerText != null) { 3110 sb.append(" tick"); 3111 } 3112 sb.append(" defaults=0x"); 3113 sb.append(Integer.toHexString(this.defaults)); 3114 sb.append(" flags=0x"); 3115 sb.append(Integer.toHexString(this.flags)); 3116 sb.append(String.format(" color=0x%08x", this.color)); 3117 if (this.category != null) { 3118 sb.append(" category="); 3119 sb.append(this.category); 3120 } 3121 if (this.mGroupKey != null) { 3122 sb.append(" groupKey="); 3123 sb.append(this.mGroupKey); 3124 } 3125 if (this.mSortKey != null) { 3126 sb.append(" sortKey="); 3127 sb.append(this.mSortKey); 3128 } 3129 if (actions != null) { 3130 sb.append(" actions="); 3131 sb.append(actions.length); 3132 } 3133 sb.append(" vis="); 3134 sb.append(visibilityToString(this.visibility)); 3135 if (this.publicVersion != null) { 3136 sb.append(" publicVersion="); 3137 sb.append(publicVersion.toString()); 3138 } 3139 if (this.mLocusId != null) { 3140 sb.append(" locusId="); 3141 sb.append(this.mLocusId); // LocusId.toString() is PII safe. 3142 } 3143 sb.append(")"); 3144 return sb.toString(); 3145 } 3146 3147 /** 3148 * {@hide} 3149 */ visibilityToString(int vis)3150 public static String visibilityToString(int vis) { 3151 switch (vis) { 3152 case VISIBILITY_PRIVATE: 3153 return "PRIVATE"; 3154 case VISIBILITY_PUBLIC: 3155 return "PUBLIC"; 3156 case VISIBILITY_SECRET: 3157 return "SECRET"; 3158 default: 3159 return "UNKNOWN(" + String.valueOf(vis) + ")"; 3160 } 3161 } 3162 3163 /** 3164 * {@hide} 3165 */ priorityToString(@riority int pri)3166 public static String priorityToString(@Priority int pri) { 3167 switch (pri) { 3168 case PRIORITY_MIN: 3169 return "MIN"; 3170 case PRIORITY_LOW: 3171 return "LOW"; 3172 case PRIORITY_DEFAULT: 3173 return "DEFAULT"; 3174 case PRIORITY_HIGH: 3175 return "HIGH"; 3176 case PRIORITY_MAX: 3177 return "MAX"; 3178 default: 3179 return "UNKNOWN(" + String.valueOf(pri) + ")"; 3180 } 3181 } 3182 3183 /** 3184 * @hide 3185 */ hasCompletedProgress()3186 public boolean hasCompletedProgress() { 3187 // not a progress notification; can't be complete 3188 if (!extras.containsKey(EXTRA_PROGRESS) 3189 || !extras.containsKey(EXTRA_PROGRESS_MAX)) { 3190 return false; 3191 } 3192 // many apps use max 0 for 'indeterminate'; not complete 3193 if (extras.getInt(EXTRA_PROGRESS_MAX) == 0) { 3194 return false; 3195 } 3196 return extras.getInt(EXTRA_PROGRESS) == extras.getInt(EXTRA_PROGRESS_MAX); 3197 } 3198 3199 /** @removed */ 3200 @Deprecated getChannel()3201 public String getChannel() { 3202 return mChannelId; 3203 } 3204 3205 /** 3206 * Returns the id of the channel this notification posts to. 3207 */ getChannelId()3208 public String getChannelId() { 3209 return mChannelId; 3210 } 3211 3212 /** @removed */ 3213 @Deprecated getTimeout()3214 public long getTimeout() { 3215 return mTimeout; 3216 } 3217 3218 /** 3219 * Returns the duration from posting after which this notification should be canceled by the 3220 * system, if it's not canceled already. 3221 */ getTimeoutAfter()3222 public long getTimeoutAfter() { 3223 return mTimeout; 3224 } 3225 3226 /** 3227 * Returns what icon should be shown for this notification if it is being displayed in a 3228 * Launcher that supports badging. Will be one of {@link #BADGE_ICON_NONE}, 3229 * {@link #BADGE_ICON_SMALL}, or {@link #BADGE_ICON_LARGE}. 3230 */ getBadgeIconType()3231 public int getBadgeIconType() { 3232 return mBadgeIcon; 3233 } 3234 3235 /** 3236 * Returns the {@link ShortcutInfo#getId() id} that this notification supersedes, if any. 3237 * 3238 * <p>Used by some Launchers that display notification content to hide shortcuts that duplicate 3239 * notifications. 3240 */ getShortcutId()3241 public String getShortcutId() { 3242 return mShortcutId; 3243 } 3244 3245 /** 3246 * Gets the {@link LocusId} associated with this notification. 3247 * 3248 * <p>Used by the device's intelligence services to correlate objects (such as 3249 * {@link ShortcutInfo} and {@link ContentCaptureContext}) that are correlated. 3250 */ 3251 @Nullable getLocusId()3252 public LocusId getLocusId() { 3253 return mLocusId; 3254 } 3255 3256 /** 3257 * Returns the settings text provided to {@link Builder#setSettingsText(CharSequence)}. 3258 */ getSettingsText()3259 public CharSequence getSettingsText() { 3260 return mSettingsText; 3261 } 3262 3263 /** 3264 * Returns which type of notifications in a group are responsible for audibly alerting the 3265 * user. See {@link #GROUP_ALERT_ALL}, {@link #GROUP_ALERT_CHILDREN}, 3266 * {@link #GROUP_ALERT_SUMMARY}. 3267 */ getGroupAlertBehavior()3268 public @GroupAlertBehavior int getGroupAlertBehavior() { 3269 return mGroupAlertBehavior; 3270 } 3271 3272 /** 3273 * Returns the bubble metadata that will be used to display app content in a floating window 3274 * over the existing foreground activity. 3275 */ 3276 @Nullable getBubbleMetadata()3277 public BubbleMetadata getBubbleMetadata() { 3278 return mBubbleMetadata; 3279 } 3280 3281 /** 3282 * Sets the {@link BubbleMetadata} for this notification. 3283 * @hide 3284 */ setBubbleMetadata(BubbleMetadata data)3285 public void setBubbleMetadata(BubbleMetadata data) { 3286 mBubbleMetadata = data; 3287 } 3288 3289 /** 3290 * Returns whether the platform is allowed (by the app developer) to generate contextual actions 3291 * for this notification. 3292 */ getAllowSystemGeneratedContextualActions()3293 public boolean getAllowSystemGeneratedContextualActions() { 3294 return mAllowSystemGeneratedContextualActions; 3295 } 3296 3297 /** 3298 * The small icon representing this notification in the status bar and content view. 3299 * 3300 * @return the small icon representing this notification. 3301 * 3302 * @see Builder#getSmallIcon() 3303 * @see Builder#setSmallIcon(Icon) 3304 */ getSmallIcon()3305 public Icon getSmallIcon() { 3306 return mSmallIcon; 3307 } 3308 3309 /** 3310 * Used when notifying to clean up legacy small icons. 3311 * @hide 3312 */ 3313 @UnsupportedAppUsage setSmallIcon(Icon icon)3314 public void setSmallIcon(Icon icon) { 3315 mSmallIcon = icon; 3316 } 3317 3318 /** 3319 * The large icon shown in this notification's content view. 3320 * @see Builder#getLargeIcon() 3321 * @see Builder#setLargeIcon(Icon) 3322 */ getLargeIcon()3323 public Icon getLargeIcon() { 3324 return mLargeIcon; 3325 } 3326 3327 /** 3328 * @hide 3329 */ 3330 @UnsupportedAppUsage isGroupSummary()3331 public boolean isGroupSummary() { 3332 return mGroupKey != null && (flags & FLAG_GROUP_SUMMARY) != 0; 3333 } 3334 3335 /** 3336 * @hide 3337 */ 3338 @UnsupportedAppUsage isGroupChild()3339 public boolean isGroupChild() { 3340 return mGroupKey != null && (flags & FLAG_GROUP_SUMMARY) == 0; 3341 } 3342 3343 /** 3344 * @hide 3345 */ suppressAlertingDueToGrouping()3346 public boolean suppressAlertingDueToGrouping() { 3347 if (isGroupSummary() 3348 && getGroupAlertBehavior() == Notification.GROUP_ALERT_CHILDREN) { 3349 return true; 3350 } else if (isGroupChild() 3351 && getGroupAlertBehavior() == Notification.GROUP_ALERT_SUMMARY) { 3352 return true; 3353 } 3354 return false; 3355 } 3356 3357 3358 /** 3359 * Finds and returns a remote input and its corresponding action. 3360 * 3361 * @param requiresFreeform requires the remoteinput to allow freeform or not. 3362 * @return the result pair, {@code null} if no result is found. 3363 */ 3364 @Nullable findRemoteInputActionPair(boolean requiresFreeform)3365 public Pair<RemoteInput, Action> findRemoteInputActionPair(boolean requiresFreeform) { 3366 if (actions == null) { 3367 return null; 3368 } 3369 for (Notification.Action action : actions) { 3370 if (action.getRemoteInputs() == null) { 3371 continue; 3372 } 3373 RemoteInput resultRemoteInput = null; 3374 for (RemoteInput remoteInput : action.getRemoteInputs()) { 3375 if (remoteInput.getAllowFreeFormInput() || !requiresFreeform) { 3376 resultRemoteInput = remoteInput; 3377 } 3378 } 3379 if (resultRemoteInput != null) { 3380 return Pair.create(resultRemoteInput, action); 3381 } 3382 } 3383 return null; 3384 } 3385 3386 /** 3387 * Returns the actions that are contextual (that is, suggested because of the content of the 3388 * notification) out of the actions in this notification. 3389 */ getContextualActions()3390 public @NonNull List<Notification.Action> getContextualActions() { 3391 if (actions == null) return Collections.emptyList(); 3392 3393 List<Notification.Action> contextualActions = new ArrayList<>(); 3394 for (Notification.Action action : actions) { 3395 if (action.isContextual()) { 3396 contextualActions.add(action); 3397 } 3398 } 3399 return contextualActions; 3400 } 3401 3402 /** 3403 * Builder class for {@link Notification} objects. 3404 * 3405 * Provides a convenient way to set the various fields of a {@link Notification} and generate 3406 * content views using the platform's notification layout template. If your app supports 3407 * versions of Android as old as API level 4, you can instead use 3408 * {@link android.support.v4.app.NotificationCompat.Builder NotificationCompat.Builder}, 3409 * available in the <a href="{@docRoot}tools/extras/support-library.html">Android Support 3410 * library</a>. 3411 * 3412 * <p>Example: 3413 * 3414 * <pre class="prettyprint"> 3415 * Notification noti = new Notification.Builder(mContext) 3416 * .setContentTitle("New mail from " + sender.toString()) 3417 * .setContentText(subject) 3418 * .setSmallIcon(R.drawable.new_mail) 3419 * .setLargeIcon(aBitmap) 3420 * .build(); 3421 * </pre> 3422 */ 3423 public static class Builder { 3424 /** 3425 * @hide 3426 */ 3427 public static final String EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT = 3428 "android.rebuild.contentViewActionCount"; 3429 /** 3430 * @hide 3431 */ 3432 public static final String EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT 3433 = "android.rebuild.bigViewActionCount"; 3434 /** 3435 * @hide 3436 */ 3437 public static final String EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT 3438 = "android.rebuild.hudViewActionCount"; 3439 3440 private static final boolean USE_ONLY_TITLE_IN_LOW_PRIORITY_SUMMARY = 3441 SystemProperties.getBoolean("notifications.only_title", true); 3442 3443 /** 3444 * The lightness difference that has to be added to the primary text color to obtain the 3445 * secondary text color when the background is light. 3446 */ 3447 private static final int LIGHTNESS_TEXT_DIFFERENCE_LIGHT = 20; 3448 3449 /** 3450 * The lightness difference that has to be added to the primary text color to obtain the 3451 * secondary text color when the background is dark. 3452 * A bit less then the above value, since it looks better on dark backgrounds. 3453 */ 3454 private static final int LIGHTNESS_TEXT_DIFFERENCE_DARK = -10; 3455 3456 private Context mContext; 3457 private Notification mN; 3458 private Bundle mUserExtras = new Bundle(); 3459 private Style mStyle; 3460 @UnsupportedAppUsage 3461 private ArrayList<Action> mActions = new ArrayList<Action>(MAX_ACTION_BUTTONS); 3462 private ArrayList<Person> mPersonList = new ArrayList<>(); 3463 private ContrastColorUtil mColorUtil; 3464 private boolean mIsLegacy; 3465 private boolean mIsLegacyInitialized; 3466 3467 /** 3468 * Caches a contrast-enhanced version of {@link #mCachedContrastColorIsFor}. 3469 */ 3470 private int mCachedContrastColor = COLOR_INVALID; 3471 private int mCachedContrastColorIsFor = COLOR_INVALID; 3472 3473 /** 3474 * A neutral color color that can be used for icons. 3475 */ 3476 private int mNeutralColor = COLOR_INVALID; 3477 3478 /** 3479 * Caches an instance of StandardTemplateParams. Note that this may have been used before, 3480 * so make sure to call {@link StandardTemplateParams#reset()} before using it. 3481 */ 3482 StandardTemplateParams mParams = new StandardTemplateParams(); 3483 private int mTextColorsAreForBackground = COLOR_INVALID; 3484 private int mPrimaryTextColor = COLOR_INVALID; 3485 private int mSecondaryTextColor = COLOR_INVALID; 3486 private int mBackgroundColor = COLOR_INVALID; 3487 private int mForegroundColor = COLOR_INVALID; 3488 /** 3489 * A temporary location where actions are stored. If != null the view originally has action 3490 * but doesn't have any for this inflation. 3491 */ 3492 private ArrayList<Action> mOriginalActions; 3493 private boolean mRebuildStyledRemoteViews; 3494 3495 private boolean mTintActionButtons; 3496 private boolean mInNightMode; 3497 3498 /** 3499 * Constructs a new Builder with the defaults: 3500 * 3501 * @param context 3502 * A {@link Context} that will be used by the Builder to construct the 3503 * RemoteViews. The Context will not be held past the lifetime of this Builder 3504 * object. 3505 * @param channelId 3506 * The constructed Notification will be posted on this 3507 * {@link NotificationChannel}. To use a NotificationChannel, it must first be 3508 * created using {@link NotificationManager#createNotificationChannel}. 3509 */ Builder(Context context, String channelId)3510 public Builder(Context context, String channelId) { 3511 this(context, (Notification) null); 3512 mN.mChannelId = channelId; 3513 } 3514 3515 /** 3516 * @deprecated use {@link #Builder(Context, String)} 3517 * instead. All posted Notifications must specify a NotificationChannel Id. 3518 */ 3519 @Deprecated Builder(Context context)3520 public Builder(Context context) { 3521 this(context, (Notification) null); 3522 } 3523 3524 /** 3525 * @hide 3526 */ Builder(Context context, Notification toAdopt)3527 public Builder(Context context, Notification toAdopt) { 3528 mContext = context; 3529 Resources res = mContext.getResources(); 3530 mTintActionButtons = res.getBoolean(R.bool.config_tintNotificationActionButtons); 3531 3532 if (res.getBoolean(R.bool.config_enableNightMode)) { 3533 Configuration currentConfig = res.getConfiguration(); 3534 mInNightMode = (currentConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK) 3535 == Configuration.UI_MODE_NIGHT_YES; 3536 } 3537 3538 if (toAdopt == null) { 3539 mN = new Notification(); 3540 if (context.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N) { 3541 mN.extras.putBoolean(EXTRA_SHOW_WHEN, true); 3542 } 3543 mN.priority = PRIORITY_DEFAULT; 3544 mN.visibility = VISIBILITY_PRIVATE; 3545 } else { 3546 mN = toAdopt; 3547 if (mN.actions != null) { 3548 Collections.addAll(mActions, mN.actions); 3549 } 3550 3551 if (mN.extras.containsKey(EXTRA_PEOPLE_LIST)) { 3552 ArrayList<Person> people = mN.extras.getParcelableArrayList(EXTRA_PEOPLE_LIST); 3553 mPersonList.addAll(people); 3554 } 3555 3556 if (mN.getSmallIcon() == null && mN.icon != 0) { 3557 setSmallIcon(mN.icon); 3558 } 3559 3560 if (mN.getLargeIcon() == null && mN.largeIcon != null) { 3561 setLargeIcon(mN.largeIcon); 3562 } 3563 3564 String templateClass = mN.extras.getString(EXTRA_TEMPLATE); 3565 if (!TextUtils.isEmpty(templateClass)) { 3566 final Class<? extends Style> styleClass 3567 = getNotificationStyleClass(templateClass); 3568 if (styleClass == null) { 3569 Log.d(TAG, "Unknown style class: " + templateClass); 3570 } else { 3571 try { 3572 final Constructor<? extends Style> ctor = 3573 styleClass.getDeclaredConstructor(); 3574 ctor.setAccessible(true); 3575 final Style style = ctor.newInstance(); 3576 style.restoreFromExtras(mN.extras); 3577 3578 if (style != null) { 3579 setStyle(style); 3580 } 3581 } catch (Throwable t) { 3582 Log.e(TAG, "Could not create Style", t); 3583 } 3584 } 3585 } 3586 } 3587 } 3588 getColorUtil()3589 private ContrastColorUtil getColorUtil() { 3590 if (mColorUtil == null) { 3591 mColorUtil = ContrastColorUtil.getInstance(mContext); 3592 } 3593 return mColorUtil; 3594 } 3595 3596 /** 3597 * From Android 11, messaging notifications (those that use {@link MessagingStyle}) that 3598 * use this method to link to a published long-lived sharing shortcut may appear in a 3599 * dedicated Conversation section of the shade and may show configuration options that 3600 * are unique to conversations. This behavior should be reserved for person to person(s) 3601 * conversations where there is a likely social obligation for an individual to respond. 3602 * <p> 3603 * For example, the following are some examples of notifications that belong in the 3604 * conversation space: 3605 * <ul> 3606 * <li>1:1 conversations between two individuals</li> 3607 * <li>Group conversations between individuals where everyone can contribute</li> 3608 * </ul> 3609 * And the following are some examples of notifications that do not belong in the 3610 * conversation space: 3611 * <ul> 3612 * <li>Advertisements from a bot (even if personal and contextualized)</li> 3613 * <li>Engagement notifications from a bot</li> 3614 * <li>Directional conversations where there is an active speaker and many passive 3615 * individuals</li> 3616 * <li>Stream / posting updates from other individuals</li> 3617 * <li>Email, document comments, or other conversation types that are not real-time</li> 3618 * </ul> 3619 * </p> 3620 * 3621 * <p> 3622 * Additionally, this method can be used for all types of notifications to mark this 3623 * notification as duplicative of a Launcher shortcut. Launchers that show badges or 3624 * notification content may then suppress the shortcut in favor of the content of this 3625 * notification. 3626 * <p> 3627 * If this notification has {@link BubbleMetadata} attached that was created with 3628 * a shortcutId a check will be performed to ensure the shortcutId supplied to bubble 3629 * metadata matches the shortcutId set here, if one was set. If the shortcutId's were 3630 * specified but do not match, an exception is thrown. 3631 * 3632 * @param shortcutId the {@link ShortcutInfo#getId() id} of the shortcut this notification 3633 * is linked to 3634 * 3635 * @see BubbleMetadata.Builder#Builder(String) 3636 */ 3637 @NonNull setShortcutId(String shortcutId)3638 public Builder setShortcutId(String shortcutId) { 3639 mN.mShortcutId = shortcutId; 3640 return this; 3641 } 3642 3643 /** 3644 * Sets the {@link LocusId} associated with this notification. 3645 * 3646 * <p>This method should be called when the {@link LocusId} is used in other places (such 3647 * as {@link ShortcutInfo} and {@link ContentCaptureContext}) so the device's intelligence 3648 * services can correlate them. 3649 */ 3650 @NonNull setLocusId(@ullable LocusId locusId)3651 public Builder setLocusId(@Nullable LocusId locusId) { 3652 mN.mLocusId = locusId; 3653 return this; 3654 } 3655 3656 /** 3657 * Sets which icon to display as a badge for this notification. 3658 * 3659 * Must be one of {@link #BADGE_ICON_NONE}, {@link #BADGE_ICON_SMALL}, 3660 * {@link #BADGE_ICON_LARGE}. 3661 * 3662 * Note: This value might be ignored, for launchers that don't support badge icons. 3663 */ 3664 @NonNull setBadgeIconType(int icon)3665 public Builder setBadgeIconType(int icon) { 3666 mN.mBadgeIcon = icon; 3667 return this; 3668 } 3669 3670 /** 3671 * Sets the group alert behavior for this notification. Use this method to mute this 3672 * notification if alerts for this notification's group should be handled by a different 3673 * notification. This is only applicable for notifications that belong to a 3674 * {@link #setGroup(String) group}. This must be called on all notifications you want to 3675 * mute. For example, if you want only the summary of your group to make noise, all 3676 * children in the group should have the group alert behavior {@link #GROUP_ALERT_SUMMARY}. 3677 * 3678 * <p> The default value is {@link #GROUP_ALERT_ALL}.</p> 3679 */ 3680 @NonNull setGroupAlertBehavior(@roupAlertBehavior int groupAlertBehavior)3681 public Builder setGroupAlertBehavior(@GroupAlertBehavior int groupAlertBehavior) { 3682 mN.mGroupAlertBehavior = groupAlertBehavior; 3683 return this; 3684 } 3685 3686 /** 3687 * Sets the {@link BubbleMetadata} that will be used to display app content in a floating 3688 * window over the existing foreground activity. 3689 * 3690 * <p>This data will be ignored unless the notification is posted to a channel that 3691 * allows {@link NotificationChannel#canBubble() bubbles}.</p> 3692 * 3693 * <p>Notifications allowed to bubble that have valid bubble metadata will display in 3694 * collapsed state outside of the notification shade on unlocked devices. When a user 3695 * interacts with the collapsed state, the bubble intent will be invoked and displayed.</p> 3696 */ 3697 @NonNull setBubbleMetadata(@ullable BubbleMetadata data)3698 public Builder setBubbleMetadata(@Nullable BubbleMetadata data) { 3699 mN.mBubbleMetadata = data; 3700 return this; 3701 } 3702 3703 /** @removed */ 3704 @Deprecated setChannel(String channelId)3705 public Builder setChannel(String channelId) { 3706 mN.mChannelId = channelId; 3707 return this; 3708 } 3709 3710 /** 3711 * Specifies the channel the notification should be delivered on. 3712 */ 3713 @NonNull setChannelId(String channelId)3714 public Builder setChannelId(String channelId) { 3715 mN.mChannelId = channelId; 3716 return this; 3717 } 3718 3719 /** @removed */ 3720 @Deprecated setTimeout(long durationMs)3721 public Builder setTimeout(long durationMs) { 3722 mN.mTimeout = durationMs; 3723 return this; 3724 } 3725 3726 /** 3727 * Specifies a duration in milliseconds after which this notification should be canceled, 3728 * if it is not already canceled. 3729 */ 3730 @NonNull setTimeoutAfter(long durationMs)3731 public Builder setTimeoutAfter(long durationMs) { 3732 mN.mTimeout = durationMs; 3733 return this; 3734 } 3735 3736 /** 3737 * Add a timestamp pertaining to the notification (usually the time the event occurred). 3738 * 3739 * For apps targeting {@link android.os.Build.VERSION_CODES#N} and above, this time is not 3740 * shown anymore by default and must be opted into by using 3741 * {@link android.app.Notification.Builder#setShowWhen(boolean)} 3742 * 3743 * @see Notification#when 3744 */ 3745 @NonNull setWhen(long when)3746 public Builder setWhen(long when) { 3747 mN.when = when; 3748 return this; 3749 } 3750 3751 /** 3752 * Control whether the timestamp set with {@link #setWhen(long) setWhen} is shown 3753 * in the content view. 3754 * For apps targeting {@link android.os.Build.VERSION_CODES#N} and above, this defaults to 3755 * {@code false}. For earlier apps, the default is {@code true}. 3756 */ 3757 @NonNull setShowWhen(boolean show)3758 public Builder setShowWhen(boolean show) { 3759 mN.extras.putBoolean(EXTRA_SHOW_WHEN, show); 3760 return this; 3761 } 3762 3763 /** 3764 * Show the {@link Notification#when} field as a stopwatch. 3765 * 3766 * Instead of presenting <code>when</code> as a timestamp, the notification will show an 3767 * automatically updating display of the minutes and seconds since <code>when</code>. 3768 * 3769 * Useful when showing an elapsed time (like an ongoing phone call). 3770 * 3771 * The counter can also be set to count down to <code>when</code> when using 3772 * {@link #setChronometerCountDown(boolean)}. 3773 * 3774 * @see android.widget.Chronometer 3775 * @see Notification#when 3776 * @see #setChronometerCountDown(boolean) 3777 */ 3778 @NonNull setUsesChronometer(boolean b)3779 public Builder setUsesChronometer(boolean b) { 3780 mN.extras.putBoolean(EXTRA_SHOW_CHRONOMETER, b); 3781 return this; 3782 } 3783 3784 /** 3785 * Sets the Chronometer to count down instead of counting up. 3786 * 3787 * <p>This is only relevant if {@link #setUsesChronometer(boolean)} has been set to true. 3788 * If it isn't set the chronometer will count up. 3789 * 3790 * @see #setUsesChronometer(boolean) 3791 */ 3792 @NonNull setChronometerCountDown(boolean countDown)3793 public Builder setChronometerCountDown(boolean countDown) { 3794 mN.extras.putBoolean(EXTRA_CHRONOMETER_COUNT_DOWN, countDown); 3795 return this; 3796 } 3797 3798 /** 3799 * Set the small icon resource, which will be used to represent the notification in the 3800 * status bar. 3801 * 3802 3803 * The platform template for the expanded view will draw this icon in the left, unless a 3804 * {@link #setLargeIcon(Bitmap) large icon} has also been specified, in which case the small 3805 * icon will be moved to the right-hand side. 3806 * 3807 3808 * @param icon 3809 * A resource ID in the application's package of the drawable to use. 3810 * @see Notification#icon 3811 */ 3812 @NonNull setSmallIcon(@rawableRes int icon)3813 public Builder setSmallIcon(@DrawableRes int icon) { 3814 return setSmallIcon(icon != 0 3815 ? Icon.createWithResource(mContext, icon) 3816 : null); 3817 } 3818 3819 /** 3820 * A variant of {@link #setSmallIcon(int) setSmallIcon(int)} that takes an additional 3821 * level parameter for when the icon is a {@link android.graphics.drawable.LevelListDrawable 3822 * LevelListDrawable}. 3823 * 3824 * @param icon A resource ID in the application's package of the drawable to use. 3825 * @param level The level to use for the icon. 3826 * 3827 * @see Notification#icon 3828 * @see Notification#iconLevel 3829 */ 3830 @NonNull setSmallIcon(@rawableRes int icon, int level)3831 public Builder setSmallIcon(@DrawableRes int icon, int level) { 3832 mN.iconLevel = level; 3833 return setSmallIcon(icon); 3834 } 3835 3836 /** 3837 * Set the small icon, which will be used to represent the notification in the 3838 * status bar and content view (unless overridden there by a 3839 * {@link #setLargeIcon(Bitmap) large icon}). 3840 * 3841 * @param icon An Icon object to use. 3842 * @see Notification#icon 3843 */ 3844 @NonNull setSmallIcon(Icon icon)3845 public Builder setSmallIcon(Icon icon) { 3846 mN.setSmallIcon(icon); 3847 if (icon != null && icon.getType() == Icon.TYPE_RESOURCE) { 3848 mN.icon = icon.getResId(); 3849 } 3850 return this; 3851 } 3852 3853 /** 3854 * Set the first line of text in the platform notification template. 3855 */ 3856 @NonNull setContentTitle(CharSequence title)3857 public Builder setContentTitle(CharSequence title) { 3858 mN.extras.putCharSequence(EXTRA_TITLE, safeCharSequence(title)); 3859 return this; 3860 } 3861 3862 /** 3863 * Set the second line of text in the platform notification template. 3864 */ 3865 @NonNull setContentText(CharSequence text)3866 public Builder setContentText(CharSequence text) { 3867 mN.extras.putCharSequence(EXTRA_TEXT, safeCharSequence(text)); 3868 return this; 3869 } 3870 3871 /** 3872 * This provides some additional information that is displayed in the notification. No 3873 * guarantees are given where exactly it is displayed. 3874 * 3875 * <p>This information should only be provided if it provides an essential 3876 * benefit to the understanding of the notification. The more text you provide the 3877 * less readable it becomes. For example, an email client should only provide the account 3878 * name here if more than one email account has been added.</p> 3879 * 3880 * <p>As of {@link android.os.Build.VERSION_CODES#N} this information is displayed in the 3881 * notification header area. 3882 * 3883 * On Android versions before {@link android.os.Build.VERSION_CODES#N} 3884 * this will be shown in the third line of text in the platform notification template. 3885 * You should not be using {@link #setProgress(int, int, boolean)} at the 3886 * same time on those versions; they occupy the same place. 3887 * </p> 3888 */ 3889 @NonNull setSubText(CharSequence text)3890 public Builder setSubText(CharSequence text) { 3891 mN.extras.putCharSequence(EXTRA_SUB_TEXT, safeCharSequence(text)); 3892 return this; 3893 } 3894 3895 /** 3896 * Provides text that will appear as a link to your application's settings. 3897 * 3898 * <p>This text does not appear within notification {@link Style templates} but may 3899 * appear when the user uses an affordance to learn more about the notification. 3900 * Additionally, this text will not appear unless you provide a valid link target by 3901 * handling {@link #INTENT_CATEGORY_NOTIFICATION_PREFERENCES}. 3902 * 3903 * <p>This text is meant to be concise description about what the user can customize 3904 * when they click on this link. The recommended maximum length is 40 characters. 3905 * @param text 3906 * @return 3907 */ 3908 @NonNull setSettingsText(CharSequence text)3909 public Builder setSettingsText(CharSequence text) { 3910 mN.mSettingsText = safeCharSequence(text); 3911 return this; 3912 } 3913 3914 /** 3915 * Set the remote input history. 3916 * 3917 * This should be set to the most recent inputs that have been sent 3918 * through a {@link RemoteInput} of this Notification and cleared once the it is no 3919 * longer relevant (e.g. for chat notifications once the other party has responded). 3920 * 3921 * The most recent input must be stored at the 0 index, the second most recent at the 3922 * 1 index, etc. Note that the system will limit both how far back the inputs will be shown 3923 * and how much of each individual input is shown. 3924 * 3925 * <p>Note: The reply text will only be shown on notifications that have least one action 3926 * with a {@code RemoteInput}.</p> 3927 */ 3928 @NonNull setRemoteInputHistory(CharSequence[] text)3929 public Builder setRemoteInputHistory(CharSequence[] text) { 3930 if (text == null) { 3931 mN.extras.putCharSequenceArray(EXTRA_REMOTE_INPUT_HISTORY, null); 3932 } else { 3933 final int itemCount = Math.min(MAX_REPLY_HISTORY, text.length); 3934 CharSequence[] safe = new CharSequence[itemCount]; 3935 RemoteInputHistoryItem[] items = new RemoteInputHistoryItem[itemCount]; 3936 for (int i = 0; i < itemCount; i++) { 3937 safe[i] = safeCharSequence(text[i]); 3938 items[i] = new RemoteInputHistoryItem(text[i]); 3939 } 3940 mN.extras.putCharSequenceArray(EXTRA_REMOTE_INPUT_HISTORY, safe); 3941 3942 // Also add these messages as structured history items. 3943 mN.extras.putParcelableArray(EXTRA_REMOTE_INPUT_HISTORY_ITEMS, items); 3944 } 3945 return this; 3946 } 3947 3948 /** 3949 * Set the remote input history, with support for embedding URIs and mime types for 3950 * images and other media. 3951 * @hide 3952 */ 3953 @NonNull setRemoteInputHistory(RemoteInputHistoryItem[] items)3954 public Builder setRemoteInputHistory(RemoteInputHistoryItem[] items) { 3955 if (items == null) { 3956 mN.extras.putParcelableArray(EXTRA_REMOTE_INPUT_HISTORY_ITEMS, null); 3957 } else { 3958 final int itemCount = Math.min(MAX_REPLY_HISTORY, items.length); 3959 RemoteInputHistoryItem[] history = new RemoteInputHistoryItem[itemCount]; 3960 for (int i = 0; i < itemCount; i++) { 3961 history[i] = items[i]; 3962 } 3963 mN.extras.putParcelableArray(EXTRA_REMOTE_INPUT_HISTORY_ITEMS, history); 3964 } 3965 return this; 3966 } 3967 3968 /** 3969 * Sets whether remote history entries view should have a spinner. 3970 * @hide 3971 */ 3972 @NonNull setShowRemoteInputSpinner(boolean showSpinner)3973 public Builder setShowRemoteInputSpinner(boolean showSpinner) { 3974 mN.extras.putBoolean(EXTRA_SHOW_REMOTE_INPUT_SPINNER, showSpinner); 3975 return this; 3976 } 3977 3978 /** 3979 * Sets whether smart reply buttons should be hidden. 3980 * @hide 3981 */ 3982 @NonNull setHideSmartReplies(boolean hideSmartReplies)3983 public Builder setHideSmartReplies(boolean hideSmartReplies) { 3984 mN.extras.putBoolean(EXTRA_HIDE_SMART_REPLIES, hideSmartReplies); 3985 return this; 3986 } 3987 3988 /** 3989 * Sets the number of items this notification represents. May be displayed as a badge count 3990 * for Launchers that support badging. 3991 */ 3992 @NonNull setNumber(int number)3993 public Builder setNumber(int number) { 3994 mN.number = number; 3995 return this; 3996 } 3997 3998 /** 3999 * A small piece of additional information pertaining to this notification. 4000 * 4001 * The platform template will draw this on the last line of the notification, at the far 4002 * right (to the right of a smallIcon if it has been placed there). 4003 * 4004 * @deprecated use {@link #setSubText(CharSequence)} instead to set a text in the header. 4005 * For legacy apps targeting a version below {@link android.os.Build.VERSION_CODES#N} this 4006 * field will still show up, but the subtext will take precedence. 4007 */ 4008 @Deprecated setContentInfo(CharSequence info)4009 public Builder setContentInfo(CharSequence info) { 4010 mN.extras.putCharSequence(EXTRA_INFO_TEXT, safeCharSequence(info)); 4011 return this; 4012 } 4013 4014 /** 4015 * Set the progress this notification represents. 4016 * 4017 * The platform template will represent this using a {@link ProgressBar}. 4018 */ 4019 @NonNull setProgress(int max, int progress, boolean indeterminate)4020 public Builder setProgress(int max, int progress, boolean indeterminate) { 4021 mN.extras.putInt(EXTRA_PROGRESS, progress); 4022 mN.extras.putInt(EXTRA_PROGRESS_MAX, max); 4023 mN.extras.putBoolean(EXTRA_PROGRESS_INDETERMINATE, indeterminate); 4024 return this; 4025 } 4026 4027 /** 4028 * Supply a custom RemoteViews to use instead of the platform template. 4029 * 4030 * Use {@link #setCustomContentView(RemoteViews)} instead. 4031 */ 4032 @Deprecated setContent(RemoteViews views)4033 public Builder setContent(RemoteViews views) { 4034 return setCustomContentView(views); 4035 } 4036 4037 /** 4038 * Supply custom RemoteViews to use instead of the platform template. 4039 * 4040 * This will override the layout that would otherwise be constructed by this Builder 4041 * object. 4042 */ 4043 @NonNull setCustomContentView(RemoteViews contentView)4044 public Builder setCustomContentView(RemoteViews contentView) { 4045 mN.contentView = contentView; 4046 return this; 4047 } 4048 4049 /** 4050 * Supply custom RemoteViews to use instead of the platform template in the expanded form. 4051 * 4052 * This will override the expanded layout that would otherwise be constructed by this 4053 * Builder object. 4054 */ 4055 @NonNull setCustomBigContentView(RemoteViews contentView)4056 public Builder setCustomBigContentView(RemoteViews contentView) { 4057 mN.bigContentView = contentView; 4058 return this; 4059 } 4060 4061 /** 4062 * Supply custom RemoteViews to use instead of the platform template in the heads up dialog. 4063 * 4064 * This will override the heads-up layout that would otherwise be constructed by this 4065 * Builder object. 4066 */ 4067 @NonNull setCustomHeadsUpContentView(RemoteViews contentView)4068 public Builder setCustomHeadsUpContentView(RemoteViews contentView) { 4069 mN.headsUpContentView = contentView; 4070 return this; 4071 } 4072 4073 /** 4074 * Supply a {@link PendingIntent} to be sent when the notification is clicked. 4075 * 4076 * As of {@link android.os.Build.VERSION_CODES#HONEYCOMB}, if this field is unset and you 4077 * have specified a custom RemoteViews with {@link #setContent(RemoteViews)}, you can use 4078 * {@link RemoteViews#setOnClickPendingIntent RemoteViews.setOnClickPendingIntent(int,PendingIntent)} 4079 * to assign PendingIntents to individual views in that custom layout (i.e., to create 4080 * clickable buttons inside the notification view). 4081 * 4082 * @see Notification#contentIntent Notification.contentIntent 4083 */ 4084 @NonNull setContentIntent(PendingIntent intent)4085 public Builder setContentIntent(PendingIntent intent) { 4086 mN.contentIntent = intent; 4087 return this; 4088 } 4089 4090 /** 4091 * Supply a {@link PendingIntent} to send when the notification is cleared explicitly by the user. 4092 * 4093 * @see Notification#deleteIntent 4094 */ 4095 @NonNull setDeleteIntent(PendingIntent intent)4096 public Builder setDeleteIntent(PendingIntent intent) { 4097 mN.deleteIntent = intent; 4098 return this; 4099 } 4100 4101 /** 4102 * An intent to launch instead of posting the notification to the status bar. 4103 * Only for use with extremely high-priority notifications demanding the user's 4104 * <strong>immediate</strong> attention, such as an incoming phone call or 4105 * alarm clock that the user has explicitly set to a particular time. 4106 * If this facility is used for something else, please give the user an option 4107 * to turn it off and use a normal notification, as this can be extremely 4108 * disruptive. 4109 * 4110 * <p> 4111 * The system UI may choose to display a heads-up notification, instead of 4112 * launching this intent, while the user is using the device. 4113 * </p> 4114 * <p>Apps targeting {@link Build.VERSION_CODES#Q} and above will have to request 4115 * a permission ({@link android.Manifest.permission#USE_FULL_SCREEN_INTENT}) in order to 4116 * use full screen intents.</p> 4117 * 4118 * @param intent The pending intent to launch. 4119 * @param highPriority Passing true will cause this notification to be sent 4120 * even if other notifications are suppressed. 4121 * 4122 * @see Notification#fullScreenIntent 4123 */ 4124 @NonNull setFullScreenIntent(PendingIntent intent, boolean highPriority)4125 public Builder setFullScreenIntent(PendingIntent intent, boolean highPriority) { 4126 mN.fullScreenIntent = intent; 4127 setFlag(FLAG_HIGH_PRIORITY, highPriority); 4128 return this; 4129 } 4130 4131 /** 4132 * Set the "ticker" text which is sent to accessibility services. 4133 * 4134 * @see Notification#tickerText 4135 */ 4136 @NonNull setTicker(CharSequence tickerText)4137 public Builder setTicker(CharSequence tickerText) { 4138 mN.tickerText = safeCharSequence(tickerText); 4139 return this; 4140 } 4141 4142 /** 4143 * Obsolete version of {@link #setTicker(CharSequence)}. 4144 * 4145 */ 4146 @Deprecated setTicker(CharSequence tickerText, RemoteViews views)4147 public Builder setTicker(CharSequence tickerText, RemoteViews views) { 4148 setTicker(tickerText); 4149 // views is ignored 4150 return this; 4151 } 4152 4153 /** 4154 * Add a large icon to the notification content view. 4155 * 4156 * In the platform template, this image will be shown on the left of the notification view 4157 * in place of the {@link #setSmallIcon(Icon) small icon} (which will be placed in a small 4158 * badge atop the large icon). 4159 */ 4160 @NonNull setLargeIcon(Bitmap b)4161 public Builder setLargeIcon(Bitmap b) { 4162 return setLargeIcon(b != null ? Icon.createWithBitmap(b) : null); 4163 } 4164 4165 /** 4166 * Add a large icon to the notification content view. 4167 * 4168 * In the platform template, this image will be shown on the left of the notification view 4169 * in place of the {@link #setSmallIcon(Icon) small icon} (which will be placed in a small 4170 * badge atop the large icon). 4171 */ 4172 @NonNull setLargeIcon(Icon icon)4173 public Builder setLargeIcon(Icon icon) { 4174 mN.mLargeIcon = icon; 4175 mN.extras.putParcelable(EXTRA_LARGE_ICON, icon); 4176 return this; 4177 } 4178 4179 /** 4180 * Set the sound to play. 4181 * 4182 * It will be played using the {@link #AUDIO_ATTRIBUTES_DEFAULT default audio attributes} 4183 * for notifications. 4184 * 4185 * @deprecated use {@link NotificationChannel#setSound(Uri, AudioAttributes)} instead. 4186 */ 4187 @Deprecated setSound(Uri sound)4188 public Builder setSound(Uri sound) { 4189 mN.sound = sound; 4190 mN.audioAttributes = AUDIO_ATTRIBUTES_DEFAULT; 4191 return this; 4192 } 4193 4194 /** 4195 * Set the sound to play, along with a specific stream on which to play it. 4196 * 4197 * See {@link android.media.AudioManager} for the <code>STREAM_</code> constants. 4198 * 4199 * @deprecated use {@link NotificationChannel#setSound(Uri, AudioAttributes)}. 4200 */ 4201 @Deprecated setSound(Uri sound, int streamType)4202 public Builder setSound(Uri sound, int streamType) { 4203 PlayerBase.deprecateStreamTypeForPlayback(streamType, "Notification", "setSound()"); 4204 mN.sound = sound; 4205 mN.audioStreamType = streamType; 4206 return this; 4207 } 4208 4209 /** 4210 * Set the sound to play, along with specific {@link AudioAttributes audio attributes} to 4211 * use during playback. 4212 * 4213 * @deprecated use {@link NotificationChannel#setSound(Uri, AudioAttributes)} instead. 4214 * @see Notification#sound 4215 */ 4216 @Deprecated setSound(Uri sound, AudioAttributes audioAttributes)4217 public Builder setSound(Uri sound, AudioAttributes audioAttributes) { 4218 mN.sound = sound; 4219 mN.audioAttributes = audioAttributes; 4220 return this; 4221 } 4222 4223 /** 4224 * Set the vibration pattern to use. 4225 * 4226 * See {@link android.os.Vibrator#vibrate(long[], int)} for a discussion of the 4227 * <code>pattern</code> parameter. 4228 * 4229 * <p> 4230 * A notification that vibrates is more likely to be presented as a heads-up notification. 4231 * </p> 4232 * 4233 * @deprecated use {@link NotificationChannel#setVibrationPattern(long[])} instead. 4234 * @see Notification#vibrate 4235 */ 4236 @Deprecated setVibrate(long[] pattern)4237 public Builder setVibrate(long[] pattern) { 4238 mN.vibrate = pattern; 4239 return this; 4240 } 4241 4242 /** 4243 * Set the desired color for the indicator LED on the device, as well as the 4244 * blink duty cycle (specified in milliseconds). 4245 * 4246 4247 * Not all devices will honor all (or even any) of these values. 4248 * 4249 * @deprecated use {@link NotificationChannel#enableLights(boolean)} instead. 4250 * @see Notification#ledARGB 4251 * @see Notification#ledOnMS 4252 * @see Notification#ledOffMS 4253 */ 4254 @Deprecated setLights(@olorInt int argb, int onMs, int offMs)4255 public Builder setLights(@ColorInt int argb, int onMs, int offMs) { 4256 mN.ledARGB = argb; 4257 mN.ledOnMS = onMs; 4258 mN.ledOffMS = offMs; 4259 if (onMs != 0 || offMs != 0) { 4260 mN.flags |= FLAG_SHOW_LIGHTS; 4261 } 4262 return this; 4263 } 4264 4265 /** 4266 * Set whether this is an "ongoing" notification. 4267 * 4268 4269 * Ongoing notifications cannot be dismissed by the user, so your application or service 4270 * must take care of canceling them. 4271 * 4272 4273 * They are typically used to indicate a background task that the user is actively engaged 4274 * with (e.g., playing music) or is pending in some way and therefore occupying the device 4275 * (e.g., a file download, sync operation, active network connection). 4276 * 4277 4278 * @see Notification#FLAG_ONGOING_EVENT 4279 */ 4280 @NonNull setOngoing(boolean ongoing)4281 public Builder setOngoing(boolean ongoing) { 4282 setFlag(FLAG_ONGOING_EVENT, ongoing); 4283 return this; 4284 } 4285 4286 /** 4287 * Set whether this notification should be colorized. When set, the color set with 4288 * {@link #setColor(int)} will be used as the background color of this notification. 4289 * <p> 4290 * This should only be used for high priority ongoing tasks like navigation, an ongoing 4291 * call, or other similarly high-priority events for the user. 4292 * <p> 4293 * For most styles, the coloring will only be applied if the notification is for a 4294 * foreground service notification. 4295 * However, for {@link MediaStyle} and {@link DecoratedMediaCustomViewStyle} notifications 4296 * that have a media session attached there is no such requirement. 4297 * 4298 * @see #setColor(int) 4299 * @see MediaStyle#setMediaSession(MediaSession.Token) 4300 */ 4301 @NonNull setColorized(boolean colorize)4302 public Builder setColorized(boolean colorize) { 4303 mN.extras.putBoolean(EXTRA_COLORIZED, colorize); 4304 return this; 4305 } 4306 4307 /** 4308 * Set this flag if you would only like the sound, vibrate 4309 * and ticker to be played if the notification is not already showing. 4310 * 4311 * @see Notification#FLAG_ONLY_ALERT_ONCE 4312 */ 4313 @NonNull setOnlyAlertOnce(boolean onlyAlertOnce)4314 public Builder setOnlyAlertOnce(boolean onlyAlertOnce) { 4315 setFlag(FLAG_ONLY_ALERT_ONCE, onlyAlertOnce); 4316 return this; 4317 } 4318 4319 /** 4320 * Make this notification automatically dismissed when the user touches it. 4321 * 4322 * @see Notification#FLAG_AUTO_CANCEL 4323 */ 4324 @NonNull setAutoCancel(boolean autoCancel)4325 public Builder setAutoCancel(boolean autoCancel) { 4326 setFlag(FLAG_AUTO_CANCEL, autoCancel); 4327 return this; 4328 } 4329 4330 /** 4331 * Set whether or not this notification should not bridge to other devices. 4332 * 4333 * <p>Some notifications can be bridged to other devices for remote display. 4334 * This hint can be set to recommend this notification not be bridged. 4335 */ 4336 @NonNull setLocalOnly(boolean localOnly)4337 public Builder setLocalOnly(boolean localOnly) { 4338 setFlag(FLAG_LOCAL_ONLY, localOnly); 4339 return this; 4340 } 4341 4342 /** 4343 * Set which notification properties will be inherited from system defaults. 4344 * <p> 4345 * The value should be one or more of the following fields combined with 4346 * bitwise-or: 4347 * {@link #DEFAULT_SOUND}, {@link #DEFAULT_VIBRATE}, {@link #DEFAULT_LIGHTS}. 4348 * <p> 4349 * For all default values, use {@link #DEFAULT_ALL}. 4350 * 4351 * @deprecated use {@link NotificationChannel#enableVibration(boolean)} and 4352 * {@link NotificationChannel#enableLights(boolean)} and 4353 * {@link NotificationChannel#setSound(Uri, AudioAttributes)} instead. 4354 */ 4355 @Deprecated setDefaults(int defaults)4356 public Builder setDefaults(int defaults) { 4357 mN.defaults = defaults; 4358 return this; 4359 } 4360 4361 /** 4362 * Set the priority of this notification. 4363 * 4364 * @see Notification#priority 4365 * @deprecated use {@link NotificationChannel#setImportance(int)} instead. 4366 */ 4367 @Deprecated setPriority(@riority int pri)4368 public Builder setPriority(@Priority int pri) { 4369 mN.priority = pri; 4370 return this; 4371 } 4372 4373 /** 4374 * Set the notification category. 4375 * 4376 * @see Notification#category 4377 */ 4378 @NonNull setCategory(String category)4379 public Builder setCategory(String category) { 4380 mN.category = category; 4381 return this; 4382 } 4383 4384 /** 4385 * Add a person that is relevant to this notification. 4386 * 4387 * <P> 4388 * Depending on user preferences, this annotation may allow the notification to pass 4389 * through interruption filters, if this notification is of category {@link #CATEGORY_CALL} 4390 * or {@link #CATEGORY_MESSAGE}. The addition of people may also cause this notification to 4391 * appear more prominently in the user interface. 4392 * </P> 4393 * 4394 * <P> 4395 * The person should be specified by the {@code String} representation of a 4396 * {@link android.provider.ContactsContract.Contacts#CONTENT_LOOKUP_URI}. 4397 * </P> 4398 * 4399 * <P>The system will also attempt to resolve {@code mailto:} and {@code tel:} schema 4400 * URIs. The path part of these URIs must exist in the contacts database, in the 4401 * appropriate column, or the reference will be discarded as invalid. Telephone schema 4402 * URIs will be resolved by {@link android.provider.ContactsContract.PhoneLookup}. 4403 * It is also possible to provide a URI with the schema {@code name:} in order to uniquely 4404 * identify a person without an entry in the contacts database. 4405 * </P> 4406 * 4407 * @param uri A URI for the person. 4408 * @see Notification#EXTRA_PEOPLE 4409 * @deprecated use {@link #addPerson(Person)} 4410 */ addPerson(String uri)4411 public Builder addPerson(String uri) { 4412 addPerson(new Person.Builder().setUri(uri).build()); 4413 return this; 4414 } 4415 4416 /** 4417 * Add a person that is relevant to this notification. 4418 * 4419 * <P> 4420 * Depending on user preferences, this annotation may allow the notification to pass 4421 * through interruption filters, if this notification is of category {@link #CATEGORY_CALL} 4422 * or {@link #CATEGORY_MESSAGE}. The addition of people may also cause this notification to 4423 * appear more prominently in the user interface. 4424 * </P> 4425 * 4426 * <P> 4427 * A person should usually contain a uri in order to benefit from the ranking boost. 4428 * However, even if no uri is provided, it's beneficial to provide other people in the 4429 * notification, such that listeners and voice only devices can announce and handle them 4430 * properly. 4431 * </P> 4432 * 4433 * @param person the person to add. 4434 * @see Notification#EXTRA_PEOPLE_LIST 4435 */ 4436 @NonNull addPerson(Person person)4437 public Builder addPerson(Person person) { 4438 mPersonList.add(person); 4439 return this; 4440 } 4441 4442 /** 4443 * Set this notification to be part of a group of notifications sharing the same key. 4444 * Grouped notifications may display in a cluster or stack on devices which 4445 * support such rendering. 4446 * 4447 * <p>To make this notification the summary for its group, also call 4448 * {@link #setGroupSummary}. A sort order can be specified for group members by using 4449 * {@link #setSortKey}. 4450 * @param groupKey The group key of the group. 4451 * @return this object for method chaining 4452 */ 4453 @NonNull setGroup(String groupKey)4454 public Builder setGroup(String groupKey) { 4455 mN.mGroupKey = groupKey; 4456 return this; 4457 } 4458 4459 /** 4460 * Set this notification to be the group summary for a group of notifications. 4461 * Grouped notifications may display in a cluster or stack on devices which 4462 * support such rendering. If thereRequires a group key also be set using {@link #setGroup}. 4463 * The group summary may be suppressed if too few notifications are included in the group. 4464 * @param isGroupSummary Whether this notification should be a group summary. 4465 * @return this object for method chaining 4466 */ 4467 @NonNull setGroupSummary(boolean isGroupSummary)4468 public Builder setGroupSummary(boolean isGroupSummary) { 4469 setFlag(FLAG_GROUP_SUMMARY, isGroupSummary); 4470 return this; 4471 } 4472 4473 /** 4474 * Set a sort key that orders this notification among other notifications from the 4475 * same package. This can be useful if an external sort was already applied and an app 4476 * would like to preserve this. Notifications will be sorted lexicographically using this 4477 * value, although providing different priorities in addition to providing sort key may 4478 * cause this value to be ignored. 4479 * 4480 * <p>This sort key can also be used to order members of a notification group. See 4481 * {@link #setGroup}. 4482 * 4483 * @see String#compareTo(String) 4484 */ 4485 @NonNull setSortKey(String sortKey)4486 public Builder setSortKey(String sortKey) { 4487 mN.mSortKey = sortKey; 4488 return this; 4489 } 4490 4491 /** 4492 * Merge additional metadata into this notification. 4493 * 4494 * <p>Values within the Bundle will replace existing extras values in this Builder. 4495 * 4496 * @see Notification#extras 4497 */ 4498 @NonNull addExtras(Bundle extras)4499 public Builder addExtras(Bundle extras) { 4500 if (extras != null) { 4501 mUserExtras.putAll(extras); 4502 } 4503 return this; 4504 } 4505 4506 /** 4507 * Set metadata for this notification. 4508 * 4509 * <p>A reference to the Bundle is held for the lifetime of this Builder, and the Bundle's 4510 * current contents are copied into the Notification each time {@link #build()} is 4511 * called. 4512 * 4513 * <p>Replaces any existing extras values with those from the provided Bundle. 4514 * Use {@link #addExtras} to merge in metadata instead. 4515 * 4516 * @see Notification#extras 4517 */ 4518 @NonNull setExtras(Bundle extras)4519 public Builder setExtras(Bundle extras) { 4520 if (extras != null) { 4521 mUserExtras = extras; 4522 } 4523 return this; 4524 } 4525 4526 /** 4527 * Get the current metadata Bundle used by this notification Builder. 4528 * 4529 * <p>The returned Bundle is shared with this Builder. 4530 * 4531 * <p>The current contents of this Bundle are copied into the Notification each time 4532 * {@link #build()} is called. 4533 * 4534 * @see Notification#extras 4535 */ getExtras()4536 public Bundle getExtras() { 4537 return mUserExtras; 4538 } 4539 getAllExtras()4540 private Bundle getAllExtras() { 4541 final Bundle saveExtras = (Bundle) mUserExtras.clone(); 4542 saveExtras.putAll(mN.extras); 4543 return saveExtras; 4544 } 4545 4546 /** 4547 * Add an action to this notification. Actions are typically displayed by 4548 * the system as a button adjacent to the notification content. 4549 * <p> 4550 * Every action must have an icon (32dp square and matching the 4551 * <a href="{@docRoot}design/style/iconography.html#action-bar">Holo 4552 * Dark action bar</a> visual style), a textual label, and a {@link PendingIntent}. 4553 * <p> 4554 * A notification in its expanded form can display up to 3 actions, from left to right in 4555 * the order they were added. Actions will not be displayed when the notification is 4556 * collapsed, however, so be sure that any essential functions may be accessed by the user 4557 * in some other way (for example, in the Activity pointed to by {@link #contentIntent}). 4558 * 4559 * @param icon Resource ID of a drawable that represents the action. 4560 * @param title Text describing the action. 4561 * @param intent PendingIntent to be fired when the action is invoked. 4562 * 4563 * @deprecated Use {@link #addAction(Action)} instead. 4564 */ 4565 @Deprecated addAction(int icon, CharSequence title, PendingIntent intent)4566 public Builder addAction(int icon, CharSequence title, PendingIntent intent) { 4567 mActions.add(new Action(icon, safeCharSequence(title), intent)); 4568 return this; 4569 } 4570 4571 /** 4572 * Add an action to this notification. Actions are typically displayed by 4573 * the system as a button adjacent to the notification content. 4574 * <p> 4575 * Every action must have an icon (32dp square and matching the 4576 * <a href="{@docRoot}design/style/iconography.html#action-bar">Holo 4577 * Dark action bar</a> visual style), a textual label, and a {@link PendingIntent}. 4578 * <p> 4579 * A notification in its expanded form can display up to 3 actions, from left to right in 4580 * the order they were added. Actions will not be displayed when the notification is 4581 * collapsed, however, so be sure that any essential functions may be accessed by the user 4582 * in some other way (for example, in the Activity pointed to by {@link #contentIntent}). 4583 * 4584 * @param action The action to add. 4585 */ 4586 @NonNull addAction(Action action)4587 public Builder addAction(Action action) { 4588 if (action != null) { 4589 mActions.add(action); 4590 } 4591 return this; 4592 } 4593 4594 /** 4595 * Alter the complete list of actions attached to this notification. 4596 * @see #addAction(Action). 4597 * 4598 * @param actions 4599 * @return 4600 */ 4601 @NonNull setActions(Action... actions)4602 public Builder setActions(Action... actions) { 4603 mActions.clear(); 4604 for (int i = 0; i < actions.length; i++) { 4605 if (actions[i] != null) { 4606 mActions.add(actions[i]); 4607 } 4608 } 4609 return this; 4610 } 4611 4612 /** 4613 * Add a rich notification style to be applied at build time. 4614 * 4615 * @param style Object responsible for modifying the notification style. 4616 */ 4617 @NonNull setStyle(Style style)4618 public Builder setStyle(Style style) { 4619 if (mStyle != style) { 4620 mStyle = style; 4621 if (mStyle != null) { 4622 mStyle.setBuilder(this); 4623 mN.extras.putString(EXTRA_TEMPLATE, style.getClass().getName()); 4624 } else { 4625 mN.extras.remove(EXTRA_TEMPLATE); 4626 } 4627 } 4628 return this; 4629 } 4630 4631 /** 4632 * Returns the style set by {@link #setStyle(Style)}. 4633 */ getStyle()4634 public Style getStyle() { 4635 return mStyle; 4636 } 4637 4638 /** 4639 * Specify the value of {@link #visibility}. 4640 * 4641 * @return The same Builder. 4642 */ 4643 @NonNull setVisibility(@isibility int visibility)4644 public Builder setVisibility(@Visibility int visibility) { 4645 mN.visibility = visibility; 4646 return this; 4647 } 4648 4649 /** 4650 * Supply a replacement Notification whose contents should be shown in insecure contexts 4651 * (i.e. atop the secure lockscreen). See {@link #visibility} and {@link #VISIBILITY_PUBLIC}. 4652 * @param n A replacement notification, presumably with some or all info redacted. 4653 * @return The same Builder. 4654 */ 4655 @NonNull setPublicVersion(Notification n)4656 public Builder setPublicVersion(Notification n) { 4657 if (n != null) { 4658 mN.publicVersion = new Notification(); 4659 n.cloneInto(mN.publicVersion, /*heavy=*/ true); 4660 } else { 4661 mN.publicVersion = null; 4662 } 4663 return this; 4664 } 4665 4666 /** 4667 * Apply an extender to this notification builder. Extenders may be used to add 4668 * metadata or change options on this builder. 4669 */ 4670 @NonNull extend(Extender extender)4671 public Builder extend(Extender extender) { 4672 extender.extend(this); 4673 return this; 4674 } 4675 4676 /** 4677 * Set the value for a notification flag 4678 * 4679 * @param mask Bit mask of the flag 4680 * @param value Status (on/off) of the flag 4681 * 4682 * @return The same Builder. 4683 */ 4684 @NonNull setFlag(@otificationFlags int mask, boolean value)4685 public Builder setFlag(@NotificationFlags int mask, boolean value) { 4686 if (value) { 4687 mN.flags |= mask; 4688 } else { 4689 mN.flags &= ~mask; 4690 } 4691 return this; 4692 } 4693 4694 /** 4695 * Sets {@link Notification#color}. 4696 * 4697 * @param argb The accent color to use 4698 * 4699 * @return The same Builder. 4700 */ 4701 @NonNull setColor(@olorInt int argb)4702 public Builder setColor(@ColorInt int argb) { 4703 mN.color = argb; 4704 sanitizeColor(); 4705 return this; 4706 } 4707 getProfileBadgeDrawable()4708 private Drawable getProfileBadgeDrawable() { 4709 if (mContext.getUserId() == UserHandle.USER_SYSTEM) { 4710 // This user can never be a badged profile, 4711 // and also includes USER_ALL system notifications. 4712 return null; 4713 } 4714 // Note: This assumes that the current user can read the profile badge of the 4715 // originating user. 4716 return mContext.getPackageManager().getUserBadgeForDensityNoBackground( 4717 new UserHandle(mContext.getUserId()), 0); 4718 } 4719 getProfileBadge()4720 private Bitmap getProfileBadge() { 4721 Drawable badge = getProfileBadgeDrawable(); 4722 if (badge == null) { 4723 return null; 4724 } 4725 final int size = mContext.getResources().getDimensionPixelSize( 4726 R.dimen.notification_badge_size); 4727 Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); 4728 Canvas canvas = new Canvas(bitmap); 4729 badge.setBounds(0, 0, size, size); 4730 badge.draw(canvas); 4731 return bitmap; 4732 } 4733 bindProfileBadge(RemoteViews contentView, StandardTemplateParams p)4734 private void bindProfileBadge(RemoteViews contentView, StandardTemplateParams p) { 4735 Bitmap profileBadge = getProfileBadge(); 4736 4737 if (profileBadge != null) { 4738 contentView.setImageViewBitmap(R.id.profile_badge, profileBadge); 4739 contentView.setViewVisibility(R.id.profile_badge, View.VISIBLE); 4740 if (isColorized(p)) { 4741 contentView.setDrawableTint(R.id.profile_badge, false, 4742 getPrimaryTextColor(p), PorterDuff.Mode.SRC_ATOP); 4743 } 4744 } 4745 } 4746 bindAlertedIcon(RemoteViews contentView, StandardTemplateParams p)4747 private void bindAlertedIcon(RemoteViews contentView, StandardTemplateParams p) { 4748 contentView.setDrawableTint( 4749 R.id.alerted_icon, 4750 false /* targetBackground */, 4751 getNeutralColor(p), 4752 PorterDuff.Mode.SRC_ATOP); 4753 } 4754 4755 /** 4756 * @hide 4757 */ usesStandardHeader()4758 public boolean usesStandardHeader() { 4759 if (mN.mUsesStandardHeader) { 4760 return true; 4761 } 4762 if (mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.N) { 4763 if (mN.contentView == null && mN.bigContentView == null) { 4764 return true; 4765 } 4766 } 4767 boolean contentViewUsesHeader = mN.contentView == null 4768 || STANDARD_LAYOUTS.contains(mN.contentView.getLayoutId()); 4769 boolean bigContentViewUsesHeader = mN.bigContentView == null 4770 || STANDARD_LAYOUTS.contains(mN.bigContentView.getLayoutId()); 4771 return contentViewUsesHeader && bigContentViewUsesHeader; 4772 } 4773 resetStandardTemplate(RemoteViews contentView)4774 private void resetStandardTemplate(RemoteViews contentView) { 4775 resetNotificationHeader(contentView); 4776 contentView.setViewVisibility(R.id.right_icon, View.GONE); 4777 contentView.setViewVisibility(R.id.title, View.GONE); 4778 contentView.setTextViewText(R.id.title, null); 4779 contentView.setViewVisibility(R.id.text, View.GONE); 4780 contentView.setTextViewText(R.id.text, null); 4781 contentView.setViewVisibility(R.id.text_line_1, View.GONE); 4782 contentView.setTextViewText(R.id.text_line_1, null); 4783 } 4784 4785 /** 4786 * Resets the notification header to its original state 4787 */ resetNotificationHeader(RemoteViews contentView)4788 private void resetNotificationHeader(RemoteViews contentView) { 4789 // Small icon doesn't need to be reset, as it's always set. Resetting would prevent 4790 // re-using the drawable when the notification is updated. 4791 contentView.setBoolean(R.id.notification_header, "setExpanded", false); 4792 contentView.setTextViewText(R.id.app_name_text, null); 4793 contentView.setViewVisibility(R.id.chronometer, View.GONE); 4794 contentView.setViewVisibility(R.id.header_text, View.GONE); 4795 contentView.setTextViewText(R.id.header_text, null); 4796 contentView.setViewVisibility(R.id.header_text_secondary, View.GONE); 4797 contentView.setTextViewText(R.id.header_text_secondary, null); 4798 contentView.setViewVisibility(R.id.header_text_divider, View.GONE); 4799 contentView.setViewVisibility(R.id.header_text_secondary_divider, View.GONE); 4800 contentView.setViewVisibility(R.id.time_divider, View.GONE); 4801 contentView.setViewVisibility(R.id.time, View.GONE); 4802 contentView.setImageViewIcon(R.id.profile_badge, null); 4803 contentView.setViewVisibility(R.id.profile_badge, View.GONE); 4804 contentView.setViewVisibility(R.id.alerted_icon, View.GONE); 4805 mN.mUsesStandardHeader = false; 4806 } 4807 applyStandardTemplate(int resId, TemplateBindResult result)4808 private RemoteViews applyStandardTemplate(int resId, TemplateBindResult result) { 4809 return applyStandardTemplate(resId, mParams.reset().fillTextsFrom(this), 4810 result); 4811 } 4812 applyStandardTemplate(int resId, StandardTemplateParams p, TemplateBindResult result)4813 private RemoteViews applyStandardTemplate(int resId, StandardTemplateParams p, 4814 TemplateBindResult result) { 4815 RemoteViews contentView = new BuilderRemoteViews(mContext.getApplicationInfo(), resId); 4816 4817 resetStandardTemplate(contentView); 4818 4819 final Bundle ex = mN.extras; 4820 updateBackgroundColor(contentView, p); 4821 bindNotificationHeader(contentView, p); 4822 bindLargeIconAndReply(contentView, p, result); 4823 boolean showProgress = handleProgressBar(contentView, ex, p); 4824 if (p.title != null) { 4825 contentView.setViewVisibility(R.id.title, View.VISIBLE); 4826 contentView.setTextViewText(R.id.title, processTextSpans(p.title)); 4827 setTextViewColorPrimary(contentView, R.id.title, p); 4828 contentView.setViewLayoutWidth(R.id.title, showProgress 4829 ? ViewGroup.LayoutParams.WRAP_CONTENT 4830 : ViewGroup.LayoutParams.MATCH_PARENT); 4831 } 4832 if (p.text != null) { 4833 int textId = showProgress ? com.android.internal.R.id.text_line_1 4834 : com.android.internal.R.id.text; 4835 contentView.setTextViewText(textId, processTextSpans(p.text)); 4836 setTextViewColorSecondary(contentView, textId, p); 4837 contentView.setViewVisibility(textId, View.VISIBLE); 4838 } 4839 4840 setContentMinHeight(contentView, showProgress || mN.hasLargeIcon()); 4841 4842 return contentView; 4843 } 4844 processTextSpans(CharSequence text)4845 private CharSequence processTextSpans(CharSequence text) { 4846 if (hasForegroundColor() || mInNightMode) { 4847 return ContrastColorUtil.clearColorSpans(text); 4848 } 4849 return text; 4850 } 4851 setTextViewColorPrimary(RemoteViews contentView, int id, StandardTemplateParams p)4852 private void setTextViewColorPrimary(RemoteViews contentView, int id, 4853 StandardTemplateParams p) { 4854 ensureColors(p); 4855 contentView.setTextColor(id, mPrimaryTextColor); 4856 } 4857 hasForegroundColor()4858 private boolean hasForegroundColor() { 4859 return mForegroundColor != COLOR_INVALID; 4860 } 4861 4862 /** 4863 * Return the primary text color using the existing template params 4864 * @hide 4865 */ 4866 @VisibleForTesting getPrimaryTextColor()4867 public int getPrimaryTextColor() { 4868 return getPrimaryTextColor(mParams); 4869 } 4870 4871 /** 4872 * @param p the template params to inflate this with 4873 * @return the primary text color 4874 * @hide 4875 */ 4876 @VisibleForTesting getPrimaryTextColor(StandardTemplateParams p)4877 public int getPrimaryTextColor(StandardTemplateParams p) { 4878 ensureColors(p); 4879 return mPrimaryTextColor; 4880 } 4881 4882 /** 4883 * Return the secondary text color using the existing template params 4884 * @hide 4885 */ 4886 @VisibleForTesting getSecondaryTextColor()4887 public int getSecondaryTextColor() { 4888 return getSecondaryTextColor(mParams); 4889 } 4890 4891 /** 4892 * @param p the template params to inflate this with 4893 * @return the secondary text color 4894 * @hide 4895 */ 4896 @VisibleForTesting getSecondaryTextColor(StandardTemplateParams p)4897 public int getSecondaryTextColor(StandardTemplateParams p) { 4898 ensureColors(p); 4899 return mSecondaryTextColor; 4900 } 4901 setTextViewColorSecondary(RemoteViews contentView, int id, StandardTemplateParams p)4902 private void setTextViewColorSecondary(RemoteViews contentView, int id, 4903 StandardTemplateParams p) { 4904 ensureColors(p); 4905 contentView.setTextColor(id, mSecondaryTextColor); 4906 } 4907 ensureColors(StandardTemplateParams p)4908 private void ensureColors(StandardTemplateParams p) { 4909 int backgroundColor = getBackgroundColor(p); 4910 if (mPrimaryTextColor == COLOR_INVALID 4911 || mSecondaryTextColor == COLOR_INVALID 4912 || mTextColorsAreForBackground != backgroundColor) { 4913 mTextColorsAreForBackground = backgroundColor; 4914 if (!hasForegroundColor() || !isColorized(p)) { 4915 mPrimaryTextColor = ContrastColorUtil.resolvePrimaryColor(mContext, 4916 backgroundColor, mInNightMode); 4917 mSecondaryTextColor = ContrastColorUtil.resolveSecondaryColor(mContext, 4918 backgroundColor, mInNightMode); 4919 if (backgroundColor != COLOR_DEFAULT && isColorized(p)) { 4920 mPrimaryTextColor = ContrastColorUtil.findAlphaToMeetContrast( 4921 mPrimaryTextColor, backgroundColor, 4.5); 4922 mSecondaryTextColor = ContrastColorUtil.findAlphaToMeetContrast( 4923 mSecondaryTextColor, backgroundColor, 4.5); 4924 } 4925 } else { 4926 double backLum = ContrastColorUtil.calculateLuminance(backgroundColor); 4927 double textLum = ContrastColorUtil.calculateLuminance(mForegroundColor); 4928 double contrast = ContrastColorUtil.calculateContrast(mForegroundColor, 4929 backgroundColor); 4930 // We only respect the given colors if worst case Black or White still has 4931 // contrast 4932 boolean backgroundLight = backLum > textLum 4933 && satisfiesTextContrast(backgroundColor, Color.BLACK) 4934 || backLum <= textLum 4935 && !satisfiesTextContrast(backgroundColor, Color.WHITE); 4936 if (contrast < 4.5f) { 4937 if (backgroundLight) { 4938 mSecondaryTextColor = ContrastColorUtil.findContrastColor( 4939 mForegroundColor, 4940 backgroundColor, 4941 true /* findFG */, 4942 4.5f); 4943 mPrimaryTextColor = ContrastColorUtil.changeColorLightness( 4944 mSecondaryTextColor, -LIGHTNESS_TEXT_DIFFERENCE_LIGHT); 4945 } else { 4946 mSecondaryTextColor = 4947 ContrastColorUtil.findContrastColorAgainstDark( 4948 mForegroundColor, 4949 backgroundColor, 4950 true /* findFG */, 4951 4.5f); 4952 mPrimaryTextColor = ContrastColorUtil.changeColorLightness( 4953 mSecondaryTextColor, -LIGHTNESS_TEXT_DIFFERENCE_DARK); 4954 } 4955 } else { 4956 mPrimaryTextColor = mForegroundColor; 4957 mSecondaryTextColor = ContrastColorUtil.changeColorLightness( 4958 mPrimaryTextColor, backgroundLight ? LIGHTNESS_TEXT_DIFFERENCE_LIGHT 4959 : LIGHTNESS_TEXT_DIFFERENCE_DARK); 4960 if (ContrastColorUtil.calculateContrast(mSecondaryTextColor, 4961 backgroundColor) < 4.5f) { 4962 // oh well the secondary is not good enough 4963 if (backgroundLight) { 4964 mSecondaryTextColor = ContrastColorUtil.findContrastColor( 4965 mSecondaryTextColor, 4966 backgroundColor, 4967 true /* findFG */, 4968 4.5f); 4969 } else { 4970 mSecondaryTextColor 4971 = ContrastColorUtil.findContrastColorAgainstDark( 4972 mSecondaryTextColor, 4973 backgroundColor, 4974 true /* findFG */, 4975 4.5f); 4976 } 4977 mPrimaryTextColor = ContrastColorUtil.changeColorLightness( 4978 mSecondaryTextColor, backgroundLight 4979 ? -LIGHTNESS_TEXT_DIFFERENCE_LIGHT 4980 : -LIGHTNESS_TEXT_DIFFERENCE_DARK); 4981 } 4982 } 4983 } 4984 } 4985 } 4986 updateBackgroundColor(RemoteViews contentView, StandardTemplateParams p)4987 private void updateBackgroundColor(RemoteViews contentView, 4988 StandardTemplateParams p) { 4989 if (isColorized(p)) { 4990 contentView.setInt(R.id.status_bar_latest_event_content, "setBackgroundColor", 4991 getBackgroundColor(p)); 4992 } else { 4993 // Clear it! 4994 contentView.setInt(R.id.status_bar_latest_event_content, "setBackgroundResource", 4995 0); 4996 } 4997 } 4998 4999 /** 5000 * @param remoteView the remote view to update the minheight in 5001 * @param hasMinHeight does it have a mimHeight 5002 * @hide 5003 */ setContentMinHeight(RemoteViews remoteView, boolean hasMinHeight)5004 void setContentMinHeight(RemoteViews remoteView, boolean hasMinHeight) { 5005 int minHeight = 0; 5006 if (hasMinHeight) { 5007 // we need to set the minHeight of the notification 5008 minHeight = mContext.getResources().getDimensionPixelSize( 5009 com.android.internal.R.dimen.notification_min_content_height); 5010 } 5011 remoteView.setInt(R.id.notification_main_column, "setMinimumHeight", minHeight); 5012 } 5013 handleProgressBar(RemoteViews contentView, Bundle ex, StandardTemplateParams p)5014 private boolean handleProgressBar(RemoteViews contentView, Bundle ex, 5015 StandardTemplateParams p) { 5016 final int max = ex.getInt(EXTRA_PROGRESS_MAX, 0); 5017 final int progress = ex.getInt(EXTRA_PROGRESS, 0); 5018 final boolean ind = ex.getBoolean(EXTRA_PROGRESS_INDETERMINATE); 5019 if (p.hasProgress && (max != 0 || ind)) { 5020 contentView.setViewVisibility(com.android.internal.R.id.progress, View.VISIBLE); 5021 contentView.setProgressBar( 5022 R.id.progress, max, progress, ind); 5023 contentView.setProgressBackgroundTintList( 5024 R.id.progress, ColorStateList.valueOf(mContext.getColor( 5025 R.color.notification_progress_background_color))); 5026 if (getRawColor(p) != COLOR_DEFAULT) { 5027 int color = isColorized(p) ? getPrimaryTextColor(p) : resolveContrastColor(p); 5028 ColorStateList colorStateList = ColorStateList.valueOf(color); 5029 contentView.setProgressTintList(R.id.progress, colorStateList); 5030 contentView.setProgressIndeterminateTintList(R.id.progress, colorStateList); 5031 } 5032 return true; 5033 } else { 5034 contentView.setViewVisibility(R.id.progress, View.GONE); 5035 return false; 5036 } 5037 } 5038 bindLargeIconAndReply(RemoteViews contentView, StandardTemplateParams p, TemplateBindResult result)5039 private void bindLargeIconAndReply(RemoteViews contentView, StandardTemplateParams p, 5040 TemplateBindResult result) { 5041 boolean largeIconShown = bindLargeIcon(contentView, p); 5042 boolean replyIconShown = bindReplyIcon(contentView, p); 5043 boolean iconContainerVisible = largeIconShown || replyIconShown; 5044 contentView.setViewVisibility(R.id.right_icon_container, 5045 iconContainerVisible ? View.VISIBLE : View.GONE); 5046 int marginEnd = calculateMarginEnd(largeIconShown, replyIconShown); 5047 contentView.setViewLayoutMarginEnd(R.id.line1, marginEnd); 5048 contentView.setViewLayoutMarginEnd(R.id.text, marginEnd); 5049 contentView.setViewLayoutMarginEnd(R.id.progress, marginEnd); 5050 if (result != null) { 5051 result.setIconMarginEnd(marginEnd); 5052 result.setRightIconContainerVisible(iconContainerVisible); 5053 } 5054 } 5055 calculateMarginEnd(boolean largeIconShown, boolean replyIconShown)5056 private int calculateMarginEnd(boolean largeIconShown, boolean replyIconShown) { 5057 int marginEnd = 0; 5058 int contentMargin = mContext.getResources().getDimensionPixelSize( 5059 R.dimen.notification_content_margin_end); 5060 int iconSize = mContext.getResources().getDimensionPixelSize( 5061 R.dimen.notification_right_icon_size); 5062 if (replyIconShown) { 5063 // The size of the reply icon 5064 marginEnd += iconSize; 5065 5066 int replyInset = mContext.getResources().getDimensionPixelSize( 5067 R.dimen.notification_reply_inset); 5068 // We're subtracting the inset of the reply icon to make sure it's 5069 // aligned nicely on the right, and remove it from the following padding 5070 marginEnd -= replyInset * 2; 5071 } 5072 if (largeIconShown) { 5073 // adding size of the right icon 5074 marginEnd += iconSize; 5075 5076 if (replyIconShown) { 5077 // We also add some padding to the reply icon if it's around 5078 marginEnd += contentMargin; 5079 } 5080 } 5081 if (replyIconShown || largeIconShown) { 5082 // The padding to the content 5083 marginEnd += contentMargin; 5084 } 5085 return marginEnd; 5086 } 5087 5088 /** 5089 * Bind the large icon. 5090 * @return if the largeIcon is visible 5091 */ bindLargeIcon(RemoteViews contentView, StandardTemplateParams p)5092 private boolean bindLargeIcon(RemoteViews contentView, StandardTemplateParams p) { 5093 if (mN.mLargeIcon == null && mN.largeIcon != null) { 5094 mN.mLargeIcon = Icon.createWithBitmap(mN.largeIcon); 5095 } 5096 boolean showLargeIcon = mN.mLargeIcon != null && !p.hideLargeIcon; 5097 if (showLargeIcon) { 5098 contentView.setViewVisibility(R.id.right_icon, View.VISIBLE); 5099 contentView.setImageViewIcon(R.id.right_icon, mN.mLargeIcon); 5100 processLargeLegacyIcon(mN.mLargeIcon, contentView, p); 5101 } 5102 return showLargeIcon; 5103 } 5104 5105 /** 5106 * Bind the reply icon. 5107 * @return if the reply icon is visible 5108 */ bindReplyIcon(RemoteViews contentView, StandardTemplateParams p)5109 private boolean bindReplyIcon(RemoteViews contentView, StandardTemplateParams p) { 5110 boolean actionVisible = !p.hideReplyIcon; 5111 Action action = null; 5112 if (actionVisible) { 5113 action = findReplyAction(); 5114 actionVisible = action != null; 5115 } 5116 if (actionVisible) { 5117 contentView.setViewVisibility(R.id.reply_icon_action, View.VISIBLE); 5118 contentView.setDrawableTint(R.id.reply_icon_action, 5119 false /* targetBackground */, 5120 getNeutralColor(p), 5121 PorterDuff.Mode.SRC_ATOP); 5122 contentView.setOnClickPendingIntent(R.id.reply_icon_action, action.actionIntent); 5123 contentView.setRemoteInputs(R.id.reply_icon_action, action.mRemoteInputs); 5124 } else { 5125 contentView.setRemoteInputs(R.id.reply_icon_action, null); 5126 } 5127 contentView.setViewVisibility(R.id.reply_icon_action, 5128 actionVisible ? View.VISIBLE : View.GONE); 5129 return actionVisible; 5130 } 5131 findReplyAction()5132 private Action findReplyAction() { 5133 ArrayList<Action> actions = mActions; 5134 if (mOriginalActions != null) { 5135 actions = mOriginalActions; 5136 } 5137 int numActions = actions.size(); 5138 for (int i = 0; i < numActions; i++) { 5139 Action action = actions.get(i); 5140 if (hasValidRemoteInput(action)) { 5141 return action; 5142 } 5143 } 5144 return null; 5145 } 5146 bindNotificationHeader(RemoteViews contentView, StandardTemplateParams p)5147 private void bindNotificationHeader(RemoteViews contentView, StandardTemplateParams p) { 5148 bindSmallIcon(contentView, p); 5149 bindHeaderAppName(contentView, p); 5150 bindHeaderText(contentView, p); 5151 bindHeaderTextSecondary(contentView, p); 5152 bindHeaderChronometerAndTime(contentView, p); 5153 bindProfileBadge(contentView, p); 5154 bindAlertedIcon(contentView, p); 5155 bindActivePermissions(contentView, p); 5156 bindExpandButton(contentView, p); 5157 mN.mUsesStandardHeader = true; 5158 } 5159 bindActivePermissions(RemoteViews contentView, StandardTemplateParams p)5160 private void bindActivePermissions(RemoteViews contentView, StandardTemplateParams p) { 5161 int color = getNeutralColor(p); 5162 contentView.setDrawableTint(R.id.camera, false, color, PorterDuff.Mode.SRC_ATOP); 5163 contentView.setDrawableTint(R.id.mic, false, color, PorterDuff.Mode.SRC_ATOP); 5164 contentView.setDrawableTint(R.id.overlay, false, color, PorterDuff.Mode.SRC_ATOP); 5165 } 5166 bindExpandButton(RemoteViews contentView, StandardTemplateParams p)5167 private void bindExpandButton(RemoteViews contentView, StandardTemplateParams p) { 5168 int color = isColorized(p) ? getPrimaryTextColor(p) : getSecondaryTextColor(p); 5169 contentView.setDrawableTint(R.id.expand_button, false, color, 5170 PorterDuff.Mode.SRC_ATOP); 5171 contentView.setInt(R.id.expand_button, "setOriginalNotificationColor", 5172 color); 5173 } 5174 bindHeaderChronometerAndTime(RemoteViews contentView, StandardTemplateParams p)5175 private void bindHeaderChronometerAndTime(RemoteViews contentView, 5176 StandardTemplateParams p) { 5177 if (showsTimeOrChronometer()) { 5178 contentView.setViewVisibility(R.id.time_divider, View.VISIBLE); 5179 setTextViewColorSecondary(contentView, R.id.time_divider, p); 5180 if (mN.extras.getBoolean(EXTRA_SHOW_CHRONOMETER)) { 5181 contentView.setViewVisibility(R.id.chronometer, View.VISIBLE); 5182 contentView.setLong(R.id.chronometer, "setBase", 5183 mN.when + (SystemClock.elapsedRealtime() - System.currentTimeMillis())); 5184 contentView.setBoolean(R.id.chronometer, "setStarted", true); 5185 boolean countsDown = mN.extras.getBoolean(EXTRA_CHRONOMETER_COUNT_DOWN); 5186 contentView.setChronometerCountDown(R.id.chronometer, countsDown); 5187 setTextViewColorSecondary(contentView, R.id.chronometer, p); 5188 } else { 5189 contentView.setViewVisibility(R.id.time, View.VISIBLE); 5190 contentView.setLong(R.id.time, "setTime", mN.when); 5191 setTextViewColorSecondary(contentView, R.id.time, p); 5192 } 5193 } else { 5194 // We still want a time to be set but gone, such that we can show and hide it 5195 // on demand in case it's a child notification without anything in the header 5196 contentView.setLong(R.id.time, "setTime", mN.when != 0 ? mN.when : mN.creationTime); 5197 } 5198 } 5199 bindHeaderText(RemoteViews contentView, StandardTemplateParams p)5200 private void bindHeaderText(RemoteViews contentView, StandardTemplateParams p) { 5201 CharSequence summaryText = p.summaryText; 5202 if (summaryText == null && mStyle != null && mStyle.mSummaryTextSet 5203 && mStyle.hasSummaryInHeader()) { 5204 summaryText = mStyle.mSummaryText; 5205 } 5206 if (summaryText == null 5207 && mContext.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N 5208 && mN.extras.getCharSequence(EXTRA_INFO_TEXT) != null) { 5209 summaryText = mN.extras.getCharSequence(EXTRA_INFO_TEXT); 5210 } 5211 if (summaryText != null) { 5212 // TODO: Remove the span entirely to only have the string with propper formating. 5213 contentView.setTextViewText(R.id.header_text, processTextSpans( 5214 processLegacyText(summaryText))); 5215 setTextViewColorSecondary(contentView, R.id.header_text, p); 5216 contentView.setViewVisibility(R.id.header_text, View.VISIBLE); 5217 contentView.setViewVisibility(R.id.header_text_divider, View.VISIBLE); 5218 setTextViewColorSecondary(contentView, R.id.header_text_divider, p); 5219 } 5220 } 5221 bindHeaderTextSecondary(RemoteViews contentView, StandardTemplateParams p)5222 private void bindHeaderTextSecondary(RemoteViews contentView, StandardTemplateParams p) { 5223 if (!TextUtils.isEmpty(p.headerTextSecondary)) { 5224 contentView.setTextViewText(R.id.header_text_secondary, processTextSpans( 5225 processLegacyText(p.headerTextSecondary))); 5226 setTextViewColorSecondary(contentView, R.id.header_text_secondary, p); 5227 contentView.setViewVisibility(R.id.header_text_secondary, View.VISIBLE); 5228 contentView.setViewVisibility(R.id.header_text_secondary_divider, View.VISIBLE); 5229 setTextViewColorSecondary(contentView, R.id.header_text_secondary_divider, p); 5230 } 5231 } 5232 5233 /** 5234 * @hide 5235 */ 5236 @UnsupportedAppUsage loadHeaderAppName()5237 public String loadHeaderAppName() { 5238 CharSequence name = null; 5239 final PackageManager pm = mContext.getPackageManager(); 5240 if (mN.extras.containsKey(EXTRA_SUBSTITUTE_APP_NAME)) { 5241 // only system packages which lump together a bunch of unrelated stuff 5242 // may substitute a different name to make the purpose of the 5243 // notification more clear. the correct package label should always 5244 // be accessible via SystemUI. 5245 final String pkg = mContext.getPackageName(); 5246 final String subName = mN.extras.getString(EXTRA_SUBSTITUTE_APP_NAME); 5247 if (PackageManager.PERMISSION_GRANTED == pm.checkPermission( 5248 android.Manifest.permission.SUBSTITUTE_NOTIFICATION_APP_NAME, pkg)) { 5249 name = subName; 5250 } else { 5251 Log.w(TAG, "warning: pkg " 5252 + pkg + " attempting to substitute app name '" + subName 5253 + "' without holding perm " 5254 + android.Manifest.permission.SUBSTITUTE_NOTIFICATION_APP_NAME); 5255 } 5256 } 5257 if (TextUtils.isEmpty(name)) { 5258 name = pm.getApplicationLabel(mContext.getApplicationInfo()); 5259 } 5260 if (TextUtils.isEmpty(name)) { 5261 // still nothing? 5262 return null; 5263 } 5264 5265 return String.valueOf(name); 5266 } bindHeaderAppName(RemoteViews contentView, StandardTemplateParams p)5267 private void bindHeaderAppName(RemoteViews contentView, StandardTemplateParams p) { 5268 contentView.setTextViewText(R.id.app_name_text, loadHeaderAppName()); 5269 if (isColorized(p)) { 5270 setTextViewColorPrimary(contentView, R.id.app_name_text, p); 5271 } else { 5272 contentView.setTextColor(R.id.app_name_text, getSecondaryTextColor(p)); 5273 } 5274 } 5275 isColorized(StandardTemplateParams p)5276 private boolean isColorized(StandardTemplateParams p) { 5277 return p.allowColorization && mN.isColorized(); 5278 } 5279 bindSmallIcon(RemoteViews contentView, StandardTemplateParams p)5280 private void bindSmallIcon(RemoteViews contentView, StandardTemplateParams p) { 5281 if (mN.mSmallIcon == null && mN.icon != 0) { 5282 mN.mSmallIcon = Icon.createWithResource(mContext, mN.icon); 5283 } 5284 contentView.setImageViewIcon(R.id.icon, mN.mSmallIcon); 5285 contentView.setInt(R.id.icon, "setImageLevel", mN.iconLevel); 5286 processSmallIconColor(mN.mSmallIcon, contentView, p); 5287 } 5288 5289 /** 5290 * @return true if the built notification will show the time or the chronometer; false 5291 * otherwise 5292 */ showsTimeOrChronometer()5293 private boolean showsTimeOrChronometer() { 5294 return mN.showsTime() || mN.showsChronometer(); 5295 } 5296 resetStandardTemplateWithActions(RemoteViews big)5297 private void resetStandardTemplateWithActions(RemoteViews big) { 5298 // actions_container is only reset when there are no actions to avoid focus issues with 5299 // remote inputs. 5300 big.setViewVisibility(R.id.actions, View.GONE); 5301 big.removeAllViews(R.id.actions); 5302 5303 big.setViewVisibility(R.id.notification_material_reply_container, View.GONE); 5304 big.setTextViewText(R.id.notification_material_reply_text_1, null); 5305 big.setViewVisibility(R.id.notification_material_reply_text_1_container, View.GONE); 5306 big.setViewVisibility(R.id.notification_material_reply_progress, View.GONE); 5307 5308 big.setViewVisibility(R.id.notification_material_reply_text_2, View.GONE); 5309 big.setTextViewText(R.id.notification_material_reply_text_2, null); 5310 big.setViewVisibility(R.id.notification_material_reply_text_3, View.GONE); 5311 big.setTextViewText(R.id.notification_material_reply_text_3, null); 5312 5313 big.setViewLayoutMarginBottomDimen(R.id.notification_action_list_margin_target, 5314 R.dimen.notification_content_margin); 5315 } 5316 applyStandardTemplateWithActions(int layoutId, TemplateBindResult result)5317 private RemoteViews applyStandardTemplateWithActions(int layoutId, 5318 TemplateBindResult result) { 5319 return applyStandardTemplateWithActions(layoutId, mParams.reset().fillTextsFrom(this), 5320 result); 5321 } 5322 filterOutContextualActions( List<Notification.Action> actions)5323 private static List<Notification.Action> filterOutContextualActions( 5324 List<Notification.Action> actions) { 5325 List<Notification.Action> nonContextualActions = new ArrayList<>(); 5326 for (Notification.Action action : actions) { 5327 if (!action.isContextual()) { 5328 nonContextualActions.add(action); 5329 } 5330 } 5331 return nonContextualActions; 5332 } 5333 applyStandardTemplateWithActions(int layoutId, StandardTemplateParams p, TemplateBindResult result)5334 private RemoteViews applyStandardTemplateWithActions(int layoutId, 5335 StandardTemplateParams p, TemplateBindResult result) { 5336 RemoteViews big = applyStandardTemplate(layoutId, p, result); 5337 5338 resetStandardTemplateWithActions(big); 5339 5340 boolean validRemoteInput = false; 5341 5342 // In the UI contextual actions appear separately from the standard actions, so we 5343 // filter them out here. 5344 List<Notification.Action> nonContextualActions = filterOutContextualActions(mActions); 5345 5346 int N = nonContextualActions.size(); 5347 boolean emphazisedMode = mN.fullScreenIntent != null; 5348 big.setBoolean(R.id.actions, "setEmphasizedMode", emphazisedMode); 5349 if (N > 0) { 5350 big.setViewVisibility(R.id.actions_container, View.VISIBLE); 5351 big.setViewVisibility(R.id.actions, View.VISIBLE); 5352 big.setViewLayoutMarginBottomDimen(R.id.notification_action_list_margin_target, 0); 5353 if (N>MAX_ACTION_BUTTONS) N=MAX_ACTION_BUTTONS; 5354 for (int i=0; i<N; i++) { 5355 Action action = nonContextualActions.get(i); 5356 5357 boolean actionHasValidInput = hasValidRemoteInput(action); 5358 validRemoteInput |= actionHasValidInput; 5359 5360 final RemoteViews button = generateActionButton(action, emphazisedMode, p); 5361 if (actionHasValidInput && !emphazisedMode) { 5362 // Clear the drawable 5363 button.setInt(R.id.action0, "setBackgroundResource", 0); 5364 } 5365 big.addView(R.id.actions, button); 5366 } 5367 } else { 5368 big.setViewVisibility(R.id.actions_container, View.GONE); 5369 } 5370 5371 RemoteInputHistoryItem[] replyText = getParcelableArrayFromBundle( 5372 mN.extras, EXTRA_REMOTE_INPUT_HISTORY_ITEMS, RemoteInputHistoryItem.class); 5373 if (validRemoteInput && replyText != null && replyText.length > 0 5374 && !TextUtils.isEmpty(replyText[0].getText()) 5375 && p.maxRemoteInputHistory > 0) { 5376 boolean showSpinner = mN.extras.getBoolean(EXTRA_SHOW_REMOTE_INPUT_SPINNER); 5377 big.setViewVisibility(R.id.notification_material_reply_container, View.VISIBLE); 5378 big.setViewVisibility(R.id.notification_material_reply_text_1_container, 5379 View.VISIBLE); 5380 big.setTextViewText(R.id.notification_material_reply_text_1, 5381 processTextSpans(replyText[0].getText())); 5382 setTextViewColorSecondary(big, R.id.notification_material_reply_text_1, p); 5383 big.setViewVisibility(R.id.notification_material_reply_progress, 5384 showSpinner ? View.VISIBLE : View.GONE); 5385 big.setProgressIndeterminateTintList( 5386 R.id.notification_material_reply_progress, 5387 ColorStateList.valueOf( 5388 isColorized(p) ? getPrimaryTextColor(p) : resolveContrastColor(p))); 5389 5390 if (replyText.length > 1 && !TextUtils.isEmpty(replyText[1].getText()) 5391 && p.maxRemoteInputHistory > 1) { 5392 big.setViewVisibility(R.id.notification_material_reply_text_2, View.VISIBLE); 5393 big.setTextViewText(R.id.notification_material_reply_text_2, 5394 processTextSpans(replyText[1].getText())); 5395 setTextViewColorSecondary(big, R.id.notification_material_reply_text_2, p); 5396 5397 if (replyText.length > 2 && !TextUtils.isEmpty(replyText[2].getText()) 5398 && p.maxRemoteInputHistory > 2) { 5399 big.setViewVisibility( 5400 R.id.notification_material_reply_text_3, View.VISIBLE); 5401 big.setTextViewText(R.id.notification_material_reply_text_3, 5402 processTextSpans(replyText[2].getText())); 5403 setTextViewColorSecondary(big, R.id.notification_material_reply_text_3, p); 5404 } 5405 } 5406 } 5407 5408 return big; 5409 } 5410 hasValidRemoteInput(Action action)5411 private boolean hasValidRemoteInput(Action action) { 5412 if (TextUtils.isEmpty(action.title) || action.actionIntent == null) { 5413 // Weird actions 5414 return false; 5415 } 5416 5417 RemoteInput[] remoteInputs = action.getRemoteInputs(); 5418 if (remoteInputs == null) { 5419 return false; 5420 } 5421 5422 for (RemoteInput r : remoteInputs) { 5423 CharSequence[] choices = r.getChoices(); 5424 if (r.getAllowFreeFormInput() || (choices != null && choices.length != 0)) { 5425 return true; 5426 } 5427 } 5428 return false; 5429 } 5430 5431 /** 5432 * Construct a RemoteViews for the final 1U notification layout. In order: 5433 * 1. Custom contentView from the caller 5434 * 2. Style's proposed content view 5435 * 3. Standard template view 5436 */ createContentView()5437 public RemoteViews createContentView() { 5438 return createContentView(false /* increasedheight */ ); 5439 } 5440 5441 /** 5442 * Construct a RemoteViews for the smaller content view. 5443 * 5444 * @param increasedHeight true if this layout be created with an increased height. Some 5445 * styles may support showing more then just that basic 1U size 5446 * and the system may decide to render important notifications 5447 * slightly bigger even when collapsed. 5448 * 5449 * @hide 5450 */ createContentView(boolean increasedHeight)5451 public RemoteViews createContentView(boolean increasedHeight) { 5452 if (mN.contentView != null && useExistingRemoteView()) { 5453 return mN.contentView; 5454 } else if (mStyle != null) { 5455 final RemoteViews styleView = mStyle.makeContentView(increasedHeight); 5456 if (styleView != null) { 5457 return styleView; 5458 } 5459 } 5460 return applyStandardTemplate(getBaseLayoutResource(), null /* result */); 5461 } 5462 useExistingRemoteView()5463 private boolean useExistingRemoteView() { 5464 return mStyle == null || (!mStyle.displayCustomViewInline() 5465 && !mRebuildStyledRemoteViews); 5466 } 5467 5468 /** 5469 * Construct a RemoteViews for the final big notification layout. 5470 */ createBigContentView()5471 public RemoteViews createBigContentView() { 5472 RemoteViews result = null; 5473 if (mN.bigContentView != null && useExistingRemoteView()) { 5474 return mN.bigContentView; 5475 } else if (mStyle != null) { 5476 result = mStyle.makeBigContentView(); 5477 hideLine1Text(result); 5478 } else if (mActions.size() != 0) { 5479 result = applyStandardTemplateWithActions(getBigBaseLayoutResource(), 5480 null /* result */); 5481 } 5482 makeHeaderExpanded(result); 5483 return result; 5484 } 5485 5486 /** 5487 * Construct a RemoteViews for the final notification header only. This will not be 5488 * colorized. 5489 * 5490 * @hide 5491 */ makeNotificationHeader()5492 public RemoteViews makeNotificationHeader() { 5493 return makeNotificationHeader(mParams.reset().fillTextsFrom(this)); 5494 } 5495 5496 /** 5497 * Construct a RemoteViews for the final notification header only. This will not be 5498 * colorized. 5499 * 5500 * @param p the template params to inflate this with 5501 */ makeNotificationHeader(StandardTemplateParams p)5502 private RemoteViews makeNotificationHeader(StandardTemplateParams p) { 5503 // Headers on their own are never colorized 5504 p.disallowColorization(); 5505 RemoteViews header = new BuilderRemoteViews(mContext.getApplicationInfo(), 5506 R.layout.notification_template_header); 5507 resetNotificationHeader(header); 5508 bindNotificationHeader(header, p); 5509 return header; 5510 } 5511 5512 /** 5513 * Construct a RemoteViews for the ambient version of the notification. 5514 * 5515 * @hide 5516 */ makeAmbientNotification()5517 public RemoteViews makeAmbientNotification() { 5518 RemoteViews headsUpContentView = createHeadsUpContentView(false /* increasedHeight */); 5519 if (headsUpContentView != null) { 5520 return headsUpContentView; 5521 } 5522 return createContentView(); 5523 } 5524 hideLine1Text(RemoteViews result)5525 private void hideLine1Text(RemoteViews result) { 5526 if (result != null) { 5527 result.setViewVisibility(R.id.text_line_1, View.GONE); 5528 } 5529 } 5530 5531 /** 5532 * Adapt the Notification header if this view is used as an expanded view. 5533 * 5534 * @hide 5535 */ makeHeaderExpanded(RemoteViews result)5536 public static void makeHeaderExpanded(RemoteViews result) { 5537 if (result != null) { 5538 result.setBoolean(R.id.notification_header, "setExpanded", true); 5539 } 5540 } 5541 5542 /** 5543 * Construct a RemoteViews for the final heads-up notification layout. 5544 * 5545 * @param increasedHeight true if this layout be created with an increased height. Some 5546 * styles may support showing more then just that basic 1U size 5547 * and the system may decide to render important notifications 5548 * slightly bigger even when collapsed. 5549 * 5550 * @hide 5551 */ createHeadsUpContentView(boolean increasedHeight)5552 public RemoteViews createHeadsUpContentView(boolean increasedHeight) { 5553 if (mN.headsUpContentView != null && useExistingRemoteView()) { 5554 return mN.headsUpContentView; 5555 } else if (mStyle != null) { 5556 final RemoteViews styleView = mStyle.makeHeadsUpContentView(increasedHeight); 5557 if (styleView != null) { 5558 return styleView; 5559 } 5560 } else if (mActions.size() == 0) { 5561 return null; 5562 } 5563 5564 // We only want at most a single remote input history to be shown here, otherwise 5565 // the content would become squished. 5566 StandardTemplateParams p = mParams.reset().fillTextsFrom(this) 5567 .setMaxRemoteInputHistory(1); 5568 return applyStandardTemplateWithActions(getBigBaseLayoutResource(), 5569 p, 5570 null /* result */); 5571 } 5572 5573 /** 5574 * Construct a RemoteViews for the final heads-up notification layout. 5575 */ createHeadsUpContentView()5576 public RemoteViews createHeadsUpContentView() { 5577 return createHeadsUpContentView(false /* useIncreasedHeight */); 5578 } 5579 5580 /** 5581 * Construct a RemoteViews for the display in public contexts like on the lockscreen. 5582 * 5583 * @param isLowPriority is this notification low priority 5584 * @hide 5585 */ 5586 @UnsupportedAppUsage makePublicContentView(boolean isLowPriority)5587 public RemoteViews makePublicContentView(boolean isLowPriority) { 5588 if (mN.publicVersion != null) { 5589 final Builder builder = recoverBuilder(mContext, mN.publicVersion); 5590 return builder.createContentView(); 5591 } 5592 Bundle savedBundle = mN.extras; 5593 Style style = mStyle; 5594 mStyle = null; 5595 Icon largeIcon = mN.mLargeIcon; 5596 mN.mLargeIcon = null; 5597 Bitmap largeIconLegacy = mN.largeIcon; 5598 mN.largeIcon = null; 5599 ArrayList<Action> actions = mActions; 5600 mActions = new ArrayList<>(); 5601 Bundle publicExtras = new Bundle(); 5602 publicExtras.putBoolean(EXTRA_SHOW_WHEN, 5603 savedBundle.getBoolean(EXTRA_SHOW_WHEN)); 5604 publicExtras.putBoolean(EXTRA_SHOW_CHRONOMETER, 5605 savedBundle.getBoolean(EXTRA_SHOW_CHRONOMETER)); 5606 publicExtras.putBoolean(EXTRA_CHRONOMETER_COUNT_DOWN, 5607 savedBundle.getBoolean(EXTRA_CHRONOMETER_COUNT_DOWN)); 5608 String appName = savedBundle.getString(EXTRA_SUBSTITUTE_APP_NAME); 5609 if (appName != null) { 5610 publicExtras.putString(EXTRA_SUBSTITUTE_APP_NAME, appName); 5611 } 5612 mN.extras = publicExtras; 5613 RemoteViews view; 5614 StandardTemplateParams params = mParams.reset().fillTextsFrom(this); 5615 if (isLowPriority) { 5616 params.forceDefaultColor(); 5617 } 5618 view = makeNotificationHeader(params); 5619 view.setBoolean(R.id.notification_header, "setExpandOnlyOnButton", true); 5620 mN.extras = savedBundle; 5621 mN.mLargeIcon = largeIcon; 5622 mN.largeIcon = largeIconLegacy; 5623 mActions = actions; 5624 mStyle = style; 5625 return view; 5626 } 5627 5628 /** 5629 * Construct a content view for the display when low - priority 5630 * 5631 * @param useRegularSubtext uses the normal subtext set if there is one available. Otherwise 5632 * a new subtext is created consisting of the content of the 5633 * notification. 5634 * @hide 5635 */ makeLowPriorityContentView(boolean useRegularSubtext)5636 public RemoteViews makeLowPriorityContentView(boolean useRegularSubtext) { 5637 StandardTemplateParams p = mParams.reset() 5638 .forceDefaultColor() 5639 .fillTextsFrom(this); 5640 if (!useRegularSubtext || TextUtils.isEmpty(mParams.summaryText)) { 5641 p.summaryText(createSummaryText()); 5642 } 5643 RemoteViews header = makeNotificationHeader(p); 5644 header.setBoolean(R.id.notification_header, "setAcceptAllTouches", true); 5645 return header; 5646 } 5647 createSummaryText()5648 private CharSequence createSummaryText() { 5649 CharSequence titleText = mN.extras.getCharSequence(Notification.EXTRA_TITLE); 5650 if (USE_ONLY_TITLE_IN_LOW_PRIORITY_SUMMARY) { 5651 return titleText; 5652 } 5653 SpannableStringBuilder summary = new SpannableStringBuilder(); 5654 if (titleText == null) { 5655 titleText = mN.extras.getCharSequence(Notification.EXTRA_TITLE_BIG); 5656 } 5657 BidiFormatter bidi = BidiFormatter.getInstance(); 5658 if (titleText != null) { 5659 summary.append(bidi.unicodeWrap(titleText)); 5660 } 5661 CharSequence contentText = mN.extras.getCharSequence(Notification.EXTRA_TEXT); 5662 if (titleText != null && contentText != null) { 5663 summary.append(bidi.unicodeWrap(mContext.getText( 5664 R.string.notification_header_divider_symbol_with_spaces))); 5665 } 5666 if (contentText != null) { 5667 summary.append(bidi.unicodeWrap(contentText)); 5668 } 5669 return summary; 5670 } 5671 generateActionButton(Action action, boolean emphazisedMode, StandardTemplateParams p)5672 private RemoteViews generateActionButton(Action action, boolean emphazisedMode, 5673 StandardTemplateParams p) { 5674 final boolean tombstone = (action.actionIntent == null); 5675 RemoteViews button = new BuilderRemoteViews(mContext.getApplicationInfo(), 5676 emphazisedMode ? getEmphasizedActionLayoutResource() 5677 : tombstone ? getActionTombstoneLayoutResource() 5678 : getActionLayoutResource()); 5679 if (!tombstone) { 5680 button.setOnClickPendingIntent(R.id.action0, action.actionIntent); 5681 } 5682 button.setContentDescription(R.id.action0, action.title); 5683 if (action.mRemoteInputs != null) { 5684 button.setRemoteInputs(R.id.action0, action.mRemoteInputs); 5685 } 5686 if (emphazisedMode) { 5687 // change the background bgColor 5688 CharSequence title = action.title; 5689 ColorStateList[] outResultColor = null; 5690 int background = resolveBackgroundColor(p); 5691 if (isLegacy()) { 5692 title = ContrastColorUtil.clearColorSpans(title); 5693 } else { 5694 outResultColor = new ColorStateList[1]; 5695 title = ensureColorSpanContrast(title, background, outResultColor); 5696 } 5697 button.setTextViewText(R.id.action0, processTextSpans(title)); 5698 setTextViewColorPrimary(button, R.id.action0, p); 5699 int rippleColor; 5700 boolean hasColorOverride = outResultColor != null && outResultColor[0] != null; 5701 if (hasColorOverride) { 5702 // There's a span spanning the full text, let's take it and use it as the 5703 // background color 5704 background = outResultColor[0].getDefaultColor(); 5705 int textColor = ContrastColorUtil.resolvePrimaryColor(mContext, 5706 background, mInNightMode); 5707 button.setTextColor(R.id.action0, textColor); 5708 rippleColor = textColor; 5709 } else if (getRawColor(p) != COLOR_DEFAULT && !isColorized(p) 5710 && mTintActionButtons && !mInNightMode) { 5711 rippleColor = resolveContrastColor(p); 5712 button.setTextColor(R.id.action0, rippleColor); 5713 } else { 5714 rippleColor = getPrimaryTextColor(p); 5715 } 5716 // We only want about 20% alpha for the ripple 5717 rippleColor = (rippleColor & 0x00ffffff) | 0x33000000; 5718 button.setColorStateList(R.id.action0, "setRippleColor", 5719 ColorStateList.valueOf(rippleColor)); 5720 button.setColorStateList(R.id.action0, "setButtonBackground", 5721 ColorStateList.valueOf(background)); 5722 button.setBoolean(R.id.action0, "setHasStroke", !hasColorOverride); 5723 } else { 5724 button.setTextViewText(R.id.action0, processTextSpans( 5725 processLegacyText(action.title))); 5726 if (isColorized(p)) { 5727 setTextViewColorPrimary(button, R.id.action0, p); 5728 } else if (getRawColor(p) != COLOR_DEFAULT && mTintActionButtons) { 5729 button.setTextColor(R.id.action0, resolveContrastColor(p)); 5730 } 5731 } 5732 button.setIntTag(R.id.action0, R.id.notification_action_index_tag, 5733 mActions.indexOf(action)); 5734 return button; 5735 } 5736 5737 /** 5738 * Ensures contrast on color spans against a background color. also returns the color of the 5739 * text if a span was found that spans over the whole text. 5740 * 5741 * @param charSequence the charSequence on which the spans are 5742 * @param background the background color to ensure the contrast against 5743 * @param outResultColor an array in which a color will be returned as the first element if 5744 * there exists a full length color span. 5745 * @return the contrasted charSequence 5746 */ ensureColorSpanContrast(CharSequence charSequence, int background, ColorStateList[] outResultColor)5747 private CharSequence ensureColorSpanContrast(CharSequence charSequence, int background, 5748 ColorStateList[] outResultColor) { 5749 if (charSequence instanceof Spanned) { 5750 Spanned ss = (Spanned) charSequence; 5751 Object[] spans = ss.getSpans(0, ss.length(), Object.class); 5752 SpannableStringBuilder builder = new SpannableStringBuilder(ss.toString()); 5753 for (Object span : spans) { 5754 Object resultSpan = span; 5755 int spanStart = ss.getSpanStart(span); 5756 int spanEnd = ss.getSpanEnd(span); 5757 boolean fullLength = (spanEnd - spanStart) == charSequence.length(); 5758 if (resultSpan instanceof CharacterStyle) { 5759 resultSpan = ((CharacterStyle) span).getUnderlying(); 5760 } 5761 if (resultSpan instanceof TextAppearanceSpan) { 5762 TextAppearanceSpan originalSpan = (TextAppearanceSpan) resultSpan; 5763 ColorStateList textColor = originalSpan.getTextColor(); 5764 if (textColor != null) { 5765 int[] colors = textColor.getColors(); 5766 int[] newColors = new int[colors.length]; 5767 for (int i = 0; i < newColors.length; i++) { 5768 newColors[i] = ContrastColorUtil.ensureLargeTextContrast( 5769 colors[i], background, mInNightMode); 5770 } 5771 textColor = new ColorStateList(textColor.getStates().clone(), 5772 newColors); 5773 if (fullLength) { 5774 outResultColor[0] = textColor; 5775 // Let's drop the color from the span 5776 textColor = null; 5777 } 5778 resultSpan = new TextAppearanceSpan( 5779 originalSpan.getFamily(), 5780 originalSpan.getTextStyle(), 5781 originalSpan.getTextSize(), 5782 textColor, 5783 originalSpan.getLinkTextColor()); 5784 } 5785 } else if (resultSpan instanceof ForegroundColorSpan) { 5786 ForegroundColorSpan originalSpan = (ForegroundColorSpan) resultSpan; 5787 int foregroundColor = originalSpan.getForegroundColor(); 5788 foregroundColor = ContrastColorUtil.ensureLargeTextContrast( 5789 foregroundColor, background, mInNightMode); 5790 if (fullLength) { 5791 outResultColor[0] = ColorStateList.valueOf(foregroundColor); 5792 resultSpan = null; 5793 } else { 5794 resultSpan = new ForegroundColorSpan(foregroundColor); 5795 } 5796 } else { 5797 resultSpan = span; 5798 } 5799 if (resultSpan != null) { 5800 builder.setSpan(resultSpan, spanStart, spanEnd, ss.getSpanFlags(span)); 5801 } 5802 } 5803 return builder; 5804 } 5805 return charSequence; 5806 } 5807 5808 /** 5809 * @return Whether we are currently building a notification from a legacy (an app that 5810 * doesn't create material notifications by itself) app. 5811 */ isLegacy()5812 private boolean isLegacy() { 5813 if (!mIsLegacyInitialized) { 5814 mIsLegacy = mContext.getApplicationInfo().targetSdkVersion 5815 < Build.VERSION_CODES.LOLLIPOP; 5816 mIsLegacyInitialized = true; 5817 } 5818 return mIsLegacy; 5819 } 5820 5821 private CharSequence processLegacyText(CharSequence charSequence) { 5822 boolean isAlreadyLightText = isLegacy() || textColorsNeedInversion(); 5823 if (isAlreadyLightText) { 5824 return getColorUtil().invertCharSequenceColors(charSequence); 5825 } else { 5826 return charSequence; 5827 } 5828 } 5829 5830 /** 5831 * Apply any necessariy colors to the small icon 5832 */ 5833 private void processSmallIconColor(Icon smallIcon, RemoteViews contentView, 5834 StandardTemplateParams p) { 5835 boolean colorable = !isLegacy() || getColorUtil().isGrayscaleIcon(mContext, smallIcon); 5836 int color; 5837 if (isColorized(p)) { 5838 color = getPrimaryTextColor(p); 5839 } else { 5840 color = resolveContrastColor(p); 5841 } 5842 if (colorable) { 5843 contentView.setDrawableTint(R.id.icon, false, color, 5844 PorterDuff.Mode.SRC_ATOP); 5845 5846 } 5847 contentView.setInt(R.id.icon, "setOriginalIconColor", 5848 colorable ? color : NotificationHeaderView.NO_COLOR); 5849 } 5850 5851 /** 5852 * Make the largeIcon dark if it's a fake smallIcon (that is, 5853 * if it's grayscale). 5854 */ 5855 // TODO: also check bounds, transparency, that sort of thing. 5856 private void processLargeLegacyIcon(Icon largeIcon, RemoteViews contentView, 5857 StandardTemplateParams p) { 5858 if (largeIcon != null && isLegacy() 5859 && getColorUtil().isGrayscaleIcon(mContext, largeIcon)) { 5860 // resolve color will fall back to the default when legacy 5861 contentView.setDrawableTint(R.id.icon, false, resolveContrastColor(p), 5862 PorterDuff.Mode.SRC_ATOP); 5863 } 5864 } 5865 5866 private void sanitizeColor() { 5867 if (mN.color != COLOR_DEFAULT) { 5868 mN.color |= 0xFF000000; // no alpha for custom colors 5869 } 5870 } 5871 5872 int resolveContrastColor(StandardTemplateParams p) { 5873 int rawColor = getRawColor(p); 5874 if (mCachedContrastColorIsFor == rawColor && mCachedContrastColor != COLOR_INVALID) { 5875 return mCachedContrastColor; 5876 } 5877 5878 int color; 5879 int background = mContext.getColor( 5880 com.android.internal.R.color.notification_material_background_color); 5881 if (rawColor == COLOR_DEFAULT) { 5882 ensureColors(p); 5883 color = ContrastColorUtil.resolveDefaultColor(mContext, background, mInNightMode); 5884 } else { 5885 color = ContrastColorUtil.resolveContrastColor(mContext, rawColor, 5886 background, mInNightMode); 5887 } 5888 if (Color.alpha(color) < 255) { 5889 // alpha doesn't go well for color filters, so let's blend it manually 5890 color = ContrastColorUtil.compositeColors(color, background); 5891 } 5892 mCachedContrastColorIsFor = rawColor; 5893 return mCachedContrastColor = color; 5894 } 5895 5896 /** 5897 * Return the raw color of this Notification, which doesn't necessarily satisfy contrast. 5898 * 5899 * @see #resolveContrastColor(StandardTemplateParams) for the contrasted color 5900 * @param p the template params to inflate this with 5901 */ 5902 private int getRawColor(StandardTemplateParams p) { 5903 if (p.forceDefaultColor) { 5904 return COLOR_DEFAULT; 5905 } 5906 return mN.color; 5907 } 5908 5909 int resolveNeutralColor() { 5910 if (mNeutralColor != COLOR_INVALID) { 5911 return mNeutralColor; 5912 } 5913 int background = mContext.getColor( 5914 com.android.internal.R.color.notification_material_background_color); 5915 mNeutralColor = ContrastColorUtil.resolveDefaultColor(mContext, background, 5916 mInNightMode); 5917 if (Color.alpha(mNeutralColor) < 255) { 5918 // alpha doesn't go well for color filters, so let's blend it manually 5919 mNeutralColor = ContrastColorUtil.compositeColors(mNeutralColor, background); 5920 } 5921 return mNeutralColor; 5922 } 5923 5924 /** 5925 * Apply the unstyled operations and return a new {@link Notification} object. 5926 * @hide 5927 */ 5928 @NonNull 5929 public Notification buildUnstyled() { 5930 if (mActions.size() > 0) { 5931 mN.actions = new Action[mActions.size()]; 5932 mActions.toArray(mN.actions); 5933 } 5934 if (!mPersonList.isEmpty()) { 5935 mN.extras.putParcelableArrayList(EXTRA_PEOPLE_LIST, mPersonList); 5936 } 5937 if (mN.bigContentView != null || mN.contentView != null 5938 || mN.headsUpContentView != null) { 5939 mN.extras.putBoolean(EXTRA_CONTAINS_CUSTOM_VIEW, true); 5940 } 5941 return mN; 5942 } 5943 5944 /** 5945 * Creates a Builder from an existing notification so further changes can be made. 5946 * @param context The context for your application / activity. 5947 * @param n The notification to create a Builder from. 5948 */ 5949 @NonNull 5950 public static Notification.Builder recoverBuilder(Context context, Notification n) { 5951 // Re-create notification context so we can access app resources. 5952 ApplicationInfo applicationInfo = n.extras.getParcelable( 5953 EXTRA_BUILDER_APPLICATION_INFO); 5954 Context builderContext; 5955 if (applicationInfo != null) { 5956 try { 5957 builderContext = context.createApplicationContext(applicationInfo, 5958 Context.CONTEXT_RESTRICTED); 5959 } catch (NameNotFoundException e) { 5960 Log.e(TAG, "ApplicationInfo " + applicationInfo + " not found"); 5961 builderContext = context; // try with our context 5962 } 5963 } else { 5964 builderContext = context; // try with given context 5965 } 5966 5967 return new Builder(builderContext, n); 5968 } 5969 5970 /** 5971 * Determines whether the platform can generate contextual actions for a notification. 5972 * By default this is true. 5973 */ 5974 @NonNull 5975 public Builder setAllowSystemGeneratedContextualActions(boolean allowed) { 5976 mN.mAllowSystemGeneratedContextualActions = allowed; 5977 return this; 5978 } 5979 5980 /** 5981 * @deprecated Use {@link #build()} instead. 5982 */ 5983 @Deprecated 5984 public Notification getNotification() { 5985 return build(); 5986 } 5987 5988 /** 5989 * Combine all of the options that have been set and return a new {@link Notification} 5990 * object. 5991 * 5992 * If this notification has {@link BubbleMetadata} attached that was created with 5993 * a shortcutId a check will be performed to ensure the shortcutId supplied to bubble 5994 * metadata matches the shortcutId set on the notification builder, if one was set. 5995 * If the shortcutId's were specified but do not match, an exception is thrown here. 5996 * 5997 * @see BubbleMetadata.Builder#Builder(String) 5998 * @see #setShortcutId(String) 5999 */ 6000 @NonNull 6001 public Notification build() { 6002 // Check shortcut id matches 6003 if (mN.mShortcutId != null 6004 && mN.mBubbleMetadata != null 6005 && mN.mBubbleMetadata.getShortcutId() != null 6006 && !mN.mShortcutId.equals(mN.mBubbleMetadata.getShortcutId())) { 6007 throw new IllegalArgumentException( 6008 "Notification and BubbleMetadata shortcut id's don't match," 6009 + " notification: " + mN.mShortcutId 6010 + " vs bubble: " + mN.mBubbleMetadata.getShortcutId()); 6011 } 6012 6013 // first, add any extras from the calling code 6014 if (mUserExtras != null) { 6015 mN.extras = getAllExtras(); 6016 } 6017 6018 mN.creationTime = System.currentTimeMillis(); 6019 6020 // lazy stuff from mContext; see comment in Builder(Context, Notification) 6021 Notification.addFieldsFromContext(mContext, mN); 6022 6023 buildUnstyled(); 6024 6025 if (mStyle != null) { 6026 mStyle.reduceImageSizes(mContext); 6027 mStyle.purgeResources(); 6028 mStyle.validate(mContext); 6029 mStyle.buildStyled(mN); 6030 } 6031 mN.reduceImageSizes(mContext); 6032 6033 if (mContext.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N 6034 && (useExistingRemoteView())) { 6035 if (mN.contentView == null) { 6036 mN.contentView = createContentView(); 6037 mN.extras.putInt(EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT, 6038 mN.contentView.getSequenceNumber()); 6039 } 6040 if (mN.bigContentView == null) { 6041 mN.bigContentView = createBigContentView(); 6042 if (mN.bigContentView != null) { 6043 mN.extras.putInt(EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT, 6044 mN.bigContentView.getSequenceNumber()); 6045 } 6046 } 6047 if (mN.headsUpContentView == null) { 6048 mN.headsUpContentView = createHeadsUpContentView(); 6049 if (mN.headsUpContentView != null) { 6050 mN.extras.putInt(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT, 6051 mN.headsUpContentView.getSequenceNumber()); 6052 } 6053 } 6054 } 6055 6056 if ((mN.defaults & DEFAULT_LIGHTS) != 0) { 6057 mN.flags |= FLAG_SHOW_LIGHTS; 6058 } 6059 6060 mN.allPendingIntents = null; 6061 6062 return mN; 6063 } 6064 6065 /** 6066 * Apply this Builder to an existing {@link Notification} object. 6067 * 6068 * @hide 6069 */ 6070 @NonNull 6071 public Notification buildInto(@NonNull Notification n) { 6072 build().cloneInto(n, true); 6073 return n; 6074 } 6075 6076 /** 6077 * Removes RemoteViews that were created for compatibility from {@param n}, if they did not 6078 * change. 6079 * 6080 * @return {@param n}, if no stripping is needed, otherwise a stripped clone of {@param n}. 6081 * 6082 * @hide 6083 */ 6084 public static Notification maybeCloneStrippedForDelivery(Notification n) { 6085 String templateClass = n.extras.getString(EXTRA_TEMPLATE); 6086 6087 // Only strip views for known Styles because we won't know how to 6088 // re-create them otherwise. 6089 if (!TextUtils.isEmpty(templateClass) 6090 && getNotificationStyleClass(templateClass) == null) { 6091 return n; 6092 } 6093 6094 // Only strip unmodified BuilderRemoteViews. 6095 boolean stripContentView = n.contentView instanceof BuilderRemoteViews && 6096 n.extras.getInt(EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT, -1) == 6097 n.contentView.getSequenceNumber(); 6098 boolean stripBigContentView = n.bigContentView instanceof BuilderRemoteViews && 6099 n.extras.getInt(EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT, -1) == 6100 n.bigContentView.getSequenceNumber(); 6101 boolean stripHeadsUpContentView = n.headsUpContentView instanceof BuilderRemoteViews && 6102 n.extras.getInt(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT, -1) == 6103 n.headsUpContentView.getSequenceNumber(); 6104 6105 // Nothing to do here, no need to clone. 6106 if (!stripContentView && !stripBigContentView && !stripHeadsUpContentView) { 6107 return n; 6108 } 6109 6110 Notification clone = n.clone(); 6111 if (stripContentView) { 6112 clone.contentView = null; 6113 clone.extras.remove(EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT); 6114 } 6115 if (stripBigContentView) { 6116 clone.bigContentView = null; 6117 clone.extras.remove(EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT); 6118 } 6119 if (stripHeadsUpContentView) { 6120 clone.headsUpContentView = null; 6121 clone.extras.remove(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT); 6122 } 6123 return clone; 6124 } 6125 6126 @UnsupportedAppUsage 6127 private int getBaseLayoutResource() { 6128 return R.layout.notification_template_material_base; 6129 } 6130 6131 private int getBigBaseLayoutResource() { 6132 return R.layout.notification_template_material_big_base; 6133 } 6134 6135 private int getBigPictureLayoutResource() { 6136 return R.layout.notification_template_material_big_picture; 6137 } 6138 6139 private int getBigTextLayoutResource() { 6140 return R.layout.notification_template_material_big_text; 6141 } 6142 6143 private int getInboxLayoutResource() { 6144 return R.layout.notification_template_material_inbox; 6145 } 6146 6147 private int getMessagingLayoutResource() { 6148 return R.layout.notification_template_material_messaging; 6149 } 6150 6151 private int getConversationLayoutResource() { 6152 return R.layout.notification_template_material_conversation; 6153 } 6154 6155 private int getActionLayoutResource() { 6156 return R.layout.notification_material_action; 6157 } 6158 6159 private int getEmphasizedActionLayoutResource() { 6160 return R.layout.notification_material_action_emphasized; 6161 } 6162 6163 private int getActionTombstoneLayoutResource() { 6164 return R.layout.notification_material_action_tombstone; 6165 } 6166 6167 private int getBackgroundColor(StandardTemplateParams p) { 6168 if (isColorized(p)) { 6169 return mBackgroundColor != COLOR_INVALID ? mBackgroundColor : getRawColor(p); 6170 } else { 6171 return COLOR_DEFAULT; 6172 } 6173 } 6174 6175 /** 6176 * Gets a neutral color that can be used for icons or similar that should not stand out. 6177 * @param p the template params to inflate this with 6178 */ 6179 private int getNeutralColor(StandardTemplateParams p) { 6180 if (isColorized(p)) { 6181 return getSecondaryTextColor(p); 6182 } else { 6183 return resolveNeutralColor(); 6184 } 6185 } 6186 6187 /** 6188 * Same as getBackgroundColor but also resolved the default color to the background. 6189 * @param p the template params to inflate this with 6190 */ 6191 private int resolveBackgroundColor(StandardTemplateParams p) { 6192 int backgroundColor = getBackgroundColor(p); 6193 if (backgroundColor == COLOR_DEFAULT) { 6194 backgroundColor = mContext.getColor( 6195 com.android.internal.R.color.notification_material_background_color); 6196 } 6197 return backgroundColor; 6198 } 6199 6200 private boolean shouldTintActionButtons() { 6201 return mTintActionButtons; 6202 } 6203 6204 private boolean textColorsNeedInversion() { 6205 if (mStyle == null || !MediaStyle.class.equals(mStyle.getClass())) { 6206 return false; 6207 } 6208 int targetSdkVersion = mContext.getApplicationInfo().targetSdkVersion; 6209 return targetSdkVersion > Build.VERSION_CODES.M 6210 && targetSdkVersion < Build.VERSION_CODES.O; 6211 } 6212 6213 /** 6214 * Set a color palette to be used as the background and textColors 6215 * 6216 * @param backgroundColor the color to be used as the background 6217 * @param foregroundColor the color to be used as the foreground 6218 * 6219 * @hide 6220 */ 6221 public void setColorPalette(int backgroundColor, int foregroundColor) { 6222 mBackgroundColor = backgroundColor; 6223 mForegroundColor = foregroundColor; 6224 mTextColorsAreForBackground = COLOR_INVALID; 6225 ensureColors(mParams.reset().fillTextsFrom(this)); 6226 } 6227 6228 /** 6229 * Forces all styled remoteViews to be built from scratch and not use any cached 6230 * RemoteViews. 6231 * This is needed for legacy apps that are baking in their remoteviews into the 6232 * notification. 6233 * 6234 * @hide 6235 */ 6236 public void setRebuildStyledRemoteViews(boolean rebuild) { 6237 mRebuildStyledRemoteViews = rebuild; 6238 } 6239 6240 /** 6241 * Get the text that should be displayed in the statusBar when heads upped. This is 6242 * usually just the app name, but may be different depending on the style. 6243 * 6244 * @param publicMode If true, return a text that is safe to display in public. 6245 * 6246 * @hide 6247 */ 6248 public CharSequence getHeadsUpStatusBarText(boolean publicMode) { 6249 if (mStyle != null && !publicMode) { 6250 CharSequence text = mStyle.getHeadsUpStatusBarText(); 6251 if (!TextUtils.isEmpty(text)) { 6252 return text; 6253 } 6254 } 6255 return loadHeaderAppName(); 6256 } 6257 6258 /** 6259 * @return if this builder uses a template 6260 * 6261 * @hide 6262 */ 6263 public boolean usesTemplate() { 6264 return (mN.contentView == null && mN.headsUpContentView == null 6265 && mN.bigContentView == null) 6266 || (mStyle != null && mStyle.displayCustomViewInline()); 6267 } 6268 } 6269 6270 /** 6271 * Reduces the image sizes to conform to a maximum allowed size. This also processes all custom 6272 * remote views. 6273 * 6274 * @hide 6275 */ 6276 void reduceImageSizes(Context context) { 6277 if (extras.getBoolean(EXTRA_REDUCED_IMAGES)) { 6278 return; 6279 } 6280 boolean isLowRam = ActivityManager.isLowRamDeviceStatic(); 6281 if (mLargeIcon != null || largeIcon != null) { 6282 Resources resources = context.getResources(); 6283 Class<? extends Style> style = getNotificationStyle(); 6284 int maxWidth = resources.getDimensionPixelSize(isLowRam 6285 ? R.dimen.notification_right_icon_size_low_ram 6286 : R.dimen.notification_right_icon_size); 6287 int maxHeight = maxWidth; 6288 if (MediaStyle.class.equals(style) 6289 || DecoratedMediaCustomViewStyle.class.equals(style)) { 6290 maxHeight = resources.getDimensionPixelSize(isLowRam 6291 ? R.dimen.notification_media_image_max_height_low_ram 6292 : R.dimen.notification_media_image_max_height); 6293 maxWidth = resources.getDimensionPixelSize(isLowRam 6294 ? R.dimen.notification_media_image_max_width_low_ram 6295 : R.dimen.notification_media_image_max_width); 6296 } 6297 if (mLargeIcon != null) { 6298 mLargeIcon.scaleDownIfNecessary(maxWidth, maxHeight); 6299 } 6300 if (largeIcon != null) { 6301 largeIcon = Icon.scaleDownIfNecessary(largeIcon, maxWidth, maxHeight); 6302 } 6303 } 6304 reduceImageSizesForRemoteView(contentView, context, isLowRam); 6305 reduceImageSizesForRemoteView(headsUpContentView, context, isLowRam); 6306 reduceImageSizesForRemoteView(bigContentView, context, isLowRam); 6307 extras.putBoolean(EXTRA_REDUCED_IMAGES, true); 6308 } 6309 6310 private void reduceImageSizesForRemoteView(RemoteViews remoteView, Context context, 6311 boolean isLowRam) { 6312 if (remoteView != null) { 6313 Resources resources = context.getResources(); 6314 int maxWidth = resources.getDimensionPixelSize(isLowRam 6315 ? R.dimen.notification_custom_view_max_image_width_low_ram 6316 : R.dimen.notification_custom_view_max_image_width); 6317 int maxHeight = resources.getDimensionPixelSize(isLowRam 6318 ? R.dimen.notification_custom_view_max_image_height_low_ram 6319 : R.dimen.notification_custom_view_max_image_height); 6320 remoteView.reduceImageSizes(maxWidth, maxHeight); 6321 } 6322 } 6323 6324 /** 6325 * @return whether this notification is a foreground service notification 6326 * @hide 6327 */ 6328 public boolean isForegroundService() { 6329 return (flags & Notification.FLAG_FOREGROUND_SERVICE) != 0; 6330 } 6331 6332 /** 6333 * @return whether this notification has a media session attached 6334 * @hide 6335 */ 6336 public boolean hasMediaSession() { 6337 return extras.getParcelable(Notification.EXTRA_MEDIA_SESSION) != null; 6338 } 6339 6340 /** 6341 * @return the style class of this notification 6342 * @hide 6343 */ 6344 public Class<? extends Notification.Style> getNotificationStyle() { 6345 String templateClass = extras.getString(Notification.EXTRA_TEMPLATE); 6346 6347 if (!TextUtils.isEmpty(templateClass)) { 6348 return Notification.getNotificationStyleClass(templateClass); 6349 } 6350 return null; 6351 } 6352 6353 /** 6354 * @return true if this notification is colorized. 6355 * 6356 * @hide 6357 */ 6358 public boolean isColorized() { 6359 if (isColorizedMedia()) { 6360 return true; 6361 } 6362 return extras.getBoolean(EXTRA_COLORIZED) 6363 && (hasColorizedPermission() || isForegroundService()); 6364 } 6365 6366 /** 6367 * Returns whether an app can colorize due to the android.permission.USE_COLORIZED_NOTIFICATIONS 6368 * permission. The permission is checked when a notification is enqueued. 6369 */ 6370 private boolean hasColorizedPermission() { 6371 return (flags & Notification.FLAG_CAN_COLORIZE) != 0; 6372 } 6373 6374 /** 6375 * @return true if this notification is colorized and it is a media notification 6376 * 6377 * @hide 6378 */ 6379 public boolean isColorizedMedia() { 6380 Class<? extends Style> style = getNotificationStyle(); 6381 if (MediaStyle.class.equals(style)) { 6382 Boolean colorized = (Boolean) extras.get(EXTRA_COLORIZED); 6383 if ((colorized == null || colorized) && hasMediaSession()) { 6384 return true; 6385 } 6386 } else if (DecoratedMediaCustomViewStyle.class.equals(style)) { 6387 if (extras.getBoolean(EXTRA_COLORIZED) && hasMediaSession()) { 6388 return true; 6389 } 6390 } 6391 return false; 6392 } 6393 6394 6395 /** 6396 * @return true if this is a media notification 6397 * 6398 * @hide 6399 */ 6400 public boolean isMediaNotification() { 6401 Class<? extends Style> style = getNotificationStyle(); 6402 if (MediaStyle.class.equals(style)) { 6403 return true; 6404 } else if (DecoratedMediaCustomViewStyle.class.equals(style)) { 6405 return true; 6406 } 6407 return false; 6408 } 6409 6410 /** 6411 * @return true if this notification is showing as a bubble 6412 * 6413 * @hide 6414 */ 6415 public boolean isBubbleNotification() { 6416 return (flags & Notification.FLAG_BUBBLE) != 0; 6417 } 6418 6419 private boolean hasLargeIcon() { 6420 return mLargeIcon != null || largeIcon != null; 6421 } 6422 6423 /** 6424 * @return true if the notification will show the time; false otherwise 6425 * @hide 6426 */ 6427 public boolean showsTime() { 6428 return when != 0 && extras.getBoolean(EXTRA_SHOW_WHEN); 6429 } 6430 6431 /** 6432 * @return true if the notification will show a chronometer; false otherwise 6433 * @hide 6434 */ 6435 public boolean showsChronometer() { 6436 return when != 0 && extras.getBoolean(EXTRA_SHOW_CHRONOMETER); 6437 } 6438 6439 /** 6440 * @removed 6441 */ 6442 @SystemApi 6443 public static Class<? extends Style> getNotificationStyleClass(String templateClass) { 6444 Class<? extends Style>[] classes = new Class[] { 6445 BigTextStyle.class, BigPictureStyle.class, InboxStyle.class, MediaStyle.class, 6446 DecoratedCustomViewStyle.class, DecoratedMediaCustomViewStyle.class, 6447 MessagingStyle.class }; 6448 for (Class<? extends Style> innerClass : classes) { 6449 if (templateClass.equals(innerClass.getName())) { 6450 return innerClass; 6451 } 6452 } 6453 return null; 6454 } 6455 6456 /** 6457 * An object that can apply a rich notification style to a {@link Notification.Builder} 6458 * object. 6459 */ 6460 public static abstract class Style { 6461 6462 /** 6463 * The number of items allowed simulatanously in the remote input history. 6464 * @hide 6465 */ 6466 static final int MAX_REMOTE_INPUT_HISTORY_LINES = 3; 6467 private CharSequence mBigContentTitle; 6468 6469 /** 6470 * @hide 6471 */ 6472 protected CharSequence mSummaryText = null; 6473 6474 /** 6475 * @hide 6476 */ 6477 protected boolean mSummaryTextSet = false; 6478 6479 protected Builder mBuilder; 6480 6481 /** 6482 * Overrides ContentTitle in the big form of the template. 6483 * This defaults to the value passed to setContentTitle(). 6484 */ 6485 protected void internalSetBigContentTitle(CharSequence title) { 6486 mBigContentTitle = title; 6487 } 6488 6489 /** 6490 * Set the first line of text after the detail section in the big form of the template. 6491 */ 6492 protected void internalSetSummaryText(CharSequence cs) { 6493 mSummaryText = cs; 6494 mSummaryTextSet = true; 6495 } 6496 6497 public void setBuilder(Builder builder) { 6498 if (mBuilder != builder) { 6499 mBuilder = builder; 6500 if (mBuilder != null) { 6501 mBuilder.setStyle(this); 6502 } 6503 } 6504 } 6505 6506 protected void checkBuilder() { 6507 if (mBuilder == null) { 6508 throw new IllegalArgumentException("Style requires a valid Builder object"); 6509 } 6510 } 6511 6512 protected RemoteViews getStandardView(int layoutId) { 6513 StandardTemplateParams p = mBuilder.mParams.reset().fillTextsFrom(mBuilder); 6514 return getStandardView(layoutId, p, null); 6515 } 6516 6517 6518 /** 6519 * Get the standard view for this style. 6520 * 6521 * @param layoutId The layout id to use. 6522 * @param p the params for this inflation. 6523 * @param result The result where template bind information is saved. 6524 * @return A remoteView for this style. 6525 * @hide 6526 */ 6527 protected RemoteViews getStandardView(int layoutId, StandardTemplateParams p, 6528 TemplateBindResult result) { 6529 checkBuilder(); 6530 6531 if (mBigContentTitle != null) { 6532 p.title = mBigContentTitle; 6533 } 6534 6535 RemoteViews contentView = mBuilder.applyStandardTemplateWithActions(layoutId, p, 6536 result); 6537 6538 if (mBigContentTitle != null && mBigContentTitle.equals("")) { 6539 contentView.setViewVisibility(R.id.line1, View.GONE); 6540 } else { 6541 contentView.setViewVisibility(R.id.line1, View.VISIBLE); 6542 } 6543 6544 return contentView; 6545 } 6546 6547 /** 6548 * Construct a Style-specific RemoteViews for the collapsed notification layout. 6549 * The default implementation has nothing additional to add. 6550 * 6551 * @param increasedHeight true if this layout be created with an increased height. 6552 * @hide 6553 */ 6554 public RemoteViews makeContentView(boolean increasedHeight) { 6555 return null; 6556 } 6557 6558 /** 6559 * Construct a Style-specific RemoteViews for the final big notification layout. 6560 * @hide 6561 */ 6562 public RemoteViews makeBigContentView() { 6563 return null; 6564 } 6565 6566 /** 6567 * Construct a Style-specific RemoteViews for the final HUN layout. 6568 * 6569 * @param increasedHeight true if this layout be created with an increased height. 6570 * @hide 6571 */ 6572 public RemoteViews makeHeadsUpContentView(boolean increasedHeight) { 6573 return null; 6574 } 6575 6576 /** 6577 * Apply any style-specific extras to this notification before shipping it out. 6578 * @hide 6579 */ 6580 public void addExtras(Bundle extras) { 6581 if (mSummaryTextSet) { 6582 extras.putCharSequence(EXTRA_SUMMARY_TEXT, mSummaryText); 6583 } 6584 if (mBigContentTitle != null) { 6585 extras.putCharSequence(EXTRA_TITLE_BIG, mBigContentTitle); 6586 } 6587 extras.putString(EXTRA_TEMPLATE, this.getClass().getName()); 6588 } 6589 6590 /** 6591 * Reconstruct the internal state of this Style object from extras. 6592 * @hide 6593 */ 6594 protected void restoreFromExtras(Bundle extras) { 6595 if (extras.containsKey(EXTRA_SUMMARY_TEXT)) { 6596 mSummaryText = extras.getCharSequence(EXTRA_SUMMARY_TEXT); 6597 mSummaryTextSet = true; 6598 } 6599 if (extras.containsKey(EXTRA_TITLE_BIG)) { 6600 mBigContentTitle = extras.getCharSequence(EXTRA_TITLE_BIG); 6601 } 6602 } 6603 6604 6605 /** 6606 * @hide 6607 */ 6608 public Notification buildStyled(Notification wip) { 6609 addExtras(wip.extras); 6610 return wip; 6611 } 6612 6613 /** 6614 * @hide 6615 */ 6616 public void purgeResources() {} 6617 6618 /** 6619 * Calls {@link android.app.Notification.Builder#build()} on the Builder this Style is 6620 * attached to. 6621 * 6622 * @return the fully constructed Notification. 6623 */ 6624 public Notification build() { 6625 checkBuilder(); 6626 return mBuilder.build(); 6627 } 6628 6629 /** 6630 * @hide 6631 * @return true if the style positions the progress bar on the second line; false if the 6632 * style hides the progress bar 6633 */ 6634 protected boolean hasProgress() { 6635 return true; 6636 } 6637 6638 /** 6639 * @hide 6640 * @return Whether we should put the summary be put into the notification header 6641 */ 6642 public boolean hasSummaryInHeader() { 6643 return true; 6644 } 6645 6646 /** 6647 * @hide 6648 * @return Whether custom content views are displayed inline in the style 6649 */ 6650 public boolean displayCustomViewInline() { 6651 return false; 6652 } 6653 6654 /** 6655 * Reduces the image sizes contained in this style. 6656 * 6657 * @hide 6658 */ 6659 public void reduceImageSizes(Context context) { 6660 } 6661 6662 /** 6663 * Validate that this style was properly composed. This is called at build time. 6664 * @hide 6665 */ 6666 public void validate(Context context) { 6667 } 6668 6669 /** 6670 * @hide 6671 */ 6672 public abstract boolean areNotificationsVisiblyDifferent(Style other); 6673 6674 /** 6675 * @return the text that should be displayed in the statusBar when heads-upped. 6676 * If {@code null} is returned, the default implementation will be used. 6677 * 6678 * @hide 6679 */ 6680 public CharSequence getHeadsUpStatusBarText() { 6681 return null; 6682 } 6683 } 6684 6685 /** 6686 * Helper class for generating large-format notifications that include a large image attachment. 6687 * 6688 * Here's how you'd set the <code>BigPictureStyle</code> on a notification: 6689 * <pre class="prettyprint"> 6690 * Notification notif = new Notification.Builder(mContext) 6691 * .setContentTitle("New photo from " + sender.toString()) 6692 * .setContentText(subject) 6693 * .setSmallIcon(R.drawable.new_post) 6694 * .setLargeIcon(aBitmap) 6695 * .setStyle(new Notification.BigPictureStyle() 6696 * .bigPicture(aBigBitmap)) 6697 * .build(); 6698 * </pre> 6699 * 6700 * @see Notification#bigContentView 6701 */ 6702 public static class BigPictureStyle extends Style { 6703 private Bitmap mPicture; 6704 private Icon mBigLargeIcon; 6705 private boolean mBigLargeIconSet = false; 6706 6707 public BigPictureStyle() { 6708 } 6709 6710 /** 6711 * @deprecated use {@code BigPictureStyle()}. 6712 */ 6713 @Deprecated 6714 public BigPictureStyle(Builder builder) { 6715 setBuilder(builder); 6716 } 6717 6718 /** 6719 * Overrides ContentTitle in the big form of the template. 6720 * This defaults to the value passed to setContentTitle(). 6721 */ 6722 public BigPictureStyle setBigContentTitle(CharSequence title) { 6723 internalSetBigContentTitle(safeCharSequence(title)); 6724 return this; 6725 } 6726 6727 /** 6728 * Set the first line of text after the detail section in the big form of the template. 6729 */ 6730 public BigPictureStyle setSummaryText(CharSequence cs) { 6731 internalSetSummaryText(safeCharSequence(cs)); 6732 return this; 6733 } 6734 6735 /** 6736 * @hide 6737 */ 6738 public Bitmap getBigPicture() { 6739 return mPicture; 6740 } 6741 6742 /** 6743 * Provide the bitmap to be used as the payload for the BigPicture notification. 6744 */ 6745 public BigPictureStyle bigPicture(Bitmap b) { 6746 mPicture = b; 6747 return this; 6748 } 6749 6750 /** 6751 * Override the large icon when the big notification is shown. 6752 */ 6753 public BigPictureStyle bigLargeIcon(Bitmap b) { 6754 return bigLargeIcon(b != null ? Icon.createWithBitmap(b) : null); 6755 } 6756 6757 /** 6758 * Override the large icon when the big notification is shown. 6759 */ 6760 public BigPictureStyle bigLargeIcon(Icon icon) { 6761 mBigLargeIconSet = true; 6762 mBigLargeIcon = icon; 6763 return this; 6764 } 6765 6766 /** @hide */ 6767 public static final int MIN_ASHMEM_BITMAP_SIZE = 128 * (1 << 10); 6768 6769 /** 6770 * @hide 6771 */ 6772 @Override 6773 public void purgeResources() { 6774 super.purgeResources(); 6775 if (mPicture != null && 6776 mPicture.isMutable() && 6777 mPicture.getAllocationByteCount() >= MIN_ASHMEM_BITMAP_SIZE) { 6778 mPicture = mPicture.createAshmemBitmap(); 6779 } 6780 if (mBigLargeIcon != null) { 6781 mBigLargeIcon.convertToAshmem(); 6782 } 6783 } 6784 6785 /** 6786 * @hide 6787 */ 6788 @Override 6789 public void reduceImageSizes(Context context) { 6790 super.reduceImageSizes(context); 6791 Resources resources = context.getResources(); 6792 boolean isLowRam = ActivityManager.isLowRamDeviceStatic(); 6793 if (mPicture != null) { 6794 int maxPictureWidth = resources.getDimensionPixelSize(isLowRam 6795 ? R.dimen.notification_big_picture_max_height_low_ram 6796 : R.dimen.notification_big_picture_max_height); 6797 int maxPictureHeight = resources.getDimensionPixelSize(isLowRam 6798 ? R.dimen.notification_big_picture_max_width_low_ram 6799 : R.dimen.notification_big_picture_max_width); 6800 mPicture = Icon.scaleDownIfNecessary(mPicture, maxPictureWidth, maxPictureHeight); 6801 } 6802 if (mBigLargeIcon != null) { 6803 int rightIconSize = resources.getDimensionPixelSize(isLowRam 6804 ? R.dimen.notification_right_icon_size_low_ram 6805 : R.dimen.notification_right_icon_size); 6806 mBigLargeIcon.scaleDownIfNecessary(rightIconSize, rightIconSize); 6807 } 6808 } 6809 6810 /** 6811 * @hide 6812 */ 6813 public RemoteViews makeBigContentView() { 6814 // Replace mN.mLargeIcon with mBigLargeIcon if mBigLargeIconSet 6815 // This covers the following cases: 6816 // 1. mBigLargeIconSet -> mBigLargeIcon (null or non-null) applies, overrides 6817 // mN.mLargeIcon 6818 // 2. !mBigLargeIconSet -> mN.mLargeIcon applies 6819 Icon oldLargeIcon = null; 6820 Bitmap largeIconLegacy = null; 6821 if (mBigLargeIconSet) { 6822 oldLargeIcon = mBuilder.mN.mLargeIcon; 6823 mBuilder.mN.mLargeIcon = mBigLargeIcon; 6824 // The legacy largeIcon might not allow us to clear the image, as it's taken in 6825 // replacement if the other one is null. Because we're restoring these legacy icons 6826 // for old listeners, this is in general non-null. 6827 largeIconLegacy = mBuilder.mN.largeIcon; 6828 mBuilder.mN.largeIcon = null; 6829 } 6830 6831 StandardTemplateParams p = mBuilder.mParams.reset().fillTextsFrom(mBuilder); 6832 RemoteViews contentView = getStandardView(mBuilder.getBigPictureLayoutResource(), 6833 p, null /* result */); 6834 if (mSummaryTextSet) { 6835 contentView.setTextViewText(R.id.text, mBuilder.processTextSpans( 6836 mBuilder.processLegacyText(mSummaryText))); 6837 mBuilder.setTextViewColorSecondary(contentView, R.id.text, p); 6838 contentView.setViewVisibility(R.id.text, View.VISIBLE); 6839 } 6840 mBuilder.setContentMinHeight(contentView, mBuilder.mN.hasLargeIcon()); 6841 6842 if (mBigLargeIconSet) { 6843 mBuilder.mN.mLargeIcon = oldLargeIcon; 6844 mBuilder.mN.largeIcon = largeIconLegacy; 6845 } 6846 6847 contentView.setImageViewBitmap(R.id.big_picture, mPicture); 6848 return contentView; 6849 } 6850 6851 /** 6852 * @hide 6853 */ 6854 public void addExtras(Bundle extras) { 6855 super.addExtras(extras); 6856 6857 if (mBigLargeIconSet) { 6858 extras.putParcelable(EXTRA_LARGE_ICON_BIG, mBigLargeIcon); 6859 } 6860 extras.putParcelable(EXTRA_PICTURE, mPicture); 6861 } 6862 6863 /** 6864 * @hide 6865 */ 6866 @Override 6867 protected void restoreFromExtras(Bundle extras) { 6868 super.restoreFromExtras(extras); 6869 6870 if (extras.containsKey(EXTRA_LARGE_ICON_BIG)) { 6871 mBigLargeIconSet = true; 6872 mBigLargeIcon = extras.getParcelable(EXTRA_LARGE_ICON_BIG); 6873 } 6874 mPicture = extras.getParcelable(EXTRA_PICTURE); 6875 } 6876 6877 /** 6878 * @hide 6879 */ 6880 @Override 6881 public boolean hasSummaryInHeader() { 6882 return false; 6883 } 6884 6885 /** 6886 * @hide 6887 * Note that we aren't actually comparing the contents of the bitmaps here, so this 6888 * is only doing a cursory inspection. Bitmaps of equal size will appear the same. 6889 */ 6890 @Override 6891 public boolean areNotificationsVisiblyDifferent(Style other) { 6892 if (other == null || getClass() != other.getClass()) { 6893 return true; 6894 } 6895 BigPictureStyle otherS = (BigPictureStyle) other; 6896 return areBitmapsObviouslyDifferent(getBigPicture(), otherS.getBigPicture()); 6897 } 6898 6899 private static boolean areBitmapsObviouslyDifferent(Bitmap a, Bitmap b) { 6900 if (a == b) { 6901 return false; 6902 } 6903 if (a == null || b == null) { 6904 return true; 6905 } 6906 return a.getWidth() != b.getWidth() 6907 || a.getHeight() != b.getHeight() 6908 || a.getConfig() != b.getConfig() 6909 || a.getGenerationId() != b.getGenerationId(); 6910 } 6911 } 6912 6913 /** 6914 * Helper class for generating large-format notifications that include a lot of text. 6915 * 6916 * Here's how you'd set the <code>BigTextStyle</code> on a notification: 6917 * <pre class="prettyprint"> 6918 * Notification notif = new Notification.Builder(mContext) 6919 * .setContentTitle("New mail from " + sender.toString()) 6920 * .setContentText(subject) 6921 * .setSmallIcon(R.drawable.new_mail) 6922 * .setLargeIcon(aBitmap) 6923 * .setStyle(new Notification.BigTextStyle() 6924 * .bigText(aVeryLongString)) 6925 * .build(); 6926 * </pre> 6927 * 6928 * @see Notification#bigContentView 6929 */ 6930 public static class BigTextStyle extends Style { 6931 6932 private CharSequence mBigText; 6933 6934 public BigTextStyle() { 6935 } 6936 6937 /** 6938 * @deprecated use {@code BigTextStyle()}. 6939 */ 6940 @Deprecated 6941 public BigTextStyle(Builder builder) { 6942 setBuilder(builder); 6943 } 6944 6945 /** 6946 * Overrides ContentTitle in the big form of the template. 6947 * This defaults to the value passed to setContentTitle(). 6948 */ 6949 public BigTextStyle setBigContentTitle(CharSequence title) { 6950 internalSetBigContentTitle(safeCharSequence(title)); 6951 return this; 6952 } 6953 6954 /** 6955 * Set the first line of text after the detail section in the big form of the template. 6956 */ 6957 public BigTextStyle setSummaryText(CharSequence cs) { 6958 internalSetSummaryText(safeCharSequence(cs)); 6959 return this; 6960 } 6961 6962 /** 6963 * Provide the longer text to be displayed in the big form of the 6964 * template in place of the content text. 6965 */ 6966 public BigTextStyle bigText(CharSequence cs) { 6967 mBigText = safeCharSequence(cs); 6968 return this; 6969 } 6970 6971 /** 6972 * @hide 6973 */ 6974 public CharSequence getBigText() { 6975 return mBigText; 6976 } 6977 6978 /** 6979 * @hide 6980 */ 6981 public void addExtras(Bundle extras) { 6982 super.addExtras(extras); 6983 6984 extras.putCharSequence(EXTRA_BIG_TEXT, mBigText); 6985 } 6986 6987 /** 6988 * @hide 6989 */ 6990 @Override 6991 protected void restoreFromExtras(Bundle extras) { 6992 super.restoreFromExtras(extras); 6993 6994 mBigText = extras.getCharSequence(EXTRA_BIG_TEXT); 6995 } 6996 6997 /** 6998 * @param increasedHeight true if this layout be created with an increased height. 6999 * 7000 * @hide 7001 */ 7002 @Override 7003 public RemoteViews makeContentView(boolean increasedHeight) { 7004 if (increasedHeight) { 7005 mBuilder.mOriginalActions = mBuilder.mActions; 7006 mBuilder.mActions = new ArrayList<>(); 7007 RemoteViews remoteViews = makeBigContentView(); 7008 mBuilder.mActions = mBuilder.mOriginalActions; 7009 mBuilder.mOriginalActions = null; 7010 return remoteViews; 7011 } 7012 return super.makeContentView(increasedHeight); 7013 } 7014 7015 /** 7016 * @hide 7017 */ 7018 @Override 7019 public RemoteViews makeHeadsUpContentView(boolean increasedHeight) { 7020 if (increasedHeight && mBuilder.mActions.size() > 0) { 7021 return makeBigContentView(); 7022 } 7023 return super.makeHeadsUpContentView(increasedHeight); 7024 } 7025 7026 /** 7027 * @hide 7028 */ 7029 public RemoteViews makeBigContentView() { 7030 StandardTemplateParams p = mBuilder.mParams.reset().fillTextsFrom(mBuilder).text(null); 7031 TemplateBindResult result = new TemplateBindResult(); 7032 RemoteViews contentView = getStandardView(mBuilder.getBigTextLayoutResource(), p, 7033 result); 7034 contentView.setInt(R.id.big_text, "setImageEndMargin", result.getIconMarginEnd()); 7035 7036 CharSequence bigTextText = mBuilder.processLegacyText(mBigText); 7037 if (TextUtils.isEmpty(bigTextText)) { 7038 // In case the bigtext is null / empty fall back to the normal text to avoid a weird 7039 // experience 7040 bigTextText = mBuilder.processLegacyText( 7041 mBuilder.getAllExtras().getCharSequence(EXTRA_TEXT)); 7042 } 7043 contentView.setTextViewText(R.id.big_text, mBuilder.processTextSpans(bigTextText)); 7044 mBuilder.setTextViewColorSecondary(contentView, R.id.big_text, p); 7045 contentView.setViewVisibility(R.id.big_text, 7046 TextUtils.isEmpty(bigTextText) ? View.GONE : View.VISIBLE); 7047 contentView.setBoolean(R.id.big_text, "setHasImage", 7048 result.isRightIconContainerVisible()); 7049 7050 return contentView; 7051 } 7052 7053 /** 7054 * @hide 7055 * Spans are ignored when comparing text for visual difference. 7056 */ 7057 @Override 7058 public boolean areNotificationsVisiblyDifferent(Style other) { 7059 if (other == null || getClass() != other.getClass()) { 7060 return true; 7061 } 7062 BigTextStyle newS = (BigTextStyle) other; 7063 return !Objects.equals(String.valueOf(getBigText()), String.valueOf(newS.getBigText())); 7064 } 7065 7066 } 7067 7068 /** 7069 * Helper class for generating large-format notifications that include multiple back-and-forth 7070 * messages of varying types between any number of people. 7071 * 7072 * <p> 7073 * If the platform does not provide large-format notifications, this method has no effect. The 7074 * user will always see the normal notification view. 7075 * 7076 * <p> 7077 * If the app is targeting Android P and above, it is required to use the {@link Person} 7078 * class in order to get an optimal rendering of the notification and its avatars. For 7079 * conversations involving multiple people, the app should also make sure that it marks the 7080 * conversation as a group with {@link #setGroupConversation(boolean)}. 7081 * 7082 * <p> 7083 * This class is a "rebuilder": It attaches to a Builder object and modifies its behavior. 7084 * Here's an example of how this may be used: 7085 * <pre class="prettyprint"> 7086 * 7087 * Person user = new Person.Builder().setIcon(userIcon).setName(userName).build(); 7088 * MessagingStyle style = new MessagingStyle(user) 7089 * .addMessage(messages[1].getText(), messages[1].getTime(), messages[1].getPerson()) 7090 * .addMessage(messages[2].getText(), messages[2].getTime(), messages[2].getPerson()) 7091 * .setGroupConversation(hasMultiplePeople()); 7092 * 7093 * Notification noti = new Notification.Builder() 7094 * .setContentTitle("2 new messages with " + sender.toString()) 7095 * .setContentText(subject) 7096 * .setSmallIcon(R.drawable.new_message) 7097 * .setLargeIcon(aBitmap) 7098 * .setStyle(style) 7099 * .build(); 7100 * </pre> 7101 */ 7102 public static class MessagingStyle extends Style { 7103 7104 /** 7105 * The maximum number of messages that will be retained in the Notification itself (the 7106 * number displayed is up to the platform). 7107 */ 7108 public static final int MAXIMUM_RETAINED_MESSAGES = 25; 7109 7110 7111 /** @hide */ 7112 public static final int CONVERSATION_TYPE_LEGACY = 0; 7113 /** @hide */ 7114 public static final int CONVERSATION_TYPE_NORMAL = 1; 7115 /** @hide */ 7116 public static final int CONVERSATION_TYPE_IMPORTANT = 2; 7117 7118 /** @hide */ 7119 @IntDef(prefix = {"CONVERSATION_TYPE_"}, value = { 7120 CONVERSATION_TYPE_LEGACY, CONVERSATION_TYPE_NORMAL, CONVERSATION_TYPE_IMPORTANT 7121 }) 7122 @Retention(RetentionPolicy.SOURCE) 7123 public @interface ConversationType {} 7124 7125 @NonNull Person mUser; 7126 @Nullable CharSequence mConversationTitle; 7127 @Nullable Icon mShortcutIcon; 7128 List<Message> mMessages = new ArrayList<>(); 7129 List<Message> mHistoricMessages = new ArrayList<>(); 7130 boolean mIsGroupConversation; 7131 @ConversationType int mConversationType = CONVERSATION_TYPE_LEGACY; 7132 int mUnreadMessageCount; 7133 7134 MessagingStyle() { 7135 } 7136 7137 /** 7138 * @param userDisplayName Required - the name to be displayed for any replies sent by the 7139 * user before the posting app reposts the notification with those messages after they've 7140 * been actually sent and in previous messages sent by the user added in 7141 * {@link #addMessage(Notification.MessagingStyle.Message)} 7142 * 7143 * @deprecated use {@code MessagingStyle(Person)} 7144 */ 7145 public MessagingStyle(@NonNull CharSequence userDisplayName) { 7146 this(new Person.Builder().setName(userDisplayName).build()); 7147 } 7148 7149 /** 7150 * @param user Required - The person displayed for any messages that are sent by the 7151 * user. Any messages added with {@link #addMessage(Notification.MessagingStyle.Message)} 7152 * who don't have a Person associated with it will be displayed as if they were sent 7153 * by this user. The user also needs to have a valid name associated with it, which will 7154 * be enforced starting in Android P. 7155 */ 7156 public MessagingStyle(@NonNull Person user) { 7157 mUser = user; 7158 } 7159 7160 /** 7161 * Validate that this style was properly composed. This is called at build time. 7162 * @hide 7163 */ 7164 @Override 7165 public void validate(Context context) { 7166 super.validate(context); 7167 if (context.getApplicationInfo().targetSdkVersion 7168 >= Build.VERSION_CODES.P && (mUser == null || mUser.getName() == null)) { 7169 throw new RuntimeException("User must be valid and have a name."); 7170 } 7171 } 7172 7173 /** 7174 * @return the text that should be displayed in the statusBar when heads upped. 7175 * If {@code null} is returned, the default implementation will be used. 7176 * 7177 * @hide 7178 */ 7179 @Override 7180 public CharSequence getHeadsUpStatusBarText() { 7181 CharSequence conversationTitle = !TextUtils.isEmpty(super.mBigContentTitle) 7182 ? super.mBigContentTitle 7183 : mConversationTitle; 7184 if (mConversationType == CONVERSATION_TYPE_LEGACY 7185 && !TextUtils.isEmpty(conversationTitle) && !hasOnlyWhiteSpaceSenders()) { 7186 return conversationTitle; 7187 } 7188 return null; 7189 } 7190 7191 /** 7192 * @return the user to be displayed for any replies sent by the user 7193 */ 7194 @NonNull 7195 public Person getUser() { 7196 return mUser; 7197 } 7198 7199 /** 7200 * Returns the name to be displayed for any replies sent by the user 7201 * 7202 * @deprecated use {@link #getUser()} instead 7203 */ 7204 public CharSequence getUserDisplayName() { 7205 return mUser.getName(); 7206 } 7207 7208 /** 7209 * Sets the title to be displayed on this conversation. May be set to {@code null}. 7210 * 7211 * <p>Starting in {@link Build.VERSION_CODES#R, this conversation title will be ignored if a 7212 * valid shortcutId is added via {@link Notification.Builder#setShortcutId(String)}. In this 7213 * case, {@link ShortcutInfo#getLongLabel()} (or, if missing, 7214 * {@link ShortcutInfo#getShortLabel()}) will be shown as the conversation title 7215 * instead. 7216 * 7217 * <p>This API's behavior was changed in SDK version {@link Build.VERSION_CODES#P}. If your 7218 * application's target version is less than {@link Build.VERSION_CODES#P}, setting a 7219 * conversation title to a non-null value will make {@link #isGroupConversation()} return 7220 * {@code true} and passing {@code null} will make it return {@code false}. In 7221 * {@link Build.VERSION_CODES#P} and beyond, use {@link #setGroupConversation(boolean)} 7222 * to set group conversation status. 7223 * 7224 * @param conversationTitle Title displayed for this conversation 7225 * @return this object for method chaining 7226 */ 7227 public MessagingStyle setConversationTitle(@Nullable CharSequence conversationTitle) { 7228 mConversationTitle = conversationTitle; 7229 return this; 7230 } 7231 7232 /** 7233 * Return the title to be displayed on this conversation. May return {@code null}. 7234 */ 7235 @Nullable 7236 public CharSequence getConversationTitle() { 7237 return mConversationTitle; 7238 } 7239 7240 /** 7241 * Sets the icon to be displayed on the conversation, derived from the shortcutId. 7242 * 7243 * @hide 7244 */ 7245 public MessagingStyle setShortcutIcon(@Nullable Icon conversationIcon) { 7246 mShortcutIcon = conversationIcon; 7247 return this; 7248 } 7249 7250 /** 7251 * Return the icon to be displayed on this conversation, derived from the shortcutId. May 7252 * return {@code null}. 7253 * 7254 * @hide 7255 */ 7256 @Nullable 7257 public Icon getShortcutIcon() { 7258 return mShortcutIcon; 7259 } 7260 7261 /** 7262 * Sets the conversation type of this MessageStyle notification. 7263 * {@link #CONVERSATION_TYPE_LEGACY} will use the "older" layout from pre-R, 7264 * {@link #CONVERSATION_TYPE_NORMAL} will use the new "conversation" layout, and 7265 * {@link #CONVERSATION_TYPE_IMPORTANT} will add additional "important" treatments. 7266 * 7267 * @hide 7268 */ 7269 public MessagingStyle setConversationType(@ConversationType int conversationType) { 7270 mConversationType = conversationType; 7271 return this; 7272 } 7273 7274 /** @hide */ 7275 @ConversationType 7276 public int getConversationType() { 7277 return mConversationType; 7278 } 7279 7280 /** @hide */ 7281 public int getUnreadMessageCount() { 7282 return mUnreadMessageCount; 7283 } 7284 7285 /** @hide */ 7286 public MessagingStyle setUnreadMessageCount(int unreadMessageCount) { 7287 mUnreadMessageCount = unreadMessageCount; 7288 return this; 7289 } 7290 7291 /** 7292 * Adds a message for display by this notification. Convenience call for a simple 7293 * {@link Message} in {@link #addMessage(Notification.MessagingStyle.Message)}. 7294 * @param text A {@link CharSequence} to be displayed as the message content 7295 * @param timestamp Time at which the message arrived 7296 * @param sender A {@link CharSequence} to be used for displaying the name of the 7297 * sender. Should be <code>null</code> for messages by the current user, in which case 7298 * the platform will insert {@link #getUserDisplayName()}. 7299 * Should be unique amongst all individuals in the conversation, and should be 7300 * consistent during re-posts of the notification. 7301 * 7302 * @see Message#Message(CharSequence, long, CharSequence) 7303 * 7304 * @return this object for method chaining 7305 * 7306 * @deprecated use {@link #addMessage(CharSequence, long, Person)} 7307 */ 7308 public MessagingStyle addMessage(CharSequence text, long timestamp, CharSequence sender) { 7309 return addMessage(text, timestamp, 7310 sender == null ? null : new Person.Builder().setName(sender).build()); 7311 } 7312 7313 /** 7314 * Adds a message for display by this notification. Convenience call for a simple 7315 * {@link Message} in {@link #addMessage(Notification.MessagingStyle.Message)}. 7316 * @param text A {@link CharSequence} to be displayed as the message content 7317 * @param timestamp Time at which the message arrived 7318 * @param sender The {@link Person} who sent the message. 7319 * Should be <code>null</code> for messages by the current user, in which case 7320 * the platform will insert the user set in {@code MessagingStyle(Person)}. 7321 * 7322 * @see Message#Message(CharSequence, long, CharSequence) 7323 * 7324 * @return this object for method chaining 7325 */ 7326 public MessagingStyle addMessage(@NonNull CharSequence text, long timestamp, 7327 @Nullable Person sender) { 7328 return addMessage(new Message(text, timestamp, sender)); 7329 } 7330 7331 /** 7332 * Adds a {@link Message} for display in this notification. 7333 * 7334 * <p>The messages should be added in chronologic order, i.e. the oldest first, 7335 * the newest last. 7336 * 7337 * @param message The {@link Message} to be displayed 7338 * @return this object for method chaining 7339 */ 7340 public MessagingStyle addMessage(Message message) { 7341 mMessages.add(message); 7342 if (mMessages.size() > MAXIMUM_RETAINED_MESSAGES) { 7343 mMessages.remove(0); 7344 } 7345 return this; 7346 } 7347 7348 /** 7349 * Adds a {@link Message} for historic context in this notification. 7350 * 7351 * <p>Messages should be added as historic if they are not the main subject of the 7352 * notification but may give context to a conversation. The system may choose to present 7353 * them only when relevant, e.g. when replying to a message through a {@link RemoteInput}. 7354 * 7355 * <p>The messages should be added in chronologic order, i.e. the oldest first, 7356 * the newest last. 7357 * 7358 * @param message The historic {@link Message} to be added 7359 * @return this object for method chaining 7360 */ 7361 public MessagingStyle addHistoricMessage(Message message) { 7362 mHistoricMessages.add(message); 7363 if (mHistoricMessages.size() > MAXIMUM_RETAINED_MESSAGES) { 7364 mHistoricMessages.remove(0); 7365 } 7366 return this; 7367 } 7368 7369 /** 7370 * Gets the list of {@code Message} objects that represent the notification 7371 */ getMessages()7372 public List<Message> getMessages() { 7373 return mMessages; 7374 } 7375 7376 /** 7377 * Gets the list of historic {@code Message}s in the notification. 7378 */ getHistoricMessages()7379 public List<Message> getHistoricMessages() { 7380 return mHistoricMessages; 7381 } 7382 7383 /** 7384 * Sets whether this conversation notification represents a group. If the app is targeting 7385 * Android P, this is required if the app wants to display the largeIcon set with 7386 * {@link Notification.Builder#setLargeIcon(Bitmap)}, otherwise it will be hidden. 7387 * 7388 * @param isGroupConversation {@code true} if the conversation represents a group, 7389 * {@code false} otherwise. 7390 * @return this object for method chaining 7391 */ setGroupConversation(boolean isGroupConversation)7392 public MessagingStyle setGroupConversation(boolean isGroupConversation) { 7393 mIsGroupConversation = isGroupConversation; 7394 return this; 7395 } 7396 7397 /** 7398 * Returns {@code true} if this notification represents a group conversation, otherwise 7399 * {@code false}. 7400 * 7401 * <p> If the application that generated this {@link MessagingStyle} targets an SDK version 7402 * less than {@link Build.VERSION_CODES#P}, this method becomes dependent on whether or 7403 * not the conversation title is set; returning {@code true} if the conversation title is 7404 * a non-null value, or {@code false} otherwise. From {@link Build.VERSION_CODES#P} forward, 7405 * this method returns what's set by {@link #setGroupConversation(boolean)} allowing for 7406 * named, non-group conversations. 7407 * 7408 * @see #setConversationTitle(CharSequence) 7409 */ isGroupConversation()7410 public boolean isGroupConversation() { 7411 // When target SDK version is < P, a non-null conversation title dictates if this is 7412 // as group conversation. 7413 if (mBuilder != null 7414 && mBuilder.mContext.getApplicationInfo().targetSdkVersion 7415 < Build.VERSION_CODES.P) { 7416 return mConversationTitle != null; 7417 } 7418 7419 return mIsGroupConversation; 7420 } 7421 7422 /** 7423 * @hide 7424 */ 7425 @Override addExtras(Bundle extras)7426 public void addExtras(Bundle extras) { 7427 super.addExtras(extras); 7428 if (mUser != null) { 7429 // For legacy usages 7430 extras.putCharSequence(EXTRA_SELF_DISPLAY_NAME, mUser.getName()); 7431 extras.putParcelable(EXTRA_MESSAGING_PERSON, mUser); 7432 } 7433 if (mConversationTitle != null) { 7434 extras.putCharSequence(EXTRA_CONVERSATION_TITLE, mConversationTitle); 7435 } 7436 if (!mMessages.isEmpty()) { extras.putParcelableArray(EXTRA_MESSAGES, 7437 Message.getBundleArrayForMessages(mMessages)); 7438 } 7439 if (!mHistoricMessages.isEmpty()) { extras.putParcelableArray(EXTRA_HISTORIC_MESSAGES, 7440 Message.getBundleArrayForMessages(mHistoricMessages)); 7441 } 7442 if (mShortcutIcon != null) { 7443 extras.putParcelable(EXTRA_CONVERSATION_ICON, mShortcutIcon); 7444 } 7445 extras.putInt(EXTRA_CONVERSATION_UNREAD_MESSAGE_COUNT, mUnreadMessageCount); 7446 7447 fixTitleAndTextExtras(extras); 7448 extras.putBoolean(EXTRA_IS_GROUP_CONVERSATION, mIsGroupConversation); 7449 } 7450 fixTitleAndTextExtras(Bundle extras)7451 private void fixTitleAndTextExtras(Bundle extras) { 7452 Message m = findLatestIncomingMessage(); 7453 CharSequence text = (m == null) ? null : m.mText; 7454 CharSequence sender = m == null ? null 7455 : m.mSender == null || TextUtils.isEmpty(m.mSender.getName()) 7456 ? mUser.getName() : m.mSender.getName(); 7457 CharSequence title; 7458 if (!TextUtils.isEmpty(mConversationTitle)) { 7459 if (!TextUtils.isEmpty(sender)) { 7460 BidiFormatter bidi = BidiFormatter.getInstance(); 7461 title = mBuilder.mContext.getString( 7462 com.android.internal.R.string.notification_messaging_title_template, 7463 bidi.unicodeWrap(mConversationTitle), bidi.unicodeWrap(sender)); 7464 } else { 7465 title = mConversationTitle; 7466 } 7467 } else { 7468 title = sender; 7469 } 7470 7471 if (title != null) { 7472 extras.putCharSequence(EXTRA_TITLE, title); 7473 } 7474 if (text != null) { 7475 extras.putCharSequence(EXTRA_TEXT, text); 7476 } 7477 } 7478 7479 /** 7480 * @hide 7481 */ 7482 @Override restoreFromExtras(Bundle extras)7483 protected void restoreFromExtras(Bundle extras) { 7484 super.restoreFromExtras(extras); 7485 7486 mUser = extras.getParcelable(EXTRA_MESSAGING_PERSON); 7487 if (mUser == null) { 7488 CharSequence displayName = extras.getCharSequence(EXTRA_SELF_DISPLAY_NAME); 7489 mUser = new Person.Builder().setName(displayName).build(); 7490 } 7491 mConversationTitle = extras.getCharSequence(EXTRA_CONVERSATION_TITLE); 7492 Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES); 7493 mMessages = Message.getMessagesFromBundleArray(messages); 7494 Parcelable[] histMessages = extras.getParcelableArray(EXTRA_HISTORIC_MESSAGES); 7495 mHistoricMessages = Message.getMessagesFromBundleArray(histMessages); 7496 mIsGroupConversation = extras.getBoolean(EXTRA_IS_GROUP_CONVERSATION); 7497 mUnreadMessageCount = extras.getInt(EXTRA_CONVERSATION_UNREAD_MESSAGE_COUNT); 7498 mShortcutIcon = extras.getParcelable(EXTRA_CONVERSATION_ICON); 7499 } 7500 7501 /** 7502 * @hide 7503 */ 7504 @Override makeContentView(boolean increasedHeight)7505 public RemoteViews makeContentView(boolean increasedHeight) { 7506 mBuilder.mOriginalActions = mBuilder.mActions; 7507 mBuilder.mActions = new ArrayList<>(); 7508 RemoteViews remoteViews = makeMessagingView(true /* isCollapsed */, 7509 false /* hideLargeIcon */); 7510 mBuilder.mActions = mBuilder.mOriginalActions; 7511 mBuilder.mOriginalActions = null; 7512 return remoteViews; 7513 } 7514 7515 /** 7516 * @hide 7517 * Spans are ignored when comparing text for visual difference. 7518 */ 7519 @Override areNotificationsVisiblyDifferent(Style other)7520 public boolean areNotificationsVisiblyDifferent(Style other) { 7521 if (other == null || getClass() != other.getClass()) { 7522 return true; 7523 } 7524 MessagingStyle newS = (MessagingStyle) other; 7525 List<MessagingStyle.Message> oldMs = getMessages(); 7526 List<MessagingStyle.Message> newMs = newS.getMessages(); 7527 7528 if (oldMs == null || newMs == null) { 7529 newMs = new ArrayList<>(); 7530 } 7531 7532 int n = oldMs.size(); 7533 if (n != newMs.size()) { 7534 return true; 7535 } 7536 for (int i = 0; i < n; i++) { 7537 MessagingStyle.Message oldM = oldMs.get(i); 7538 MessagingStyle.Message newM = newMs.get(i); 7539 if (!Objects.equals( 7540 String.valueOf(oldM.getText()), 7541 String.valueOf(newM.getText()))) { 7542 return true; 7543 } 7544 if (!Objects.equals(oldM.getDataUri(), newM.getDataUri())) { 7545 return true; 7546 } 7547 String oldSender = String.valueOf(oldM.getSenderPerson() == null 7548 ? oldM.getSender() 7549 : oldM.getSenderPerson().getName()); 7550 String newSender = String.valueOf(newM.getSenderPerson() == null 7551 ? newM.getSender() 7552 : newM.getSenderPerson().getName()); 7553 if (!Objects.equals(oldSender, newSender)) { 7554 return true; 7555 } 7556 7557 String oldKey = oldM.getSenderPerson() == null 7558 ? null : oldM.getSenderPerson().getKey(); 7559 String newKey = newM.getSenderPerson() == null 7560 ? null : newM.getSenderPerson().getKey(); 7561 if (!Objects.equals(oldKey, newKey)) { 7562 return true; 7563 } 7564 // Other fields (like timestamp) intentionally excluded 7565 } 7566 return false; 7567 } 7568 findLatestIncomingMessage()7569 private Message findLatestIncomingMessage() { 7570 return findLatestIncomingMessage(mMessages); 7571 } 7572 7573 /** 7574 * @hide 7575 */ 7576 @Nullable findLatestIncomingMessage( List<Message> messages)7577 public static Message findLatestIncomingMessage( 7578 List<Message> messages) { 7579 for (int i = messages.size() - 1; i >= 0; i--) { 7580 Message m = messages.get(i); 7581 // Incoming messages have a non-empty sender. 7582 if (m.mSender != null && !TextUtils.isEmpty(m.mSender.getName())) { 7583 return m; 7584 } 7585 } 7586 if (!messages.isEmpty()) { 7587 // No incoming messages, fall back to outgoing message 7588 return messages.get(messages.size() - 1); 7589 } 7590 return null; 7591 } 7592 7593 /** 7594 * @hide 7595 */ 7596 @Override makeBigContentView()7597 public RemoteViews makeBigContentView() { 7598 return makeMessagingView(false /* isCollapsed */, true /* hideLargeIcon */); 7599 } 7600 7601 /** 7602 * Create a messaging layout. 7603 * 7604 * @param isCollapsed Should this use the collapsed layout 7605 * @param hideRightIcons Should the reply affordance be shown at the end of the notification 7606 * @return the created remoteView. 7607 */ 7608 @NonNull makeMessagingView(boolean isCollapsed, boolean hideRightIcons)7609 private RemoteViews makeMessagingView(boolean isCollapsed, boolean hideRightIcons) { 7610 CharSequence conversationTitle = !TextUtils.isEmpty(super.mBigContentTitle) 7611 ? super.mBigContentTitle 7612 : mConversationTitle; 7613 boolean atLeastP = mBuilder.mContext.getApplicationInfo().targetSdkVersion 7614 >= Build.VERSION_CODES.P; 7615 boolean isOneToOne; 7616 CharSequence nameReplacement = null; 7617 if (!atLeastP) { 7618 isOneToOne = TextUtils.isEmpty(conversationTitle); 7619 if (hasOnlyWhiteSpaceSenders()) { 7620 isOneToOne = true; 7621 nameReplacement = conversationTitle; 7622 conversationTitle = null; 7623 } 7624 } else { 7625 isOneToOne = !isGroupConversation(); 7626 } 7627 boolean isConversationLayout = mConversationType != CONVERSATION_TYPE_LEGACY; 7628 boolean isImportantConversation = mConversationType == CONVERSATION_TYPE_IMPORTANT; 7629 Icon largeIcon = mBuilder.mN.mLargeIcon; 7630 TemplateBindResult bindResult = new TemplateBindResult(); 7631 StandardTemplateParams p = mBuilder.mParams.reset() 7632 .hasProgress(false) 7633 .title(conversationTitle) 7634 .text(null) 7635 .hideLargeIcon(hideRightIcons || isOneToOne) 7636 .hideReplyIcon(hideRightIcons) 7637 .headerTextSecondary(conversationTitle); 7638 RemoteViews contentView = mBuilder.applyStandardTemplateWithActions( 7639 isConversationLayout 7640 ? mBuilder.getConversationLayoutResource() 7641 : mBuilder.getMessagingLayoutResource(), 7642 p, 7643 bindResult); 7644 7645 addExtras(mBuilder.mN.extras); 7646 if (!isConversationLayout) { 7647 // also update the end margin if there is an image 7648 contentView.setViewLayoutMarginEnd(R.id.notification_messaging, 7649 bindResult.getIconMarginEnd()); 7650 } 7651 contentView.setInt(R.id.status_bar_latest_event_content, "setLayoutColor", 7652 mBuilder.isColorized(p) 7653 ? mBuilder.getPrimaryTextColor(p) 7654 : mBuilder.resolveContrastColor(p)); 7655 contentView.setInt(R.id.status_bar_latest_event_content, "setSenderTextColor", 7656 mBuilder.getPrimaryTextColor(p)); 7657 contentView.setInt(R.id.status_bar_latest_event_content, "setMessageTextColor", 7658 mBuilder.getSecondaryTextColor(p)); 7659 contentView.setInt(R.id.status_bar_latest_event_content, 7660 "setNotificationBackgroundColor", 7661 mBuilder.resolveBackgroundColor(p)); 7662 contentView.setBoolean(R.id.status_bar_latest_event_content, "setIsCollapsed", 7663 isCollapsed); 7664 contentView.setIcon(R.id.status_bar_latest_event_content, "setAvatarReplacement", 7665 mBuilder.mN.mLargeIcon); 7666 contentView.setCharSequence(R.id.status_bar_latest_event_content, "setNameReplacement", 7667 nameReplacement); 7668 contentView.setBoolean(R.id.status_bar_latest_event_content, "setIsOneToOne", 7669 isOneToOne); 7670 contentView.setCharSequence(R.id.status_bar_latest_event_content, 7671 "setConversationTitle", conversationTitle); 7672 if (isConversationLayout) { 7673 contentView.setIcon(R.id.status_bar_latest_event_content, 7674 "setShortcutIcon", mShortcutIcon); 7675 contentView.setBoolean(R.id.status_bar_latest_event_content, 7676 "setIsImportantConversation", isImportantConversation); 7677 } 7678 contentView.setIcon(R.id.status_bar_latest_event_content, "setLargeIcon", 7679 largeIcon); 7680 contentView.setBundle(R.id.status_bar_latest_event_content, "setData", 7681 mBuilder.mN.extras); 7682 return contentView; 7683 } 7684 hasOnlyWhiteSpaceSenders()7685 private boolean hasOnlyWhiteSpaceSenders() { 7686 for (int i = 0; i < mMessages.size(); i++) { 7687 Message m = mMessages.get(i); 7688 Person sender = m.getSenderPerson(); 7689 if (sender != null && !isWhiteSpace(sender.getName())) { 7690 return false; 7691 } 7692 } 7693 return true; 7694 } 7695 isWhiteSpace(CharSequence sender)7696 private boolean isWhiteSpace(CharSequence sender) { 7697 if (TextUtils.isEmpty(sender)) { 7698 return true; 7699 } 7700 if (sender.toString().matches("^\\s*$")) { 7701 return true; 7702 } 7703 // Let's check if we only have 0 whitespace chars. Some apps did this as a workaround 7704 // For the presentation that we had. 7705 for (int i = 0; i < sender.length(); i++) { 7706 char c = sender.charAt(i); 7707 if (c != '\u200B') { 7708 return false; 7709 } 7710 } 7711 return true; 7712 } 7713 createConversationTitleFromMessages()7714 private CharSequence createConversationTitleFromMessages() { 7715 ArraySet<CharSequence> names = new ArraySet<>(); 7716 for (int i = 0; i < mMessages.size(); i++) { 7717 Message m = mMessages.get(i); 7718 Person sender = m.getSenderPerson(); 7719 if (sender != null) { 7720 names.add(sender.getName()); 7721 } 7722 } 7723 SpannableStringBuilder title = new SpannableStringBuilder(); 7724 int size = names.size(); 7725 for (int i = 0; i < size; i++) { 7726 CharSequence name = names.valueAt(i); 7727 if (!TextUtils.isEmpty(title)) { 7728 title.append(", "); 7729 } 7730 title.append(BidiFormatter.getInstance().unicodeWrap(name)); 7731 } 7732 return title; 7733 } 7734 7735 /** 7736 * @hide 7737 */ 7738 @Override makeHeadsUpContentView(boolean increasedHeight)7739 public RemoteViews makeHeadsUpContentView(boolean increasedHeight) { 7740 RemoteViews remoteViews = makeMessagingView(true /* isCollapsed */, 7741 true /* hideLargeIcon */); 7742 if (mConversationType == CONVERSATION_TYPE_LEGACY) { 7743 remoteViews.setInt(R.id.notification_messaging, "setMaxDisplayedLines", 1); 7744 } 7745 return remoteViews; 7746 } 7747 makeFontColorSpan(int color)7748 private static TextAppearanceSpan makeFontColorSpan(int color) { 7749 return new TextAppearanceSpan(null, 0, 0, 7750 ColorStateList.valueOf(color), null); 7751 } 7752 7753 public static final class Message { 7754 /** @hide */ 7755 public static final String KEY_TEXT = "text"; 7756 static final String KEY_TIMESTAMP = "time"; 7757 static final String KEY_SENDER = "sender"; 7758 static final String KEY_SENDER_PERSON = "sender_person"; 7759 static final String KEY_DATA_MIME_TYPE = "type"; 7760 static final String KEY_DATA_URI= "uri"; 7761 static final String KEY_EXTRAS_BUNDLE = "extras"; 7762 static final String KEY_REMOTE_INPUT_HISTORY = "remote_input_history"; 7763 7764 private final CharSequence mText; 7765 private final long mTimestamp; 7766 @Nullable 7767 private final Person mSender; 7768 /** True if this message was generated from the extra 7769 * {@link Notification#EXTRA_REMOTE_INPUT_HISTORY_ITEMS} 7770 */ 7771 private final boolean mRemoteInputHistory; 7772 7773 private Bundle mExtras = new Bundle(); 7774 private String mDataMimeType; 7775 private Uri mDataUri; 7776 7777 /** 7778 * Constructor 7779 * @param text A {@link CharSequence} to be displayed as the message content 7780 * @param timestamp Time at which the message arrived 7781 * @param sender A {@link CharSequence} to be used for displaying the name of the 7782 * sender. Should be <code>null</code> for messages by the current user, in which case 7783 * the platform will insert {@link MessagingStyle#getUserDisplayName()}. 7784 * Should be unique amongst all individuals in the conversation, and should be 7785 * consistent during re-posts of the notification. 7786 * 7787 * @deprecated use {@code Message(CharSequence, long, Person)} 7788 */ Message(CharSequence text, long timestamp, CharSequence sender)7789 public Message(CharSequence text, long timestamp, CharSequence sender){ 7790 this(text, timestamp, sender == null ? null 7791 : new Person.Builder().setName(sender).build()); 7792 } 7793 7794 /** 7795 * Constructor 7796 * @param text A {@link CharSequence} to be displayed as the message content 7797 * @param timestamp Time at which the message arrived 7798 * @param sender The {@link Person} who sent the message. 7799 * Should be <code>null</code> for messages by the current user, in which case 7800 * the platform will insert the user set in {@code MessagingStyle(Person)}. 7801 * <p> 7802 * The person provided should contain an Icon, set with 7803 * {@link Person.Builder#setIcon(Icon)} and also have a name provided 7804 * with {@link Person.Builder#setName(CharSequence)}. If multiple users have the same 7805 * name, consider providing a key with {@link Person.Builder#setKey(String)} in order 7806 * to differentiate between the different users. 7807 * </p> 7808 */ Message(@onNull CharSequence text, long timestamp, @Nullable Person sender)7809 public Message(@NonNull CharSequence text, long timestamp, @Nullable Person sender) { 7810 this(text, timestamp, sender, false /* remoteHistory */); 7811 } 7812 7813 /** 7814 * Constructor 7815 * @param text A {@link CharSequence} to be displayed as the message content 7816 * @param timestamp Time at which the message arrived 7817 * @param sender The {@link Person} who sent the message. 7818 * Should be <code>null</code> for messages by the current user, in which case 7819 * the platform will insert the user set in {@code MessagingStyle(Person)}. 7820 * @param remoteInputHistory True if the messages was generated from the extra 7821 * {@link Notification#EXTRA_REMOTE_INPUT_HISTORY_ITEMS}. 7822 * <p> 7823 * The person provided should contain an Icon, set with 7824 * {@link Person.Builder#setIcon(Icon)} and also have a name provided 7825 * with {@link Person.Builder#setName(CharSequence)}. If multiple users have the same 7826 * name, consider providing a key with {@link Person.Builder#setKey(String)} in order 7827 * to differentiate between the different users. 7828 * </p> 7829 * @hide 7830 */ Message(@onNull CharSequence text, long timestamp, @Nullable Person sender, boolean remoteInputHistory)7831 public Message(@NonNull CharSequence text, long timestamp, @Nullable Person sender, 7832 boolean remoteInputHistory) { 7833 mText = text; 7834 mTimestamp = timestamp; 7835 mSender = sender; 7836 mRemoteInputHistory = remoteInputHistory; 7837 } 7838 7839 /** 7840 * Sets a binary blob of data and an associated MIME type for a message. In the case 7841 * where the platform doesn't support the MIME type, the original text provided in the 7842 * constructor will be used. 7843 * @param dataMimeType The MIME type of the content. See 7844 * <a href="{@docRoot}notifications/messaging.html"> for the list of supported MIME 7845 * types on Android and Android Wear. 7846 * @param dataUri The uri containing the content whose type is given by the MIME type. 7847 * <p class="note"> 7848 * <ol> 7849 * <li>Notification Listeners including the System UI need permission to access the 7850 * data the Uri points to. The recommended ways to do this are:</li> 7851 * <li>Store the data in your own ContentProvider, making sure that other apps have 7852 * the correct permission to access your provider. The preferred mechanism for 7853 * providing access is to use per-URI permissions which are temporary and only 7854 * grant access to the receiving application. An easy way to create a 7855 * ContentProvider like this is to use the FileProvider helper class.</li> 7856 * <li>Use the system MediaStore. The MediaStore is primarily aimed at video, audio 7857 * and image MIME types, however beginning with Android 3.0 (API level 11) it can 7858 * also store non-media types (see MediaStore.Files for more info). Files can be 7859 * inserted into the MediaStore using scanFile() after which a content:// style 7860 * Uri suitable for sharing is passed to the provided onScanCompleted() callback. 7861 * Note that once added to the system MediaStore the content is accessible to any 7862 * app on the device.</li> 7863 * </ol> 7864 * @return this object for method chaining 7865 */ setData(String dataMimeType, Uri dataUri)7866 public Message setData(String dataMimeType, Uri dataUri) { 7867 mDataMimeType = dataMimeType; 7868 mDataUri = dataUri; 7869 return this; 7870 } 7871 7872 /** 7873 * Get the text to be used for this message, or the fallback text if a type and content 7874 * Uri have been set 7875 */ getText()7876 public CharSequence getText() { 7877 return mText; 7878 } 7879 7880 /** 7881 * Get the time at which this message arrived 7882 */ getTimestamp()7883 public long getTimestamp() { 7884 return mTimestamp; 7885 } 7886 7887 /** 7888 * Get the extras Bundle for this message. 7889 */ getExtras()7890 public Bundle getExtras() { 7891 return mExtras; 7892 } 7893 7894 /** 7895 * Get the text used to display the contact's name in the messaging experience 7896 * 7897 * @deprecated use {@link #getSenderPerson()} 7898 */ getSender()7899 public CharSequence getSender() { 7900 return mSender == null ? null : mSender.getName(); 7901 } 7902 7903 /** 7904 * Get the sender associated with this message. 7905 */ 7906 @Nullable getSenderPerson()7907 public Person getSenderPerson() { 7908 return mSender; 7909 } 7910 7911 /** 7912 * Get the MIME type of the data pointed to by the Uri 7913 */ getDataMimeType()7914 public String getDataMimeType() { 7915 return mDataMimeType; 7916 } 7917 7918 /** 7919 * Get the Uri pointing to the content of the message. Can be null, in which case 7920 * {@see #getText()} is used. 7921 */ getDataUri()7922 public Uri getDataUri() { 7923 return mDataUri; 7924 } 7925 7926 /** 7927 * @return True if the message was generated from 7928 * {@link Notification#EXTRA_REMOTE_INPUT_HISTORY_ITEMS}. 7929 * @hide 7930 */ isRemoteInputHistory()7931 public boolean isRemoteInputHistory() { 7932 return mRemoteInputHistory; 7933 } 7934 7935 /** 7936 * @hide 7937 */ 7938 @VisibleForTesting toBundle()7939 public Bundle toBundle() { 7940 Bundle bundle = new Bundle(); 7941 if (mText != null) { 7942 bundle.putCharSequence(KEY_TEXT, mText); 7943 } 7944 bundle.putLong(KEY_TIMESTAMP, mTimestamp); 7945 if (mSender != null) { 7946 // Legacy listeners need this 7947 bundle.putCharSequence(KEY_SENDER, mSender.getName()); 7948 bundle.putParcelable(KEY_SENDER_PERSON, mSender); 7949 } 7950 if (mDataMimeType != null) { 7951 bundle.putString(KEY_DATA_MIME_TYPE, mDataMimeType); 7952 } 7953 if (mDataUri != null) { 7954 bundle.putParcelable(KEY_DATA_URI, mDataUri); 7955 } 7956 if (mExtras != null) { 7957 bundle.putBundle(KEY_EXTRAS_BUNDLE, mExtras); 7958 } 7959 if (mRemoteInputHistory) { 7960 bundle.putBoolean(KEY_REMOTE_INPUT_HISTORY, mRemoteInputHistory); 7961 } 7962 return bundle; 7963 } 7964 getBundleArrayForMessages(List<Message> messages)7965 static Bundle[] getBundleArrayForMessages(List<Message> messages) { 7966 Bundle[] bundles = new Bundle[messages.size()]; 7967 final int N = messages.size(); 7968 for (int i = 0; i < N; i++) { 7969 bundles[i] = messages.get(i).toBundle(); 7970 } 7971 return bundles; 7972 } 7973 7974 /** 7975 * Returns a list of messages read from the given bundle list, e.g. 7976 * {@link #EXTRA_MESSAGES} or {@link #EXTRA_HISTORIC_MESSAGES}. 7977 */ 7978 @NonNull getMessagesFromBundleArray(@ullable Parcelable[] bundles)7979 public static List<Message> getMessagesFromBundleArray(@Nullable Parcelable[] bundles) { 7980 if (bundles == null) { 7981 return new ArrayList<>(); 7982 } 7983 List<Message> messages = new ArrayList<>(bundles.length); 7984 for (int i = 0; i < bundles.length; i++) { 7985 if (bundles[i] instanceof Bundle) { 7986 Message message = getMessageFromBundle((Bundle)bundles[i]); 7987 if (message != null) { 7988 messages.add(message); 7989 } 7990 } 7991 } 7992 return messages; 7993 } 7994 7995 /** 7996 * Returns the message that is stored in the bundle (e.g. one of the values in the lists 7997 * in {@link #EXTRA_MESSAGES} or {@link #EXTRA_HISTORIC_MESSAGES}) or null if the 7998 * message couldn't be resolved. 7999 * @hide 8000 */ 8001 @Nullable getMessageFromBundle(@onNull Bundle bundle)8002 public static Message getMessageFromBundle(@NonNull Bundle bundle) { 8003 try { 8004 if (!bundle.containsKey(KEY_TEXT) || !bundle.containsKey(KEY_TIMESTAMP)) { 8005 return null; 8006 } else { 8007 8008 Person senderPerson = bundle.getParcelable(KEY_SENDER_PERSON); 8009 if (senderPerson == null) { 8010 // Legacy apps that use compat don't actually provide the sender objects 8011 // We need to fix the compat version to provide people / use 8012 // the native api instead 8013 CharSequence senderName = bundle.getCharSequence(KEY_SENDER); 8014 if (senderName != null) { 8015 senderPerson = new Person.Builder().setName(senderName).build(); 8016 } 8017 } 8018 Message message = new Message(bundle.getCharSequence(KEY_TEXT), 8019 bundle.getLong(KEY_TIMESTAMP), 8020 senderPerson, 8021 bundle.getBoolean(KEY_REMOTE_INPUT_HISTORY, false)); 8022 if (bundle.containsKey(KEY_DATA_MIME_TYPE) && 8023 bundle.containsKey(KEY_DATA_URI)) { 8024 message.setData(bundle.getString(KEY_DATA_MIME_TYPE), 8025 (Uri) bundle.getParcelable(KEY_DATA_URI)); 8026 } 8027 if (bundle.containsKey(KEY_EXTRAS_BUNDLE)) { 8028 message.getExtras().putAll(bundle.getBundle(KEY_EXTRAS_BUNDLE)); 8029 } 8030 return message; 8031 } 8032 } catch (ClassCastException e) { 8033 return null; 8034 } 8035 } 8036 } 8037 } 8038 8039 /** 8040 * Helper class for generating large-format notifications that include a list of (up to 5) strings. 8041 * 8042 * Here's how you'd set the <code>InboxStyle</code> on a notification: 8043 * <pre class="prettyprint"> 8044 * Notification notif = new Notification.Builder(mContext) 8045 * .setContentTitle("5 New mails from " + sender.toString()) 8046 * .setContentText(subject) 8047 * .setSmallIcon(R.drawable.new_mail) 8048 * .setLargeIcon(aBitmap) 8049 * .setStyle(new Notification.InboxStyle() 8050 * .addLine(str1) 8051 * .addLine(str2) 8052 * .setContentTitle("") 8053 * .setSummaryText("+3 more")) 8054 * .build(); 8055 * </pre> 8056 * 8057 * @see Notification#bigContentView 8058 */ 8059 public static class InboxStyle extends Style { 8060 8061 /** 8062 * The number of lines of remote input history allowed until we start reducing lines. 8063 */ 8064 private static final int NUMBER_OF_HISTORY_ALLOWED_UNTIL_REDUCTION = 1; 8065 private ArrayList<CharSequence> mTexts = new ArrayList<CharSequence>(5); 8066 InboxStyle()8067 public InboxStyle() { 8068 } 8069 8070 /** 8071 * @deprecated use {@code InboxStyle()}. 8072 */ 8073 @Deprecated InboxStyle(Builder builder)8074 public InboxStyle(Builder builder) { 8075 setBuilder(builder); 8076 } 8077 8078 /** 8079 * Overrides ContentTitle in the big form of the template. 8080 * This defaults to the value passed to setContentTitle(). 8081 */ setBigContentTitle(CharSequence title)8082 public InboxStyle setBigContentTitle(CharSequence title) { 8083 internalSetBigContentTitle(safeCharSequence(title)); 8084 return this; 8085 } 8086 8087 /** 8088 * Set the first line of text after the detail section in the big form of the template. 8089 */ setSummaryText(CharSequence cs)8090 public InboxStyle setSummaryText(CharSequence cs) { 8091 internalSetSummaryText(safeCharSequence(cs)); 8092 return this; 8093 } 8094 8095 /** 8096 * Append a line to the digest section of the Inbox notification. 8097 */ addLine(CharSequence cs)8098 public InboxStyle addLine(CharSequence cs) { 8099 mTexts.add(safeCharSequence(cs)); 8100 return this; 8101 } 8102 8103 /** 8104 * @hide 8105 */ getLines()8106 public ArrayList<CharSequence> getLines() { 8107 return mTexts; 8108 } 8109 8110 /** 8111 * @hide 8112 */ addExtras(Bundle extras)8113 public void addExtras(Bundle extras) { 8114 super.addExtras(extras); 8115 8116 CharSequence[] a = new CharSequence[mTexts.size()]; 8117 extras.putCharSequenceArray(EXTRA_TEXT_LINES, mTexts.toArray(a)); 8118 } 8119 8120 /** 8121 * @hide 8122 */ 8123 @Override restoreFromExtras(Bundle extras)8124 protected void restoreFromExtras(Bundle extras) { 8125 super.restoreFromExtras(extras); 8126 8127 mTexts.clear(); 8128 if (extras.containsKey(EXTRA_TEXT_LINES)) { 8129 Collections.addAll(mTexts, extras.getCharSequenceArray(EXTRA_TEXT_LINES)); 8130 } 8131 } 8132 8133 /** 8134 * @hide 8135 */ makeBigContentView()8136 public RemoteViews makeBigContentView() { 8137 StandardTemplateParams p = mBuilder.mParams.reset().fillTextsFrom(mBuilder).text(null); 8138 TemplateBindResult result = new TemplateBindResult(); 8139 RemoteViews contentView = getStandardView(mBuilder.getInboxLayoutResource(), p, result); 8140 8141 int[] rowIds = {R.id.inbox_text0, R.id.inbox_text1, R.id.inbox_text2, R.id.inbox_text3, 8142 R.id.inbox_text4, R.id.inbox_text5, R.id.inbox_text6}; 8143 8144 // Make sure all rows are gone in case we reuse a view. 8145 for (int rowId : rowIds) { 8146 contentView.setViewVisibility(rowId, View.GONE); 8147 } 8148 8149 int i=0; 8150 int topPadding = mBuilder.mContext.getResources().getDimensionPixelSize( 8151 R.dimen.notification_inbox_item_top_padding); 8152 boolean first = true; 8153 int onlyViewId = 0; 8154 int maxRows = rowIds.length; 8155 if (mBuilder.mActions.size() > 0) { 8156 maxRows--; 8157 } 8158 RemoteInputHistoryItem[] remoteInputHistory = getParcelableArrayFromBundle( 8159 mBuilder.mN.extras, EXTRA_REMOTE_INPUT_HISTORY_ITEMS, 8160 RemoteInputHistoryItem.class); 8161 if (remoteInputHistory != null 8162 && remoteInputHistory.length > NUMBER_OF_HISTORY_ALLOWED_UNTIL_REDUCTION) { 8163 // Let's remove some messages to make room for the remote input history. 8164 // 1 is always able to fit, but let's remove them if they are 2 or 3 8165 int numRemoteInputs = Math.min(remoteInputHistory.length, 8166 MAX_REMOTE_INPUT_HISTORY_LINES); 8167 int totalNumRows = mTexts.size() + numRemoteInputs 8168 - NUMBER_OF_HISTORY_ALLOWED_UNTIL_REDUCTION; 8169 if (totalNumRows > maxRows) { 8170 int overflow = totalNumRows - maxRows; 8171 if (mTexts.size() > maxRows) { 8172 // Heuristic: if the Texts don't fit anyway, we'll rather drop the last 8173 // few messages, even with the remote input 8174 maxRows -= overflow; 8175 } else { 8176 // otherwise we drop the first messages 8177 i = overflow; 8178 } 8179 } 8180 } 8181 while (i < mTexts.size() && i < maxRows) { 8182 CharSequence str = mTexts.get(i); 8183 if (!TextUtils.isEmpty(str)) { 8184 contentView.setViewVisibility(rowIds[i], View.VISIBLE); 8185 contentView.setTextViewText(rowIds[i], 8186 mBuilder.processTextSpans(mBuilder.processLegacyText(str))); 8187 mBuilder.setTextViewColorSecondary(contentView, rowIds[i], p); 8188 contentView.setViewPadding(rowIds[i], 0, topPadding, 0, 0); 8189 handleInboxImageMargin(contentView, rowIds[i], first, 8190 result.getIconMarginEnd()); 8191 if (first) { 8192 onlyViewId = rowIds[i]; 8193 } else { 8194 onlyViewId = 0; 8195 } 8196 first = false; 8197 } 8198 i++; 8199 } 8200 if (onlyViewId != 0) { 8201 // We only have 1 entry, lets make it look like the normal Text of a Bigtext 8202 topPadding = mBuilder.mContext.getResources().getDimensionPixelSize( 8203 R.dimen.notification_text_margin_top); 8204 contentView.setViewPadding(onlyViewId, 0, topPadding, 0, 0); 8205 } 8206 8207 return contentView; 8208 } 8209 8210 /** 8211 * @hide 8212 */ 8213 @Override areNotificationsVisiblyDifferent(Style other)8214 public boolean areNotificationsVisiblyDifferent(Style other) { 8215 if (other == null || getClass() != other.getClass()) { 8216 return true; 8217 } 8218 InboxStyle newS = (InboxStyle) other; 8219 8220 final ArrayList<CharSequence> myLines = getLines(); 8221 final ArrayList<CharSequence> newLines = newS.getLines(); 8222 final int n = myLines.size(); 8223 if (n != newLines.size()) { 8224 return true; 8225 } 8226 8227 for (int i = 0; i < n; i++) { 8228 if (!Objects.equals( 8229 String.valueOf(myLines.get(i)), 8230 String.valueOf(newLines.get(i)))) { 8231 return true; 8232 } 8233 } 8234 return false; 8235 } 8236 handleInboxImageMargin(RemoteViews contentView, int id, boolean first, int marginEndValue)8237 private void handleInboxImageMargin(RemoteViews contentView, int id, boolean first, 8238 int marginEndValue) { 8239 int endMargin = 0; 8240 if (first) { 8241 final int max = mBuilder.mN.extras.getInt(EXTRA_PROGRESS_MAX, 0); 8242 final boolean ind = mBuilder.mN.extras.getBoolean(EXTRA_PROGRESS_INDETERMINATE); 8243 boolean hasProgress = max != 0 || ind; 8244 if (!hasProgress) { 8245 endMargin = marginEndValue; 8246 } 8247 } 8248 contentView.setViewLayoutMarginEnd(id, endMargin); 8249 } 8250 } 8251 8252 /** 8253 * Notification style for media playback notifications. 8254 * 8255 * In the expanded form, {@link Notification#bigContentView}, up to 5 8256 * {@link Notification.Action}s specified with 8257 * {@link Notification.Builder#addAction(Action) addAction} will be 8258 * shown as icon-only pushbuttons, suitable for transport controls. The Bitmap given to 8259 * {@link Notification.Builder#setLargeIcon(android.graphics.Bitmap) setLargeIcon()} will be 8260 * treated as album artwork. 8261 * <p> 8262 * Unlike the other styles provided here, MediaStyle can also modify the standard-size 8263 * {@link Notification#contentView}; by providing action indices to 8264 * {@link #setShowActionsInCompactView(int...)} you can promote up to 3 actions to be displayed 8265 * in the standard view alongside the usual content. 8266 * <p> 8267 * Notifications created with MediaStyle will have their category set to 8268 * {@link Notification#CATEGORY_TRANSPORT CATEGORY_TRANSPORT} unless you set a different 8269 * category using {@link Notification.Builder#setCategory(String) setCategory()}. 8270 * <p> 8271 * Finally, if you attach a {@link android.media.session.MediaSession.Token} using 8272 * {@link android.app.Notification.MediaStyle#setMediaSession(MediaSession.Token)}, 8273 * the System UI can identify this as a notification representing an active media session 8274 * and respond accordingly (by showing album artwork in the lockscreen, for example). 8275 * 8276 * <p> 8277 * Starting at {@link android.os.Build.VERSION_CODES#O Android O} any notification that has a 8278 * media session attached with {@link #setMediaSession(MediaSession.Token)} will be colorized. 8279 * You can opt-out of this behavior by using {@link Notification.Builder#setColorized(boolean)}. 8280 * <p> 8281 * 8282 * To use this style with your Notification, feed it to 8283 * {@link Notification.Builder#setStyle(android.app.Notification.Style)} like so: 8284 * <pre class="prettyprint"> 8285 * Notification noti = new Notification.Builder() 8286 * .setSmallIcon(R.drawable.ic_stat_player) 8287 * .setContentTitle("Track title") 8288 * .setContentText("Artist - Album") 8289 * .setLargeIcon(albumArtBitmap)) 8290 * .setStyle(<b>new Notification.MediaStyle()</b> 8291 * .setMediaSession(mySession)) 8292 * .build(); 8293 * </pre> 8294 * 8295 * @see Notification#bigContentView 8296 * @see Notification.Builder#setColorized(boolean) 8297 */ 8298 public static class MediaStyle extends Style { 8299 // Changing max media buttons requires also changing templates 8300 // (notification_template_material_media and notification_template_material_big_media). 8301 static final int MAX_MEDIA_BUTTONS_IN_COMPACT = 3; 8302 static final int MAX_MEDIA_BUTTONS = 5; 8303 @IdRes private static final int[] MEDIA_BUTTON_IDS = { 8304 R.id.action0, 8305 R.id.action1, 8306 R.id.action2, 8307 R.id.action3, 8308 R.id.action4, 8309 }; 8310 8311 private int[] mActionsToShowInCompact = null; 8312 private MediaSession.Token mToken; 8313 MediaStyle()8314 public MediaStyle() { 8315 } 8316 8317 /** 8318 * @deprecated use {@code MediaStyle()}. 8319 */ 8320 @Deprecated MediaStyle(Builder builder)8321 public MediaStyle(Builder builder) { 8322 setBuilder(builder); 8323 } 8324 8325 /** 8326 * Request up to 3 actions (by index in the order of addition) to be shown in the compact 8327 * notification view. 8328 * 8329 * @param actions the indices of the actions to show in the compact notification view 8330 */ setShowActionsInCompactView(int...actions)8331 public MediaStyle setShowActionsInCompactView(int...actions) { 8332 mActionsToShowInCompact = actions; 8333 return this; 8334 } 8335 8336 /** 8337 * Attach a {@link android.media.session.MediaSession.Token} to this Notification 8338 * to provide additional playback information and control to the SystemUI. 8339 */ setMediaSession(MediaSession.Token token)8340 public MediaStyle setMediaSession(MediaSession.Token token) { 8341 mToken = token; 8342 return this; 8343 } 8344 8345 /** 8346 * @hide 8347 */ 8348 @Override 8349 @UnsupportedAppUsage buildStyled(Notification wip)8350 public Notification buildStyled(Notification wip) { 8351 super.buildStyled(wip); 8352 if (wip.category == null) { 8353 wip.category = Notification.CATEGORY_TRANSPORT; 8354 } 8355 return wip; 8356 } 8357 8358 /** 8359 * @hide 8360 */ 8361 @Override makeContentView(boolean increasedHeight)8362 public RemoteViews makeContentView(boolean increasedHeight) { 8363 return makeMediaContentView(); 8364 } 8365 8366 /** 8367 * @hide 8368 */ 8369 @Override makeBigContentView()8370 public RemoteViews makeBigContentView() { 8371 return makeMediaBigContentView(); 8372 } 8373 8374 /** 8375 * @hide 8376 */ 8377 @Override makeHeadsUpContentView(boolean increasedHeight)8378 public RemoteViews makeHeadsUpContentView(boolean increasedHeight) { 8379 return makeMediaContentView(); 8380 } 8381 8382 /** @hide */ 8383 @Override addExtras(Bundle extras)8384 public void addExtras(Bundle extras) { 8385 super.addExtras(extras); 8386 8387 if (mToken != null) { 8388 extras.putParcelable(EXTRA_MEDIA_SESSION, mToken); 8389 } 8390 if (mActionsToShowInCompact != null) { 8391 extras.putIntArray(EXTRA_COMPACT_ACTIONS, mActionsToShowInCompact); 8392 } 8393 } 8394 8395 /** 8396 * @hide 8397 */ 8398 @Override restoreFromExtras(Bundle extras)8399 protected void restoreFromExtras(Bundle extras) { 8400 super.restoreFromExtras(extras); 8401 8402 if (extras.containsKey(EXTRA_MEDIA_SESSION)) { 8403 mToken = extras.getParcelable(EXTRA_MEDIA_SESSION); 8404 } 8405 if (extras.containsKey(EXTRA_COMPACT_ACTIONS)) { 8406 mActionsToShowInCompact = extras.getIntArray(EXTRA_COMPACT_ACTIONS); 8407 } 8408 } 8409 8410 /** 8411 * @hide 8412 */ 8413 @Override areNotificationsVisiblyDifferent(Style other)8414 public boolean areNotificationsVisiblyDifferent(Style other) { 8415 if (other == null || getClass() != other.getClass()) { 8416 return true; 8417 } 8418 // All fields to compare are on the Notification object 8419 return false; 8420 } 8421 bindMediaActionButton(RemoteViews container, @IdRes int buttonId, Action action, StandardTemplateParams p)8422 private void bindMediaActionButton(RemoteViews container, @IdRes int buttonId, 8423 Action action, StandardTemplateParams p) { 8424 final boolean tombstone = (action.actionIntent == null); 8425 container.setViewVisibility(buttonId, View.VISIBLE); 8426 if (buttonId != R.id.media_seamless) { 8427 container.setImageViewIcon(buttonId, action.getIcon()); 8428 } 8429 8430 // If the action buttons should not be tinted, then just use the default 8431 // notification color. Otherwise, just use the passed-in color. 8432 Resources resources = mBuilder.mContext.getResources(); 8433 Configuration currentConfig = resources.getConfiguration(); 8434 boolean inNightMode = (currentConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK) 8435 == Configuration.UI_MODE_NIGHT_YES; 8436 int tintColor = mBuilder.shouldTintActionButtons() || mBuilder.isColorized(p) 8437 ? getActionColor(p) 8438 : ContrastColorUtil.resolveColor(mBuilder.mContext, 8439 Notification.COLOR_DEFAULT, inNightMode); 8440 8441 container.setDrawableTint(buttonId, false, tintColor, 8442 PorterDuff.Mode.SRC_ATOP); 8443 8444 final TypedArray typedArray = mBuilder.mContext.obtainStyledAttributes( 8445 new int[]{ android.R.attr.colorControlHighlight }); 8446 int rippleAlpha = Color.alpha(typedArray.getColor(0, 0)); 8447 typedArray.recycle(); 8448 int rippleColor = Color.argb(rippleAlpha, Color.red(tintColor), Color.green(tintColor), 8449 Color.blue(tintColor)); 8450 container.setRippleDrawableColor(buttonId, ColorStateList.valueOf(rippleColor)); 8451 8452 if (!tombstone) { 8453 container.setOnClickPendingIntent(buttonId, action.actionIntent); 8454 } 8455 container.setContentDescription(buttonId, action.title); 8456 } 8457 makeMediaContentView()8458 private RemoteViews makeMediaContentView() { 8459 StandardTemplateParams p = mBuilder.mParams.reset().hasProgress(false).fillTextsFrom( 8460 mBuilder); 8461 RemoteViews view = mBuilder.applyStandardTemplate( 8462 R.layout.notification_template_material_media, p, 8463 null /* result */); 8464 8465 final int numActions = mBuilder.mActions.size(); 8466 final int numActionsToShow = mActionsToShowInCompact == null 8467 ? 0 8468 : Math.min(mActionsToShowInCompact.length, MAX_MEDIA_BUTTONS_IN_COMPACT); 8469 if (numActionsToShow > numActions) { 8470 throw new IllegalArgumentException(String.format( 8471 "setShowActionsInCompactView: action %d out of bounds (max %d)", 8472 numActions, numActions - 1)); 8473 } 8474 for (int i = 0; i < MAX_MEDIA_BUTTONS_IN_COMPACT; i++) { 8475 if (i < numActionsToShow) { 8476 final Action action = mBuilder.mActions.get(mActionsToShowInCompact[i]); 8477 bindMediaActionButton(view, MEDIA_BUTTON_IDS[i], action, p); 8478 } else { 8479 view.setViewVisibility(MEDIA_BUTTON_IDS[i], View.GONE); 8480 } 8481 } 8482 bindMediaActionButton(view, R.id.media_seamless, new Action( 8483 R.drawable.ic_media_seamless, mBuilder.mContext.getString( 8484 com.android.internal.R.string.ext_media_seamless_action), null), p); 8485 view.setViewVisibility(R.id.media_seamless, View.GONE); 8486 handleImage(view); 8487 // handle the content margin 8488 int endMargin = R.dimen.notification_content_margin_end; 8489 if (mBuilder.mN.hasLargeIcon()) { 8490 endMargin = R.dimen.notification_media_image_margin_end; 8491 } 8492 view.setViewLayoutMarginEndDimen(R.id.notification_main_column, endMargin); 8493 return view; 8494 } 8495 getActionColor(StandardTemplateParams p)8496 private int getActionColor(StandardTemplateParams p) { 8497 return mBuilder.isColorized(p) ? mBuilder.getPrimaryTextColor(p) 8498 : mBuilder.resolveContrastColor(p); 8499 } 8500 makeMediaBigContentView()8501 private RemoteViews makeMediaBigContentView() { 8502 final int actionCount = Math.min(mBuilder.mActions.size(), MAX_MEDIA_BUTTONS); 8503 // Dont add an expanded view if there is no more content to be revealed 8504 int actionsInCompact = mActionsToShowInCompact == null 8505 ? 0 8506 : Math.min(mActionsToShowInCompact.length, MAX_MEDIA_BUTTONS_IN_COMPACT); 8507 if (!mBuilder.mN.hasLargeIcon() && actionCount <= actionsInCompact) { 8508 return null; 8509 } 8510 StandardTemplateParams p = mBuilder.mParams.reset().hasProgress(false).fillTextsFrom( 8511 mBuilder); 8512 RemoteViews big = mBuilder.applyStandardTemplate( 8513 R.layout.notification_template_material_big_media, p , null /* result */); 8514 8515 for (int i = 0; i < MAX_MEDIA_BUTTONS; i++) { 8516 if (i < actionCount) { 8517 bindMediaActionButton(big, MEDIA_BUTTON_IDS[i], mBuilder.mActions.get(i), p); 8518 } else { 8519 big.setViewVisibility(MEDIA_BUTTON_IDS[i], View.GONE); 8520 } 8521 } 8522 bindMediaActionButton(big, R.id.media_seamless, new Action(R.drawable.ic_media_seamless, 8523 mBuilder.mContext.getString( 8524 com.android.internal.R.string.ext_media_seamless_action), null), p); 8525 big.setViewVisibility(R.id.media_seamless, View.GONE); 8526 handleImage(big); 8527 return big; 8528 } 8529 handleImage(RemoteViews contentView)8530 private void handleImage(RemoteViews contentView) { 8531 if (mBuilder.mN.hasLargeIcon()) { 8532 contentView.setViewLayoutMarginEndDimen(R.id.line1, 0); 8533 contentView.setViewLayoutMarginEndDimen(R.id.text, 0); 8534 } 8535 } 8536 8537 /** 8538 * @hide 8539 */ 8540 @Override hasProgress()8541 protected boolean hasProgress() { 8542 return false; 8543 } 8544 } 8545 8546 /** 8547 * Notification style for custom views that are decorated by the system 8548 * 8549 * <p>Instead of providing a notification that is completely custom, a developer can set this 8550 * style and still obtain system decorations like the notification header with the expand 8551 * affordance and actions. 8552 * 8553 * <p>Use {@link android.app.Notification.Builder#setCustomContentView(RemoteViews)}, 8554 * {@link android.app.Notification.Builder#setCustomBigContentView(RemoteViews)} and 8555 * {@link android.app.Notification.Builder#setCustomHeadsUpContentView(RemoteViews)} to set the 8556 * corresponding custom views to display. 8557 * 8558 * To use this style with your Notification, feed it to 8559 * {@link Notification.Builder#setStyle(android.app.Notification.Style)} like so: 8560 * <pre class="prettyprint"> 8561 * Notification noti = new Notification.Builder() 8562 * .setSmallIcon(R.drawable.ic_stat_player) 8563 * .setLargeIcon(albumArtBitmap)) 8564 * .setCustomContentView(contentView); 8565 * .setStyle(<b>new Notification.DecoratedCustomViewStyle()</b>) 8566 * .build(); 8567 * </pre> 8568 */ 8569 public static class DecoratedCustomViewStyle extends Style { 8570 DecoratedCustomViewStyle()8571 public DecoratedCustomViewStyle() { 8572 } 8573 8574 /** 8575 * @hide 8576 */ displayCustomViewInline()8577 public boolean displayCustomViewInline() { 8578 return true; 8579 } 8580 8581 /** 8582 * @hide 8583 */ 8584 @Override makeContentView(boolean increasedHeight)8585 public RemoteViews makeContentView(boolean increasedHeight) { 8586 return makeStandardTemplateWithCustomContent(mBuilder.mN.contentView); 8587 } 8588 8589 /** 8590 * @hide 8591 */ 8592 @Override makeBigContentView()8593 public RemoteViews makeBigContentView() { 8594 return makeDecoratedBigContentView(); 8595 } 8596 8597 /** 8598 * @hide 8599 */ 8600 @Override makeHeadsUpContentView(boolean increasedHeight)8601 public RemoteViews makeHeadsUpContentView(boolean increasedHeight) { 8602 return makeDecoratedHeadsUpContentView(); 8603 } 8604 makeDecoratedHeadsUpContentView()8605 private RemoteViews makeDecoratedHeadsUpContentView() { 8606 RemoteViews headsUpContentView = mBuilder.mN.headsUpContentView == null 8607 ? mBuilder.mN.contentView 8608 : mBuilder.mN.headsUpContentView; 8609 if (mBuilder.mActions.size() == 0) { 8610 return makeStandardTemplateWithCustomContent(headsUpContentView); 8611 } 8612 TemplateBindResult result = new TemplateBindResult(); 8613 RemoteViews remoteViews = mBuilder.applyStandardTemplateWithActions( 8614 mBuilder.getBigBaseLayoutResource(), result); 8615 buildIntoRemoteViewContent(remoteViews, headsUpContentView, result); 8616 return remoteViews; 8617 } 8618 makeStandardTemplateWithCustomContent(RemoteViews customContent)8619 private RemoteViews makeStandardTemplateWithCustomContent(RemoteViews customContent) { 8620 TemplateBindResult result = new TemplateBindResult(); 8621 RemoteViews remoteViews = mBuilder.applyStandardTemplate( 8622 mBuilder.getBaseLayoutResource(), result); 8623 buildIntoRemoteViewContent(remoteViews, customContent, result); 8624 return remoteViews; 8625 } 8626 makeDecoratedBigContentView()8627 private RemoteViews makeDecoratedBigContentView() { 8628 RemoteViews bigContentView = mBuilder.mN.bigContentView == null 8629 ? mBuilder.mN.contentView 8630 : mBuilder.mN.bigContentView; 8631 if (mBuilder.mActions.size() == 0) { 8632 return makeStandardTemplateWithCustomContent(bigContentView); 8633 } 8634 TemplateBindResult result = new TemplateBindResult(); 8635 RemoteViews remoteViews = mBuilder.applyStandardTemplateWithActions( 8636 mBuilder.getBigBaseLayoutResource(), result); 8637 buildIntoRemoteViewContent(remoteViews, bigContentView, result); 8638 return remoteViews; 8639 } 8640 buildIntoRemoteViewContent(RemoteViews remoteViews, RemoteViews customContent, TemplateBindResult result)8641 private void buildIntoRemoteViewContent(RemoteViews remoteViews, 8642 RemoteViews customContent, TemplateBindResult result) { 8643 int childIndex = -1; 8644 if (customContent != null) { 8645 // Need to clone customContent before adding, because otherwise it can no longer be 8646 // parceled independently of remoteViews. 8647 customContent = customContent.clone(); 8648 remoteViews.removeAllViewsExceptId(R.id.notification_main_column, R.id.progress); 8649 remoteViews.addView(R.id.notification_main_column, customContent, 0 /* index */); 8650 remoteViews.addFlags(RemoteViews.FLAG_REAPPLY_DISALLOWED); 8651 childIndex = 0; 8652 } 8653 remoteViews.setIntTag(R.id.notification_main_column, 8654 com.android.internal.R.id.notification_custom_view_index_tag, 8655 childIndex); 8656 // also update the end margin if there is an image 8657 Resources resources = mBuilder.mContext.getResources(); 8658 int endMargin = resources.getDimensionPixelSize( 8659 R.dimen.notification_content_margin_end) + result.getIconMarginEnd(); 8660 remoteViews.setViewLayoutMarginEnd(R.id.notification_main_column, endMargin); 8661 } 8662 8663 /** 8664 * @hide 8665 */ 8666 @Override areNotificationsVisiblyDifferent(Style other)8667 public boolean areNotificationsVisiblyDifferent(Style other) { 8668 if (other == null || getClass() != other.getClass()) { 8669 return true; 8670 } 8671 // Comparison done for all custom RemoteViews, independent of style 8672 return false; 8673 } 8674 } 8675 8676 /** 8677 * Notification style for media custom views that are decorated by the system 8678 * 8679 * <p>Instead of providing a media notification that is completely custom, a developer can set 8680 * this style and still obtain system decorations like the notification header with the expand 8681 * affordance and actions. 8682 * 8683 * <p>Use {@link android.app.Notification.Builder#setCustomContentView(RemoteViews)}, 8684 * {@link android.app.Notification.Builder#setCustomBigContentView(RemoteViews)} and 8685 * {@link android.app.Notification.Builder#setCustomHeadsUpContentView(RemoteViews)} to set the 8686 * corresponding custom views to display. 8687 * <p> 8688 * Contrary to {@link MediaStyle} a developer has to opt-in to the colorizing of the 8689 * notification by using {@link Notification.Builder#setColorized(boolean)}. 8690 * <p> 8691 * To use this style with your Notification, feed it to 8692 * {@link Notification.Builder#setStyle(android.app.Notification.Style)} like so: 8693 * <pre class="prettyprint"> 8694 * Notification noti = new Notification.Builder() 8695 * .setSmallIcon(R.drawable.ic_stat_player) 8696 * .setLargeIcon(albumArtBitmap)) 8697 * .setCustomContentView(contentView); 8698 * .setStyle(<b>new Notification.DecoratedMediaCustomViewStyle()</b> 8699 * .setMediaSession(mySession)) 8700 * .build(); 8701 * </pre> 8702 * 8703 * @see android.app.Notification.DecoratedCustomViewStyle 8704 * @see android.app.Notification.MediaStyle 8705 */ 8706 public static class DecoratedMediaCustomViewStyle extends MediaStyle { 8707 DecoratedMediaCustomViewStyle()8708 public DecoratedMediaCustomViewStyle() { 8709 } 8710 8711 /** 8712 * @hide 8713 */ displayCustomViewInline()8714 public boolean displayCustomViewInline() { 8715 return true; 8716 } 8717 8718 /** 8719 * @hide 8720 */ 8721 @Override makeContentView(boolean increasedHeight)8722 public RemoteViews makeContentView(boolean increasedHeight) { 8723 RemoteViews remoteViews = super.makeContentView(false /* increasedHeight */); 8724 return buildIntoRemoteView(remoteViews, R.id.notification_content_container, 8725 mBuilder.mN.contentView); 8726 } 8727 8728 /** 8729 * @hide 8730 */ 8731 @Override makeBigContentView()8732 public RemoteViews makeBigContentView() { 8733 RemoteViews customRemoteView = mBuilder.mN.bigContentView != null 8734 ? mBuilder.mN.bigContentView 8735 : mBuilder.mN.contentView; 8736 return makeBigContentViewWithCustomContent(customRemoteView); 8737 } 8738 makeBigContentViewWithCustomContent(RemoteViews customRemoteView)8739 private RemoteViews makeBigContentViewWithCustomContent(RemoteViews customRemoteView) { 8740 RemoteViews remoteViews = super.makeBigContentView(); 8741 if (remoteViews != null) { 8742 return buildIntoRemoteView(remoteViews, R.id.notification_main_column, 8743 customRemoteView); 8744 } else if (customRemoteView != mBuilder.mN.contentView){ 8745 remoteViews = super.makeContentView(false /* increasedHeight */); 8746 return buildIntoRemoteView(remoteViews, R.id.notification_content_container, 8747 customRemoteView); 8748 } else { 8749 return null; 8750 } 8751 } 8752 8753 /** 8754 * @hide 8755 */ 8756 @Override makeHeadsUpContentView(boolean increasedHeight)8757 public RemoteViews makeHeadsUpContentView(boolean increasedHeight) { 8758 RemoteViews customRemoteView = mBuilder.mN.headsUpContentView != null 8759 ? mBuilder.mN.headsUpContentView 8760 : mBuilder.mN.contentView; 8761 return makeBigContentViewWithCustomContent(customRemoteView); 8762 } 8763 8764 /** 8765 * @hide 8766 */ 8767 @Override areNotificationsVisiblyDifferent(Style other)8768 public boolean areNotificationsVisiblyDifferent(Style other) { 8769 if (other == null || getClass() != other.getClass()) { 8770 return true; 8771 } 8772 // Comparison done for all custom RemoteViews, independent of style 8773 return false; 8774 } 8775 buildIntoRemoteView(RemoteViews remoteViews, int id, RemoteViews customContent)8776 private RemoteViews buildIntoRemoteView(RemoteViews remoteViews, int id, 8777 RemoteViews customContent) { 8778 if (customContent != null) { 8779 // Need to clone customContent before adding, because otherwise it can no longer be 8780 // parceled independently of remoteViews. 8781 customContent = customContent.clone(); 8782 customContent.overrideTextColors(mBuilder.getPrimaryTextColor(mBuilder.mParams)); 8783 remoteViews.removeAllViews(id); 8784 remoteViews.addView(id, customContent); 8785 remoteViews.addFlags(RemoteViews.FLAG_REAPPLY_DISALLOWED); 8786 } 8787 return remoteViews; 8788 } 8789 } 8790 8791 /** 8792 * Encapsulates the information needed to display a notification as a bubble. 8793 * 8794 * <p>A bubble is used to display app content in a floating window over the existing 8795 * foreground activity. A bubble has a collapsed state represented by an icon and an 8796 * expanded state that displays an activity. These may be defined via 8797 * {@link Builder#Builder(PendingIntent, Icon)} or they may 8798 * be defined via an existing shortcut using {@link Builder#Builder(String)}. 8799 * </p> 8800 * 8801 * <b>Notifications with a valid and allowed bubble will display in collapsed state 8802 * outside of the notification shade on unlocked devices. When a user interacts with the 8803 * collapsed bubble, the bubble activity will be invoked and displayed.</b> 8804 * 8805 * @see Notification.Builder#setBubbleMetadata(BubbleMetadata) 8806 */ 8807 public static final class BubbleMetadata implements Parcelable { 8808 8809 private PendingIntent mPendingIntent; 8810 private PendingIntent mDeleteIntent; 8811 private Icon mIcon; 8812 private int mDesiredHeight; 8813 @DimenRes private int mDesiredHeightResId; 8814 private int mFlags; 8815 private String mShortcutId; 8816 8817 /** 8818 * If set and the app creating the bubble is in the foreground, the bubble will be posted 8819 * in its expanded state. 8820 * 8821 * <p>This flag has no effect if the app posting the bubble is not in the foreground. 8822 * The app is considered foreground if it is visible and on the screen, note that 8823 * a foreground service does not qualify. 8824 * </p> 8825 * 8826 * <p>Generally this flag should only be set if the user has performed an action to request 8827 * or create a bubble.</p> 8828 * 8829 * @hide 8830 */ 8831 public static final int FLAG_AUTO_EXPAND_BUBBLE = 0x00000001; 8832 8833 /** 8834 * Indicates whether the notification associated with the bubble is being visually 8835 * suppressed from the notification shade. When <code>true</code> the notification is 8836 * hidden, when <code>false</code> the notification shows as normal. 8837 * 8838 * <p>Apps sending bubbles may set this flag so that the bubble is posted <b>without</b> 8839 * the associated notification in the notification shade.</p> 8840 * 8841 * <p>Apps sending bubbles can only apply this flag when the app is in the foreground, 8842 * otherwise the flag is not respected. The app is considered foreground if it is visible 8843 * and on the screen, note that a foreground service does not qualify.</p> 8844 * 8845 * <p>Generally this flag should only be set by the app if the user has performed an 8846 * action to request or create a bubble, or if the user has seen the content in the 8847 * notification and the notification is no longer relevant. </p> 8848 * 8849 * <p>The system will also update this flag with <code>true</code> to hide the notification 8850 * from the user once the bubble has been expanded. </p> 8851 * 8852 * @hide 8853 */ 8854 public static final int FLAG_SUPPRESS_NOTIFICATION = 0x00000002; 8855 BubbleMetadata(PendingIntent expandIntent, PendingIntent deleteIntent, Icon icon, int height, @DimenRes int heightResId, String shortcutId)8856 private BubbleMetadata(PendingIntent expandIntent, PendingIntent deleteIntent, 8857 Icon icon, int height, @DimenRes int heightResId, String shortcutId) { 8858 mPendingIntent = expandIntent; 8859 mIcon = icon; 8860 mDesiredHeight = height; 8861 mDesiredHeightResId = heightResId; 8862 mDeleteIntent = deleteIntent; 8863 mShortcutId = shortcutId; 8864 } 8865 BubbleMetadata(Parcel in)8866 private BubbleMetadata(Parcel in) { 8867 if (in.readInt() != 0) { 8868 mPendingIntent = PendingIntent.CREATOR.createFromParcel(in); 8869 } 8870 if (in.readInt() != 0) { 8871 mIcon = Icon.CREATOR.createFromParcel(in); 8872 } 8873 mDesiredHeight = in.readInt(); 8874 mFlags = in.readInt(); 8875 if (in.readInt() != 0) { 8876 mDeleteIntent = PendingIntent.CREATOR.createFromParcel(in); 8877 } 8878 mDesiredHeightResId = in.readInt(); 8879 if (in.readInt() != 0) { 8880 mShortcutId = in.readString8(); 8881 } 8882 } 8883 8884 /** 8885 * @return the shortcut id used for this bubble if created via 8886 * {@link Builder#Builder(String)} or null if created 8887 * via {@link Builder#Builder(PendingIntent, Icon)}. 8888 */ 8889 @Nullable getShortcutId()8890 public String getShortcutId() { 8891 return mShortcutId; 8892 } 8893 8894 /** 8895 * @return the pending intent used to populate the floating window for this bubble, or 8896 * null if this bubble is created via {@link Builder#Builder(String)}. 8897 */ 8898 @SuppressLint("InvalidNullConversion") 8899 @Nullable getIntent()8900 public PendingIntent getIntent() { 8901 return mPendingIntent; 8902 } 8903 8904 /** 8905 * @deprecated use {@link #getIntent()} instead. 8906 * @removed Removed from the R SDK but was never publicly stable. 8907 */ 8908 @Nullable 8909 @Deprecated getBubbleIntent()8910 public PendingIntent getBubbleIntent() { 8911 return mPendingIntent; 8912 } 8913 8914 /** 8915 * @return the pending intent to send when the bubble is dismissed by a user, if one exists. 8916 */ 8917 @Nullable getDeleteIntent()8918 public PendingIntent getDeleteIntent() { 8919 return mDeleteIntent; 8920 } 8921 8922 /** 8923 * @return the icon that will be displayed for this bubble when it is collapsed, or null 8924 * if the bubble is created via {@link Builder#Builder(String)}. 8925 */ 8926 @SuppressLint("InvalidNullConversion") 8927 @Nullable getIcon()8928 public Icon getIcon() { 8929 return mIcon; 8930 } 8931 8932 /** 8933 * @deprecated use {@link #getIcon()} instead. 8934 * @removed Removed from the R SDK but was never publicly stable. 8935 */ 8936 @Nullable 8937 @Deprecated getBubbleIcon()8938 public Icon getBubbleIcon() { 8939 return mIcon; 8940 } 8941 8942 /** 8943 * @return the ideal height, in DPs, for the floating window that app content defined by 8944 * {@link #getIntent()} for this bubble. A value of 0 indicates a desired height has 8945 * not been set. 8946 */ 8947 @Dimension(unit = DP) getDesiredHeight()8948 public int getDesiredHeight() { 8949 return mDesiredHeight; 8950 } 8951 8952 /** 8953 * @return the resId of ideal height for the floating window that app content defined by 8954 * {@link #getIntent()} for this bubble. A value of 0 indicates a res value has not 8955 * been provided for the desired height. 8956 */ 8957 @DimenRes getDesiredHeightResId()8958 public int getDesiredHeightResId() { 8959 return mDesiredHeightResId; 8960 } 8961 8962 /** 8963 * @return whether this bubble should auto expand when it is posted. 8964 * 8965 * @see BubbleMetadata.Builder#setAutoExpandBubble(boolean) 8966 */ getAutoExpandBubble()8967 public boolean getAutoExpandBubble() { 8968 return (mFlags & FLAG_AUTO_EXPAND_BUBBLE) != 0; 8969 } 8970 8971 /** 8972 * Indicates whether the notification associated with the bubble is being visually 8973 * suppressed from the notification shade. When <code>true</code> the notification is 8974 * hidden, when <code>false</code> the notification shows as normal. 8975 * 8976 * <p>Apps sending bubbles may set this flag so that the bubble is posted <b>without</b> 8977 * the associated notification in the notification shade.</p> 8978 * 8979 * <p>Apps sending bubbles can only apply this flag when the app is in the foreground, 8980 * otherwise the flag is not respected. The app is considered foreground if it is visible 8981 * and on the screen, note that a foreground service does not qualify.</p> 8982 * 8983 * <p>Generally the app should only set this flag if the user has performed an 8984 * action to request or create a bubble, or if the user has seen the content in the 8985 * notification and the notification is no longer relevant. </p> 8986 * 8987 * <p>The system will update this flag with <code>true</code> to hide the notification 8988 * from the user once the bubble has been expanded.</p> 8989 * 8990 * @return whether this bubble should suppress the notification when it is posted. 8991 * 8992 * @see BubbleMetadata.Builder#setSuppressNotification(boolean) 8993 */ isNotificationSuppressed()8994 public boolean isNotificationSuppressed() { 8995 return (mFlags & FLAG_SUPPRESS_NOTIFICATION) != 0; 8996 } 8997 8998 public static final @android.annotation.NonNull Parcelable.Creator<BubbleMetadata> CREATOR = 8999 new Parcelable.Creator<BubbleMetadata>() { 9000 9001 @Override 9002 public BubbleMetadata createFromParcel(Parcel source) { 9003 return new BubbleMetadata(source); 9004 } 9005 9006 @Override 9007 public BubbleMetadata[] newArray(int size) { 9008 return new BubbleMetadata[size]; 9009 } 9010 }; 9011 9012 @Override describeContents()9013 public int describeContents() { 9014 return 0; 9015 } 9016 9017 @Override writeToParcel(Parcel out, int flags)9018 public void writeToParcel(Parcel out, int flags) { 9019 out.writeInt(mPendingIntent != null ? 1 : 0); 9020 if (mPendingIntent != null) { 9021 mPendingIntent.writeToParcel(out, 0); 9022 } 9023 out.writeInt(mIcon != null ? 1 : 0); 9024 if (mIcon != null) { 9025 mIcon.writeToParcel(out, 0); 9026 } 9027 out.writeInt(mDesiredHeight); 9028 out.writeInt(mFlags); 9029 out.writeInt(mDeleteIntent != null ? 1 : 0); 9030 if (mDeleteIntent != null) { 9031 mDeleteIntent.writeToParcel(out, 0); 9032 } 9033 out.writeInt(mDesiredHeightResId); 9034 out.writeInt(TextUtils.isEmpty(mShortcutId) ? 0 : 1); 9035 if (!TextUtils.isEmpty(mShortcutId)) { 9036 out.writeString8(mShortcutId); 9037 } 9038 } 9039 9040 /** 9041 * @hide 9042 */ setFlags(int flags)9043 public void setFlags(int flags) { 9044 mFlags = flags; 9045 } 9046 9047 /** 9048 * @hide 9049 */ getFlags()9050 public int getFlags() { 9051 return mFlags; 9052 } 9053 9054 /** 9055 * Builder to construct a {@link BubbleMetadata} object. 9056 */ 9057 public static final class Builder { 9058 9059 private PendingIntent mPendingIntent; 9060 private Icon mIcon; 9061 private int mDesiredHeight; 9062 @DimenRes private int mDesiredHeightResId; 9063 private int mFlags; 9064 private PendingIntent mDeleteIntent; 9065 private String mShortcutId; 9066 9067 /** 9068 * @deprecated use {@link Builder#Builder(String)} for a bubble created via a 9069 * {@link ShortcutInfo} or {@link Builder#Builder(PendingIntent, Icon)} for a bubble 9070 * created via a {@link PendingIntent}. 9071 */ 9072 @Deprecated Builder()9073 public Builder() { 9074 } 9075 9076 /** 9077 * Creates a {@link BubbleMetadata.Builder} based on a {@link ShortcutInfo}. To create 9078 * a shortcut bubble, ensure that the shortcut associated with the provided 9079 * {@param shortcutId} is published as a dynamic shortcut that was built with 9080 * {@link ShortcutInfo.Builder#setLongLived(boolean)} being true, otherwise your 9081 * notification will not be able to bubble. 9082 * 9083 * <p>The shortcut icon will be used to represent the bubble when it is collapsed.</p> 9084 * 9085 * <p>The shortcut activity will be used when the bubble is expanded. This will display 9086 * the shortcut activity in a floating window over the existing foreground activity.</p> 9087 * 9088 * <p>If the shortcut has not been published when the bubble notification is sent, 9089 * no bubble will be produced. If the shortcut is deleted while the bubble is active, 9090 * the bubble will be removed.</p> 9091 * 9092 * @throws NullPointerException if shortcutId is null. 9093 * 9094 * @see ShortcutInfo 9095 * @see ShortcutInfo.Builder#setLongLived(boolean) 9096 * @see android.content.pm.ShortcutManager#addDynamicShortcuts(List) 9097 */ Builder(@onNull String shortcutId)9098 public Builder(@NonNull String shortcutId) { 9099 if (TextUtils.isEmpty(shortcutId)) { 9100 throw new NullPointerException("Bubble requires a non-null shortcut id"); 9101 } 9102 mShortcutId = shortcutId; 9103 } 9104 9105 /** 9106 * Creates a {@link BubbleMetadata.Builder} based on the provided intent and icon. 9107 * 9108 * <p>The icon will be used to represent the bubble when it is collapsed. An icon 9109 * should be representative of the content within the bubble. If your app produces 9110 * multiple bubbles, the icon should be unique for each of them.</p> 9111 * 9112 * <p>The intent that will be used when the bubble is expanded. This will display the 9113 * app content in a floating window over the existing foreground activity. The intent 9114 * should point to a resizable activity. </p> 9115 * 9116 * @throws NullPointerException if intent is null. 9117 * @throws NullPointerException if icon is null. 9118 */ Builder(@onNull PendingIntent intent, @NonNull Icon icon)9119 public Builder(@NonNull PendingIntent intent, @NonNull Icon icon) { 9120 if (intent == null) { 9121 throw new NullPointerException("Bubble requires non-null pending intent"); 9122 } 9123 if (icon == null) { 9124 throw new NullPointerException("Bubbles require non-null icon"); 9125 } 9126 if (icon.getType() != TYPE_URI_ADAPTIVE_BITMAP 9127 && icon.getType() != TYPE_URI) { 9128 Log.w(TAG, "Bubbles work best with icons of TYPE_URI or " 9129 + "TYPE_URI_ADAPTIVE_BITMAP. " 9130 + "In the future, using an icon of this type will be required."); 9131 } 9132 mPendingIntent = intent; 9133 mIcon = icon; 9134 } 9135 9136 /** 9137 * @deprecated use {@link Builder#Builder(String)} instead. 9138 * @removed Removed from the R SDK but was never publicly stable. 9139 */ 9140 @NonNull 9141 @Deprecated createShortcutBubble(@onNull String shortcutId)9142 public BubbleMetadata.Builder createShortcutBubble(@NonNull String shortcutId) { 9143 if (!TextUtils.isEmpty(shortcutId)) { 9144 // If shortcut id is set, we don't use these if they were previously set. 9145 mPendingIntent = null; 9146 mIcon = null; 9147 } 9148 mShortcutId = shortcutId; 9149 return this; 9150 } 9151 9152 /** 9153 * @deprecated use {@link Builder#Builder(PendingIntent, Icon)} instead. 9154 * @removed Removed from the R SDK but was never publicly stable. 9155 */ 9156 @NonNull 9157 @Deprecated createIntentBubble(@onNull PendingIntent intent, @NonNull Icon icon)9158 public BubbleMetadata.Builder createIntentBubble(@NonNull PendingIntent intent, 9159 @NonNull Icon icon) { 9160 if (intent == null) { 9161 throw new IllegalArgumentException("Bubble requires non-null pending intent"); 9162 } 9163 if (icon == null) { 9164 throw new IllegalArgumentException("Bubbles require non-null icon"); 9165 } 9166 if (icon.getType() != TYPE_URI_ADAPTIVE_BITMAP 9167 && icon.getType() != TYPE_URI) { 9168 Log.w(TAG, "Bubbles work best with icons of TYPE_URI or " 9169 + "TYPE_URI_ADAPTIVE_BITMAP. " 9170 + "In the future, using an icon of this type will be required."); 9171 } 9172 mShortcutId = null; 9173 mPendingIntent = intent; 9174 mIcon = icon; 9175 return this; 9176 } 9177 9178 /** 9179 * Sets the intent for the bubble. 9180 * 9181 * <p>The intent that will be used when the bubble is expanded. This will display the 9182 * app content in a floating window over the existing foreground activity. The intent 9183 * should point to a resizable activity. </p> 9184 * 9185 * @throws NullPointerException if intent is null. 9186 * @throws IllegalStateException if this builder was created via 9187 * {@link Builder#Builder(String)}. 9188 */ 9189 @NonNull setIntent(@onNull PendingIntent intent)9190 public BubbleMetadata.Builder setIntent(@NonNull PendingIntent intent) { 9191 if (mShortcutId != null) { 9192 throw new IllegalStateException("Created as a shortcut bubble, cannot set a " 9193 + "PendingIntent. Consider using " 9194 + "BubbleMetadata.Builder(PendingIntent,Icon) instead."); 9195 } 9196 if (intent == null) { 9197 throw new NullPointerException("Bubble requires non-null pending intent"); 9198 } 9199 mPendingIntent = intent; 9200 return this; 9201 } 9202 9203 /** 9204 * Sets the icon for the bubble. Can only be used if the bubble was created 9205 * via {@link Builder#Builder(PendingIntent, Icon)}. 9206 * 9207 * <p>The icon will be used to represent the bubble when it is collapsed. An icon 9208 * should be representative of the content within the bubble. If your app produces 9209 * multiple bubbles, the icon should be unique for each of them.</p> 9210 * 9211 * <p>It is recommended to use an {@link Icon} of type {@link Icon#TYPE_URI} 9212 * or {@link Icon#TYPE_URI_ADAPTIVE_BITMAP}</p> 9213 * 9214 * @throws NullPointerException if icon is null. 9215 * @throws IllegalStateException if this builder was created via 9216 * {@link Builder#Builder(String)}. 9217 */ 9218 @NonNull setIcon(@onNull Icon icon)9219 public BubbleMetadata.Builder setIcon(@NonNull Icon icon) { 9220 if (mShortcutId != null) { 9221 throw new IllegalStateException("Created as a shortcut bubble, cannot set an " 9222 + "Icon. Consider using " 9223 + "BubbleMetadata.Builder(PendingIntent,Icon) instead."); 9224 } 9225 if (icon == null) { 9226 throw new NullPointerException("Bubbles require non-null icon"); 9227 } 9228 if (icon.getType() != TYPE_URI_ADAPTIVE_BITMAP 9229 && icon.getType() != TYPE_URI) { 9230 Log.w(TAG, "Bubbles work best with icons of TYPE_URI or " 9231 + "TYPE_URI_ADAPTIVE_BITMAP. " 9232 + "In the future, using an icon of this type will be required."); 9233 } 9234 mIcon = icon; 9235 return this; 9236 } 9237 9238 /** 9239 * Sets the desired height in DPs for the expanded content of the bubble. 9240 * 9241 * <p>This height may not be respected if there is not enough space on the screen or if 9242 * the provided height is too small to be useful.</p> 9243 * 9244 * <p>If {@link #setDesiredHeightResId(int)} was previously called on this builder, the 9245 * previous value set will be cleared after calling this method, and this value will 9246 * be used instead.</p> 9247 * 9248 * <p>A desired height (in DPs or via resID) is optional.</p> 9249 * 9250 * @see #setDesiredHeightResId(int) 9251 */ 9252 @NonNull setDesiredHeight(@imensionunit = DP) int height)9253 public BubbleMetadata.Builder setDesiredHeight(@Dimension(unit = DP) int height) { 9254 mDesiredHeight = Math.max(height, 0); 9255 mDesiredHeightResId = 0; 9256 return this; 9257 } 9258 9259 9260 /** 9261 * Sets the desired height via resId for the expanded content of the bubble. 9262 * 9263 * <p>This height may not be respected if there is not enough space on the screen or if 9264 * the provided height is too small to be useful.</p> 9265 * 9266 * <p>If {@link #setDesiredHeight(int)} was previously called on this builder, the 9267 * previous value set will be cleared after calling this method, and this value will 9268 * be used instead.</p> 9269 * 9270 * <p>A desired height (in DPs or via resID) is optional.</p> 9271 * 9272 * @see #setDesiredHeight(int) 9273 */ 9274 @NonNull setDesiredHeightResId(@imenRes int heightResId)9275 public BubbleMetadata.Builder setDesiredHeightResId(@DimenRes int heightResId) { 9276 mDesiredHeightResId = heightResId; 9277 mDesiredHeight = 0; 9278 return this; 9279 } 9280 9281 /** 9282 * Sets whether the bubble will be posted in its expanded state. 9283 * 9284 * <p>This flag has no effect if the app posting the bubble is not in the foreground. 9285 * The app is considered foreground if it is visible and on the screen, note that 9286 * a foreground service does not qualify. 9287 * </p> 9288 * 9289 * <p>Generally, this flag should only be set if the user has performed an action to 9290 * request or create a bubble.</p> 9291 * 9292 * <p>Setting this flag is optional; it defaults to false.</p> 9293 */ 9294 @NonNull setAutoExpandBubble(boolean shouldExpand)9295 public BubbleMetadata.Builder setAutoExpandBubble(boolean shouldExpand) { 9296 setFlag(FLAG_AUTO_EXPAND_BUBBLE, shouldExpand); 9297 return this; 9298 } 9299 9300 /** 9301 * Sets whether the bubble will be posted <b>without</b> the associated notification in 9302 * the notification shade. 9303 * 9304 * <p>This flag has no effect if the app posting the bubble is not in the foreground. 9305 * The app is considered foreground if it is visible and on the screen, note that 9306 * a foreground service does not qualify. 9307 * </p> 9308 * 9309 * <p>Generally, this flag should only be set if the user has performed an action to 9310 * request or create a bubble, or if the user has seen the content in the notification 9311 * and the notification is no longer relevant.</p> 9312 * 9313 * <p>Setting this flag is optional; it defaults to false.</p> 9314 */ 9315 @NonNull setSuppressNotification(boolean shouldSuppressNotif)9316 public BubbleMetadata.Builder setSuppressNotification(boolean shouldSuppressNotif) { 9317 setFlag(FLAG_SUPPRESS_NOTIFICATION, shouldSuppressNotif); 9318 return this; 9319 } 9320 9321 /** 9322 * Sets an intent to send when this bubble is explicitly removed by the user. 9323 * 9324 * <p>Setting a delete intent is optional.</p> 9325 */ 9326 @NonNull setDeleteIntent(@ullable PendingIntent deleteIntent)9327 public BubbleMetadata.Builder setDeleteIntent(@Nullable PendingIntent deleteIntent) { 9328 mDeleteIntent = deleteIntent; 9329 return this; 9330 } 9331 9332 /** 9333 * Creates the {@link BubbleMetadata} defined by this builder. 9334 * 9335 * @throws NullPointerException if required elements have not been set. 9336 */ 9337 @NonNull build()9338 public BubbleMetadata build() { 9339 if (mShortcutId == null && mPendingIntent == null) { 9340 throw new NullPointerException( 9341 "Must supply pending intent or shortcut to bubble"); 9342 } 9343 if (mShortcutId == null && mIcon == null) { 9344 throw new NullPointerException( 9345 "Must supply an icon or shortcut for the bubble"); 9346 } 9347 BubbleMetadata data = new BubbleMetadata(mPendingIntent, mDeleteIntent, 9348 mIcon, mDesiredHeight, mDesiredHeightResId, mShortcutId); 9349 data.setFlags(mFlags); 9350 return data; 9351 } 9352 9353 /** 9354 * @hide 9355 */ setFlag(int mask, boolean value)9356 public BubbleMetadata.Builder setFlag(int mask, boolean value) { 9357 if (value) { 9358 mFlags |= mask; 9359 } else { 9360 mFlags &= ~mask; 9361 } 9362 return this; 9363 } 9364 } 9365 } 9366 9367 9368 // When adding a new Style subclass here, don't forget to update 9369 // Builder.getNotificationStyleClass. 9370 9371 /** 9372 * Extender interface for use with {@link Builder#extend}. Extenders may be used to add 9373 * metadata or change options on a notification builder. 9374 */ 9375 public interface Extender { 9376 /** 9377 * Apply this extender to a notification builder. 9378 * @param builder the builder to be modified. 9379 * @return the build object for chaining. 9380 */ extend(Builder builder)9381 public Builder extend(Builder builder); 9382 } 9383 9384 /** 9385 * Helper class to add wearable extensions to notifications. 9386 * <p class="note"> See 9387 * <a href="{@docRoot}wear/notifications/creating.html">Creating Notifications 9388 * for Android Wear</a> for more information on how to use this class. 9389 * <p> 9390 * To create a notification with wearable extensions: 9391 * <ol> 9392 * <li>Create a {@link android.app.Notification.Builder}, setting any desired 9393 * properties. 9394 * <li>Create a {@link android.app.Notification.WearableExtender}. 9395 * <li>Set wearable-specific properties using the 9396 * {@code add} and {@code set} methods of {@link android.app.Notification.WearableExtender}. 9397 * <li>Call {@link android.app.Notification.Builder#extend} to apply the extensions to a 9398 * notification. 9399 * <li>Post the notification to the notification system with the 9400 * {@code NotificationManager.notify(...)} methods. 9401 * </ol> 9402 * 9403 * <pre class="prettyprint"> 9404 * Notification notif = new Notification.Builder(mContext) 9405 * .setContentTitle("New mail from " + sender.toString()) 9406 * .setContentText(subject) 9407 * .setSmallIcon(R.drawable.new_mail) 9408 * .extend(new Notification.WearableExtender() 9409 * .setContentIcon(R.drawable.new_mail)) 9410 * .build(); 9411 * NotificationManager notificationManger = 9412 * (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); 9413 * notificationManger.notify(0, notif);</pre> 9414 * 9415 * <p>Wearable extensions can be accessed on an existing notification by using the 9416 * {@code WearableExtender(Notification)} constructor, 9417 * and then using the {@code get} methods to access values. 9418 * 9419 * <pre class="prettyprint"> 9420 * Notification.WearableExtender wearableExtender = new Notification.WearableExtender( 9421 * notification); 9422 * List<Notification> pages = wearableExtender.getPages();</pre> 9423 */ 9424 public static final class WearableExtender implements Extender { 9425 /** 9426 * Sentinel value for an action index that is unset. 9427 */ 9428 public static final int UNSET_ACTION_INDEX = -1; 9429 9430 /** 9431 * Size value for use with {@link #setCustomSizePreset} to show this notification with 9432 * default sizing. 9433 * <p>For custom display notifications created using {@link #setDisplayIntent}, 9434 * the default is {@link #SIZE_MEDIUM}. All other notifications size automatically based 9435 * on their content. 9436 * 9437 * @deprecated Display intents are no longer supported. 9438 */ 9439 @Deprecated 9440 public static final int SIZE_DEFAULT = 0; 9441 9442 /** 9443 * Size value for use with {@link #setCustomSizePreset} to show this notification 9444 * with an extra small size. 9445 * <p>This value is only applicable for custom display notifications created using 9446 * {@link #setDisplayIntent}. 9447 * 9448 * @deprecated Display intents are no longer supported. 9449 */ 9450 @Deprecated 9451 public static final int SIZE_XSMALL = 1; 9452 9453 /** 9454 * Size value for use with {@link #setCustomSizePreset} to show this notification 9455 * with a small size. 9456 * <p>This value is only applicable for custom display notifications created using 9457 * {@link #setDisplayIntent}. 9458 * 9459 * @deprecated Display intents are no longer supported. 9460 */ 9461 @Deprecated 9462 public static final int SIZE_SMALL = 2; 9463 9464 /** 9465 * Size value for use with {@link #setCustomSizePreset} to show this notification 9466 * with a medium size. 9467 * <p>This value is only applicable for custom display notifications created using 9468 * {@link #setDisplayIntent}. 9469 * 9470 * @deprecated Display intents are no longer supported. 9471 */ 9472 @Deprecated 9473 public static final int SIZE_MEDIUM = 3; 9474 9475 /** 9476 * Size value for use with {@link #setCustomSizePreset} to show this notification 9477 * with a large size. 9478 * <p>This value is only applicable for custom display notifications created using 9479 * {@link #setDisplayIntent}. 9480 * 9481 * @deprecated Display intents are no longer supported. 9482 */ 9483 @Deprecated 9484 public static final int SIZE_LARGE = 4; 9485 9486 /** 9487 * Size value for use with {@link #setCustomSizePreset} to show this notification 9488 * full screen. 9489 * <p>This value is only applicable for custom display notifications created using 9490 * {@link #setDisplayIntent}. 9491 * 9492 * @deprecated Display intents are no longer supported. 9493 */ 9494 @Deprecated 9495 public static final int SIZE_FULL_SCREEN = 5; 9496 9497 /** 9498 * Sentinel value for use with {@link #setHintScreenTimeout} to keep the screen on for a 9499 * short amount of time when this notification is displayed on the screen. This 9500 * is the default value. 9501 * 9502 * @deprecated This feature is no longer supported. 9503 */ 9504 @Deprecated 9505 public static final int SCREEN_TIMEOUT_SHORT = 0; 9506 9507 /** 9508 * Sentinel value for use with {@link #setHintScreenTimeout} to keep the screen on 9509 * for a longer amount of time when this notification is displayed on the screen. 9510 * 9511 * @deprecated This feature is no longer supported. 9512 */ 9513 @Deprecated 9514 public static final int SCREEN_TIMEOUT_LONG = -1; 9515 9516 /** Notification extra which contains wearable extensions */ 9517 private static final String EXTRA_WEARABLE_EXTENSIONS = "android.wearable.EXTENSIONS"; 9518 9519 // Keys within EXTRA_WEARABLE_EXTENSIONS for wearable options. 9520 private static final String KEY_ACTIONS = "actions"; 9521 private static final String KEY_FLAGS = "flags"; 9522 private static final String KEY_DISPLAY_INTENT = "displayIntent"; 9523 private static final String KEY_PAGES = "pages"; 9524 private static final String KEY_BACKGROUND = "background"; 9525 private static final String KEY_CONTENT_ICON = "contentIcon"; 9526 private static final String KEY_CONTENT_ICON_GRAVITY = "contentIconGravity"; 9527 private static final String KEY_CONTENT_ACTION_INDEX = "contentActionIndex"; 9528 private static final String KEY_CUSTOM_SIZE_PRESET = "customSizePreset"; 9529 private static final String KEY_CUSTOM_CONTENT_HEIGHT = "customContentHeight"; 9530 private static final String KEY_GRAVITY = "gravity"; 9531 private static final String KEY_HINT_SCREEN_TIMEOUT = "hintScreenTimeout"; 9532 private static final String KEY_DISMISSAL_ID = "dismissalId"; 9533 private static final String KEY_BRIDGE_TAG = "bridgeTag"; 9534 9535 // Flags bitwise-ored to mFlags 9536 private static final int FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE = 0x1; 9537 private static final int FLAG_HINT_HIDE_ICON = 1 << 1; 9538 private static final int FLAG_HINT_SHOW_BACKGROUND_ONLY = 1 << 2; 9539 private static final int FLAG_START_SCROLL_BOTTOM = 1 << 3; 9540 private static final int FLAG_HINT_AVOID_BACKGROUND_CLIPPING = 1 << 4; 9541 private static final int FLAG_BIG_PICTURE_AMBIENT = 1 << 5; 9542 private static final int FLAG_HINT_CONTENT_INTENT_LAUNCHES_ACTIVITY = 1 << 6; 9543 9544 // Default value for flags integer 9545 private static final int DEFAULT_FLAGS = FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE; 9546 9547 private static final int DEFAULT_CONTENT_ICON_GRAVITY = Gravity.END; 9548 private static final int DEFAULT_GRAVITY = Gravity.BOTTOM; 9549 9550 private ArrayList<Action> mActions = new ArrayList<Action>(); 9551 private int mFlags = DEFAULT_FLAGS; 9552 private PendingIntent mDisplayIntent; 9553 private ArrayList<Notification> mPages = new ArrayList<Notification>(); 9554 private Bitmap mBackground; 9555 private int mContentIcon; 9556 private int mContentIconGravity = DEFAULT_CONTENT_ICON_GRAVITY; 9557 private int mContentActionIndex = UNSET_ACTION_INDEX; 9558 private int mCustomSizePreset = SIZE_DEFAULT; 9559 private int mCustomContentHeight; 9560 private int mGravity = DEFAULT_GRAVITY; 9561 private int mHintScreenTimeout; 9562 private String mDismissalId; 9563 private String mBridgeTag; 9564 9565 /** 9566 * Create a {@link android.app.Notification.WearableExtender} with default 9567 * options. 9568 */ WearableExtender()9569 public WearableExtender() { 9570 } 9571 WearableExtender(Notification notif)9572 public WearableExtender(Notification notif) { 9573 Bundle wearableBundle = notif.extras.getBundle(EXTRA_WEARABLE_EXTENSIONS); 9574 if (wearableBundle != null) { 9575 List<Action> actions = wearableBundle.getParcelableArrayList(KEY_ACTIONS); 9576 if (actions != null) { 9577 mActions.addAll(actions); 9578 } 9579 9580 mFlags = wearableBundle.getInt(KEY_FLAGS, DEFAULT_FLAGS); 9581 mDisplayIntent = wearableBundle.getParcelable(KEY_DISPLAY_INTENT); 9582 9583 Notification[] pages = getParcelableArrayFromBundle( 9584 wearableBundle, KEY_PAGES, Notification.class); 9585 if (pages != null) { 9586 Collections.addAll(mPages, pages); 9587 } 9588 9589 mBackground = wearableBundle.getParcelable(KEY_BACKGROUND); 9590 mContentIcon = wearableBundle.getInt(KEY_CONTENT_ICON); 9591 mContentIconGravity = wearableBundle.getInt(KEY_CONTENT_ICON_GRAVITY, 9592 DEFAULT_CONTENT_ICON_GRAVITY); 9593 mContentActionIndex = wearableBundle.getInt(KEY_CONTENT_ACTION_INDEX, 9594 UNSET_ACTION_INDEX); 9595 mCustomSizePreset = wearableBundle.getInt(KEY_CUSTOM_SIZE_PRESET, 9596 SIZE_DEFAULT); 9597 mCustomContentHeight = wearableBundle.getInt(KEY_CUSTOM_CONTENT_HEIGHT); 9598 mGravity = wearableBundle.getInt(KEY_GRAVITY, DEFAULT_GRAVITY); 9599 mHintScreenTimeout = wearableBundle.getInt(KEY_HINT_SCREEN_TIMEOUT); 9600 mDismissalId = wearableBundle.getString(KEY_DISMISSAL_ID); 9601 mBridgeTag = wearableBundle.getString(KEY_BRIDGE_TAG); 9602 } 9603 } 9604 9605 /** 9606 * Apply wearable extensions to a notification that is being built. This is typically 9607 * called by the {@link android.app.Notification.Builder#extend} method of 9608 * {@link android.app.Notification.Builder}. 9609 */ 9610 @Override extend(Notification.Builder builder)9611 public Notification.Builder extend(Notification.Builder builder) { 9612 Bundle wearableBundle = new Bundle(); 9613 9614 if (!mActions.isEmpty()) { 9615 wearableBundle.putParcelableArrayList(KEY_ACTIONS, mActions); 9616 } 9617 if (mFlags != DEFAULT_FLAGS) { 9618 wearableBundle.putInt(KEY_FLAGS, mFlags); 9619 } 9620 if (mDisplayIntent != null) { 9621 wearableBundle.putParcelable(KEY_DISPLAY_INTENT, mDisplayIntent); 9622 } 9623 if (!mPages.isEmpty()) { 9624 wearableBundle.putParcelableArray(KEY_PAGES, mPages.toArray( 9625 new Notification[mPages.size()])); 9626 } 9627 if (mBackground != null) { 9628 wearableBundle.putParcelable(KEY_BACKGROUND, mBackground); 9629 } 9630 if (mContentIcon != 0) { 9631 wearableBundle.putInt(KEY_CONTENT_ICON, mContentIcon); 9632 } 9633 if (mContentIconGravity != DEFAULT_CONTENT_ICON_GRAVITY) { 9634 wearableBundle.putInt(KEY_CONTENT_ICON_GRAVITY, mContentIconGravity); 9635 } 9636 if (mContentActionIndex != UNSET_ACTION_INDEX) { 9637 wearableBundle.putInt(KEY_CONTENT_ACTION_INDEX, 9638 mContentActionIndex); 9639 } 9640 if (mCustomSizePreset != SIZE_DEFAULT) { 9641 wearableBundle.putInt(KEY_CUSTOM_SIZE_PRESET, mCustomSizePreset); 9642 } 9643 if (mCustomContentHeight != 0) { 9644 wearableBundle.putInt(KEY_CUSTOM_CONTENT_HEIGHT, mCustomContentHeight); 9645 } 9646 if (mGravity != DEFAULT_GRAVITY) { 9647 wearableBundle.putInt(KEY_GRAVITY, mGravity); 9648 } 9649 if (mHintScreenTimeout != 0) { 9650 wearableBundle.putInt(KEY_HINT_SCREEN_TIMEOUT, mHintScreenTimeout); 9651 } 9652 if (mDismissalId != null) { 9653 wearableBundle.putString(KEY_DISMISSAL_ID, mDismissalId); 9654 } 9655 if (mBridgeTag != null) { 9656 wearableBundle.putString(KEY_BRIDGE_TAG, mBridgeTag); 9657 } 9658 9659 builder.getExtras().putBundle(EXTRA_WEARABLE_EXTENSIONS, wearableBundle); 9660 return builder; 9661 } 9662 9663 @Override clone()9664 public WearableExtender clone() { 9665 WearableExtender that = new WearableExtender(); 9666 that.mActions = new ArrayList<Action>(this.mActions); 9667 that.mFlags = this.mFlags; 9668 that.mDisplayIntent = this.mDisplayIntent; 9669 that.mPages = new ArrayList<Notification>(this.mPages); 9670 that.mBackground = this.mBackground; 9671 that.mContentIcon = this.mContentIcon; 9672 that.mContentIconGravity = this.mContentIconGravity; 9673 that.mContentActionIndex = this.mContentActionIndex; 9674 that.mCustomSizePreset = this.mCustomSizePreset; 9675 that.mCustomContentHeight = this.mCustomContentHeight; 9676 that.mGravity = this.mGravity; 9677 that.mHintScreenTimeout = this.mHintScreenTimeout; 9678 that.mDismissalId = this.mDismissalId; 9679 that.mBridgeTag = this.mBridgeTag; 9680 return that; 9681 } 9682 9683 /** 9684 * Add a wearable action to this notification. 9685 * 9686 * <p>When wearable actions are added using this method, the set of actions that 9687 * show on a wearable device splits from devices that only show actions added 9688 * using {@link android.app.Notification.Builder#addAction}. This allows for customization 9689 * of which actions display on different devices. 9690 * 9691 * @param action the action to add to this notification 9692 * @return this object for method chaining 9693 * @see android.app.Notification.Action 9694 */ addAction(Action action)9695 public WearableExtender addAction(Action action) { 9696 mActions.add(action); 9697 return this; 9698 } 9699 9700 /** 9701 * Adds wearable actions to this notification. 9702 * 9703 * <p>When wearable actions are added using this method, the set of actions that 9704 * show on a wearable device splits from devices that only show actions added 9705 * using {@link android.app.Notification.Builder#addAction}. This allows for customization 9706 * of which actions display on different devices. 9707 * 9708 * @param actions the actions to add to this notification 9709 * @return this object for method chaining 9710 * @see android.app.Notification.Action 9711 */ addActions(List<Action> actions)9712 public WearableExtender addActions(List<Action> actions) { 9713 mActions.addAll(actions); 9714 return this; 9715 } 9716 9717 /** 9718 * Clear all wearable actions present on this builder. 9719 * @return this object for method chaining. 9720 * @see #addAction 9721 */ clearActions()9722 public WearableExtender clearActions() { 9723 mActions.clear(); 9724 return this; 9725 } 9726 9727 /** 9728 * Get the wearable actions present on this notification. 9729 */ getActions()9730 public List<Action> getActions() { 9731 return mActions; 9732 } 9733 9734 /** 9735 * Set an intent to launch inside of an activity view when displaying 9736 * this notification. The {@link PendingIntent} provided should be for an activity. 9737 * 9738 * <pre class="prettyprint"> 9739 * Intent displayIntent = new Intent(context, MyDisplayActivity.class); 9740 * PendingIntent displayPendingIntent = PendingIntent.getActivity(context, 9741 * 0, displayIntent, PendingIntent.FLAG_UPDATE_CURRENT); 9742 * Notification notif = new Notification.Builder(context) 9743 * .extend(new Notification.WearableExtender() 9744 * .setDisplayIntent(displayPendingIntent) 9745 * .setCustomSizePreset(Notification.WearableExtender.SIZE_MEDIUM)) 9746 * .build();</pre> 9747 * 9748 * <p>The activity to launch needs to allow embedding, must be exported, and 9749 * should have an empty task affinity. It is also recommended to use the device 9750 * default light theme. 9751 * 9752 * <p>Example AndroidManifest.xml entry: 9753 * <pre class="prettyprint"> 9754 * <activity android:name="com.example.MyDisplayActivity" 9755 * android:exported="true" 9756 * android:allowEmbedded="true" 9757 * android:taskAffinity="" 9758 * android:theme="@android:style/Theme.DeviceDefault.Light" /></pre> 9759 * 9760 * @param intent the {@link PendingIntent} for an activity 9761 * @return this object for method chaining 9762 * @see android.app.Notification.WearableExtender#getDisplayIntent 9763 * @deprecated Display intents are no longer supported. 9764 */ 9765 @Deprecated setDisplayIntent(PendingIntent intent)9766 public WearableExtender setDisplayIntent(PendingIntent intent) { 9767 mDisplayIntent = intent; 9768 return this; 9769 } 9770 9771 /** 9772 * Get the intent to launch inside of an activity view when displaying this 9773 * notification. This {@code PendingIntent} should be for an activity. 9774 * 9775 * @deprecated Display intents are no longer supported. 9776 */ 9777 @Deprecated getDisplayIntent()9778 public PendingIntent getDisplayIntent() { 9779 return mDisplayIntent; 9780 } 9781 9782 /** 9783 * Add an additional page of content to display with this notification. The current 9784 * notification forms the first page, and pages added using this function form 9785 * subsequent pages. This field can be used to separate a notification into multiple 9786 * sections. 9787 * 9788 * @param page the notification to add as another page 9789 * @return this object for method chaining 9790 * @see android.app.Notification.WearableExtender#getPages 9791 * @deprecated Multiple content pages are no longer supported. 9792 */ 9793 @Deprecated addPage(Notification page)9794 public WearableExtender addPage(Notification page) { 9795 mPages.add(page); 9796 return this; 9797 } 9798 9799 /** 9800 * Add additional pages of content to display with this notification. The current 9801 * notification forms the first page, and pages added using this function form 9802 * subsequent pages. This field can be used to separate a notification into multiple 9803 * sections. 9804 * 9805 * @param pages a list of notifications 9806 * @return this object for method chaining 9807 * @see android.app.Notification.WearableExtender#getPages 9808 * @deprecated Multiple content pages are no longer supported. 9809 */ 9810 @Deprecated addPages(List<Notification> pages)9811 public WearableExtender addPages(List<Notification> pages) { 9812 mPages.addAll(pages); 9813 return this; 9814 } 9815 9816 /** 9817 * Clear all additional pages present on this builder. 9818 * @return this object for method chaining. 9819 * @see #addPage 9820 * @deprecated Multiple content pages are no longer supported. 9821 */ 9822 @Deprecated clearPages()9823 public WearableExtender clearPages() { 9824 mPages.clear(); 9825 return this; 9826 } 9827 9828 /** 9829 * Get the array of additional pages of content for displaying this notification. The 9830 * current notification forms the first page, and elements within this array form 9831 * subsequent pages. This field can be used to separate a notification into multiple 9832 * sections. 9833 * @return the pages for this notification 9834 * @deprecated Multiple content pages are no longer supported. 9835 */ 9836 @Deprecated getPages()9837 public List<Notification> getPages() { 9838 return mPages; 9839 } 9840 9841 /** 9842 * Set a background image to be displayed behind the notification content. 9843 * Contrary to the {@link android.app.Notification.BigPictureStyle}, this background 9844 * will work with any notification style. 9845 * 9846 * @param background the background bitmap 9847 * @return this object for method chaining 9848 * @see android.app.Notification.WearableExtender#getBackground 9849 * @deprecated Background images are no longer supported. 9850 */ 9851 @Deprecated setBackground(Bitmap background)9852 public WearableExtender setBackground(Bitmap background) { 9853 mBackground = background; 9854 return this; 9855 } 9856 9857 /** 9858 * Get a background image to be displayed behind the notification content. 9859 * Contrary to the {@link android.app.Notification.BigPictureStyle}, this background 9860 * will work with any notification style. 9861 * 9862 * @return the background image 9863 * @see android.app.Notification.WearableExtender#setBackground 9864 * @deprecated Background images are no longer supported. 9865 */ 9866 @Deprecated getBackground()9867 public Bitmap getBackground() { 9868 return mBackground; 9869 } 9870 9871 /** 9872 * Set an icon that goes with the content of this notification. 9873 */ 9874 @Deprecated setContentIcon(int icon)9875 public WearableExtender setContentIcon(int icon) { 9876 mContentIcon = icon; 9877 return this; 9878 } 9879 9880 /** 9881 * Get an icon that goes with the content of this notification. 9882 */ 9883 @Deprecated getContentIcon()9884 public int getContentIcon() { 9885 return mContentIcon; 9886 } 9887 9888 /** 9889 * Set the gravity that the content icon should have within the notification display. 9890 * Supported values include {@link android.view.Gravity#START} and 9891 * {@link android.view.Gravity#END}. The default value is {@link android.view.Gravity#END}. 9892 * @see #setContentIcon 9893 */ 9894 @Deprecated setContentIconGravity(int contentIconGravity)9895 public WearableExtender setContentIconGravity(int contentIconGravity) { 9896 mContentIconGravity = contentIconGravity; 9897 return this; 9898 } 9899 9900 /** 9901 * Get the gravity that the content icon should have within the notification display. 9902 * Supported values include {@link android.view.Gravity#START} and 9903 * {@link android.view.Gravity#END}. The default value is {@link android.view.Gravity#END}. 9904 * @see #getContentIcon 9905 */ 9906 @Deprecated getContentIconGravity()9907 public int getContentIconGravity() { 9908 return mContentIconGravity; 9909 } 9910 9911 /** 9912 * Set an action from this notification's actions as the primary action. If the action has a 9913 * {@link RemoteInput} associated with it, shortcuts to the options for that input are shown 9914 * directly on the notification. 9915 * 9916 * @param actionIndex The index of the primary action. 9917 * If wearable actions were added to the main notification, this index 9918 * will apply to that list, otherwise it will apply to the regular 9919 * actions list. 9920 */ setContentAction(int actionIndex)9921 public WearableExtender setContentAction(int actionIndex) { 9922 mContentActionIndex = actionIndex; 9923 return this; 9924 } 9925 9926 /** 9927 * Get the index of the notification action, if any, that was specified as the primary 9928 * action. 9929 * 9930 * <p>If wearable specific actions were added to the main notification, this index will 9931 * apply to that list, otherwise it will apply to the regular actions list. 9932 * 9933 * @return the action index or {@link #UNSET_ACTION_INDEX} if no action was selected. 9934 */ getContentAction()9935 public int getContentAction() { 9936 return mContentActionIndex; 9937 } 9938 9939 /** 9940 * Set the gravity that this notification should have within the available viewport space. 9941 * Supported values include {@link android.view.Gravity#TOP}, 9942 * {@link android.view.Gravity#CENTER_VERTICAL} and {@link android.view.Gravity#BOTTOM}. 9943 * The default value is {@link android.view.Gravity#BOTTOM}. 9944 */ 9945 @Deprecated setGravity(int gravity)9946 public WearableExtender setGravity(int gravity) { 9947 mGravity = gravity; 9948 return this; 9949 } 9950 9951 /** 9952 * Get the gravity that this notification should have within the available viewport space. 9953 * Supported values include {@link android.view.Gravity#TOP}, 9954 * {@link android.view.Gravity#CENTER_VERTICAL} and {@link android.view.Gravity#BOTTOM}. 9955 * The default value is {@link android.view.Gravity#BOTTOM}. 9956 */ 9957 @Deprecated getGravity()9958 public int getGravity() { 9959 return mGravity; 9960 } 9961 9962 /** 9963 * Set the custom size preset for the display of this notification out of the available 9964 * presets found in {@link android.app.Notification.WearableExtender}, e.g. 9965 * {@link #SIZE_LARGE}. 9966 * <p>Some custom size presets are only applicable for custom display notifications created 9967 * using {@link android.app.Notification.WearableExtender#setDisplayIntent}. Check the 9968 * documentation for the preset in question. See also 9969 * {@link #setCustomContentHeight} and {@link #getCustomSizePreset}. 9970 */ 9971 @Deprecated setCustomSizePreset(int sizePreset)9972 public WearableExtender setCustomSizePreset(int sizePreset) { 9973 mCustomSizePreset = sizePreset; 9974 return this; 9975 } 9976 9977 /** 9978 * Get the custom size preset for the display of this notification out of the available 9979 * presets found in {@link android.app.Notification.WearableExtender}, e.g. 9980 * {@link #SIZE_LARGE}. 9981 * <p>Some custom size presets are only applicable for custom display notifications created 9982 * using {@link #setDisplayIntent}. Check the documentation for the preset in question. 9983 * See also {@link #setCustomContentHeight} and {@link #setCustomSizePreset}. 9984 */ 9985 @Deprecated getCustomSizePreset()9986 public int getCustomSizePreset() { 9987 return mCustomSizePreset; 9988 } 9989 9990 /** 9991 * Set the custom height in pixels for the display of this notification's content. 9992 * <p>This option is only available for custom display notifications created 9993 * using {@link android.app.Notification.WearableExtender#setDisplayIntent}. See also 9994 * {@link android.app.Notification.WearableExtender#setCustomSizePreset} and 9995 * {@link #getCustomContentHeight}. 9996 */ 9997 @Deprecated setCustomContentHeight(int height)9998 public WearableExtender setCustomContentHeight(int height) { 9999 mCustomContentHeight = height; 10000 return this; 10001 } 10002 10003 /** 10004 * Get the custom height in pixels for the display of this notification's content. 10005 * <p>This option is only available for custom display notifications created 10006 * using {@link #setDisplayIntent}. See also {@link #setCustomSizePreset} and 10007 * {@link #setCustomContentHeight}. 10008 */ 10009 @Deprecated getCustomContentHeight()10010 public int getCustomContentHeight() { 10011 return mCustomContentHeight; 10012 } 10013 10014 /** 10015 * Set whether the scrolling position for the contents of this notification should start 10016 * at the bottom of the contents instead of the top when the contents are too long to 10017 * display within the screen. Default is false (start scroll at the top). 10018 */ setStartScrollBottom(boolean startScrollBottom)10019 public WearableExtender setStartScrollBottom(boolean startScrollBottom) { 10020 setFlag(FLAG_START_SCROLL_BOTTOM, startScrollBottom); 10021 return this; 10022 } 10023 10024 /** 10025 * Get whether the scrolling position for the contents of this notification should start 10026 * at the bottom of the contents instead of the top when the contents are too long to 10027 * display within the screen. Default is false (start scroll at the top). 10028 */ getStartScrollBottom()10029 public boolean getStartScrollBottom() { 10030 return (mFlags & FLAG_START_SCROLL_BOTTOM) != 0; 10031 } 10032 10033 /** 10034 * Set whether the content intent is available when the wearable device is not connected 10035 * to a companion device. The user can still trigger this intent when the wearable device 10036 * is offline, but a visual hint will indicate that the content intent may not be available. 10037 * Defaults to true. 10038 */ setContentIntentAvailableOffline( boolean contentIntentAvailableOffline)10039 public WearableExtender setContentIntentAvailableOffline( 10040 boolean contentIntentAvailableOffline) { 10041 setFlag(FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE, contentIntentAvailableOffline); 10042 return this; 10043 } 10044 10045 /** 10046 * Get whether the content intent is available when the wearable device is not connected 10047 * to a companion device. The user can still trigger this intent when the wearable device 10048 * is offline, but a visual hint will indicate that the content intent may not be available. 10049 * Defaults to true. 10050 */ getContentIntentAvailableOffline()10051 public boolean getContentIntentAvailableOffline() { 10052 return (mFlags & FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE) != 0; 10053 } 10054 10055 /** 10056 * Set a hint that this notification's icon should not be displayed. 10057 * @param hintHideIcon {@code true} to hide the icon, {@code false} otherwise. 10058 * @return this object for method chaining 10059 */ 10060 @Deprecated setHintHideIcon(boolean hintHideIcon)10061 public WearableExtender setHintHideIcon(boolean hintHideIcon) { 10062 setFlag(FLAG_HINT_HIDE_ICON, hintHideIcon); 10063 return this; 10064 } 10065 10066 /** 10067 * Get a hint that this notification's icon should not be displayed. 10068 * @return {@code true} if this icon should not be displayed, false otherwise. 10069 * The default value is {@code false} if this was never set. 10070 */ 10071 @Deprecated getHintHideIcon()10072 public boolean getHintHideIcon() { 10073 return (mFlags & FLAG_HINT_HIDE_ICON) != 0; 10074 } 10075 10076 /** 10077 * Set a visual hint that only the background image of this notification should be 10078 * displayed, and other semantic content should be hidden. This hint is only applicable 10079 * to sub-pages added using {@link #addPage}. 10080 */ 10081 @Deprecated setHintShowBackgroundOnly(boolean hintShowBackgroundOnly)10082 public WearableExtender setHintShowBackgroundOnly(boolean hintShowBackgroundOnly) { 10083 setFlag(FLAG_HINT_SHOW_BACKGROUND_ONLY, hintShowBackgroundOnly); 10084 return this; 10085 } 10086 10087 /** 10088 * Get a visual hint that only the background image of this notification should be 10089 * displayed, and other semantic content should be hidden. This hint is only applicable 10090 * to sub-pages added using {@link android.app.Notification.WearableExtender#addPage}. 10091 */ 10092 @Deprecated getHintShowBackgroundOnly()10093 public boolean getHintShowBackgroundOnly() { 10094 return (mFlags & FLAG_HINT_SHOW_BACKGROUND_ONLY) != 0; 10095 } 10096 10097 /** 10098 * Set a hint that this notification's background should not be clipped if possible, 10099 * and should instead be resized to fully display on the screen, retaining the aspect 10100 * ratio of the image. This can be useful for images like barcodes or qr codes. 10101 * @param hintAvoidBackgroundClipping {@code true} to avoid clipping if possible. 10102 * @return this object for method chaining 10103 */ 10104 @Deprecated setHintAvoidBackgroundClipping( boolean hintAvoidBackgroundClipping)10105 public WearableExtender setHintAvoidBackgroundClipping( 10106 boolean hintAvoidBackgroundClipping) { 10107 setFlag(FLAG_HINT_AVOID_BACKGROUND_CLIPPING, hintAvoidBackgroundClipping); 10108 return this; 10109 } 10110 10111 /** 10112 * Get a hint that this notification's background should not be clipped if possible, 10113 * and should instead be resized to fully display on the screen, retaining the aspect 10114 * ratio of the image. This can be useful for images like barcodes or qr codes. 10115 * @return {@code true} if it's ok if the background is clipped on the screen, false 10116 * otherwise. The default value is {@code false} if this was never set. 10117 */ 10118 @Deprecated getHintAvoidBackgroundClipping()10119 public boolean getHintAvoidBackgroundClipping() { 10120 return (mFlags & FLAG_HINT_AVOID_BACKGROUND_CLIPPING) != 0; 10121 } 10122 10123 /** 10124 * Set a hint that the screen should remain on for at least this duration when 10125 * this notification is displayed on the screen. 10126 * @param timeout The requested screen timeout in milliseconds. Can also be either 10127 * {@link #SCREEN_TIMEOUT_SHORT} or {@link #SCREEN_TIMEOUT_LONG}. 10128 * @return this object for method chaining 10129 */ 10130 @Deprecated setHintScreenTimeout(int timeout)10131 public WearableExtender setHintScreenTimeout(int timeout) { 10132 mHintScreenTimeout = timeout; 10133 return this; 10134 } 10135 10136 /** 10137 * Get the duration, in milliseconds, that the screen should remain on for 10138 * when this notification is displayed. 10139 * @return the duration in milliseconds if > 0, or either one of the sentinel values 10140 * {@link #SCREEN_TIMEOUT_SHORT} or {@link #SCREEN_TIMEOUT_LONG}. 10141 */ 10142 @Deprecated getHintScreenTimeout()10143 public int getHintScreenTimeout() { 10144 return mHintScreenTimeout; 10145 } 10146 10147 /** 10148 * Set a hint that this notification's {@link BigPictureStyle} (if present) should be 10149 * converted to low-bit and displayed in ambient mode, especially useful for barcodes and 10150 * qr codes, as well as other simple black-and-white tickets. 10151 * @param hintAmbientBigPicture {@code true} to enable converstion and ambient. 10152 * @return this object for method chaining 10153 * @deprecated This feature is no longer supported. 10154 */ 10155 @Deprecated setHintAmbientBigPicture(boolean hintAmbientBigPicture)10156 public WearableExtender setHintAmbientBigPicture(boolean hintAmbientBigPicture) { 10157 setFlag(FLAG_BIG_PICTURE_AMBIENT, hintAmbientBigPicture); 10158 return this; 10159 } 10160 10161 /** 10162 * Get a hint that this notification's {@link BigPictureStyle} (if present) should be 10163 * converted to low-bit and displayed in ambient mode, especially useful for barcodes and 10164 * qr codes, as well as other simple black-and-white tickets. 10165 * @return {@code true} if it should be displayed in ambient, false otherwise 10166 * otherwise. The default value is {@code false} if this was never set. 10167 * @deprecated This feature is no longer supported. 10168 */ 10169 @Deprecated getHintAmbientBigPicture()10170 public boolean getHintAmbientBigPicture() { 10171 return (mFlags & FLAG_BIG_PICTURE_AMBIENT) != 0; 10172 } 10173 10174 /** 10175 * Set a hint that this notification's content intent will launch an {@link Activity} 10176 * directly, telling the platform that it can generate the appropriate transitions. 10177 * @param hintContentIntentLaunchesActivity {@code true} if the content intent will launch 10178 * an activity and transitions should be generated, false otherwise. 10179 * @return this object for method chaining 10180 */ setHintContentIntentLaunchesActivity( boolean hintContentIntentLaunchesActivity)10181 public WearableExtender setHintContentIntentLaunchesActivity( 10182 boolean hintContentIntentLaunchesActivity) { 10183 setFlag(FLAG_HINT_CONTENT_INTENT_LAUNCHES_ACTIVITY, hintContentIntentLaunchesActivity); 10184 return this; 10185 } 10186 10187 /** 10188 * Get a hint that this notification's content intent will launch an {@link Activity} 10189 * directly, telling the platform that it can generate the appropriate transitions 10190 * @return {@code true} if the content intent will launch an activity and transitions should 10191 * be generated, false otherwise. The default value is {@code false} if this was never set. 10192 */ getHintContentIntentLaunchesActivity()10193 public boolean getHintContentIntentLaunchesActivity() { 10194 return (mFlags & FLAG_HINT_CONTENT_INTENT_LAUNCHES_ACTIVITY) != 0; 10195 } 10196 10197 /** 10198 * Sets the dismissal id for this notification. If a notification is posted with a 10199 * dismissal id, then when that notification is canceled, notifications on other wearables 10200 * and the paired Android phone having that same dismissal id will also be canceled. See 10201 * <a href="{@docRoot}wear/notifications/index.html">Adding Wearable Features to 10202 * Notifications</a> for more information. 10203 * @param dismissalId the dismissal id of the notification. 10204 * @return this object for method chaining 10205 */ setDismissalId(String dismissalId)10206 public WearableExtender setDismissalId(String dismissalId) { 10207 mDismissalId = dismissalId; 10208 return this; 10209 } 10210 10211 /** 10212 * Returns the dismissal id of the notification. 10213 * @return the dismissal id of the notification or null if it has not been set. 10214 */ getDismissalId()10215 public String getDismissalId() { 10216 return mDismissalId; 10217 } 10218 10219 /** 10220 * Sets a bridge tag for this notification. A bridge tag can be set for notifications 10221 * posted from a phone to provide finer-grained control on what notifications are bridged 10222 * to wearables. See <a href="{@docRoot}wear/notifications/index.html">Adding Wearable 10223 * Features to Notifications</a> for more information. 10224 * @param bridgeTag the bridge tag of the notification. 10225 * @return this object for method chaining 10226 */ setBridgeTag(String bridgeTag)10227 public WearableExtender setBridgeTag(String bridgeTag) { 10228 mBridgeTag = bridgeTag; 10229 return this; 10230 } 10231 10232 /** 10233 * Returns the bridge tag of the notification. 10234 * @return the bridge tag or null if not present. 10235 */ getBridgeTag()10236 public String getBridgeTag() { 10237 return mBridgeTag; 10238 } 10239 setFlag(int mask, boolean value)10240 private void setFlag(int mask, boolean value) { 10241 if (value) { 10242 mFlags |= mask; 10243 } else { 10244 mFlags &= ~mask; 10245 } 10246 } 10247 } 10248 10249 /** 10250 * <p>Helper class to add Android Auto extensions to notifications. To create a notification 10251 * with car extensions: 10252 * 10253 * <ol> 10254 * <li>Create an {@link Notification.Builder}, setting any desired 10255 * properties. 10256 * <li>Create a {@link CarExtender}. 10257 * <li>Set car-specific properties using the {@code add} and {@code set} methods of 10258 * {@link CarExtender}. 10259 * <li>Call {@link Notification.Builder#extend(Notification.Extender)} 10260 * to apply the extensions to a notification. 10261 * </ol> 10262 * 10263 * <pre class="prettyprint"> 10264 * Notification notification = new Notification.Builder(context) 10265 * ... 10266 * .extend(new CarExtender() 10267 * .set*(...)) 10268 * .build(); 10269 * </pre> 10270 * 10271 * <p>Car extensions can be accessed on an existing notification by using the 10272 * {@code CarExtender(Notification)} constructor, and then using the {@code get} methods 10273 * to access values. 10274 */ 10275 public static final class CarExtender implements Extender { 10276 private static final String TAG = "CarExtender"; 10277 10278 private static final String EXTRA_CAR_EXTENDER = "android.car.EXTENSIONS"; 10279 private static final String EXTRA_LARGE_ICON = "large_icon"; 10280 private static final String EXTRA_CONVERSATION = "car_conversation"; 10281 private static final String EXTRA_COLOR = "app_color"; 10282 10283 private Bitmap mLargeIcon; 10284 private UnreadConversation mUnreadConversation; 10285 private int mColor = Notification.COLOR_DEFAULT; 10286 10287 /** 10288 * Create a {@link CarExtender} with default options. 10289 */ CarExtender()10290 public CarExtender() { 10291 } 10292 10293 /** 10294 * Create a {@link CarExtender} from the CarExtender options of an existing Notification. 10295 * 10296 * @param notif The notification from which to copy options. 10297 */ CarExtender(Notification notif)10298 public CarExtender(Notification notif) { 10299 Bundle carBundle = notif.extras == null ? 10300 null : notif.extras.getBundle(EXTRA_CAR_EXTENDER); 10301 if (carBundle != null) { 10302 mLargeIcon = carBundle.getParcelable(EXTRA_LARGE_ICON); 10303 mColor = carBundle.getInt(EXTRA_COLOR, Notification.COLOR_DEFAULT); 10304 10305 Bundle b = carBundle.getBundle(EXTRA_CONVERSATION); 10306 mUnreadConversation = UnreadConversation.getUnreadConversationFromBundle(b); 10307 } 10308 } 10309 10310 /** 10311 * Apply car extensions to a notification that is being built. This is typically called by 10312 * the {@link Notification.Builder#extend(Notification.Extender)} 10313 * method of {@link Notification.Builder}. 10314 */ 10315 @Override extend(Notification.Builder builder)10316 public Notification.Builder extend(Notification.Builder builder) { 10317 Bundle carExtensions = new Bundle(); 10318 10319 if (mLargeIcon != null) { 10320 carExtensions.putParcelable(EXTRA_LARGE_ICON, mLargeIcon); 10321 } 10322 if (mColor != Notification.COLOR_DEFAULT) { 10323 carExtensions.putInt(EXTRA_COLOR, mColor); 10324 } 10325 10326 if (mUnreadConversation != null) { 10327 Bundle b = mUnreadConversation.getBundleForUnreadConversation(); 10328 carExtensions.putBundle(EXTRA_CONVERSATION, b); 10329 } 10330 10331 builder.getExtras().putBundle(EXTRA_CAR_EXTENDER, carExtensions); 10332 return builder; 10333 } 10334 10335 /** 10336 * Sets the accent color to use when Android Auto presents the notification. 10337 * 10338 * Android Auto uses the color set with {@link Notification.Builder#setColor(int)} 10339 * to accent the displayed notification. However, not all colors are acceptable in an 10340 * automotive setting. This method can be used to override the color provided in the 10341 * notification in such a situation. 10342 */ setColor(@olorInt int color)10343 public CarExtender setColor(@ColorInt int color) { 10344 mColor = color; 10345 return this; 10346 } 10347 10348 /** 10349 * Gets the accent color. 10350 * 10351 * @see #setColor 10352 */ 10353 @ColorInt getColor()10354 public int getColor() { 10355 return mColor; 10356 } 10357 10358 /** 10359 * Sets the large icon of the car notification. 10360 * 10361 * If no large icon is set in the extender, Android Auto will display the icon 10362 * specified by {@link Notification.Builder#setLargeIcon(android.graphics.Bitmap)} 10363 * 10364 * @param largeIcon The large icon to use in the car notification. 10365 * @return This object for method chaining. 10366 */ setLargeIcon(Bitmap largeIcon)10367 public CarExtender setLargeIcon(Bitmap largeIcon) { 10368 mLargeIcon = largeIcon; 10369 return this; 10370 } 10371 10372 /** 10373 * Gets the large icon used in this car notification, or null if no icon has been set. 10374 * 10375 * @return The large icon for the car notification. 10376 * @see CarExtender#setLargeIcon 10377 */ getLargeIcon()10378 public Bitmap getLargeIcon() { 10379 return mLargeIcon; 10380 } 10381 10382 /** 10383 * Sets the unread conversation in a message notification. 10384 * 10385 * @param unreadConversation The unread part of the conversation this notification conveys. 10386 * @return This object for method chaining. 10387 */ setUnreadConversation(UnreadConversation unreadConversation)10388 public CarExtender setUnreadConversation(UnreadConversation unreadConversation) { 10389 mUnreadConversation = unreadConversation; 10390 return this; 10391 } 10392 10393 /** 10394 * Returns the unread conversation conveyed by this notification. 10395 * @see #setUnreadConversation(UnreadConversation) 10396 */ getUnreadConversation()10397 public UnreadConversation getUnreadConversation() { 10398 return mUnreadConversation; 10399 } 10400 10401 /** 10402 * A class which holds the unread messages from a conversation. 10403 */ 10404 public static class UnreadConversation { 10405 private static final String KEY_AUTHOR = "author"; 10406 private static final String KEY_TEXT = "text"; 10407 private static final String KEY_MESSAGES = "messages"; 10408 private static final String KEY_REMOTE_INPUT = "remote_input"; 10409 private static final String KEY_ON_REPLY = "on_reply"; 10410 private static final String KEY_ON_READ = "on_read"; 10411 private static final String KEY_PARTICIPANTS = "participants"; 10412 private static final String KEY_TIMESTAMP = "timestamp"; 10413 10414 private final String[] mMessages; 10415 private final RemoteInput mRemoteInput; 10416 private final PendingIntent mReplyPendingIntent; 10417 private final PendingIntent mReadPendingIntent; 10418 private final String[] mParticipants; 10419 private final long mLatestTimestamp; 10420 UnreadConversation(String[] messages, RemoteInput remoteInput, PendingIntent replyPendingIntent, PendingIntent readPendingIntent, String[] participants, long latestTimestamp)10421 UnreadConversation(String[] messages, RemoteInput remoteInput, 10422 PendingIntent replyPendingIntent, PendingIntent readPendingIntent, 10423 String[] participants, long latestTimestamp) { 10424 mMessages = messages; 10425 mRemoteInput = remoteInput; 10426 mReadPendingIntent = readPendingIntent; 10427 mReplyPendingIntent = replyPendingIntent; 10428 mParticipants = participants; 10429 mLatestTimestamp = latestTimestamp; 10430 } 10431 10432 /** 10433 * Gets the list of messages conveyed by this notification. 10434 */ getMessages()10435 public String[] getMessages() { 10436 return mMessages; 10437 } 10438 10439 /** 10440 * Gets the remote input that will be used to convey the response to a message list, or 10441 * null if no such remote input exists. 10442 */ getRemoteInput()10443 public RemoteInput getRemoteInput() { 10444 return mRemoteInput; 10445 } 10446 10447 /** 10448 * Gets the pending intent that will be triggered when the user replies to this 10449 * notification. 10450 */ getReplyPendingIntent()10451 public PendingIntent getReplyPendingIntent() { 10452 return mReplyPendingIntent; 10453 } 10454 10455 /** 10456 * Gets the pending intent that Android Auto will send after it reads aloud all messages 10457 * in this object's message list. 10458 */ getReadPendingIntent()10459 public PendingIntent getReadPendingIntent() { 10460 return mReadPendingIntent; 10461 } 10462 10463 /** 10464 * Gets the participants in the conversation. 10465 */ getParticipants()10466 public String[] getParticipants() { 10467 return mParticipants; 10468 } 10469 10470 /** 10471 * Gets the firs participant in the conversation. 10472 */ getParticipant()10473 public String getParticipant() { 10474 return mParticipants.length > 0 ? mParticipants[0] : null; 10475 } 10476 10477 /** 10478 * Gets the timestamp of the conversation. 10479 */ getLatestTimestamp()10480 public long getLatestTimestamp() { 10481 return mLatestTimestamp; 10482 } 10483 getBundleForUnreadConversation()10484 Bundle getBundleForUnreadConversation() { 10485 Bundle b = new Bundle(); 10486 String author = null; 10487 if (mParticipants != null && mParticipants.length > 1) { 10488 author = mParticipants[0]; 10489 } 10490 Parcelable[] messages = new Parcelable[mMessages.length]; 10491 for (int i = 0; i < messages.length; i++) { 10492 Bundle m = new Bundle(); 10493 m.putString(KEY_TEXT, mMessages[i]); 10494 m.putString(KEY_AUTHOR, author); 10495 messages[i] = m; 10496 } 10497 b.putParcelableArray(KEY_MESSAGES, messages); 10498 if (mRemoteInput != null) { 10499 b.putParcelable(KEY_REMOTE_INPUT, mRemoteInput); 10500 } 10501 b.putParcelable(KEY_ON_REPLY, mReplyPendingIntent); 10502 b.putParcelable(KEY_ON_READ, mReadPendingIntent); 10503 b.putStringArray(KEY_PARTICIPANTS, mParticipants); 10504 b.putLong(KEY_TIMESTAMP, mLatestTimestamp); 10505 return b; 10506 } 10507 getUnreadConversationFromBundle(Bundle b)10508 static UnreadConversation getUnreadConversationFromBundle(Bundle b) { 10509 if (b == null) { 10510 return null; 10511 } 10512 Parcelable[] parcelableMessages = b.getParcelableArray(KEY_MESSAGES); 10513 String[] messages = null; 10514 if (parcelableMessages != null) { 10515 String[] tmp = new String[parcelableMessages.length]; 10516 boolean success = true; 10517 for (int i = 0; i < tmp.length; i++) { 10518 if (!(parcelableMessages[i] instanceof Bundle)) { 10519 success = false; 10520 break; 10521 } 10522 tmp[i] = ((Bundle) parcelableMessages[i]).getString(KEY_TEXT); 10523 if (tmp[i] == null) { 10524 success = false; 10525 break; 10526 } 10527 } 10528 if (success) { 10529 messages = tmp; 10530 } else { 10531 return null; 10532 } 10533 } 10534 10535 PendingIntent onRead = b.getParcelable(KEY_ON_READ); 10536 PendingIntent onReply = b.getParcelable(KEY_ON_REPLY); 10537 10538 RemoteInput remoteInput = b.getParcelable(KEY_REMOTE_INPUT); 10539 10540 String[] participants = b.getStringArray(KEY_PARTICIPANTS); 10541 if (participants == null || participants.length != 1) { 10542 return null; 10543 } 10544 10545 return new UnreadConversation(messages, 10546 remoteInput, 10547 onReply, 10548 onRead, 10549 participants, b.getLong(KEY_TIMESTAMP)); 10550 } 10551 }; 10552 10553 /** 10554 * Builder class for {@link CarExtender.UnreadConversation} objects. 10555 */ 10556 public static class Builder { 10557 private final List<String> mMessages = new ArrayList<String>(); 10558 private final String mParticipant; 10559 private RemoteInput mRemoteInput; 10560 private PendingIntent mReadPendingIntent; 10561 private PendingIntent mReplyPendingIntent; 10562 private long mLatestTimestamp; 10563 10564 /** 10565 * Constructs a new builder for {@link CarExtender.UnreadConversation}. 10566 * 10567 * @param name The name of the other participant in the conversation. 10568 */ Builder(String name)10569 public Builder(String name) { 10570 mParticipant = name; 10571 } 10572 10573 /** 10574 * Appends a new unread message to the list of messages for this conversation. 10575 * 10576 * The messages should be added from oldest to newest. 10577 * 10578 * @param message The text of the new unread message. 10579 * @return This object for method chaining. 10580 */ addMessage(String message)10581 public Builder addMessage(String message) { 10582 mMessages.add(message); 10583 return this; 10584 } 10585 10586 /** 10587 * Sets the pending intent and remote input which will convey the reply to this 10588 * notification. 10589 * 10590 * @param pendingIntent The pending intent which will be triggered on a reply. 10591 * @param remoteInput The remote input parcelable which will carry the reply. 10592 * @return This object for method chaining. 10593 * 10594 * @see CarExtender.UnreadConversation#getRemoteInput 10595 * @see CarExtender.UnreadConversation#getReplyPendingIntent 10596 */ setReplyAction( PendingIntent pendingIntent, RemoteInput remoteInput)10597 public Builder setReplyAction( 10598 PendingIntent pendingIntent, RemoteInput remoteInput) { 10599 mRemoteInput = remoteInput; 10600 mReplyPendingIntent = pendingIntent; 10601 10602 return this; 10603 } 10604 10605 /** 10606 * Sets the pending intent that will be sent once the messages in this notification 10607 * are read. 10608 * 10609 * @param pendingIntent The pending intent to use. 10610 * @return This object for method chaining. 10611 */ setReadPendingIntent(PendingIntent pendingIntent)10612 public Builder setReadPendingIntent(PendingIntent pendingIntent) { 10613 mReadPendingIntent = pendingIntent; 10614 return this; 10615 } 10616 10617 /** 10618 * Sets the timestamp of the most recent message in an unread conversation. 10619 * 10620 * If a messaging notification has been posted by your application and has not 10621 * yet been cancelled, posting a later notification with the same id and tag 10622 * but without a newer timestamp may result in Android Auto not displaying a 10623 * heads up notification for the later notification. 10624 * 10625 * @param timestamp The timestamp of the most recent message in the conversation. 10626 * @return This object for method chaining. 10627 */ setLatestTimestamp(long timestamp)10628 public Builder setLatestTimestamp(long timestamp) { 10629 mLatestTimestamp = timestamp; 10630 return this; 10631 } 10632 10633 /** 10634 * Builds a new unread conversation object. 10635 * 10636 * @return The new unread conversation object. 10637 */ build()10638 public UnreadConversation build() { 10639 String[] messages = mMessages.toArray(new String[mMessages.size()]); 10640 String[] participants = { mParticipant }; 10641 return new UnreadConversation(messages, mRemoteInput, mReplyPendingIntent, 10642 mReadPendingIntent, participants, mLatestTimestamp); 10643 } 10644 } 10645 } 10646 10647 /** 10648 * <p>Helper class to add Android TV extensions to notifications. To create a notification 10649 * with a TV extension: 10650 * 10651 * <ol> 10652 * <li>Create an {@link Notification.Builder}, setting any desired properties. 10653 * <li>Create a {@link TvExtender}. 10654 * <li>Set TV-specific properties using the {@code set} methods of 10655 * {@link TvExtender}. 10656 * <li>Call {@link Notification.Builder#extend(Notification.Extender)} 10657 * to apply the extension to a notification. 10658 * </ol> 10659 * 10660 * <pre class="prettyprint"> 10661 * Notification notification = new Notification.Builder(context) 10662 * ... 10663 * .extend(new TvExtender() 10664 * .set*(...)) 10665 * .build(); 10666 * </pre> 10667 * 10668 * <p>TV extensions can be accessed on an existing notification by using the 10669 * {@code TvExtender(Notification)} constructor, and then using the {@code get} methods 10670 * to access values. 10671 * 10672 * @hide 10673 */ 10674 @SystemApi 10675 public static final class TvExtender implements Extender { 10676 private static final String TAG = "TvExtender"; 10677 10678 private static final String EXTRA_TV_EXTENDER = "android.tv.EXTENSIONS"; 10679 private static final String EXTRA_FLAGS = "flags"; 10680 private static final String EXTRA_CONTENT_INTENT = "content_intent"; 10681 private static final String EXTRA_DELETE_INTENT = "delete_intent"; 10682 private static final String EXTRA_CHANNEL_ID = "channel_id"; 10683 private static final String EXTRA_SUPPRESS_SHOW_OVER_APPS = "suppressShowOverApps"; 10684 10685 // Flags bitwise-ored to mFlags 10686 private static final int FLAG_AVAILABLE_ON_TV = 0x1; 10687 10688 private int mFlags; 10689 private String mChannelId; 10690 private PendingIntent mContentIntent; 10691 private PendingIntent mDeleteIntent; 10692 private boolean mSuppressShowOverApps; 10693 10694 /** 10695 * Create a {@link TvExtender} with default options. 10696 */ TvExtender()10697 public TvExtender() { 10698 mFlags = FLAG_AVAILABLE_ON_TV; 10699 } 10700 10701 /** 10702 * Create a {@link TvExtender} from the TvExtender options of an existing Notification. 10703 * 10704 * @param notif The notification from which to copy options. 10705 */ TvExtender(Notification notif)10706 public TvExtender(Notification notif) { 10707 Bundle bundle = notif.extras == null ? 10708 null : notif.extras.getBundle(EXTRA_TV_EXTENDER); 10709 if (bundle != null) { 10710 mFlags = bundle.getInt(EXTRA_FLAGS); 10711 mChannelId = bundle.getString(EXTRA_CHANNEL_ID); 10712 mSuppressShowOverApps = bundle.getBoolean(EXTRA_SUPPRESS_SHOW_OVER_APPS); 10713 mContentIntent = bundle.getParcelable(EXTRA_CONTENT_INTENT); 10714 mDeleteIntent = bundle.getParcelable(EXTRA_DELETE_INTENT); 10715 } 10716 } 10717 10718 /** 10719 * Apply a TV extension to a notification that is being built. This is typically called by 10720 * the {@link Notification.Builder#extend(Notification.Extender)} 10721 * method of {@link Notification.Builder}. 10722 */ 10723 @Override extend(Notification.Builder builder)10724 public Notification.Builder extend(Notification.Builder builder) { 10725 Bundle bundle = new Bundle(); 10726 10727 bundle.putInt(EXTRA_FLAGS, mFlags); 10728 bundle.putString(EXTRA_CHANNEL_ID, mChannelId); 10729 bundle.putBoolean(EXTRA_SUPPRESS_SHOW_OVER_APPS, mSuppressShowOverApps); 10730 if (mContentIntent != null) { 10731 bundle.putParcelable(EXTRA_CONTENT_INTENT, mContentIntent); 10732 } 10733 10734 if (mDeleteIntent != null) { 10735 bundle.putParcelable(EXTRA_DELETE_INTENT, mDeleteIntent); 10736 } 10737 10738 builder.getExtras().putBundle(EXTRA_TV_EXTENDER, bundle); 10739 return builder; 10740 } 10741 10742 /** 10743 * Returns true if this notification should be shown on TV. This method return true 10744 * if the notification was extended with a TvExtender. 10745 */ isAvailableOnTv()10746 public boolean isAvailableOnTv() { 10747 return (mFlags & FLAG_AVAILABLE_ON_TV) != 0; 10748 } 10749 10750 /** 10751 * Specifies the channel the notification should be delivered on when shown on TV. 10752 * It can be different from the channel that the notification is delivered to when 10753 * posting on a non-TV device. 10754 */ setChannel(String channelId)10755 public TvExtender setChannel(String channelId) { 10756 mChannelId = channelId; 10757 return this; 10758 } 10759 10760 /** 10761 * Specifies the channel the notification should be delivered on when shown on TV. 10762 * It can be different from the channel that the notification is delivered to when 10763 * posting on a non-TV device. 10764 */ setChannelId(String channelId)10765 public TvExtender setChannelId(String channelId) { 10766 mChannelId = channelId; 10767 return this; 10768 } 10769 10770 /** @removed */ 10771 @Deprecated getChannel()10772 public String getChannel() { 10773 return mChannelId; 10774 } 10775 10776 /** 10777 * Returns the id of the channel this notification posts to on TV. 10778 */ getChannelId()10779 public String getChannelId() { 10780 return mChannelId; 10781 } 10782 10783 /** 10784 * Supplies a {@link PendingIntent} to be sent when the notification is selected on TV. 10785 * If provided, it is used instead of the content intent specified 10786 * at the level of Notification. 10787 */ setContentIntent(PendingIntent intent)10788 public TvExtender setContentIntent(PendingIntent intent) { 10789 mContentIntent = intent; 10790 return this; 10791 } 10792 10793 /** 10794 * Returns the TV-specific content intent. If this method returns null, the 10795 * main content intent on the notification should be used. 10796 * 10797 * @see {@link Notification#contentIntent} 10798 */ getContentIntent()10799 public PendingIntent getContentIntent() { 10800 return mContentIntent; 10801 } 10802 10803 /** 10804 * Supplies a {@link PendingIntent} to send when the notification is cleared explicitly 10805 * by the user on TV. If provided, it is used instead of the delete intent specified 10806 * at the level of Notification. 10807 */ setDeleteIntent(PendingIntent intent)10808 public TvExtender setDeleteIntent(PendingIntent intent) { 10809 mDeleteIntent = intent; 10810 return this; 10811 } 10812 10813 /** 10814 * Returns the TV-specific delete intent. If this method returns null, the 10815 * main delete intent on the notification should be used. 10816 * 10817 * @see {@link Notification#deleteIntent} 10818 */ getDeleteIntent()10819 public PendingIntent getDeleteIntent() { 10820 return mDeleteIntent; 10821 } 10822 10823 /** 10824 * Specifies whether this notification should suppress showing a message over top of apps 10825 * outside of the launcher. 10826 */ setSuppressShowOverApps(boolean suppress)10827 public TvExtender setSuppressShowOverApps(boolean suppress) { 10828 mSuppressShowOverApps = suppress; 10829 return this; 10830 } 10831 10832 /** 10833 * Returns true if this notification should not show messages over top of apps 10834 * outside of the launcher. 10835 */ getSuppressShowOverApps()10836 public boolean getSuppressShowOverApps() { 10837 return mSuppressShowOverApps; 10838 } 10839 } 10840 10841 /** 10842 * Get an array of Parcelable objects from a parcelable array bundle field. 10843 * Update the bundle to have a typed array so fetches in the future don't need 10844 * to do an array copy. 10845 */ 10846 @Nullable getParcelableArrayFromBundle( Bundle bundle, String key, Class<T> itemClass)10847 private static <T extends Parcelable> T[] getParcelableArrayFromBundle( 10848 Bundle bundle, String key, Class<T> itemClass) { 10849 final Parcelable[] array = bundle.getParcelableArray(key); 10850 final Class<?> arrayClass = Array.newInstance(itemClass, 0).getClass(); 10851 if (arrayClass.isInstance(array) || array == null) { 10852 return (T[]) array; 10853 } 10854 final T[] typedArray = (T[]) Array.newInstance(itemClass, array.length); 10855 for (int i = 0; i < array.length; i++) { 10856 typedArray[i] = (T) array[i]; 10857 } 10858 bundle.putParcelableArray(key, typedArray); 10859 return typedArray; 10860 } 10861 10862 private static class BuilderRemoteViews extends RemoteViews { BuilderRemoteViews(Parcel parcel)10863 public BuilderRemoteViews(Parcel parcel) { 10864 super(parcel); 10865 } 10866 BuilderRemoteViews(ApplicationInfo appInfo, int layoutId)10867 public BuilderRemoteViews(ApplicationInfo appInfo, int layoutId) { 10868 super(appInfo, layoutId); 10869 } 10870 10871 @Override clone()10872 public BuilderRemoteViews clone() { 10873 Parcel p = Parcel.obtain(); 10874 writeToParcel(p, 0); 10875 p.setDataPosition(0); 10876 BuilderRemoteViews brv = new BuilderRemoteViews(p); 10877 p.recycle(); 10878 return brv; 10879 } 10880 10881 /** 10882 * Override and return true, since {@link RemoteViews#onLoadClass(Class)} is not overridden. 10883 * 10884 * @see RemoteViews#shouldUseStaticFilter() 10885 */ 10886 @Override shouldUseStaticFilter()10887 protected boolean shouldUseStaticFilter() { 10888 return true; 10889 } 10890 } 10891 10892 /** 10893 * A result object where information about the template that was created is saved. 10894 */ 10895 private static class TemplateBindResult { 10896 int mIconMarginEnd; 10897 boolean mRightIconContainerVisible; 10898 10899 /** 10900 * Get the margin end that needs to be added to any fields that may overlap 10901 * with the right actions. 10902 */ getIconMarginEnd()10903 public int getIconMarginEnd() { 10904 return mIconMarginEnd; 10905 } 10906 10907 /** 10908 * Is the icon container visible on the right size because of the reply button or the 10909 * right icon. 10910 */ isRightIconContainerVisible()10911 public boolean isRightIconContainerVisible() { 10912 return mRightIconContainerVisible; 10913 } 10914 setIconMarginEnd(int iconMarginEnd)10915 public void setIconMarginEnd(int iconMarginEnd) { 10916 this.mIconMarginEnd = iconMarginEnd; 10917 } 10918 setRightIconContainerVisible(boolean iconContainerVisible)10919 public void setRightIconContainerVisible(boolean iconContainerVisible) { 10920 mRightIconContainerVisible = iconContainerVisible; 10921 } 10922 } 10923 10924 private static class StandardTemplateParams { 10925 boolean hasProgress = true; 10926 CharSequence title; 10927 CharSequence text; 10928 CharSequence headerTextSecondary; 10929 CharSequence summaryText; 10930 int maxRemoteInputHistory = Style.MAX_REMOTE_INPUT_HISTORY_LINES; 10931 boolean hideLargeIcon; 10932 boolean hideReplyIcon; 10933 boolean allowColorization = true; 10934 boolean forceDefaultColor = false; 10935 reset()10936 final StandardTemplateParams reset() { 10937 hasProgress = true; 10938 title = null; 10939 text = null; 10940 summaryText = null; 10941 headerTextSecondary = null; 10942 maxRemoteInputHistory = Style.MAX_REMOTE_INPUT_HISTORY_LINES; 10943 allowColorization = true; 10944 forceDefaultColor = false; 10945 return this; 10946 } 10947 hasProgress(boolean hasProgress)10948 final StandardTemplateParams hasProgress(boolean hasProgress) { 10949 this.hasProgress = hasProgress; 10950 return this; 10951 } 10952 title(CharSequence title)10953 final StandardTemplateParams title(CharSequence title) { 10954 this.title = title; 10955 return this; 10956 } 10957 text(CharSequence text)10958 final StandardTemplateParams text(CharSequence text) { 10959 this.text = text; 10960 return this; 10961 } 10962 summaryText(CharSequence text)10963 final StandardTemplateParams summaryText(CharSequence text) { 10964 this.summaryText = text; 10965 return this; 10966 } 10967 headerTextSecondary(CharSequence text)10968 final StandardTemplateParams headerTextSecondary(CharSequence text) { 10969 this.headerTextSecondary = text; 10970 return this; 10971 } 10972 hideLargeIcon(boolean hideLargeIcon)10973 final StandardTemplateParams hideLargeIcon(boolean hideLargeIcon) { 10974 this.hideLargeIcon = hideLargeIcon; 10975 return this; 10976 } 10977 hideReplyIcon(boolean hideReplyIcon)10978 final StandardTemplateParams hideReplyIcon(boolean hideReplyIcon) { 10979 this.hideReplyIcon = hideReplyIcon; 10980 return this; 10981 } 10982 disallowColorization()10983 final StandardTemplateParams disallowColorization() { 10984 this.allowColorization = false; 10985 return this; 10986 } 10987 forceDefaultColor()10988 final StandardTemplateParams forceDefaultColor() { 10989 this.forceDefaultColor = true; 10990 return this; 10991 } 10992 fillTextsFrom(Builder b)10993 final StandardTemplateParams fillTextsFrom(Builder b) { 10994 Bundle extras = b.mN.extras; 10995 this.title = b.processLegacyText(extras.getCharSequence(EXTRA_TITLE)); 10996 this.text = b.processLegacyText(extras.getCharSequence(EXTRA_TEXT)); 10997 this.summaryText = extras.getCharSequence(EXTRA_SUB_TEXT); 10998 return this; 10999 } 11000 11001 /** 11002 * Set the maximum lines of remote input history lines allowed. 11003 * @param maxRemoteInputHistory The number of lines. 11004 * @return The builder for method chaining. 11005 */ setMaxRemoteInputHistory(int maxRemoteInputHistory)11006 public StandardTemplateParams setMaxRemoteInputHistory(int maxRemoteInputHistory) { 11007 this.maxRemoteInputHistory = maxRemoteInputHistory; 11008 return this; 11009 } 11010 } 11011 } 11012