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