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 = 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 * Sets the token used for background operations for the pending intents associated with this 3033 * notification. 3034 * 3035 * This token is automatically set during deserialization for you, you usually won't need to 3036 * call this unless you want to change the existing token, if any. 3037 * 3038 * @hide 3039 */ setAllowlistToken(@ullable IBinder token)3040 public void setAllowlistToken(@Nullable IBinder token) { 3041 mWhitelistToken = token; 3042 } 3043 3044 /** 3045 * @hide 3046 */ addFieldsFromContext(Context context, Notification notification)3047 public static void addFieldsFromContext(Context context, Notification notification) { 3048 addFieldsFromContext(context.getApplicationInfo(), notification); 3049 } 3050 3051 /** 3052 * @hide 3053 */ addFieldsFromContext(ApplicationInfo ai, Notification notification)3054 public static void addFieldsFromContext(ApplicationInfo ai, Notification notification) { 3055 notification.extras.putParcelable(EXTRA_BUILDER_APPLICATION_INFO, ai); 3056 } 3057 3058 /** 3059 * @hide 3060 */ dumpDebug(ProtoOutputStream proto, long fieldId)3061 public void dumpDebug(ProtoOutputStream proto, long fieldId) { 3062 long token = proto.start(fieldId); 3063 proto.write(NotificationProto.CHANNEL_ID, getChannelId()); 3064 proto.write(NotificationProto.HAS_TICKER_TEXT, this.tickerText != null); 3065 proto.write(NotificationProto.FLAGS, this.flags); 3066 proto.write(NotificationProto.COLOR, this.color); 3067 proto.write(NotificationProto.CATEGORY, this.category); 3068 proto.write(NotificationProto.GROUP_KEY, this.mGroupKey); 3069 proto.write(NotificationProto.SORT_KEY, this.mSortKey); 3070 if (this.actions != null) { 3071 proto.write(NotificationProto.ACTION_LENGTH, this.actions.length); 3072 } 3073 if (this.visibility >= VISIBILITY_SECRET && this.visibility <= VISIBILITY_PUBLIC) { 3074 proto.write(NotificationProto.VISIBILITY, this.visibility); 3075 } 3076 if (publicVersion != null) { 3077 publicVersion.dumpDebug(proto, NotificationProto.PUBLIC_VERSION); 3078 } 3079 proto.end(token); 3080 } 3081 3082 @Override toString()3083 public String toString() { 3084 StringBuilder sb = new StringBuilder(); 3085 sb.append("Notification(channel="); 3086 sb.append(getChannelId()); 3087 sb.append(" shortcut="); 3088 sb.append(getShortcutId()); 3089 sb.append(" contentView="); 3090 if (contentView != null) { 3091 sb.append(contentView.getPackage()); 3092 sb.append("/0x"); 3093 sb.append(Integer.toHexString(contentView.getLayoutId())); 3094 } else { 3095 sb.append("null"); 3096 } 3097 sb.append(" vibrate="); 3098 if ((this.defaults & DEFAULT_VIBRATE) != 0) { 3099 sb.append("default"); 3100 } else if (this.vibrate != null) { 3101 int N = this.vibrate.length-1; 3102 sb.append("["); 3103 for (int i=0; i<N; i++) { 3104 sb.append(this.vibrate[i]); 3105 sb.append(','); 3106 } 3107 if (N != -1) { 3108 sb.append(this.vibrate[N]); 3109 } 3110 sb.append("]"); 3111 } else { 3112 sb.append("null"); 3113 } 3114 sb.append(" sound="); 3115 if ((this.defaults & DEFAULT_SOUND) != 0) { 3116 sb.append("default"); 3117 } else if (this.sound != null) { 3118 sb.append(this.sound.toString()); 3119 } else { 3120 sb.append("null"); 3121 } 3122 if (this.tickerText != null) { 3123 sb.append(" tick"); 3124 } 3125 sb.append(" defaults=0x"); 3126 sb.append(Integer.toHexString(this.defaults)); 3127 sb.append(" flags=0x"); 3128 sb.append(Integer.toHexString(this.flags)); 3129 sb.append(String.format(" color=0x%08x", this.color)); 3130 if (this.category != null) { 3131 sb.append(" category="); 3132 sb.append(this.category); 3133 } 3134 if (this.mGroupKey != null) { 3135 sb.append(" groupKey="); 3136 sb.append(this.mGroupKey); 3137 } 3138 if (this.mSortKey != null) { 3139 sb.append(" sortKey="); 3140 sb.append(this.mSortKey); 3141 } 3142 if (actions != null) { 3143 sb.append(" actions="); 3144 sb.append(actions.length); 3145 } 3146 sb.append(" vis="); 3147 sb.append(visibilityToString(this.visibility)); 3148 if (this.publicVersion != null) { 3149 sb.append(" publicVersion="); 3150 sb.append(publicVersion.toString()); 3151 } 3152 if (this.mLocusId != null) { 3153 sb.append(" locusId="); 3154 sb.append(this.mLocusId); // LocusId.toString() is PII safe. 3155 } 3156 sb.append(")"); 3157 return sb.toString(); 3158 } 3159 3160 /** 3161 * {@hide} 3162 */ visibilityToString(int vis)3163 public static String visibilityToString(int vis) { 3164 switch (vis) { 3165 case VISIBILITY_PRIVATE: 3166 return "PRIVATE"; 3167 case VISIBILITY_PUBLIC: 3168 return "PUBLIC"; 3169 case VISIBILITY_SECRET: 3170 return "SECRET"; 3171 default: 3172 return "UNKNOWN(" + String.valueOf(vis) + ")"; 3173 } 3174 } 3175 3176 /** 3177 * {@hide} 3178 */ priorityToString(@riority int pri)3179 public static String priorityToString(@Priority int pri) { 3180 switch (pri) { 3181 case PRIORITY_MIN: 3182 return "MIN"; 3183 case PRIORITY_LOW: 3184 return "LOW"; 3185 case PRIORITY_DEFAULT: 3186 return "DEFAULT"; 3187 case PRIORITY_HIGH: 3188 return "HIGH"; 3189 case PRIORITY_MAX: 3190 return "MAX"; 3191 default: 3192 return "UNKNOWN(" + String.valueOf(pri) + ")"; 3193 } 3194 } 3195 3196 /** 3197 * @hide 3198 */ hasCompletedProgress()3199 public boolean hasCompletedProgress() { 3200 // not a progress notification; can't be complete 3201 if (!extras.containsKey(EXTRA_PROGRESS) 3202 || !extras.containsKey(EXTRA_PROGRESS_MAX)) { 3203 return false; 3204 } 3205 // many apps use max 0 for 'indeterminate'; not complete 3206 if (extras.getInt(EXTRA_PROGRESS_MAX) == 0) { 3207 return false; 3208 } 3209 return extras.getInt(EXTRA_PROGRESS) == extras.getInt(EXTRA_PROGRESS_MAX); 3210 } 3211 3212 /** @removed */ 3213 @Deprecated getChannel()3214 public String getChannel() { 3215 return mChannelId; 3216 } 3217 3218 /** 3219 * Returns the id of the channel this notification posts to. 3220 */ getChannelId()3221 public String getChannelId() { 3222 return mChannelId; 3223 } 3224 3225 /** @removed */ 3226 @Deprecated getTimeout()3227 public long getTimeout() { 3228 return mTimeout; 3229 } 3230 3231 /** 3232 * Returns the duration from posting after which this notification should be canceled by the 3233 * system, if it's not canceled already. 3234 */ getTimeoutAfter()3235 public long getTimeoutAfter() { 3236 return mTimeout; 3237 } 3238 3239 /** 3240 * Returns what icon should be shown for this notification if it is being displayed in a 3241 * Launcher that supports badging. Will be one of {@link #BADGE_ICON_NONE}, 3242 * {@link #BADGE_ICON_SMALL}, or {@link #BADGE_ICON_LARGE}. 3243 */ getBadgeIconType()3244 public int getBadgeIconType() { 3245 return mBadgeIcon; 3246 } 3247 3248 /** 3249 * Returns the {@link ShortcutInfo#getId() id} that this notification supersedes, if any. 3250 * 3251 * <p>Used by some Launchers that display notification content to hide shortcuts that duplicate 3252 * notifications. 3253 */ getShortcutId()3254 public String getShortcutId() { 3255 return mShortcutId; 3256 } 3257 3258 /** 3259 * Gets the {@link LocusId} associated with this notification. 3260 * 3261 * <p>Used by the device's intelligence services to correlate objects (such as 3262 * {@link ShortcutInfo} and {@link ContentCaptureContext}) that are correlated. 3263 */ 3264 @Nullable getLocusId()3265 public LocusId getLocusId() { 3266 return mLocusId; 3267 } 3268 3269 /** 3270 * Returns the settings text provided to {@link Builder#setSettingsText(CharSequence)}. 3271 */ getSettingsText()3272 public CharSequence getSettingsText() { 3273 return mSettingsText; 3274 } 3275 3276 /** 3277 * Returns which type of notifications in a group are responsible for audibly alerting the 3278 * user. See {@link #GROUP_ALERT_ALL}, {@link #GROUP_ALERT_CHILDREN}, 3279 * {@link #GROUP_ALERT_SUMMARY}. 3280 */ getGroupAlertBehavior()3281 public @GroupAlertBehavior int getGroupAlertBehavior() { 3282 return mGroupAlertBehavior; 3283 } 3284 3285 /** 3286 * Returns the bubble metadata that will be used to display app content in a floating window 3287 * over the existing foreground activity. 3288 */ 3289 @Nullable getBubbleMetadata()3290 public BubbleMetadata getBubbleMetadata() { 3291 return mBubbleMetadata; 3292 } 3293 3294 /** 3295 * Sets the {@link BubbleMetadata} for this notification. 3296 * @hide 3297 */ setBubbleMetadata(BubbleMetadata data)3298 public void setBubbleMetadata(BubbleMetadata data) { 3299 mBubbleMetadata = data; 3300 } 3301 3302 /** 3303 * Returns whether the platform is allowed (by the app developer) to generate contextual actions 3304 * for this notification. 3305 */ getAllowSystemGeneratedContextualActions()3306 public boolean getAllowSystemGeneratedContextualActions() { 3307 return mAllowSystemGeneratedContextualActions; 3308 } 3309 3310 /** 3311 * The small icon representing this notification in the status bar and content view. 3312 * 3313 * @return the small icon representing this notification. 3314 * 3315 * @see Builder#getSmallIcon() 3316 * @see Builder#setSmallIcon(Icon) 3317 */ getSmallIcon()3318 public Icon getSmallIcon() { 3319 return mSmallIcon; 3320 } 3321 3322 /** 3323 * Used when notifying to clean up legacy small icons. 3324 * @hide 3325 */ 3326 @UnsupportedAppUsage setSmallIcon(Icon icon)3327 public void setSmallIcon(Icon icon) { 3328 mSmallIcon = icon; 3329 } 3330 3331 /** 3332 * The large icon shown in this notification's content view. 3333 * @see Builder#getLargeIcon() 3334 * @see Builder#setLargeIcon(Icon) 3335 */ getLargeIcon()3336 public Icon getLargeIcon() { 3337 return mLargeIcon; 3338 } 3339 3340 /** 3341 * @hide 3342 */ 3343 @UnsupportedAppUsage isGroupSummary()3344 public boolean isGroupSummary() { 3345 return mGroupKey != null && (flags & FLAG_GROUP_SUMMARY) != 0; 3346 } 3347 3348 /** 3349 * @hide 3350 */ 3351 @UnsupportedAppUsage isGroupChild()3352 public boolean isGroupChild() { 3353 return mGroupKey != null && (flags & FLAG_GROUP_SUMMARY) == 0; 3354 } 3355 3356 /** 3357 * @hide 3358 */ suppressAlertingDueToGrouping()3359 public boolean suppressAlertingDueToGrouping() { 3360 if (isGroupSummary() 3361 && getGroupAlertBehavior() == Notification.GROUP_ALERT_CHILDREN) { 3362 return true; 3363 } else if (isGroupChild() 3364 && getGroupAlertBehavior() == Notification.GROUP_ALERT_SUMMARY) { 3365 return true; 3366 } 3367 return false; 3368 } 3369 3370 3371 /** 3372 * Finds and returns a remote input and its corresponding action. 3373 * 3374 * @param requiresFreeform requires the remoteinput to allow freeform or not. 3375 * @return the result pair, {@code null} if no result is found. 3376 */ 3377 @Nullable findRemoteInputActionPair(boolean requiresFreeform)3378 public Pair<RemoteInput, Action> findRemoteInputActionPair(boolean requiresFreeform) { 3379 if (actions == null) { 3380 return null; 3381 } 3382 for (Notification.Action action : actions) { 3383 if (action.getRemoteInputs() == null) { 3384 continue; 3385 } 3386 RemoteInput resultRemoteInput = null; 3387 for (RemoteInput remoteInput : action.getRemoteInputs()) { 3388 if (remoteInput.getAllowFreeFormInput() || !requiresFreeform) { 3389 resultRemoteInput = remoteInput; 3390 } 3391 } 3392 if (resultRemoteInput != null) { 3393 return Pair.create(resultRemoteInput, action); 3394 } 3395 } 3396 return null; 3397 } 3398 3399 /** 3400 * Returns the actions that are contextual (that is, suggested because of the content of the 3401 * notification) out of the actions in this notification. 3402 */ getContextualActions()3403 public @NonNull List<Notification.Action> getContextualActions() { 3404 if (actions == null) return Collections.emptyList(); 3405 3406 List<Notification.Action> contextualActions = new ArrayList<>(); 3407 for (Notification.Action action : actions) { 3408 if (action.isContextual()) { 3409 contextualActions.add(action); 3410 } 3411 } 3412 return contextualActions; 3413 } 3414 3415 /** 3416 * Builder class for {@link Notification} objects. 3417 * 3418 * Provides a convenient way to set the various fields of a {@link Notification} and generate 3419 * content views using the platform's notification layout template. If your app supports 3420 * versions of Android as old as API level 4, you can instead use 3421 * {@link android.support.v4.app.NotificationCompat.Builder NotificationCompat.Builder}, 3422 * available in the <a href="{@docRoot}tools/extras/support-library.html">Android Support 3423 * library</a>. 3424 * 3425 * <p>Example: 3426 * 3427 * <pre class="prettyprint"> 3428 * Notification noti = new Notification.Builder(mContext) 3429 * .setContentTitle("New mail from " + sender.toString()) 3430 * .setContentText(subject) 3431 * .setSmallIcon(R.drawable.new_mail) 3432 * .setLargeIcon(aBitmap) 3433 * .build(); 3434 * </pre> 3435 */ 3436 public static class Builder { 3437 /** 3438 * @hide 3439 */ 3440 public static final String EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT = 3441 "android.rebuild.contentViewActionCount"; 3442 /** 3443 * @hide 3444 */ 3445 public static final String EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT 3446 = "android.rebuild.bigViewActionCount"; 3447 /** 3448 * @hide 3449 */ 3450 public static final String EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT 3451 = "android.rebuild.hudViewActionCount"; 3452 3453 private static final boolean USE_ONLY_TITLE_IN_LOW_PRIORITY_SUMMARY = 3454 SystemProperties.getBoolean("notifications.only_title", true); 3455 3456 /** 3457 * The lightness difference that has to be added to the primary text color to obtain the 3458 * secondary text color when the background is light. 3459 */ 3460 private static final int LIGHTNESS_TEXT_DIFFERENCE_LIGHT = 20; 3461 3462 /** 3463 * The lightness difference that has to be added to the primary text color to obtain the 3464 * secondary text color when the background is dark. 3465 * A bit less then the above value, since it looks better on dark backgrounds. 3466 */ 3467 private static final int LIGHTNESS_TEXT_DIFFERENCE_DARK = -10; 3468 3469 private Context mContext; 3470 private Notification mN; 3471 private Bundle mUserExtras = new Bundle(); 3472 private Style mStyle; 3473 @UnsupportedAppUsage 3474 private ArrayList<Action> mActions = new ArrayList<Action>(MAX_ACTION_BUTTONS); 3475 private ArrayList<Person> mPersonList = new ArrayList<>(); 3476 private ContrastColorUtil mColorUtil; 3477 private boolean mIsLegacy; 3478 private boolean mIsLegacyInitialized; 3479 3480 /** 3481 * Caches a contrast-enhanced version of {@link #mCachedContrastColorIsFor}. 3482 */ 3483 private int mCachedContrastColor = COLOR_INVALID; 3484 private int mCachedContrastColorIsFor = COLOR_INVALID; 3485 3486 /** 3487 * A neutral color color that can be used for icons. 3488 */ 3489 private int mNeutralColor = COLOR_INVALID; 3490 3491 /** 3492 * Caches an instance of StandardTemplateParams. Note that this may have been used before, 3493 * so make sure to call {@link StandardTemplateParams#reset()} before using it. 3494 */ 3495 StandardTemplateParams mParams = new StandardTemplateParams(); 3496 private int mTextColorsAreForBackground = COLOR_INVALID; 3497 private int mPrimaryTextColor = COLOR_INVALID; 3498 private int mSecondaryTextColor = COLOR_INVALID; 3499 private int mBackgroundColor = COLOR_INVALID; 3500 private int mForegroundColor = COLOR_INVALID; 3501 /** 3502 * A temporary location where actions are stored. If != null the view originally has action 3503 * but doesn't have any for this inflation. 3504 */ 3505 private ArrayList<Action> mOriginalActions; 3506 private boolean mRebuildStyledRemoteViews; 3507 3508 private boolean mTintActionButtons; 3509 private boolean mInNightMode; 3510 3511 /** 3512 * Constructs a new Builder with the defaults: 3513 * 3514 * @param context 3515 * A {@link Context} that will be used by the Builder to construct the 3516 * RemoteViews. The Context will not be held past the lifetime of this Builder 3517 * object. 3518 * @param channelId 3519 * The constructed Notification will be posted on this 3520 * {@link NotificationChannel}. To use a NotificationChannel, it must first be 3521 * created using {@link NotificationManager#createNotificationChannel}. 3522 */ Builder(Context context, String channelId)3523 public Builder(Context context, String channelId) { 3524 this(context, (Notification) null); 3525 mN.mChannelId = channelId; 3526 } 3527 3528 /** 3529 * @deprecated use {@link #Builder(Context, String)} 3530 * instead. All posted Notifications must specify a NotificationChannel Id. 3531 */ 3532 @Deprecated Builder(Context context)3533 public Builder(Context context) { 3534 this(context, (Notification) null); 3535 } 3536 3537 /** 3538 * @hide 3539 */ Builder(Context context, Notification toAdopt)3540 public Builder(Context context, Notification toAdopt) { 3541 mContext = context; 3542 Resources res = mContext.getResources(); 3543 mTintActionButtons = res.getBoolean(R.bool.config_tintNotificationActionButtons); 3544 3545 if (res.getBoolean(R.bool.config_enableNightMode)) { 3546 Configuration currentConfig = res.getConfiguration(); 3547 mInNightMode = (currentConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK) 3548 == Configuration.UI_MODE_NIGHT_YES; 3549 } 3550 3551 if (toAdopt == null) { 3552 mN = new Notification(); 3553 if (context.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N) { 3554 mN.extras.putBoolean(EXTRA_SHOW_WHEN, true); 3555 } 3556 mN.priority = PRIORITY_DEFAULT; 3557 mN.visibility = VISIBILITY_PRIVATE; 3558 } else { 3559 mN = toAdopt; 3560 if (mN.actions != null) { 3561 Collections.addAll(mActions, mN.actions); 3562 } 3563 3564 if (mN.extras.containsKey(EXTRA_PEOPLE_LIST)) { 3565 ArrayList<Person> people = mN.extras.getParcelableArrayList(EXTRA_PEOPLE_LIST); 3566 mPersonList.addAll(people); 3567 } 3568 3569 if (mN.getSmallIcon() == null && mN.icon != 0) { 3570 setSmallIcon(mN.icon); 3571 } 3572 3573 if (mN.getLargeIcon() == null && mN.largeIcon != null) { 3574 setLargeIcon(mN.largeIcon); 3575 } 3576 3577 String templateClass = mN.extras.getString(EXTRA_TEMPLATE); 3578 if (!TextUtils.isEmpty(templateClass)) { 3579 final Class<? extends Style> styleClass 3580 = getNotificationStyleClass(templateClass); 3581 if (styleClass == null) { 3582 Log.d(TAG, "Unknown style class: " + templateClass); 3583 } else { 3584 try { 3585 final Constructor<? extends Style> ctor = 3586 styleClass.getDeclaredConstructor(); 3587 ctor.setAccessible(true); 3588 final Style style = ctor.newInstance(); 3589 style.restoreFromExtras(mN.extras); 3590 3591 if (style != null) { 3592 setStyle(style); 3593 } 3594 } catch (Throwable t) { 3595 Log.e(TAG, "Could not create Style", t); 3596 } 3597 } 3598 } 3599 } 3600 } 3601 getColorUtil()3602 private ContrastColorUtil getColorUtil() { 3603 if (mColorUtil == null) { 3604 mColorUtil = ContrastColorUtil.getInstance(mContext); 3605 } 3606 return mColorUtil; 3607 } 3608 3609 /** 3610 * From Android 11, messaging notifications (those that use {@link MessagingStyle}) that 3611 * use this method to link to a published long-lived sharing shortcut may appear in a 3612 * dedicated Conversation section of the shade and may show configuration options that 3613 * are unique to conversations. This behavior should be reserved for person to person(s) 3614 * conversations where there is a likely social obligation for an individual to respond. 3615 * <p> 3616 * For example, the following are some examples of notifications that belong in the 3617 * conversation space: 3618 * <ul> 3619 * <li>1:1 conversations between two individuals</li> 3620 * <li>Group conversations between individuals where everyone can contribute</li> 3621 * </ul> 3622 * And the following are some examples of notifications that do not belong in the 3623 * conversation space: 3624 * <ul> 3625 * <li>Advertisements from a bot (even if personal and contextualized)</li> 3626 * <li>Engagement notifications from a bot</li> 3627 * <li>Directional conversations where there is an active speaker and many passive 3628 * individuals</li> 3629 * <li>Stream / posting updates from other individuals</li> 3630 * <li>Email, document comments, or other conversation types that are not real-time</li> 3631 * </ul> 3632 * </p> 3633 * 3634 * <p> 3635 * Additionally, this method can be used for all types of notifications to mark this 3636 * notification as duplicative of a Launcher shortcut. Launchers that show badges or 3637 * notification content may then suppress the shortcut in favor of the content of this 3638 * notification. 3639 * <p> 3640 * If this notification has {@link BubbleMetadata} attached that was created with 3641 * a shortcutId a check will be performed to ensure the shortcutId supplied to bubble 3642 * metadata matches the shortcutId set here, if one was set. If the shortcutId's were 3643 * specified but do not match, an exception is thrown. 3644 * 3645 * @param shortcutId the {@link ShortcutInfo#getId() id} of the shortcut this notification 3646 * is linked to 3647 * 3648 * @see BubbleMetadata.Builder#Builder(String) 3649 */ 3650 @NonNull setShortcutId(String shortcutId)3651 public Builder setShortcutId(String shortcutId) { 3652 mN.mShortcutId = shortcutId; 3653 return this; 3654 } 3655 3656 /** 3657 * Sets the {@link LocusId} associated with this notification. 3658 * 3659 * <p>This method should be called when the {@link LocusId} is used in other places (such 3660 * as {@link ShortcutInfo} and {@link ContentCaptureContext}) so the device's intelligence 3661 * services can correlate them. 3662 */ 3663 @NonNull setLocusId(@ullable LocusId locusId)3664 public Builder setLocusId(@Nullable LocusId locusId) { 3665 mN.mLocusId = locusId; 3666 return this; 3667 } 3668 3669 /** 3670 * Sets which icon to display as a badge for this notification. 3671 * 3672 * Must be one of {@link #BADGE_ICON_NONE}, {@link #BADGE_ICON_SMALL}, 3673 * {@link #BADGE_ICON_LARGE}. 3674 * 3675 * Note: This value might be ignored, for launchers that don't support badge icons. 3676 */ 3677 @NonNull setBadgeIconType(int icon)3678 public Builder setBadgeIconType(int icon) { 3679 mN.mBadgeIcon = icon; 3680 return this; 3681 } 3682 3683 /** 3684 * Sets the group alert behavior for this notification. Use this method to mute this 3685 * notification if alerts for this notification's group should be handled by a different 3686 * notification. This is only applicable for notifications that belong to a 3687 * {@link #setGroup(String) group}. This must be called on all notifications you want to 3688 * mute. For example, if you want only the summary of your group to make noise, all 3689 * children in the group should have the group alert behavior {@link #GROUP_ALERT_SUMMARY}. 3690 * 3691 * <p> The default value is {@link #GROUP_ALERT_ALL}.</p> 3692 */ 3693 @NonNull setGroupAlertBehavior(@roupAlertBehavior int groupAlertBehavior)3694 public Builder setGroupAlertBehavior(@GroupAlertBehavior int groupAlertBehavior) { 3695 mN.mGroupAlertBehavior = groupAlertBehavior; 3696 return this; 3697 } 3698 3699 /** 3700 * Sets the {@link BubbleMetadata} that will be used to display app content in a floating 3701 * window over the existing foreground activity. 3702 * 3703 * <p>This data will be ignored unless the notification is posted to a channel that 3704 * allows {@link NotificationChannel#canBubble() bubbles}.</p> 3705 * 3706 * <p>Notifications allowed to bubble that have valid bubble metadata will display in 3707 * collapsed state outside of the notification shade on unlocked devices. When a user 3708 * interacts with the collapsed state, the bubble intent will be invoked and displayed.</p> 3709 */ 3710 @NonNull setBubbleMetadata(@ullable BubbleMetadata data)3711 public Builder setBubbleMetadata(@Nullable BubbleMetadata data) { 3712 mN.mBubbleMetadata = data; 3713 return this; 3714 } 3715 3716 /** @removed */ 3717 @Deprecated setChannel(String channelId)3718 public Builder setChannel(String channelId) { 3719 mN.mChannelId = channelId; 3720 return this; 3721 } 3722 3723 /** 3724 * Specifies the channel the notification should be delivered on. 3725 */ 3726 @NonNull setChannelId(String channelId)3727 public Builder setChannelId(String channelId) { 3728 mN.mChannelId = channelId; 3729 return this; 3730 } 3731 3732 /** @removed */ 3733 @Deprecated setTimeout(long durationMs)3734 public Builder setTimeout(long durationMs) { 3735 mN.mTimeout = durationMs; 3736 return this; 3737 } 3738 3739 /** 3740 * Specifies a duration in milliseconds after which this notification should be canceled, 3741 * if it is not already canceled. 3742 */ 3743 @NonNull setTimeoutAfter(long durationMs)3744 public Builder setTimeoutAfter(long durationMs) { 3745 mN.mTimeout = durationMs; 3746 return this; 3747 } 3748 3749 /** 3750 * Add a timestamp pertaining to the notification (usually the time the event occurred). 3751 * 3752 * For apps targeting {@link android.os.Build.VERSION_CODES#N} and above, this time is not 3753 * shown anymore by default and must be opted into by using 3754 * {@link android.app.Notification.Builder#setShowWhen(boolean)} 3755 * 3756 * @see Notification#when 3757 */ 3758 @NonNull setWhen(long when)3759 public Builder setWhen(long when) { 3760 mN.when = when; 3761 return this; 3762 } 3763 3764 /** 3765 * Control whether the timestamp set with {@link #setWhen(long) setWhen} is shown 3766 * in the content view. 3767 * For apps targeting {@link android.os.Build.VERSION_CODES#N} and above, this defaults to 3768 * {@code false}. For earlier apps, the default is {@code true}. 3769 */ 3770 @NonNull setShowWhen(boolean show)3771 public Builder setShowWhen(boolean show) { 3772 mN.extras.putBoolean(EXTRA_SHOW_WHEN, show); 3773 return this; 3774 } 3775 3776 /** 3777 * Show the {@link Notification#when} field as a stopwatch. 3778 * 3779 * Instead of presenting <code>when</code> as a timestamp, the notification will show an 3780 * automatically updating display of the minutes and seconds since <code>when</code>. 3781 * 3782 * Useful when showing an elapsed time (like an ongoing phone call). 3783 * 3784 * The counter can also be set to count down to <code>when</code> when using 3785 * {@link #setChronometerCountDown(boolean)}. 3786 * 3787 * @see android.widget.Chronometer 3788 * @see Notification#when 3789 * @see #setChronometerCountDown(boolean) 3790 */ 3791 @NonNull setUsesChronometer(boolean b)3792 public Builder setUsesChronometer(boolean b) { 3793 mN.extras.putBoolean(EXTRA_SHOW_CHRONOMETER, b); 3794 return this; 3795 } 3796 3797 /** 3798 * Sets the Chronometer to count down instead of counting up. 3799 * 3800 * <p>This is only relevant if {@link #setUsesChronometer(boolean)} has been set to true. 3801 * If it isn't set the chronometer will count up. 3802 * 3803 * @see #setUsesChronometer(boolean) 3804 */ 3805 @NonNull setChronometerCountDown(boolean countDown)3806 public Builder setChronometerCountDown(boolean countDown) { 3807 mN.extras.putBoolean(EXTRA_CHRONOMETER_COUNT_DOWN, countDown); 3808 return this; 3809 } 3810 3811 /** 3812 * Set the small icon resource, which will be used to represent the notification in the 3813 * status bar. 3814 * 3815 3816 * The platform template for the expanded view will draw this icon in the left, unless a 3817 * {@link #setLargeIcon(Bitmap) large icon} has also been specified, in which case the small 3818 * icon will be moved to the right-hand side. 3819 * 3820 3821 * @param icon 3822 * A resource ID in the application's package of the drawable to use. 3823 * @see Notification#icon 3824 */ 3825 @NonNull setSmallIcon(@rawableRes int icon)3826 public Builder setSmallIcon(@DrawableRes int icon) { 3827 return setSmallIcon(icon != 0 3828 ? Icon.createWithResource(mContext, icon) 3829 : null); 3830 } 3831 3832 /** 3833 * A variant of {@link #setSmallIcon(int) setSmallIcon(int)} that takes an additional 3834 * level parameter for when the icon is a {@link android.graphics.drawable.LevelListDrawable 3835 * LevelListDrawable}. 3836 * 3837 * @param icon A resource ID in the application's package of the drawable to use. 3838 * @param level The level to use for the icon. 3839 * 3840 * @see Notification#icon 3841 * @see Notification#iconLevel 3842 */ 3843 @NonNull setSmallIcon(@rawableRes int icon, int level)3844 public Builder setSmallIcon(@DrawableRes int icon, int level) { 3845 mN.iconLevel = level; 3846 return setSmallIcon(icon); 3847 } 3848 3849 /** 3850 * Set the small icon, which will be used to represent the notification in the 3851 * status bar and content view (unless overridden there by a 3852 * {@link #setLargeIcon(Bitmap) large icon}). 3853 * 3854 * @param icon An Icon object to use. 3855 * @see Notification#icon 3856 */ 3857 @NonNull setSmallIcon(Icon icon)3858 public Builder setSmallIcon(Icon icon) { 3859 mN.setSmallIcon(icon); 3860 if (icon != null && icon.getType() == Icon.TYPE_RESOURCE) { 3861 mN.icon = icon.getResId(); 3862 } 3863 return this; 3864 } 3865 3866 /** 3867 * Set the first line of text in the platform notification template. 3868 */ 3869 @NonNull setContentTitle(CharSequence title)3870 public Builder setContentTitle(CharSequence title) { 3871 mN.extras.putCharSequence(EXTRA_TITLE, safeCharSequence(title)); 3872 return this; 3873 } 3874 3875 /** 3876 * Set the second line of text in the platform notification template. 3877 */ 3878 @NonNull setContentText(CharSequence text)3879 public Builder setContentText(CharSequence text) { 3880 mN.extras.putCharSequence(EXTRA_TEXT, safeCharSequence(text)); 3881 return this; 3882 } 3883 3884 /** 3885 * This provides some additional information that is displayed in the notification. No 3886 * guarantees are given where exactly it is displayed. 3887 * 3888 * <p>This information should only be provided if it provides an essential 3889 * benefit to the understanding of the notification. The more text you provide the 3890 * less readable it becomes. For example, an email client should only provide the account 3891 * name here if more than one email account has been added.</p> 3892 * 3893 * <p>As of {@link android.os.Build.VERSION_CODES#N} this information is displayed in the 3894 * notification header area. 3895 * 3896 * On Android versions before {@link android.os.Build.VERSION_CODES#N} 3897 * this will be shown in the third line of text in the platform notification template. 3898 * You should not be using {@link #setProgress(int, int, boolean)} at the 3899 * same time on those versions; they occupy the same place. 3900 * </p> 3901 */ 3902 @NonNull setSubText(CharSequence text)3903 public Builder setSubText(CharSequence text) { 3904 mN.extras.putCharSequence(EXTRA_SUB_TEXT, safeCharSequence(text)); 3905 return this; 3906 } 3907 3908 /** 3909 * Provides text that will appear as a link to your application's settings. 3910 * 3911 * <p>This text does not appear within notification {@link Style templates} but may 3912 * appear when the user uses an affordance to learn more about the notification. 3913 * Additionally, this text will not appear unless you provide a valid link target by 3914 * handling {@link #INTENT_CATEGORY_NOTIFICATION_PREFERENCES}. 3915 * 3916 * <p>This text is meant to be concise description about what the user can customize 3917 * when they click on this link. The recommended maximum length is 40 characters. 3918 * @param text 3919 * @return 3920 */ 3921 @NonNull setSettingsText(CharSequence text)3922 public Builder setSettingsText(CharSequence text) { 3923 mN.mSettingsText = safeCharSequence(text); 3924 return this; 3925 } 3926 3927 /** 3928 * Set the remote input history. 3929 * 3930 * This should be set to the most recent inputs that have been sent 3931 * through a {@link RemoteInput} of this Notification and cleared once the it is no 3932 * longer relevant (e.g. for chat notifications once the other party has responded). 3933 * 3934 * The most recent input must be stored at the 0 index, the second most recent at the 3935 * 1 index, etc. Note that the system will limit both how far back the inputs will be shown 3936 * and how much of each individual input is shown. 3937 * 3938 * <p>Note: The reply text will only be shown on notifications that have least one action 3939 * with a {@code RemoteInput}.</p> 3940 */ 3941 @NonNull setRemoteInputHistory(CharSequence[] text)3942 public Builder setRemoteInputHistory(CharSequence[] text) { 3943 if (text == null) { 3944 mN.extras.putCharSequenceArray(EXTRA_REMOTE_INPUT_HISTORY, null); 3945 } else { 3946 final int itemCount = Math.min(MAX_REPLY_HISTORY, text.length); 3947 CharSequence[] safe = new CharSequence[itemCount]; 3948 RemoteInputHistoryItem[] items = new RemoteInputHistoryItem[itemCount]; 3949 for (int i = 0; i < itemCount; i++) { 3950 safe[i] = safeCharSequence(text[i]); 3951 items[i] = new RemoteInputHistoryItem(text[i]); 3952 } 3953 mN.extras.putCharSequenceArray(EXTRA_REMOTE_INPUT_HISTORY, safe); 3954 3955 // Also add these messages as structured history items. 3956 mN.extras.putParcelableArray(EXTRA_REMOTE_INPUT_HISTORY_ITEMS, items); 3957 } 3958 return this; 3959 } 3960 3961 /** 3962 * Set the remote input history, with support for embedding URIs and mime types for 3963 * images and other media. 3964 * @hide 3965 */ 3966 @NonNull setRemoteInputHistory(RemoteInputHistoryItem[] items)3967 public Builder setRemoteInputHistory(RemoteInputHistoryItem[] items) { 3968 if (items == null) { 3969 mN.extras.putParcelableArray(EXTRA_REMOTE_INPUT_HISTORY_ITEMS, null); 3970 } else { 3971 final int itemCount = Math.min(MAX_REPLY_HISTORY, items.length); 3972 RemoteInputHistoryItem[] history = new RemoteInputHistoryItem[itemCount]; 3973 for (int i = 0; i < itemCount; i++) { 3974 history[i] = items[i]; 3975 } 3976 mN.extras.putParcelableArray(EXTRA_REMOTE_INPUT_HISTORY_ITEMS, history); 3977 } 3978 return this; 3979 } 3980 3981 /** 3982 * Sets whether remote history entries view should have a spinner. 3983 * @hide 3984 */ 3985 @NonNull setShowRemoteInputSpinner(boolean showSpinner)3986 public Builder setShowRemoteInputSpinner(boolean showSpinner) { 3987 mN.extras.putBoolean(EXTRA_SHOW_REMOTE_INPUT_SPINNER, showSpinner); 3988 return this; 3989 } 3990 3991 /** 3992 * Sets whether smart reply buttons should be hidden. 3993 * @hide 3994 */ 3995 @NonNull setHideSmartReplies(boolean hideSmartReplies)3996 public Builder setHideSmartReplies(boolean hideSmartReplies) { 3997 mN.extras.putBoolean(EXTRA_HIDE_SMART_REPLIES, hideSmartReplies); 3998 return this; 3999 } 4000 4001 /** 4002 * Sets the number of items this notification represents. May be displayed as a badge count 4003 * for Launchers that support badging. 4004 */ 4005 @NonNull setNumber(int number)4006 public Builder setNumber(int number) { 4007 mN.number = number; 4008 return this; 4009 } 4010 4011 /** 4012 * A small piece of additional information pertaining to this notification. 4013 * 4014 * The platform template will draw this on the last line of the notification, at the far 4015 * right (to the right of a smallIcon if it has been placed there). 4016 * 4017 * @deprecated use {@link #setSubText(CharSequence)} instead to set a text in the header. 4018 * For legacy apps targeting a version below {@link android.os.Build.VERSION_CODES#N} this 4019 * field will still show up, but the subtext will take precedence. 4020 */ 4021 @Deprecated setContentInfo(CharSequence info)4022 public Builder setContentInfo(CharSequence info) { 4023 mN.extras.putCharSequence(EXTRA_INFO_TEXT, safeCharSequence(info)); 4024 return this; 4025 } 4026 4027 /** 4028 * Set the progress this notification represents. 4029 * 4030 * The platform template will represent this using a {@link ProgressBar}. 4031 */ 4032 @NonNull setProgress(int max, int progress, boolean indeterminate)4033 public Builder setProgress(int max, int progress, boolean indeterminate) { 4034 mN.extras.putInt(EXTRA_PROGRESS, progress); 4035 mN.extras.putInt(EXTRA_PROGRESS_MAX, max); 4036 mN.extras.putBoolean(EXTRA_PROGRESS_INDETERMINATE, indeterminate); 4037 return this; 4038 } 4039 4040 /** 4041 * Supply a custom RemoteViews to use instead of the platform template. 4042 * 4043 * Use {@link #setCustomContentView(RemoteViews)} instead. 4044 */ 4045 @Deprecated setContent(RemoteViews views)4046 public Builder setContent(RemoteViews views) { 4047 return setCustomContentView(views); 4048 } 4049 4050 /** 4051 * Supply custom RemoteViews to use instead of the platform template. 4052 * 4053 * This will override the layout that would otherwise be constructed by this Builder 4054 * object. 4055 */ 4056 @NonNull setCustomContentView(RemoteViews contentView)4057 public Builder setCustomContentView(RemoteViews contentView) { 4058 mN.contentView = contentView; 4059 return this; 4060 } 4061 4062 /** 4063 * Supply custom RemoteViews to use instead of the platform template in the expanded form. 4064 * 4065 * This will override the expanded layout that would otherwise be constructed by this 4066 * Builder object. 4067 */ 4068 @NonNull setCustomBigContentView(RemoteViews contentView)4069 public Builder setCustomBigContentView(RemoteViews contentView) { 4070 mN.bigContentView = contentView; 4071 return this; 4072 } 4073 4074 /** 4075 * Supply custom RemoteViews to use instead of the platform template in the heads up dialog. 4076 * 4077 * This will override the heads-up layout that would otherwise be constructed by this 4078 * Builder object. 4079 */ 4080 @NonNull setCustomHeadsUpContentView(RemoteViews contentView)4081 public Builder setCustomHeadsUpContentView(RemoteViews contentView) { 4082 mN.headsUpContentView = contentView; 4083 return this; 4084 } 4085 4086 /** 4087 * Supply a {@link PendingIntent} to be sent when the notification is clicked. 4088 * 4089 * As of {@link android.os.Build.VERSION_CODES#HONEYCOMB}, if this field is unset and you 4090 * have specified a custom RemoteViews with {@link #setContent(RemoteViews)}, you can use 4091 * {@link RemoteViews#setOnClickPendingIntent RemoteViews.setOnClickPendingIntent(int,PendingIntent)} 4092 * to assign PendingIntents to individual views in that custom layout (i.e., to create 4093 * clickable buttons inside the notification view). 4094 * 4095 * @see Notification#contentIntent Notification.contentIntent 4096 */ 4097 @NonNull setContentIntent(PendingIntent intent)4098 public Builder setContentIntent(PendingIntent intent) { 4099 mN.contentIntent = intent; 4100 return this; 4101 } 4102 4103 /** 4104 * Supply a {@link PendingIntent} to send when the notification is cleared explicitly by the user. 4105 * 4106 * @see Notification#deleteIntent 4107 */ 4108 @NonNull setDeleteIntent(PendingIntent intent)4109 public Builder setDeleteIntent(PendingIntent intent) { 4110 mN.deleteIntent = intent; 4111 return this; 4112 } 4113 4114 /** 4115 * An intent to launch instead of posting the notification to the status bar. 4116 * Only for use with extremely high-priority notifications demanding the user's 4117 * <strong>immediate</strong> attention, such as an incoming phone call or 4118 * alarm clock that the user has explicitly set to a particular time. 4119 * If this facility is used for something else, please give the user an option 4120 * to turn it off and use a normal notification, as this can be extremely 4121 * disruptive. 4122 * 4123 * <p> 4124 * The system UI may choose to display a heads-up notification, instead of 4125 * launching this intent, while the user is using the device. 4126 * </p> 4127 * <p>Apps targeting {@link Build.VERSION_CODES#Q} and above will have to request 4128 * a permission ({@link android.Manifest.permission#USE_FULL_SCREEN_INTENT}) in order to 4129 * use full screen intents.</p> 4130 * 4131 * @param intent The pending intent to launch. 4132 * @param highPriority Passing true will cause this notification to be sent 4133 * even if other notifications are suppressed. 4134 * 4135 * @see Notification#fullScreenIntent 4136 */ 4137 @NonNull setFullScreenIntent(PendingIntent intent, boolean highPriority)4138 public Builder setFullScreenIntent(PendingIntent intent, boolean highPriority) { 4139 mN.fullScreenIntent = intent; 4140 setFlag(FLAG_HIGH_PRIORITY, highPriority); 4141 return this; 4142 } 4143 4144 /** 4145 * Set the "ticker" text which is sent to accessibility services. 4146 * 4147 * @see Notification#tickerText 4148 */ 4149 @NonNull setTicker(CharSequence tickerText)4150 public Builder setTicker(CharSequence tickerText) { 4151 mN.tickerText = safeCharSequence(tickerText); 4152 return this; 4153 } 4154 4155 /** 4156 * Obsolete version of {@link #setTicker(CharSequence)}. 4157 * 4158 */ 4159 @Deprecated setTicker(CharSequence tickerText, RemoteViews views)4160 public Builder setTicker(CharSequence tickerText, RemoteViews views) { 4161 setTicker(tickerText); 4162 // views is ignored 4163 return this; 4164 } 4165 4166 /** 4167 * Add a large icon to the notification content view. 4168 * 4169 * In the platform template, this image will be shown on the left of the notification view 4170 * in place of the {@link #setSmallIcon(Icon) small icon} (which will be placed in a small 4171 * badge atop the large icon). 4172 */ 4173 @NonNull setLargeIcon(Bitmap b)4174 public Builder setLargeIcon(Bitmap b) { 4175 return setLargeIcon(b != null ? Icon.createWithBitmap(b) : null); 4176 } 4177 4178 /** 4179 * Add a large icon to the notification content view. 4180 * 4181 * In the platform template, this image will be shown on the left of the notification view 4182 * in place of the {@link #setSmallIcon(Icon) small icon} (which will be placed in a small 4183 * badge atop the large icon). 4184 */ 4185 @NonNull setLargeIcon(Icon icon)4186 public Builder setLargeIcon(Icon icon) { 4187 mN.mLargeIcon = icon; 4188 mN.extras.putParcelable(EXTRA_LARGE_ICON, icon); 4189 return this; 4190 } 4191 4192 /** 4193 * Set the sound to play. 4194 * 4195 * It will be played using the {@link #AUDIO_ATTRIBUTES_DEFAULT default audio attributes} 4196 * for notifications. 4197 * 4198 * @deprecated use {@link NotificationChannel#setSound(Uri, AudioAttributes)} instead. 4199 */ 4200 @Deprecated setSound(Uri sound)4201 public Builder setSound(Uri sound) { 4202 mN.sound = sound; 4203 mN.audioAttributes = AUDIO_ATTRIBUTES_DEFAULT; 4204 return this; 4205 } 4206 4207 /** 4208 * Set the sound to play, along with a specific stream on which to play it. 4209 * 4210 * See {@link android.media.AudioManager} for the <code>STREAM_</code> constants. 4211 * 4212 * @deprecated use {@link NotificationChannel#setSound(Uri, AudioAttributes)}. 4213 */ 4214 @Deprecated setSound(Uri sound, int streamType)4215 public Builder setSound(Uri sound, int streamType) { 4216 PlayerBase.deprecateStreamTypeForPlayback(streamType, "Notification", "setSound()"); 4217 mN.sound = sound; 4218 mN.audioStreamType = streamType; 4219 return this; 4220 } 4221 4222 /** 4223 * Set the sound to play, along with specific {@link AudioAttributes audio attributes} to 4224 * use during playback. 4225 * 4226 * @deprecated use {@link NotificationChannel#setSound(Uri, AudioAttributes)} instead. 4227 * @see Notification#sound 4228 */ 4229 @Deprecated setSound(Uri sound, AudioAttributes audioAttributes)4230 public Builder setSound(Uri sound, AudioAttributes audioAttributes) { 4231 mN.sound = sound; 4232 mN.audioAttributes = audioAttributes; 4233 return this; 4234 } 4235 4236 /** 4237 * Set the vibration pattern to use. 4238 * 4239 * See {@link android.os.Vibrator#vibrate(long[], int)} for a discussion of the 4240 * <code>pattern</code> parameter. 4241 * 4242 * <p> 4243 * A notification that vibrates is more likely to be presented as a heads-up notification. 4244 * </p> 4245 * 4246 * @deprecated use {@link NotificationChannel#setVibrationPattern(long[])} instead. 4247 * @see Notification#vibrate 4248 */ 4249 @Deprecated setVibrate(long[] pattern)4250 public Builder setVibrate(long[] pattern) { 4251 mN.vibrate = pattern; 4252 return this; 4253 } 4254 4255 /** 4256 * Set the desired color for the indicator LED on the device, as well as the 4257 * blink duty cycle (specified in milliseconds). 4258 * 4259 4260 * Not all devices will honor all (or even any) of these values. 4261 * 4262 * @deprecated use {@link NotificationChannel#enableLights(boolean)} instead. 4263 * @see Notification#ledARGB 4264 * @see Notification#ledOnMS 4265 * @see Notification#ledOffMS 4266 */ 4267 @Deprecated setLights(@olorInt int argb, int onMs, int offMs)4268 public Builder setLights(@ColorInt int argb, int onMs, int offMs) { 4269 mN.ledARGB = argb; 4270 mN.ledOnMS = onMs; 4271 mN.ledOffMS = offMs; 4272 if (onMs != 0 || offMs != 0) { 4273 mN.flags |= FLAG_SHOW_LIGHTS; 4274 } 4275 return this; 4276 } 4277 4278 /** 4279 * Set whether this is an "ongoing" notification. 4280 * 4281 4282 * Ongoing notifications cannot be dismissed by the user, so your application or service 4283 * must take care of canceling them. 4284 * 4285 4286 * They are typically used to indicate a background task that the user is actively engaged 4287 * with (e.g., playing music) or is pending in some way and therefore occupying the device 4288 * (e.g., a file download, sync operation, active network connection). 4289 * 4290 4291 * @see Notification#FLAG_ONGOING_EVENT 4292 */ 4293 @NonNull setOngoing(boolean ongoing)4294 public Builder setOngoing(boolean ongoing) { 4295 setFlag(FLAG_ONGOING_EVENT, ongoing); 4296 return this; 4297 } 4298 4299 /** 4300 * Set whether this notification should be colorized. When set, the color set with 4301 * {@link #setColor(int)} will be used as the background color of this notification. 4302 * <p> 4303 * This should only be used for high priority ongoing tasks like navigation, an ongoing 4304 * call, or other similarly high-priority events for the user. 4305 * <p> 4306 * For most styles, the coloring will only be applied if the notification is for a 4307 * foreground service notification. 4308 * However, for {@link MediaStyle} and {@link DecoratedMediaCustomViewStyle} notifications 4309 * that have a media session attached there is no such requirement. 4310 * 4311 * @see #setColor(int) 4312 * @see MediaStyle#setMediaSession(MediaSession.Token) 4313 */ 4314 @NonNull setColorized(boolean colorize)4315 public Builder setColorized(boolean colorize) { 4316 mN.extras.putBoolean(EXTRA_COLORIZED, colorize); 4317 return this; 4318 } 4319 4320 /** 4321 * Set this flag if you would only like the sound, vibrate 4322 * and ticker to be played if the notification is not already showing. 4323 * 4324 * @see Notification#FLAG_ONLY_ALERT_ONCE 4325 */ 4326 @NonNull setOnlyAlertOnce(boolean onlyAlertOnce)4327 public Builder setOnlyAlertOnce(boolean onlyAlertOnce) { 4328 setFlag(FLAG_ONLY_ALERT_ONCE, onlyAlertOnce); 4329 return this; 4330 } 4331 4332 /** 4333 * Make this notification automatically dismissed when the user touches it. 4334 * 4335 * @see Notification#FLAG_AUTO_CANCEL 4336 */ 4337 @NonNull setAutoCancel(boolean autoCancel)4338 public Builder setAutoCancel(boolean autoCancel) { 4339 setFlag(FLAG_AUTO_CANCEL, autoCancel); 4340 return this; 4341 } 4342 4343 /** 4344 * Set whether or not this notification should not bridge to other devices. 4345 * 4346 * <p>Some notifications can be bridged to other devices for remote display. 4347 * This hint can be set to recommend this notification not be bridged. 4348 */ 4349 @NonNull setLocalOnly(boolean localOnly)4350 public Builder setLocalOnly(boolean localOnly) { 4351 setFlag(FLAG_LOCAL_ONLY, localOnly); 4352 return this; 4353 } 4354 4355 /** 4356 * Set which notification properties will be inherited from system defaults. 4357 * <p> 4358 * The value should be one or more of the following fields combined with 4359 * bitwise-or: 4360 * {@link #DEFAULT_SOUND}, {@link #DEFAULT_VIBRATE}, {@link #DEFAULT_LIGHTS}. 4361 * <p> 4362 * For all default values, use {@link #DEFAULT_ALL}. 4363 * 4364 * @deprecated use {@link NotificationChannel#enableVibration(boolean)} and 4365 * {@link NotificationChannel#enableLights(boolean)} and 4366 * {@link NotificationChannel#setSound(Uri, AudioAttributes)} instead. 4367 */ 4368 @Deprecated setDefaults(int defaults)4369 public Builder setDefaults(int defaults) { 4370 mN.defaults = defaults; 4371 return this; 4372 } 4373 4374 /** 4375 * Set the priority of this notification. 4376 * 4377 * @see Notification#priority 4378 * @deprecated use {@link NotificationChannel#setImportance(int)} instead. 4379 */ 4380 @Deprecated setPriority(@riority int pri)4381 public Builder setPriority(@Priority int pri) { 4382 mN.priority = pri; 4383 return this; 4384 } 4385 4386 /** 4387 * Set the notification category. 4388 * 4389 * @see Notification#category 4390 */ 4391 @NonNull setCategory(String category)4392 public Builder setCategory(String category) { 4393 mN.category = category; 4394 return this; 4395 } 4396 4397 /** 4398 * Add a person that is relevant to this notification. 4399 * 4400 * <P> 4401 * Depending on user preferences, this annotation may allow the notification to pass 4402 * through interruption filters, if this notification is of category {@link #CATEGORY_CALL} 4403 * or {@link #CATEGORY_MESSAGE}. The addition of people may also cause this notification to 4404 * appear more prominently in the user interface. 4405 * </P> 4406 * 4407 * <P> 4408 * The person should be specified by the {@code String} representation of a 4409 * {@link android.provider.ContactsContract.Contacts#CONTENT_LOOKUP_URI}. 4410 * </P> 4411 * 4412 * <P>The system will also attempt to resolve {@code mailto:} and {@code tel:} schema 4413 * URIs. The path part of these URIs must exist in the contacts database, in the 4414 * appropriate column, or the reference will be discarded as invalid. Telephone schema 4415 * URIs will be resolved by {@link android.provider.ContactsContract.PhoneLookup}. 4416 * It is also possible to provide a URI with the schema {@code name:} in order to uniquely 4417 * identify a person without an entry in the contacts database. 4418 * </P> 4419 * 4420 * @param uri A URI for the person. 4421 * @see Notification#EXTRA_PEOPLE 4422 * @deprecated use {@link #addPerson(Person)} 4423 */ addPerson(String uri)4424 public Builder addPerson(String uri) { 4425 addPerson(new Person.Builder().setUri(uri).build()); 4426 return this; 4427 } 4428 4429 /** 4430 * Add a person that is relevant to this notification. 4431 * 4432 * <P> 4433 * Depending on user preferences, this annotation may allow the notification to pass 4434 * through interruption filters, if this notification is of category {@link #CATEGORY_CALL} 4435 * or {@link #CATEGORY_MESSAGE}. The addition of people may also cause this notification to 4436 * appear more prominently in the user interface. 4437 * </P> 4438 * 4439 * <P> 4440 * A person should usually contain a uri in order to benefit from the ranking boost. 4441 * However, even if no uri is provided, it's beneficial to provide other people in the 4442 * notification, such that listeners and voice only devices can announce and handle them 4443 * properly. 4444 * </P> 4445 * 4446 * @param person the person to add. 4447 * @see Notification#EXTRA_PEOPLE_LIST 4448 */ 4449 @NonNull addPerson(Person person)4450 public Builder addPerson(Person person) { 4451 mPersonList.add(person); 4452 return this; 4453 } 4454 4455 /** 4456 * Set this notification to be part of a group of notifications sharing the same key. 4457 * Grouped notifications may display in a cluster or stack on devices which 4458 * support such rendering. 4459 * 4460 * <p>To make this notification the summary for its group, also call 4461 * {@link #setGroupSummary}. A sort order can be specified for group members by using 4462 * {@link #setSortKey}. 4463 * @param groupKey The group key of the group. 4464 * @return this object for method chaining 4465 */ 4466 @NonNull setGroup(String groupKey)4467 public Builder setGroup(String groupKey) { 4468 mN.mGroupKey = groupKey; 4469 return this; 4470 } 4471 4472 /** 4473 * Set this notification to be the group summary for a group of notifications. 4474 * Grouped notifications may display in a cluster or stack on devices which 4475 * support such rendering. If thereRequires a group key also be set using {@link #setGroup}. 4476 * The group summary may be suppressed if too few notifications are included in the group. 4477 * @param isGroupSummary Whether this notification should be a group summary. 4478 * @return this object for method chaining 4479 */ 4480 @NonNull setGroupSummary(boolean isGroupSummary)4481 public Builder setGroupSummary(boolean isGroupSummary) { 4482 setFlag(FLAG_GROUP_SUMMARY, isGroupSummary); 4483 return this; 4484 } 4485 4486 /** 4487 * Set a sort key that orders this notification among other notifications from the 4488 * same package. This can be useful if an external sort was already applied and an app 4489 * would like to preserve this. Notifications will be sorted lexicographically using this 4490 * value, although providing different priorities in addition to providing sort key may 4491 * cause this value to be ignored. 4492 * 4493 * <p>This sort key can also be used to order members of a notification group. See 4494 * {@link #setGroup}. 4495 * 4496 * @see String#compareTo(String) 4497 */ 4498 @NonNull setSortKey(String sortKey)4499 public Builder setSortKey(String sortKey) { 4500 mN.mSortKey = sortKey; 4501 return this; 4502 } 4503 4504 /** 4505 * Merge additional metadata into this notification. 4506 * 4507 * <p>Values within the Bundle will replace existing extras values in this Builder. 4508 * 4509 * @see Notification#extras 4510 */ 4511 @NonNull addExtras(Bundle extras)4512 public Builder addExtras(Bundle extras) { 4513 if (extras != null) { 4514 mUserExtras.putAll(extras); 4515 } 4516 return this; 4517 } 4518 4519 /** 4520 * Set metadata for this notification. 4521 * 4522 * <p>A reference to the Bundle is held for the lifetime of this Builder, and the Bundle's 4523 * current contents are copied into the Notification each time {@link #build()} is 4524 * called. 4525 * 4526 * <p>Replaces any existing extras values with those from the provided Bundle. 4527 * Use {@link #addExtras} to merge in metadata instead. 4528 * 4529 * @see Notification#extras 4530 */ 4531 @NonNull setExtras(Bundle extras)4532 public Builder setExtras(Bundle extras) { 4533 if (extras != null) { 4534 mUserExtras = extras; 4535 } 4536 return this; 4537 } 4538 4539 /** 4540 * Get the current metadata Bundle used by this notification Builder. 4541 * 4542 * <p>The returned Bundle is shared with this Builder. 4543 * 4544 * <p>The current contents of this Bundle are copied into the Notification each time 4545 * {@link #build()} is called. 4546 * 4547 * @see Notification#extras 4548 */ getExtras()4549 public Bundle getExtras() { 4550 return mUserExtras; 4551 } 4552 getAllExtras()4553 private Bundle getAllExtras() { 4554 final Bundle saveExtras = (Bundle) mUserExtras.clone(); 4555 saveExtras.putAll(mN.extras); 4556 return saveExtras; 4557 } 4558 4559 /** 4560 * Add an action to this notification. Actions are typically displayed by 4561 * the system as a button adjacent to the notification content. 4562 * <p> 4563 * Every action must have an icon (32dp square and matching the 4564 * <a href="{@docRoot}design/style/iconography.html#action-bar">Holo 4565 * Dark action bar</a> visual style), a textual label, and a {@link PendingIntent}. 4566 * <p> 4567 * A notification in its expanded form can display up to 3 actions, from left to right in 4568 * the order they were added. Actions will not be displayed when the notification is 4569 * collapsed, however, so be sure that any essential functions may be accessed by the user 4570 * in some other way (for example, in the Activity pointed to by {@link #contentIntent}). 4571 * 4572 * @param icon Resource ID of a drawable that represents the action. 4573 * @param title Text describing the action. 4574 * @param intent PendingIntent to be fired when the action is invoked. 4575 * 4576 * @deprecated Use {@link #addAction(Action)} instead. 4577 */ 4578 @Deprecated addAction(int icon, CharSequence title, PendingIntent intent)4579 public Builder addAction(int icon, CharSequence title, PendingIntent intent) { 4580 mActions.add(new Action(icon, safeCharSequence(title), intent)); 4581 return this; 4582 } 4583 4584 /** 4585 * Add an action to this notification. Actions are typically displayed by 4586 * the system as a button adjacent to the notification content. 4587 * <p> 4588 * Every action must have an icon (32dp square and matching the 4589 * <a href="{@docRoot}design/style/iconography.html#action-bar">Holo 4590 * Dark action bar</a> visual style), a textual label, and a {@link PendingIntent}. 4591 * <p> 4592 * A notification in its expanded form can display up to 3 actions, from left to right in 4593 * the order they were added. Actions will not be displayed when the notification is 4594 * collapsed, however, so be sure that any essential functions may be accessed by the user 4595 * in some other way (for example, in the Activity pointed to by {@link #contentIntent}). 4596 * 4597 * @param action The action to add. 4598 */ 4599 @NonNull addAction(Action action)4600 public Builder addAction(Action action) { 4601 if (action != null) { 4602 mActions.add(action); 4603 } 4604 return this; 4605 } 4606 4607 /** 4608 * Alter the complete list of actions attached to this notification. 4609 * @see #addAction(Action). 4610 * 4611 * @param actions 4612 * @return 4613 */ 4614 @NonNull setActions(Action... actions)4615 public Builder setActions(Action... actions) { 4616 mActions.clear(); 4617 for (int i = 0; i < actions.length; i++) { 4618 if (actions[i] != null) { 4619 mActions.add(actions[i]); 4620 } 4621 } 4622 return this; 4623 } 4624 4625 /** 4626 * Add a rich notification style to be applied at build time. 4627 * 4628 * @param style Object responsible for modifying the notification style. 4629 */ 4630 @NonNull setStyle(Style style)4631 public Builder setStyle(Style style) { 4632 if (mStyle != style) { 4633 mStyle = style; 4634 if (mStyle != null) { 4635 mStyle.setBuilder(this); 4636 mN.extras.putString(EXTRA_TEMPLATE, style.getClass().getName()); 4637 } else { 4638 mN.extras.remove(EXTRA_TEMPLATE); 4639 } 4640 } 4641 return this; 4642 } 4643 4644 /** 4645 * Returns the style set by {@link #setStyle(Style)}. 4646 */ getStyle()4647 public Style getStyle() { 4648 return mStyle; 4649 } 4650 4651 /** 4652 * Specify the value of {@link #visibility}. 4653 * 4654 * @return The same Builder. 4655 */ 4656 @NonNull setVisibility(@isibility int visibility)4657 public Builder setVisibility(@Visibility int visibility) { 4658 mN.visibility = visibility; 4659 return this; 4660 } 4661 4662 /** 4663 * Supply a replacement Notification whose contents should be shown in insecure contexts 4664 * (i.e. atop the secure lockscreen). See {@link #visibility} and {@link #VISIBILITY_PUBLIC}. 4665 * @param n A replacement notification, presumably with some or all info redacted. 4666 * @return The same Builder. 4667 */ 4668 @NonNull setPublicVersion(Notification n)4669 public Builder setPublicVersion(Notification n) { 4670 if (n != null) { 4671 mN.publicVersion = new Notification(); 4672 n.cloneInto(mN.publicVersion, /*heavy=*/ true); 4673 } else { 4674 mN.publicVersion = null; 4675 } 4676 return this; 4677 } 4678 4679 /** 4680 * Apply an extender to this notification builder. Extenders may be used to add 4681 * metadata or change options on this builder. 4682 */ 4683 @NonNull extend(Extender extender)4684 public Builder extend(Extender extender) { 4685 extender.extend(this); 4686 return this; 4687 } 4688 4689 /** 4690 * Set the value for a notification flag 4691 * 4692 * @param mask Bit mask of the flag 4693 * @param value Status (on/off) of the flag 4694 * 4695 * @return The same Builder. 4696 */ 4697 @NonNull setFlag(@otificationFlags int mask, boolean value)4698 public Builder setFlag(@NotificationFlags int mask, boolean value) { 4699 if (value) { 4700 mN.flags |= mask; 4701 } else { 4702 mN.flags &= ~mask; 4703 } 4704 return this; 4705 } 4706 4707 /** 4708 * Sets {@link Notification#color}. 4709 * 4710 * @param argb The accent color to use 4711 * 4712 * @return The same Builder. 4713 */ 4714 @NonNull setColor(@olorInt int argb)4715 public Builder setColor(@ColorInt int argb) { 4716 mN.color = argb; 4717 sanitizeColor(); 4718 return this; 4719 } 4720 getProfileBadgeDrawable()4721 private Drawable getProfileBadgeDrawable() { 4722 if (mContext.getUserId() == UserHandle.USER_SYSTEM) { 4723 // This user can never be a badged profile, 4724 // and also includes USER_ALL system notifications. 4725 return null; 4726 } 4727 // Note: This assumes that the current user can read the profile badge of the 4728 // originating user. 4729 return mContext.getPackageManager().getUserBadgeForDensityNoBackground( 4730 new UserHandle(mContext.getUserId()), 0); 4731 } 4732 getProfileBadge()4733 private Bitmap getProfileBadge() { 4734 Drawable badge = getProfileBadgeDrawable(); 4735 if (badge == null) { 4736 return null; 4737 } 4738 final int size = mContext.getResources().getDimensionPixelSize( 4739 R.dimen.notification_badge_size); 4740 Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); 4741 Canvas canvas = new Canvas(bitmap); 4742 badge.setBounds(0, 0, size, size); 4743 badge.draw(canvas); 4744 return bitmap; 4745 } 4746 bindProfileBadge(RemoteViews contentView, StandardTemplateParams p)4747 private void bindProfileBadge(RemoteViews contentView, StandardTemplateParams p) { 4748 Bitmap profileBadge = getProfileBadge(); 4749 4750 if (profileBadge != null) { 4751 contentView.setImageViewBitmap(R.id.profile_badge, profileBadge); 4752 contentView.setViewVisibility(R.id.profile_badge, View.VISIBLE); 4753 if (isColorized(p)) { 4754 contentView.setDrawableTint(R.id.profile_badge, false, 4755 getPrimaryTextColor(p), PorterDuff.Mode.SRC_ATOP); 4756 } 4757 } 4758 } 4759 bindAlertedIcon(RemoteViews contentView, StandardTemplateParams p)4760 private void bindAlertedIcon(RemoteViews contentView, StandardTemplateParams p) { 4761 contentView.setDrawableTint( 4762 R.id.alerted_icon, 4763 false /* targetBackground */, 4764 getNeutralColor(p), 4765 PorterDuff.Mode.SRC_ATOP); 4766 } 4767 4768 /** 4769 * @hide 4770 */ usesStandardHeader()4771 public boolean usesStandardHeader() { 4772 if (mN.mUsesStandardHeader) { 4773 return true; 4774 } 4775 if (mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.N) { 4776 if (mN.contentView == null && mN.bigContentView == null) { 4777 return true; 4778 } 4779 } 4780 boolean contentViewUsesHeader = mN.contentView == null 4781 || STANDARD_LAYOUTS.contains(mN.contentView.getLayoutId()); 4782 boolean bigContentViewUsesHeader = mN.bigContentView == null 4783 || STANDARD_LAYOUTS.contains(mN.bigContentView.getLayoutId()); 4784 return contentViewUsesHeader && bigContentViewUsesHeader; 4785 } 4786 resetStandardTemplate(RemoteViews contentView)4787 private void resetStandardTemplate(RemoteViews contentView) { 4788 resetNotificationHeader(contentView); 4789 contentView.setViewVisibility(R.id.right_icon, View.GONE); 4790 contentView.setViewVisibility(R.id.title, View.GONE); 4791 contentView.setTextViewText(R.id.title, null); 4792 contentView.setViewVisibility(R.id.text, View.GONE); 4793 contentView.setTextViewText(R.id.text, null); 4794 contentView.setViewVisibility(R.id.text_line_1, View.GONE); 4795 contentView.setTextViewText(R.id.text_line_1, null); 4796 } 4797 4798 /** 4799 * Resets the notification header to its original state 4800 */ resetNotificationHeader(RemoteViews contentView)4801 private void resetNotificationHeader(RemoteViews contentView) { 4802 // Small icon doesn't need to be reset, as it's always set. Resetting would prevent 4803 // re-using the drawable when the notification is updated. 4804 contentView.setBoolean(R.id.notification_header, "setExpanded", false); 4805 contentView.setTextViewText(R.id.app_name_text, null); 4806 contentView.setViewVisibility(R.id.chronometer, View.GONE); 4807 contentView.setViewVisibility(R.id.header_text, View.GONE); 4808 contentView.setTextViewText(R.id.header_text, null); 4809 contentView.setViewVisibility(R.id.header_text_secondary, View.GONE); 4810 contentView.setTextViewText(R.id.header_text_secondary, null); 4811 contentView.setViewVisibility(R.id.header_text_divider, View.GONE); 4812 contentView.setViewVisibility(R.id.header_text_secondary_divider, View.GONE); 4813 contentView.setViewVisibility(R.id.time_divider, View.GONE); 4814 contentView.setViewVisibility(R.id.time, View.GONE); 4815 contentView.setImageViewIcon(R.id.profile_badge, null); 4816 contentView.setViewVisibility(R.id.profile_badge, View.GONE); 4817 mN.mUsesStandardHeader = false; 4818 } 4819 applyStandardTemplate(int resId, TemplateBindResult result)4820 private RemoteViews applyStandardTemplate(int resId, TemplateBindResult result) { 4821 return applyStandardTemplate(resId, mParams.reset().fillTextsFrom(this), 4822 result); 4823 } 4824 applyStandardTemplate(int resId, StandardTemplateParams p, TemplateBindResult result)4825 private RemoteViews applyStandardTemplate(int resId, StandardTemplateParams p, 4826 TemplateBindResult result) { 4827 RemoteViews contentView = new BuilderRemoteViews(mContext.getApplicationInfo(), resId); 4828 4829 resetStandardTemplate(contentView); 4830 4831 final Bundle ex = mN.extras; 4832 updateBackgroundColor(contentView, p); 4833 bindNotificationHeader(contentView, p); 4834 bindLargeIconAndReply(contentView, p, result); 4835 boolean showProgress = handleProgressBar(contentView, ex, p); 4836 if (p.title != null) { 4837 contentView.setViewVisibility(R.id.title, View.VISIBLE); 4838 contentView.setTextViewText(R.id.title, processTextSpans(p.title)); 4839 setTextViewColorPrimary(contentView, R.id.title, p); 4840 contentView.setViewLayoutWidth(R.id.title, showProgress 4841 ? ViewGroup.LayoutParams.WRAP_CONTENT 4842 : ViewGroup.LayoutParams.MATCH_PARENT); 4843 } 4844 if (p.text != null) { 4845 int textId = showProgress ? com.android.internal.R.id.text_line_1 4846 : com.android.internal.R.id.text; 4847 contentView.setTextViewText(textId, processTextSpans(p.text)); 4848 setTextViewColorSecondary(contentView, textId, p); 4849 contentView.setViewVisibility(textId, View.VISIBLE); 4850 } 4851 4852 setContentMinHeight(contentView, showProgress || mN.hasLargeIcon()); 4853 4854 return contentView; 4855 } 4856 processTextSpans(CharSequence text)4857 private CharSequence processTextSpans(CharSequence text) { 4858 if (hasForegroundColor() || mInNightMode) { 4859 return ContrastColorUtil.clearColorSpans(text); 4860 } 4861 return text; 4862 } 4863 setTextViewColorPrimary(RemoteViews contentView, int id, StandardTemplateParams p)4864 private void setTextViewColorPrimary(RemoteViews contentView, int id, 4865 StandardTemplateParams p) { 4866 ensureColors(p); 4867 contentView.setTextColor(id, mPrimaryTextColor); 4868 } 4869 hasForegroundColor()4870 private boolean hasForegroundColor() { 4871 return mForegroundColor != COLOR_INVALID; 4872 } 4873 4874 /** 4875 * Return the primary text color using the existing template params 4876 * @hide 4877 */ 4878 @VisibleForTesting getPrimaryTextColor()4879 public int getPrimaryTextColor() { 4880 return getPrimaryTextColor(mParams); 4881 } 4882 4883 /** 4884 * @param p the template params to inflate this with 4885 * @return the primary text color 4886 * @hide 4887 */ 4888 @VisibleForTesting getPrimaryTextColor(StandardTemplateParams p)4889 public int getPrimaryTextColor(StandardTemplateParams p) { 4890 ensureColors(p); 4891 return mPrimaryTextColor; 4892 } 4893 4894 /** 4895 * Return the secondary text color using the existing template params 4896 * @hide 4897 */ 4898 @VisibleForTesting getSecondaryTextColor()4899 public int getSecondaryTextColor() { 4900 return getSecondaryTextColor(mParams); 4901 } 4902 4903 /** 4904 * @param p the template params to inflate this with 4905 * @return the secondary text color 4906 * @hide 4907 */ 4908 @VisibleForTesting getSecondaryTextColor(StandardTemplateParams p)4909 public int getSecondaryTextColor(StandardTemplateParams p) { 4910 ensureColors(p); 4911 return mSecondaryTextColor; 4912 } 4913 setTextViewColorSecondary(RemoteViews contentView, int id, StandardTemplateParams p)4914 private void setTextViewColorSecondary(RemoteViews contentView, int id, 4915 StandardTemplateParams p) { 4916 ensureColors(p); 4917 contentView.setTextColor(id, mSecondaryTextColor); 4918 } 4919 ensureColors(StandardTemplateParams p)4920 private void ensureColors(StandardTemplateParams p) { 4921 int backgroundColor = getBackgroundColor(p); 4922 if (mPrimaryTextColor == COLOR_INVALID 4923 || mSecondaryTextColor == COLOR_INVALID 4924 || mTextColorsAreForBackground != backgroundColor) { 4925 mTextColorsAreForBackground = backgroundColor; 4926 if (!hasForegroundColor() || !isColorized(p)) { 4927 mPrimaryTextColor = ContrastColorUtil.resolvePrimaryColor(mContext, 4928 backgroundColor, mInNightMode); 4929 mSecondaryTextColor = ContrastColorUtil.resolveSecondaryColor(mContext, 4930 backgroundColor, mInNightMode); 4931 if (backgroundColor != COLOR_DEFAULT && isColorized(p)) { 4932 mPrimaryTextColor = ContrastColorUtil.findAlphaToMeetContrast( 4933 mPrimaryTextColor, backgroundColor, 4.5); 4934 mSecondaryTextColor = ContrastColorUtil.findAlphaToMeetContrast( 4935 mSecondaryTextColor, backgroundColor, 4.5); 4936 } 4937 } else { 4938 double backLum = ContrastColorUtil.calculateLuminance(backgroundColor); 4939 double textLum = ContrastColorUtil.calculateLuminance(mForegroundColor); 4940 double contrast = ContrastColorUtil.calculateContrast(mForegroundColor, 4941 backgroundColor); 4942 // We only respect the given colors if worst case Black or White still has 4943 // contrast 4944 boolean backgroundLight = backLum > textLum 4945 && satisfiesTextContrast(backgroundColor, Color.BLACK) 4946 || backLum <= textLum 4947 && !satisfiesTextContrast(backgroundColor, Color.WHITE); 4948 if (contrast < 4.5f) { 4949 if (backgroundLight) { 4950 mSecondaryTextColor = ContrastColorUtil.findContrastColor( 4951 mForegroundColor, 4952 backgroundColor, 4953 true /* findFG */, 4954 4.5f); 4955 mPrimaryTextColor = ContrastColorUtil.changeColorLightness( 4956 mSecondaryTextColor, -LIGHTNESS_TEXT_DIFFERENCE_LIGHT); 4957 } else { 4958 mSecondaryTextColor = 4959 ContrastColorUtil.findContrastColorAgainstDark( 4960 mForegroundColor, 4961 backgroundColor, 4962 true /* findFG */, 4963 4.5f); 4964 mPrimaryTextColor = ContrastColorUtil.changeColorLightness( 4965 mSecondaryTextColor, -LIGHTNESS_TEXT_DIFFERENCE_DARK); 4966 } 4967 } else { 4968 mPrimaryTextColor = mForegroundColor; 4969 mSecondaryTextColor = ContrastColorUtil.changeColorLightness( 4970 mPrimaryTextColor, backgroundLight ? LIGHTNESS_TEXT_DIFFERENCE_LIGHT 4971 : LIGHTNESS_TEXT_DIFFERENCE_DARK); 4972 if (ContrastColorUtil.calculateContrast(mSecondaryTextColor, 4973 backgroundColor) < 4.5f) { 4974 // oh well the secondary is not good enough 4975 if (backgroundLight) { 4976 mSecondaryTextColor = ContrastColorUtil.findContrastColor( 4977 mSecondaryTextColor, 4978 backgroundColor, 4979 true /* findFG */, 4980 4.5f); 4981 } else { 4982 mSecondaryTextColor 4983 = ContrastColorUtil.findContrastColorAgainstDark( 4984 mSecondaryTextColor, 4985 backgroundColor, 4986 true /* findFG */, 4987 4.5f); 4988 } 4989 mPrimaryTextColor = ContrastColorUtil.changeColorLightness( 4990 mSecondaryTextColor, backgroundLight 4991 ? -LIGHTNESS_TEXT_DIFFERENCE_LIGHT 4992 : -LIGHTNESS_TEXT_DIFFERENCE_DARK); 4993 } 4994 } 4995 } 4996 } 4997 } 4998 updateBackgroundColor(RemoteViews contentView, StandardTemplateParams p)4999 private void updateBackgroundColor(RemoteViews contentView, 5000 StandardTemplateParams p) { 5001 if (isColorized(p)) { 5002 contentView.setInt(R.id.status_bar_latest_event_content, "setBackgroundColor", 5003 getBackgroundColor(p)); 5004 } else { 5005 // Clear it! 5006 contentView.setInt(R.id.status_bar_latest_event_content, "setBackgroundResource", 5007 0); 5008 } 5009 } 5010 5011 /** 5012 * @param remoteView the remote view to update the minheight in 5013 * @param hasMinHeight does it have a mimHeight 5014 * @hide 5015 */ setContentMinHeight(RemoteViews remoteView, boolean hasMinHeight)5016 void setContentMinHeight(RemoteViews remoteView, boolean hasMinHeight) { 5017 int minHeight = 0; 5018 if (hasMinHeight) { 5019 // we need to set the minHeight of the notification 5020 minHeight = mContext.getResources().getDimensionPixelSize( 5021 com.android.internal.R.dimen.notification_min_content_height); 5022 } 5023 remoteView.setInt(R.id.notification_main_column, "setMinimumHeight", minHeight); 5024 } 5025 handleProgressBar(RemoteViews contentView, Bundle ex, StandardTemplateParams p)5026 private boolean handleProgressBar(RemoteViews contentView, Bundle ex, 5027 StandardTemplateParams p) { 5028 final int max = ex.getInt(EXTRA_PROGRESS_MAX, 0); 5029 final int progress = ex.getInt(EXTRA_PROGRESS, 0); 5030 final boolean ind = ex.getBoolean(EXTRA_PROGRESS_INDETERMINATE); 5031 if (p.hasProgress && (max != 0 || ind)) { 5032 contentView.setViewVisibility(com.android.internal.R.id.progress, View.VISIBLE); 5033 contentView.setProgressBar( 5034 R.id.progress, max, progress, ind); 5035 contentView.setProgressBackgroundTintList( 5036 R.id.progress, ColorStateList.valueOf(mContext.getColor( 5037 R.color.notification_progress_background_color))); 5038 if (getRawColor(p) != COLOR_DEFAULT) { 5039 int color = isColorized(p) ? getPrimaryTextColor(p) : resolveContrastColor(p); 5040 ColorStateList colorStateList = ColorStateList.valueOf(color); 5041 contentView.setProgressTintList(R.id.progress, colorStateList); 5042 contentView.setProgressIndeterminateTintList(R.id.progress, colorStateList); 5043 } 5044 return true; 5045 } else { 5046 contentView.setViewVisibility(R.id.progress, View.GONE); 5047 return false; 5048 } 5049 } 5050 bindLargeIconAndReply(RemoteViews contentView, StandardTemplateParams p, TemplateBindResult result)5051 private void bindLargeIconAndReply(RemoteViews contentView, StandardTemplateParams p, 5052 TemplateBindResult result) { 5053 boolean largeIconShown = bindLargeIcon(contentView, p); 5054 boolean replyIconShown = bindReplyIcon(contentView, p); 5055 boolean iconContainerVisible = largeIconShown || replyIconShown; 5056 contentView.setViewVisibility(R.id.right_icon_container, 5057 iconContainerVisible ? View.VISIBLE : View.GONE); 5058 int marginEnd = calculateMarginEnd(largeIconShown, replyIconShown); 5059 contentView.setViewLayoutMarginEnd(R.id.line1, marginEnd); 5060 contentView.setViewLayoutMarginEnd(R.id.text, marginEnd); 5061 contentView.setViewLayoutMarginEnd(R.id.progress, marginEnd); 5062 if (result != null) { 5063 result.setIconMarginEnd(marginEnd); 5064 result.setRightIconContainerVisible(iconContainerVisible); 5065 } 5066 } 5067 calculateMarginEnd(boolean largeIconShown, boolean replyIconShown)5068 private int calculateMarginEnd(boolean largeIconShown, boolean replyIconShown) { 5069 int marginEnd = 0; 5070 int contentMargin = mContext.getResources().getDimensionPixelSize( 5071 R.dimen.notification_content_margin_end); 5072 int iconSize = mContext.getResources().getDimensionPixelSize( 5073 R.dimen.notification_right_icon_size); 5074 if (replyIconShown) { 5075 // The size of the reply icon 5076 marginEnd += iconSize; 5077 5078 int replyInset = mContext.getResources().getDimensionPixelSize( 5079 R.dimen.notification_reply_inset); 5080 // We're subtracting the inset of the reply icon to make sure it's 5081 // aligned nicely on the right, and remove it from the following padding 5082 marginEnd -= replyInset * 2; 5083 } 5084 if (largeIconShown) { 5085 // adding size of the right icon 5086 marginEnd += iconSize; 5087 5088 if (replyIconShown) { 5089 // We also add some padding to the reply icon if it's around 5090 marginEnd += contentMargin; 5091 } 5092 } 5093 if (replyIconShown || largeIconShown) { 5094 // The padding to the content 5095 marginEnd += contentMargin; 5096 } 5097 return marginEnd; 5098 } 5099 5100 /** 5101 * Bind the large icon. 5102 * @return if the largeIcon is visible 5103 */ bindLargeIcon(RemoteViews contentView, StandardTemplateParams p)5104 private boolean bindLargeIcon(RemoteViews contentView, StandardTemplateParams p) { 5105 if (mN.mLargeIcon == null && mN.largeIcon != null) { 5106 mN.mLargeIcon = Icon.createWithBitmap(mN.largeIcon); 5107 } 5108 boolean showLargeIcon = mN.mLargeIcon != null && !p.hideLargeIcon; 5109 if (showLargeIcon) { 5110 contentView.setViewVisibility(R.id.right_icon, View.VISIBLE); 5111 contentView.setImageViewIcon(R.id.right_icon, mN.mLargeIcon); 5112 processLargeLegacyIcon(mN.mLargeIcon, contentView, p); 5113 } 5114 return showLargeIcon; 5115 } 5116 5117 /** 5118 * Bind the reply icon. 5119 * @return if the reply icon is visible 5120 */ bindReplyIcon(RemoteViews contentView, StandardTemplateParams p)5121 private boolean bindReplyIcon(RemoteViews contentView, StandardTemplateParams p) { 5122 boolean actionVisible = !p.hideReplyIcon; 5123 Action action = null; 5124 if (actionVisible) { 5125 action = findReplyAction(); 5126 actionVisible = action != null; 5127 } 5128 if (actionVisible) { 5129 contentView.setViewVisibility(R.id.reply_icon_action, View.VISIBLE); 5130 contentView.setDrawableTint(R.id.reply_icon_action, 5131 false /* targetBackground */, 5132 getNeutralColor(p), 5133 PorterDuff.Mode.SRC_ATOP); 5134 contentView.setOnClickPendingIntent(R.id.reply_icon_action, action.actionIntent); 5135 contentView.setRemoteInputs(R.id.reply_icon_action, action.mRemoteInputs); 5136 } else { 5137 contentView.setRemoteInputs(R.id.reply_icon_action, null); 5138 } 5139 contentView.setViewVisibility(R.id.reply_icon_action, 5140 actionVisible ? View.VISIBLE : View.GONE); 5141 return actionVisible; 5142 } 5143 findReplyAction()5144 private Action findReplyAction() { 5145 ArrayList<Action> actions = mActions; 5146 if (mOriginalActions != null) { 5147 actions = mOriginalActions; 5148 } 5149 int numActions = actions.size(); 5150 for (int i = 0; i < numActions; i++) { 5151 Action action = actions.get(i); 5152 if (hasValidRemoteInput(action)) { 5153 return action; 5154 } 5155 } 5156 return null; 5157 } 5158 bindNotificationHeader(RemoteViews contentView, StandardTemplateParams p)5159 private void bindNotificationHeader(RemoteViews contentView, StandardTemplateParams p) { 5160 bindSmallIcon(contentView, p); 5161 bindHeaderAppName(contentView, p); 5162 bindHeaderText(contentView, p); 5163 bindHeaderTextSecondary(contentView, p); 5164 bindHeaderChronometerAndTime(contentView, p); 5165 bindProfileBadge(contentView, p); 5166 bindAlertedIcon(contentView, p); 5167 bindActivePermissions(contentView, p); 5168 bindExpandButton(contentView, p); 5169 mN.mUsesStandardHeader = true; 5170 } 5171 bindActivePermissions(RemoteViews contentView, StandardTemplateParams p)5172 private void bindActivePermissions(RemoteViews contentView, StandardTemplateParams p) { 5173 int color = getNeutralColor(p); 5174 contentView.setDrawableTint(R.id.camera, false, color, PorterDuff.Mode.SRC_ATOP); 5175 contentView.setDrawableTint(R.id.mic, false, color, PorterDuff.Mode.SRC_ATOP); 5176 contentView.setDrawableTint(R.id.overlay, false, color, PorterDuff.Mode.SRC_ATOP); 5177 } 5178 bindExpandButton(RemoteViews contentView, StandardTemplateParams p)5179 private void bindExpandButton(RemoteViews contentView, StandardTemplateParams p) { 5180 int color = isColorized(p) ? getPrimaryTextColor(p) : getSecondaryTextColor(p); 5181 contentView.setDrawableTint(R.id.expand_button, false, color, 5182 PorterDuff.Mode.SRC_ATOP); 5183 contentView.setInt(R.id.expand_button, "setOriginalNotificationColor", 5184 color); 5185 } 5186 bindHeaderChronometerAndTime(RemoteViews contentView, StandardTemplateParams p)5187 private void bindHeaderChronometerAndTime(RemoteViews contentView, 5188 StandardTemplateParams p) { 5189 if (showsTimeOrChronometer()) { 5190 contentView.setViewVisibility(R.id.time_divider, View.VISIBLE); 5191 setTextViewColorSecondary(contentView, R.id.time_divider, p); 5192 if (mN.extras.getBoolean(EXTRA_SHOW_CHRONOMETER)) { 5193 contentView.setViewVisibility(R.id.chronometer, View.VISIBLE); 5194 contentView.setLong(R.id.chronometer, "setBase", 5195 mN.when + (SystemClock.elapsedRealtime() - System.currentTimeMillis())); 5196 contentView.setBoolean(R.id.chronometer, "setStarted", true); 5197 boolean countsDown = mN.extras.getBoolean(EXTRA_CHRONOMETER_COUNT_DOWN); 5198 contentView.setChronometerCountDown(R.id.chronometer, countsDown); 5199 setTextViewColorSecondary(contentView, R.id.chronometer, p); 5200 } else { 5201 contentView.setViewVisibility(R.id.time, View.VISIBLE); 5202 contentView.setLong(R.id.time, "setTime", mN.when); 5203 setTextViewColorSecondary(contentView, R.id.time, p); 5204 } 5205 } else { 5206 // We still want a time to be set but gone, such that we can show and hide it 5207 // on demand in case it's a child notification without anything in the header 5208 contentView.setLong(R.id.time, "setTime", mN.when != 0 ? mN.when : mN.creationTime); 5209 } 5210 } 5211 bindHeaderText(RemoteViews contentView, StandardTemplateParams p)5212 private void bindHeaderText(RemoteViews contentView, StandardTemplateParams p) { 5213 CharSequence summaryText = p.summaryText; 5214 if (summaryText == null && mStyle != null && mStyle.mSummaryTextSet 5215 && mStyle.hasSummaryInHeader()) { 5216 summaryText = mStyle.mSummaryText; 5217 } 5218 if (summaryText == null 5219 && mContext.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N 5220 && mN.extras.getCharSequence(EXTRA_INFO_TEXT) != null) { 5221 summaryText = mN.extras.getCharSequence(EXTRA_INFO_TEXT); 5222 } 5223 if (summaryText != null) { 5224 // TODO: Remove the span entirely to only have the string with propper formating. 5225 contentView.setTextViewText(R.id.header_text, processTextSpans( 5226 processLegacyText(summaryText))); 5227 setTextViewColorSecondary(contentView, R.id.header_text, p); 5228 contentView.setViewVisibility(R.id.header_text, View.VISIBLE); 5229 contentView.setViewVisibility(R.id.header_text_divider, View.VISIBLE); 5230 setTextViewColorSecondary(contentView, R.id.header_text_divider, p); 5231 } 5232 } 5233 bindHeaderTextSecondary(RemoteViews contentView, StandardTemplateParams p)5234 private void bindHeaderTextSecondary(RemoteViews contentView, StandardTemplateParams p) { 5235 if (!TextUtils.isEmpty(p.headerTextSecondary)) { 5236 contentView.setTextViewText(R.id.header_text_secondary, processTextSpans( 5237 processLegacyText(p.headerTextSecondary))); 5238 setTextViewColorSecondary(contentView, R.id.header_text_secondary, p); 5239 contentView.setViewVisibility(R.id.header_text_secondary, View.VISIBLE); 5240 contentView.setViewVisibility(R.id.header_text_secondary_divider, View.VISIBLE); 5241 setTextViewColorSecondary(contentView, R.id.header_text_secondary_divider, p); 5242 } 5243 } 5244 5245 /** 5246 * @hide 5247 */ 5248 @UnsupportedAppUsage loadHeaderAppName()5249 public String loadHeaderAppName() { 5250 CharSequence name = null; 5251 final PackageManager pm = mContext.getPackageManager(); 5252 if (mN.extras.containsKey(EXTRA_SUBSTITUTE_APP_NAME)) { 5253 // only system packages which lump together a bunch of unrelated stuff 5254 // may substitute a different name to make the purpose of the 5255 // notification more clear. the correct package label should always 5256 // be accessible via SystemUI. 5257 final String pkg = mContext.getPackageName(); 5258 final String subName = mN.extras.getString(EXTRA_SUBSTITUTE_APP_NAME); 5259 if (PackageManager.PERMISSION_GRANTED == pm.checkPermission( 5260 android.Manifest.permission.SUBSTITUTE_NOTIFICATION_APP_NAME, pkg)) { 5261 name = subName; 5262 } else { 5263 Log.w(TAG, "warning: pkg " 5264 + pkg + " attempting to substitute app name '" + subName 5265 + "' without holding perm " 5266 + android.Manifest.permission.SUBSTITUTE_NOTIFICATION_APP_NAME); 5267 } 5268 } 5269 if (TextUtils.isEmpty(name)) { 5270 name = pm.getApplicationLabel(mContext.getApplicationInfo()); 5271 } 5272 if (TextUtils.isEmpty(name)) { 5273 // still nothing? 5274 return null; 5275 } 5276 5277 return String.valueOf(name); 5278 } bindHeaderAppName(RemoteViews contentView, StandardTemplateParams p)5279 private void bindHeaderAppName(RemoteViews contentView, StandardTemplateParams p) { 5280 contentView.setTextViewText(R.id.app_name_text, loadHeaderAppName()); 5281 if (isColorized(p)) { 5282 setTextViewColorPrimary(contentView, R.id.app_name_text, p); 5283 } else { 5284 contentView.setTextColor(R.id.app_name_text, getSecondaryTextColor(p)); 5285 } 5286 } 5287 isColorized(StandardTemplateParams p)5288 private boolean isColorized(StandardTemplateParams p) { 5289 return p.allowColorization && mN.isColorized(); 5290 } 5291 bindSmallIcon(RemoteViews contentView, StandardTemplateParams p)5292 private void bindSmallIcon(RemoteViews contentView, StandardTemplateParams p) { 5293 if (mN.mSmallIcon == null && mN.icon != 0) { 5294 mN.mSmallIcon = Icon.createWithResource(mContext, mN.icon); 5295 } 5296 contentView.setImageViewIcon(R.id.icon, mN.mSmallIcon); 5297 contentView.setInt(R.id.icon, "setImageLevel", mN.iconLevel); 5298 processSmallIconColor(mN.mSmallIcon, contentView, p); 5299 } 5300 5301 /** 5302 * @return true if the built notification will show the time or the chronometer; false 5303 * otherwise 5304 */ showsTimeOrChronometer()5305 private boolean showsTimeOrChronometer() { 5306 return mN.showsTime() || mN.showsChronometer(); 5307 } 5308 resetStandardTemplateWithActions(RemoteViews big)5309 private void resetStandardTemplateWithActions(RemoteViews big) { 5310 // actions_container is only reset when there are no actions to avoid focus issues with 5311 // remote inputs. 5312 big.setViewVisibility(R.id.actions, View.GONE); 5313 big.removeAllViews(R.id.actions); 5314 5315 big.setViewVisibility(R.id.notification_material_reply_container, View.GONE); 5316 big.setTextViewText(R.id.notification_material_reply_text_1, null); 5317 big.setViewVisibility(R.id.notification_material_reply_text_1_container, View.GONE); 5318 big.setViewVisibility(R.id.notification_material_reply_progress, View.GONE); 5319 5320 big.setViewVisibility(R.id.notification_material_reply_text_2, View.GONE); 5321 big.setTextViewText(R.id.notification_material_reply_text_2, null); 5322 big.setViewVisibility(R.id.notification_material_reply_text_3, View.GONE); 5323 big.setTextViewText(R.id.notification_material_reply_text_3, null); 5324 5325 big.setViewLayoutMarginBottomDimen(R.id.notification_action_list_margin_target, 5326 R.dimen.notification_content_margin); 5327 } 5328 applyStandardTemplateWithActions(int layoutId, TemplateBindResult result)5329 private RemoteViews applyStandardTemplateWithActions(int layoutId, 5330 TemplateBindResult result) { 5331 return applyStandardTemplateWithActions(layoutId, mParams.reset().fillTextsFrom(this), 5332 result); 5333 } 5334 filterOutContextualActions( List<Notification.Action> actions)5335 private static List<Notification.Action> filterOutContextualActions( 5336 List<Notification.Action> actions) { 5337 List<Notification.Action> nonContextualActions = new ArrayList<>(); 5338 for (Notification.Action action : actions) { 5339 if (!action.isContextual()) { 5340 nonContextualActions.add(action); 5341 } 5342 } 5343 return nonContextualActions; 5344 } 5345 applyStandardTemplateWithActions(int layoutId, StandardTemplateParams p, TemplateBindResult result)5346 private RemoteViews applyStandardTemplateWithActions(int layoutId, 5347 StandardTemplateParams p, TemplateBindResult result) { 5348 RemoteViews big = applyStandardTemplate(layoutId, p, result); 5349 5350 resetStandardTemplateWithActions(big); 5351 5352 boolean validRemoteInput = false; 5353 5354 // In the UI contextual actions appear separately from the standard actions, so we 5355 // filter them out here. 5356 List<Notification.Action> nonContextualActions = filterOutContextualActions(mActions); 5357 5358 int N = nonContextualActions.size(); 5359 boolean emphazisedMode = mN.fullScreenIntent != null; 5360 big.setBoolean(R.id.actions, "setEmphasizedMode", emphazisedMode); 5361 if (N > 0) { 5362 big.setViewVisibility(R.id.actions_container, View.VISIBLE); 5363 big.setViewVisibility(R.id.actions, View.VISIBLE); 5364 big.setViewLayoutMarginBottomDimen(R.id.notification_action_list_margin_target, 0); 5365 if (N>MAX_ACTION_BUTTONS) N=MAX_ACTION_BUTTONS; 5366 for (int i=0; i<N; i++) { 5367 Action action = nonContextualActions.get(i); 5368 5369 boolean actionHasValidInput = hasValidRemoteInput(action); 5370 validRemoteInput |= actionHasValidInput; 5371 5372 final RemoteViews button = generateActionButton(action, emphazisedMode, p); 5373 if (actionHasValidInput && !emphazisedMode) { 5374 // Clear the drawable 5375 button.setInt(R.id.action0, "setBackgroundResource", 0); 5376 } 5377 big.addView(R.id.actions, button); 5378 } 5379 } else { 5380 big.setViewVisibility(R.id.actions_container, View.GONE); 5381 } 5382 5383 RemoteInputHistoryItem[] replyText = getParcelableArrayFromBundle( 5384 mN.extras, EXTRA_REMOTE_INPUT_HISTORY_ITEMS, RemoteInputHistoryItem.class); 5385 if (validRemoteInput && replyText != null && replyText.length > 0 5386 && !TextUtils.isEmpty(replyText[0].getText()) 5387 && p.maxRemoteInputHistory > 0) { 5388 boolean showSpinner = mN.extras.getBoolean(EXTRA_SHOW_REMOTE_INPUT_SPINNER); 5389 big.setViewVisibility(R.id.notification_material_reply_container, View.VISIBLE); 5390 big.setViewVisibility(R.id.notification_material_reply_text_1_container, 5391 View.VISIBLE); 5392 big.setTextViewText(R.id.notification_material_reply_text_1, 5393 processTextSpans(replyText[0].getText())); 5394 setTextViewColorSecondary(big, R.id.notification_material_reply_text_1, p); 5395 big.setViewVisibility(R.id.notification_material_reply_progress, 5396 showSpinner ? View.VISIBLE : View.GONE); 5397 big.setProgressIndeterminateTintList( 5398 R.id.notification_material_reply_progress, 5399 ColorStateList.valueOf( 5400 isColorized(p) ? getPrimaryTextColor(p) : resolveContrastColor(p))); 5401 5402 if (replyText.length > 1 && !TextUtils.isEmpty(replyText[1].getText()) 5403 && p.maxRemoteInputHistory > 1) { 5404 big.setViewVisibility(R.id.notification_material_reply_text_2, View.VISIBLE); 5405 big.setTextViewText(R.id.notification_material_reply_text_2, 5406 processTextSpans(replyText[1].getText())); 5407 setTextViewColorSecondary(big, R.id.notification_material_reply_text_2, p); 5408 5409 if (replyText.length > 2 && !TextUtils.isEmpty(replyText[2].getText()) 5410 && p.maxRemoteInputHistory > 2) { 5411 big.setViewVisibility( 5412 R.id.notification_material_reply_text_3, View.VISIBLE); 5413 big.setTextViewText(R.id.notification_material_reply_text_3, 5414 processTextSpans(replyText[2].getText())); 5415 setTextViewColorSecondary(big, R.id.notification_material_reply_text_3, p); 5416 } 5417 } 5418 } 5419 5420 return big; 5421 } 5422 hasValidRemoteInput(Action action)5423 private boolean hasValidRemoteInput(Action action) { 5424 if (TextUtils.isEmpty(action.title) || action.actionIntent == null) { 5425 // Weird actions 5426 return false; 5427 } 5428 5429 RemoteInput[] remoteInputs = action.getRemoteInputs(); 5430 if (remoteInputs == null) { 5431 return false; 5432 } 5433 5434 for (RemoteInput r : remoteInputs) { 5435 CharSequence[] choices = r.getChoices(); 5436 if (r.getAllowFreeFormInput() || (choices != null && choices.length != 0)) { 5437 return true; 5438 } 5439 } 5440 return false; 5441 } 5442 5443 /** 5444 * Construct a RemoteViews for the final 1U notification layout. In order: 5445 * 1. Custom contentView from the caller 5446 * 2. Style's proposed content view 5447 * 3. Standard template view 5448 */ createContentView()5449 public RemoteViews createContentView() { 5450 return createContentView(false /* increasedheight */ ); 5451 } 5452 5453 /** 5454 * Construct a RemoteViews for the smaller content view. 5455 * 5456 * @param increasedHeight true if this layout be created with an increased height. Some 5457 * styles may support showing more then just that basic 1U size 5458 * and the system may decide to render important notifications 5459 * slightly bigger even when collapsed. 5460 * 5461 * @hide 5462 */ createContentView(boolean increasedHeight)5463 public RemoteViews createContentView(boolean increasedHeight) { 5464 if (mN.contentView != null && useExistingRemoteView()) { 5465 return mN.contentView; 5466 } else if (mStyle != null) { 5467 final RemoteViews styleView = mStyle.makeContentView(increasedHeight); 5468 if (styleView != null) { 5469 return styleView; 5470 } 5471 } 5472 return applyStandardTemplate(getBaseLayoutResource(), null /* result */); 5473 } 5474 useExistingRemoteView()5475 private boolean useExistingRemoteView() { 5476 return mStyle == null || (!mStyle.displayCustomViewInline() 5477 && !mRebuildStyledRemoteViews); 5478 } 5479 5480 /** 5481 * Construct a RemoteViews for the final big notification layout. 5482 */ createBigContentView()5483 public RemoteViews createBigContentView() { 5484 RemoteViews result = null; 5485 if (mN.bigContentView != null && useExistingRemoteView()) { 5486 return mN.bigContentView; 5487 } else if (mStyle != null) { 5488 result = mStyle.makeBigContentView(); 5489 hideLine1Text(result); 5490 } else if (mActions.size() != 0) { 5491 result = applyStandardTemplateWithActions(getBigBaseLayoutResource(), 5492 null /* result */); 5493 } 5494 makeHeaderExpanded(result); 5495 return result; 5496 } 5497 5498 /** 5499 * Construct a RemoteViews for the final notification header only. This will not be 5500 * colorized. 5501 * 5502 * @hide 5503 */ makeNotificationHeader()5504 public RemoteViews makeNotificationHeader() { 5505 return makeNotificationHeader(mParams.reset().fillTextsFrom(this)); 5506 } 5507 5508 /** 5509 * Construct a RemoteViews for the final notification header only. This will not be 5510 * colorized. 5511 * 5512 * @param p the template params to inflate this with 5513 */ makeNotificationHeader(StandardTemplateParams p)5514 private RemoteViews makeNotificationHeader(StandardTemplateParams p) { 5515 // Headers on their own are never colorized 5516 p.disallowColorization(); 5517 RemoteViews header = new BuilderRemoteViews(mContext.getApplicationInfo(), 5518 R.layout.notification_template_header); 5519 resetNotificationHeader(header); 5520 bindNotificationHeader(header, p); 5521 return header; 5522 } 5523 5524 /** 5525 * Construct a RemoteViews for the ambient version of the notification. 5526 * 5527 * @hide 5528 */ makeAmbientNotification()5529 public RemoteViews makeAmbientNotification() { 5530 RemoteViews headsUpContentView = createHeadsUpContentView(false /* increasedHeight */); 5531 if (headsUpContentView != null) { 5532 return headsUpContentView; 5533 } 5534 return createContentView(); 5535 } 5536 hideLine1Text(RemoteViews result)5537 private void hideLine1Text(RemoteViews result) { 5538 if (result != null) { 5539 result.setViewVisibility(R.id.text_line_1, View.GONE); 5540 } 5541 } 5542 5543 /** 5544 * Adapt the Notification header if this view is used as an expanded view. 5545 * 5546 * @hide 5547 */ makeHeaderExpanded(RemoteViews result)5548 public static void makeHeaderExpanded(RemoteViews result) { 5549 if (result != null) { 5550 result.setBoolean(R.id.notification_header, "setExpanded", true); 5551 } 5552 } 5553 5554 /** 5555 * Construct a RemoteViews for the final heads-up notification layout. 5556 * 5557 * @param increasedHeight true if this layout be created with an increased height. Some 5558 * styles may support showing more then just that basic 1U size 5559 * and the system may decide to render important notifications 5560 * slightly bigger even when collapsed. 5561 * 5562 * @hide 5563 */ createHeadsUpContentView(boolean increasedHeight)5564 public RemoteViews createHeadsUpContentView(boolean increasedHeight) { 5565 if (mN.headsUpContentView != null && useExistingRemoteView()) { 5566 return mN.headsUpContentView; 5567 } else if (mStyle != null) { 5568 final RemoteViews styleView = mStyle.makeHeadsUpContentView(increasedHeight); 5569 if (styleView != null) { 5570 return styleView; 5571 } 5572 } else if (mActions.size() == 0) { 5573 return null; 5574 } 5575 5576 // We only want at most a single remote input history to be shown here, otherwise 5577 // the content would become squished. 5578 StandardTemplateParams p = mParams.reset().fillTextsFrom(this) 5579 .setMaxRemoteInputHistory(1); 5580 return applyStandardTemplateWithActions(getBigBaseLayoutResource(), 5581 p, 5582 null /* result */); 5583 } 5584 5585 /** 5586 * Construct a RemoteViews for the final heads-up notification layout. 5587 */ createHeadsUpContentView()5588 public RemoteViews createHeadsUpContentView() { 5589 return createHeadsUpContentView(false /* useIncreasedHeight */); 5590 } 5591 5592 /** 5593 * Construct a RemoteViews for the display in public contexts like on the lockscreen. 5594 * 5595 * @param isLowPriority is this notification low priority 5596 * @hide 5597 */ 5598 @UnsupportedAppUsage makePublicContentView(boolean isLowPriority)5599 public RemoteViews makePublicContentView(boolean isLowPriority) { 5600 if (mN.publicVersion != null) { 5601 final Builder builder = recoverBuilder(mContext, mN.publicVersion); 5602 return builder.createContentView(); 5603 } 5604 Bundle savedBundle = mN.extras; 5605 Style style = mStyle; 5606 mStyle = null; 5607 Icon largeIcon = mN.mLargeIcon; 5608 mN.mLargeIcon = null; 5609 Bitmap largeIconLegacy = mN.largeIcon; 5610 mN.largeIcon = null; 5611 ArrayList<Action> actions = mActions; 5612 mActions = new ArrayList<>(); 5613 Bundle publicExtras = new Bundle(); 5614 publicExtras.putBoolean(EXTRA_SHOW_WHEN, 5615 savedBundle.getBoolean(EXTRA_SHOW_WHEN)); 5616 publicExtras.putBoolean(EXTRA_SHOW_CHRONOMETER, 5617 savedBundle.getBoolean(EXTRA_SHOW_CHRONOMETER)); 5618 publicExtras.putBoolean(EXTRA_CHRONOMETER_COUNT_DOWN, 5619 savedBundle.getBoolean(EXTRA_CHRONOMETER_COUNT_DOWN)); 5620 String appName = savedBundle.getString(EXTRA_SUBSTITUTE_APP_NAME); 5621 if (appName != null) { 5622 publicExtras.putString(EXTRA_SUBSTITUTE_APP_NAME, appName); 5623 } 5624 mN.extras = publicExtras; 5625 RemoteViews view; 5626 StandardTemplateParams params = mParams.reset().fillTextsFrom(this); 5627 if (isLowPriority) { 5628 params.forceDefaultColor(); 5629 } 5630 view = makeNotificationHeader(params); 5631 view.setBoolean(R.id.notification_header, "setExpandOnlyOnButton", true); 5632 mN.extras = savedBundle; 5633 mN.mLargeIcon = largeIcon; 5634 mN.largeIcon = largeIconLegacy; 5635 mActions = actions; 5636 mStyle = style; 5637 return view; 5638 } 5639 5640 /** 5641 * Construct a content view for the display when low - priority 5642 * 5643 * @param useRegularSubtext uses the normal subtext set if there is one available. Otherwise 5644 * a new subtext is created consisting of the content of the 5645 * notification. 5646 * @hide 5647 */ makeLowPriorityContentView(boolean useRegularSubtext)5648 public RemoteViews makeLowPriorityContentView(boolean useRegularSubtext) { 5649 StandardTemplateParams p = mParams.reset() 5650 .forceDefaultColor() 5651 .fillTextsFrom(this); 5652 if (!useRegularSubtext || TextUtils.isEmpty(mParams.summaryText)) { 5653 p.summaryText(createSummaryText()); 5654 } 5655 RemoteViews header = makeNotificationHeader(p); 5656 header.setBoolean(R.id.notification_header, "setAcceptAllTouches", true); 5657 return header; 5658 } 5659 createSummaryText()5660 private CharSequence createSummaryText() { 5661 CharSequence titleText = mN.extras.getCharSequence(Notification.EXTRA_TITLE); 5662 if (USE_ONLY_TITLE_IN_LOW_PRIORITY_SUMMARY) { 5663 return titleText; 5664 } 5665 SpannableStringBuilder summary = new SpannableStringBuilder(); 5666 if (titleText == null) { 5667 titleText = mN.extras.getCharSequence(Notification.EXTRA_TITLE_BIG); 5668 } 5669 BidiFormatter bidi = BidiFormatter.getInstance(); 5670 if (titleText != null) { 5671 summary.append(bidi.unicodeWrap(titleText)); 5672 } 5673 CharSequence contentText = mN.extras.getCharSequence(Notification.EXTRA_TEXT); 5674 if (titleText != null && contentText != null) { 5675 summary.append(bidi.unicodeWrap(mContext.getText( 5676 R.string.notification_header_divider_symbol_with_spaces))); 5677 } 5678 if (contentText != null) { 5679 summary.append(bidi.unicodeWrap(contentText)); 5680 } 5681 return summary; 5682 } 5683 generateActionButton(Action action, boolean emphazisedMode, StandardTemplateParams p)5684 private RemoteViews generateActionButton(Action action, boolean emphazisedMode, 5685 StandardTemplateParams p) { 5686 final boolean tombstone = (action.actionIntent == null); 5687 RemoteViews button = new BuilderRemoteViews(mContext.getApplicationInfo(), 5688 emphazisedMode ? getEmphasizedActionLayoutResource() 5689 : tombstone ? getActionTombstoneLayoutResource() 5690 : getActionLayoutResource()); 5691 if (!tombstone) { 5692 button.setOnClickPendingIntent(R.id.action0, action.actionIntent); 5693 } 5694 button.setContentDescription(R.id.action0, action.title); 5695 if (action.mRemoteInputs != null) { 5696 button.setRemoteInputs(R.id.action0, action.mRemoteInputs); 5697 } 5698 if (emphazisedMode) { 5699 // change the background bgColor 5700 CharSequence title = action.title; 5701 ColorStateList[] outResultColor = null; 5702 int background = resolveBackgroundColor(p); 5703 if (isLegacy()) { 5704 title = ContrastColorUtil.clearColorSpans(title); 5705 } else { 5706 outResultColor = new ColorStateList[1]; 5707 title = ensureColorSpanContrast(title, background, outResultColor); 5708 } 5709 button.setTextViewText(R.id.action0, processTextSpans(title)); 5710 setTextViewColorPrimary(button, R.id.action0, p); 5711 int rippleColor; 5712 boolean hasColorOverride = outResultColor != null && outResultColor[0] != null; 5713 if (hasColorOverride) { 5714 // There's a span spanning the full text, let's take it and use it as the 5715 // background color 5716 background = outResultColor[0].getDefaultColor(); 5717 int textColor = ContrastColorUtil.resolvePrimaryColor(mContext, 5718 background, mInNightMode); 5719 button.setTextColor(R.id.action0, textColor); 5720 rippleColor = textColor; 5721 } else if (getRawColor(p) != COLOR_DEFAULT && !isColorized(p) 5722 && mTintActionButtons && !mInNightMode) { 5723 rippleColor = resolveContrastColor(p); 5724 button.setTextColor(R.id.action0, rippleColor); 5725 } else { 5726 rippleColor = getPrimaryTextColor(p); 5727 } 5728 // We only want about 20% alpha for the ripple 5729 rippleColor = (rippleColor & 0x00ffffff) | 0x33000000; 5730 button.setColorStateList(R.id.action0, "setRippleColor", 5731 ColorStateList.valueOf(rippleColor)); 5732 button.setColorStateList(R.id.action0, "setButtonBackground", 5733 ColorStateList.valueOf(background)); 5734 button.setBoolean(R.id.action0, "setHasStroke", !hasColorOverride); 5735 } else { 5736 button.setTextViewText(R.id.action0, processTextSpans( 5737 processLegacyText(action.title))); 5738 if (isColorized(p)) { 5739 setTextViewColorPrimary(button, R.id.action0, p); 5740 } else if (getRawColor(p) != COLOR_DEFAULT && mTintActionButtons) { 5741 button.setTextColor(R.id.action0, resolveContrastColor(p)); 5742 } 5743 } 5744 button.setIntTag(R.id.action0, R.id.notification_action_index_tag, 5745 mActions.indexOf(action)); 5746 return button; 5747 } 5748 5749 /** 5750 * Ensures contrast on color spans against a background color. also returns the color of the 5751 * text if a span was found that spans over the whole text. 5752 * 5753 * @param charSequence the charSequence on which the spans are 5754 * @param background the background color to ensure the contrast against 5755 * @param outResultColor an array in which a color will be returned as the first element if 5756 * there exists a full length color span. 5757 * @return the contrasted charSequence 5758 */ ensureColorSpanContrast(CharSequence charSequence, int background, ColorStateList[] outResultColor)5759 private CharSequence ensureColorSpanContrast(CharSequence charSequence, int background, 5760 ColorStateList[] outResultColor) { 5761 if (charSequence instanceof Spanned) { 5762 Spanned ss = (Spanned) charSequence; 5763 Object[] spans = ss.getSpans(0, ss.length(), Object.class); 5764 SpannableStringBuilder builder = new SpannableStringBuilder(ss.toString()); 5765 for (Object span : spans) { 5766 Object resultSpan = span; 5767 int spanStart = ss.getSpanStart(span); 5768 int spanEnd = ss.getSpanEnd(span); 5769 boolean fullLength = (spanEnd - spanStart) == charSequence.length(); 5770 if (resultSpan instanceof CharacterStyle) { 5771 resultSpan = ((CharacterStyle) span).getUnderlying(); 5772 } 5773 if (resultSpan instanceof TextAppearanceSpan) { 5774 TextAppearanceSpan originalSpan = (TextAppearanceSpan) resultSpan; 5775 ColorStateList textColor = originalSpan.getTextColor(); 5776 if (textColor != null) { 5777 int[] colors = textColor.getColors(); 5778 int[] newColors = new int[colors.length]; 5779 for (int i = 0; i < newColors.length; i++) { 5780 newColors[i] = ContrastColorUtil.ensureLargeTextContrast( 5781 colors[i], background, mInNightMode); 5782 } 5783 textColor = new ColorStateList(textColor.getStates().clone(), 5784 newColors); 5785 if (fullLength) { 5786 outResultColor[0] = textColor; 5787 // Let's drop the color from the span 5788 textColor = null; 5789 } 5790 resultSpan = new TextAppearanceSpan( 5791 originalSpan.getFamily(), 5792 originalSpan.getTextStyle(), 5793 originalSpan.getTextSize(), 5794 textColor, 5795 originalSpan.getLinkTextColor()); 5796 } 5797 } else if (resultSpan instanceof ForegroundColorSpan) { 5798 ForegroundColorSpan originalSpan = (ForegroundColorSpan) resultSpan; 5799 int foregroundColor = originalSpan.getForegroundColor(); 5800 foregroundColor = ContrastColorUtil.ensureLargeTextContrast( 5801 foregroundColor, background, mInNightMode); 5802 if (fullLength) { 5803 outResultColor[0] = ColorStateList.valueOf(foregroundColor); 5804 resultSpan = null; 5805 } else { 5806 resultSpan = new ForegroundColorSpan(foregroundColor); 5807 } 5808 } else { 5809 resultSpan = span; 5810 } 5811 if (resultSpan != null) { 5812 builder.setSpan(resultSpan, spanStart, spanEnd, ss.getSpanFlags(span)); 5813 } 5814 } 5815 return builder; 5816 } 5817 return charSequence; 5818 } 5819 5820 /** 5821 * @return Whether we are currently building a notification from a legacy (an app that 5822 * doesn't create material notifications by itself) app. 5823 */ isLegacy()5824 private boolean isLegacy() { 5825 if (!mIsLegacyInitialized) { 5826 mIsLegacy = mContext.getApplicationInfo().targetSdkVersion 5827 < Build.VERSION_CODES.LOLLIPOP; 5828 mIsLegacyInitialized = true; 5829 } 5830 return mIsLegacy; 5831 } 5832 5833 private CharSequence processLegacyText(CharSequence charSequence) { 5834 boolean isAlreadyLightText = isLegacy() || textColorsNeedInversion(); 5835 if (isAlreadyLightText) { 5836 return getColorUtil().invertCharSequenceColors(charSequence); 5837 } else { 5838 return charSequence; 5839 } 5840 } 5841 5842 /** 5843 * Apply any necessariy colors to the small icon 5844 */ 5845 private void processSmallIconColor(Icon smallIcon, RemoteViews contentView, 5846 StandardTemplateParams p) { 5847 boolean colorable = !isLegacy() || getColorUtil().isGrayscaleIcon(mContext, smallIcon); 5848 int color; 5849 if (isColorized(p)) { 5850 color = getPrimaryTextColor(p); 5851 } else { 5852 color = resolveContrastColor(p); 5853 } 5854 if (colorable) { 5855 contentView.setDrawableTint(R.id.icon, false, color, 5856 PorterDuff.Mode.SRC_ATOP); 5857 5858 } 5859 contentView.setInt(R.id.icon, "setOriginalIconColor", 5860 colorable ? color : NotificationHeaderView.NO_COLOR); 5861 } 5862 5863 /** 5864 * Make the largeIcon dark if it's a fake smallIcon (that is, 5865 * if it's grayscale). 5866 */ 5867 // TODO: also check bounds, transparency, that sort of thing. 5868 private void processLargeLegacyIcon(Icon largeIcon, RemoteViews contentView, 5869 StandardTemplateParams p) { 5870 if (largeIcon != null && isLegacy() 5871 && getColorUtil().isGrayscaleIcon(mContext, largeIcon)) { 5872 // resolve color will fall back to the default when legacy 5873 contentView.setDrawableTint(R.id.icon, false, resolveContrastColor(p), 5874 PorterDuff.Mode.SRC_ATOP); 5875 } 5876 } 5877 5878 private void sanitizeColor() { 5879 if (mN.color != COLOR_DEFAULT) { 5880 mN.color |= 0xFF000000; // no alpha for custom colors 5881 } 5882 } 5883 5884 int resolveContrastColor(StandardTemplateParams p) { 5885 int rawColor = getRawColor(p); 5886 if (mCachedContrastColorIsFor == rawColor && mCachedContrastColor != COLOR_INVALID) { 5887 return mCachedContrastColor; 5888 } 5889 5890 int color; 5891 int background = mContext.getColor( 5892 com.android.internal.R.color.notification_material_background_color); 5893 if (rawColor == COLOR_DEFAULT) { 5894 ensureColors(p); 5895 color = ContrastColorUtil.resolveDefaultColor(mContext, background, mInNightMode); 5896 } else { 5897 color = ContrastColorUtil.resolveContrastColor(mContext, rawColor, 5898 background, mInNightMode); 5899 } 5900 if (Color.alpha(color) < 255) { 5901 // alpha doesn't go well for color filters, so let's blend it manually 5902 color = ContrastColorUtil.compositeColors(color, background); 5903 } 5904 mCachedContrastColorIsFor = rawColor; 5905 return mCachedContrastColor = color; 5906 } 5907 5908 /** 5909 * Return the raw color of this Notification, which doesn't necessarily satisfy contrast. 5910 * 5911 * @see #resolveContrastColor(StandardTemplateParams) for the contrasted color 5912 * @param p the template params to inflate this with 5913 */ 5914 private int getRawColor(StandardTemplateParams p) { 5915 if (p.forceDefaultColor) { 5916 return COLOR_DEFAULT; 5917 } 5918 return mN.color; 5919 } 5920 5921 int resolveNeutralColor() { 5922 if (mNeutralColor != COLOR_INVALID) { 5923 return mNeutralColor; 5924 } 5925 int background = mContext.getColor( 5926 com.android.internal.R.color.notification_material_background_color); 5927 mNeutralColor = ContrastColorUtil.resolveDefaultColor(mContext, background, 5928 mInNightMode); 5929 if (Color.alpha(mNeutralColor) < 255) { 5930 // alpha doesn't go well for color filters, so let's blend it manually 5931 mNeutralColor = ContrastColorUtil.compositeColors(mNeutralColor, background); 5932 } 5933 return mNeutralColor; 5934 } 5935 5936 /** 5937 * Apply the unstyled operations and return a new {@link Notification} object. 5938 * @hide 5939 */ 5940 @NonNull 5941 public Notification buildUnstyled() { 5942 if (mActions.size() > 0) { 5943 mN.actions = new Action[mActions.size()]; 5944 mActions.toArray(mN.actions); 5945 } 5946 if (!mPersonList.isEmpty()) { 5947 mN.extras.putParcelableArrayList(EXTRA_PEOPLE_LIST, mPersonList); 5948 } 5949 if (mN.bigContentView != null || mN.contentView != null 5950 || mN.headsUpContentView != null) { 5951 mN.extras.putBoolean(EXTRA_CONTAINS_CUSTOM_VIEW, true); 5952 } 5953 return mN; 5954 } 5955 5956 /** 5957 * Creates a Builder from an existing notification so further changes can be made. 5958 * @param context The context for your application / activity. 5959 * @param n The notification to create a Builder from. 5960 */ 5961 @NonNull 5962 public static Notification.Builder recoverBuilder(Context context, Notification n) { 5963 // Re-create notification context so we can access app resources. 5964 ApplicationInfo applicationInfo = n.extras.getParcelable( 5965 EXTRA_BUILDER_APPLICATION_INFO); 5966 Context builderContext; 5967 if (applicationInfo != null) { 5968 try { 5969 builderContext = context.createApplicationContext(applicationInfo, 5970 Context.CONTEXT_RESTRICTED); 5971 } catch (NameNotFoundException e) { 5972 Log.e(TAG, "ApplicationInfo " + applicationInfo + " not found"); 5973 builderContext = context; // try with our context 5974 } 5975 } else { 5976 builderContext = context; // try with given context 5977 } 5978 5979 return new Builder(builderContext, n); 5980 } 5981 5982 /** 5983 * Determines whether the platform can generate contextual actions for a notification. 5984 * By default this is true. 5985 */ 5986 @NonNull 5987 public Builder setAllowSystemGeneratedContextualActions(boolean allowed) { 5988 mN.mAllowSystemGeneratedContextualActions = allowed; 5989 return this; 5990 } 5991 5992 /** 5993 * @deprecated Use {@link #build()} instead. 5994 */ 5995 @Deprecated 5996 public Notification getNotification() { 5997 return build(); 5998 } 5999 6000 /** 6001 * Combine all of the options that have been set and return a new {@link Notification} 6002 * object. 6003 * 6004 * If this notification has {@link BubbleMetadata} attached that was created with 6005 * a shortcutId a check will be performed to ensure the shortcutId supplied to bubble 6006 * metadata matches the shortcutId set on the notification builder, if one was set. 6007 * If the shortcutId's were specified but do not match, an exception is thrown here. 6008 * 6009 * @see BubbleMetadata.Builder#Builder(String) 6010 * @see #setShortcutId(String) 6011 */ 6012 @NonNull 6013 public Notification build() { 6014 // Check shortcut id matches 6015 if (mN.mShortcutId != null 6016 && mN.mBubbleMetadata != null 6017 && mN.mBubbleMetadata.getShortcutId() != null 6018 && !mN.mShortcutId.equals(mN.mBubbleMetadata.getShortcutId())) { 6019 throw new IllegalArgumentException( 6020 "Notification and BubbleMetadata shortcut id's don't match," 6021 + " notification: " + mN.mShortcutId 6022 + " vs bubble: " + mN.mBubbleMetadata.getShortcutId()); 6023 } 6024 6025 // first, add any extras from the calling code 6026 if (mUserExtras != null) { 6027 mN.extras = getAllExtras(); 6028 } 6029 6030 mN.creationTime = System.currentTimeMillis(); 6031 6032 // lazy stuff from mContext; see comment in Builder(Context, Notification) 6033 Notification.addFieldsFromContext(mContext, mN); 6034 6035 buildUnstyled(); 6036 6037 if (mStyle != null) { 6038 mStyle.reduceImageSizes(mContext); 6039 mStyle.purgeResources(); 6040 mStyle.validate(mContext); 6041 mStyle.buildStyled(mN); 6042 } 6043 mN.reduceImageSizes(mContext); 6044 6045 if (mContext.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N 6046 && (useExistingRemoteView())) { 6047 if (mN.contentView == null) { 6048 mN.contentView = createContentView(); 6049 mN.extras.putInt(EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT, 6050 mN.contentView.getSequenceNumber()); 6051 } 6052 if (mN.bigContentView == null) { 6053 mN.bigContentView = createBigContentView(); 6054 if (mN.bigContentView != null) { 6055 mN.extras.putInt(EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT, 6056 mN.bigContentView.getSequenceNumber()); 6057 } 6058 } 6059 if (mN.headsUpContentView == null) { 6060 mN.headsUpContentView = createHeadsUpContentView(); 6061 if (mN.headsUpContentView != null) { 6062 mN.extras.putInt(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT, 6063 mN.headsUpContentView.getSequenceNumber()); 6064 } 6065 } 6066 } 6067 6068 if ((mN.defaults & DEFAULT_LIGHTS) != 0) { 6069 mN.flags |= FLAG_SHOW_LIGHTS; 6070 } 6071 6072 mN.allPendingIntents = null; 6073 6074 return mN; 6075 } 6076 6077 /** 6078 * Apply this Builder to an existing {@link Notification} object. 6079 * 6080 * @hide 6081 */ 6082 @NonNull 6083 public Notification buildInto(@NonNull Notification n) { 6084 build().cloneInto(n, true); 6085 return n; 6086 } 6087 6088 /** 6089 * Removes RemoteViews that were created for compatibility from {@param n}, if they did not 6090 * change. 6091 * 6092 * @return {@param n}, if no stripping is needed, otherwise a stripped clone of {@param n}. 6093 * 6094 * @hide 6095 */ 6096 public static Notification maybeCloneStrippedForDelivery(Notification n) { 6097 String templateClass = n.extras.getString(EXTRA_TEMPLATE); 6098 6099 // Only strip views for known Styles because we won't know how to 6100 // re-create them otherwise. 6101 if (!TextUtils.isEmpty(templateClass) 6102 && getNotificationStyleClass(templateClass) == null) { 6103 return n; 6104 } 6105 6106 // Only strip unmodified BuilderRemoteViews. 6107 boolean stripContentView = n.contentView instanceof BuilderRemoteViews && 6108 n.extras.getInt(EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT, -1) == 6109 n.contentView.getSequenceNumber(); 6110 boolean stripBigContentView = n.bigContentView instanceof BuilderRemoteViews && 6111 n.extras.getInt(EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT, -1) == 6112 n.bigContentView.getSequenceNumber(); 6113 boolean stripHeadsUpContentView = n.headsUpContentView instanceof BuilderRemoteViews && 6114 n.extras.getInt(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT, -1) == 6115 n.headsUpContentView.getSequenceNumber(); 6116 6117 // Nothing to do here, no need to clone. 6118 if (!stripContentView && !stripBigContentView && !stripHeadsUpContentView) { 6119 return n; 6120 } 6121 6122 Notification clone = n.clone(); 6123 if (stripContentView) { 6124 clone.contentView = null; 6125 clone.extras.remove(EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT); 6126 } 6127 if (stripBigContentView) { 6128 clone.bigContentView = null; 6129 clone.extras.remove(EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT); 6130 } 6131 if (stripHeadsUpContentView) { 6132 clone.headsUpContentView = null; 6133 clone.extras.remove(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT); 6134 } 6135 return clone; 6136 } 6137 6138 @UnsupportedAppUsage 6139 private int getBaseLayoutResource() { 6140 return R.layout.notification_template_material_base; 6141 } 6142 6143 private int getBigBaseLayoutResource() { 6144 return R.layout.notification_template_material_big_base; 6145 } 6146 6147 private int getBigPictureLayoutResource() { 6148 return R.layout.notification_template_material_big_picture; 6149 } 6150 6151 private int getBigTextLayoutResource() { 6152 return R.layout.notification_template_material_big_text; 6153 } 6154 6155 private int getInboxLayoutResource() { 6156 return R.layout.notification_template_material_inbox; 6157 } 6158 6159 private int getMessagingLayoutResource() { 6160 return R.layout.notification_template_material_messaging; 6161 } 6162 6163 private int getConversationLayoutResource() { 6164 return R.layout.notification_template_material_conversation; 6165 } 6166 6167 private int getActionLayoutResource() { 6168 return R.layout.notification_material_action; 6169 } 6170 6171 private int getEmphasizedActionLayoutResource() { 6172 return R.layout.notification_material_action_emphasized; 6173 } 6174 6175 private int getActionTombstoneLayoutResource() { 6176 return R.layout.notification_material_action_tombstone; 6177 } 6178 6179 private int getBackgroundColor(StandardTemplateParams p) { 6180 if (isColorized(p)) { 6181 return mBackgroundColor != COLOR_INVALID ? mBackgroundColor : getRawColor(p); 6182 } else { 6183 return COLOR_DEFAULT; 6184 } 6185 } 6186 6187 /** 6188 * Gets a neutral color that can be used for icons or similar that should not stand out. 6189 * @param p the template params to inflate this with 6190 */ 6191 private int getNeutralColor(StandardTemplateParams p) { 6192 if (isColorized(p)) { 6193 return getSecondaryTextColor(p); 6194 } else { 6195 return resolveNeutralColor(); 6196 } 6197 } 6198 6199 /** 6200 * Same as getBackgroundColor but also resolved the default color to the background. 6201 * @param p the template params to inflate this with 6202 */ 6203 private int resolveBackgroundColor(StandardTemplateParams p) { 6204 int backgroundColor = getBackgroundColor(p); 6205 if (backgroundColor == COLOR_DEFAULT) { 6206 backgroundColor = mContext.getColor( 6207 com.android.internal.R.color.notification_material_background_color); 6208 } 6209 return backgroundColor; 6210 } 6211 6212 private boolean shouldTintActionButtons() { 6213 return mTintActionButtons; 6214 } 6215 6216 private boolean textColorsNeedInversion() { 6217 if (mStyle == null || !MediaStyle.class.equals(mStyle.getClass())) { 6218 return false; 6219 } 6220 int targetSdkVersion = mContext.getApplicationInfo().targetSdkVersion; 6221 return targetSdkVersion > Build.VERSION_CODES.M 6222 && targetSdkVersion < Build.VERSION_CODES.O; 6223 } 6224 6225 /** 6226 * Set a color palette to be used as the background and textColors 6227 * 6228 * @param backgroundColor the color to be used as the background 6229 * @param foregroundColor the color to be used as the foreground 6230 * 6231 * @hide 6232 */ 6233 public void setColorPalette(int backgroundColor, int foregroundColor) { 6234 mBackgroundColor = backgroundColor; 6235 mForegroundColor = foregroundColor; 6236 mTextColorsAreForBackground = COLOR_INVALID; 6237 ensureColors(mParams.reset().fillTextsFrom(this)); 6238 } 6239 6240 /** 6241 * Forces all styled remoteViews to be built from scratch and not use any cached 6242 * RemoteViews. 6243 * This is needed for legacy apps that are baking in their remoteviews into the 6244 * notification. 6245 * 6246 * @hide 6247 */ 6248 public void setRebuildStyledRemoteViews(boolean rebuild) { 6249 mRebuildStyledRemoteViews = rebuild; 6250 } 6251 6252 /** 6253 * Get the text that should be displayed in the statusBar when heads upped. This is 6254 * usually just the app name, but may be different depending on the style. 6255 * 6256 * @param publicMode If true, return a text that is safe to display in public. 6257 * 6258 * @hide 6259 */ 6260 public CharSequence getHeadsUpStatusBarText(boolean publicMode) { 6261 if (mStyle != null && !publicMode) { 6262 CharSequence text = mStyle.getHeadsUpStatusBarText(); 6263 if (!TextUtils.isEmpty(text)) { 6264 return text; 6265 } 6266 } 6267 return loadHeaderAppName(); 6268 } 6269 6270 /** 6271 * @return if this builder uses a template 6272 * 6273 * @hide 6274 */ 6275 public boolean usesTemplate() { 6276 return (mN.contentView == null && mN.headsUpContentView == null 6277 && mN.bigContentView == null) 6278 || (mStyle != null && mStyle.displayCustomViewInline()); 6279 } 6280 } 6281 6282 /** 6283 * Reduces the image sizes to conform to a maximum allowed size. This also processes all custom 6284 * remote views. 6285 * 6286 * @hide 6287 */ 6288 void reduceImageSizes(Context context) { 6289 if (extras.getBoolean(EXTRA_REDUCED_IMAGES)) { 6290 return; 6291 } 6292 boolean isLowRam = ActivityManager.isLowRamDeviceStatic(); 6293 if (mLargeIcon != null || largeIcon != null) { 6294 Resources resources = context.getResources(); 6295 Class<? extends Style> style = getNotificationStyle(); 6296 int maxWidth = resources.getDimensionPixelSize(isLowRam 6297 ? R.dimen.notification_right_icon_size_low_ram 6298 : R.dimen.notification_right_icon_size); 6299 int maxHeight = maxWidth; 6300 if (MediaStyle.class.equals(style) 6301 || DecoratedMediaCustomViewStyle.class.equals(style)) { 6302 maxHeight = resources.getDimensionPixelSize(isLowRam 6303 ? R.dimen.notification_media_image_max_height_low_ram 6304 : R.dimen.notification_media_image_max_height); 6305 maxWidth = resources.getDimensionPixelSize(isLowRam 6306 ? R.dimen.notification_media_image_max_width_low_ram 6307 : R.dimen.notification_media_image_max_width); 6308 } 6309 if (mLargeIcon != null) { 6310 mLargeIcon.scaleDownIfNecessary(maxWidth, maxHeight); 6311 } 6312 if (largeIcon != null) { 6313 largeIcon = Icon.scaleDownIfNecessary(largeIcon, maxWidth, maxHeight); 6314 } 6315 } 6316 reduceImageSizesForRemoteView(contentView, context, isLowRam); 6317 reduceImageSizesForRemoteView(headsUpContentView, context, isLowRam); 6318 reduceImageSizesForRemoteView(bigContentView, context, isLowRam); 6319 extras.putBoolean(EXTRA_REDUCED_IMAGES, true); 6320 } 6321 6322 private void reduceImageSizesForRemoteView(RemoteViews remoteView, Context context, 6323 boolean isLowRam) { 6324 if (remoteView != null) { 6325 Resources resources = context.getResources(); 6326 int maxWidth = resources.getDimensionPixelSize(isLowRam 6327 ? R.dimen.notification_custom_view_max_image_width_low_ram 6328 : R.dimen.notification_custom_view_max_image_width); 6329 int maxHeight = resources.getDimensionPixelSize(isLowRam 6330 ? R.dimen.notification_custom_view_max_image_height_low_ram 6331 : R.dimen.notification_custom_view_max_image_height); 6332 remoteView.reduceImageSizes(maxWidth, maxHeight); 6333 } 6334 } 6335 6336 /** 6337 * @return whether this notification is a foreground service notification 6338 * @hide 6339 */ 6340 public boolean isForegroundService() { 6341 return (flags & Notification.FLAG_FOREGROUND_SERVICE) != 0; 6342 } 6343 6344 /** 6345 * @return whether this notification has a media session attached 6346 * @hide 6347 */ 6348 public boolean hasMediaSession() { 6349 return extras.getParcelable(Notification.EXTRA_MEDIA_SESSION) != null; 6350 } 6351 6352 /** 6353 * @return the style class of this notification 6354 * @hide 6355 */ 6356 public Class<? extends Notification.Style> getNotificationStyle() { 6357 String templateClass = extras.getString(Notification.EXTRA_TEMPLATE); 6358 6359 if (!TextUtils.isEmpty(templateClass)) { 6360 return Notification.getNotificationStyleClass(templateClass); 6361 } 6362 return null; 6363 } 6364 6365 /** 6366 * @return true if this notification is colorized. 6367 * 6368 * @hide 6369 */ 6370 public boolean isColorized() { 6371 if (isColorizedMedia()) { 6372 return true; 6373 } 6374 return extras.getBoolean(EXTRA_COLORIZED) 6375 && (hasColorizedPermission() || isForegroundService()); 6376 } 6377 6378 /** 6379 * Returns whether an app can colorize due to the android.permission.USE_COLORIZED_NOTIFICATIONS 6380 * permission. The permission is checked when a notification is enqueued. 6381 */ 6382 private boolean hasColorizedPermission() { 6383 return (flags & Notification.FLAG_CAN_COLORIZE) != 0; 6384 } 6385 6386 /** 6387 * @return true if this notification is colorized and it is a media notification 6388 * 6389 * @hide 6390 */ 6391 public boolean isColorizedMedia() { 6392 Class<? extends Style> style = getNotificationStyle(); 6393 if (MediaStyle.class.equals(style)) { 6394 Boolean colorized = (Boolean) extras.get(EXTRA_COLORIZED); 6395 if ((colorized == null || colorized) && hasMediaSession()) { 6396 return true; 6397 } 6398 } else if (DecoratedMediaCustomViewStyle.class.equals(style)) { 6399 if (extras.getBoolean(EXTRA_COLORIZED) && hasMediaSession()) { 6400 return true; 6401 } 6402 } 6403 return false; 6404 } 6405 6406 6407 /** 6408 * @return true if this is a media notification 6409 * 6410 * @hide 6411 */ 6412 public boolean isMediaNotification() { 6413 Class<? extends Style> style = getNotificationStyle(); 6414 if (MediaStyle.class.equals(style)) { 6415 return true; 6416 } else if (DecoratedMediaCustomViewStyle.class.equals(style)) { 6417 return true; 6418 } 6419 return false; 6420 } 6421 6422 /** 6423 * @return true if this notification is showing as a bubble 6424 * 6425 * @hide 6426 */ 6427 public boolean isBubbleNotification() { 6428 return (flags & Notification.FLAG_BUBBLE) != 0; 6429 } 6430 6431 private boolean hasLargeIcon() { 6432 return mLargeIcon != null || largeIcon != null; 6433 } 6434 6435 /** 6436 * @return true if the notification will show the time; false otherwise 6437 * @hide 6438 */ 6439 public boolean showsTime() { 6440 return when != 0 && extras.getBoolean(EXTRA_SHOW_WHEN); 6441 } 6442 6443 /** 6444 * @return true if the notification will show a chronometer; false otherwise 6445 * @hide 6446 */ 6447 public boolean showsChronometer() { 6448 return when != 0 && extras.getBoolean(EXTRA_SHOW_CHRONOMETER); 6449 } 6450 6451 /** 6452 * @removed 6453 */ 6454 @SystemApi 6455 public static Class<? extends Style> getNotificationStyleClass(String templateClass) { 6456 Class<? extends Style>[] classes = new Class[] { 6457 BigTextStyle.class, BigPictureStyle.class, InboxStyle.class, MediaStyle.class, 6458 DecoratedCustomViewStyle.class, DecoratedMediaCustomViewStyle.class, 6459 MessagingStyle.class }; 6460 for (Class<? extends Style> innerClass : classes) { 6461 if (templateClass.equals(innerClass.getName())) { 6462 return innerClass; 6463 } 6464 } 6465 return null; 6466 } 6467 6468 /** 6469 * An object that can apply a rich notification style to a {@link Notification.Builder} 6470 * object. 6471 */ 6472 public static abstract class Style { 6473 6474 /** 6475 * The number of items allowed simulatanously in the remote input history. 6476 * @hide 6477 */ 6478 static final int MAX_REMOTE_INPUT_HISTORY_LINES = 3; 6479 private CharSequence mBigContentTitle; 6480 6481 /** 6482 * @hide 6483 */ 6484 protected CharSequence mSummaryText = null; 6485 6486 /** 6487 * @hide 6488 */ 6489 protected boolean mSummaryTextSet = false; 6490 6491 protected Builder mBuilder; 6492 6493 /** 6494 * Overrides ContentTitle in the big form of the template. 6495 * This defaults to the value passed to setContentTitle(). 6496 */ 6497 protected void internalSetBigContentTitle(CharSequence title) { 6498 mBigContentTitle = title; 6499 } 6500 6501 /** 6502 * Set the first line of text after the detail section in the big form of the template. 6503 */ 6504 protected void internalSetSummaryText(CharSequence cs) { 6505 mSummaryText = cs; 6506 mSummaryTextSet = true; 6507 } 6508 6509 public void setBuilder(Builder builder) { 6510 if (mBuilder != builder) { 6511 mBuilder = builder; 6512 if (mBuilder != null) { 6513 mBuilder.setStyle(this); 6514 } 6515 } 6516 } 6517 6518 protected void checkBuilder() { 6519 if (mBuilder == null) { 6520 throw new IllegalArgumentException("Style requires a valid Builder object"); 6521 } 6522 } 6523 6524 protected RemoteViews getStandardView(int layoutId) { 6525 StandardTemplateParams p = mBuilder.mParams.reset().fillTextsFrom(mBuilder); 6526 return getStandardView(layoutId, p, null); 6527 } 6528 6529 6530 /** 6531 * Get the standard view for this style. 6532 * 6533 * @param layoutId The layout id to use. 6534 * @param p the params for this inflation. 6535 * @param result The result where template bind information is saved. 6536 * @return A remoteView for this style. 6537 * @hide 6538 */ 6539 protected RemoteViews getStandardView(int layoutId, StandardTemplateParams p, 6540 TemplateBindResult result) { 6541 checkBuilder(); 6542 6543 if (mBigContentTitle != null) { 6544 p.title = mBigContentTitle; 6545 } 6546 6547 RemoteViews contentView = mBuilder.applyStandardTemplateWithActions(layoutId, p, 6548 result); 6549 6550 if (mBigContentTitle != null && mBigContentTitle.equals("")) { 6551 contentView.setViewVisibility(R.id.line1, View.GONE); 6552 } else { 6553 contentView.setViewVisibility(R.id.line1, View.VISIBLE); 6554 } 6555 6556 return contentView; 6557 } 6558 6559 /** 6560 * Construct a Style-specific RemoteViews for the collapsed notification layout. 6561 * The default implementation has nothing additional to add. 6562 * 6563 * @param increasedHeight true if this layout be created with an increased height. 6564 * @hide 6565 */ 6566 public RemoteViews makeContentView(boolean increasedHeight) { 6567 return null; 6568 } 6569 6570 /** 6571 * Construct a Style-specific RemoteViews for the final big notification layout. 6572 * @hide 6573 */ 6574 public RemoteViews makeBigContentView() { 6575 return null; 6576 } 6577 6578 /** 6579 * Construct a Style-specific RemoteViews for the final HUN layout. 6580 * 6581 * @param increasedHeight true if this layout be created with an increased height. 6582 * @hide 6583 */ 6584 public RemoteViews makeHeadsUpContentView(boolean increasedHeight) { 6585 return null; 6586 } 6587 6588 /** 6589 * Apply any style-specific extras to this notification before shipping it out. 6590 * @hide 6591 */ 6592 public void addExtras(Bundle extras) { 6593 if (mSummaryTextSet) { 6594 extras.putCharSequence(EXTRA_SUMMARY_TEXT, mSummaryText); 6595 } 6596 if (mBigContentTitle != null) { 6597 extras.putCharSequence(EXTRA_TITLE_BIG, mBigContentTitle); 6598 } 6599 extras.putString(EXTRA_TEMPLATE, this.getClass().getName()); 6600 } 6601 6602 /** 6603 * Reconstruct the internal state of this Style object from extras. 6604 * @hide 6605 */ 6606 protected void restoreFromExtras(Bundle extras) { 6607 if (extras.containsKey(EXTRA_SUMMARY_TEXT)) { 6608 mSummaryText = extras.getCharSequence(EXTRA_SUMMARY_TEXT); 6609 mSummaryTextSet = true; 6610 } 6611 if (extras.containsKey(EXTRA_TITLE_BIG)) { 6612 mBigContentTitle = extras.getCharSequence(EXTRA_TITLE_BIG); 6613 } 6614 } 6615 6616 6617 /** 6618 * @hide 6619 */ 6620 public Notification buildStyled(Notification wip) { 6621 addExtras(wip.extras); 6622 return wip; 6623 } 6624 6625 /** 6626 * @hide 6627 */ 6628 public void purgeResources() {} 6629 6630 /** 6631 * Calls {@link android.app.Notification.Builder#build()} on the Builder this Style is 6632 * attached to. 6633 * 6634 * @return the fully constructed Notification. 6635 */ 6636 public Notification build() { 6637 checkBuilder(); 6638 return mBuilder.build(); 6639 } 6640 6641 /** 6642 * @hide 6643 * @return true if the style positions the progress bar on the second line; false if the 6644 * style hides the progress bar 6645 */ 6646 protected boolean hasProgress() { 6647 return true; 6648 } 6649 6650 /** 6651 * @hide 6652 * @return Whether we should put the summary be put into the notification header 6653 */ 6654 public boolean hasSummaryInHeader() { 6655 return true; 6656 } 6657 6658 /** 6659 * @hide 6660 * @return Whether custom content views are displayed inline in the style 6661 */ 6662 public boolean displayCustomViewInline() { 6663 return false; 6664 } 6665 6666 /** 6667 * Reduces the image sizes contained in this style. 6668 * 6669 * @hide 6670 */ 6671 public void reduceImageSizes(Context context) { 6672 } 6673 6674 /** 6675 * Validate that this style was properly composed. This is called at build time. 6676 * @hide 6677 */ 6678 public void validate(Context context) { 6679 } 6680 6681 /** 6682 * @hide 6683 */ 6684 public abstract boolean areNotificationsVisiblyDifferent(Style other); 6685 6686 /** 6687 * @return the text that should be displayed in the statusBar when heads-upped. 6688 * If {@code null} is returned, the default implementation will be used. 6689 * 6690 * @hide 6691 */ 6692 public CharSequence getHeadsUpStatusBarText() { 6693 return null; 6694 } 6695 } 6696 6697 /** 6698 * Helper class for generating large-format notifications that include a large image attachment. 6699 * 6700 * Here's how you'd set the <code>BigPictureStyle</code> on a notification: 6701 * <pre class="prettyprint"> 6702 * Notification notif = new Notification.Builder(mContext) 6703 * .setContentTitle("New photo from " + sender.toString()) 6704 * .setContentText(subject) 6705 * .setSmallIcon(R.drawable.new_post) 6706 * .setLargeIcon(aBitmap) 6707 * .setStyle(new Notification.BigPictureStyle() 6708 * .bigPicture(aBigBitmap)) 6709 * .build(); 6710 * </pre> 6711 * 6712 * @see Notification#bigContentView 6713 */ 6714 public static class BigPictureStyle extends Style { 6715 private Bitmap mPicture; 6716 private Icon mBigLargeIcon; 6717 private boolean mBigLargeIconSet = false; 6718 6719 public BigPictureStyle() { 6720 } 6721 6722 /** 6723 * @deprecated use {@code BigPictureStyle()}. 6724 */ 6725 @Deprecated 6726 public BigPictureStyle(Builder builder) { 6727 setBuilder(builder); 6728 } 6729 6730 /** 6731 * Overrides ContentTitle in the big form of the template. 6732 * This defaults to the value passed to setContentTitle(). 6733 */ 6734 public BigPictureStyle setBigContentTitle(CharSequence title) { 6735 internalSetBigContentTitle(safeCharSequence(title)); 6736 return this; 6737 } 6738 6739 /** 6740 * Set the first line of text after the detail section in the big form of the template. 6741 */ 6742 public BigPictureStyle setSummaryText(CharSequence cs) { 6743 internalSetSummaryText(safeCharSequence(cs)); 6744 return this; 6745 } 6746 6747 /** 6748 * @hide 6749 */ 6750 public Bitmap getBigPicture() { 6751 return mPicture; 6752 } 6753 6754 /** 6755 * Provide the bitmap to be used as the payload for the BigPicture notification. 6756 */ 6757 public BigPictureStyle bigPicture(Bitmap b) { 6758 mPicture = b; 6759 return this; 6760 } 6761 6762 /** 6763 * Override the large icon when the big notification is shown. 6764 */ 6765 public BigPictureStyle bigLargeIcon(Bitmap b) { 6766 return bigLargeIcon(b != null ? Icon.createWithBitmap(b) : null); 6767 } 6768 6769 /** 6770 * Override the large icon when the big notification is shown. 6771 */ 6772 public BigPictureStyle bigLargeIcon(Icon icon) { 6773 mBigLargeIconSet = true; 6774 mBigLargeIcon = icon; 6775 return this; 6776 } 6777 6778 /** @hide */ 6779 public static final int MIN_ASHMEM_BITMAP_SIZE = 128 * (1 << 10); 6780 6781 /** 6782 * @hide 6783 */ 6784 @Override 6785 public void purgeResources() { 6786 super.purgeResources(); 6787 if (mPicture != null && 6788 mPicture.isMutable() && 6789 mPicture.getAllocationByteCount() >= MIN_ASHMEM_BITMAP_SIZE) { 6790 mPicture = mPicture.createAshmemBitmap(); 6791 } 6792 if (mBigLargeIcon != null) { 6793 mBigLargeIcon.convertToAshmem(); 6794 } 6795 } 6796 6797 /** 6798 * @hide 6799 */ 6800 @Override 6801 public void reduceImageSizes(Context context) { 6802 super.reduceImageSizes(context); 6803 Resources resources = context.getResources(); 6804 boolean isLowRam = ActivityManager.isLowRamDeviceStatic(); 6805 if (mPicture != null) { 6806 int maxPictureWidth = resources.getDimensionPixelSize(isLowRam 6807 ? R.dimen.notification_big_picture_max_height_low_ram 6808 : R.dimen.notification_big_picture_max_height); 6809 int maxPictureHeight = resources.getDimensionPixelSize(isLowRam 6810 ? R.dimen.notification_big_picture_max_width_low_ram 6811 : R.dimen.notification_big_picture_max_width); 6812 mPicture = Icon.scaleDownIfNecessary(mPicture, maxPictureWidth, maxPictureHeight); 6813 } 6814 if (mBigLargeIcon != null) { 6815 int rightIconSize = resources.getDimensionPixelSize(isLowRam 6816 ? R.dimen.notification_right_icon_size_low_ram 6817 : R.dimen.notification_right_icon_size); 6818 mBigLargeIcon.scaleDownIfNecessary(rightIconSize, rightIconSize); 6819 } 6820 } 6821 6822 /** 6823 * @hide 6824 */ 6825 public RemoteViews makeBigContentView() { 6826 // Replace mN.mLargeIcon with mBigLargeIcon if mBigLargeIconSet 6827 // This covers the following cases: 6828 // 1. mBigLargeIconSet -> mBigLargeIcon (null or non-null) applies, overrides 6829 // mN.mLargeIcon 6830 // 2. !mBigLargeIconSet -> mN.mLargeIcon applies 6831 Icon oldLargeIcon = null; 6832 Bitmap largeIconLegacy = null; 6833 if (mBigLargeIconSet) { 6834 oldLargeIcon = mBuilder.mN.mLargeIcon; 6835 mBuilder.mN.mLargeIcon = mBigLargeIcon; 6836 // The legacy largeIcon might not allow us to clear the image, as it's taken in 6837 // replacement if the other one is null. Because we're restoring these legacy icons 6838 // for old listeners, this is in general non-null. 6839 largeIconLegacy = mBuilder.mN.largeIcon; 6840 mBuilder.mN.largeIcon = null; 6841 } 6842 6843 StandardTemplateParams p = mBuilder.mParams.reset().fillTextsFrom(mBuilder); 6844 RemoteViews contentView = getStandardView(mBuilder.getBigPictureLayoutResource(), 6845 p, null /* result */); 6846 if (mSummaryTextSet) { 6847 contentView.setTextViewText(R.id.text, mBuilder.processTextSpans( 6848 mBuilder.processLegacyText(mSummaryText))); 6849 mBuilder.setTextViewColorSecondary(contentView, R.id.text, p); 6850 contentView.setViewVisibility(R.id.text, View.VISIBLE); 6851 } 6852 mBuilder.setContentMinHeight(contentView, mBuilder.mN.hasLargeIcon()); 6853 6854 if (mBigLargeIconSet) { 6855 mBuilder.mN.mLargeIcon = oldLargeIcon; 6856 mBuilder.mN.largeIcon = largeIconLegacy; 6857 } 6858 6859 contentView.setImageViewBitmap(R.id.big_picture, mPicture); 6860 return contentView; 6861 } 6862 6863 /** 6864 * @hide 6865 */ 6866 public void addExtras(Bundle extras) { 6867 super.addExtras(extras); 6868 6869 if (mBigLargeIconSet) { 6870 extras.putParcelable(EXTRA_LARGE_ICON_BIG, mBigLargeIcon); 6871 } 6872 extras.putParcelable(EXTRA_PICTURE, mPicture); 6873 } 6874 6875 /** 6876 * @hide 6877 */ 6878 @Override 6879 protected void restoreFromExtras(Bundle extras) { 6880 super.restoreFromExtras(extras); 6881 6882 if (extras.containsKey(EXTRA_LARGE_ICON_BIG)) { 6883 mBigLargeIconSet = true; 6884 mBigLargeIcon = extras.getParcelable(EXTRA_LARGE_ICON_BIG); 6885 } 6886 mPicture = extras.getParcelable(EXTRA_PICTURE); 6887 } 6888 6889 /** 6890 * @hide 6891 */ 6892 @Override 6893 public boolean hasSummaryInHeader() { 6894 return false; 6895 } 6896 6897 /** 6898 * @hide 6899 * Note that we aren't actually comparing the contents of the bitmaps here, so this 6900 * is only doing a cursory inspection. Bitmaps of equal size will appear the same. 6901 */ 6902 @Override 6903 public boolean areNotificationsVisiblyDifferent(Style other) { 6904 if (other == null || getClass() != other.getClass()) { 6905 return true; 6906 } 6907 BigPictureStyle otherS = (BigPictureStyle) other; 6908 return areBitmapsObviouslyDifferent(getBigPicture(), otherS.getBigPicture()); 6909 } 6910 6911 private static boolean areBitmapsObviouslyDifferent(Bitmap a, Bitmap b) { 6912 if (a == b) { 6913 return false; 6914 } 6915 if (a == null || b == null) { 6916 return true; 6917 } 6918 return a.getWidth() != b.getWidth() 6919 || a.getHeight() != b.getHeight() 6920 || a.getConfig() != b.getConfig() 6921 || a.getGenerationId() != b.getGenerationId(); 6922 } 6923 } 6924 6925 /** 6926 * Helper class for generating large-format notifications that include a lot of text. 6927 * 6928 * Here's how you'd set the <code>BigTextStyle</code> on a notification: 6929 * <pre class="prettyprint"> 6930 * Notification notif = new Notification.Builder(mContext) 6931 * .setContentTitle("New mail from " + sender.toString()) 6932 * .setContentText(subject) 6933 * .setSmallIcon(R.drawable.new_mail) 6934 * .setLargeIcon(aBitmap) 6935 * .setStyle(new Notification.BigTextStyle() 6936 * .bigText(aVeryLongString)) 6937 * .build(); 6938 * </pre> 6939 * 6940 * @see Notification#bigContentView 6941 */ 6942 public static class BigTextStyle extends Style { 6943 6944 private CharSequence mBigText; 6945 6946 public BigTextStyle() { 6947 } 6948 6949 /** 6950 * @deprecated use {@code BigTextStyle()}. 6951 */ 6952 @Deprecated 6953 public BigTextStyle(Builder builder) { 6954 setBuilder(builder); 6955 } 6956 6957 /** 6958 * Overrides ContentTitle in the big form of the template. 6959 * This defaults to the value passed to setContentTitle(). 6960 */ 6961 public BigTextStyle setBigContentTitle(CharSequence title) { 6962 internalSetBigContentTitle(safeCharSequence(title)); 6963 return this; 6964 } 6965 6966 /** 6967 * Set the first line of text after the detail section in the big form of the template. 6968 */ 6969 public BigTextStyle setSummaryText(CharSequence cs) { 6970 internalSetSummaryText(safeCharSequence(cs)); 6971 return this; 6972 } 6973 6974 /** 6975 * Provide the longer text to be displayed in the big form of the 6976 * template in place of the content text. 6977 */ 6978 public BigTextStyle bigText(CharSequence cs) { 6979 mBigText = safeCharSequence(cs); 6980 return this; 6981 } 6982 6983 /** 6984 * @hide 6985 */ 6986 public CharSequence getBigText() { 6987 return mBigText; 6988 } 6989 6990 /** 6991 * @hide 6992 */ 6993 public void addExtras(Bundle extras) { 6994 super.addExtras(extras); 6995 6996 extras.putCharSequence(EXTRA_BIG_TEXT, mBigText); 6997 } 6998 6999 /** 7000 * @hide 7001 */ 7002 @Override 7003 protected void restoreFromExtras(Bundle extras) { 7004 super.restoreFromExtras(extras); 7005 7006 mBigText = extras.getCharSequence(EXTRA_BIG_TEXT); 7007 } 7008 7009 /** 7010 * @param increasedHeight true if this layout be created with an increased height. 7011 * 7012 * @hide 7013 */ 7014 @Override 7015 public RemoteViews makeContentView(boolean increasedHeight) { 7016 if (increasedHeight) { 7017 mBuilder.mOriginalActions = mBuilder.mActions; 7018 mBuilder.mActions = new ArrayList<>(); 7019 RemoteViews remoteViews = makeBigContentView(); 7020 mBuilder.mActions = mBuilder.mOriginalActions; 7021 mBuilder.mOriginalActions = null; 7022 return remoteViews; 7023 } 7024 return super.makeContentView(increasedHeight); 7025 } 7026 7027 /** 7028 * @hide 7029 */ 7030 @Override 7031 public RemoteViews makeHeadsUpContentView(boolean increasedHeight) { 7032 if (increasedHeight && mBuilder.mActions.size() > 0) { 7033 return makeBigContentView(); 7034 } 7035 return super.makeHeadsUpContentView(increasedHeight); 7036 } 7037 7038 /** 7039 * @hide 7040 */ 7041 public RemoteViews makeBigContentView() { 7042 StandardTemplateParams p = mBuilder.mParams.reset().fillTextsFrom(mBuilder).text(null); 7043 TemplateBindResult result = new TemplateBindResult(); 7044 RemoteViews contentView = getStandardView(mBuilder.getBigTextLayoutResource(), p, 7045 result); 7046 contentView.setInt(R.id.big_text, "setImageEndMargin", result.getIconMarginEnd()); 7047 7048 CharSequence bigTextText = mBuilder.processLegacyText(mBigText); 7049 if (TextUtils.isEmpty(bigTextText)) { 7050 // In case the bigtext is null / empty fall back to the normal text to avoid a weird 7051 // experience 7052 bigTextText = mBuilder.processLegacyText( 7053 mBuilder.getAllExtras().getCharSequence(EXTRA_TEXT)); 7054 } 7055 contentView.setTextViewText(R.id.big_text, mBuilder.processTextSpans(bigTextText)); 7056 mBuilder.setTextViewColorSecondary(contentView, R.id.big_text, p); 7057 contentView.setViewVisibility(R.id.big_text, 7058 TextUtils.isEmpty(bigTextText) ? View.GONE : View.VISIBLE); 7059 contentView.setBoolean(R.id.big_text, "setHasImage", 7060 result.isRightIconContainerVisible()); 7061 7062 return contentView; 7063 } 7064 7065 /** 7066 * @hide 7067 * Spans are ignored when comparing text for visual difference. 7068 */ 7069 @Override 7070 public boolean areNotificationsVisiblyDifferent(Style other) { 7071 if (other == null || getClass() != other.getClass()) { 7072 return true; 7073 } 7074 BigTextStyle newS = (BigTextStyle) other; 7075 return !Objects.equals(String.valueOf(getBigText()), String.valueOf(newS.getBigText())); 7076 } 7077 7078 } 7079 7080 /** 7081 * Helper class for generating large-format notifications that include multiple back-and-forth 7082 * messages of varying types between any number of people. 7083 * 7084 * <p> 7085 * If the platform does not provide large-format notifications, this method has no effect. The 7086 * user will always see the normal notification view. 7087 * 7088 * <p> 7089 * If the app is targeting Android P and above, it is required to use the {@link Person} 7090 * class in order to get an optimal rendering of the notification and its avatars. For 7091 * conversations involving multiple people, the app should also make sure that it marks the 7092 * conversation as a group with {@link #setGroupConversation(boolean)}. 7093 * 7094 * <p> 7095 * This class is a "rebuilder": It attaches to a Builder object and modifies its behavior. 7096 * Here's an example of how this may be used: 7097 * <pre class="prettyprint"> 7098 * 7099 * Person user = new Person.Builder().setIcon(userIcon).setName(userName).build(); 7100 * MessagingStyle style = new MessagingStyle(user) 7101 * .addMessage(messages[1].getText(), messages[1].getTime(), messages[1].getPerson()) 7102 * .addMessage(messages[2].getText(), messages[2].getTime(), messages[2].getPerson()) 7103 * .setGroupConversation(hasMultiplePeople()); 7104 * 7105 * Notification noti = new Notification.Builder() 7106 * .setContentTitle("2 new messages with " + sender.toString()) 7107 * .setContentText(subject) 7108 * .setSmallIcon(R.drawable.new_message) 7109 * .setLargeIcon(aBitmap) 7110 * .setStyle(style) 7111 * .build(); 7112 * </pre> 7113 */ 7114 public static class MessagingStyle extends Style { 7115 7116 /** 7117 * The maximum number of messages that will be retained in the Notification itself (the 7118 * number displayed is up to the platform). 7119 */ 7120 public static final int MAXIMUM_RETAINED_MESSAGES = 25; 7121 7122 7123 /** @hide */ 7124 public static final int CONVERSATION_TYPE_LEGACY = 0; 7125 /** @hide */ 7126 public static final int CONVERSATION_TYPE_NORMAL = 1; 7127 /** @hide */ 7128 public static final int CONVERSATION_TYPE_IMPORTANT = 2; 7129 7130 /** @hide */ 7131 @IntDef(prefix = {"CONVERSATION_TYPE_"}, value = { 7132 CONVERSATION_TYPE_LEGACY, CONVERSATION_TYPE_NORMAL, CONVERSATION_TYPE_IMPORTANT 7133 }) 7134 @Retention(RetentionPolicy.SOURCE) 7135 public @interface ConversationType {} 7136 7137 @NonNull Person mUser; 7138 @Nullable CharSequence mConversationTitle; 7139 @Nullable Icon mShortcutIcon; 7140 List<Message> mMessages = new ArrayList<>(); 7141 List<Message> mHistoricMessages = new ArrayList<>(); 7142 boolean mIsGroupConversation; 7143 @ConversationType int mConversationType = CONVERSATION_TYPE_LEGACY; 7144 int mUnreadMessageCount; 7145 7146 MessagingStyle() { 7147 } 7148 7149 /** 7150 * @param userDisplayName Required - the name to be displayed for any replies sent by the 7151 * user before the posting app reposts the notification with those messages after they've 7152 * been actually sent and in previous messages sent by the user added in 7153 * {@link #addMessage(Notification.MessagingStyle.Message)} 7154 * 7155 * @deprecated use {@code MessagingStyle(Person)} 7156 */ 7157 public MessagingStyle(@NonNull CharSequence userDisplayName) { 7158 this(new Person.Builder().setName(userDisplayName).build()); 7159 } 7160 7161 /** 7162 * @param user Required - The person displayed for any messages that are sent by the 7163 * user. Any messages added with {@link #addMessage(Notification.MessagingStyle.Message)} 7164 * who don't have a Person associated with it will be displayed as if they were sent 7165 * by this user. The user also needs to have a valid name associated with it, which will 7166 * be enforced starting in Android P. 7167 */ 7168 public MessagingStyle(@NonNull Person user) { 7169 mUser = user; 7170 } 7171 7172 /** 7173 * Validate that this style was properly composed. This is called at build time. 7174 * @hide 7175 */ 7176 @Override 7177 public void validate(Context context) { 7178 super.validate(context); 7179 if (context.getApplicationInfo().targetSdkVersion 7180 >= Build.VERSION_CODES.P && (mUser == null || mUser.getName() == null)) { 7181 throw new RuntimeException("User must be valid and have a name."); 7182 } 7183 } 7184 7185 /** 7186 * @return the text that should be displayed in the statusBar when heads upped. 7187 * If {@code null} is returned, the default implementation will be used. 7188 * 7189 * @hide 7190 */ 7191 @Override 7192 public CharSequence getHeadsUpStatusBarText() { 7193 CharSequence conversationTitle = !TextUtils.isEmpty(super.mBigContentTitle) 7194 ? super.mBigContentTitle 7195 : mConversationTitle; 7196 if (mConversationType == CONVERSATION_TYPE_LEGACY 7197 && !TextUtils.isEmpty(conversationTitle) && !hasOnlyWhiteSpaceSenders()) { 7198 return conversationTitle; 7199 } 7200 return null; 7201 } 7202 7203 /** 7204 * @return the user to be displayed for any replies sent by the user 7205 */ 7206 @NonNull 7207 public Person getUser() { 7208 return mUser; 7209 } 7210 7211 /** 7212 * Returns the name to be displayed for any replies sent by the user 7213 * 7214 * @deprecated use {@link #getUser()} instead 7215 */ 7216 public CharSequence getUserDisplayName() { 7217 return mUser.getName(); 7218 } 7219 7220 /** 7221 * Sets the title to be displayed on this conversation. May be set to {@code null}. 7222 * 7223 * <p>Starting in {@link Build.VERSION_CODES#R, this conversation title will be ignored if a 7224 * valid shortcutId is added via {@link Notification.Builder#setShortcutId(String)}. In this 7225 * case, {@link ShortcutInfo#getLongLabel()} (or, if missing, 7226 * {@link ShortcutInfo#getShortLabel()}) will be shown as the conversation title 7227 * instead. 7228 * 7229 * <p>This API's behavior was changed in SDK version {@link Build.VERSION_CODES#P}. If your 7230 * application's target version is less than {@link Build.VERSION_CODES#P}, setting a 7231 * conversation title to a non-null value will make {@link #isGroupConversation()} return 7232 * {@code true} and passing {@code null} will make it return {@code false}. In 7233 * {@link Build.VERSION_CODES#P} and beyond, use {@link #setGroupConversation(boolean)} 7234 * to set group conversation status. 7235 * 7236 * @param conversationTitle Title displayed for this conversation 7237 * @return this object for method chaining 7238 */ 7239 public MessagingStyle setConversationTitle(@Nullable CharSequence conversationTitle) { 7240 mConversationTitle = conversationTitle; 7241 return this; 7242 } 7243 7244 /** 7245 * Return the title to be displayed on this conversation. May return {@code null}. 7246 */ 7247 @Nullable 7248 public CharSequence getConversationTitle() { 7249 return mConversationTitle; 7250 } 7251 7252 /** 7253 * Sets the icon to be displayed on the conversation, derived from the shortcutId. 7254 * 7255 * @hide 7256 */ 7257 public MessagingStyle setShortcutIcon(@Nullable Icon conversationIcon) { 7258 mShortcutIcon = conversationIcon; 7259 return this; 7260 } 7261 7262 /** 7263 * Return the icon to be displayed on this conversation, derived from the shortcutId. May 7264 * return {@code null}. 7265 * 7266 * @hide 7267 */ 7268 @Nullable 7269 public Icon getShortcutIcon() { 7270 return mShortcutIcon; 7271 } 7272 7273 /** 7274 * Sets the conversation type of this MessageStyle notification. 7275 * {@link #CONVERSATION_TYPE_LEGACY} will use the "older" layout from pre-R, 7276 * {@link #CONVERSATION_TYPE_NORMAL} will use the new "conversation" layout, and 7277 * {@link #CONVERSATION_TYPE_IMPORTANT} will add additional "important" treatments. 7278 * 7279 * @hide 7280 */ 7281 public MessagingStyle setConversationType(@ConversationType int conversationType) { 7282 mConversationType = conversationType; 7283 return this; 7284 } 7285 7286 /** @hide */ 7287 @ConversationType 7288 public int getConversationType() { 7289 return mConversationType; 7290 } 7291 7292 /** @hide */ 7293 public int getUnreadMessageCount() { 7294 return mUnreadMessageCount; 7295 } 7296 7297 /** @hide */ 7298 public MessagingStyle setUnreadMessageCount(int unreadMessageCount) { 7299 mUnreadMessageCount = unreadMessageCount; 7300 return this; 7301 } 7302 7303 /** 7304 * Adds a message for display by this notification. Convenience call for a simple 7305 * {@link Message} in {@link #addMessage(Notification.MessagingStyle.Message)}. 7306 * @param text A {@link CharSequence} to be displayed as the message content 7307 * @param timestamp Time at which the message arrived 7308 * @param sender A {@link CharSequence} to be used for displaying the name of the 7309 * sender. Should be <code>null</code> for messages by the current user, in which case 7310 * the platform will insert {@link #getUserDisplayName()}. 7311 * Should be unique amongst all individuals in the conversation, and should be 7312 * consistent during re-posts of the notification. 7313 * 7314 * @see Message#Message(CharSequence, long, CharSequence) 7315 * 7316 * @return this object for method chaining 7317 * 7318 * @deprecated use {@link #addMessage(CharSequence, long, Person)} 7319 */ 7320 public MessagingStyle addMessage(CharSequence text, long timestamp, CharSequence sender) { 7321 return addMessage(text, timestamp, 7322 sender == null ? null : new Person.Builder().setName(sender).build()); 7323 } 7324 7325 /** 7326 * Adds a message for display by this notification. Convenience call for a simple 7327 * {@link Message} in {@link #addMessage(Notification.MessagingStyle.Message)}. 7328 * @param text A {@link CharSequence} to be displayed as the message content 7329 * @param timestamp Time at which the message arrived 7330 * @param sender The {@link Person} who sent the message. 7331 * Should be <code>null</code> for messages by the current user, in which case 7332 * the platform will insert the user set in {@code MessagingStyle(Person)}. 7333 * 7334 * @see Message#Message(CharSequence, long, CharSequence) 7335 * 7336 * @return this object for method chaining 7337 */ 7338 public MessagingStyle addMessage(@NonNull CharSequence text, long timestamp, 7339 @Nullable Person sender) { 7340 return addMessage(new Message(text, timestamp, sender)); 7341 } 7342 7343 /** 7344 * Adds a {@link Message} for display in this notification. 7345 * 7346 * <p>The messages should be added in chronologic order, i.e. the oldest first, 7347 * the newest last. 7348 * 7349 * @param message The {@link Message} to be displayed 7350 * @return this object for method chaining 7351 */ 7352 public MessagingStyle addMessage(Message message) { 7353 mMessages.add(message); 7354 if (mMessages.size() > MAXIMUM_RETAINED_MESSAGES) { 7355 mMessages.remove(0); 7356 } 7357 return this; 7358 } 7359 7360 /** 7361 * Adds a {@link Message} for historic context in this notification. 7362 * 7363 * <p>Messages should be added as historic if they are not the main subject of the 7364 * notification but may give context to a conversation. The system may choose to present 7365 * them only when relevant, e.g. when replying to a message through a {@link RemoteInput}. 7366 * 7367 * <p>The messages should be added in chronologic order, i.e. the oldest first, 7368 * the newest last. 7369 * 7370 * @param message The historic {@link Message} to be added 7371 * @return this object for method chaining 7372 */ 7373 public MessagingStyle addHistoricMessage(Message message) { 7374 mHistoricMessages.add(message); 7375 if (mHistoricMessages.size() > MAXIMUM_RETAINED_MESSAGES) { 7376 mHistoricMessages.remove(0); 7377 } 7378 return this; 7379 } 7380 7381 /** 7382 * Gets the list of {@code Message} objects that represent the notification 7383 */ getMessages()7384 public List<Message> getMessages() { 7385 return mMessages; 7386 } 7387 7388 /** 7389 * Gets the list of historic {@code Message}s in the notification. 7390 */ getHistoricMessages()7391 public List<Message> getHistoricMessages() { 7392 return mHistoricMessages; 7393 } 7394 7395 /** 7396 * Sets whether this conversation notification represents a group. If the app is targeting 7397 * Android P, this is required if the app wants to display the largeIcon set with 7398 * {@link Notification.Builder#setLargeIcon(Bitmap)}, otherwise it will be hidden. 7399 * 7400 * @param isGroupConversation {@code true} if the conversation represents a group, 7401 * {@code false} otherwise. 7402 * @return this object for method chaining 7403 */ setGroupConversation(boolean isGroupConversation)7404 public MessagingStyle setGroupConversation(boolean isGroupConversation) { 7405 mIsGroupConversation = isGroupConversation; 7406 return this; 7407 } 7408 7409 /** 7410 * Returns {@code true} if this notification represents a group conversation, otherwise 7411 * {@code false}. 7412 * 7413 * <p> If the application that generated this {@link MessagingStyle} targets an SDK version 7414 * less than {@link Build.VERSION_CODES#P}, this method becomes dependent on whether or 7415 * not the conversation title is set; returning {@code true} if the conversation title is 7416 * a non-null value, or {@code false} otherwise. From {@link Build.VERSION_CODES#P} forward, 7417 * this method returns what's set by {@link #setGroupConversation(boolean)} allowing for 7418 * named, non-group conversations. 7419 * 7420 * @see #setConversationTitle(CharSequence) 7421 */ isGroupConversation()7422 public boolean isGroupConversation() { 7423 // When target SDK version is < P, a non-null conversation title dictates if this is 7424 // as group conversation. 7425 if (mBuilder != null 7426 && mBuilder.mContext.getApplicationInfo().targetSdkVersion 7427 < Build.VERSION_CODES.P) { 7428 return mConversationTitle != null; 7429 } 7430 7431 return mIsGroupConversation; 7432 } 7433 7434 /** 7435 * @hide 7436 */ 7437 @Override addExtras(Bundle extras)7438 public void addExtras(Bundle extras) { 7439 super.addExtras(extras); 7440 if (mUser != null) { 7441 // For legacy usages 7442 extras.putCharSequence(EXTRA_SELF_DISPLAY_NAME, mUser.getName()); 7443 extras.putParcelable(EXTRA_MESSAGING_PERSON, mUser); 7444 } 7445 if (mConversationTitle != null) { 7446 extras.putCharSequence(EXTRA_CONVERSATION_TITLE, mConversationTitle); 7447 } 7448 if (!mMessages.isEmpty()) { extras.putParcelableArray(EXTRA_MESSAGES, 7449 Message.getBundleArrayForMessages(mMessages)); 7450 } 7451 if (!mHistoricMessages.isEmpty()) { extras.putParcelableArray(EXTRA_HISTORIC_MESSAGES, 7452 Message.getBundleArrayForMessages(mHistoricMessages)); 7453 } 7454 if (mShortcutIcon != null) { 7455 extras.putParcelable(EXTRA_CONVERSATION_ICON, mShortcutIcon); 7456 } 7457 extras.putInt(EXTRA_CONVERSATION_UNREAD_MESSAGE_COUNT, mUnreadMessageCount); 7458 7459 fixTitleAndTextExtras(extras); 7460 extras.putBoolean(EXTRA_IS_GROUP_CONVERSATION, mIsGroupConversation); 7461 } 7462 fixTitleAndTextExtras(Bundle extras)7463 private void fixTitleAndTextExtras(Bundle extras) { 7464 Message m = findLatestIncomingMessage(); 7465 CharSequence text = (m == null) ? null : m.mText; 7466 CharSequence sender = m == null ? null 7467 : m.mSender == null || TextUtils.isEmpty(m.mSender.getName()) 7468 ? mUser.getName() : m.mSender.getName(); 7469 CharSequence title; 7470 if (!TextUtils.isEmpty(mConversationTitle)) { 7471 if (!TextUtils.isEmpty(sender)) { 7472 BidiFormatter bidi = BidiFormatter.getInstance(); 7473 title = mBuilder.mContext.getString( 7474 com.android.internal.R.string.notification_messaging_title_template, 7475 bidi.unicodeWrap(mConversationTitle), bidi.unicodeWrap(sender)); 7476 } else { 7477 title = mConversationTitle; 7478 } 7479 } else { 7480 title = sender; 7481 } 7482 7483 if (title != null) { 7484 extras.putCharSequence(EXTRA_TITLE, title); 7485 } 7486 if (text != null) { 7487 extras.putCharSequence(EXTRA_TEXT, text); 7488 } 7489 } 7490 7491 /** 7492 * @hide 7493 */ 7494 @Override restoreFromExtras(Bundle extras)7495 protected void restoreFromExtras(Bundle extras) { 7496 super.restoreFromExtras(extras); 7497 7498 mUser = extras.getParcelable(EXTRA_MESSAGING_PERSON); 7499 if (mUser == null) { 7500 CharSequence displayName = extras.getCharSequence(EXTRA_SELF_DISPLAY_NAME); 7501 mUser = new Person.Builder().setName(displayName).build(); 7502 } 7503 mConversationTitle = extras.getCharSequence(EXTRA_CONVERSATION_TITLE); 7504 Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES); 7505 mMessages = Message.getMessagesFromBundleArray(messages); 7506 Parcelable[] histMessages = extras.getParcelableArray(EXTRA_HISTORIC_MESSAGES); 7507 mHistoricMessages = Message.getMessagesFromBundleArray(histMessages); 7508 mIsGroupConversation = extras.getBoolean(EXTRA_IS_GROUP_CONVERSATION); 7509 mUnreadMessageCount = extras.getInt(EXTRA_CONVERSATION_UNREAD_MESSAGE_COUNT); 7510 mShortcutIcon = extras.getParcelable(EXTRA_CONVERSATION_ICON); 7511 } 7512 7513 /** 7514 * @hide 7515 */ 7516 @Override makeContentView(boolean increasedHeight)7517 public RemoteViews makeContentView(boolean increasedHeight) { 7518 mBuilder.mOriginalActions = mBuilder.mActions; 7519 mBuilder.mActions = new ArrayList<>(); 7520 RemoteViews remoteViews = makeMessagingView(true /* isCollapsed */, 7521 false /* hideLargeIcon */); 7522 mBuilder.mActions = mBuilder.mOriginalActions; 7523 mBuilder.mOriginalActions = null; 7524 return remoteViews; 7525 } 7526 7527 /** 7528 * @hide 7529 * Spans are ignored when comparing text for visual difference. 7530 */ 7531 @Override areNotificationsVisiblyDifferent(Style other)7532 public boolean areNotificationsVisiblyDifferent(Style other) { 7533 if (other == null || getClass() != other.getClass()) { 7534 return true; 7535 } 7536 MessagingStyle newS = (MessagingStyle) other; 7537 List<MessagingStyle.Message> oldMs = getMessages(); 7538 List<MessagingStyle.Message> newMs = newS.getMessages(); 7539 7540 if (oldMs == null || newMs == null) { 7541 newMs = new ArrayList<>(); 7542 } 7543 7544 int n = oldMs.size(); 7545 if (n != newMs.size()) { 7546 return true; 7547 } 7548 for (int i = 0; i < n; i++) { 7549 MessagingStyle.Message oldM = oldMs.get(i); 7550 MessagingStyle.Message newM = newMs.get(i); 7551 if (!Objects.equals( 7552 String.valueOf(oldM.getText()), 7553 String.valueOf(newM.getText()))) { 7554 return true; 7555 } 7556 if (!Objects.equals(oldM.getDataUri(), newM.getDataUri())) { 7557 return true; 7558 } 7559 String oldSender = String.valueOf(oldM.getSenderPerson() == null 7560 ? oldM.getSender() 7561 : oldM.getSenderPerson().getName()); 7562 String newSender = String.valueOf(newM.getSenderPerson() == null 7563 ? newM.getSender() 7564 : newM.getSenderPerson().getName()); 7565 if (!Objects.equals(oldSender, newSender)) { 7566 return true; 7567 } 7568 7569 String oldKey = oldM.getSenderPerson() == null 7570 ? null : oldM.getSenderPerson().getKey(); 7571 String newKey = newM.getSenderPerson() == null 7572 ? null : newM.getSenderPerson().getKey(); 7573 if (!Objects.equals(oldKey, newKey)) { 7574 return true; 7575 } 7576 // Other fields (like timestamp) intentionally excluded 7577 } 7578 return false; 7579 } 7580 findLatestIncomingMessage()7581 private Message findLatestIncomingMessage() { 7582 return findLatestIncomingMessage(mMessages); 7583 } 7584 7585 /** 7586 * @hide 7587 */ 7588 @Nullable findLatestIncomingMessage( List<Message> messages)7589 public static Message findLatestIncomingMessage( 7590 List<Message> messages) { 7591 for (int i = messages.size() - 1; i >= 0; i--) { 7592 Message m = messages.get(i); 7593 // Incoming messages have a non-empty sender. 7594 if (m.mSender != null && !TextUtils.isEmpty(m.mSender.getName())) { 7595 return m; 7596 } 7597 } 7598 if (!messages.isEmpty()) { 7599 // No incoming messages, fall back to outgoing message 7600 return messages.get(messages.size() - 1); 7601 } 7602 return null; 7603 } 7604 7605 /** 7606 * @hide 7607 */ 7608 @Override makeBigContentView()7609 public RemoteViews makeBigContentView() { 7610 return makeMessagingView(false /* isCollapsed */, true /* hideLargeIcon */); 7611 } 7612 7613 /** 7614 * Create a messaging layout. 7615 * 7616 * @param isCollapsed Should this use the collapsed layout 7617 * @param hideRightIcons Should the reply affordance be shown at the end of the notification 7618 * @return the created remoteView. 7619 */ 7620 @NonNull makeMessagingView(boolean isCollapsed, boolean hideRightIcons)7621 private RemoteViews makeMessagingView(boolean isCollapsed, boolean hideRightIcons) { 7622 CharSequence conversationTitle = !TextUtils.isEmpty(super.mBigContentTitle) 7623 ? super.mBigContentTitle 7624 : mConversationTitle; 7625 boolean atLeastP = mBuilder.mContext.getApplicationInfo().targetSdkVersion 7626 >= Build.VERSION_CODES.P; 7627 boolean isOneToOne; 7628 CharSequence nameReplacement = null; 7629 if (!atLeastP) { 7630 isOneToOne = TextUtils.isEmpty(conversationTitle); 7631 if (hasOnlyWhiteSpaceSenders()) { 7632 isOneToOne = true; 7633 nameReplacement = conversationTitle; 7634 conversationTitle = null; 7635 } 7636 } else { 7637 isOneToOne = !isGroupConversation(); 7638 } 7639 boolean isConversationLayout = mConversationType != CONVERSATION_TYPE_LEGACY; 7640 boolean isImportantConversation = mConversationType == CONVERSATION_TYPE_IMPORTANT; 7641 Icon largeIcon = mBuilder.mN.mLargeIcon; 7642 TemplateBindResult bindResult = new TemplateBindResult(); 7643 StandardTemplateParams p = mBuilder.mParams.reset() 7644 .hasProgress(false) 7645 .title(conversationTitle) 7646 .text(null) 7647 .hideLargeIcon(hideRightIcons || isOneToOne) 7648 .hideReplyIcon(hideRightIcons) 7649 .headerTextSecondary(conversationTitle); 7650 RemoteViews contentView = mBuilder.applyStandardTemplateWithActions( 7651 isConversationLayout 7652 ? mBuilder.getConversationLayoutResource() 7653 : mBuilder.getMessagingLayoutResource(), 7654 p, 7655 bindResult); 7656 7657 addExtras(mBuilder.mN.extras); 7658 if (!isConversationLayout) { 7659 // also update the end margin if there is an image 7660 contentView.setViewLayoutMarginEnd(R.id.notification_messaging, 7661 bindResult.getIconMarginEnd()); 7662 } 7663 contentView.setInt(R.id.status_bar_latest_event_content, "setLayoutColor", 7664 mBuilder.isColorized(p) 7665 ? mBuilder.getPrimaryTextColor(p) 7666 : mBuilder.resolveContrastColor(p)); 7667 contentView.setInt(R.id.status_bar_latest_event_content, "setSenderTextColor", 7668 mBuilder.getPrimaryTextColor(p)); 7669 contentView.setInt(R.id.status_bar_latest_event_content, "setMessageTextColor", 7670 mBuilder.getSecondaryTextColor(p)); 7671 contentView.setInt(R.id.status_bar_latest_event_content, 7672 "setNotificationBackgroundColor", 7673 mBuilder.resolveBackgroundColor(p)); 7674 contentView.setBoolean(R.id.status_bar_latest_event_content, "setIsCollapsed", 7675 isCollapsed); 7676 contentView.setIcon(R.id.status_bar_latest_event_content, "setAvatarReplacement", 7677 mBuilder.mN.mLargeIcon); 7678 contentView.setCharSequence(R.id.status_bar_latest_event_content, "setNameReplacement", 7679 nameReplacement); 7680 contentView.setBoolean(R.id.status_bar_latest_event_content, "setIsOneToOne", 7681 isOneToOne); 7682 contentView.setCharSequence(R.id.status_bar_latest_event_content, 7683 "setConversationTitle", conversationTitle); 7684 if (isConversationLayout) { 7685 contentView.setIcon(R.id.status_bar_latest_event_content, 7686 "setShortcutIcon", mShortcutIcon); 7687 contentView.setBoolean(R.id.status_bar_latest_event_content, 7688 "setIsImportantConversation", isImportantConversation); 7689 } 7690 contentView.setIcon(R.id.status_bar_latest_event_content, "setLargeIcon", 7691 largeIcon); 7692 contentView.setBundle(R.id.status_bar_latest_event_content, "setData", 7693 mBuilder.mN.extras); 7694 return contentView; 7695 } 7696 hasOnlyWhiteSpaceSenders()7697 private boolean hasOnlyWhiteSpaceSenders() { 7698 for (int i = 0; i < mMessages.size(); i++) { 7699 Message m = mMessages.get(i); 7700 Person sender = m.getSenderPerson(); 7701 if (sender != null && !isWhiteSpace(sender.getName())) { 7702 return false; 7703 } 7704 } 7705 return true; 7706 } 7707 isWhiteSpace(CharSequence sender)7708 private boolean isWhiteSpace(CharSequence sender) { 7709 if (TextUtils.isEmpty(sender)) { 7710 return true; 7711 } 7712 if (sender.toString().matches("^\\s*$")) { 7713 return true; 7714 } 7715 // Let's check if we only have 0 whitespace chars. Some apps did this as a workaround 7716 // For the presentation that we had. 7717 for (int i = 0; i < sender.length(); i++) { 7718 char c = sender.charAt(i); 7719 if (c != '\u200B') { 7720 return false; 7721 } 7722 } 7723 return true; 7724 } 7725 createConversationTitleFromMessages()7726 private CharSequence createConversationTitleFromMessages() { 7727 ArraySet<CharSequence> names = new ArraySet<>(); 7728 for (int i = 0; i < mMessages.size(); i++) { 7729 Message m = mMessages.get(i); 7730 Person sender = m.getSenderPerson(); 7731 if (sender != null) { 7732 names.add(sender.getName()); 7733 } 7734 } 7735 SpannableStringBuilder title = new SpannableStringBuilder(); 7736 int size = names.size(); 7737 for (int i = 0; i < size; i++) { 7738 CharSequence name = names.valueAt(i); 7739 if (!TextUtils.isEmpty(title)) { 7740 title.append(", "); 7741 } 7742 title.append(BidiFormatter.getInstance().unicodeWrap(name)); 7743 } 7744 return title; 7745 } 7746 7747 /** 7748 * @hide 7749 */ 7750 @Override makeHeadsUpContentView(boolean increasedHeight)7751 public RemoteViews makeHeadsUpContentView(boolean increasedHeight) { 7752 RemoteViews remoteViews = makeMessagingView(true /* isCollapsed */, 7753 true /* hideLargeIcon */); 7754 if (mConversationType == CONVERSATION_TYPE_LEGACY) { 7755 remoteViews.setInt(R.id.notification_messaging, "setMaxDisplayedLines", 1); 7756 } 7757 return remoteViews; 7758 } 7759 makeFontColorSpan(int color)7760 private static TextAppearanceSpan makeFontColorSpan(int color) { 7761 return new TextAppearanceSpan(null, 0, 0, 7762 ColorStateList.valueOf(color), null); 7763 } 7764 7765 public static final class Message { 7766 /** @hide */ 7767 public static final String KEY_TEXT = "text"; 7768 static final String KEY_TIMESTAMP = "time"; 7769 static final String KEY_SENDER = "sender"; 7770 static final String KEY_SENDER_PERSON = "sender_person"; 7771 static final String KEY_DATA_MIME_TYPE = "type"; 7772 static final String KEY_DATA_URI= "uri"; 7773 static final String KEY_EXTRAS_BUNDLE = "extras"; 7774 static final String KEY_REMOTE_INPUT_HISTORY = "remote_input_history"; 7775 7776 private final CharSequence mText; 7777 private final long mTimestamp; 7778 @Nullable 7779 private final Person mSender; 7780 /** True if this message was generated from the extra 7781 * {@link Notification#EXTRA_REMOTE_INPUT_HISTORY_ITEMS} 7782 */ 7783 private final boolean mRemoteInputHistory; 7784 7785 private Bundle mExtras = new Bundle(); 7786 private String mDataMimeType; 7787 private Uri mDataUri; 7788 7789 /** 7790 * Constructor 7791 * @param text A {@link CharSequence} to be displayed as the message content 7792 * @param timestamp Time at which the message arrived 7793 * @param sender A {@link CharSequence} to be used for displaying the name of the 7794 * sender. Should be <code>null</code> for messages by the current user, in which case 7795 * the platform will insert {@link MessagingStyle#getUserDisplayName()}. 7796 * Should be unique amongst all individuals in the conversation, and should be 7797 * consistent during re-posts of the notification. 7798 * 7799 * @deprecated use {@code Message(CharSequence, long, Person)} 7800 */ Message(CharSequence text, long timestamp, CharSequence sender)7801 public Message(CharSequence text, long timestamp, CharSequence sender){ 7802 this(text, timestamp, sender == null ? null 7803 : new Person.Builder().setName(sender).build()); 7804 } 7805 7806 /** 7807 * Constructor 7808 * @param text A {@link CharSequence} to be displayed as the message content 7809 * @param timestamp Time at which the message arrived 7810 * @param sender The {@link Person} who sent the message. 7811 * Should be <code>null</code> for messages by the current user, in which case 7812 * the platform will insert the user set in {@code MessagingStyle(Person)}. 7813 * <p> 7814 * The person provided should contain an Icon, set with 7815 * {@link Person.Builder#setIcon(Icon)} and also have a name provided 7816 * with {@link Person.Builder#setName(CharSequence)}. If multiple users have the same 7817 * name, consider providing a key with {@link Person.Builder#setKey(String)} in order 7818 * to differentiate between the different users. 7819 * </p> 7820 */ Message(@onNull CharSequence text, long timestamp, @Nullable Person sender)7821 public Message(@NonNull CharSequence text, long timestamp, @Nullable Person sender) { 7822 this(text, timestamp, sender, false /* remoteHistory */); 7823 } 7824 7825 /** 7826 * Constructor 7827 * @param text A {@link CharSequence} to be displayed as the message content 7828 * @param timestamp Time at which the message arrived 7829 * @param sender The {@link Person} who sent the message. 7830 * Should be <code>null</code> for messages by the current user, in which case 7831 * the platform will insert the user set in {@code MessagingStyle(Person)}. 7832 * @param remoteInputHistory True if the messages was generated from the extra 7833 * {@link Notification#EXTRA_REMOTE_INPUT_HISTORY_ITEMS}. 7834 * <p> 7835 * The person provided should contain an Icon, set with 7836 * {@link Person.Builder#setIcon(Icon)} and also have a name provided 7837 * with {@link Person.Builder#setName(CharSequence)}. If multiple users have the same 7838 * name, consider providing a key with {@link Person.Builder#setKey(String)} in order 7839 * to differentiate between the different users. 7840 * </p> 7841 * @hide 7842 */ Message(@onNull CharSequence text, long timestamp, @Nullable Person sender, boolean remoteInputHistory)7843 public Message(@NonNull CharSequence text, long timestamp, @Nullable Person sender, 7844 boolean remoteInputHistory) { 7845 mText = safeCharSequence(text); 7846 mTimestamp = timestamp; 7847 mSender = sender; 7848 mRemoteInputHistory = remoteInputHistory; 7849 } 7850 7851 /** 7852 * Sets a binary blob of data and an associated MIME type for a message. In the case 7853 * where the platform doesn't support the MIME type, the original text provided in the 7854 * constructor will be used. 7855 * @param dataMimeType The MIME type of the content. See 7856 * <a href="{@docRoot}notifications/messaging.html"> for the list of supported MIME 7857 * types on Android and Android Wear. 7858 * @param dataUri The uri containing the content whose type is given by the MIME type. 7859 * <p class="note"> 7860 * <ol> 7861 * <li>Notification Listeners including the System UI need permission to access the 7862 * data the Uri points to. The recommended ways to do this are:</li> 7863 * <li>Store the data in your own ContentProvider, making sure that other apps have 7864 * the correct permission to access your provider. The preferred mechanism for 7865 * providing access is to use per-URI permissions which are temporary and only 7866 * grant access to the receiving application. An easy way to create a 7867 * ContentProvider like this is to use the FileProvider helper class.</li> 7868 * <li>Use the system MediaStore. The MediaStore is primarily aimed at video, audio 7869 * and image MIME types, however beginning with Android 3.0 (API level 11) it can 7870 * also store non-media types (see MediaStore.Files for more info). Files can be 7871 * inserted into the MediaStore using scanFile() after which a content:// style 7872 * Uri suitable for sharing is passed to the provided onScanCompleted() callback. 7873 * Note that once added to the system MediaStore the content is accessible to any 7874 * app on the device.</li> 7875 * </ol> 7876 * @return this object for method chaining 7877 */ setData(String dataMimeType, Uri dataUri)7878 public Message setData(String dataMimeType, Uri dataUri) { 7879 mDataMimeType = dataMimeType; 7880 mDataUri = dataUri; 7881 return this; 7882 } 7883 7884 /** 7885 * Get the text to be used for this message, or the fallback text if a type and content 7886 * Uri have been set 7887 */ getText()7888 public CharSequence getText() { 7889 return mText; 7890 } 7891 7892 /** 7893 * Get the time at which this message arrived 7894 */ getTimestamp()7895 public long getTimestamp() { 7896 return mTimestamp; 7897 } 7898 7899 /** 7900 * Get the extras Bundle for this message. 7901 */ getExtras()7902 public Bundle getExtras() { 7903 return mExtras; 7904 } 7905 7906 /** 7907 * Get the text used to display the contact's name in the messaging experience 7908 * 7909 * @deprecated use {@link #getSenderPerson()} 7910 */ getSender()7911 public CharSequence getSender() { 7912 return mSender == null ? null : mSender.getName(); 7913 } 7914 7915 /** 7916 * Get the sender associated with this message. 7917 */ 7918 @Nullable getSenderPerson()7919 public Person getSenderPerson() { 7920 return mSender; 7921 } 7922 7923 /** 7924 * Get the MIME type of the data pointed to by the Uri 7925 */ getDataMimeType()7926 public String getDataMimeType() { 7927 return mDataMimeType; 7928 } 7929 7930 /** 7931 * Get the Uri pointing to the content of the message. Can be null, in which case 7932 * {@see #getText()} is used. 7933 */ getDataUri()7934 public Uri getDataUri() { 7935 return mDataUri; 7936 } 7937 7938 /** 7939 * @return True if the message was generated from 7940 * {@link Notification#EXTRA_REMOTE_INPUT_HISTORY_ITEMS}. 7941 * @hide 7942 */ isRemoteInputHistory()7943 public boolean isRemoteInputHistory() { 7944 return mRemoteInputHistory; 7945 } 7946 7947 /** 7948 * @hide 7949 */ 7950 @VisibleForTesting toBundle()7951 public Bundle toBundle() { 7952 Bundle bundle = new Bundle(); 7953 if (mText != null) { 7954 bundle.putCharSequence(KEY_TEXT, mText); 7955 } 7956 bundle.putLong(KEY_TIMESTAMP, mTimestamp); 7957 if (mSender != null) { 7958 // Legacy listeners need this 7959 bundle.putCharSequence(KEY_SENDER, safeCharSequence(mSender.getName())); 7960 bundle.putParcelable(KEY_SENDER_PERSON, mSender); 7961 } 7962 if (mDataMimeType != null) { 7963 bundle.putString(KEY_DATA_MIME_TYPE, mDataMimeType); 7964 } 7965 if (mDataUri != null) { 7966 bundle.putParcelable(KEY_DATA_URI, mDataUri); 7967 } 7968 if (mExtras != null) { 7969 bundle.putBundle(KEY_EXTRAS_BUNDLE, mExtras); 7970 } 7971 if (mRemoteInputHistory) { 7972 bundle.putBoolean(KEY_REMOTE_INPUT_HISTORY, mRemoteInputHistory); 7973 } 7974 return bundle; 7975 } 7976 getBundleArrayForMessages(List<Message> messages)7977 static Bundle[] getBundleArrayForMessages(List<Message> messages) { 7978 Bundle[] bundles = new Bundle[messages.size()]; 7979 final int N = messages.size(); 7980 for (int i = 0; i < N; i++) { 7981 bundles[i] = messages.get(i).toBundle(); 7982 } 7983 return bundles; 7984 } 7985 7986 /** 7987 * Returns a list of messages read from the given bundle list, e.g. 7988 * {@link #EXTRA_MESSAGES} or {@link #EXTRA_HISTORIC_MESSAGES}. 7989 */ 7990 @NonNull getMessagesFromBundleArray(@ullable Parcelable[] bundles)7991 public static List<Message> getMessagesFromBundleArray(@Nullable Parcelable[] bundles) { 7992 if (bundles == null) { 7993 return new ArrayList<>(); 7994 } 7995 List<Message> messages = new ArrayList<>(bundles.length); 7996 for (int i = 0; i < bundles.length; i++) { 7997 if (bundles[i] instanceof Bundle) { 7998 Message message = getMessageFromBundle((Bundle)bundles[i]); 7999 if (message != null) { 8000 messages.add(message); 8001 } 8002 } 8003 } 8004 return messages; 8005 } 8006 8007 /** 8008 * Returns the message that is stored in the bundle (e.g. one of the values in the lists 8009 * in {@link #EXTRA_MESSAGES} or {@link #EXTRA_HISTORIC_MESSAGES}) or null if the 8010 * message couldn't be resolved. 8011 * @hide 8012 */ 8013 @Nullable getMessageFromBundle(@onNull Bundle bundle)8014 public static Message getMessageFromBundle(@NonNull Bundle bundle) { 8015 try { 8016 if (!bundle.containsKey(KEY_TEXT) || !bundle.containsKey(KEY_TIMESTAMP)) { 8017 return null; 8018 } else { 8019 8020 Person senderPerson = bundle.getParcelable(KEY_SENDER_PERSON); 8021 if (senderPerson == null) { 8022 // Legacy apps that use compat don't actually provide the sender objects 8023 // We need to fix the compat version to provide people / use 8024 // the native api instead 8025 CharSequence senderName = bundle.getCharSequence(KEY_SENDER); 8026 if (senderName != null) { 8027 senderPerson = new Person.Builder().setName(senderName).build(); 8028 } 8029 } 8030 Message message = new Message(bundle.getCharSequence(KEY_TEXT), 8031 bundle.getLong(KEY_TIMESTAMP), 8032 senderPerson, 8033 bundle.getBoolean(KEY_REMOTE_INPUT_HISTORY, false)); 8034 if (bundle.containsKey(KEY_DATA_MIME_TYPE) && 8035 bundle.containsKey(KEY_DATA_URI)) { 8036 message.setData(bundle.getString(KEY_DATA_MIME_TYPE), 8037 (Uri) bundle.getParcelable(KEY_DATA_URI)); 8038 } 8039 if (bundle.containsKey(KEY_EXTRAS_BUNDLE)) { 8040 message.getExtras().putAll(bundle.getBundle(KEY_EXTRAS_BUNDLE)); 8041 } 8042 return message; 8043 } 8044 } catch (ClassCastException e) { 8045 return null; 8046 } 8047 } 8048 } 8049 } 8050 8051 /** 8052 * Helper class for generating large-format notifications that include a list of (up to 5) strings. 8053 * 8054 * Here's how you'd set the <code>InboxStyle</code> on a notification: 8055 * <pre class="prettyprint"> 8056 * Notification notif = new Notification.Builder(mContext) 8057 * .setContentTitle("5 New mails from " + sender.toString()) 8058 * .setContentText(subject) 8059 * .setSmallIcon(R.drawable.new_mail) 8060 * .setLargeIcon(aBitmap) 8061 * .setStyle(new Notification.InboxStyle() 8062 * .addLine(str1) 8063 * .addLine(str2) 8064 * .setContentTitle("") 8065 * .setSummaryText("+3 more")) 8066 * .build(); 8067 * </pre> 8068 * 8069 * @see Notification#bigContentView 8070 */ 8071 public static class InboxStyle extends Style { 8072 8073 /** 8074 * The number of lines of remote input history allowed until we start reducing lines. 8075 */ 8076 private static final int NUMBER_OF_HISTORY_ALLOWED_UNTIL_REDUCTION = 1; 8077 private ArrayList<CharSequence> mTexts = new ArrayList<CharSequence>(5); 8078 InboxStyle()8079 public InboxStyle() { 8080 } 8081 8082 /** 8083 * @deprecated use {@code InboxStyle()}. 8084 */ 8085 @Deprecated InboxStyle(Builder builder)8086 public InboxStyle(Builder builder) { 8087 setBuilder(builder); 8088 } 8089 8090 /** 8091 * Overrides ContentTitle in the big form of the template. 8092 * This defaults to the value passed to setContentTitle(). 8093 */ setBigContentTitle(CharSequence title)8094 public InboxStyle setBigContentTitle(CharSequence title) { 8095 internalSetBigContentTitle(safeCharSequence(title)); 8096 return this; 8097 } 8098 8099 /** 8100 * Set the first line of text after the detail section in the big form of the template. 8101 */ setSummaryText(CharSequence cs)8102 public InboxStyle setSummaryText(CharSequence cs) { 8103 internalSetSummaryText(safeCharSequence(cs)); 8104 return this; 8105 } 8106 8107 /** 8108 * Append a line to the digest section of the Inbox notification. 8109 */ addLine(CharSequence cs)8110 public InboxStyle addLine(CharSequence cs) { 8111 mTexts.add(safeCharSequence(cs)); 8112 return this; 8113 } 8114 8115 /** 8116 * @hide 8117 */ getLines()8118 public ArrayList<CharSequence> getLines() { 8119 return mTexts; 8120 } 8121 8122 /** 8123 * @hide 8124 */ addExtras(Bundle extras)8125 public void addExtras(Bundle extras) { 8126 super.addExtras(extras); 8127 8128 CharSequence[] a = new CharSequence[mTexts.size()]; 8129 extras.putCharSequenceArray(EXTRA_TEXT_LINES, mTexts.toArray(a)); 8130 } 8131 8132 /** 8133 * @hide 8134 */ 8135 @Override restoreFromExtras(Bundle extras)8136 protected void restoreFromExtras(Bundle extras) { 8137 super.restoreFromExtras(extras); 8138 8139 mTexts.clear(); 8140 if (extras.containsKey(EXTRA_TEXT_LINES)) { 8141 Collections.addAll(mTexts, extras.getCharSequenceArray(EXTRA_TEXT_LINES)); 8142 } 8143 } 8144 8145 /** 8146 * @hide 8147 */ makeBigContentView()8148 public RemoteViews makeBigContentView() { 8149 StandardTemplateParams p = mBuilder.mParams.reset().fillTextsFrom(mBuilder).text(null); 8150 TemplateBindResult result = new TemplateBindResult(); 8151 RemoteViews contentView = getStandardView(mBuilder.getInboxLayoutResource(), p, result); 8152 8153 int[] rowIds = {R.id.inbox_text0, R.id.inbox_text1, R.id.inbox_text2, R.id.inbox_text3, 8154 R.id.inbox_text4, R.id.inbox_text5, R.id.inbox_text6}; 8155 8156 // Make sure all rows are gone in case we reuse a view. 8157 for (int rowId : rowIds) { 8158 contentView.setViewVisibility(rowId, View.GONE); 8159 } 8160 8161 int i=0; 8162 int topPadding = mBuilder.mContext.getResources().getDimensionPixelSize( 8163 R.dimen.notification_inbox_item_top_padding); 8164 boolean first = true; 8165 int onlyViewId = 0; 8166 int maxRows = rowIds.length; 8167 if (mBuilder.mActions.size() > 0) { 8168 maxRows--; 8169 } 8170 RemoteInputHistoryItem[] remoteInputHistory = getParcelableArrayFromBundle( 8171 mBuilder.mN.extras, EXTRA_REMOTE_INPUT_HISTORY_ITEMS, 8172 RemoteInputHistoryItem.class); 8173 if (remoteInputHistory != null 8174 && remoteInputHistory.length > NUMBER_OF_HISTORY_ALLOWED_UNTIL_REDUCTION) { 8175 // Let's remove some messages to make room for the remote input history. 8176 // 1 is always able to fit, but let's remove them if they are 2 or 3 8177 int numRemoteInputs = Math.min(remoteInputHistory.length, 8178 MAX_REMOTE_INPUT_HISTORY_LINES); 8179 int totalNumRows = mTexts.size() + numRemoteInputs 8180 - NUMBER_OF_HISTORY_ALLOWED_UNTIL_REDUCTION; 8181 if (totalNumRows > maxRows) { 8182 int overflow = totalNumRows - maxRows; 8183 if (mTexts.size() > maxRows) { 8184 // Heuristic: if the Texts don't fit anyway, we'll rather drop the last 8185 // few messages, even with the remote input 8186 maxRows -= overflow; 8187 } else { 8188 // otherwise we drop the first messages 8189 i = overflow; 8190 } 8191 } 8192 } 8193 while (i < mTexts.size() && i < maxRows) { 8194 CharSequence str = mTexts.get(i); 8195 if (!TextUtils.isEmpty(str)) { 8196 contentView.setViewVisibility(rowIds[i], View.VISIBLE); 8197 contentView.setTextViewText(rowIds[i], 8198 mBuilder.processTextSpans(mBuilder.processLegacyText(str))); 8199 mBuilder.setTextViewColorSecondary(contentView, rowIds[i], p); 8200 contentView.setViewPadding(rowIds[i], 0, topPadding, 0, 0); 8201 handleInboxImageMargin(contentView, rowIds[i], first, 8202 result.getIconMarginEnd()); 8203 if (first) { 8204 onlyViewId = rowIds[i]; 8205 } else { 8206 onlyViewId = 0; 8207 } 8208 first = false; 8209 } 8210 i++; 8211 } 8212 if (onlyViewId != 0) { 8213 // We only have 1 entry, lets make it look like the normal Text of a Bigtext 8214 topPadding = mBuilder.mContext.getResources().getDimensionPixelSize( 8215 R.dimen.notification_text_margin_top); 8216 contentView.setViewPadding(onlyViewId, 0, topPadding, 0, 0); 8217 } 8218 8219 return contentView; 8220 } 8221 8222 /** 8223 * @hide 8224 */ 8225 @Override areNotificationsVisiblyDifferent(Style other)8226 public boolean areNotificationsVisiblyDifferent(Style other) { 8227 if (other == null || getClass() != other.getClass()) { 8228 return true; 8229 } 8230 InboxStyle newS = (InboxStyle) other; 8231 8232 final ArrayList<CharSequence> myLines = getLines(); 8233 final ArrayList<CharSequence> newLines = newS.getLines(); 8234 final int n = myLines.size(); 8235 if (n != newLines.size()) { 8236 return true; 8237 } 8238 8239 for (int i = 0; i < n; i++) { 8240 if (!Objects.equals( 8241 String.valueOf(myLines.get(i)), 8242 String.valueOf(newLines.get(i)))) { 8243 return true; 8244 } 8245 } 8246 return false; 8247 } 8248 handleInboxImageMargin(RemoteViews contentView, int id, boolean first, int marginEndValue)8249 private void handleInboxImageMargin(RemoteViews contentView, int id, boolean first, 8250 int marginEndValue) { 8251 int endMargin = 0; 8252 if (first) { 8253 final int max = mBuilder.mN.extras.getInt(EXTRA_PROGRESS_MAX, 0); 8254 final boolean ind = mBuilder.mN.extras.getBoolean(EXTRA_PROGRESS_INDETERMINATE); 8255 boolean hasProgress = max != 0 || ind; 8256 if (!hasProgress) { 8257 endMargin = marginEndValue; 8258 } 8259 } 8260 contentView.setViewLayoutMarginEnd(id, endMargin); 8261 } 8262 } 8263 8264 /** 8265 * Notification style for media playback notifications. 8266 * 8267 * In the expanded form, {@link Notification#bigContentView}, up to 5 8268 * {@link Notification.Action}s specified with 8269 * {@link Notification.Builder#addAction(Action) addAction} will be 8270 * shown as icon-only pushbuttons, suitable for transport controls. The Bitmap given to 8271 * {@link Notification.Builder#setLargeIcon(android.graphics.Bitmap) setLargeIcon()} will be 8272 * treated as album artwork. 8273 * <p> 8274 * Unlike the other styles provided here, MediaStyle can also modify the standard-size 8275 * {@link Notification#contentView}; by providing action indices to 8276 * {@link #setShowActionsInCompactView(int...)} you can promote up to 3 actions to be displayed 8277 * in the standard view alongside the usual content. 8278 * <p> 8279 * Notifications created with MediaStyle will have their category set to 8280 * {@link Notification#CATEGORY_TRANSPORT CATEGORY_TRANSPORT} unless you set a different 8281 * category using {@link Notification.Builder#setCategory(String) setCategory()}. 8282 * <p> 8283 * Finally, if you attach a {@link android.media.session.MediaSession.Token} using 8284 * {@link android.app.Notification.MediaStyle#setMediaSession(MediaSession.Token)}, 8285 * the System UI can identify this as a notification representing an active media session 8286 * and respond accordingly (by showing album artwork in the lockscreen, for example). 8287 * 8288 * <p> 8289 * Starting at {@link android.os.Build.VERSION_CODES#O Android O} any notification that has a 8290 * media session attached with {@link #setMediaSession(MediaSession.Token)} will be colorized. 8291 * You can opt-out of this behavior by using {@link Notification.Builder#setColorized(boolean)}. 8292 * <p> 8293 * 8294 * To use this style with your Notification, feed it to 8295 * {@link Notification.Builder#setStyle(android.app.Notification.Style)} like so: 8296 * <pre class="prettyprint"> 8297 * Notification noti = new Notification.Builder() 8298 * .setSmallIcon(R.drawable.ic_stat_player) 8299 * .setContentTitle("Track title") 8300 * .setContentText("Artist - Album") 8301 * .setLargeIcon(albumArtBitmap)) 8302 * .setStyle(<b>new Notification.MediaStyle()</b> 8303 * .setMediaSession(mySession)) 8304 * .build(); 8305 * </pre> 8306 * 8307 * @see Notification#bigContentView 8308 * @see Notification.Builder#setColorized(boolean) 8309 */ 8310 public static class MediaStyle extends Style { 8311 // Changing max media buttons requires also changing templates 8312 // (notification_template_material_media and notification_template_material_big_media). 8313 static final int MAX_MEDIA_BUTTONS_IN_COMPACT = 3; 8314 static final int MAX_MEDIA_BUTTONS = 5; 8315 @IdRes private static final int[] MEDIA_BUTTON_IDS = { 8316 R.id.action0, 8317 R.id.action1, 8318 R.id.action2, 8319 R.id.action3, 8320 R.id.action4, 8321 }; 8322 8323 private int[] mActionsToShowInCompact = null; 8324 private MediaSession.Token mToken; 8325 MediaStyle()8326 public MediaStyle() { 8327 } 8328 8329 /** 8330 * @deprecated use {@code MediaStyle()}. 8331 */ 8332 @Deprecated MediaStyle(Builder builder)8333 public MediaStyle(Builder builder) { 8334 setBuilder(builder); 8335 } 8336 8337 /** 8338 * Request up to 3 actions (by index in the order of addition) to be shown in the compact 8339 * notification view. 8340 * 8341 * @param actions the indices of the actions to show in the compact notification view 8342 */ setShowActionsInCompactView(int...actions)8343 public MediaStyle setShowActionsInCompactView(int...actions) { 8344 mActionsToShowInCompact = actions; 8345 return this; 8346 } 8347 8348 /** 8349 * Attach a {@link android.media.session.MediaSession.Token} to this Notification 8350 * to provide additional playback information and control to the SystemUI. 8351 */ setMediaSession(MediaSession.Token token)8352 public MediaStyle setMediaSession(MediaSession.Token token) { 8353 mToken = token; 8354 return this; 8355 } 8356 8357 /** 8358 * @hide 8359 */ 8360 @Override 8361 @UnsupportedAppUsage buildStyled(Notification wip)8362 public Notification buildStyled(Notification wip) { 8363 super.buildStyled(wip); 8364 if (wip.category == null) { 8365 wip.category = Notification.CATEGORY_TRANSPORT; 8366 } 8367 return wip; 8368 } 8369 8370 /** 8371 * @hide 8372 */ 8373 @Override makeContentView(boolean increasedHeight)8374 public RemoteViews makeContentView(boolean increasedHeight) { 8375 return makeMediaContentView(); 8376 } 8377 8378 /** 8379 * @hide 8380 */ 8381 @Override makeBigContentView()8382 public RemoteViews makeBigContentView() { 8383 return makeMediaBigContentView(); 8384 } 8385 8386 /** 8387 * @hide 8388 */ 8389 @Override makeHeadsUpContentView(boolean increasedHeight)8390 public RemoteViews makeHeadsUpContentView(boolean increasedHeight) { 8391 return makeMediaContentView(); 8392 } 8393 8394 /** @hide */ 8395 @Override addExtras(Bundle extras)8396 public void addExtras(Bundle extras) { 8397 super.addExtras(extras); 8398 8399 if (mToken != null) { 8400 extras.putParcelable(EXTRA_MEDIA_SESSION, mToken); 8401 } 8402 if (mActionsToShowInCompact != null) { 8403 extras.putIntArray(EXTRA_COMPACT_ACTIONS, mActionsToShowInCompact); 8404 } 8405 } 8406 8407 /** 8408 * @hide 8409 */ 8410 @Override restoreFromExtras(Bundle extras)8411 protected void restoreFromExtras(Bundle extras) { 8412 super.restoreFromExtras(extras); 8413 8414 if (extras.containsKey(EXTRA_MEDIA_SESSION)) { 8415 mToken = extras.getParcelable(EXTRA_MEDIA_SESSION); 8416 } 8417 if (extras.containsKey(EXTRA_COMPACT_ACTIONS)) { 8418 mActionsToShowInCompact = extras.getIntArray(EXTRA_COMPACT_ACTIONS); 8419 } 8420 } 8421 8422 /** 8423 * @hide 8424 */ 8425 @Override areNotificationsVisiblyDifferent(Style other)8426 public boolean areNotificationsVisiblyDifferent(Style other) { 8427 if (other == null || getClass() != other.getClass()) { 8428 return true; 8429 } 8430 // All fields to compare are on the Notification object 8431 return false; 8432 } 8433 bindMediaActionButton(RemoteViews container, @IdRes int buttonId, Action action, StandardTemplateParams p)8434 private void bindMediaActionButton(RemoteViews container, @IdRes int buttonId, 8435 Action action, StandardTemplateParams p) { 8436 final boolean tombstone = (action.actionIntent == null); 8437 container.setViewVisibility(buttonId, View.VISIBLE); 8438 if (buttonId != R.id.media_seamless) { 8439 container.setImageViewIcon(buttonId, action.getIcon()); 8440 } 8441 8442 // If the action buttons should not be tinted, then just use the default 8443 // notification color. Otherwise, just use the passed-in color. 8444 Resources resources = mBuilder.mContext.getResources(); 8445 Configuration currentConfig = resources.getConfiguration(); 8446 boolean inNightMode = (currentConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK) 8447 == Configuration.UI_MODE_NIGHT_YES; 8448 int tintColor = mBuilder.shouldTintActionButtons() || mBuilder.isColorized(p) 8449 ? getActionColor(p) 8450 : ContrastColorUtil.resolveColor(mBuilder.mContext, 8451 Notification.COLOR_DEFAULT, inNightMode); 8452 8453 container.setDrawableTint(buttonId, false, tintColor, 8454 PorterDuff.Mode.SRC_ATOP); 8455 8456 final TypedArray typedArray = mBuilder.mContext.obtainStyledAttributes( 8457 new int[]{ android.R.attr.colorControlHighlight }); 8458 int rippleAlpha = Color.alpha(typedArray.getColor(0, 0)); 8459 typedArray.recycle(); 8460 int rippleColor = Color.argb(rippleAlpha, Color.red(tintColor), Color.green(tintColor), 8461 Color.blue(tintColor)); 8462 container.setRippleDrawableColor(buttonId, ColorStateList.valueOf(rippleColor)); 8463 8464 if (!tombstone) { 8465 container.setOnClickPendingIntent(buttonId, action.actionIntent); 8466 } 8467 container.setContentDescription(buttonId, action.title); 8468 } 8469 makeMediaContentView()8470 private RemoteViews makeMediaContentView() { 8471 StandardTemplateParams p = mBuilder.mParams.reset().hasProgress(false).fillTextsFrom( 8472 mBuilder); 8473 RemoteViews view = mBuilder.applyStandardTemplate( 8474 R.layout.notification_template_material_media, p, 8475 null /* result */); 8476 8477 final int numActions = mBuilder.mActions.size(); 8478 final int numActionsToShow = mActionsToShowInCompact == null 8479 ? 0 8480 : Math.min(mActionsToShowInCompact.length, MAX_MEDIA_BUTTONS_IN_COMPACT); 8481 if (numActionsToShow > numActions) { 8482 throw new IllegalArgumentException(String.format( 8483 "setShowActionsInCompactView: action %d out of bounds (max %d)", 8484 numActions, numActions - 1)); 8485 } 8486 for (int i = 0; i < MAX_MEDIA_BUTTONS_IN_COMPACT; i++) { 8487 if (i < numActionsToShow) { 8488 final Action action = mBuilder.mActions.get(mActionsToShowInCompact[i]); 8489 bindMediaActionButton(view, MEDIA_BUTTON_IDS[i], action, p); 8490 } else { 8491 view.setViewVisibility(MEDIA_BUTTON_IDS[i], View.GONE); 8492 } 8493 } 8494 bindMediaActionButton(view, R.id.media_seamless, new Action( 8495 R.drawable.ic_media_seamless, mBuilder.mContext.getString( 8496 com.android.internal.R.string.ext_media_seamless_action), null), p); 8497 view.setViewVisibility(R.id.media_seamless, View.GONE); 8498 handleImage(view); 8499 // handle the content margin 8500 int endMargin = R.dimen.notification_content_margin_end; 8501 if (mBuilder.mN.hasLargeIcon()) { 8502 endMargin = R.dimen.notification_media_image_margin_end; 8503 } 8504 view.setViewLayoutMarginEndDimen(R.id.notification_main_column, endMargin); 8505 return view; 8506 } 8507 getActionColor(StandardTemplateParams p)8508 private int getActionColor(StandardTemplateParams p) { 8509 return mBuilder.isColorized(p) ? mBuilder.getPrimaryTextColor(p) 8510 : mBuilder.resolveContrastColor(p); 8511 } 8512 makeMediaBigContentView()8513 private RemoteViews makeMediaBigContentView() { 8514 final int actionCount = Math.min(mBuilder.mActions.size(), MAX_MEDIA_BUTTONS); 8515 // Dont add an expanded view if there is no more content to be revealed 8516 int actionsInCompact = mActionsToShowInCompact == null 8517 ? 0 8518 : Math.min(mActionsToShowInCompact.length, MAX_MEDIA_BUTTONS_IN_COMPACT); 8519 if (!mBuilder.mN.hasLargeIcon() && actionCount <= actionsInCompact) { 8520 return null; 8521 } 8522 StandardTemplateParams p = mBuilder.mParams.reset().hasProgress(false).fillTextsFrom( 8523 mBuilder); 8524 RemoteViews big = mBuilder.applyStandardTemplate( 8525 R.layout.notification_template_material_big_media, p , null /* result */); 8526 8527 for (int i = 0; i < MAX_MEDIA_BUTTONS; i++) { 8528 if (i < actionCount) { 8529 bindMediaActionButton(big, MEDIA_BUTTON_IDS[i], mBuilder.mActions.get(i), p); 8530 } else { 8531 big.setViewVisibility(MEDIA_BUTTON_IDS[i], View.GONE); 8532 } 8533 } 8534 bindMediaActionButton(big, R.id.media_seamless, new Action(R.drawable.ic_media_seamless, 8535 mBuilder.mContext.getString( 8536 com.android.internal.R.string.ext_media_seamless_action), null), p); 8537 big.setViewVisibility(R.id.media_seamless, View.GONE); 8538 handleImage(big); 8539 return big; 8540 } 8541 handleImage(RemoteViews contentView)8542 private void handleImage(RemoteViews contentView) { 8543 if (mBuilder.mN.hasLargeIcon()) { 8544 contentView.setViewLayoutMarginEndDimen(R.id.line1, 0); 8545 contentView.setViewLayoutMarginEndDimen(R.id.text, 0); 8546 } 8547 } 8548 8549 /** 8550 * @hide 8551 */ 8552 @Override hasProgress()8553 protected boolean hasProgress() { 8554 return false; 8555 } 8556 } 8557 8558 /** 8559 * Notification style for custom views that are decorated by the system 8560 * 8561 * <p>Instead of providing a notification that is completely custom, a developer can set this 8562 * style and still obtain system decorations like the notification header with the expand 8563 * affordance and actions. 8564 * 8565 * <p>Use {@link android.app.Notification.Builder#setCustomContentView(RemoteViews)}, 8566 * {@link android.app.Notification.Builder#setCustomBigContentView(RemoteViews)} and 8567 * {@link android.app.Notification.Builder#setCustomHeadsUpContentView(RemoteViews)} to set the 8568 * corresponding custom views to display. 8569 * 8570 * To use this style with your Notification, feed it to 8571 * {@link Notification.Builder#setStyle(android.app.Notification.Style)} like so: 8572 * <pre class="prettyprint"> 8573 * Notification noti = new Notification.Builder() 8574 * .setSmallIcon(R.drawable.ic_stat_player) 8575 * .setLargeIcon(albumArtBitmap)) 8576 * .setCustomContentView(contentView); 8577 * .setStyle(<b>new Notification.DecoratedCustomViewStyle()</b>) 8578 * .build(); 8579 * </pre> 8580 */ 8581 public static class DecoratedCustomViewStyle extends Style { 8582 DecoratedCustomViewStyle()8583 public DecoratedCustomViewStyle() { 8584 } 8585 8586 /** 8587 * @hide 8588 */ displayCustomViewInline()8589 public boolean displayCustomViewInline() { 8590 return true; 8591 } 8592 8593 /** 8594 * @hide 8595 */ 8596 @Override makeContentView(boolean increasedHeight)8597 public RemoteViews makeContentView(boolean increasedHeight) { 8598 return makeStandardTemplateWithCustomContent(mBuilder.mN.contentView); 8599 } 8600 8601 /** 8602 * @hide 8603 */ 8604 @Override makeBigContentView()8605 public RemoteViews makeBigContentView() { 8606 return makeDecoratedBigContentView(); 8607 } 8608 8609 /** 8610 * @hide 8611 */ 8612 @Override makeHeadsUpContentView(boolean increasedHeight)8613 public RemoteViews makeHeadsUpContentView(boolean increasedHeight) { 8614 return makeDecoratedHeadsUpContentView(); 8615 } 8616 makeDecoratedHeadsUpContentView()8617 private RemoteViews makeDecoratedHeadsUpContentView() { 8618 RemoteViews headsUpContentView = mBuilder.mN.headsUpContentView == null 8619 ? mBuilder.mN.contentView 8620 : mBuilder.mN.headsUpContentView; 8621 if (mBuilder.mActions.size() == 0) { 8622 return makeStandardTemplateWithCustomContent(headsUpContentView); 8623 } 8624 TemplateBindResult result = new TemplateBindResult(); 8625 RemoteViews remoteViews = mBuilder.applyStandardTemplateWithActions( 8626 mBuilder.getBigBaseLayoutResource(), result); 8627 buildIntoRemoteViewContent(remoteViews, headsUpContentView, result); 8628 return remoteViews; 8629 } 8630 makeStandardTemplateWithCustomContent(RemoteViews customContent)8631 private RemoteViews makeStandardTemplateWithCustomContent(RemoteViews customContent) { 8632 TemplateBindResult result = new TemplateBindResult(); 8633 RemoteViews remoteViews = mBuilder.applyStandardTemplate( 8634 mBuilder.getBaseLayoutResource(), result); 8635 buildIntoRemoteViewContent(remoteViews, customContent, result); 8636 return remoteViews; 8637 } 8638 makeDecoratedBigContentView()8639 private RemoteViews makeDecoratedBigContentView() { 8640 RemoteViews bigContentView = mBuilder.mN.bigContentView == null 8641 ? mBuilder.mN.contentView 8642 : mBuilder.mN.bigContentView; 8643 if (mBuilder.mActions.size() == 0) { 8644 return makeStandardTemplateWithCustomContent(bigContentView); 8645 } 8646 TemplateBindResult result = new TemplateBindResult(); 8647 RemoteViews remoteViews = mBuilder.applyStandardTemplateWithActions( 8648 mBuilder.getBigBaseLayoutResource(), result); 8649 buildIntoRemoteViewContent(remoteViews, bigContentView, result); 8650 return remoteViews; 8651 } 8652 buildIntoRemoteViewContent(RemoteViews remoteViews, RemoteViews customContent, TemplateBindResult result)8653 private void buildIntoRemoteViewContent(RemoteViews remoteViews, 8654 RemoteViews customContent, TemplateBindResult result) { 8655 int childIndex = -1; 8656 if (customContent != null) { 8657 // Need to clone customContent before adding, because otherwise it can no longer be 8658 // parceled independently of remoteViews. 8659 customContent = customContent.clone(); 8660 remoteViews.removeAllViewsExceptId(R.id.notification_main_column, R.id.progress); 8661 remoteViews.addView(R.id.notification_main_column, customContent, 0 /* index */); 8662 remoteViews.addFlags(RemoteViews.FLAG_REAPPLY_DISALLOWED); 8663 childIndex = 0; 8664 } 8665 remoteViews.setIntTag(R.id.notification_main_column, 8666 com.android.internal.R.id.notification_custom_view_index_tag, 8667 childIndex); 8668 // also update the end margin if there is an image 8669 Resources resources = mBuilder.mContext.getResources(); 8670 int endMargin = resources.getDimensionPixelSize( 8671 R.dimen.notification_content_margin_end) + result.getIconMarginEnd(); 8672 remoteViews.setViewLayoutMarginEnd(R.id.notification_main_column, endMargin); 8673 } 8674 8675 /** 8676 * @hide 8677 */ 8678 @Override areNotificationsVisiblyDifferent(Style other)8679 public boolean areNotificationsVisiblyDifferent(Style other) { 8680 if (other == null || getClass() != other.getClass()) { 8681 return true; 8682 } 8683 // Comparison done for all custom RemoteViews, independent of style 8684 return false; 8685 } 8686 } 8687 8688 /** 8689 * Notification style for media custom views that are decorated by the system 8690 * 8691 * <p>Instead of providing a media notification that is completely custom, a developer can set 8692 * this style and still obtain system decorations like the notification header with the expand 8693 * affordance and actions. 8694 * 8695 * <p>Use {@link android.app.Notification.Builder#setCustomContentView(RemoteViews)}, 8696 * {@link android.app.Notification.Builder#setCustomBigContentView(RemoteViews)} and 8697 * {@link android.app.Notification.Builder#setCustomHeadsUpContentView(RemoteViews)} to set the 8698 * corresponding custom views to display. 8699 * <p> 8700 * Contrary to {@link MediaStyle} a developer has to opt-in to the colorizing of the 8701 * notification by using {@link Notification.Builder#setColorized(boolean)}. 8702 * <p> 8703 * To use this style with your Notification, feed it to 8704 * {@link Notification.Builder#setStyle(android.app.Notification.Style)} like so: 8705 * <pre class="prettyprint"> 8706 * Notification noti = new Notification.Builder() 8707 * .setSmallIcon(R.drawable.ic_stat_player) 8708 * .setLargeIcon(albumArtBitmap)) 8709 * .setCustomContentView(contentView); 8710 * .setStyle(<b>new Notification.DecoratedMediaCustomViewStyle()</b> 8711 * .setMediaSession(mySession)) 8712 * .build(); 8713 * </pre> 8714 * 8715 * @see android.app.Notification.DecoratedCustomViewStyle 8716 * @see android.app.Notification.MediaStyle 8717 */ 8718 public static class DecoratedMediaCustomViewStyle extends MediaStyle { 8719 DecoratedMediaCustomViewStyle()8720 public DecoratedMediaCustomViewStyle() { 8721 } 8722 8723 /** 8724 * @hide 8725 */ displayCustomViewInline()8726 public boolean displayCustomViewInline() { 8727 return true; 8728 } 8729 8730 /** 8731 * @hide 8732 */ 8733 @Override makeContentView(boolean increasedHeight)8734 public RemoteViews makeContentView(boolean increasedHeight) { 8735 RemoteViews remoteViews = super.makeContentView(false /* increasedHeight */); 8736 return buildIntoRemoteView(remoteViews, R.id.notification_content_container, 8737 mBuilder.mN.contentView); 8738 } 8739 8740 /** 8741 * @hide 8742 */ 8743 @Override makeBigContentView()8744 public RemoteViews makeBigContentView() { 8745 RemoteViews customRemoteView = mBuilder.mN.bigContentView != null 8746 ? mBuilder.mN.bigContentView 8747 : mBuilder.mN.contentView; 8748 return makeBigContentViewWithCustomContent(customRemoteView); 8749 } 8750 makeBigContentViewWithCustomContent(RemoteViews customRemoteView)8751 private RemoteViews makeBigContentViewWithCustomContent(RemoteViews customRemoteView) { 8752 RemoteViews remoteViews = super.makeBigContentView(); 8753 if (remoteViews != null) { 8754 return buildIntoRemoteView(remoteViews, R.id.notification_main_column, 8755 customRemoteView); 8756 } else if (customRemoteView != mBuilder.mN.contentView){ 8757 remoteViews = super.makeContentView(false /* increasedHeight */); 8758 return buildIntoRemoteView(remoteViews, R.id.notification_content_container, 8759 customRemoteView); 8760 } else { 8761 return null; 8762 } 8763 } 8764 8765 /** 8766 * @hide 8767 */ 8768 @Override makeHeadsUpContentView(boolean increasedHeight)8769 public RemoteViews makeHeadsUpContentView(boolean increasedHeight) { 8770 RemoteViews customRemoteView = mBuilder.mN.headsUpContentView != null 8771 ? mBuilder.mN.headsUpContentView 8772 : mBuilder.mN.contentView; 8773 return makeBigContentViewWithCustomContent(customRemoteView); 8774 } 8775 8776 /** 8777 * @hide 8778 */ 8779 @Override areNotificationsVisiblyDifferent(Style other)8780 public boolean areNotificationsVisiblyDifferent(Style other) { 8781 if (other == null || getClass() != other.getClass()) { 8782 return true; 8783 } 8784 // Comparison done for all custom RemoteViews, independent of style 8785 return false; 8786 } 8787 buildIntoRemoteView(RemoteViews remoteViews, int id, RemoteViews customContent)8788 private RemoteViews buildIntoRemoteView(RemoteViews remoteViews, int id, 8789 RemoteViews customContent) { 8790 if (customContent != null) { 8791 // Need to clone customContent before adding, because otherwise it can no longer be 8792 // parceled independently of remoteViews. 8793 customContent = customContent.clone(); 8794 customContent.overrideTextColors(mBuilder.getPrimaryTextColor(mBuilder.mParams)); 8795 remoteViews.removeAllViews(id); 8796 remoteViews.addView(id, customContent); 8797 remoteViews.addFlags(RemoteViews.FLAG_REAPPLY_DISALLOWED); 8798 } 8799 return remoteViews; 8800 } 8801 } 8802 8803 /** 8804 * Encapsulates the information needed to display a notification as a bubble. 8805 * 8806 * <p>A bubble is used to display app content in a floating window over the existing 8807 * foreground activity. A bubble has a collapsed state represented by an icon and an 8808 * expanded state that displays an activity. These may be defined via 8809 * {@link Builder#Builder(PendingIntent, Icon)} or they may 8810 * be defined via an existing shortcut using {@link Builder#Builder(String)}. 8811 * </p> 8812 * 8813 * <b>Notifications with a valid and allowed bubble will display in collapsed state 8814 * outside of the notification shade on unlocked devices. When a user interacts with the 8815 * collapsed bubble, the bubble activity will be invoked and displayed.</b> 8816 * 8817 * @see Notification.Builder#setBubbleMetadata(BubbleMetadata) 8818 */ 8819 public static final class BubbleMetadata implements Parcelable { 8820 8821 private PendingIntent mPendingIntent; 8822 private PendingIntent mDeleteIntent; 8823 private Icon mIcon; 8824 private int mDesiredHeight; 8825 @DimenRes private int mDesiredHeightResId; 8826 private int mFlags; 8827 private String mShortcutId; 8828 8829 /** 8830 * If set and the app creating the bubble is in the foreground, the bubble will be posted 8831 * in its expanded state. 8832 * 8833 * <p>This flag has no effect if the app posting the bubble is not in the foreground. 8834 * The app is considered foreground if it is visible and on the screen, note that 8835 * a foreground service does not qualify. 8836 * </p> 8837 * 8838 * <p>Generally this flag should only be set if the user has performed an action to request 8839 * or create a bubble.</p> 8840 * 8841 * @hide 8842 */ 8843 public static final int FLAG_AUTO_EXPAND_BUBBLE = 0x00000001; 8844 8845 /** 8846 * Indicates whether the notification associated with the bubble is being visually 8847 * suppressed from the notification shade. When <code>true</code> the notification is 8848 * hidden, when <code>false</code> the notification shows as normal. 8849 * 8850 * <p>Apps sending bubbles may set this flag so that the bubble is posted <b>without</b> 8851 * the associated notification in the notification shade.</p> 8852 * 8853 * <p>Apps sending bubbles can only apply this flag when the app is in the foreground, 8854 * otherwise the flag is not respected. The app is considered foreground if it is visible 8855 * and on the screen, note that a foreground service does not qualify.</p> 8856 * 8857 * <p>Generally this flag should only be set by the app if the user has performed an 8858 * action to request or create a bubble, or if the user has seen the content in the 8859 * notification and the notification is no longer relevant. </p> 8860 * 8861 * <p>The system will also update this flag with <code>true</code> to hide the notification 8862 * from the user once the bubble has been expanded. </p> 8863 * 8864 * @hide 8865 */ 8866 public static final int FLAG_SUPPRESS_NOTIFICATION = 0x00000002; 8867 BubbleMetadata(PendingIntent expandIntent, PendingIntent deleteIntent, Icon icon, int height, @DimenRes int heightResId, String shortcutId)8868 private BubbleMetadata(PendingIntent expandIntent, PendingIntent deleteIntent, 8869 Icon icon, int height, @DimenRes int heightResId, String shortcutId) { 8870 mPendingIntent = expandIntent; 8871 mIcon = icon; 8872 mDesiredHeight = height; 8873 mDesiredHeightResId = heightResId; 8874 mDeleteIntent = deleteIntent; 8875 mShortcutId = shortcutId; 8876 } 8877 BubbleMetadata(Parcel in)8878 private BubbleMetadata(Parcel in) { 8879 if (in.readInt() != 0) { 8880 mPendingIntent = PendingIntent.CREATOR.createFromParcel(in); 8881 } 8882 if (in.readInt() != 0) { 8883 mIcon = Icon.CREATOR.createFromParcel(in); 8884 } 8885 mDesiredHeight = in.readInt(); 8886 mFlags = in.readInt(); 8887 if (in.readInt() != 0) { 8888 mDeleteIntent = PendingIntent.CREATOR.createFromParcel(in); 8889 } 8890 mDesiredHeightResId = in.readInt(); 8891 if (in.readInt() != 0) { 8892 mShortcutId = in.readString8(); 8893 } 8894 } 8895 8896 /** 8897 * @return the shortcut id used for this bubble if created via 8898 * {@link Builder#Builder(String)} or null if created 8899 * via {@link Builder#Builder(PendingIntent, Icon)}. 8900 */ 8901 @Nullable getShortcutId()8902 public String getShortcutId() { 8903 return mShortcutId; 8904 } 8905 8906 /** 8907 * @return the pending intent used to populate the floating window for this bubble, or 8908 * null if this bubble is created via {@link Builder#Builder(String)}. 8909 */ 8910 @SuppressLint("InvalidNullConversion") 8911 @Nullable getIntent()8912 public PendingIntent getIntent() { 8913 return mPendingIntent; 8914 } 8915 8916 /** 8917 * @deprecated use {@link #getIntent()} instead. 8918 * @removed Removed from the R SDK but was never publicly stable. 8919 */ 8920 @Nullable 8921 @Deprecated getBubbleIntent()8922 public PendingIntent getBubbleIntent() { 8923 return mPendingIntent; 8924 } 8925 8926 /** 8927 * @return the pending intent to send when the bubble is dismissed by a user, if one exists. 8928 */ 8929 @Nullable getDeleteIntent()8930 public PendingIntent getDeleteIntent() { 8931 return mDeleteIntent; 8932 } 8933 8934 /** 8935 * @return the icon that will be displayed for this bubble when it is collapsed, or null 8936 * if the bubble is created via {@link Builder#Builder(String)}. 8937 */ 8938 @SuppressLint("InvalidNullConversion") 8939 @Nullable getIcon()8940 public Icon getIcon() { 8941 return mIcon; 8942 } 8943 8944 /** 8945 * @deprecated use {@link #getIcon()} instead. 8946 * @removed Removed from the R SDK but was never publicly stable. 8947 */ 8948 @Nullable 8949 @Deprecated getBubbleIcon()8950 public Icon getBubbleIcon() { 8951 return mIcon; 8952 } 8953 8954 /** 8955 * @return the ideal height, in DPs, for the floating window that app content defined by 8956 * {@link #getIntent()} for this bubble. A value of 0 indicates a desired height has 8957 * not been set. 8958 */ 8959 @Dimension(unit = DP) getDesiredHeight()8960 public int getDesiredHeight() { 8961 return mDesiredHeight; 8962 } 8963 8964 /** 8965 * @return the resId of ideal height for the floating window that app content defined by 8966 * {@link #getIntent()} for this bubble. A value of 0 indicates a res value has not 8967 * been provided for the desired height. 8968 */ 8969 @DimenRes getDesiredHeightResId()8970 public int getDesiredHeightResId() { 8971 return mDesiredHeightResId; 8972 } 8973 8974 /** 8975 * @return whether this bubble should auto expand when it is posted. 8976 * 8977 * @see BubbleMetadata.Builder#setAutoExpandBubble(boolean) 8978 */ getAutoExpandBubble()8979 public boolean getAutoExpandBubble() { 8980 return (mFlags & FLAG_AUTO_EXPAND_BUBBLE) != 0; 8981 } 8982 8983 /** 8984 * Indicates whether the notification associated with the bubble is being visually 8985 * suppressed from the notification shade. When <code>true</code> the notification is 8986 * hidden, when <code>false</code> the notification shows as normal. 8987 * 8988 * <p>Apps sending bubbles may set this flag so that the bubble is posted <b>without</b> 8989 * the associated notification in the notification shade.</p> 8990 * 8991 * <p>Apps sending bubbles can only apply this flag when the app is in the foreground, 8992 * otherwise the flag is not respected. The app is considered foreground if it is visible 8993 * and on the screen, note that a foreground service does not qualify.</p> 8994 * 8995 * <p>Generally the app should only set this flag if the user has performed an 8996 * action to request or create a bubble, or if the user has seen the content in the 8997 * notification and the notification is no longer relevant. </p> 8998 * 8999 * <p>The system will update this flag with <code>true</code> to hide the notification 9000 * from the user once the bubble has been expanded.</p> 9001 * 9002 * @return whether this bubble should suppress the notification when it is posted. 9003 * 9004 * @see BubbleMetadata.Builder#setSuppressNotification(boolean) 9005 */ isNotificationSuppressed()9006 public boolean isNotificationSuppressed() { 9007 return (mFlags & FLAG_SUPPRESS_NOTIFICATION) != 0; 9008 } 9009 9010 public static final @android.annotation.NonNull Parcelable.Creator<BubbleMetadata> CREATOR = 9011 new Parcelable.Creator<BubbleMetadata>() { 9012 9013 @Override 9014 public BubbleMetadata createFromParcel(Parcel source) { 9015 return new BubbleMetadata(source); 9016 } 9017 9018 @Override 9019 public BubbleMetadata[] newArray(int size) { 9020 return new BubbleMetadata[size]; 9021 } 9022 }; 9023 9024 @Override describeContents()9025 public int describeContents() { 9026 return 0; 9027 } 9028 9029 @Override writeToParcel(Parcel out, int flags)9030 public void writeToParcel(Parcel out, int flags) { 9031 out.writeInt(mPendingIntent != null ? 1 : 0); 9032 if (mPendingIntent != null) { 9033 mPendingIntent.writeToParcel(out, 0); 9034 } 9035 out.writeInt(mIcon != null ? 1 : 0); 9036 if (mIcon != null) { 9037 mIcon.writeToParcel(out, 0); 9038 } 9039 out.writeInt(mDesiredHeight); 9040 out.writeInt(mFlags); 9041 out.writeInt(mDeleteIntent != null ? 1 : 0); 9042 if (mDeleteIntent != null) { 9043 mDeleteIntent.writeToParcel(out, 0); 9044 } 9045 out.writeInt(mDesiredHeightResId); 9046 out.writeInt(TextUtils.isEmpty(mShortcutId) ? 0 : 1); 9047 if (!TextUtils.isEmpty(mShortcutId)) { 9048 out.writeString8(mShortcutId); 9049 } 9050 } 9051 9052 /** 9053 * @hide 9054 */ setFlags(int flags)9055 public void setFlags(int flags) { 9056 mFlags = flags; 9057 } 9058 9059 /** 9060 * @hide 9061 */ getFlags()9062 public int getFlags() { 9063 return mFlags; 9064 } 9065 9066 /** 9067 * Builder to construct a {@link BubbleMetadata} object. 9068 */ 9069 public static final class Builder { 9070 9071 private PendingIntent mPendingIntent; 9072 private Icon mIcon; 9073 private int mDesiredHeight; 9074 @DimenRes private int mDesiredHeightResId; 9075 private int mFlags; 9076 private PendingIntent mDeleteIntent; 9077 private String mShortcutId; 9078 9079 /** 9080 * @deprecated use {@link Builder#Builder(String)} for a bubble created via a 9081 * {@link ShortcutInfo} or {@link Builder#Builder(PendingIntent, Icon)} for a bubble 9082 * created via a {@link PendingIntent}. 9083 */ 9084 @Deprecated Builder()9085 public Builder() { 9086 } 9087 9088 /** 9089 * Creates a {@link BubbleMetadata.Builder} based on a {@link ShortcutInfo}. To create 9090 * a shortcut bubble, ensure that the shortcut associated with the provided 9091 * {@param shortcutId} is published as a dynamic shortcut that was built with 9092 * {@link ShortcutInfo.Builder#setLongLived(boolean)} being true, otherwise your 9093 * notification will not be able to bubble. 9094 * 9095 * <p>The shortcut icon will be used to represent the bubble when it is collapsed.</p> 9096 * 9097 * <p>The shortcut activity will be used when the bubble is expanded. This will display 9098 * the shortcut activity in a floating window over the existing foreground activity.</p> 9099 * 9100 * <p>If the shortcut has not been published when the bubble notification is sent, 9101 * no bubble will be produced. If the shortcut is deleted while the bubble is active, 9102 * the bubble will be removed.</p> 9103 * 9104 * @throws NullPointerException if shortcutId is null. 9105 * 9106 * @see ShortcutInfo 9107 * @see ShortcutInfo.Builder#setLongLived(boolean) 9108 * @see android.content.pm.ShortcutManager#addDynamicShortcuts(List) 9109 */ Builder(@onNull String shortcutId)9110 public Builder(@NonNull String shortcutId) { 9111 if (TextUtils.isEmpty(shortcutId)) { 9112 throw new NullPointerException("Bubble requires a non-null shortcut id"); 9113 } 9114 mShortcutId = shortcutId; 9115 } 9116 9117 /** 9118 * Creates a {@link BubbleMetadata.Builder} based on the provided intent and icon. 9119 * 9120 * <p>The icon will be used to represent the bubble when it is collapsed. An icon 9121 * should be representative of the content within the bubble. If your app produces 9122 * multiple bubbles, the icon should be unique for each of them.</p> 9123 * 9124 * <p>The intent that will be used when the bubble is expanded. This will display the 9125 * app content in a floating window over the existing foreground activity. The intent 9126 * should point to a resizable activity. </p> 9127 * 9128 * @throws NullPointerException if intent is null. 9129 * @throws NullPointerException if icon is null. 9130 */ Builder(@onNull PendingIntent intent, @NonNull Icon icon)9131 public Builder(@NonNull PendingIntent intent, @NonNull Icon icon) { 9132 if (intent == null) { 9133 throw new NullPointerException("Bubble requires non-null pending intent"); 9134 } 9135 if (icon == null) { 9136 throw new NullPointerException("Bubbles require non-null icon"); 9137 } 9138 if (icon.getType() != TYPE_URI_ADAPTIVE_BITMAP 9139 && icon.getType() != TYPE_URI) { 9140 Log.w(TAG, "Bubbles work best with icons of TYPE_URI or " 9141 + "TYPE_URI_ADAPTIVE_BITMAP. " 9142 + "In the future, using an icon of this type will be required."); 9143 } 9144 mPendingIntent = intent; 9145 mIcon = icon; 9146 } 9147 9148 /** 9149 * @deprecated use {@link Builder#Builder(String)} instead. 9150 * @removed Removed from the R SDK but was never publicly stable. 9151 */ 9152 @NonNull 9153 @Deprecated createShortcutBubble(@onNull String shortcutId)9154 public BubbleMetadata.Builder createShortcutBubble(@NonNull String shortcutId) { 9155 if (!TextUtils.isEmpty(shortcutId)) { 9156 // If shortcut id is set, we don't use these if they were previously set. 9157 mPendingIntent = null; 9158 mIcon = null; 9159 } 9160 mShortcutId = shortcutId; 9161 return this; 9162 } 9163 9164 /** 9165 * @deprecated use {@link Builder#Builder(PendingIntent, Icon)} instead. 9166 * @removed Removed from the R SDK but was never publicly stable. 9167 */ 9168 @NonNull 9169 @Deprecated createIntentBubble(@onNull PendingIntent intent, @NonNull Icon icon)9170 public BubbleMetadata.Builder createIntentBubble(@NonNull PendingIntent intent, 9171 @NonNull Icon icon) { 9172 if (intent == null) { 9173 throw new IllegalArgumentException("Bubble requires non-null pending intent"); 9174 } 9175 if (icon == null) { 9176 throw new IllegalArgumentException("Bubbles require non-null icon"); 9177 } 9178 if (icon.getType() != TYPE_URI_ADAPTIVE_BITMAP 9179 && icon.getType() != TYPE_URI) { 9180 Log.w(TAG, "Bubbles work best with icons of TYPE_URI or " 9181 + "TYPE_URI_ADAPTIVE_BITMAP. " 9182 + "In the future, using an icon of this type will be required."); 9183 } 9184 mShortcutId = null; 9185 mPendingIntent = intent; 9186 mIcon = icon; 9187 return this; 9188 } 9189 9190 /** 9191 * Sets the intent for the bubble. 9192 * 9193 * <p>The intent that will be used when the bubble is expanded. This will display the 9194 * app content in a floating window over the existing foreground activity. The intent 9195 * should point to a resizable activity. </p> 9196 * 9197 * @throws NullPointerException if intent is null. 9198 * @throws IllegalStateException if this builder was created via 9199 * {@link Builder#Builder(String)}. 9200 */ 9201 @NonNull setIntent(@onNull PendingIntent intent)9202 public BubbleMetadata.Builder setIntent(@NonNull PendingIntent intent) { 9203 if (mShortcutId != null) { 9204 throw new IllegalStateException("Created as a shortcut bubble, cannot set a " 9205 + "PendingIntent. Consider using " 9206 + "BubbleMetadata.Builder(PendingIntent,Icon) instead."); 9207 } 9208 if (intent == null) { 9209 throw new NullPointerException("Bubble requires non-null pending intent"); 9210 } 9211 mPendingIntent = intent; 9212 return this; 9213 } 9214 9215 /** 9216 * Sets the icon for the bubble. Can only be used if the bubble was created 9217 * via {@link Builder#Builder(PendingIntent, Icon)}. 9218 * 9219 * <p>The icon will be used to represent the bubble when it is collapsed. An icon 9220 * should be representative of the content within the bubble. If your app produces 9221 * multiple bubbles, the icon should be unique for each of them.</p> 9222 * 9223 * <p>It is recommended to use an {@link Icon} of type {@link Icon#TYPE_URI} 9224 * or {@link Icon#TYPE_URI_ADAPTIVE_BITMAP}</p> 9225 * 9226 * @throws NullPointerException if icon is null. 9227 * @throws IllegalStateException if this builder was created via 9228 * {@link Builder#Builder(String)}. 9229 */ 9230 @NonNull setIcon(@onNull Icon icon)9231 public BubbleMetadata.Builder setIcon(@NonNull Icon icon) { 9232 if (mShortcutId != null) { 9233 throw new IllegalStateException("Created as a shortcut bubble, cannot set an " 9234 + "Icon. Consider using " 9235 + "BubbleMetadata.Builder(PendingIntent,Icon) instead."); 9236 } 9237 if (icon == null) { 9238 throw new NullPointerException("Bubbles require non-null icon"); 9239 } 9240 if (icon.getType() != TYPE_URI_ADAPTIVE_BITMAP 9241 && icon.getType() != TYPE_URI) { 9242 Log.w(TAG, "Bubbles work best with icons of TYPE_URI or " 9243 + "TYPE_URI_ADAPTIVE_BITMAP. " 9244 + "In the future, using an icon of this type will be required."); 9245 } 9246 mIcon = icon; 9247 return this; 9248 } 9249 9250 /** 9251 * Sets the desired height in DPs for the expanded content of the bubble. 9252 * 9253 * <p>This height may not be respected if there is not enough space on the screen or if 9254 * the provided height is too small to be useful.</p> 9255 * 9256 * <p>If {@link #setDesiredHeightResId(int)} was previously called on this builder, the 9257 * previous value set will be cleared after calling this method, and this value will 9258 * be used instead.</p> 9259 * 9260 * <p>A desired height (in DPs or via resID) is optional.</p> 9261 * 9262 * @see #setDesiredHeightResId(int) 9263 */ 9264 @NonNull setDesiredHeight(@imensionunit = DP) int height)9265 public BubbleMetadata.Builder setDesiredHeight(@Dimension(unit = DP) int height) { 9266 mDesiredHeight = Math.max(height, 0); 9267 mDesiredHeightResId = 0; 9268 return this; 9269 } 9270 9271 9272 /** 9273 * Sets the desired height via resId for the expanded content of the bubble. 9274 * 9275 * <p>This height may not be respected if there is not enough space on the screen or if 9276 * the provided height is too small to be useful.</p> 9277 * 9278 * <p>If {@link #setDesiredHeight(int)} was previously called on this builder, the 9279 * previous value set will be cleared after calling this method, and this value will 9280 * be used instead.</p> 9281 * 9282 * <p>A desired height (in DPs or via resID) is optional.</p> 9283 * 9284 * @see #setDesiredHeight(int) 9285 */ 9286 @NonNull setDesiredHeightResId(@imenRes int heightResId)9287 public BubbleMetadata.Builder setDesiredHeightResId(@DimenRes int heightResId) { 9288 mDesiredHeightResId = heightResId; 9289 mDesiredHeight = 0; 9290 return this; 9291 } 9292 9293 /** 9294 * Sets whether the bubble will be posted in its expanded state. 9295 * 9296 * <p>This flag has no effect if the app posting the bubble is not in the foreground. 9297 * The app is considered foreground if it is visible and on the screen, note that 9298 * a foreground service does not qualify. 9299 * </p> 9300 * 9301 * <p>Generally, this flag should only be set if the user has performed an action to 9302 * request or create a bubble.</p> 9303 * 9304 * <p>Setting this flag is optional; it defaults to false.</p> 9305 */ 9306 @NonNull setAutoExpandBubble(boolean shouldExpand)9307 public BubbleMetadata.Builder setAutoExpandBubble(boolean shouldExpand) { 9308 setFlag(FLAG_AUTO_EXPAND_BUBBLE, shouldExpand); 9309 return this; 9310 } 9311 9312 /** 9313 * Sets whether the bubble will be posted <b>without</b> the associated notification in 9314 * the notification shade. 9315 * 9316 * <p>This flag has no effect if the app posting the bubble is not in the foreground. 9317 * The app is considered foreground if it is visible and on the screen, note that 9318 * a foreground service does not qualify. 9319 * </p> 9320 * 9321 * <p>Generally, this flag should only be set if the user has performed an action to 9322 * request or create a bubble, or if the user has seen the content in the notification 9323 * and the notification is no longer relevant.</p> 9324 * 9325 * <p>Setting this flag is optional; it defaults to false.</p> 9326 */ 9327 @NonNull setSuppressNotification(boolean shouldSuppressNotif)9328 public BubbleMetadata.Builder setSuppressNotification(boolean shouldSuppressNotif) { 9329 setFlag(FLAG_SUPPRESS_NOTIFICATION, shouldSuppressNotif); 9330 return this; 9331 } 9332 9333 /** 9334 * Sets an intent to send when this bubble is explicitly removed by the user. 9335 * 9336 * <p>Setting a delete intent is optional.</p> 9337 */ 9338 @NonNull setDeleteIntent(@ullable PendingIntent deleteIntent)9339 public BubbleMetadata.Builder setDeleteIntent(@Nullable PendingIntent deleteIntent) { 9340 mDeleteIntent = deleteIntent; 9341 return this; 9342 } 9343 9344 /** 9345 * Creates the {@link BubbleMetadata} defined by this builder. 9346 * 9347 * @throws NullPointerException if required elements have not been set. 9348 */ 9349 @NonNull build()9350 public BubbleMetadata build() { 9351 if (mShortcutId == null && mPendingIntent == null) { 9352 throw new NullPointerException( 9353 "Must supply pending intent or shortcut to bubble"); 9354 } 9355 if (mShortcutId == null && mIcon == null) { 9356 throw new NullPointerException( 9357 "Must supply an icon or shortcut for the bubble"); 9358 } 9359 BubbleMetadata data = new BubbleMetadata(mPendingIntent, mDeleteIntent, 9360 mIcon, mDesiredHeight, mDesiredHeightResId, mShortcutId); 9361 data.setFlags(mFlags); 9362 return data; 9363 } 9364 9365 /** 9366 * @hide 9367 */ setFlag(int mask, boolean value)9368 public BubbleMetadata.Builder setFlag(int mask, boolean value) { 9369 if (value) { 9370 mFlags |= mask; 9371 } else { 9372 mFlags &= ~mask; 9373 } 9374 return this; 9375 } 9376 } 9377 } 9378 9379 9380 // When adding a new Style subclass here, don't forget to update 9381 // Builder.getNotificationStyleClass. 9382 9383 /** 9384 * Extender interface for use with {@link Builder#extend}. Extenders may be used to add 9385 * metadata or change options on a notification builder. 9386 */ 9387 public interface Extender { 9388 /** 9389 * Apply this extender to a notification builder. 9390 * @param builder the builder to be modified. 9391 * @return the build object for chaining. 9392 */ extend(Builder builder)9393 public Builder extend(Builder builder); 9394 } 9395 9396 /** 9397 * Helper class to add wearable extensions to notifications. 9398 * <p class="note"> See 9399 * <a href="{@docRoot}wear/notifications/creating.html">Creating Notifications 9400 * for Android Wear</a> for more information on how to use this class. 9401 * <p> 9402 * To create a notification with wearable extensions: 9403 * <ol> 9404 * <li>Create a {@link android.app.Notification.Builder}, setting any desired 9405 * properties. 9406 * <li>Create a {@link android.app.Notification.WearableExtender}. 9407 * <li>Set wearable-specific properties using the 9408 * {@code add} and {@code set} methods of {@link android.app.Notification.WearableExtender}. 9409 * <li>Call {@link android.app.Notification.Builder#extend} to apply the extensions to a 9410 * notification. 9411 * <li>Post the notification to the notification system with the 9412 * {@code NotificationManager.notify(...)} methods. 9413 * </ol> 9414 * 9415 * <pre class="prettyprint"> 9416 * Notification notif = new Notification.Builder(mContext) 9417 * .setContentTitle("New mail from " + sender.toString()) 9418 * .setContentText(subject) 9419 * .setSmallIcon(R.drawable.new_mail) 9420 * .extend(new Notification.WearableExtender() 9421 * .setContentIcon(R.drawable.new_mail)) 9422 * .build(); 9423 * NotificationManager notificationManger = 9424 * (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); 9425 * notificationManger.notify(0, notif);</pre> 9426 * 9427 * <p>Wearable extensions can be accessed on an existing notification by using the 9428 * {@code WearableExtender(Notification)} constructor, 9429 * and then using the {@code get} methods to access values. 9430 * 9431 * <pre class="prettyprint"> 9432 * Notification.WearableExtender wearableExtender = new Notification.WearableExtender( 9433 * notification); 9434 * List<Notification> pages = wearableExtender.getPages();</pre> 9435 */ 9436 public static final class WearableExtender implements Extender { 9437 /** 9438 * Sentinel value for an action index that is unset. 9439 */ 9440 public static final int UNSET_ACTION_INDEX = -1; 9441 9442 /** 9443 * Size value for use with {@link #setCustomSizePreset} to show this notification with 9444 * default sizing. 9445 * <p>For custom display notifications created using {@link #setDisplayIntent}, 9446 * the default is {@link #SIZE_MEDIUM}. All other notifications size automatically based 9447 * on their content. 9448 * 9449 * @deprecated Display intents are no longer supported. 9450 */ 9451 @Deprecated 9452 public static final int SIZE_DEFAULT = 0; 9453 9454 /** 9455 * Size value for use with {@link #setCustomSizePreset} to show this notification 9456 * with an extra small size. 9457 * <p>This value is only applicable for custom display notifications created using 9458 * {@link #setDisplayIntent}. 9459 * 9460 * @deprecated Display intents are no longer supported. 9461 */ 9462 @Deprecated 9463 public static final int SIZE_XSMALL = 1; 9464 9465 /** 9466 * Size value for use with {@link #setCustomSizePreset} to show this notification 9467 * with a small size. 9468 * <p>This value is only applicable for custom display notifications created using 9469 * {@link #setDisplayIntent}. 9470 * 9471 * @deprecated Display intents are no longer supported. 9472 */ 9473 @Deprecated 9474 public static final int SIZE_SMALL = 2; 9475 9476 /** 9477 * Size value for use with {@link #setCustomSizePreset} to show this notification 9478 * with a medium size. 9479 * <p>This value is only applicable for custom display notifications created using 9480 * {@link #setDisplayIntent}. 9481 * 9482 * @deprecated Display intents are no longer supported. 9483 */ 9484 @Deprecated 9485 public static final int SIZE_MEDIUM = 3; 9486 9487 /** 9488 * Size value for use with {@link #setCustomSizePreset} to show this notification 9489 * with a large size. 9490 * <p>This value is only applicable for custom display notifications created using 9491 * {@link #setDisplayIntent}. 9492 * 9493 * @deprecated Display intents are no longer supported. 9494 */ 9495 @Deprecated 9496 public static final int SIZE_LARGE = 4; 9497 9498 /** 9499 * Size value for use with {@link #setCustomSizePreset} to show this notification 9500 * full screen. 9501 * <p>This value is only applicable for custom display notifications created using 9502 * {@link #setDisplayIntent}. 9503 * 9504 * @deprecated Display intents are no longer supported. 9505 */ 9506 @Deprecated 9507 public static final int SIZE_FULL_SCREEN = 5; 9508 9509 /** 9510 * Sentinel value for use with {@link #setHintScreenTimeout} to keep the screen on for a 9511 * short amount of time when this notification is displayed on the screen. This 9512 * is the default value. 9513 * 9514 * @deprecated This feature is no longer supported. 9515 */ 9516 @Deprecated 9517 public static final int SCREEN_TIMEOUT_SHORT = 0; 9518 9519 /** 9520 * Sentinel value for use with {@link #setHintScreenTimeout} to keep the screen on 9521 * for a longer amount of time when this notification is displayed on the screen. 9522 * 9523 * @deprecated This feature is no longer supported. 9524 */ 9525 @Deprecated 9526 public static final int SCREEN_TIMEOUT_LONG = -1; 9527 9528 /** Notification extra which contains wearable extensions */ 9529 private static final String EXTRA_WEARABLE_EXTENSIONS = "android.wearable.EXTENSIONS"; 9530 9531 // Keys within EXTRA_WEARABLE_EXTENSIONS for wearable options. 9532 private static final String KEY_ACTIONS = "actions"; 9533 private static final String KEY_FLAGS = "flags"; 9534 private static final String KEY_DISPLAY_INTENT = "displayIntent"; 9535 private static final String KEY_PAGES = "pages"; 9536 private static final String KEY_BACKGROUND = "background"; 9537 private static final String KEY_CONTENT_ICON = "contentIcon"; 9538 private static final String KEY_CONTENT_ICON_GRAVITY = "contentIconGravity"; 9539 private static final String KEY_CONTENT_ACTION_INDEX = "contentActionIndex"; 9540 private static final String KEY_CUSTOM_SIZE_PRESET = "customSizePreset"; 9541 private static final String KEY_CUSTOM_CONTENT_HEIGHT = "customContentHeight"; 9542 private static final String KEY_GRAVITY = "gravity"; 9543 private static final String KEY_HINT_SCREEN_TIMEOUT = "hintScreenTimeout"; 9544 private static final String KEY_DISMISSAL_ID = "dismissalId"; 9545 private static final String KEY_BRIDGE_TAG = "bridgeTag"; 9546 9547 // Flags bitwise-ored to mFlags 9548 private static final int FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE = 0x1; 9549 private static final int FLAG_HINT_HIDE_ICON = 1 << 1; 9550 private static final int FLAG_HINT_SHOW_BACKGROUND_ONLY = 1 << 2; 9551 private static final int FLAG_START_SCROLL_BOTTOM = 1 << 3; 9552 private static final int FLAG_HINT_AVOID_BACKGROUND_CLIPPING = 1 << 4; 9553 private static final int FLAG_BIG_PICTURE_AMBIENT = 1 << 5; 9554 private static final int FLAG_HINT_CONTENT_INTENT_LAUNCHES_ACTIVITY = 1 << 6; 9555 9556 // Default value for flags integer 9557 private static final int DEFAULT_FLAGS = FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE; 9558 9559 private static final int DEFAULT_CONTENT_ICON_GRAVITY = Gravity.END; 9560 private static final int DEFAULT_GRAVITY = Gravity.BOTTOM; 9561 9562 private ArrayList<Action> mActions = new ArrayList<Action>(); 9563 private int mFlags = DEFAULT_FLAGS; 9564 private PendingIntent mDisplayIntent; 9565 private ArrayList<Notification> mPages = new ArrayList<Notification>(); 9566 private Bitmap mBackground; 9567 private int mContentIcon; 9568 private int mContentIconGravity = DEFAULT_CONTENT_ICON_GRAVITY; 9569 private int mContentActionIndex = UNSET_ACTION_INDEX; 9570 private int mCustomSizePreset = SIZE_DEFAULT; 9571 private int mCustomContentHeight; 9572 private int mGravity = DEFAULT_GRAVITY; 9573 private int mHintScreenTimeout; 9574 private String mDismissalId; 9575 private String mBridgeTag; 9576 9577 /** 9578 * Create a {@link android.app.Notification.WearableExtender} with default 9579 * options. 9580 */ WearableExtender()9581 public WearableExtender() { 9582 } 9583 WearableExtender(Notification notif)9584 public WearableExtender(Notification notif) { 9585 Bundle wearableBundle = notif.extras.getBundle(EXTRA_WEARABLE_EXTENSIONS); 9586 if (wearableBundle != null) { 9587 List<Action> actions = wearableBundle.getParcelableArrayList(KEY_ACTIONS); 9588 if (actions != null) { 9589 mActions.addAll(actions); 9590 } 9591 9592 mFlags = wearableBundle.getInt(KEY_FLAGS, DEFAULT_FLAGS); 9593 mDisplayIntent = wearableBundle.getParcelable(KEY_DISPLAY_INTENT); 9594 9595 Notification[] pages = getParcelableArrayFromBundle( 9596 wearableBundle, KEY_PAGES, Notification.class); 9597 if (pages != null) { 9598 Collections.addAll(mPages, pages); 9599 } 9600 9601 mBackground = wearableBundle.getParcelable(KEY_BACKGROUND); 9602 mContentIcon = wearableBundle.getInt(KEY_CONTENT_ICON); 9603 mContentIconGravity = wearableBundle.getInt(KEY_CONTENT_ICON_GRAVITY, 9604 DEFAULT_CONTENT_ICON_GRAVITY); 9605 mContentActionIndex = wearableBundle.getInt(KEY_CONTENT_ACTION_INDEX, 9606 UNSET_ACTION_INDEX); 9607 mCustomSizePreset = wearableBundle.getInt(KEY_CUSTOM_SIZE_PRESET, 9608 SIZE_DEFAULT); 9609 mCustomContentHeight = wearableBundle.getInt(KEY_CUSTOM_CONTENT_HEIGHT); 9610 mGravity = wearableBundle.getInt(KEY_GRAVITY, DEFAULT_GRAVITY); 9611 mHintScreenTimeout = wearableBundle.getInt(KEY_HINT_SCREEN_TIMEOUT); 9612 mDismissalId = wearableBundle.getString(KEY_DISMISSAL_ID); 9613 mBridgeTag = wearableBundle.getString(KEY_BRIDGE_TAG); 9614 } 9615 } 9616 9617 /** 9618 * Apply wearable extensions to a notification that is being built. This is typically 9619 * called by the {@link android.app.Notification.Builder#extend} method of 9620 * {@link android.app.Notification.Builder}. 9621 */ 9622 @Override extend(Notification.Builder builder)9623 public Notification.Builder extend(Notification.Builder builder) { 9624 Bundle wearableBundle = new Bundle(); 9625 9626 if (!mActions.isEmpty()) { 9627 wearableBundle.putParcelableArrayList(KEY_ACTIONS, mActions); 9628 } 9629 if (mFlags != DEFAULT_FLAGS) { 9630 wearableBundle.putInt(KEY_FLAGS, mFlags); 9631 } 9632 if (mDisplayIntent != null) { 9633 wearableBundle.putParcelable(KEY_DISPLAY_INTENT, mDisplayIntent); 9634 } 9635 if (!mPages.isEmpty()) { 9636 wearableBundle.putParcelableArray(KEY_PAGES, mPages.toArray( 9637 new Notification[mPages.size()])); 9638 } 9639 if (mBackground != null) { 9640 wearableBundle.putParcelable(KEY_BACKGROUND, mBackground); 9641 } 9642 if (mContentIcon != 0) { 9643 wearableBundle.putInt(KEY_CONTENT_ICON, mContentIcon); 9644 } 9645 if (mContentIconGravity != DEFAULT_CONTENT_ICON_GRAVITY) { 9646 wearableBundle.putInt(KEY_CONTENT_ICON_GRAVITY, mContentIconGravity); 9647 } 9648 if (mContentActionIndex != UNSET_ACTION_INDEX) { 9649 wearableBundle.putInt(KEY_CONTENT_ACTION_INDEX, 9650 mContentActionIndex); 9651 } 9652 if (mCustomSizePreset != SIZE_DEFAULT) { 9653 wearableBundle.putInt(KEY_CUSTOM_SIZE_PRESET, mCustomSizePreset); 9654 } 9655 if (mCustomContentHeight != 0) { 9656 wearableBundle.putInt(KEY_CUSTOM_CONTENT_HEIGHT, mCustomContentHeight); 9657 } 9658 if (mGravity != DEFAULT_GRAVITY) { 9659 wearableBundle.putInt(KEY_GRAVITY, mGravity); 9660 } 9661 if (mHintScreenTimeout != 0) { 9662 wearableBundle.putInt(KEY_HINT_SCREEN_TIMEOUT, mHintScreenTimeout); 9663 } 9664 if (mDismissalId != null) { 9665 wearableBundle.putString(KEY_DISMISSAL_ID, mDismissalId); 9666 } 9667 if (mBridgeTag != null) { 9668 wearableBundle.putString(KEY_BRIDGE_TAG, mBridgeTag); 9669 } 9670 9671 builder.getExtras().putBundle(EXTRA_WEARABLE_EXTENSIONS, wearableBundle); 9672 return builder; 9673 } 9674 9675 @Override clone()9676 public WearableExtender clone() { 9677 WearableExtender that = new WearableExtender(); 9678 that.mActions = new ArrayList<Action>(this.mActions); 9679 that.mFlags = this.mFlags; 9680 that.mDisplayIntent = this.mDisplayIntent; 9681 that.mPages = new ArrayList<Notification>(this.mPages); 9682 that.mBackground = this.mBackground; 9683 that.mContentIcon = this.mContentIcon; 9684 that.mContentIconGravity = this.mContentIconGravity; 9685 that.mContentActionIndex = this.mContentActionIndex; 9686 that.mCustomSizePreset = this.mCustomSizePreset; 9687 that.mCustomContentHeight = this.mCustomContentHeight; 9688 that.mGravity = this.mGravity; 9689 that.mHintScreenTimeout = this.mHintScreenTimeout; 9690 that.mDismissalId = this.mDismissalId; 9691 that.mBridgeTag = this.mBridgeTag; 9692 return that; 9693 } 9694 9695 /** 9696 * Add a wearable action to this notification. 9697 * 9698 * <p>When wearable actions are added using this method, the set of actions that 9699 * show on a wearable device splits from devices that only show actions added 9700 * using {@link android.app.Notification.Builder#addAction}. This allows for customization 9701 * of which actions display on different devices. 9702 * 9703 * @param action the action to add to this notification 9704 * @return this object for method chaining 9705 * @see android.app.Notification.Action 9706 */ addAction(Action action)9707 public WearableExtender addAction(Action action) { 9708 mActions.add(action); 9709 return this; 9710 } 9711 9712 /** 9713 * Adds wearable actions to this notification. 9714 * 9715 * <p>When wearable actions are added using this method, the set of actions that 9716 * show on a wearable device splits from devices that only show actions added 9717 * using {@link android.app.Notification.Builder#addAction}. This allows for customization 9718 * of which actions display on different devices. 9719 * 9720 * @param actions the actions to add to this notification 9721 * @return this object for method chaining 9722 * @see android.app.Notification.Action 9723 */ addActions(List<Action> actions)9724 public WearableExtender addActions(List<Action> actions) { 9725 mActions.addAll(actions); 9726 return this; 9727 } 9728 9729 /** 9730 * Clear all wearable actions present on this builder. 9731 * @return this object for method chaining. 9732 * @see #addAction 9733 */ clearActions()9734 public WearableExtender clearActions() { 9735 mActions.clear(); 9736 return this; 9737 } 9738 9739 /** 9740 * Get the wearable actions present on this notification. 9741 */ getActions()9742 public List<Action> getActions() { 9743 return mActions; 9744 } 9745 9746 /** 9747 * Set an intent to launch inside of an activity view when displaying 9748 * this notification. The {@link PendingIntent} provided should be for an activity. 9749 * 9750 * <pre class="prettyprint"> 9751 * Intent displayIntent = new Intent(context, MyDisplayActivity.class); 9752 * PendingIntent displayPendingIntent = PendingIntent.getActivity(context, 9753 * 0, displayIntent, PendingIntent.FLAG_UPDATE_CURRENT); 9754 * Notification notif = new Notification.Builder(context) 9755 * .extend(new Notification.WearableExtender() 9756 * .setDisplayIntent(displayPendingIntent) 9757 * .setCustomSizePreset(Notification.WearableExtender.SIZE_MEDIUM)) 9758 * .build();</pre> 9759 * 9760 * <p>The activity to launch needs to allow embedding, must be exported, and 9761 * should have an empty task affinity. It is also recommended to use the device 9762 * default light theme. 9763 * 9764 * <p>Example AndroidManifest.xml entry: 9765 * <pre class="prettyprint"> 9766 * <activity android:name="com.example.MyDisplayActivity" 9767 * android:exported="true" 9768 * android:allowEmbedded="true" 9769 * android:taskAffinity="" 9770 * android:theme="@android:style/Theme.DeviceDefault.Light" /></pre> 9771 * 9772 * @param intent the {@link PendingIntent} for an activity 9773 * @return this object for method chaining 9774 * @see android.app.Notification.WearableExtender#getDisplayIntent 9775 * @deprecated Display intents are no longer supported. 9776 */ 9777 @Deprecated setDisplayIntent(PendingIntent intent)9778 public WearableExtender setDisplayIntent(PendingIntent intent) { 9779 mDisplayIntent = intent; 9780 return this; 9781 } 9782 9783 /** 9784 * Get the intent to launch inside of an activity view when displaying this 9785 * notification. This {@code PendingIntent} should be for an activity. 9786 * 9787 * @deprecated Display intents are no longer supported. 9788 */ 9789 @Deprecated getDisplayIntent()9790 public PendingIntent getDisplayIntent() { 9791 return mDisplayIntent; 9792 } 9793 9794 /** 9795 * Add an additional page of content to display with this notification. The current 9796 * notification forms the first page, and pages added using this function form 9797 * subsequent pages. This field can be used to separate a notification into multiple 9798 * sections. 9799 * 9800 * @param page the notification to add as another page 9801 * @return this object for method chaining 9802 * @see android.app.Notification.WearableExtender#getPages 9803 * @deprecated Multiple content pages are no longer supported. 9804 */ 9805 @Deprecated addPage(Notification page)9806 public WearableExtender addPage(Notification page) { 9807 mPages.add(page); 9808 return this; 9809 } 9810 9811 /** 9812 * Add additional pages of content to display with this notification. The current 9813 * notification forms the first page, and pages added using this function form 9814 * subsequent pages. This field can be used to separate a notification into multiple 9815 * sections. 9816 * 9817 * @param pages a list of notifications 9818 * @return this object for method chaining 9819 * @see android.app.Notification.WearableExtender#getPages 9820 * @deprecated Multiple content pages are no longer supported. 9821 */ 9822 @Deprecated addPages(List<Notification> pages)9823 public WearableExtender addPages(List<Notification> pages) { 9824 mPages.addAll(pages); 9825 return this; 9826 } 9827 9828 /** 9829 * Clear all additional pages present on this builder. 9830 * @return this object for method chaining. 9831 * @see #addPage 9832 * @deprecated Multiple content pages are no longer supported. 9833 */ 9834 @Deprecated clearPages()9835 public WearableExtender clearPages() { 9836 mPages.clear(); 9837 return this; 9838 } 9839 9840 /** 9841 * Get the array of additional pages of content for displaying this notification. The 9842 * current notification forms the first page, and elements within this array form 9843 * subsequent pages. This field can be used to separate a notification into multiple 9844 * sections. 9845 * @return the pages for this notification 9846 * @deprecated Multiple content pages are no longer supported. 9847 */ 9848 @Deprecated getPages()9849 public List<Notification> getPages() { 9850 return mPages; 9851 } 9852 9853 /** 9854 * Set a background image to be displayed behind the notification content. 9855 * Contrary to the {@link android.app.Notification.BigPictureStyle}, this background 9856 * will work with any notification style. 9857 * 9858 * @param background the background bitmap 9859 * @return this object for method chaining 9860 * @see android.app.Notification.WearableExtender#getBackground 9861 * @deprecated Background images are no longer supported. 9862 */ 9863 @Deprecated setBackground(Bitmap background)9864 public WearableExtender setBackground(Bitmap background) { 9865 mBackground = background; 9866 return this; 9867 } 9868 9869 /** 9870 * Get a background image to be displayed behind the notification content. 9871 * Contrary to the {@link android.app.Notification.BigPictureStyle}, this background 9872 * will work with any notification style. 9873 * 9874 * @return the background image 9875 * @see android.app.Notification.WearableExtender#setBackground 9876 * @deprecated Background images are no longer supported. 9877 */ 9878 @Deprecated getBackground()9879 public Bitmap getBackground() { 9880 return mBackground; 9881 } 9882 9883 /** 9884 * Set an icon that goes with the content of this notification. 9885 */ 9886 @Deprecated setContentIcon(int icon)9887 public WearableExtender setContentIcon(int icon) { 9888 mContentIcon = icon; 9889 return this; 9890 } 9891 9892 /** 9893 * Get an icon that goes with the content of this notification. 9894 */ 9895 @Deprecated getContentIcon()9896 public int getContentIcon() { 9897 return mContentIcon; 9898 } 9899 9900 /** 9901 * Set 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 #setContentIcon 9905 */ 9906 @Deprecated setContentIconGravity(int contentIconGravity)9907 public WearableExtender setContentIconGravity(int contentIconGravity) { 9908 mContentIconGravity = contentIconGravity; 9909 return this; 9910 } 9911 9912 /** 9913 * Get the gravity that the content icon should have within the notification display. 9914 * Supported values include {@link android.view.Gravity#START} and 9915 * {@link android.view.Gravity#END}. The default value is {@link android.view.Gravity#END}. 9916 * @see #getContentIcon 9917 */ 9918 @Deprecated getContentIconGravity()9919 public int getContentIconGravity() { 9920 return mContentIconGravity; 9921 } 9922 9923 /** 9924 * Set an action from this notification's actions as the primary action. If the action has a 9925 * {@link RemoteInput} associated with it, shortcuts to the options for that input are shown 9926 * directly on the notification. 9927 * 9928 * @param actionIndex The index of the primary action. 9929 * If wearable actions were added to the main notification, this index 9930 * will apply to that list, otherwise it will apply to the regular 9931 * actions list. 9932 */ setContentAction(int actionIndex)9933 public WearableExtender setContentAction(int actionIndex) { 9934 mContentActionIndex = actionIndex; 9935 return this; 9936 } 9937 9938 /** 9939 * Get the index of the notification action, if any, that was specified as the primary 9940 * action. 9941 * 9942 * <p>If wearable specific actions were added to the main notification, this index will 9943 * apply to that list, otherwise it will apply to the regular actions list. 9944 * 9945 * @return the action index or {@link #UNSET_ACTION_INDEX} if no action was selected. 9946 */ getContentAction()9947 public int getContentAction() { 9948 return mContentActionIndex; 9949 } 9950 9951 /** 9952 * Set 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 setGravity(int gravity)9958 public WearableExtender setGravity(int gravity) { 9959 mGravity = gravity; 9960 return this; 9961 } 9962 9963 /** 9964 * Get the gravity that this notification should have within the available viewport space. 9965 * Supported values include {@link android.view.Gravity#TOP}, 9966 * {@link android.view.Gravity#CENTER_VERTICAL} and {@link android.view.Gravity#BOTTOM}. 9967 * The default value is {@link android.view.Gravity#BOTTOM}. 9968 */ 9969 @Deprecated getGravity()9970 public int getGravity() { 9971 return mGravity; 9972 } 9973 9974 /** 9975 * Set the custom size preset for the display of this notification out of the available 9976 * presets found in {@link android.app.Notification.WearableExtender}, e.g. 9977 * {@link #SIZE_LARGE}. 9978 * <p>Some custom size presets are only applicable for custom display notifications created 9979 * using {@link android.app.Notification.WearableExtender#setDisplayIntent}. Check the 9980 * documentation for the preset in question. See also 9981 * {@link #setCustomContentHeight} and {@link #getCustomSizePreset}. 9982 */ 9983 @Deprecated setCustomSizePreset(int sizePreset)9984 public WearableExtender setCustomSizePreset(int sizePreset) { 9985 mCustomSizePreset = sizePreset; 9986 return this; 9987 } 9988 9989 /** 9990 * Get the custom size preset for the display of this notification out of the available 9991 * presets found in {@link android.app.Notification.WearableExtender}, e.g. 9992 * {@link #SIZE_LARGE}. 9993 * <p>Some custom size presets are only applicable for custom display notifications created 9994 * using {@link #setDisplayIntent}. Check the documentation for the preset in question. 9995 * See also {@link #setCustomContentHeight} and {@link #setCustomSizePreset}. 9996 */ 9997 @Deprecated getCustomSizePreset()9998 public int getCustomSizePreset() { 9999 return mCustomSizePreset; 10000 } 10001 10002 /** 10003 * Set the custom height in pixels for the display of this notification's content. 10004 * <p>This option is only available for custom display notifications created 10005 * using {@link android.app.Notification.WearableExtender#setDisplayIntent}. See also 10006 * {@link android.app.Notification.WearableExtender#setCustomSizePreset} and 10007 * {@link #getCustomContentHeight}. 10008 */ 10009 @Deprecated setCustomContentHeight(int height)10010 public WearableExtender setCustomContentHeight(int height) { 10011 mCustomContentHeight = height; 10012 return this; 10013 } 10014 10015 /** 10016 * Get the custom height in pixels for the display of this notification's content. 10017 * <p>This option is only available for custom display notifications created 10018 * using {@link #setDisplayIntent}. See also {@link #setCustomSizePreset} and 10019 * {@link #setCustomContentHeight}. 10020 */ 10021 @Deprecated getCustomContentHeight()10022 public int getCustomContentHeight() { 10023 return mCustomContentHeight; 10024 } 10025 10026 /** 10027 * Set whether the scrolling position for the contents of this notification should start 10028 * at the bottom of the contents instead of the top when the contents are too long to 10029 * display within the screen. Default is false (start scroll at the top). 10030 */ setStartScrollBottom(boolean startScrollBottom)10031 public WearableExtender setStartScrollBottom(boolean startScrollBottom) { 10032 setFlag(FLAG_START_SCROLL_BOTTOM, startScrollBottom); 10033 return this; 10034 } 10035 10036 /** 10037 * Get whether the scrolling position for the contents of this notification should start 10038 * at the bottom of the contents instead of the top when the contents are too long to 10039 * display within the screen. Default is false (start scroll at the top). 10040 */ getStartScrollBottom()10041 public boolean getStartScrollBottom() { 10042 return (mFlags & FLAG_START_SCROLL_BOTTOM) != 0; 10043 } 10044 10045 /** 10046 * Set 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 */ setContentIntentAvailableOffline( boolean contentIntentAvailableOffline)10051 public WearableExtender setContentIntentAvailableOffline( 10052 boolean contentIntentAvailableOffline) { 10053 setFlag(FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE, contentIntentAvailableOffline); 10054 return this; 10055 } 10056 10057 /** 10058 * Get whether the content intent is available when the wearable device is not connected 10059 * to a companion device. The user can still trigger this intent when the wearable device 10060 * is offline, but a visual hint will indicate that the content intent may not be available. 10061 * Defaults to true. 10062 */ getContentIntentAvailableOffline()10063 public boolean getContentIntentAvailableOffline() { 10064 return (mFlags & FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE) != 0; 10065 } 10066 10067 /** 10068 * Set a hint that this notification's icon should not be displayed. 10069 * @param hintHideIcon {@code true} to hide the icon, {@code false} otherwise. 10070 * @return this object for method chaining 10071 */ 10072 @Deprecated setHintHideIcon(boolean hintHideIcon)10073 public WearableExtender setHintHideIcon(boolean hintHideIcon) { 10074 setFlag(FLAG_HINT_HIDE_ICON, hintHideIcon); 10075 return this; 10076 } 10077 10078 /** 10079 * Get a hint that this notification's icon should not be displayed. 10080 * @return {@code true} if this icon should not be displayed, false otherwise. 10081 * The default value is {@code false} if this was never set. 10082 */ 10083 @Deprecated getHintHideIcon()10084 public boolean getHintHideIcon() { 10085 return (mFlags & FLAG_HINT_HIDE_ICON) != 0; 10086 } 10087 10088 /** 10089 * Set a visual hint that only the background image of this notification should be 10090 * displayed, and other semantic content should be hidden. This hint is only applicable 10091 * to sub-pages added using {@link #addPage}. 10092 */ 10093 @Deprecated setHintShowBackgroundOnly(boolean hintShowBackgroundOnly)10094 public WearableExtender setHintShowBackgroundOnly(boolean hintShowBackgroundOnly) { 10095 setFlag(FLAG_HINT_SHOW_BACKGROUND_ONLY, hintShowBackgroundOnly); 10096 return this; 10097 } 10098 10099 /** 10100 * Get a visual hint that only the background image of this notification should be 10101 * displayed, and other semantic content should be hidden. This hint is only applicable 10102 * to sub-pages added using {@link android.app.Notification.WearableExtender#addPage}. 10103 */ 10104 @Deprecated getHintShowBackgroundOnly()10105 public boolean getHintShowBackgroundOnly() { 10106 return (mFlags & FLAG_HINT_SHOW_BACKGROUND_ONLY) != 0; 10107 } 10108 10109 /** 10110 * Set a hint that this notification's background should not be clipped if possible, 10111 * and should instead be resized to fully display on the screen, retaining the aspect 10112 * ratio of the image. This can be useful for images like barcodes or qr codes. 10113 * @param hintAvoidBackgroundClipping {@code true} to avoid clipping if possible. 10114 * @return this object for method chaining 10115 */ 10116 @Deprecated setHintAvoidBackgroundClipping( boolean hintAvoidBackgroundClipping)10117 public WearableExtender setHintAvoidBackgroundClipping( 10118 boolean hintAvoidBackgroundClipping) { 10119 setFlag(FLAG_HINT_AVOID_BACKGROUND_CLIPPING, hintAvoidBackgroundClipping); 10120 return this; 10121 } 10122 10123 /** 10124 * Get a hint that this notification's background should not be clipped if possible, 10125 * and should instead be resized to fully display on the screen, retaining the aspect 10126 * ratio of the image. This can be useful for images like barcodes or qr codes. 10127 * @return {@code true} if it's ok if the background is clipped on the screen, false 10128 * otherwise. The default value is {@code false} if this was never set. 10129 */ 10130 @Deprecated getHintAvoidBackgroundClipping()10131 public boolean getHintAvoidBackgroundClipping() { 10132 return (mFlags & FLAG_HINT_AVOID_BACKGROUND_CLIPPING) != 0; 10133 } 10134 10135 /** 10136 * Set a hint that the screen should remain on for at least this duration when 10137 * this notification is displayed on the screen. 10138 * @param timeout The requested screen timeout in milliseconds. Can also be either 10139 * {@link #SCREEN_TIMEOUT_SHORT} or {@link #SCREEN_TIMEOUT_LONG}. 10140 * @return this object for method chaining 10141 */ 10142 @Deprecated setHintScreenTimeout(int timeout)10143 public WearableExtender setHintScreenTimeout(int timeout) { 10144 mHintScreenTimeout = timeout; 10145 return this; 10146 } 10147 10148 /** 10149 * Get the duration, in milliseconds, that the screen should remain on for 10150 * when this notification is displayed. 10151 * @return the duration in milliseconds if > 0, or either one of the sentinel values 10152 * {@link #SCREEN_TIMEOUT_SHORT} or {@link #SCREEN_TIMEOUT_LONG}. 10153 */ 10154 @Deprecated getHintScreenTimeout()10155 public int getHintScreenTimeout() { 10156 return mHintScreenTimeout; 10157 } 10158 10159 /** 10160 * Set a hint that this notification's {@link BigPictureStyle} (if present) should be 10161 * converted to low-bit and displayed in ambient mode, especially useful for barcodes and 10162 * qr codes, as well as other simple black-and-white tickets. 10163 * @param hintAmbientBigPicture {@code true} to enable converstion and ambient. 10164 * @return this object for method chaining 10165 * @deprecated This feature is no longer supported. 10166 */ 10167 @Deprecated setHintAmbientBigPicture(boolean hintAmbientBigPicture)10168 public WearableExtender setHintAmbientBigPicture(boolean hintAmbientBigPicture) { 10169 setFlag(FLAG_BIG_PICTURE_AMBIENT, hintAmbientBigPicture); 10170 return this; 10171 } 10172 10173 /** 10174 * Get a hint that this notification's {@link BigPictureStyle} (if present) should be 10175 * converted to low-bit and displayed in ambient mode, especially useful for barcodes and 10176 * qr codes, as well as other simple black-and-white tickets. 10177 * @return {@code true} if it should be displayed in ambient, false otherwise 10178 * otherwise. The default value is {@code false} if this was never set. 10179 * @deprecated This feature is no longer supported. 10180 */ 10181 @Deprecated getHintAmbientBigPicture()10182 public boolean getHintAmbientBigPicture() { 10183 return (mFlags & FLAG_BIG_PICTURE_AMBIENT) != 0; 10184 } 10185 10186 /** 10187 * Set a hint that this notification's content intent will launch an {@link Activity} 10188 * directly, telling the platform that it can generate the appropriate transitions. 10189 * @param hintContentIntentLaunchesActivity {@code true} if the content intent will launch 10190 * an activity and transitions should be generated, false otherwise. 10191 * @return this object for method chaining 10192 */ setHintContentIntentLaunchesActivity( boolean hintContentIntentLaunchesActivity)10193 public WearableExtender setHintContentIntentLaunchesActivity( 10194 boolean hintContentIntentLaunchesActivity) { 10195 setFlag(FLAG_HINT_CONTENT_INTENT_LAUNCHES_ACTIVITY, hintContentIntentLaunchesActivity); 10196 return this; 10197 } 10198 10199 /** 10200 * Get a hint that this notification's content intent will launch an {@link Activity} 10201 * directly, telling the platform that it can generate the appropriate transitions 10202 * @return {@code true} if the content intent will launch an activity and transitions should 10203 * be generated, false otherwise. The default value is {@code false} if this was never set. 10204 */ getHintContentIntentLaunchesActivity()10205 public boolean getHintContentIntentLaunchesActivity() { 10206 return (mFlags & FLAG_HINT_CONTENT_INTENT_LAUNCHES_ACTIVITY) != 0; 10207 } 10208 10209 /** 10210 * Sets the dismissal id for this notification. If a notification is posted with a 10211 * dismissal id, then when that notification is canceled, notifications on other wearables 10212 * and the paired Android phone having that same dismissal id will also be canceled. See 10213 * <a href="{@docRoot}wear/notifications/index.html">Adding Wearable Features to 10214 * Notifications</a> for more information. 10215 * @param dismissalId the dismissal id of the notification. 10216 * @return this object for method chaining 10217 */ setDismissalId(String dismissalId)10218 public WearableExtender setDismissalId(String dismissalId) { 10219 mDismissalId = dismissalId; 10220 return this; 10221 } 10222 10223 /** 10224 * Returns the dismissal id of the notification. 10225 * @return the dismissal id of the notification or null if it has not been set. 10226 */ getDismissalId()10227 public String getDismissalId() { 10228 return mDismissalId; 10229 } 10230 10231 /** 10232 * Sets a bridge tag for this notification. A bridge tag can be set for notifications 10233 * posted from a phone to provide finer-grained control on what notifications are bridged 10234 * to wearables. See <a href="{@docRoot}wear/notifications/index.html">Adding Wearable 10235 * Features to Notifications</a> for more information. 10236 * @param bridgeTag the bridge tag of the notification. 10237 * @return this object for method chaining 10238 */ setBridgeTag(String bridgeTag)10239 public WearableExtender setBridgeTag(String bridgeTag) { 10240 mBridgeTag = bridgeTag; 10241 return this; 10242 } 10243 10244 /** 10245 * Returns the bridge tag of the notification. 10246 * @return the bridge tag or null if not present. 10247 */ getBridgeTag()10248 public String getBridgeTag() { 10249 return mBridgeTag; 10250 } 10251 setFlag(int mask, boolean value)10252 private void setFlag(int mask, boolean value) { 10253 if (value) { 10254 mFlags |= mask; 10255 } else { 10256 mFlags &= ~mask; 10257 } 10258 } 10259 } 10260 10261 /** 10262 * <p>Helper class to add Android Auto extensions to notifications. To create a notification 10263 * with car extensions: 10264 * 10265 * <ol> 10266 * <li>Create an {@link Notification.Builder}, setting any desired 10267 * properties. 10268 * <li>Create a {@link CarExtender}. 10269 * <li>Set car-specific properties using the {@code add} and {@code set} methods of 10270 * {@link CarExtender}. 10271 * <li>Call {@link Notification.Builder#extend(Notification.Extender)} 10272 * to apply the extensions to a notification. 10273 * </ol> 10274 * 10275 * <pre class="prettyprint"> 10276 * Notification notification = new Notification.Builder(context) 10277 * ... 10278 * .extend(new CarExtender() 10279 * .set*(...)) 10280 * .build(); 10281 * </pre> 10282 * 10283 * <p>Car extensions can be accessed on an existing notification by using the 10284 * {@code CarExtender(Notification)} constructor, and then using the {@code get} methods 10285 * to access values. 10286 */ 10287 public static final class CarExtender implements Extender { 10288 private static final String TAG = "CarExtender"; 10289 10290 private static final String EXTRA_CAR_EXTENDER = "android.car.EXTENSIONS"; 10291 private static final String EXTRA_LARGE_ICON = "large_icon"; 10292 private static final String EXTRA_CONVERSATION = "car_conversation"; 10293 private static final String EXTRA_COLOR = "app_color"; 10294 10295 private Bitmap mLargeIcon; 10296 private UnreadConversation mUnreadConversation; 10297 private int mColor = Notification.COLOR_DEFAULT; 10298 10299 /** 10300 * Create a {@link CarExtender} with default options. 10301 */ CarExtender()10302 public CarExtender() { 10303 } 10304 10305 /** 10306 * Create a {@link CarExtender} from the CarExtender options of an existing Notification. 10307 * 10308 * @param notif The notification from which to copy options. 10309 */ CarExtender(Notification notif)10310 public CarExtender(Notification notif) { 10311 Bundle carBundle = notif.extras == null ? 10312 null : notif.extras.getBundle(EXTRA_CAR_EXTENDER); 10313 if (carBundle != null) { 10314 mLargeIcon = carBundle.getParcelable(EXTRA_LARGE_ICON); 10315 mColor = carBundle.getInt(EXTRA_COLOR, Notification.COLOR_DEFAULT); 10316 10317 Bundle b = carBundle.getBundle(EXTRA_CONVERSATION); 10318 mUnreadConversation = UnreadConversation.getUnreadConversationFromBundle(b); 10319 } 10320 } 10321 10322 /** 10323 * Apply car extensions to a notification that is being built. This is typically called by 10324 * the {@link Notification.Builder#extend(Notification.Extender)} 10325 * method of {@link Notification.Builder}. 10326 */ 10327 @Override extend(Notification.Builder builder)10328 public Notification.Builder extend(Notification.Builder builder) { 10329 Bundle carExtensions = new Bundle(); 10330 10331 if (mLargeIcon != null) { 10332 carExtensions.putParcelable(EXTRA_LARGE_ICON, mLargeIcon); 10333 } 10334 if (mColor != Notification.COLOR_DEFAULT) { 10335 carExtensions.putInt(EXTRA_COLOR, mColor); 10336 } 10337 10338 if (mUnreadConversation != null) { 10339 Bundle b = mUnreadConversation.getBundleForUnreadConversation(); 10340 carExtensions.putBundle(EXTRA_CONVERSATION, b); 10341 } 10342 10343 builder.getExtras().putBundle(EXTRA_CAR_EXTENDER, carExtensions); 10344 return builder; 10345 } 10346 10347 /** 10348 * Sets the accent color to use when Android Auto presents the notification. 10349 * 10350 * Android Auto uses the color set with {@link Notification.Builder#setColor(int)} 10351 * to accent the displayed notification. However, not all colors are acceptable in an 10352 * automotive setting. This method can be used to override the color provided in the 10353 * notification in such a situation. 10354 */ setColor(@olorInt int color)10355 public CarExtender setColor(@ColorInt int color) { 10356 mColor = color; 10357 return this; 10358 } 10359 10360 /** 10361 * Gets the accent color. 10362 * 10363 * @see #setColor 10364 */ 10365 @ColorInt getColor()10366 public int getColor() { 10367 return mColor; 10368 } 10369 10370 /** 10371 * Sets the large icon of the car notification. 10372 * 10373 * If no large icon is set in the extender, Android Auto will display the icon 10374 * specified by {@link Notification.Builder#setLargeIcon(android.graphics.Bitmap)} 10375 * 10376 * @param largeIcon The large icon to use in the car notification. 10377 * @return This object for method chaining. 10378 */ setLargeIcon(Bitmap largeIcon)10379 public CarExtender setLargeIcon(Bitmap largeIcon) { 10380 mLargeIcon = largeIcon; 10381 return this; 10382 } 10383 10384 /** 10385 * Gets the large icon used in this car notification, or null if no icon has been set. 10386 * 10387 * @return The large icon for the car notification. 10388 * @see CarExtender#setLargeIcon 10389 */ getLargeIcon()10390 public Bitmap getLargeIcon() { 10391 return mLargeIcon; 10392 } 10393 10394 /** 10395 * Sets the unread conversation in a message notification. 10396 * 10397 * @param unreadConversation The unread part of the conversation this notification conveys. 10398 * @return This object for method chaining. 10399 */ setUnreadConversation(UnreadConversation unreadConversation)10400 public CarExtender setUnreadConversation(UnreadConversation unreadConversation) { 10401 mUnreadConversation = unreadConversation; 10402 return this; 10403 } 10404 10405 /** 10406 * Returns the unread conversation conveyed by this notification. 10407 * @see #setUnreadConversation(UnreadConversation) 10408 */ getUnreadConversation()10409 public UnreadConversation getUnreadConversation() { 10410 return mUnreadConversation; 10411 } 10412 10413 /** 10414 * A class which holds the unread messages from a conversation. 10415 */ 10416 public static class UnreadConversation { 10417 private static final String KEY_AUTHOR = "author"; 10418 private static final String KEY_TEXT = "text"; 10419 private static final String KEY_MESSAGES = "messages"; 10420 private static final String KEY_REMOTE_INPUT = "remote_input"; 10421 private static final String KEY_ON_REPLY = "on_reply"; 10422 private static final String KEY_ON_READ = "on_read"; 10423 private static final String KEY_PARTICIPANTS = "participants"; 10424 private static final String KEY_TIMESTAMP = "timestamp"; 10425 10426 private final String[] mMessages; 10427 private final RemoteInput mRemoteInput; 10428 private final PendingIntent mReplyPendingIntent; 10429 private final PendingIntent mReadPendingIntent; 10430 private final String[] mParticipants; 10431 private final long mLatestTimestamp; 10432 UnreadConversation(String[] messages, RemoteInput remoteInput, PendingIntent replyPendingIntent, PendingIntent readPendingIntent, String[] participants, long latestTimestamp)10433 UnreadConversation(String[] messages, RemoteInput remoteInput, 10434 PendingIntent replyPendingIntent, PendingIntent readPendingIntent, 10435 String[] participants, long latestTimestamp) { 10436 mMessages = messages; 10437 mRemoteInput = remoteInput; 10438 mReadPendingIntent = readPendingIntent; 10439 mReplyPendingIntent = replyPendingIntent; 10440 mParticipants = participants; 10441 mLatestTimestamp = latestTimestamp; 10442 } 10443 10444 /** 10445 * Gets the list of messages conveyed by this notification. 10446 */ getMessages()10447 public String[] getMessages() { 10448 return mMessages; 10449 } 10450 10451 /** 10452 * Gets the remote input that will be used to convey the response to a message list, or 10453 * null if no such remote input exists. 10454 */ getRemoteInput()10455 public RemoteInput getRemoteInput() { 10456 return mRemoteInput; 10457 } 10458 10459 /** 10460 * Gets the pending intent that will be triggered when the user replies to this 10461 * notification. 10462 */ getReplyPendingIntent()10463 public PendingIntent getReplyPendingIntent() { 10464 return mReplyPendingIntent; 10465 } 10466 10467 /** 10468 * Gets the pending intent that Android Auto will send after it reads aloud all messages 10469 * in this object's message list. 10470 */ getReadPendingIntent()10471 public PendingIntent getReadPendingIntent() { 10472 return mReadPendingIntent; 10473 } 10474 10475 /** 10476 * Gets the participants in the conversation. 10477 */ getParticipants()10478 public String[] getParticipants() { 10479 return mParticipants; 10480 } 10481 10482 /** 10483 * Gets the firs participant in the conversation. 10484 */ getParticipant()10485 public String getParticipant() { 10486 return mParticipants.length > 0 ? mParticipants[0] : null; 10487 } 10488 10489 /** 10490 * Gets the timestamp of the conversation. 10491 */ getLatestTimestamp()10492 public long getLatestTimestamp() { 10493 return mLatestTimestamp; 10494 } 10495 getBundleForUnreadConversation()10496 Bundle getBundleForUnreadConversation() { 10497 Bundle b = new Bundle(); 10498 String author = null; 10499 if (mParticipants != null && mParticipants.length > 1) { 10500 author = mParticipants[0]; 10501 } 10502 Parcelable[] messages = new Parcelable[mMessages.length]; 10503 for (int i = 0; i < messages.length; i++) { 10504 Bundle m = new Bundle(); 10505 m.putString(KEY_TEXT, mMessages[i]); 10506 m.putString(KEY_AUTHOR, author); 10507 messages[i] = m; 10508 } 10509 b.putParcelableArray(KEY_MESSAGES, messages); 10510 if (mRemoteInput != null) { 10511 b.putParcelable(KEY_REMOTE_INPUT, mRemoteInput); 10512 } 10513 b.putParcelable(KEY_ON_REPLY, mReplyPendingIntent); 10514 b.putParcelable(KEY_ON_READ, mReadPendingIntent); 10515 b.putStringArray(KEY_PARTICIPANTS, mParticipants); 10516 b.putLong(KEY_TIMESTAMP, mLatestTimestamp); 10517 return b; 10518 } 10519 getUnreadConversationFromBundle(Bundle b)10520 static UnreadConversation getUnreadConversationFromBundle(Bundle b) { 10521 if (b == null) { 10522 return null; 10523 } 10524 Parcelable[] parcelableMessages = b.getParcelableArray(KEY_MESSAGES); 10525 String[] messages = null; 10526 if (parcelableMessages != null) { 10527 String[] tmp = new String[parcelableMessages.length]; 10528 boolean success = true; 10529 for (int i = 0; i < tmp.length; i++) { 10530 if (!(parcelableMessages[i] instanceof Bundle)) { 10531 success = false; 10532 break; 10533 } 10534 tmp[i] = ((Bundle) parcelableMessages[i]).getString(KEY_TEXT); 10535 if (tmp[i] == null) { 10536 success = false; 10537 break; 10538 } 10539 } 10540 if (success) { 10541 messages = tmp; 10542 } else { 10543 return null; 10544 } 10545 } 10546 10547 PendingIntent onRead = b.getParcelable(KEY_ON_READ); 10548 PendingIntent onReply = b.getParcelable(KEY_ON_REPLY); 10549 10550 RemoteInput remoteInput = b.getParcelable(KEY_REMOTE_INPUT); 10551 10552 String[] participants = b.getStringArray(KEY_PARTICIPANTS); 10553 if (participants == null || participants.length != 1) { 10554 return null; 10555 } 10556 10557 return new UnreadConversation(messages, 10558 remoteInput, 10559 onReply, 10560 onRead, 10561 participants, b.getLong(KEY_TIMESTAMP)); 10562 } 10563 }; 10564 10565 /** 10566 * Builder class for {@link CarExtender.UnreadConversation} objects. 10567 */ 10568 public static class Builder { 10569 private final List<String> mMessages = new ArrayList<String>(); 10570 private final String mParticipant; 10571 private RemoteInput mRemoteInput; 10572 private PendingIntent mReadPendingIntent; 10573 private PendingIntent mReplyPendingIntent; 10574 private long mLatestTimestamp; 10575 10576 /** 10577 * Constructs a new builder for {@link CarExtender.UnreadConversation}. 10578 * 10579 * @param name The name of the other participant in the conversation. 10580 */ Builder(String name)10581 public Builder(String name) { 10582 mParticipant = name; 10583 } 10584 10585 /** 10586 * Appends a new unread message to the list of messages for this conversation. 10587 * 10588 * The messages should be added from oldest to newest. 10589 * 10590 * @param message The text of the new unread message. 10591 * @return This object for method chaining. 10592 */ addMessage(String message)10593 public Builder addMessage(String message) { 10594 mMessages.add(message); 10595 return this; 10596 } 10597 10598 /** 10599 * Sets the pending intent and remote input which will convey the reply to this 10600 * notification. 10601 * 10602 * @param pendingIntent The pending intent which will be triggered on a reply. 10603 * @param remoteInput The remote input parcelable which will carry the reply. 10604 * @return This object for method chaining. 10605 * 10606 * @see CarExtender.UnreadConversation#getRemoteInput 10607 * @see CarExtender.UnreadConversation#getReplyPendingIntent 10608 */ setReplyAction( PendingIntent pendingIntent, RemoteInput remoteInput)10609 public Builder setReplyAction( 10610 PendingIntent pendingIntent, RemoteInput remoteInput) { 10611 mRemoteInput = remoteInput; 10612 mReplyPendingIntent = pendingIntent; 10613 10614 return this; 10615 } 10616 10617 /** 10618 * Sets the pending intent that will be sent once the messages in this notification 10619 * are read. 10620 * 10621 * @param pendingIntent The pending intent to use. 10622 * @return This object for method chaining. 10623 */ setReadPendingIntent(PendingIntent pendingIntent)10624 public Builder setReadPendingIntent(PendingIntent pendingIntent) { 10625 mReadPendingIntent = pendingIntent; 10626 return this; 10627 } 10628 10629 /** 10630 * Sets the timestamp of the most recent message in an unread conversation. 10631 * 10632 * If a messaging notification has been posted by your application and has not 10633 * yet been cancelled, posting a later notification with the same id and tag 10634 * but without a newer timestamp may result in Android Auto not displaying a 10635 * heads up notification for the later notification. 10636 * 10637 * @param timestamp The timestamp of the most recent message in the conversation. 10638 * @return This object for method chaining. 10639 */ setLatestTimestamp(long timestamp)10640 public Builder setLatestTimestamp(long timestamp) { 10641 mLatestTimestamp = timestamp; 10642 return this; 10643 } 10644 10645 /** 10646 * Builds a new unread conversation object. 10647 * 10648 * @return The new unread conversation object. 10649 */ build()10650 public UnreadConversation build() { 10651 String[] messages = mMessages.toArray(new String[mMessages.size()]); 10652 String[] participants = { mParticipant }; 10653 return new UnreadConversation(messages, mRemoteInput, mReplyPendingIntent, 10654 mReadPendingIntent, participants, mLatestTimestamp); 10655 } 10656 } 10657 } 10658 10659 /** 10660 * <p>Helper class to add Android TV extensions to notifications. To create a notification 10661 * with a TV extension: 10662 * 10663 * <ol> 10664 * <li>Create an {@link Notification.Builder}, setting any desired properties. 10665 * <li>Create a {@link TvExtender}. 10666 * <li>Set TV-specific properties using the {@code set} methods of 10667 * {@link TvExtender}. 10668 * <li>Call {@link Notification.Builder#extend(Notification.Extender)} 10669 * to apply the extension to a notification. 10670 * </ol> 10671 * 10672 * <pre class="prettyprint"> 10673 * Notification notification = new Notification.Builder(context) 10674 * ... 10675 * .extend(new TvExtender() 10676 * .set*(...)) 10677 * .build(); 10678 * </pre> 10679 * 10680 * <p>TV extensions can be accessed on an existing notification by using the 10681 * {@code TvExtender(Notification)} constructor, and then using the {@code get} methods 10682 * to access values. 10683 * 10684 * @hide 10685 */ 10686 @SystemApi 10687 public static final class TvExtender implements Extender { 10688 private static final String TAG = "TvExtender"; 10689 10690 private static final String EXTRA_TV_EXTENDER = "android.tv.EXTENSIONS"; 10691 private static final String EXTRA_FLAGS = "flags"; 10692 private static final String EXTRA_CONTENT_INTENT = "content_intent"; 10693 private static final String EXTRA_DELETE_INTENT = "delete_intent"; 10694 private static final String EXTRA_CHANNEL_ID = "channel_id"; 10695 private static final String EXTRA_SUPPRESS_SHOW_OVER_APPS = "suppressShowOverApps"; 10696 10697 // Flags bitwise-ored to mFlags 10698 private static final int FLAG_AVAILABLE_ON_TV = 0x1; 10699 10700 private int mFlags; 10701 private String mChannelId; 10702 private PendingIntent mContentIntent; 10703 private PendingIntent mDeleteIntent; 10704 private boolean mSuppressShowOverApps; 10705 10706 /** 10707 * Create a {@link TvExtender} with default options. 10708 */ TvExtender()10709 public TvExtender() { 10710 mFlags = FLAG_AVAILABLE_ON_TV; 10711 } 10712 10713 /** 10714 * Create a {@link TvExtender} from the TvExtender options of an existing Notification. 10715 * 10716 * @param notif The notification from which to copy options. 10717 */ TvExtender(Notification notif)10718 public TvExtender(Notification notif) { 10719 Bundle bundle = notif.extras == null ? 10720 null : notif.extras.getBundle(EXTRA_TV_EXTENDER); 10721 if (bundle != null) { 10722 mFlags = bundle.getInt(EXTRA_FLAGS); 10723 mChannelId = bundle.getString(EXTRA_CHANNEL_ID); 10724 mSuppressShowOverApps = bundle.getBoolean(EXTRA_SUPPRESS_SHOW_OVER_APPS); 10725 mContentIntent = bundle.getParcelable(EXTRA_CONTENT_INTENT); 10726 mDeleteIntent = bundle.getParcelable(EXTRA_DELETE_INTENT); 10727 } 10728 } 10729 10730 /** 10731 * Apply a TV extension to a notification that is being built. This is typically called by 10732 * the {@link Notification.Builder#extend(Notification.Extender)} 10733 * method of {@link Notification.Builder}. 10734 */ 10735 @Override extend(Notification.Builder builder)10736 public Notification.Builder extend(Notification.Builder builder) { 10737 Bundle bundle = new Bundle(); 10738 10739 bundle.putInt(EXTRA_FLAGS, mFlags); 10740 bundle.putString(EXTRA_CHANNEL_ID, mChannelId); 10741 bundle.putBoolean(EXTRA_SUPPRESS_SHOW_OVER_APPS, mSuppressShowOverApps); 10742 if (mContentIntent != null) { 10743 bundle.putParcelable(EXTRA_CONTENT_INTENT, mContentIntent); 10744 } 10745 10746 if (mDeleteIntent != null) { 10747 bundle.putParcelable(EXTRA_DELETE_INTENT, mDeleteIntent); 10748 } 10749 10750 builder.getExtras().putBundle(EXTRA_TV_EXTENDER, bundle); 10751 return builder; 10752 } 10753 10754 /** 10755 * Returns true if this notification should be shown on TV. This method return true 10756 * if the notification was extended with a TvExtender. 10757 */ isAvailableOnTv()10758 public boolean isAvailableOnTv() { 10759 return (mFlags & FLAG_AVAILABLE_ON_TV) != 0; 10760 } 10761 10762 /** 10763 * Specifies the channel the notification should be delivered on when shown on TV. 10764 * It can be different from the channel that the notification is delivered to when 10765 * posting on a non-TV device. 10766 */ setChannel(String channelId)10767 public TvExtender setChannel(String channelId) { 10768 mChannelId = channelId; 10769 return this; 10770 } 10771 10772 /** 10773 * Specifies the channel the notification should be delivered on when shown on TV. 10774 * It can be different from the channel that the notification is delivered to when 10775 * posting on a non-TV device. 10776 */ setChannelId(String channelId)10777 public TvExtender setChannelId(String channelId) { 10778 mChannelId = channelId; 10779 return this; 10780 } 10781 10782 /** @removed */ 10783 @Deprecated getChannel()10784 public String getChannel() { 10785 return mChannelId; 10786 } 10787 10788 /** 10789 * Returns the id of the channel this notification posts to on TV. 10790 */ getChannelId()10791 public String getChannelId() { 10792 return mChannelId; 10793 } 10794 10795 /** 10796 * Supplies a {@link PendingIntent} to be sent when the notification is selected on TV. 10797 * If provided, it is used instead of the content intent specified 10798 * at the level of Notification. 10799 */ setContentIntent(PendingIntent intent)10800 public TvExtender setContentIntent(PendingIntent intent) { 10801 mContentIntent = intent; 10802 return this; 10803 } 10804 10805 /** 10806 * Returns the TV-specific content intent. If this method returns null, the 10807 * main content intent on the notification should be used. 10808 * 10809 * @see {@link Notification#contentIntent} 10810 */ getContentIntent()10811 public PendingIntent getContentIntent() { 10812 return mContentIntent; 10813 } 10814 10815 /** 10816 * Supplies a {@link PendingIntent} to send when the notification is cleared explicitly 10817 * by the user on TV. If provided, it is used instead of the delete intent specified 10818 * at the level of Notification. 10819 */ setDeleteIntent(PendingIntent intent)10820 public TvExtender setDeleteIntent(PendingIntent intent) { 10821 mDeleteIntent = intent; 10822 return this; 10823 } 10824 10825 /** 10826 * Returns the TV-specific delete intent. If this method returns null, the 10827 * main delete intent on the notification should be used. 10828 * 10829 * @see {@link Notification#deleteIntent} 10830 */ getDeleteIntent()10831 public PendingIntent getDeleteIntent() { 10832 return mDeleteIntent; 10833 } 10834 10835 /** 10836 * Specifies whether this notification should suppress showing a message over top of apps 10837 * outside of the launcher. 10838 */ setSuppressShowOverApps(boolean suppress)10839 public TvExtender setSuppressShowOverApps(boolean suppress) { 10840 mSuppressShowOverApps = suppress; 10841 return this; 10842 } 10843 10844 /** 10845 * Returns true if this notification should not show messages over top of apps 10846 * outside of the launcher. 10847 */ getSuppressShowOverApps()10848 public boolean getSuppressShowOverApps() { 10849 return mSuppressShowOverApps; 10850 } 10851 } 10852 10853 /** 10854 * Get an array of Parcelable objects from a parcelable array bundle field. 10855 * Update the bundle to have a typed array so fetches in the future don't need 10856 * to do an array copy. 10857 */ 10858 @Nullable getParcelableArrayFromBundle( Bundle bundle, String key, Class<T> itemClass)10859 private static <T extends Parcelable> T[] getParcelableArrayFromBundle( 10860 Bundle bundle, String key, Class<T> itemClass) { 10861 final Parcelable[] array = bundle.getParcelableArray(key); 10862 final Class<?> arrayClass = Array.newInstance(itemClass, 0).getClass(); 10863 if (arrayClass.isInstance(array) || array == null) { 10864 return (T[]) array; 10865 } 10866 final T[] typedArray = (T[]) Array.newInstance(itemClass, array.length); 10867 for (int i = 0; i < array.length; i++) { 10868 typedArray[i] = (T) array[i]; 10869 } 10870 bundle.putParcelableArray(key, typedArray); 10871 return typedArray; 10872 } 10873 10874 private static class BuilderRemoteViews extends RemoteViews { BuilderRemoteViews(Parcel parcel)10875 public BuilderRemoteViews(Parcel parcel) { 10876 super(parcel); 10877 } 10878 BuilderRemoteViews(ApplicationInfo appInfo, int layoutId)10879 public BuilderRemoteViews(ApplicationInfo appInfo, int layoutId) { 10880 super(appInfo, layoutId); 10881 } 10882 10883 @Override clone()10884 public BuilderRemoteViews clone() { 10885 Parcel p = Parcel.obtain(); 10886 writeToParcel(p, 0); 10887 p.setDataPosition(0); 10888 BuilderRemoteViews brv = new BuilderRemoteViews(p); 10889 p.recycle(); 10890 return brv; 10891 } 10892 10893 /** 10894 * Override and return true, since {@link RemoteViews#onLoadClass(Class)} is not overridden. 10895 * 10896 * @see RemoteViews#shouldUseStaticFilter() 10897 */ 10898 @Override shouldUseStaticFilter()10899 protected boolean shouldUseStaticFilter() { 10900 return true; 10901 } 10902 } 10903 10904 /** 10905 * A result object where information about the template that was created is saved. 10906 */ 10907 private static class TemplateBindResult { 10908 int mIconMarginEnd; 10909 boolean mRightIconContainerVisible; 10910 10911 /** 10912 * Get the margin end that needs to be added to any fields that may overlap 10913 * with the right actions. 10914 */ getIconMarginEnd()10915 public int getIconMarginEnd() { 10916 return mIconMarginEnd; 10917 } 10918 10919 /** 10920 * Is the icon container visible on the right size because of the reply button or the 10921 * right icon. 10922 */ isRightIconContainerVisible()10923 public boolean isRightIconContainerVisible() { 10924 return mRightIconContainerVisible; 10925 } 10926 setIconMarginEnd(int iconMarginEnd)10927 public void setIconMarginEnd(int iconMarginEnd) { 10928 this.mIconMarginEnd = iconMarginEnd; 10929 } 10930 setRightIconContainerVisible(boolean iconContainerVisible)10931 public void setRightIconContainerVisible(boolean iconContainerVisible) { 10932 mRightIconContainerVisible = iconContainerVisible; 10933 } 10934 } 10935 10936 private static class StandardTemplateParams { 10937 boolean hasProgress = true; 10938 CharSequence title; 10939 CharSequence text; 10940 CharSequence headerTextSecondary; 10941 CharSequence summaryText; 10942 int maxRemoteInputHistory = Style.MAX_REMOTE_INPUT_HISTORY_LINES; 10943 boolean hideLargeIcon; 10944 boolean hideReplyIcon; 10945 boolean allowColorization = true; 10946 boolean forceDefaultColor = false; 10947 reset()10948 final StandardTemplateParams reset() { 10949 hasProgress = true; 10950 title = null; 10951 text = null; 10952 summaryText = null; 10953 headerTextSecondary = null; 10954 maxRemoteInputHistory = Style.MAX_REMOTE_INPUT_HISTORY_LINES; 10955 allowColorization = true; 10956 forceDefaultColor = false; 10957 return this; 10958 } 10959 hasProgress(boolean hasProgress)10960 final StandardTemplateParams hasProgress(boolean hasProgress) { 10961 this.hasProgress = hasProgress; 10962 return this; 10963 } 10964 title(CharSequence title)10965 final StandardTemplateParams title(CharSequence title) { 10966 this.title = title; 10967 return this; 10968 } 10969 text(CharSequence text)10970 final StandardTemplateParams text(CharSequence text) { 10971 this.text = text; 10972 return this; 10973 } 10974 summaryText(CharSequence text)10975 final StandardTemplateParams summaryText(CharSequence text) { 10976 this.summaryText = text; 10977 return this; 10978 } 10979 headerTextSecondary(CharSequence text)10980 final StandardTemplateParams headerTextSecondary(CharSequence text) { 10981 this.headerTextSecondary = text; 10982 return this; 10983 } 10984 hideLargeIcon(boolean hideLargeIcon)10985 final StandardTemplateParams hideLargeIcon(boolean hideLargeIcon) { 10986 this.hideLargeIcon = hideLargeIcon; 10987 return this; 10988 } 10989 hideReplyIcon(boolean hideReplyIcon)10990 final StandardTemplateParams hideReplyIcon(boolean hideReplyIcon) { 10991 this.hideReplyIcon = hideReplyIcon; 10992 return this; 10993 } 10994 disallowColorization()10995 final StandardTemplateParams disallowColorization() { 10996 this.allowColorization = false; 10997 return this; 10998 } 10999 forceDefaultColor()11000 final StandardTemplateParams forceDefaultColor() { 11001 this.forceDefaultColor = true; 11002 return this; 11003 } 11004 fillTextsFrom(Builder b)11005 final StandardTemplateParams fillTextsFrom(Builder b) { 11006 Bundle extras = b.mN.extras; 11007 this.title = b.processLegacyText(extras.getCharSequence(EXTRA_TITLE)); 11008 this.text = b.processLegacyText(extras.getCharSequence(EXTRA_TEXT)); 11009 this.summaryText = extras.getCharSequence(EXTRA_SUB_TEXT); 11010 return this; 11011 } 11012 11013 /** 11014 * Set the maximum lines of remote input history lines allowed. 11015 * @param maxRemoteInputHistory The number of lines. 11016 * @return The builder for method chaining. 11017 */ setMaxRemoteInputHistory(int maxRemoteInputHistory)11018 public StandardTemplateParams setMaxRemoteInputHistory(int maxRemoteInputHistory) { 11019 this.maxRemoteInputHistory = maxRemoteInputHistory; 11020 return this; 11021 } 11022 } 11023 } 11024