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