1 /* 2 * Copyright (C) 2012 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.support.v4.app; 18 19 import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP; 20 21 import static java.lang.annotation.RetentionPolicy.SOURCE; 22 23 import android.app.Activity; 24 import android.app.Notification; 25 import android.app.PendingIntent; 26 import android.content.Context; 27 import android.content.res.ColorStateList; 28 import android.content.res.Resources; 29 import android.graphics.Bitmap; 30 import android.graphics.Canvas; 31 import android.graphics.Color; 32 import android.graphics.PorterDuff; 33 import android.graphics.PorterDuffColorFilter; 34 import android.graphics.drawable.Drawable; 35 import android.media.AudioManager; 36 import android.net.Uri; 37 import android.os.Build; 38 import android.os.Bundle; 39 import android.os.Parcelable; 40 import android.os.SystemClock; 41 import android.support.annotation.ColorInt; 42 import android.support.annotation.IntDef; 43 import android.support.annotation.NonNull; 44 import android.support.annotation.Nullable; 45 import android.support.annotation.RequiresApi; 46 import android.support.annotation.RestrictTo; 47 import android.support.compat.R; 48 import android.support.v4.text.BidiFormatter; 49 import android.support.v4.view.GravityCompat; 50 import android.text.SpannableStringBuilder; 51 import android.text.Spanned; 52 import android.text.TextUtils; 53 import android.text.style.TextAppearanceSpan; 54 import android.util.SparseArray; 55 import android.util.TypedValue; 56 import android.view.Gravity; 57 import android.view.View; 58 import android.widget.RemoteViews; 59 60 import java.lang.annotation.Retention; 61 import java.lang.annotation.RetentionPolicy; 62 import java.text.NumberFormat; 63 import java.util.ArrayList; 64 import java.util.Arrays; 65 import java.util.Collections; 66 import java.util.List; 67 68 /** 69 * Helper for accessing features in {@link android.app.Notification}. 70 */ 71 public class NotificationCompat { 72 73 /** 74 * Use all default values (where applicable). 75 */ 76 public static final int DEFAULT_ALL = ~0; 77 78 /** 79 * Use the default notification sound. This will ignore any sound set using 80 * {@link Builder#setSound} 81 * 82 * <p> 83 * A notification that is noisy is more likely to be presented as a heads-up notification, 84 * on some platforms. 85 * </p> 86 * 87 * @see Builder#setDefaults 88 */ 89 public static final int DEFAULT_SOUND = 1; 90 91 /** 92 * Use the default notification vibrate. This will ignore any vibrate set using 93 * {@link Builder#setVibrate}. Using phone vibration requires the 94 * {@link android.Manifest.permission#VIBRATE VIBRATE} permission. 95 * 96 * <p> 97 * A notification that vibrates is more likely to be presented as a heads-up notification, 98 * on some platforms. 99 * </p> 100 * 101 * @see Builder#setDefaults 102 */ 103 public static final int DEFAULT_VIBRATE = 2; 104 105 /** 106 * Use the default notification lights. This will ignore the 107 * {@link #FLAG_SHOW_LIGHTS} bit, and values set with {@link Builder#setLights}. 108 * 109 * @see Builder#setDefaults 110 */ 111 public static final int DEFAULT_LIGHTS = 4; 112 113 /** 114 * Use this constant as the value for audioStreamType to request that 115 * the default stream type for notifications be used. Currently the 116 * default stream type is {@link AudioManager#STREAM_NOTIFICATION}. 117 */ 118 public static final int STREAM_DEFAULT = -1; 119 120 /** 121 * Bit set in the Notification flags field when LEDs should be turned on 122 * for this notification. 123 */ 124 public static final int FLAG_SHOW_LIGHTS = 0x00000001; 125 126 /** 127 * Bit set in the Notification flags field if this notification is in 128 * reference to something that is ongoing, like a phone call. It should 129 * not be set if this notification is in reference to something that 130 * happened at a particular point in time, like a missed phone call. 131 */ 132 public static final int FLAG_ONGOING_EVENT = 0x00000002; 133 134 /** 135 * Bit set in the Notification flags field if 136 * the audio will be repeated until the notification is 137 * cancelled or the notification window is opened. 138 */ 139 public static final int FLAG_INSISTENT = 0x00000004; 140 141 /** 142 * Bit set in the Notification flags field if the notification's sound, 143 * vibrate and ticker should only be played if the notification is not already showing. 144 */ 145 public static final int FLAG_ONLY_ALERT_ONCE = 0x00000008; 146 147 /** 148 * Bit set in the Notification flags field if the notification should be canceled when 149 * it is clicked by the user. 150 */ 151 public static final int FLAG_AUTO_CANCEL = 0x00000010; 152 153 /** 154 * Bit set in the Notification flags field if the notification should not be canceled 155 * when the user clicks the Clear all button. 156 */ 157 public static final int FLAG_NO_CLEAR = 0x00000020; 158 159 /** 160 * Bit set in the Notification flags field if this notification represents a currently 161 * running service. This will normally be set for you by 162 * {@link android.app.Service#startForeground}. 163 */ 164 public static final int FLAG_FOREGROUND_SERVICE = 0x00000040; 165 166 /** 167 * Obsolete flag indicating high-priority notifications; use the priority field instead. 168 * 169 * @deprecated Use {@link NotificationCompat.Builder#setPriority(int)} with a positive value. 170 */ 171 @Deprecated 172 public static final int FLAG_HIGH_PRIORITY = 0x00000080; 173 174 /** 175 * Bit set in the Notification flags field if this notification is relevant to the current 176 * device only and it is not recommended that it bridge to other devices. 177 */ 178 public static final int FLAG_LOCAL_ONLY = 0x00000100; 179 180 /** 181 * Bit set in the Notification flags field if this notification is the group summary for a 182 * group of notifications. Grouped notifications may display in a cluster or stack on devices 183 * which support such rendering. Requires a group key also be set using 184 * {@link Builder#setGroup}. 185 */ 186 public static final int FLAG_GROUP_SUMMARY = 0x00000200; 187 188 /** 189 * Default notification priority for {@link NotificationCompat.Builder#setPriority(int)}. 190 * If your application does not prioritize its own notifications, 191 * use this value for all notifications. 192 */ 193 public static final int PRIORITY_DEFAULT = 0; 194 195 /** 196 * Lower notification priority for {@link NotificationCompat.Builder#setPriority(int)}, 197 * for items that are less important. The UI may choose to show 198 * these items smaller, or at a different position in the list, 199 * compared with your app's {@link #PRIORITY_DEFAULT} items. 200 */ 201 public static final int PRIORITY_LOW = -1; 202 203 /** 204 * Lowest notification priority for {@link NotificationCompat.Builder#setPriority(int)}; 205 * these items might not be shown to the user except under 206 * special circumstances, such as detailed notification logs. 207 */ 208 public static final int PRIORITY_MIN = -2; 209 210 /** 211 * Higher notification priority for {@link NotificationCompat.Builder#setPriority(int)}, 212 * for more important notifications or alerts. The UI may choose 213 * to show these items larger, or at a different position in 214 * notification lists, compared with your app's {@link #PRIORITY_DEFAULT} items. 215 */ 216 public static final int PRIORITY_HIGH = 1; 217 218 /** 219 * Highest notification priority for {@link NotificationCompat.Builder#setPriority(int)}, 220 * for your application's most important items that require the user's 221 * prompt attention or input. 222 */ 223 public static final int PRIORITY_MAX = 2; 224 225 /** 226 * Notification extras key: this is the title of the notification, 227 * as supplied to {@link Builder#setContentTitle(CharSequence)}. 228 */ 229 public static final String EXTRA_TITLE = "android.title"; 230 231 /** 232 * Notification extras key: this is the title of the notification when shown in expanded form, 233 * e.g. as supplied to {@link BigTextStyle#setBigContentTitle(CharSequence)}. 234 */ 235 public static final String EXTRA_TITLE_BIG = EXTRA_TITLE + ".big"; 236 237 /** 238 * Notification extras key: this is the main text payload, as supplied to 239 * {@link Builder#setContentText(CharSequence)}. 240 */ 241 public static final String EXTRA_TEXT = "android.text"; 242 243 /** 244 * Notification extras key: this is a third line of text, as supplied to 245 * {@link Builder#setSubText(CharSequence)}. 246 */ 247 public static final String EXTRA_SUB_TEXT = "android.subText"; 248 249 /** 250 * Notification extras key: this is the remote input history, as supplied to 251 * {@link Builder#setRemoteInputHistory(CharSequence[])}. 252 * 253 * Apps can fill this through {@link Builder#setRemoteInputHistory(CharSequence[])} 254 * with the most recent inputs that have been sent through a {@link RemoteInput} of this 255 * Notification and are expected to clear it once the it is no longer relevant (e.g. for chat 256 * notifications once the other party has responded). 257 * 258 * The extra with this key is of type CharSequence[] and contains the most recent entry at 259 * the 0 index, the second most recent at the 1 index, etc. 260 * 261 * @see Builder#setRemoteInputHistory(CharSequence[]) 262 */ 263 public static final String EXTRA_REMOTE_INPUT_HISTORY = "android.remoteInputHistory"; 264 265 /** 266 * Notification extras key: this is a small piece of additional text as supplied to 267 * {@link Builder#setContentInfo(CharSequence)}. 268 */ 269 public static final String EXTRA_INFO_TEXT = "android.infoText"; 270 271 /** 272 * Notification extras key: this is a line of summary information intended to be shown 273 * alongside expanded notifications, as supplied to (e.g.) 274 * {@link BigTextStyle#setSummaryText(CharSequence)}. 275 */ 276 public static final String EXTRA_SUMMARY_TEXT = "android.summaryText"; 277 278 /** 279 * Notification extras key: this is the longer text shown in the big form of a 280 * {@link BigTextStyle} notification, as supplied to 281 * {@link BigTextStyle#bigText(CharSequence)}. 282 */ 283 public static final String EXTRA_BIG_TEXT = "android.bigText"; 284 285 /** 286 * Notification extras key: this is the resource ID of the notification's main small icon, as 287 * supplied to {@link Builder#setSmallIcon(int)}. 288 */ 289 public static final String EXTRA_SMALL_ICON = "android.icon"; 290 291 /** 292 * Notification extras key: this is a bitmap to be used instead of the small icon when showing the 293 * notification payload, as 294 * supplied to {@link Builder#setLargeIcon(android.graphics.Bitmap)}. 295 */ 296 public static final String EXTRA_LARGE_ICON = "android.largeIcon"; 297 298 /** 299 * Notification extras key: this is a bitmap to be used instead of the one from 300 * {@link Builder#setLargeIcon(android.graphics.Bitmap)} when the notification is 301 * shown in its expanded form, as supplied to 302 * {@link BigPictureStyle#bigLargeIcon(android.graphics.Bitmap)}. 303 */ 304 public static final String EXTRA_LARGE_ICON_BIG = EXTRA_LARGE_ICON + ".big"; 305 306 /** 307 * Notification extras key: this is the progress value supplied to 308 * {@link Builder#setProgress(int, int, boolean)}. 309 */ 310 public static final String EXTRA_PROGRESS = "android.progress"; 311 312 /** 313 * Notification extras key: this is the maximum value supplied to 314 * {@link Builder#setProgress(int, int, boolean)}. 315 */ 316 public static final String EXTRA_PROGRESS_MAX = "android.progressMax"; 317 318 /** 319 * Notification extras key: whether the progress bar is indeterminate, supplied to 320 * {@link Builder#setProgress(int, int, boolean)}. 321 */ 322 public static final String EXTRA_PROGRESS_INDETERMINATE = "android.progressIndeterminate"; 323 324 /** 325 * Notification extras key: whether the when field set using {@link Builder#setWhen} should 326 * be shown as a count-up timer (specifically a {@link android.widget.Chronometer}) instead 327 * of a timestamp, as supplied to {@link Builder#setUsesChronometer(boolean)}. 328 */ 329 public static final String EXTRA_SHOW_CHRONOMETER = "android.showChronometer"; 330 331 /** 332 * Notification extras key: whether the when field set using {@link Builder#setWhen} should 333 * be shown, as supplied to {@link Builder#setShowWhen(boolean)}. 334 */ 335 public static final String EXTRA_SHOW_WHEN = "android.showWhen"; 336 337 /** 338 * Notification extras key: this is a bitmap to be shown in {@link BigPictureStyle} expanded 339 * notifications, supplied to {@link BigPictureStyle#bigPicture(android.graphics.Bitmap)}. 340 */ 341 public static final String EXTRA_PICTURE = "android.picture"; 342 343 /** 344 * Notification extras key: An array of CharSequences to show in {@link InboxStyle} expanded 345 * notifications, each of which was supplied to {@link InboxStyle#addLine(CharSequence)}. 346 */ 347 public static final String EXTRA_TEXT_LINES = "android.textLines"; 348 349 /** 350 * Notification extras key: A string representing the name of the specific 351 * {@link android.app.Notification.Style} used to create this notification. 352 */ 353 public static final String EXTRA_TEMPLATE = "android.template"; 354 355 /** 356 * Notification extras key: A String array containing the people that this 357 * notification relates to, each of which was supplied to 358 * {@link Builder#addPerson(String)}. 359 */ 360 public static final String EXTRA_PEOPLE = "android.people"; 361 362 /** 363 * Notification extras key: A 364 * {@link android.content.ContentUris content URI} pointing to an image that can be displayed 365 * in the background when the notification is selected. The URI must point to an image stream 366 * suitable for passing into 367 * {@link android.graphics.BitmapFactory#decodeStream(java.io.InputStream) 368 * BitmapFactory.decodeStream}; all other content types will be ignored. The content provider 369 * URI used for this purpose must require no permissions to read the image data. 370 */ 371 public static final String EXTRA_BACKGROUND_IMAGE_URI = "android.backgroundImageUri"; 372 373 /** 374 * Notification key: A 375 * {@link android.media.session.MediaSession.Token} associated with a 376 * {@link android.app.Notification.MediaStyle} notification. 377 */ 378 public static final String EXTRA_MEDIA_SESSION = "android.mediaSession"; 379 380 /** 381 * Notification extras key: the indices of actions to be shown in the compact view, 382 * as supplied to (e.g.) {@link Notification.MediaStyle#setShowActionsInCompactView(int...)}. 383 */ 384 public static final String EXTRA_COMPACT_ACTIONS = "android.compactActions"; 385 386 /** 387 * Notification key: the username to be displayed for all messages sent by the user 388 * including 389 * direct replies 390 * {@link MessagingStyle} notification. 391 */ 392 public static final String EXTRA_SELF_DISPLAY_NAME = "android.selfDisplayName"; 393 394 /** 395 * Notification key: a {@link String} to be displayed as the title to a conversation 396 * represented by a {@link MessagingStyle} 397 */ 398 public static final String EXTRA_CONVERSATION_TITLE = "android.conversationTitle"; 399 400 /** 401 * Notification key: an array of {@link Bundle} objects representing 402 * {@link MessagingStyle.Message} objects for a {@link MessagingStyle} notification. 403 */ 404 public static final String EXTRA_MESSAGES = "android.messages"; 405 406 /** 407 * Keys into the {@link #getExtras} Bundle: the audio contents of this notification. 408 * 409 * This is for use when rendering the notification on an audio-focused interface; 410 * the audio contents are a complete sound sample that contains the contents/body of the 411 * notification. This may be used in substitute of a Text-to-Speech reading of the 412 * notification. For example if the notification represents a voice message this should point 413 * to the audio of that message. 414 * 415 * The data stored under this key should be a String representation of a Uri that contains the 416 * audio contents in one of the following formats: WAV, PCM 16-bit, AMR-WB. 417 * 418 * This extra is unnecessary if you are using {@code MessagingStyle} since each {@code Message} 419 * has a field for holding data URI. That field can be used for audio. 420 * See {@code Message#setData}. 421 * 422 * Example usage: 423 * <pre> 424 * {@code 425 * NotificationCompat.Builder myBuilder = (build your Notification as normal); 426 * myBuilder.getExtras().putString(EXTRA_AUDIO_CONTENTS_URI, myAudioUri.toString()); 427 * } 428 * </pre> 429 */ 430 public static final String EXTRA_AUDIO_CONTENTS_URI = "android.audioContents"; 431 432 /** 433 * Value of {@link Notification#color} equal to 0 (also known as 434 * {@link android.graphics.Color#TRANSPARENT Color.TRANSPARENT}), 435 * telling the system not to decorate this notification with any special color but instead use 436 * default colors when presenting this notification. 437 */ 438 @ColorInt 439 public static final int COLOR_DEFAULT = Color.TRANSPARENT; 440 441 /** @hide */ 442 @Retention(SOURCE) 443 @IntDef({VISIBILITY_PUBLIC, VISIBILITY_PRIVATE, VISIBILITY_SECRET}) 444 public @interface NotificationVisibility {} 445 /** 446 * Notification visibility: Show this notification in its entirety on all lockscreens. 447 * 448 * {@see android.app.Notification#visibility} 449 */ 450 public static final int VISIBILITY_PUBLIC = Notification.VISIBILITY_PUBLIC; 451 452 /** 453 * Notification visibility: Show this notification on all lockscreens, but conceal sensitive or 454 * private information on secure lockscreens. 455 * 456 * {@see android.app.Notification#visibility} 457 */ 458 public static final int VISIBILITY_PRIVATE = Notification.VISIBILITY_PRIVATE; 459 460 /** 461 * Notification visibility: Do not reveal any part of this notification on a secure lockscreen. 462 * 463 * {@see android.app.Notification#visibility} 464 */ 465 public static final int VISIBILITY_SECRET = Notification.VISIBILITY_SECRET; 466 467 /** 468 * Notification category: incoming call (voice or video) or similar synchronous communication request. 469 */ 470 public static final String CATEGORY_CALL = Notification.CATEGORY_CALL; 471 472 /** 473 * Notification category: incoming direct message (SMS, instant message, etc.). 474 */ 475 public static final String CATEGORY_MESSAGE = Notification.CATEGORY_MESSAGE; 476 477 /** 478 * Notification category: asynchronous bulk message (email). 479 */ 480 public static final String CATEGORY_EMAIL = Notification.CATEGORY_EMAIL; 481 482 /** 483 * Notification category: calendar event. 484 */ 485 public static final String CATEGORY_EVENT = Notification.CATEGORY_EVENT; 486 487 /** 488 * Notification category: promotion or advertisement. 489 */ 490 public static final String CATEGORY_PROMO = Notification.CATEGORY_PROMO; 491 492 /** 493 * Notification category: alarm or timer. 494 */ 495 public static final String CATEGORY_ALARM = Notification.CATEGORY_ALARM; 496 497 /** 498 * Notification category: progress of a long-running background operation. 499 */ 500 public static final String CATEGORY_PROGRESS = Notification.CATEGORY_PROGRESS; 501 502 /** 503 * Notification category: social network or sharing update. 504 */ 505 public static final String CATEGORY_SOCIAL = Notification.CATEGORY_SOCIAL; 506 507 /** 508 * Notification category: error in background operation or authentication status. 509 */ 510 public static final String CATEGORY_ERROR = Notification.CATEGORY_ERROR; 511 512 /** 513 * Notification category: media transport control for playback. 514 */ 515 public static final String CATEGORY_TRANSPORT = Notification.CATEGORY_TRANSPORT; 516 517 /** 518 * Notification category: system or device status update. Reserved for system use. 519 */ 520 public static final String CATEGORY_SYSTEM = Notification.CATEGORY_SYSTEM; 521 522 /** 523 * Notification category: indication of running background service. 524 */ 525 public static final String CATEGORY_SERVICE = Notification.CATEGORY_SERVICE; 526 527 /** 528 * Notification category: user-scheduled reminder. 529 */ 530 public static final String CATEGORY_REMINDER = Notification.CATEGORY_REMINDER; 531 532 /** 533 * Notification category: a specific, timely recommendation for a single thing. 534 * For example, a news app might want to recommend a news story it believes the user will 535 * want to read next. 536 */ 537 public static final String CATEGORY_RECOMMENDATION = 538 Notification.CATEGORY_RECOMMENDATION; 539 540 /** 541 * Notification category: ongoing information about device or contextual status. 542 */ 543 public static final String CATEGORY_STATUS = Notification.CATEGORY_STATUS; 544 545 /** @hide */ 546 @Retention(RetentionPolicy.SOURCE) 547 @RestrictTo(LIBRARY_GROUP) 548 @IntDef({BADGE_ICON_NONE, BADGE_ICON_SMALL, BADGE_ICON_LARGE}) 549 public @interface BadgeIconType {} 550 /** 551 * If this notification is being shown as a badge, always show as a number. 552 */ 553 public static final int BADGE_ICON_NONE = Notification.BADGE_ICON_NONE; 554 555 /** 556 * If this notification is being shown as a badge, use the icon provided to 557 * {@link Builder#setSmallIcon(int)} to represent this notification. 558 */ 559 public static final int BADGE_ICON_SMALL = Notification.BADGE_ICON_SMALL; 560 561 /** 562 * If this notification is being shown as a badge, use the icon provided to 563 * {@link Builder#setLargeIcon(Bitmap) to represent this notification. 564 */ 565 public static final int BADGE_ICON_LARGE = Notification.BADGE_ICON_LARGE; 566 567 /** @hide */ 568 @Retention(RetentionPolicy.SOURCE) 569 @RestrictTo(LIBRARY_GROUP) 570 @IntDef({GROUP_ALERT_ALL, GROUP_ALERT_SUMMARY, GROUP_ALERT_CHILDREN}) 571 public @interface GroupAlertBehavior {} 572 573 /** 574 * Constant for {@link Builder#setGroupAlertBehavior(int)}, meaning that all notifications in a 575 * group with sound or vibration ought to make sound or vibrate (respectively), so this 576 * notification will not be muted when it is in a group. 577 */ 578 public static final int GROUP_ALERT_ALL = Notification.GROUP_ALERT_ALL; 579 580 /** 581 * Constant for {@link Builder#setGroupAlertBehavior(int)}, meaning that all children 582 * notification in a group should be silenced (no sound or vibration) even if they would 583 * otherwise make sound or vibrate. Use this constant to mute this notification if this 584 * notification is a group child. This must be applied to all children notifications you want 585 * to mute. 586 * 587 * <p> For example, you might want to use this constant if you post a number of children 588 * notifications at once (say, after a periodic sync), and only need to notify the user 589 * audibly once. 590 */ 591 public static final int GROUP_ALERT_SUMMARY = Notification.GROUP_ALERT_SUMMARY; 592 593 /** 594 * Constant for {@link Builder#setGroupAlertBehavior(int)}, meaning that the summary 595 * notification in a group should be silenced (no sound or vibration) even if they would 596 * otherwise make sound or vibrate. Use this constant 597 * to mute this notification if this notification is a group summary. 598 * 599 * <p>For example, you might want to use this constant if only the children notifications 600 * in your group have content and the summary is only used to visually group notifications 601 * rather than to alert the user that new information is available. 602 */ 603 public static final int GROUP_ALERT_CHILDREN = Notification.GROUP_ALERT_CHILDREN; 604 605 /** 606 * Builder class for {@link NotificationCompat} objects. Allows easier control over 607 * all the flags, as well as help constructing the typical notification layouts. 608 * <p> 609 * On platform versions that don't offer expanded notifications, methods that depend on 610 * expanded notifications have no effect. 611 * </p> 612 * <p> 613 * For example, action buttons won't appear on platforms prior to Android 4.1. Action 614 * buttons depend on expanded notifications, which are only available in Android 4.1 615 * and later. 616 * <p> 617 * For this reason, you should always ensure that UI controls in a notification are also 618 * available in an {@link android.app.Activity} in your app, and you should always start that 619 * {@link android.app.Activity} when users click the notification. To do this, use the 620 * {@link NotificationCompat.Builder#setContentIntent setContentIntent()} 621 * method. 622 * </p> 623 * 624 */ 625 public static class Builder { 626 /** 627 * Maximum length of CharSequences accepted by Builder and friends. 628 * 629 * <p> 630 * Avoids spamming the system with overly large strings such as full e-mails. 631 */ 632 private static final int MAX_CHARSEQUENCE_LENGTH = 5 * 1024; 633 634 // All these variables are declared public/hidden so they can be accessed by a builder 635 // extender. 636 637 /** @hide */ 638 @RestrictTo(LIBRARY_GROUP) 639 public Context mContext; 640 641 /** @hide */ 642 @RestrictTo(LIBRARY_GROUP) 643 public ArrayList<Action> mActions = new ArrayList<>(); 644 645 CharSequence mContentTitle; 646 CharSequence mContentText; 647 PendingIntent mContentIntent; 648 PendingIntent mFullScreenIntent; 649 RemoteViews mTickerView; 650 Bitmap mLargeIcon; 651 CharSequence mContentInfo; 652 int mNumber; 653 int mPriority; 654 boolean mShowWhen = true; 655 boolean mUseChronometer; 656 Style mStyle; 657 CharSequence mSubText; 658 CharSequence[] mRemoteInputHistory; 659 int mProgressMax; 660 int mProgress; 661 boolean mProgressIndeterminate; 662 String mGroupKey; 663 boolean mGroupSummary; 664 String mSortKey; 665 boolean mLocalOnly = false; 666 boolean mColorized; 667 boolean mColorizedSet; 668 String mCategory; 669 Bundle mExtras; 670 int mColor = COLOR_DEFAULT; 671 @NotificationVisibility int mVisibility = VISIBILITY_PRIVATE; 672 Notification mPublicVersion; 673 RemoteViews mContentView; 674 RemoteViews mBigContentView; 675 RemoteViews mHeadsUpContentView; 676 String mChannelId; 677 int mBadgeIcon = BADGE_ICON_NONE; 678 String mShortcutId; 679 long mTimeout; 680 @GroupAlertBehavior int mGroupAlertBehavior = GROUP_ALERT_ALL; 681 Notification mNotification = new Notification(); 682 683 /** 684 * @deprecated This field was not meant to be public. 685 */ 686 @Deprecated 687 public ArrayList<String> mPeople; 688 689 /** 690 * Constructor. 691 * 692 * Automatically sets the when field to {@link System#currentTimeMillis() 693 * System.currentTimeMillis()} and the audio stream to the 694 * {@link Notification#STREAM_DEFAULT}. 695 * 696 * @param context A {@link Context} that will be used to construct the 697 * RemoteViews. The Context will not be held past the lifetime of this 698 * Builder object. 699 * @param channelId The constructed Notification will be posted on this 700 * NotificationChannel. 701 */ Builder(@onNull Context context, @NonNull String channelId)702 public Builder(@NonNull Context context, @NonNull String channelId) { 703 mContext = context; 704 mChannelId = channelId; 705 706 // Set defaults to match the defaults of a Notification 707 mNotification.when = System.currentTimeMillis(); 708 mNotification.audioStreamType = Notification.STREAM_DEFAULT; 709 mPriority = PRIORITY_DEFAULT; 710 mPeople = new ArrayList<String>(); 711 } 712 713 /** 714 * @deprecated use {@link #NotificationCompat.Builder(Context,String)} instead. 715 * All posted Notifications must specify a NotificationChannel Id. 716 */ 717 @Deprecated Builder(Context context)718 public Builder(Context context) { 719 this(context, null); 720 } 721 722 /** 723 * Set the time that the event occurred. Notifications in the panel are 724 * sorted by this time. 725 */ setWhen(long when)726 public Builder setWhen(long when) { 727 mNotification.when = when; 728 return this; 729 } 730 731 /** 732 * Control whether the timestamp set with {@link #setWhen(long) setWhen} is shown 733 * in the content view. 734 */ setShowWhen(boolean show)735 public Builder setShowWhen(boolean show) { 736 mShowWhen = show; 737 return this; 738 } 739 740 /** 741 * Show the {@link Notification#when} field as a stopwatch. 742 * 743 * Instead of presenting <code>when</code> as a timestamp, the notification will show an 744 * automatically updating display of the minutes and seconds since <code>when</code>. 745 * 746 * Useful when showing an elapsed time (like an ongoing phone call). 747 * 748 * @see android.widget.Chronometer 749 * @see Notification#when 750 */ setUsesChronometer(boolean b)751 public Builder setUsesChronometer(boolean b) { 752 mUseChronometer = b; 753 return this; 754 } 755 756 /** 757 * Set the small icon to use in the notification layouts. Different classes of devices 758 * may return different sizes. See the UX guidelines for more information on how to 759 * design these icons. 760 * 761 * @param icon A resource ID in the application's package of the drawable to use. 762 */ setSmallIcon(int icon)763 public Builder setSmallIcon(int icon) { 764 mNotification.icon = icon; 765 return this; 766 } 767 768 /** 769 * A variant of {@link #setSmallIcon(int) setSmallIcon(int)} that takes an additional 770 * level parameter for when the icon is a {@link android.graphics.drawable.LevelListDrawable 771 * LevelListDrawable}. 772 * 773 * @param icon A resource ID in the application's package of the drawable to use. 774 * @param level The level to use for the icon. 775 * 776 * @see android.graphics.drawable.LevelListDrawable 777 */ setSmallIcon(int icon, int level)778 public Builder setSmallIcon(int icon, int level) { 779 mNotification.icon = icon; 780 mNotification.iconLevel = level; 781 return this; 782 } 783 784 /** 785 * Set the title (first row) of the notification, in a standard notification. 786 */ setContentTitle(CharSequence title)787 public Builder setContentTitle(CharSequence title) { 788 mContentTitle = limitCharSequenceLength(title); 789 return this; 790 } 791 792 /** 793 * Set the text (second row) of the notification, in a standard notification. 794 */ setContentText(CharSequence text)795 public Builder setContentText(CharSequence text) { 796 mContentText = limitCharSequenceLength(text); 797 return this; 798 } 799 800 /** 801 * Set the third line of text in the platform notification template. 802 * Don't use if you're also using {@link #setProgress(int, int, boolean)}; 803 * they occupy the same location in the standard template. 804 * <br> 805 * If the platform does not provide large-format notifications, this method has no effect. 806 * The third line of text only appears in expanded view. 807 * <br> 808 */ setSubText(CharSequence text)809 public Builder setSubText(CharSequence text) { 810 mSubText = limitCharSequenceLength(text); 811 return this; 812 } 813 814 /** 815 * Set the remote input history. 816 * 817 * This should be set to the most recent inputs that have been sent 818 * through a {@link RemoteInput} of this Notification and cleared once the it is no 819 * longer relevant (e.g. for chat notifications once the other party has responded). 820 * 821 * The most recent input must be stored at the 0 index, the second most recent at the 822 * 1 index, etc. Note that the system will limit both how far back the inputs will be shown 823 * and how much of each individual input is shown. 824 * 825 * <p>Note: The reply text will only be shown on notifications that have least one action 826 * with a {@code RemoteInput}.</p> 827 */ setRemoteInputHistory(CharSequence[] text)828 public Builder setRemoteInputHistory(CharSequence[] text) { 829 mRemoteInputHistory = text; 830 return this; 831 } 832 833 /** 834 * Set the large number at the right-hand side of the notification. This is 835 * equivalent to setContentInfo, although it might show the number in a different 836 * font size for readability. 837 */ setNumber(int number)838 public Builder setNumber(int number) { 839 mNumber = number; 840 return this; 841 } 842 843 /** 844 * Set the large text at the right-hand side of the notification. 845 */ setContentInfo(CharSequence info)846 public Builder setContentInfo(CharSequence info) { 847 mContentInfo = limitCharSequenceLength(info); 848 return this; 849 } 850 851 /** 852 * Set the progress this notification represents, which may be 853 * represented as a {@link android.widget.ProgressBar}. 854 */ setProgress(int max, int progress, boolean indeterminate)855 public Builder setProgress(int max, int progress, boolean indeterminate) { 856 mProgressMax = max; 857 mProgress = progress; 858 mProgressIndeterminate = indeterminate; 859 return this; 860 } 861 862 /** 863 * Supply a custom RemoteViews to use instead of the standard one. 864 */ setContent(RemoteViews views)865 public Builder setContent(RemoteViews views) { 866 mNotification.contentView = views; 867 return this; 868 } 869 870 /** 871 * Supply a {@link PendingIntent} to send when the notification is clicked. 872 * If you do not supply an intent, you can now add PendingIntents to individual 873 * views to be launched when clicked by calling {@link RemoteViews#setOnClickPendingIntent 874 * RemoteViews.setOnClickPendingIntent(int,PendingIntent)}. Be sure to 875 * read {@link Notification#contentIntent Notification.contentIntent} for 876 * how to correctly use this. 877 */ setContentIntent(PendingIntent intent)878 public Builder setContentIntent(PendingIntent intent) { 879 mContentIntent = intent; 880 return this; 881 } 882 883 /** 884 * Supply a {@link PendingIntent} to send when the notification is cleared by the user 885 * directly from the notification panel. For example, this intent is sent when the user 886 * clicks the "Clear all" button, or the individual "X" buttons on notifications. This 887 * intent is not sent when the application calls 888 * {@link android.app.NotificationManager#cancel NotificationManager.cancel(int)}. 889 */ setDeleteIntent(PendingIntent intent)890 public Builder setDeleteIntent(PendingIntent intent) { 891 mNotification.deleteIntent = intent; 892 return this; 893 } 894 895 /** 896 * An intent to launch instead of posting the notification to the status bar. 897 * Only for use with extremely high-priority notifications demanding the user's 898 * <strong>immediate</strong> attention, such as an incoming phone call or 899 * alarm clock that the user has explicitly set to a particular time. 900 * If this facility is used for something else, please give the user an option 901 * to turn it off and use a normal notification, as this can be extremely 902 * disruptive. 903 * 904 * <p> 905 * On some platforms, the system UI may choose to display a heads-up notification, 906 * instead of launching this intent, while the user is using the device. 907 * </p> 908 * 909 * @param intent The pending intent to launch. 910 * @param highPriority Passing true will cause this notification to be sent 911 * even if other notifications are suppressed. 912 */ setFullScreenIntent(PendingIntent intent, boolean highPriority)913 public Builder setFullScreenIntent(PendingIntent intent, boolean highPriority) { 914 mFullScreenIntent = intent; 915 setFlag(FLAG_HIGH_PRIORITY, highPriority); 916 return this; 917 } 918 919 /** 920 * Sets the "ticker" text which is sent to accessibility services. Prior to 921 * {@link Build.VERSION_CODES#LOLLIPOP}, sets the text that is displayed in the status bar 922 * when the notification first arrives. 923 */ setTicker(CharSequence tickerText)924 public Builder setTicker(CharSequence tickerText) { 925 mNotification.tickerText = limitCharSequenceLength(tickerText); 926 return this; 927 } 928 929 /** 930 * Sets the "ticker" text which is sent to accessibility services. Prior to 931 * {@link Build.VERSION_CODES#LOLLIPOP}, sets the text that is displayed in the status bar 932 * when the notification first arrives, and also a RemoteViews object that may be displayed 933 * instead on some devices. 934 */ setTicker(CharSequence tickerText, RemoteViews views)935 public Builder setTicker(CharSequence tickerText, RemoteViews views) { 936 mNotification.tickerText = limitCharSequenceLength(tickerText); 937 mTickerView = views; 938 return this; 939 } 940 941 /** 942 * Set the large icon that is shown in the ticker and notification. 943 */ setLargeIcon(Bitmap icon)944 public Builder setLargeIcon(Bitmap icon) { 945 mLargeIcon = icon; 946 return this; 947 } 948 949 /** 950 * Set the sound to play. It will play on the default stream. 951 * 952 * <p> 953 * On some platforms, a notification that is noisy is more likely to be presented 954 * as a heads-up notification. 955 * </p> 956 */ setSound(Uri sound)957 public Builder setSound(Uri sound) { 958 mNotification.sound = sound; 959 mNotification.audioStreamType = Notification.STREAM_DEFAULT; 960 return this; 961 } 962 963 /** 964 * Set the sound to play. It will play on the stream you supply. 965 * 966 * <p> 967 * On some platforms, a notification that is noisy is more likely to be presented 968 * as a heads-up notification. 969 * </p> 970 * 971 * @see Notification#STREAM_DEFAULT 972 * @see AudioManager for the <code>STREAM_</code> constants. 973 */ setSound(Uri sound, int streamType)974 public Builder setSound(Uri sound, int streamType) { 975 mNotification.sound = sound; 976 mNotification.audioStreamType = streamType; 977 return this; 978 } 979 980 /** 981 * Set the vibration pattern to use. 982 * 983 * <p> 984 * On some platforms, a notification that vibrates is more likely to be presented 985 * as a heads-up notification. 986 * </p> 987 * 988 * @see android.os.Vibrator for a discussion of the <code>pattern</code> 989 * parameter. 990 */ setVibrate(long[] pattern)991 public Builder setVibrate(long[] pattern) { 992 mNotification.vibrate = pattern; 993 return this; 994 } 995 996 /** 997 * Set the argb value that you would like the LED on the device to blink, as well as the 998 * rate. The rate is specified in terms of the number of milliseconds to be on 999 * and then the number of milliseconds to be off. 1000 */ setLights(@olorInt int argb, int onMs, int offMs)1001 public Builder setLights(@ColorInt int argb, int onMs, int offMs) { 1002 mNotification.ledARGB = argb; 1003 mNotification.ledOnMS = onMs; 1004 mNotification.ledOffMS = offMs; 1005 boolean showLights = mNotification.ledOnMS != 0 && mNotification.ledOffMS != 0; 1006 mNotification.flags = (mNotification.flags & ~Notification.FLAG_SHOW_LIGHTS) | 1007 (showLights ? Notification.FLAG_SHOW_LIGHTS : 0); 1008 return this; 1009 } 1010 1011 /** 1012 * Set whether this is an ongoing notification. 1013 * 1014 * <p>Ongoing notifications differ from regular notifications in the following ways: 1015 * <ul> 1016 * <li>Ongoing notifications are sorted above the regular notifications in the 1017 * notification panel.</li> 1018 * <li>Ongoing notifications do not have an 'X' close button, and are not affected 1019 * by the "Clear all" button. 1020 * </ul> 1021 */ setOngoing(boolean ongoing)1022 public Builder setOngoing(boolean ongoing) { 1023 setFlag(Notification.FLAG_ONGOING_EVENT, ongoing); 1024 return this; 1025 } 1026 1027 /** 1028 * Set whether this notification should be colorized. When set, the color set with 1029 * {@link #setColor(int)} will be used as the background color of this notification. 1030 * <p> 1031 * This should only be used for high priority ongoing tasks like navigation, an ongoing 1032 * call, or other similarly high-priority events for the user. 1033 * <p> 1034 * For most styles, the coloring will only be applied if the notification is for a 1035 * foreground service notification. 1036 * <p> 1037 * However, for MediaStyle and DecoratedMediaCustomViewStyle notifications 1038 * that have a media session attached there is no such requirement. 1039 * <p> 1040 * Calling this method on any version prior to {@link android.os.Build.VERSION_CODES#O} will 1041 * not have an effect on the notification and it won't be colorized. 1042 * 1043 * @see #setColor(int) 1044 */ setColorized(boolean colorize)1045 public Builder setColorized(boolean colorize) { 1046 mColorized = colorize; 1047 mColorizedSet = true; 1048 return this; 1049 } 1050 1051 /** 1052 * Set this flag if you would only like the sound, vibrate 1053 * and ticker to be played if the notification is not already showing. 1054 */ setOnlyAlertOnce(boolean onlyAlertOnce)1055 public Builder setOnlyAlertOnce(boolean onlyAlertOnce) { 1056 setFlag(Notification.FLAG_ONLY_ALERT_ONCE, onlyAlertOnce); 1057 return this; 1058 } 1059 1060 /** 1061 * Setting this flag will make it so the notification is automatically 1062 * canceled when the user clicks it in the panel. The PendingIntent 1063 * set with {@link #setDeleteIntent} will be broadcast when the notification 1064 * is canceled. 1065 */ setAutoCancel(boolean autoCancel)1066 public Builder setAutoCancel(boolean autoCancel) { 1067 setFlag(Notification.FLAG_AUTO_CANCEL, autoCancel); 1068 return this; 1069 } 1070 1071 /** 1072 * Set whether or not this notification is only relevant to the current device. 1073 * 1074 * <p>Some notifications can be bridged to other devices for remote display. 1075 * This hint can be set to recommend this notification not be bridged. 1076 */ setLocalOnly(boolean b)1077 public Builder setLocalOnly(boolean b) { 1078 mLocalOnly = b; 1079 return this; 1080 } 1081 1082 /** 1083 * Set the notification category. 1084 * 1085 * <p>Must be one of the predefined notification categories (see the <code>CATEGORY_*</code> 1086 * constants in {@link Notification}) that best describes this notification. 1087 * May be used by the system for ranking and filtering. 1088 */ setCategory(String category)1089 public Builder setCategory(String category) { 1090 mCategory = category; 1091 return this; 1092 } 1093 1094 /** 1095 * Set the default notification options that will be used. 1096 * <p> 1097 * The value should be one or more of the following fields combined with 1098 * bitwise-or: 1099 * {@link Notification#DEFAULT_SOUND}, {@link Notification#DEFAULT_VIBRATE}, 1100 * {@link Notification#DEFAULT_LIGHTS}. 1101 * <p> 1102 * For all default values, use {@link Notification#DEFAULT_ALL}. 1103 */ setDefaults(int defaults)1104 public Builder setDefaults(int defaults) { 1105 mNotification.defaults = defaults; 1106 if ((defaults & Notification.DEFAULT_LIGHTS) != 0) { 1107 mNotification.flags |= Notification.FLAG_SHOW_LIGHTS; 1108 } 1109 return this; 1110 } 1111 setFlag(int mask, boolean value)1112 private void setFlag(int mask, boolean value) { 1113 if (value) { 1114 mNotification.flags |= mask; 1115 } else { 1116 mNotification.flags &= ~mask; 1117 } 1118 } 1119 1120 /** 1121 * Set the relative priority for this notification. 1122 * 1123 * Priority is an indication of how much of the user's 1124 * valuable attention should be consumed by this 1125 * notification. Low-priority notifications may be hidden from 1126 * the user in certain situations, while the user might be 1127 * interrupted for a higher-priority notification. 1128 * The system sets a notification's priority based on various factors including the 1129 * setPriority value. The effect may differ slightly on different platforms. 1130 * 1131 * @param pri Relative priority for this notification. Must be one of 1132 * the priority constants defined by {@link NotificationCompat}. 1133 * Acceptable values range from {@link 1134 * NotificationCompat#PRIORITY_MIN} (-2) to {@link 1135 * NotificationCompat#PRIORITY_MAX} (2). 1136 */ setPriority(int pri)1137 public Builder setPriority(int pri) { 1138 mPriority = pri; 1139 return this; 1140 } 1141 1142 /** 1143 * Add a person that is relevant to this notification. 1144 * 1145 * <P> 1146 * Depending on user preferences, this annotation may allow the notification to pass 1147 * through interruption filters, and to appear more prominently in the user interface. 1148 * </P> 1149 * 1150 * <P> 1151 * The person should be specified by the {@code String} representation of a 1152 * {@link android.provider.ContactsContract.Contacts#CONTENT_LOOKUP_URI}. 1153 * </P> 1154 * 1155 * <P>The system will also attempt to resolve {@code mailto:} and {@code tel:} schema 1156 * URIs. The path part of these URIs must exist in the contacts database, in the 1157 * appropriate column, or the reference will be discarded as invalid. Telephone schema 1158 * URIs will be resolved by {@link android.provider.ContactsContract.PhoneLookup}. 1159 * </P> 1160 * 1161 * @param uri A URI for the person. 1162 * @see Notification#EXTRA_PEOPLE 1163 */ addPerson(String uri)1164 public Builder addPerson(String uri) { 1165 mPeople.add(uri); 1166 return this; 1167 } 1168 1169 /** 1170 * Set this notification to be part of a group of notifications sharing the same key. 1171 * Grouped notifications may display in a cluster or stack on devices which 1172 * support such rendering. 1173 * 1174 * <p>To make this notification the summary for its group, also call 1175 * {@link #setGroupSummary}. A sort order can be specified for group members by using 1176 * {@link #setSortKey}. 1177 * @param groupKey The group key of the group. 1178 * @return this object for method chaining 1179 */ setGroup(String groupKey)1180 public Builder setGroup(String groupKey) { 1181 mGroupKey = groupKey; 1182 return this; 1183 } 1184 1185 /** 1186 * Set this notification to be the group summary for a group of notifications. 1187 * Grouped notifications may display in a cluster or stack on devices which 1188 * support such rendering. Requires a group key also be set using {@link #setGroup}. 1189 * @param isGroupSummary Whether this notification should be a group summary. 1190 * @return this object for method chaining 1191 */ setGroupSummary(boolean isGroupSummary)1192 public Builder setGroupSummary(boolean isGroupSummary) { 1193 mGroupSummary = isGroupSummary; 1194 return this; 1195 } 1196 1197 /** 1198 * Set a sort key that orders this notification among other notifications from the 1199 * same package. This can be useful if an external sort was already applied and an app 1200 * would like to preserve this. Notifications will be sorted lexicographically using this 1201 * value, although providing different priorities in addition to providing sort key may 1202 * cause this value to be ignored. 1203 * 1204 * <p>This sort key can also be used to order members of a notification group. See 1205 * {@link Builder#setGroup}. 1206 * 1207 * @see String#compareTo(String) 1208 */ setSortKey(String sortKey)1209 public Builder setSortKey(String sortKey) { 1210 mSortKey = sortKey; 1211 return this; 1212 } 1213 1214 /** 1215 * Merge additional metadata into this notification. 1216 * 1217 * <p>Values within the Bundle will replace existing extras values in this Builder. 1218 * 1219 * @see Notification#extras 1220 */ addExtras(Bundle extras)1221 public Builder addExtras(Bundle extras) { 1222 if (extras != null) { 1223 if (mExtras == null) { 1224 mExtras = new Bundle(extras); 1225 } else { 1226 mExtras.putAll(extras); 1227 } 1228 } 1229 return this; 1230 } 1231 1232 /** 1233 * Set metadata for this notification. 1234 * 1235 * <p>A reference to the Bundle is held for the lifetime of this Builder, and the Bundle's 1236 * current contents are copied into the Notification each time {@link #build()} is 1237 * called. 1238 * 1239 * <p>Replaces any existing extras values with those from the provided Bundle. 1240 * Use {@link #addExtras} to merge in metadata instead. 1241 * 1242 * @see Notification#extras 1243 */ setExtras(Bundle extras)1244 public Builder setExtras(Bundle extras) { 1245 mExtras = extras; 1246 return this; 1247 } 1248 1249 /** 1250 * Get the current metadata Bundle used by this notification Builder. 1251 * 1252 * <p>The returned Bundle is shared with this Builder. 1253 * 1254 * <p>The current contents of this Bundle are copied into the Notification each time 1255 * {@link #build()} is called. 1256 * 1257 * @see Notification#extras 1258 */ getExtras()1259 public Bundle getExtras() { 1260 if (mExtras == null) { 1261 mExtras = new Bundle(); 1262 } 1263 return mExtras; 1264 } 1265 1266 /** 1267 * Add an action to this notification. Actions are typically displayed by 1268 * the system as a button adjacent to the notification content. 1269 * <br> 1270 * Action buttons won't appear on platforms prior to Android 4.1. Action 1271 * buttons depend on expanded notifications, which are only available in Android 4.1 1272 * and later. To ensure that an action button's functionality is always available, first 1273 * implement the functionality in the {@link android.app.Activity} that starts when a user 1274 * clicks the notification (see {@link #setContentIntent setContentIntent()}), and then 1275 * enhance the notification by implementing the same functionality with 1276 * {@link #addAction addAction()}. 1277 * 1278 * @param icon Resource ID of a drawable that represents the action. 1279 * @param title Text describing the action. 1280 * @param intent {@link android.app.PendingIntent} to be fired when the action is invoked. 1281 */ addAction(int icon, CharSequence title, PendingIntent intent)1282 public Builder addAction(int icon, CharSequence title, PendingIntent intent) { 1283 mActions.add(new Action(icon, title, intent)); 1284 return this; 1285 } 1286 1287 /** 1288 * Add an action to this notification. Actions are typically displayed by 1289 * the system as a button adjacent to the notification content. 1290 * <br> 1291 * Action buttons won't appear on platforms prior to Android 4.1. Action 1292 * buttons depend on expanded notifications, which are only available in Android 4.1 1293 * and later. To ensure that an action button's functionality is always available, first 1294 * implement the functionality in the {@link android.app.Activity} that starts when a user 1295 * clicks the notification (see {@link #setContentIntent setContentIntent()}), and then 1296 * enhance the notification by implementing the same functionality with 1297 * {@link #addAction addAction()}. 1298 * 1299 * @param action The action to add. 1300 */ addAction(Action action)1301 public Builder addAction(Action action) { 1302 mActions.add(action); 1303 return this; 1304 } 1305 1306 /** 1307 * Add a rich notification style to be applied at build time. 1308 * <br> 1309 * If the platform does not provide rich notification styles, this method has no effect. The 1310 * user will always see the normal notification style. 1311 * 1312 * @param style Object responsible for modifying the notification style. 1313 */ setStyle(Style style)1314 public Builder setStyle(Style style) { 1315 if (mStyle != style) { 1316 mStyle = style; 1317 if (mStyle != null) { 1318 mStyle.setBuilder(this); 1319 } 1320 } 1321 return this; 1322 } 1323 1324 /** 1325 * Sets {@link Notification#color}. 1326 * 1327 * @param argb The accent color to use 1328 * 1329 * @return The same Builder. 1330 */ setColor(@olorInt int argb)1331 public Builder setColor(@ColorInt int argb) { 1332 mColor = argb; 1333 return this; 1334 } 1335 1336 /** 1337 * Sets {@link Notification#visibility}. 1338 * 1339 * @param visibility One of {@link Notification#VISIBILITY_PRIVATE} (the default), 1340 * {@link Notification#VISIBILITY_PUBLIC}, or 1341 * {@link Notification#VISIBILITY_SECRET}. 1342 */ setVisibility(@otificationVisibility int visibility)1343 public Builder setVisibility(@NotificationVisibility int visibility) { 1344 mVisibility = visibility; 1345 return this; 1346 } 1347 1348 /** 1349 * Supply a replacement Notification whose contents should be shown in insecure contexts 1350 * (i.e. atop the secure lockscreen). See {@link Notification#visibility} and 1351 * {@link #VISIBILITY_PUBLIC}. 1352 * 1353 * @param n A replacement notification, presumably with some or all info redacted. 1354 * @return The same Builder. 1355 */ setPublicVersion(Notification n)1356 public Builder setPublicVersion(Notification n) { 1357 mPublicVersion = n; 1358 return this; 1359 } 1360 1361 /** 1362 * Supply custom RemoteViews to use instead of the platform template. 1363 * 1364 * This will override the layout that would otherwise be constructed by this Builder 1365 * object. 1366 */ setCustomContentView(RemoteViews contentView)1367 public Builder setCustomContentView(RemoteViews contentView) { 1368 mContentView = contentView; 1369 return this; 1370 } 1371 1372 /** 1373 * Supply custom RemoteViews to use instead of the platform template in the expanded form. 1374 * 1375 * This will override the expanded layout that would otherwise be constructed by this 1376 * Builder object. 1377 * 1378 * No-op on versions prior to {@link android.os.Build.VERSION_CODES#JELLY_BEAN}. 1379 */ setCustomBigContentView(RemoteViews contentView)1380 public Builder setCustomBigContentView(RemoteViews contentView) { 1381 mBigContentView = contentView; 1382 return this; 1383 } 1384 1385 /** 1386 * Supply custom RemoteViews to use instead of the platform template in the heads up dialog. 1387 * 1388 * This will override the heads-up layout that would otherwise be constructed by this 1389 * Builder object. 1390 * 1391 * No-op on versions prior to {@link android.os.Build.VERSION_CODES#LOLLIPOP}. 1392 */ setCustomHeadsUpContentView(RemoteViews contentView)1393 public Builder setCustomHeadsUpContentView(RemoteViews contentView) { 1394 mHeadsUpContentView = contentView; 1395 return this; 1396 } 1397 1398 /** 1399 * Specifies the channel the notification should be delivered on. 1400 * 1401 * No-op on versions prior to {@link android.os.Build.VERSION_CODES#O} . 1402 */ setChannelId(@onNull String channelId)1403 public Builder setChannelId(@NonNull String channelId) { 1404 mChannelId = channelId; 1405 return this; 1406 } 1407 1408 /** 1409 * Specifies the time at which this notification should be canceled, if it is not already 1410 * canceled. 1411 */ setTimeoutAfter(long durationMs)1412 public Builder setTimeoutAfter(long durationMs) { 1413 mTimeout = durationMs; 1414 return this; 1415 } 1416 1417 /** 1418 * If this notification is duplicative of a Launcher shortcut, sets the 1419 * {@link android.support.v4.content.pm.ShortcutInfoCompat#getId() id} of the shortcut, in 1420 * case the Launcher wants to hide the shortcut. 1421 * 1422 * <p><strong>Note:</strong>This field will be ignored by Launchers that don't support 1423 * badging or {@link android.support.v4.content.pm.ShortcutManagerCompat shortcuts}. 1424 * 1425 * @param shortcutId the {@link android.support.v4.content.pm.ShortcutInfoCompat#getId() id} 1426 * of the shortcut this notification supersedes 1427 */ setShortcutId(String shortcutId)1428 public Builder setShortcutId(String shortcutId) { 1429 mShortcutId = shortcutId; 1430 return this; 1431 } 1432 1433 /** 1434 * Sets which icon to display as a badge for this notification. 1435 * 1436 * <p>Must be one of {@link #BADGE_ICON_NONE}, {@link #BADGE_ICON_SMALL}, 1437 * {@link #BADGE_ICON_LARGE}. 1438 * 1439 * <p><strong>Note:</strong> This value might be ignored, for launchers that don't support 1440 * badge icons. 1441 */ setBadgeIconType(@adgeIconType int icon)1442 public Builder setBadgeIconType(@BadgeIconType int icon) { 1443 mBadgeIcon = icon; 1444 return this; 1445 } 1446 1447 /** 1448 * Sets the group alert behavior for this notification. Use this method to mute this 1449 * notification if alerts for this notification's group should be handled by a different 1450 * notification. This is only applicable for notifications that belong to a 1451 * {@link #setGroup(String) group}. This must be called on all notifications you want to 1452 * mute. For example, if you want only the summary of your group to make noise, all 1453 * children in the group should have the group alert behavior {@link #GROUP_ALERT_SUMMARY}. 1454 * 1455 * <p> The default value is {@link #GROUP_ALERT_ALL}.</p> 1456 */ setGroupAlertBehavior(@roupAlertBehavior int groupAlertBehavior)1457 public Builder setGroupAlertBehavior(@GroupAlertBehavior int groupAlertBehavior) { 1458 mGroupAlertBehavior = groupAlertBehavior; 1459 return this; 1460 } 1461 1462 /** 1463 * Apply an extender to this notification builder. Extenders may be used to add 1464 * metadata or change options on this builder. 1465 */ extend(Extender extender)1466 public Builder extend(Extender extender) { 1467 extender.extend(this); 1468 return this; 1469 } 1470 1471 /** 1472 * @deprecated Use {@link #build()} instead. 1473 */ 1474 @Deprecated getNotification()1475 public Notification getNotification() { 1476 return build(); 1477 } 1478 1479 /** 1480 * Combine all of the options that have been set and return a new {@link Notification} 1481 * object. 1482 */ build()1483 public Notification build() { 1484 return new NotificationCompatBuilder(this).build(); 1485 } 1486 limitCharSequenceLength(CharSequence cs)1487 protected static CharSequence limitCharSequenceLength(CharSequence cs) { 1488 if (cs == null) return cs; 1489 if (cs.length() > MAX_CHARSEQUENCE_LENGTH) { 1490 cs = cs.subSequence(0, MAX_CHARSEQUENCE_LENGTH); 1491 } 1492 return cs; 1493 } 1494 1495 /** 1496 * @hide 1497 */ 1498 @RestrictTo(LIBRARY_GROUP) getContentView()1499 public RemoteViews getContentView() { 1500 return mContentView; 1501 } 1502 1503 /** 1504 * @hide 1505 */ 1506 @RestrictTo(LIBRARY_GROUP) getBigContentView()1507 public RemoteViews getBigContentView() { 1508 return mBigContentView; 1509 } 1510 1511 /** 1512 * @hide 1513 */ 1514 @RestrictTo(LIBRARY_GROUP) getHeadsUpContentView()1515 public RemoteViews getHeadsUpContentView() { 1516 return mHeadsUpContentView; 1517 } 1518 1519 /** 1520 * return when if it is showing or 0 otherwise 1521 * 1522 * @hide 1523 */ 1524 @RestrictTo(LIBRARY_GROUP) getWhenIfShowing()1525 public long getWhenIfShowing() { 1526 return mShowWhen ? mNotification.when : 0; 1527 } 1528 1529 /** 1530 * @return the priority set on the notification 1531 * 1532 * @hide 1533 */ 1534 @RestrictTo(LIBRARY_GROUP) getPriority()1535 public int getPriority() { 1536 return mPriority; 1537 } 1538 1539 /** 1540 * @return the color of the notification 1541 * 1542 * @hide 1543 */ 1544 @RestrictTo(LIBRARY_GROUP) getColor()1545 public int getColor() { 1546 return mColor; 1547 } 1548 } 1549 1550 /** 1551 * An object that can apply a rich notification style to a {@link Notification.Builder} 1552 * object. 1553 * <br> 1554 * If the platform does not provide rich notification styles, methods in this class have no 1555 * effect. 1556 */ 1557 public static abstract class Style { 1558 /** 1559 * @hide 1560 */ 1561 @RestrictTo(LIBRARY_GROUP) 1562 protected Builder mBuilder; 1563 CharSequence mBigContentTitle; 1564 CharSequence mSummaryText; 1565 boolean mSummaryTextSet = false; 1566 setBuilder(Builder builder)1567 public void setBuilder(Builder builder) { 1568 if (mBuilder != builder) { 1569 mBuilder = builder; 1570 if (mBuilder != null) { 1571 mBuilder.setStyle(this); 1572 } 1573 } 1574 } 1575 build()1576 public Notification build() { 1577 Notification notification = null; 1578 if (mBuilder != null) { 1579 notification = mBuilder.build(); 1580 } 1581 return notification; 1582 } 1583 1584 /** 1585 * @hide 1586 */ 1587 @RestrictTo(LIBRARY_GROUP) 1588 // TODO: implement for all styles apply(NotificationBuilderWithBuilderAccessor builder)1589 public void apply(NotificationBuilderWithBuilderAccessor builder) { 1590 } 1591 1592 /** 1593 * @hide 1594 */ 1595 @RestrictTo(LIBRARY_GROUP) makeContentView(NotificationBuilderWithBuilderAccessor builder)1596 public RemoteViews makeContentView(NotificationBuilderWithBuilderAccessor builder) { 1597 return null; 1598 } 1599 1600 /** 1601 * @hide 1602 */ 1603 @RestrictTo(LIBRARY_GROUP) makeBigContentView(NotificationBuilderWithBuilderAccessor builder)1604 public RemoteViews makeBigContentView(NotificationBuilderWithBuilderAccessor builder) { 1605 return null; 1606 } 1607 1608 /** 1609 * @hide 1610 */ 1611 @RestrictTo(LIBRARY_GROUP) makeHeadsUpContentView(NotificationBuilderWithBuilderAccessor builder)1612 public RemoteViews makeHeadsUpContentView(NotificationBuilderWithBuilderAccessor builder) { 1613 return null; 1614 } 1615 1616 /** 1617 * @hide 1618 */ 1619 @RestrictTo(LIBRARY_GROUP) 1620 // TODO: implement for all styles addCompatExtras(Bundle extras)1621 public void addCompatExtras(Bundle extras) { 1622 } 1623 1624 /** 1625 * @hide 1626 */ 1627 @RestrictTo(LIBRARY_GROUP) 1628 // TODO: implement for all styles restoreFromCompatExtras(Bundle extras)1629 protected void restoreFromCompatExtras(Bundle extras) { 1630 } 1631 1632 /** 1633 * @hide 1634 */ 1635 @RestrictTo(LIBRARY_GROUP) applyStandardTemplate(boolean showSmallIcon, int resId, boolean fitIn1U)1636 public RemoteViews applyStandardTemplate(boolean showSmallIcon, 1637 int resId, boolean fitIn1U) { 1638 Resources res = mBuilder.mContext.getResources(); 1639 RemoteViews contentView = new RemoteViews(mBuilder.mContext.getPackageName(), resId); 1640 boolean showLine3 = false; 1641 boolean showLine2 = false; 1642 1643 boolean minPriority = mBuilder.getPriority() < NotificationCompat.PRIORITY_LOW; 1644 if (Build.VERSION.SDK_INT >= 16 && Build.VERSION.SDK_INT < 21) { 1645 // lets color the backgrounds 1646 if (minPriority) { 1647 contentView.setInt(R.id.notification_background, 1648 "setBackgroundResource", R.drawable.notification_bg_low); 1649 contentView.setInt(R.id.icon, 1650 "setBackgroundResource", R.drawable.notification_template_icon_low_bg); 1651 } else { 1652 contentView.setInt(R.id.notification_background, 1653 "setBackgroundResource", R.drawable.notification_bg); 1654 contentView.setInt(R.id.icon, 1655 "setBackgroundResource", R.drawable.notification_template_icon_bg); 1656 } 1657 } 1658 1659 if (mBuilder.mLargeIcon != null) { 1660 // On versions before Jellybean, the large icon was shown by SystemUI, so we need 1661 // to hide it here. 1662 if (Build.VERSION.SDK_INT >= 16) { 1663 contentView.setViewVisibility(R.id.icon, View.VISIBLE); 1664 contentView.setImageViewBitmap(R.id.icon, mBuilder.mLargeIcon); 1665 } else { 1666 contentView.setViewVisibility(R.id.icon, View.GONE); 1667 } 1668 if (showSmallIcon && mBuilder.mNotification.icon != 0) { 1669 int backgroundSize = res.getDimensionPixelSize( 1670 R.dimen.notification_right_icon_size); 1671 int iconSize = backgroundSize - res.getDimensionPixelSize( 1672 R.dimen.notification_small_icon_background_padding) * 2; 1673 if (Build.VERSION.SDK_INT >= 21) { 1674 Bitmap smallBit = createIconWithBackground( 1675 mBuilder.mNotification.icon, 1676 backgroundSize, 1677 iconSize, 1678 mBuilder.getColor()); 1679 contentView.setImageViewBitmap(R.id.right_icon, smallBit); 1680 } else { 1681 contentView.setImageViewBitmap(R.id.right_icon, createColoredBitmap( 1682 mBuilder.mNotification.icon, Color.WHITE)); 1683 } 1684 contentView.setViewVisibility(R.id.right_icon, View.VISIBLE); 1685 } 1686 } else if (showSmallIcon && mBuilder.mNotification.icon != 0) { // small icon at left 1687 contentView.setViewVisibility(R.id.icon, View.VISIBLE); 1688 if (Build.VERSION.SDK_INT >= 21) { 1689 int backgroundSize = res.getDimensionPixelSize( 1690 R.dimen.notification_large_icon_width) 1691 - res.getDimensionPixelSize(R.dimen.notification_big_circle_margin); 1692 int iconSize = res.getDimensionPixelSize( 1693 R.dimen.notification_small_icon_size_as_large); 1694 Bitmap smallBit = createIconWithBackground( 1695 mBuilder.mNotification.icon, 1696 backgroundSize, 1697 iconSize, 1698 mBuilder.getColor()); 1699 contentView.setImageViewBitmap(R.id.icon, smallBit); 1700 } else { 1701 contentView.setImageViewBitmap(R.id.icon, createColoredBitmap( 1702 mBuilder.mNotification.icon, Color.WHITE)); 1703 } 1704 } 1705 if (mBuilder.mContentTitle != null) { 1706 contentView.setTextViewText(R.id.title, mBuilder.mContentTitle); 1707 } 1708 if (mBuilder.mContentText != null) { 1709 contentView.setTextViewText(R.id.text, mBuilder.mContentText); 1710 showLine3 = true; 1711 } 1712 // If there is a large icon we have a right side 1713 boolean hasRightSide = !(Build.VERSION.SDK_INT >= 21) && mBuilder.mLargeIcon != null; 1714 if (mBuilder.mContentInfo != null) { 1715 contentView.setTextViewText(R.id.info, mBuilder.mContentInfo); 1716 contentView.setViewVisibility(R.id.info, View.VISIBLE); 1717 showLine3 = true; 1718 hasRightSide = true; 1719 } else if (mBuilder.mNumber > 0) { 1720 final int tooBig = res.getInteger( 1721 R.integer.status_bar_notification_info_maxnum); 1722 if (mBuilder.mNumber > tooBig) { 1723 contentView.setTextViewText(R.id.info, ((Resources) res).getString( 1724 R.string.status_bar_notification_info_overflow)); 1725 } else { 1726 NumberFormat f = NumberFormat.getIntegerInstance(); 1727 contentView.setTextViewText(R.id.info, f.format(mBuilder.mNumber)); 1728 } 1729 contentView.setViewVisibility(R.id.info, View.VISIBLE); 1730 showLine3 = true; 1731 hasRightSide = true; 1732 } else { 1733 contentView.setViewVisibility(R.id.info, View.GONE); 1734 } 1735 1736 // Need to show three lines? Only allow on Jellybean+ 1737 if (mBuilder.mSubText != null && Build.VERSION.SDK_INT >= 16) { 1738 contentView.setTextViewText(R.id.text, mBuilder.mSubText); 1739 if (mBuilder.mContentText != null) { 1740 contentView.setTextViewText(R.id.text2, mBuilder.mContentText); 1741 contentView.setViewVisibility(R.id.text2, View.VISIBLE); 1742 showLine2 = true; 1743 } else { 1744 contentView.setViewVisibility(R.id.text2, View.GONE); 1745 } 1746 } 1747 1748 // RemoteViews.setViewPadding and RemoteViews.setTextViewTextSize is not available on 1749 // ICS- 1750 if (showLine2 && Build.VERSION.SDK_INT >= 16) { 1751 if (fitIn1U) { 1752 // need to shrink all the type to make sure everything fits 1753 final float subTextSize = res.getDimensionPixelSize( 1754 R.dimen.notification_subtext_size); 1755 contentView.setTextViewTextSize(R.id.text, TypedValue.COMPLEX_UNIT_PX, 1756 subTextSize); 1757 } 1758 // vertical centering 1759 contentView.setViewPadding(R.id.line1, 0, 0, 0, 0); 1760 } 1761 1762 if (mBuilder.getWhenIfShowing() != 0) { 1763 if (mBuilder.mUseChronometer && Build.VERSION.SDK_INT >= 16) { 1764 contentView.setViewVisibility(R.id.chronometer, View.VISIBLE); 1765 contentView.setLong(R.id.chronometer, "setBase", 1766 mBuilder.getWhenIfShowing() 1767 + (SystemClock.elapsedRealtime() - System.currentTimeMillis())); 1768 contentView.setBoolean(R.id.chronometer, "setStarted", true); 1769 } else { 1770 contentView.setViewVisibility(R.id.time, View.VISIBLE); 1771 contentView.setLong(R.id.time, "setTime", mBuilder.getWhenIfShowing()); 1772 } 1773 hasRightSide = true; 1774 } 1775 contentView.setViewVisibility(R.id.right_side, hasRightSide ? View.VISIBLE : View.GONE); 1776 contentView.setViewVisibility(R.id.line3, showLine3 ? View.VISIBLE : View.GONE); 1777 return contentView; 1778 } 1779 1780 /** 1781 * @hide 1782 */ 1783 @RestrictTo(LIBRARY_GROUP) createColoredBitmap(int iconId, int color)1784 public Bitmap createColoredBitmap(int iconId, int color) { 1785 return createColoredBitmap(iconId, color, 0); 1786 } 1787 createColoredBitmap(int iconId, int color, int size)1788 private Bitmap createColoredBitmap(int iconId, int color, int size) { 1789 Drawable drawable = mBuilder.mContext.getResources().getDrawable(iconId); 1790 int width = size == 0 ? drawable.getIntrinsicWidth() : size; 1791 int height = size == 0 ? drawable.getIntrinsicHeight() : size; 1792 Bitmap resultBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 1793 drawable.setBounds(0, 0, width, height); 1794 if (color != 0) { 1795 drawable.mutate().setColorFilter( 1796 new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN)); 1797 } 1798 Canvas canvas = new Canvas(resultBitmap); 1799 drawable.draw(canvas); 1800 return resultBitmap; 1801 } 1802 createIconWithBackground(int iconId, int size, int iconSize, int color)1803 private Bitmap createIconWithBackground(int iconId, int size, 1804 int iconSize, int color) { 1805 Bitmap coloredBitmap = createColoredBitmap(R.drawable.notification_icon_background, 1806 color == NotificationCompat.COLOR_DEFAULT ? 0 : color, size); 1807 Canvas canvas = new Canvas(coloredBitmap); 1808 Drawable icon = mBuilder.mContext.getResources().getDrawable(iconId).mutate(); 1809 icon.setFilterBitmap(true); 1810 int inset = (size - iconSize) / 2; 1811 icon.setBounds(inset, inset, iconSize + inset, iconSize + inset); 1812 icon.setColorFilter(new PorterDuffColorFilter(Color.WHITE, PorterDuff.Mode.SRC_ATOP)); 1813 icon.draw(canvas); 1814 return coloredBitmap; 1815 } 1816 1817 /** 1818 * @hide 1819 */ 1820 @RestrictTo(LIBRARY_GROUP) buildIntoRemoteViews(RemoteViews outerView, RemoteViews innerView)1821 public void buildIntoRemoteViews(RemoteViews outerView, 1822 RemoteViews innerView) { 1823 // this needs to be done fore the other calls, since otherwise we might hide the wrong 1824 // things if our ids collide. 1825 hideNormalContent(outerView); 1826 outerView.removeAllViews(R.id.notification_main_column); 1827 outerView.addView(R.id.notification_main_column, innerView.clone()); 1828 outerView.setViewVisibility(R.id.notification_main_column, View.VISIBLE); 1829 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 1830 // Adjust padding depending on font size. 1831 outerView.setViewPadding(R.id.notification_main_column_container, 1832 0, calculateTopPadding(), 0, 0); 1833 } 1834 } 1835 hideNormalContent(RemoteViews outerView)1836 private void hideNormalContent(RemoteViews outerView) { 1837 outerView.setViewVisibility(R.id.title, View.GONE); 1838 outerView.setViewVisibility(R.id.text2, View.GONE); 1839 outerView.setViewVisibility(R.id.text, View.GONE); 1840 } 1841 calculateTopPadding()1842 private int calculateTopPadding() { 1843 Resources resources = mBuilder.mContext.getResources(); 1844 int padding = resources.getDimensionPixelSize(R.dimen.notification_top_pad); 1845 int largePadding = resources.getDimensionPixelSize( 1846 R.dimen.notification_top_pad_large_text); 1847 float fontScale = resources.getConfiguration().fontScale; 1848 float largeFactor = (constrain(fontScale, 1.0f, 1.3f) - 1f) / (1.3f - 1f); 1849 1850 // Linearly interpolate the padding between large and normal with the font scale ranging 1851 // from 1f to LARGE_TEXT_SCALE 1852 return Math.round((1 - largeFactor) * padding + largeFactor * largePadding); 1853 } 1854 constrain(float amount, float low, float high)1855 private static float constrain(float amount, float low, float high) { 1856 return amount < low ? low : (amount > high ? high : amount); 1857 } 1858 } 1859 1860 /** 1861 * Helper class for generating large-format notifications that include a large image attachment. 1862 * <br> 1863 * If the platform does not provide large-format notifications, this method has no effect. The 1864 * user will always see the normal notification view. 1865 * <br> 1866 * This class is a "rebuilder": It attaches to a Builder object and modifies its behavior, like so: 1867 * <pre class="prettyprint"> 1868 * Notification notification = new Notification.Builder(mContext) 1869 * .setContentTitle("New photo from " + sender.toString()) 1870 * .setContentText(subject) 1871 * .setSmallIcon(R.drawable.new_post) 1872 * .setLargeIcon(aBitmap) 1873 * .setStyle(new Notification.BigPictureStyle() 1874 * .bigPicture(aBigBitmap)) 1875 * .build(); 1876 * </pre> 1877 * 1878 * @see Notification#bigContentView 1879 */ 1880 public static class BigPictureStyle extends Style { 1881 private Bitmap mPicture; 1882 private Bitmap mBigLargeIcon; 1883 private boolean mBigLargeIconSet; 1884 BigPictureStyle()1885 public BigPictureStyle() { 1886 } 1887 BigPictureStyle(Builder builder)1888 public BigPictureStyle(Builder builder) { 1889 setBuilder(builder); 1890 } 1891 1892 /** 1893 * Overrides ContentTitle in the big form of the template. 1894 * This defaults to the value passed to setContentTitle(). 1895 */ setBigContentTitle(CharSequence title)1896 public BigPictureStyle setBigContentTitle(CharSequence title) { 1897 mBigContentTitle = Builder.limitCharSequenceLength(title); 1898 return this; 1899 } 1900 1901 /** 1902 * Set the first line of text after the detail section in the big form of the template. 1903 */ setSummaryText(CharSequence cs)1904 public BigPictureStyle setSummaryText(CharSequence cs) { 1905 mSummaryText = Builder.limitCharSequenceLength(cs); 1906 mSummaryTextSet = true; 1907 return this; 1908 } 1909 1910 /** 1911 * Provide the bitmap to be used as the payload for the BigPicture notification. 1912 */ bigPicture(Bitmap b)1913 public BigPictureStyle bigPicture(Bitmap b) { 1914 mPicture = b; 1915 return this; 1916 } 1917 1918 /** 1919 * Override the large icon when the big notification is shown. 1920 */ bigLargeIcon(Bitmap b)1921 public BigPictureStyle bigLargeIcon(Bitmap b) { 1922 mBigLargeIcon = b; 1923 mBigLargeIconSet = true; 1924 return this; 1925 } 1926 1927 /** 1928 * @hide 1929 */ 1930 @RestrictTo(LIBRARY_GROUP) 1931 @Override apply(NotificationBuilderWithBuilderAccessor builder)1932 public void apply(NotificationBuilderWithBuilderAccessor builder) { 1933 if (Build.VERSION.SDK_INT >= 16) { 1934 Notification.BigPictureStyle style = 1935 new Notification.BigPictureStyle(builder.getBuilder()) 1936 .setBigContentTitle(mBigContentTitle) 1937 .bigPicture(mPicture); 1938 if (mBigLargeIconSet) { 1939 style.bigLargeIcon(mBigLargeIcon); 1940 } 1941 if (mSummaryTextSet) { 1942 style.setSummaryText(mSummaryText); 1943 } 1944 } 1945 } 1946 } 1947 1948 /** 1949 * Helper class for generating large-format notifications that include a lot of text. 1950 * 1951 * <br> 1952 * If the platform does not provide large-format notifications, this method has no effect. The 1953 * user will always see the normal notification view. 1954 * <br> 1955 * This class is a "rebuilder": It attaches to a Builder object and modifies its behavior, like so: 1956 * <pre class="prettyprint"> 1957 * Notification notification = new Notification.Builder(mContext) 1958 * .setContentTitle("New mail from " + sender.toString()) 1959 * .setContentText(subject) 1960 * .setSmallIcon(R.drawable.new_mail) 1961 * .setLargeIcon(aBitmap) 1962 * .setStyle(new Notification.BigTextStyle() 1963 * .bigText(aVeryLongString)) 1964 * .build(); 1965 * </pre> 1966 * 1967 * @see Notification#bigContentView 1968 */ 1969 public static class BigTextStyle extends Style { 1970 private CharSequence mBigText; 1971 BigTextStyle()1972 public BigTextStyle() { 1973 } 1974 BigTextStyle(Builder builder)1975 public BigTextStyle(Builder builder) { 1976 setBuilder(builder); 1977 } 1978 1979 /** 1980 * Overrides ContentTitle in the big form of the template. 1981 * This defaults to the value passed to setContentTitle(). 1982 */ setBigContentTitle(CharSequence title)1983 public BigTextStyle setBigContentTitle(CharSequence title) { 1984 mBigContentTitle = Builder.limitCharSequenceLength(title); 1985 return this; 1986 } 1987 1988 /** 1989 * Set the first line of text after the detail section in the big form of the template. 1990 */ setSummaryText(CharSequence cs)1991 public BigTextStyle setSummaryText(CharSequence cs) { 1992 mSummaryText = Builder.limitCharSequenceLength(cs); 1993 mSummaryTextSet = true; 1994 return this; 1995 } 1996 1997 /** 1998 * Provide the longer text to be displayed in the big form of the 1999 * template in place of the content text. 2000 */ bigText(CharSequence cs)2001 public BigTextStyle bigText(CharSequence cs) { 2002 mBigText = Builder.limitCharSequenceLength(cs); 2003 return this; 2004 } 2005 2006 /** 2007 * @hide 2008 */ 2009 @RestrictTo(LIBRARY_GROUP) 2010 @Override apply(NotificationBuilderWithBuilderAccessor builder)2011 public void apply(NotificationBuilderWithBuilderAccessor builder) { 2012 if (Build.VERSION.SDK_INT >= 16) { 2013 Notification.BigTextStyle style = 2014 new Notification.BigTextStyle(builder.getBuilder()) 2015 .setBigContentTitle(mBigContentTitle) 2016 .bigText(mBigText); 2017 if (mSummaryTextSet) { 2018 style.setSummaryText(mSummaryText); 2019 } 2020 } 2021 } 2022 } 2023 2024 /** 2025 * Helper class for generating large-format notifications that include multiple back-and-forth 2026 * messages of varying types between any number of people. 2027 * 2028 * <br> 2029 * In order to get a backwards compatible behavior, the app needs to use the v7 version of the 2030 * notification builder together with this style, otherwise the user will see the normal 2031 * notification view. 2032 * 2033 * <br> 2034 * Use {@link MessagingStyle#setConversationTitle(CharSequence)} to set a conversation title for 2035 * group chats with more than two people. This could be the user-created name of the group or, 2036 * if it doesn't have a specific name, a list of the participants in the conversation. Do not 2037 * set a conversation title for one-on-one chats, since platforms use the existence of this 2038 * field as a hint that the conversation is a group. 2039 * 2040 * <br> 2041 * This class is a "rebuilder": It attaches to a Builder object and modifies its behavior, like 2042 * so: 2043 * <pre class="prettyprint"> 2044 * 2045 * Notification notification = new Notification.Builder() 2046 * .setContentTitle("2 new messages with " + sender.toString()) 2047 * .setContentText(subject) 2048 * .setSmallIcon(R.drawable.new_message) 2049 * .setLargeIcon(aBitmap) 2050 * .setStyle(new Notification.MessagingStyle(resources.getString(R.string.reply_name)) 2051 * .addMessage(messages[0].getText(), messages[0].getTime(), messages[0].getSender()) 2052 * .addMessage(messages[1].getText(), messages[1].getTime(), messages[1].getSender())) 2053 * .build(); 2054 * </pre> 2055 */ 2056 public static class MessagingStyle extends Style { 2057 2058 /** 2059 * The maximum number of messages that will be retained in the Notification itself (the 2060 * number displayed is up to the platform). 2061 */ 2062 public static final int MAXIMUM_RETAINED_MESSAGES = 25; 2063 2064 CharSequence mUserDisplayName; 2065 CharSequence mConversationTitle; 2066 List<Message> mMessages = new ArrayList<>(); 2067 MessagingStyle()2068 MessagingStyle() { 2069 } 2070 2071 /** 2072 * @param userDisplayName Required - the name to be displayed for any replies sent by the 2073 * user before the posting app reposts the notification with those messages after they've 2074 * been actually sent and in previous messages sent by the user added in 2075 * {@link #addMessage(Message)} 2076 */ MessagingStyle(@onNull CharSequence userDisplayName)2077 public MessagingStyle(@NonNull CharSequence userDisplayName) { 2078 mUserDisplayName = userDisplayName; 2079 } 2080 2081 /** 2082 * Returns the name to be displayed for any replies sent by the user 2083 */ getUserDisplayName()2084 public CharSequence getUserDisplayName() { 2085 return mUserDisplayName; 2086 } 2087 2088 /** 2089 * Sets the title to be displayed on this conversation. This should only be used for 2090 * group messaging and left unset for one-on-one conversations. 2091 * @param conversationTitle Title displayed for this conversation. 2092 * @return this object for method chaining. 2093 */ setConversationTitle(CharSequence conversationTitle)2094 public MessagingStyle setConversationTitle(CharSequence conversationTitle) { 2095 mConversationTitle = conversationTitle; 2096 return this; 2097 } 2098 2099 /** 2100 * Return the title to be displayed on this conversation. Can be <code>null</code> and 2101 * should be for one-on-one conversations 2102 */ getConversationTitle()2103 public CharSequence getConversationTitle() { 2104 return mConversationTitle; 2105 } 2106 2107 /** 2108 * Adds a message for display by this notification. Convenience call for a simple 2109 * {@link Message} in {@link #addMessage(Message)} 2110 * @param text A {@link CharSequence} to be displayed as the message content 2111 * @param timestamp Time at which the message arrived in ms since Unix epoch 2112 * @param sender A {@link CharSequence} to be used for displaying the name of the 2113 * sender. Should be <code>null</code> for messages by the current user, in which case 2114 * the platform will insert {@link #getUserDisplayName()}. 2115 * Should be unique amongst all individuals in the conversation, and should be 2116 * consistent during re-posts of the notification. 2117 * 2118 * @see Message#Message(CharSequence, long, CharSequence) 2119 * 2120 * @return this object for method chaining 2121 */ addMessage(CharSequence text, long timestamp, CharSequence sender)2122 public MessagingStyle addMessage(CharSequence text, long timestamp, CharSequence sender) { 2123 mMessages.add(new Message(text, timestamp, sender)); 2124 if (mMessages.size() > MAXIMUM_RETAINED_MESSAGES) { 2125 mMessages.remove(0); 2126 } 2127 return this; 2128 } 2129 2130 /** 2131 * Adds a {@link Message} for display in this notification. 2132 * @param message The {@link Message} to be displayed 2133 * @return this object for method chaining 2134 */ addMessage(Message message)2135 public MessagingStyle addMessage(Message message) { 2136 mMessages.add(message); 2137 if (mMessages.size() > MAXIMUM_RETAINED_MESSAGES) { 2138 mMessages.remove(0); 2139 } 2140 return this; 2141 } 2142 2143 /** 2144 * Gets the list of {@code Message} objects that represent the notification 2145 */ getMessages()2146 public List<Message> getMessages() { 2147 return mMessages; 2148 } 2149 2150 /** 2151 * Retrieves a {@link MessagingStyle} from a {@link Notification}, enabling an application 2152 * that has set a {@link MessagingStyle} using {@link NotificationCompat} or 2153 * {@link android.app.Notification.Builder} to send messaging information to another 2154 * application using {@link NotificationCompat}, regardless of the API level of the system. 2155 * Returns {@code null} if there is no {@link MessagingStyle} set. 2156 */ extractMessagingStyleFromNotification( Notification notification)2157 public static MessagingStyle extractMessagingStyleFromNotification( 2158 Notification notification) { 2159 MessagingStyle style; 2160 Bundle extras = NotificationCompat.getExtras(notification); 2161 if (extras != null && !extras.containsKey(EXTRA_SELF_DISPLAY_NAME)) { 2162 style = null; 2163 } else { 2164 try { 2165 style = new MessagingStyle(); 2166 style.restoreFromCompatExtras(extras); 2167 } catch (ClassCastException e) { 2168 style = null; 2169 } 2170 } 2171 return style; 2172 } 2173 2174 /** 2175 * @hide 2176 */ 2177 @RestrictTo(LIBRARY_GROUP) 2178 @Override apply(NotificationBuilderWithBuilderAccessor builder)2179 public void apply(NotificationBuilderWithBuilderAccessor builder) { 2180 if (Build.VERSION.SDK_INT >= 24) { 2181 Notification.MessagingStyle style = 2182 new Notification.MessagingStyle(mUserDisplayName) 2183 .setConversationTitle(mConversationTitle); 2184 for (MessagingStyle.Message message : mMessages) { 2185 Notification.MessagingStyle.Message frameworkMessage = 2186 new Notification.MessagingStyle.Message( 2187 message.getText(), 2188 message.getTimestamp(), 2189 message.getSender()); 2190 if (message.getDataMimeType() != null) { 2191 frameworkMessage.setData(message.getDataMimeType(), message.getDataUri()); 2192 } 2193 style.addMessage(frameworkMessage); 2194 } 2195 style.setBuilder(builder.getBuilder()); 2196 } else { 2197 MessagingStyle.Message latestIncomingMessage = findLatestIncomingMessage(); 2198 // Set the title 2199 if (mConversationTitle != null) { 2200 builder.getBuilder().setContentTitle(mConversationTitle); 2201 } else if (latestIncomingMessage != null) { 2202 builder.getBuilder().setContentTitle(latestIncomingMessage.getSender()); 2203 } 2204 // Set the text 2205 if (latestIncomingMessage != null) { 2206 builder.getBuilder().setContentText(mConversationTitle != null 2207 ? makeMessageLine(latestIncomingMessage) 2208 : latestIncomingMessage.getText()); 2209 } 2210 // Build a fallback BigTextStyle for API 16-23 devices 2211 if (Build.VERSION.SDK_INT >= 16) { 2212 SpannableStringBuilder completeMessage = new SpannableStringBuilder(); 2213 boolean showNames = mConversationTitle != null 2214 || hasMessagesWithoutSender(); 2215 for (int i = mMessages.size() - 1; i >= 0; i--) { 2216 MessagingStyle.Message message = mMessages.get(i); 2217 CharSequence line; 2218 line = showNames ? makeMessageLine(message) : message.getText(); 2219 if (i != mMessages.size() - 1) { 2220 completeMessage.insert(0, "\n"); 2221 } 2222 completeMessage.insert(0, line); 2223 } 2224 new Notification.BigTextStyle(builder.getBuilder()) 2225 .setBigContentTitle(null) 2226 .bigText(completeMessage); 2227 } 2228 } 2229 } 2230 2231 @Nullable findLatestIncomingMessage()2232 private MessagingStyle.Message findLatestIncomingMessage() { 2233 for (int i = mMessages.size() - 1; i >= 0; i--) { 2234 MessagingStyle.Message message = mMessages.get(i); 2235 // Incoming messages have a non-empty sender. 2236 if (!TextUtils.isEmpty(message.getSender())) { 2237 return message; 2238 } 2239 } 2240 if (!mMessages.isEmpty()) { 2241 // No incoming messages, fall back to outgoing message 2242 return mMessages.get(mMessages.size() - 1); 2243 } 2244 return null; 2245 } 2246 hasMessagesWithoutSender()2247 private boolean hasMessagesWithoutSender() { 2248 for (int i = mMessages.size() - 1; i >= 0; i--) { 2249 MessagingStyle.Message message = mMessages.get(i); 2250 if (message.getSender() == null) { 2251 return true; 2252 } 2253 } 2254 return false; 2255 } 2256 makeMessageLine(MessagingStyle.Message message)2257 private CharSequence makeMessageLine(MessagingStyle.Message message) { 2258 BidiFormatter bidi = BidiFormatter.getInstance(); 2259 SpannableStringBuilder sb = new SpannableStringBuilder(); 2260 final boolean afterLollipop = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP; 2261 int color = afterLollipop ? Color.BLACK : Color.WHITE; 2262 CharSequence replyName = message.getSender(); 2263 if (TextUtils.isEmpty(message.getSender())) { 2264 replyName = mUserDisplayName == null 2265 ? "" : mUserDisplayName; 2266 color = afterLollipop && mBuilder.getColor() != NotificationCompat.COLOR_DEFAULT 2267 ? mBuilder.getColor() 2268 : color; 2269 } 2270 CharSequence senderText = bidi.unicodeWrap(replyName); 2271 sb.append(senderText); 2272 sb.setSpan(makeFontColorSpan(color), 2273 sb.length() - senderText.length(), 2274 sb.length(), 2275 Spanned.SPAN_EXCLUSIVE_EXCLUSIVE /* flags */); 2276 CharSequence text = message.getText() == null ? "" : message.getText(); 2277 sb.append(" ").append(bidi.unicodeWrap(text)); 2278 return sb; 2279 } 2280 2281 @NonNull makeFontColorSpan(int color)2282 private TextAppearanceSpan makeFontColorSpan(int color) { 2283 return new TextAppearanceSpan(null, 0, 0, ColorStateList.valueOf(color), null); 2284 } 2285 2286 @Override addCompatExtras(Bundle extras)2287 public void addCompatExtras(Bundle extras) { 2288 super.addCompatExtras(extras); 2289 if (mUserDisplayName != null) { 2290 extras.putCharSequence(EXTRA_SELF_DISPLAY_NAME, mUserDisplayName); 2291 } 2292 if (mConversationTitle != null) { 2293 extras.putCharSequence(EXTRA_CONVERSATION_TITLE, mConversationTitle); 2294 } 2295 if (!mMessages.isEmpty()) { extras.putParcelableArray(EXTRA_MESSAGES, 2296 Message.getBundleArrayForMessages(mMessages)); 2297 } 2298 } 2299 2300 /** 2301 * @hide 2302 */ 2303 @RestrictTo(LIBRARY_GROUP) 2304 @Override restoreFromCompatExtras(Bundle extras)2305 protected void restoreFromCompatExtras(Bundle extras) { 2306 mMessages.clear(); 2307 mUserDisplayName = extras.getString(EXTRA_SELF_DISPLAY_NAME); 2308 mConversationTitle = extras.getString(EXTRA_CONVERSATION_TITLE); 2309 Parcelable[] parcelables = extras.getParcelableArray(EXTRA_MESSAGES); 2310 if (parcelables != null) { 2311 mMessages = Message.getMessagesFromBundleArray(parcelables); 2312 } 2313 } 2314 2315 public static final class Message { 2316 2317 static final String KEY_TEXT = "text"; 2318 static final String KEY_TIMESTAMP = "time"; 2319 static final String KEY_SENDER = "sender"; 2320 static final String KEY_DATA_MIME_TYPE = "type"; 2321 static final String KEY_DATA_URI= "uri"; 2322 static final String KEY_EXTRAS_BUNDLE = "extras"; 2323 2324 private final CharSequence mText; 2325 private final long mTimestamp; 2326 private final CharSequence mSender; 2327 2328 private Bundle mExtras = new Bundle(); 2329 private String mDataMimeType; 2330 private Uri mDataUri; 2331 2332 /** 2333 * Constructor 2334 * @param text A {@link CharSequence} to be displayed as the message content 2335 * @param timestamp Time at which the message arrived in ms since Unix epoch 2336 * @param sender A {@link CharSequence} to be used for displaying the name of the 2337 * sender. Should be <code>null</code> for messages by the current user, in which case 2338 * the platform will insert {@link MessagingStyle#getUserDisplayName()}. 2339 * Should be unique amongst all individuals in the conversation, and should be 2340 * consistent during re-posts of the notification. 2341 */ Message(CharSequence text, long timestamp, CharSequence sender)2342 public Message(CharSequence text, long timestamp, CharSequence sender){ 2343 mText = text; 2344 mTimestamp = timestamp; 2345 mSender = sender; 2346 } 2347 2348 /** 2349 * Sets a binary blob of data and an associated MIME type for a message. In the case 2350 * where the platform doesn't support the MIME type, the original text provided in the 2351 * constructor will be used. 2352 * @param dataMimeType The MIME type of the content. See 2353 * <a href="{@docRoot}notifications/messaging.html"> for the list of supported MIME 2354 * types on Android and Android Wear. 2355 * @param dataUri The uri containing the content whose type is given by the MIME type. 2356 * <p class="note"> 2357 * <ol> 2358 * <li>Notification Listeners including the System UI need permission to access the 2359 * data the Uri points to. The recommended ways to do this are:</li> 2360 * <li>Store the data in your own ContentProvider, making sure that other apps have 2361 * the correct permission to access your provider. The preferred mechanism for 2362 * providing access is to use per-URI permissions which are temporary and only 2363 * grant access to the receiving application. An easy way to create a 2364 * ContentProvider like this is to use the FileProvider helper class.</li> 2365 * <li>Use the system MediaStore. The MediaStore is primarily aimed at video, audio 2366 * and image MIME types, however beginning with Android 3.0 (API level 11) it can 2367 * also store non-media types (see MediaStore.Files for more info). Files can be 2368 * inserted into the MediaStore using scanFile() after which a content:// style 2369 * Uri suitable for sharing is passed to the provided onScanCompleted() callback. 2370 * Note that once added to the system MediaStore the content is accessible to any 2371 * app on the device.</li> 2372 * </ol> 2373 * @return this object for method chaining 2374 */ setData(String dataMimeType, Uri dataUri)2375 public Message setData(String dataMimeType, Uri dataUri) { 2376 mDataMimeType = dataMimeType; 2377 mDataUri = dataUri; 2378 return this; 2379 } 2380 2381 /** 2382 * Get the text to be used for this message, or the fallback text if a type and content 2383 * Uri have been set 2384 */ getText()2385 public CharSequence getText() { 2386 return mText; 2387 } 2388 2389 /** 2390 * Get the time at which this message arrived in ms since Unix epoch 2391 */ getTimestamp()2392 public long getTimestamp() { 2393 return mTimestamp; 2394 } 2395 2396 /** 2397 * Get the extras Bundle for this message. 2398 */ getExtras()2399 public Bundle getExtras() { 2400 return mExtras; 2401 } 2402 2403 /** 2404 * Get the text used to display the contact's name in the messaging experience 2405 */ getSender()2406 public CharSequence getSender() { 2407 return mSender; 2408 } 2409 2410 /** 2411 * Get the MIME type of the data pointed to by the Uri 2412 */ getDataMimeType()2413 public String getDataMimeType() { 2414 return mDataMimeType; 2415 } 2416 2417 /** 2418 * Get the the Uri pointing to the content of the message. Can be null, in which case 2419 * {@see #getText()} is used. 2420 */ getDataUri()2421 public Uri getDataUri() { 2422 return mDataUri; 2423 } 2424 toBundle()2425 private Bundle toBundle() { 2426 Bundle bundle = new Bundle(); 2427 if (mText != null) { 2428 bundle.putCharSequence(KEY_TEXT, mText); 2429 } 2430 bundle.putLong(KEY_TIMESTAMP, mTimestamp); 2431 if (mSender != null) { 2432 bundle.putCharSequence(KEY_SENDER, mSender); 2433 } 2434 if (mDataMimeType != null) { 2435 bundle.putString(KEY_DATA_MIME_TYPE, mDataMimeType); 2436 } 2437 if (mDataUri != null) { 2438 bundle.putParcelable(KEY_DATA_URI, mDataUri); 2439 } 2440 if (mExtras != null) { 2441 bundle.putBundle(KEY_EXTRAS_BUNDLE, mExtras); 2442 } 2443 return bundle; 2444 } 2445 getBundleArrayForMessages(List<Message> messages)2446 static Bundle[] getBundleArrayForMessages(List<Message> messages) { 2447 Bundle[] bundles = new Bundle[messages.size()]; 2448 final int N = messages.size(); 2449 for (int i = 0; i < N; i++) { 2450 bundles[i] = messages.get(i).toBundle(); 2451 } 2452 return bundles; 2453 } 2454 getMessagesFromBundleArray(Parcelable[] bundles)2455 static List<Message> getMessagesFromBundleArray(Parcelable[] bundles) { 2456 List<Message> messages = new ArrayList<>(bundles.length); 2457 for (int i = 0; i < bundles.length; i++) { 2458 if (bundles[i] instanceof Bundle) { 2459 Message message = getMessageFromBundle((Bundle)bundles[i]); 2460 if (message != null) { 2461 messages.add(message); 2462 } 2463 } 2464 } 2465 return messages; 2466 } 2467 getMessageFromBundle(Bundle bundle)2468 static Message getMessageFromBundle(Bundle bundle) { 2469 try { 2470 if (!bundle.containsKey(KEY_TEXT) || !bundle.containsKey(KEY_TIMESTAMP)) { 2471 return null; 2472 } else { 2473 Message message = new Message(bundle.getCharSequence(KEY_TEXT), 2474 bundle.getLong(KEY_TIMESTAMP), bundle.getCharSequence(KEY_SENDER)); 2475 if (bundle.containsKey(KEY_DATA_MIME_TYPE) && 2476 bundle.containsKey(KEY_DATA_URI)) { 2477 message.setData(bundle.getString(KEY_DATA_MIME_TYPE), 2478 (Uri) bundle.getParcelable(KEY_DATA_URI)); 2479 } 2480 if (bundle.containsKey(KEY_EXTRAS_BUNDLE)) { 2481 message.getExtras().putAll(bundle.getBundle(KEY_EXTRAS_BUNDLE)); 2482 } 2483 return message; 2484 } 2485 } catch (ClassCastException e) { 2486 return null; 2487 } 2488 } 2489 } 2490 } 2491 2492 /** 2493 * Helper class for generating large-format notifications that include a list of (up to 5) strings. 2494 * 2495 * <br> 2496 * If the platform does not provide large-format notifications, this method has no effect. The 2497 * user will always see the normal notification view. 2498 * <br> 2499 * This class is a "rebuilder": It attaches to a Builder object and modifies its behavior, like so: 2500 * <pre class="prettyprint"> 2501 * Notification notification = new Notification.Builder() 2502 * .setContentTitle("5 New mails from " + sender.toString()) 2503 * .setContentText(subject) 2504 * .setSmallIcon(R.drawable.new_mail) 2505 * .setLargeIcon(aBitmap) 2506 * .setStyle(new Notification.InboxStyle() 2507 * .addLine(str1) 2508 * .addLine(str2) 2509 * .setContentTitle("") 2510 * .setSummaryText("+3 more")) 2511 * .build(); 2512 * </pre> 2513 * 2514 * @see Notification#bigContentView 2515 */ 2516 public static class InboxStyle extends Style { 2517 private ArrayList<CharSequence> mTexts = new ArrayList<CharSequence>(); 2518 InboxStyle()2519 public InboxStyle() { 2520 } 2521 InboxStyle(Builder builder)2522 public InboxStyle(Builder builder) { 2523 setBuilder(builder); 2524 } 2525 2526 /** 2527 * Overrides ContentTitle in the big form of the template. 2528 * This defaults to the value passed to setContentTitle(). 2529 */ setBigContentTitle(CharSequence title)2530 public InboxStyle setBigContentTitle(CharSequence title) { 2531 mBigContentTitle = Builder.limitCharSequenceLength(title); 2532 return this; 2533 } 2534 2535 /** 2536 * Set the first line of text after the detail section in the big form of the template. 2537 */ setSummaryText(CharSequence cs)2538 public InboxStyle setSummaryText(CharSequence cs) { 2539 mSummaryText = Builder.limitCharSequenceLength(cs); 2540 mSummaryTextSet = true; 2541 return this; 2542 } 2543 2544 /** 2545 * Append a line to the digest section of the Inbox notification. 2546 */ addLine(CharSequence cs)2547 public InboxStyle addLine(CharSequence cs) { 2548 mTexts.add(Builder.limitCharSequenceLength(cs)); 2549 return this; 2550 } 2551 2552 /** 2553 * @hide 2554 */ 2555 @RestrictTo(LIBRARY_GROUP) 2556 @Override apply(NotificationBuilderWithBuilderAccessor builder)2557 public void apply(NotificationBuilderWithBuilderAccessor builder) { 2558 if (Build.VERSION.SDK_INT >= 16) { 2559 Notification.InboxStyle style = 2560 new Notification.InboxStyle(builder.getBuilder()) 2561 .setBigContentTitle(mBigContentTitle); 2562 if (mSummaryTextSet) { 2563 style.setSummaryText(mSummaryText); 2564 } 2565 for (CharSequence text: mTexts) { 2566 style.addLine(text); 2567 } 2568 } 2569 } 2570 } 2571 2572 /** 2573 * Notification style for custom views that are decorated by the system. 2574 * 2575 * <p>Instead of providing a notification that is completely custom, a developer can set this 2576 * style and still obtain system decorations like the notification header with the expand 2577 * affordance and actions. 2578 * 2579 * <p>Use {@link NotificationCompat.Builder#setCustomContentView(RemoteViews)}, 2580 * {@link NotificationCompat.Builder#setCustomBigContentView(RemoteViews)} and 2581 * {@link NotificationCompat.Builder#setCustomHeadsUpContentView(RemoteViews)} to set the 2582 * corresponding custom views to display. 2583 * 2584 * <p>To use this style with your Notification, feed it to 2585 * {@link NotificationCompat.Builder#setStyle(Style)} like so: 2586 * <pre class="prettyprint"> 2587 * Notification noti = new NotificationCompat.Builder() 2588 * .setSmallIcon(R.drawable.ic_stat_player) 2589 * .setLargeIcon(albumArtBitmap)) 2590 * .setCustomContentView(contentView) 2591 * .setStyle(<b>new NotificationCompat.DecoratedCustomViewStyle()</b>) 2592 * .build(); 2593 * </pre> 2594 * 2595 * <p>If you are using this style, consider using the corresponding styles like 2596 * {@link android.support.compat.R.style#TextAppearance_Compat_Notification} or 2597 * {@link android.support.compat.R.style#TextAppearance_Compat_Notification_Title} in 2598 * your custom views in order to get the correct styling on each platform version. 2599 */ 2600 public static class DecoratedCustomViewStyle extends Style { 2601 2602 private static final int MAX_ACTION_BUTTONS = 3; 2603 DecoratedCustomViewStyle()2604 public DecoratedCustomViewStyle() { 2605 } 2606 2607 /** 2608 * @hide 2609 */ 2610 @RestrictTo(LIBRARY_GROUP) 2611 @Override apply(NotificationBuilderWithBuilderAccessor builder)2612 public void apply(NotificationBuilderWithBuilderAccessor builder) { 2613 if (Build.VERSION.SDK_INT >= 24) { 2614 builder.getBuilder().setStyle(new Notification.DecoratedCustomViewStyle()); 2615 } 2616 } 2617 2618 /** 2619 * @hide 2620 */ 2621 @RestrictTo(LIBRARY_GROUP) 2622 @Override makeContentView(NotificationBuilderWithBuilderAccessor builder)2623 public RemoteViews makeContentView(NotificationBuilderWithBuilderAccessor builder) { 2624 if (Build.VERSION.SDK_INT >= 24) { 2625 // No custom content view required 2626 return null; 2627 } 2628 if (mBuilder.getContentView() == null) { 2629 // No special content view 2630 return null; 2631 } 2632 return createRemoteViews(mBuilder.getContentView(), false); 2633 } 2634 2635 /** 2636 * @hide 2637 */ 2638 @RestrictTo(LIBRARY_GROUP) 2639 @Override makeBigContentView(NotificationBuilderWithBuilderAccessor builder)2640 public RemoteViews makeBigContentView(NotificationBuilderWithBuilderAccessor builder) { 2641 if (Build.VERSION.SDK_INT >= 24) { 2642 // No custom big content view required 2643 return null; 2644 } 2645 RemoteViews bigContentView = mBuilder.getBigContentView(); 2646 RemoteViews innerView = bigContentView != null 2647 ? bigContentView 2648 : mBuilder.getContentView(); 2649 if (innerView == null) { 2650 // No expandable notification 2651 return null; 2652 } 2653 return createRemoteViews(innerView, true); 2654 } 2655 2656 /** 2657 * @hide 2658 */ 2659 @RestrictTo(LIBRARY_GROUP) 2660 @Override makeHeadsUpContentView(NotificationBuilderWithBuilderAccessor builder)2661 public RemoteViews makeHeadsUpContentView(NotificationBuilderWithBuilderAccessor builder) { 2662 if (Build.VERSION.SDK_INT >= 24) { 2663 // No custom heads up content view required 2664 return null; 2665 } 2666 RemoteViews headsUp = mBuilder.getHeadsUpContentView(); 2667 RemoteViews innerView = headsUp != null ? headsUp : mBuilder.getContentView(); 2668 if (headsUp == null) { 2669 // No expandable notification 2670 return null; 2671 } 2672 return createRemoteViews(innerView, true); 2673 } 2674 createRemoteViews(RemoteViews innerView, boolean showActions)2675 private RemoteViews createRemoteViews(RemoteViews innerView, boolean showActions) { 2676 RemoteViews remoteViews = applyStandardTemplate(true /* showSmallIcon */, 2677 R.layout.notification_template_custom_big, false /* fitIn1U */); 2678 remoteViews.removeAllViews(R.id.actions); 2679 boolean actionsVisible = false; 2680 if (showActions && mBuilder.mActions != null) { 2681 int numActions = Math.min(mBuilder.mActions.size(), MAX_ACTION_BUTTONS); 2682 if (numActions > 0) { 2683 actionsVisible = true; 2684 for (int i = 0; i < numActions; i++) { 2685 final RemoteViews button = generateActionButton(mBuilder.mActions.get(i)); 2686 remoteViews.addView(R.id.actions, button); 2687 } 2688 } 2689 } 2690 int actionVisibility = actionsVisible ? View.VISIBLE : View.GONE; 2691 remoteViews.setViewVisibility(R.id.actions, actionVisibility); 2692 remoteViews.setViewVisibility(R.id.action_divider, actionVisibility); 2693 buildIntoRemoteViews(remoteViews, innerView); 2694 return remoteViews; 2695 } 2696 generateActionButton(NotificationCompat.Action action)2697 private RemoteViews generateActionButton(NotificationCompat.Action action) { 2698 final boolean tombstone = (action.actionIntent == null); 2699 RemoteViews button = new RemoteViews(mBuilder.mContext.getPackageName(), 2700 tombstone ? R.layout.notification_action_tombstone 2701 : R.layout.notification_action); 2702 button.setImageViewBitmap(R.id.action_image, 2703 createColoredBitmap(action.getIcon(), mBuilder.mContext.getResources() 2704 .getColor(R.color.notification_action_color_filter))); 2705 button.setTextViewText(R.id.action_text, action.title); 2706 if (!tombstone) { 2707 button.setOnClickPendingIntent(R.id.action_container, action.actionIntent); 2708 } 2709 if (Build.VERSION.SDK_INT >= 15) { 2710 button.setContentDescription(R.id.action_container, action.title); 2711 } 2712 return button; 2713 } 2714 } 2715 2716 /** 2717 * Structure to encapsulate a named action that can be shown as part of this notification. 2718 * It must include an icon, a label, and a {@link PendingIntent} to be fired when the action is 2719 * selected by the user. Action buttons won't appear on platforms prior to Android 4.1. 2720 * <p> 2721 * Apps should use {@link NotificationCompat.Builder#addAction(int, CharSequence, PendingIntent)} 2722 * or {@link NotificationCompat.Builder#addAction(NotificationCompat.Action)} 2723 * to attach actions. 2724 */ 2725 public static class Action { 2726 final Bundle mExtras; 2727 private final RemoteInput[] mRemoteInputs; 2728 2729 /** 2730 * Holds {@link RemoteInput}s that only accept data, meaning 2731 * {@link RemoteInput#getAllowFreeFormInput} is false, {@link RemoteInput#getChoices} 2732 * is null or empty, and {@link RemoteInput#getAllowedDataTypes is non-null and not 2733 * empty. These {@link RemoteInput}s will be ignored by devices that do not 2734 * support non-text-based {@link RemoteInput}s. See {@link Builder#build}. 2735 * 2736 * You can test if a RemoteInput matches these constraints using 2737 * {@link RemoteInput#isDataOnly}. 2738 */ 2739 private final RemoteInput[] mDataOnlyRemoteInputs; 2740 2741 private boolean mAllowGeneratedReplies; 2742 2743 /** 2744 * Small icon representing the action. 2745 */ 2746 public int icon; 2747 /** 2748 * Title of the action. 2749 */ 2750 public CharSequence title; 2751 /** 2752 * Intent to send when the user invokes this action. May be null, in which case the action 2753 * may be rendered in a disabled presentation. 2754 */ 2755 public PendingIntent actionIntent; 2756 Action(int icon, CharSequence title, PendingIntent intent)2757 public Action(int icon, CharSequence title, PendingIntent intent) { 2758 this(icon, title, intent, new Bundle(), null, null, true); 2759 } 2760 Action(int icon, CharSequence title, PendingIntent intent, Bundle extras, RemoteInput[] remoteInputs, RemoteInput[] dataOnlyRemoteInputs, boolean allowGeneratedReplies)2761 Action(int icon, CharSequence title, PendingIntent intent, Bundle extras, 2762 RemoteInput[] remoteInputs, RemoteInput[] dataOnlyRemoteInputs, 2763 boolean allowGeneratedReplies) { 2764 this.icon = icon; 2765 this.title = NotificationCompat.Builder.limitCharSequenceLength(title); 2766 this.actionIntent = intent; 2767 this.mExtras = extras != null ? extras : new Bundle(); 2768 this.mRemoteInputs = remoteInputs; 2769 this.mDataOnlyRemoteInputs = dataOnlyRemoteInputs; 2770 this.mAllowGeneratedReplies = allowGeneratedReplies; 2771 } 2772 getIcon()2773 public int getIcon() { 2774 return icon; 2775 } 2776 getTitle()2777 public CharSequence getTitle() { 2778 return title; 2779 } 2780 getActionIntent()2781 public PendingIntent getActionIntent() { 2782 return actionIntent; 2783 } 2784 2785 /** 2786 * Get additional metadata carried around with this Action. 2787 */ getExtras()2788 public Bundle getExtras() { 2789 return mExtras; 2790 } 2791 2792 /** 2793 * Return whether the platform should automatically generate possible replies for this 2794 * {@link Action} 2795 */ getAllowGeneratedReplies()2796 public boolean getAllowGeneratedReplies() { 2797 return mAllowGeneratedReplies; 2798 } 2799 2800 /** 2801 * Get the list of inputs to be collected from the user when this action is sent. 2802 * May return null if no remote inputs were added. Only returns inputs which accept 2803 * a text input. For inputs which only accept data use {@link #getDataOnlyRemoteInputs}. 2804 */ getRemoteInputs()2805 public RemoteInput[] getRemoteInputs() { 2806 return mRemoteInputs; 2807 } 2808 2809 /** 2810 * Get the list of inputs to be collected from the user that ONLY accept data when this 2811 * action is sent. These remote inputs are guaranteed to return true on a call to 2812 * {@link RemoteInput#isDataOnly}. 2813 * 2814 * <p>May return null if no data-only remote inputs were added. 2815 * 2816 * <p>This method exists so that legacy RemoteInput collectors that pre-date the addition 2817 * of non-textual RemoteInputs do not access these remote inputs. 2818 */ getDataOnlyRemoteInputs()2819 public RemoteInput[] getDataOnlyRemoteInputs() { 2820 return mDataOnlyRemoteInputs; 2821 } 2822 2823 /** 2824 * Builder class for {@link Action} objects. 2825 */ 2826 public static final class Builder { 2827 private final int mIcon; 2828 private final CharSequence mTitle; 2829 private final PendingIntent mIntent; 2830 private boolean mAllowGeneratedReplies = true; 2831 private final Bundle mExtras; 2832 private ArrayList<RemoteInput> mRemoteInputs; 2833 2834 /** 2835 * Construct a new builder for {@link Action} object. 2836 * @param icon icon to show for this action 2837 * @param title the title of the action 2838 * @param intent the {@link PendingIntent} to fire when users trigger this action 2839 */ Builder(int icon, CharSequence title, PendingIntent intent)2840 public Builder(int icon, CharSequence title, PendingIntent intent) { 2841 this(icon, title, intent, new Bundle(), null, true); 2842 } 2843 2844 /** 2845 * Construct a new builder for {@link Action} object using the fields from an 2846 * {@link Action}. 2847 * @param action the action to read fields from. 2848 */ Builder(Action action)2849 public Builder(Action action) { 2850 this(action.icon, action.title, action.actionIntent, new Bundle(action.mExtras), 2851 action.getRemoteInputs(), action.getAllowGeneratedReplies()); 2852 } 2853 Builder(int icon, CharSequence title, PendingIntent intent, Bundle extras, RemoteInput[] remoteInputs, boolean allowGeneratedReplies)2854 private Builder(int icon, CharSequence title, PendingIntent intent, Bundle extras, 2855 RemoteInput[] remoteInputs, boolean allowGeneratedReplies) { 2856 mIcon = icon; 2857 mTitle = NotificationCompat.Builder.limitCharSequenceLength(title); 2858 mIntent = intent; 2859 mExtras = extras; 2860 mRemoteInputs = remoteInputs == null ? null : new ArrayList<>( 2861 Arrays.asList(remoteInputs)); 2862 mAllowGeneratedReplies = allowGeneratedReplies; 2863 } 2864 2865 /** 2866 * Merge additional metadata into this builder. 2867 * 2868 * <p>Values within the Bundle will replace existing extras values in this Builder. 2869 * 2870 * @see NotificationCompat.Action#getExtras 2871 */ addExtras(Bundle extras)2872 public Builder addExtras(Bundle extras) { 2873 if (extras != null) { 2874 mExtras.putAll(extras); 2875 } 2876 return this; 2877 } 2878 2879 /** 2880 * Get the metadata Bundle used by this Builder. 2881 * 2882 * <p>The returned Bundle is shared with this Builder. 2883 */ getExtras()2884 public Bundle getExtras() { 2885 return mExtras; 2886 } 2887 2888 /** 2889 * Add an input to be collected from the user when this action is sent. 2890 * Response values can be retrieved from the fired intent by using the 2891 * {@link RemoteInput#getResultsFromIntent} function. 2892 * @param remoteInput a {@link RemoteInput} to add to the action 2893 * @return this object for method chaining 2894 */ addRemoteInput(RemoteInput remoteInput)2895 public Builder addRemoteInput(RemoteInput remoteInput) { 2896 if (mRemoteInputs == null) { 2897 mRemoteInputs = new ArrayList<RemoteInput>(); 2898 } 2899 mRemoteInputs.add(remoteInput); 2900 return this; 2901 } 2902 2903 /** 2904 * Set whether the platform should automatically generate possible replies to add to 2905 * {@link RemoteInput#getChoices()}. If the {@link Action} doesn't have a 2906 * {@link RemoteInput}, this has no effect. 2907 * @param allowGeneratedReplies {@code true} to allow generated replies, {@code false} 2908 * otherwise 2909 * @return this object for method chaining 2910 * The default value is {@code true} 2911 */ setAllowGeneratedReplies(boolean allowGeneratedReplies)2912 public Builder setAllowGeneratedReplies(boolean allowGeneratedReplies) { 2913 mAllowGeneratedReplies = allowGeneratedReplies; 2914 return this; 2915 } 2916 2917 /** 2918 * Apply an extender to this action builder. Extenders may be used to add 2919 * metadata or change options on this builder. 2920 */ extend(Extender extender)2921 public Builder extend(Extender extender) { 2922 extender.extend(this); 2923 return this; 2924 } 2925 2926 /** 2927 * Combine all of the options that have been set and return a new {@link Action} 2928 * object. 2929 * @return the built action 2930 */ build()2931 public Action build() { 2932 List<RemoteInput> dataOnlyInputs = new ArrayList<>(); 2933 List<RemoteInput> textInputs = new ArrayList<>(); 2934 if (mRemoteInputs != null) { 2935 for (RemoteInput input : mRemoteInputs) { 2936 if (input.isDataOnly()) { 2937 dataOnlyInputs.add(input); 2938 } else { 2939 textInputs.add(input); 2940 } 2941 } 2942 } 2943 RemoteInput[] dataOnlyInputsArr = dataOnlyInputs.isEmpty() 2944 ? null : dataOnlyInputs.toArray(new RemoteInput[dataOnlyInputs.size()]); 2945 RemoteInput[] textInputsArr = textInputs.isEmpty() 2946 ? null : textInputs.toArray(new RemoteInput[textInputs.size()]); 2947 return new Action(mIcon, mTitle, mIntent, mExtras, textInputsArr, 2948 dataOnlyInputsArr, mAllowGeneratedReplies); 2949 } 2950 } 2951 2952 2953 /** 2954 * Extender interface for use with {@link Builder#extend}. Extenders may be used to add 2955 * metadata or change options on an action builder. 2956 */ 2957 public interface Extender { 2958 /** 2959 * Apply this extender to a notification action builder. 2960 * @param builder the builder to be modified. 2961 * @return the build object for chaining. 2962 */ extend(Builder builder)2963 Builder extend(Builder builder); 2964 } 2965 2966 /** 2967 * Wearable extender for notification actions. To add extensions to an action, 2968 * create a new {@link NotificationCompat.Action.WearableExtender} object using 2969 * the {@code WearableExtender()} constructor and apply it to a 2970 * {@link NotificationCompat.Action.Builder} using 2971 * {@link NotificationCompat.Action.Builder#extend}. 2972 * 2973 * <pre class="prettyprint"> 2974 * NotificationCompat.Action action = new NotificationCompat.Action.Builder( 2975 * R.drawable.archive_all, "Archive all", actionIntent) 2976 * .extend(new NotificationCompat.Action.WearableExtender() 2977 * .setAvailableOffline(false)) 2978 * .build();</pre> 2979 */ 2980 public static final class WearableExtender implements Extender { 2981 /** Notification action extra which contains wearable extensions */ 2982 private static final String EXTRA_WEARABLE_EXTENSIONS = "android.wearable.EXTENSIONS"; 2983 2984 // Keys within EXTRA_WEARABLE_EXTENSIONS for wearable options. 2985 private static final String KEY_FLAGS = "flags"; 2986 private static final String KEY_IN_PROGRESS_LABEL = "inProgressLabel"; 2987 private static final String KEY_CONFIRM_LABEL = "confirmLabel"; 2988 private static final String KEY_CANCEL_LABEL = "cancelLabel"; 2989 2990 // Flags bitwise-ored to mFlags 2991 private static final int FLAG_AVAILABLE_OFFLINE = 0x1; 2992 private static final int FLAG_HINT_LAUNCHES_ACTIVITY = 1 << 1; 2993 private static final int FLAG_HINT_DISPLAY_INLINE = 1 << 2; 2994 2995 // Default value for flags integer 2996 private static final int DEFAULT_FLAGS = FLAG_AVAILABLE_OFFLINE; 2997 2998 private int mFlags = DEFAULT_FLAGS; 2999 3000 private CharSequence mInProgressLabel; 3001 private CharSequence mConfirmLabel; 3002 private CharSequence mCancelLabel; 3003 3004 /** 3005 * Create a {@link NotificationCompat.Action.WearableExtender} with default 3006 * options. 3007 */ WearableExtender()3008 public WearableExtender() { 3009 } 3010 3011 /** 3012 * Create a {@link NotificationCompat.Action.WearableExtender} by reading 3013 * wearable options present in an existing notification action. 3014 * @param action the notification action to inspect. 3015 */ WearableExtender(Action action)3016 public WearableExtender(Action action) { 3017 Bundle wearableBundle = action.getExtras().getBundle(EXTRA_WEARABLE_EXTENSIONS); 3018 if (wearableBundle != null) { 3019 mFlags = wearableBundle.getInt(KEY_FLAGS, DEFAULT_FLAGS); 3020 mInProgressLabel = wearableBundle.getCharSequence(KEY_IN_PROGRESS_LABEL); 3021 mConfirmLabel = wearableBundle.getCharSequence(KEY_CONFIRM_LABEL); 3022 mCancelLabel = wearableBundle.getCharSequence(KEY_CANCEL_LABEL); 3023 } 3024 } 3025 3026 /** 3027 * Apply wearable extensions to a notification action that is being built. This is 3028 * typically called by the {@link NotificationCompat.Action.Builder#extend} 3029 * method of {@link NotificationCompat.Action.Builder}. 3030 */ 3031 @Override extend(Action.Builder builder)3032 public Action.Builder extend(Action.Builder builder) { 3033 Bundle wearableBundle = new Bundle(); 3034 3035 if (mFlags != DEFAULT_FLAGS) { 3036 wearableBundle.putInt(KEY_FLAGS, mFlags); 3037 } 3038 if (mInProgressLabel != null) { 3039 wearableBundle.putCharSequence(KEY_IN_PROGRESS_LABEL, mInProgressLabel); 3040 } 3041 if (mConfirmLabel != null) { 3042 wearableBundle.putCharSequence(KEY_CONFIRM_LABEL, mConfirmLabel); 3043 } 3044 if (mCancelLabel != null) { 3045 wearableBundle.putCharSequence(KEY_CANCEL_LABEL, mCancelLabel); 3046 } 3047 3048 builder.getExtras().putBundle(EXTRA_WEARABLE_EXTENSIONS, wearableBundle); 3049 return builder; 3050 } 3051 3052 @Override clone()3053 public WearableExtender clone() { 3054 WearableExtender that = new WearableExtender(); 3055 that.mFlags = this.mFlags; 3056 that.mInProgressLabel = this.mInProgressLabel; 3057 that.mConfirmLabel = this.mConfirmLabel; 3058 that.mCancelLabel = this.mCancelLabel; 3059 return that; 3060 } 3061 3062 /** 3063 * Set whether this action is available when the wearable device is not connected to 3064 * a companion device. The user can still trigger this action when the wearable device 3065 * is offline, but a visual hint will indicate that the action may not be available. 3066 * Defaults to true. 3067 */ setAvailableOffline(boolean availableOffline)3068 public WearableExtender setAvailableOffline(boolean availableOffline) { 3069 setFlag(FLAG_AVAILABLE_OFFLINE, availableOffline); 3070 return this; 3071 } 3072 3073 /** 3074 * Get whether this action is available when the wearable device is not connected to 3075 * a companion device. The user can still trigger this action when the wearable device 3076 * is offline, but a visual hint will indicate that the action may not be available. 3077 * Defaults to true. 3078 */ isAvailableOffline()3079 public boolean isAvailableOffline() { 3080 return (mFlags & FLAG_AVAILABLE_OFFLINE) != 0; 3081 } 3082 setFlag(int mask, boolean value)3083 private void setFlag(int mask, boolean value) { 3084 if (value) { 3085 mFlags |= mask; 3086 } else { 3087 mFlags &= ~mask; 3088 } 3089 } 3090 3091 /** 3092 * Set a label to display while the wearable is preparing to automatically execute the 3093 * action. This is usually a 'ing' verb ending in ellipsis like "Sending..." 3094 * 3095 * @param label the label to display while the action is being prepared to execute 3096 * @return this object for method chaining 3097 */ setInProgressLabel(CharSequence label)3098 public WearableExtender setInProgressLabel(CharSequence label) { 3099 mInProgressLabel = label; 3100 return this; 3101 } 3102 3103 /** 3104 * Get the label to display while the wearable is preparing to automatically execute 3105 * the action. This is usually a 'ing' verb ending in ellipsis like "Sending..." 3106 * 3107 * @return the label to display while the action is being prepared to execute 3108 */ getInProgressLabel()3109 public CharSequence getInProgressLabel() { 3110 return mInProgressLabel; 3111 } 3112 3113 /** 3114 * Set a label to display to confirm that the action should be executed. 3115 * This is usually an imperative verb like "Send". 3116 * 3117 * @param label the label to confirm the action should be executed 3118 * @return this object for method chaining 3119 */ setConfirmLabel(CharSequence label)3120 public WearableExtender setConfirmLabel(CharSequence label) { 3121 mConfirmLabel = label; 3122 return this; 3123 } 3124 3125 /** 3126 * Get the label to display to confirm that the action should be executed. 3127 * This is usually an imperative verb like "Send". 3128 * 3129 * @return the label to confirm the action should be executed 3130 */ getConfirmLabel()3131 public CharSequence getConfirmLabel() { 3132 return mConfirmLabel; 3133 } 3134 3135 /** 3136 * Set a label to display to cancel the action. 3137 * This is usually an imperative verb, like "Cancel". 3138 * 3139 * @param label the label to display to cancel the action 3140 * @return this object for method chaining 3141 */ setCancelLabel(CharSequence label)3142 public WearableExtender setCancelLabel(CharSequence label) { 3143 mCancelLabel = label; 3144 return this; 3145 } 3146 3147 /** 3148 * Get the label to display to cancel the action. 3149 * This is usually an imperative verb like "Cancel". 3150 * 3151 * @return the label to display to cancel the action 3152 */ getCancelLabel()3153 public CharSequence getCancelLabel() { 3154 return mCancelLabel; 3155 } 3156 3157 /** 3158 * Set a hint that this Action will launch an {@link Activity} directly, telling the 3159 * platform that it can generate the appropriate transitions. 3160 * @param hintLaunchesActivity {@code true} if the content intent will launch 3161 * an activity and transitions should be generated, false otherwise. 3162 * @return this object for method chaining 3163 */ setHintLaunchesActivity( boolean hintLaunchesActivity)3164 public WearableExtender setHintLaunchesActivity( 3165 boolean hintLaunchesActivity) { 3166 setFlag(FLAG_HINT_LAUNCHES_ACTIVITY, hintLaunchesActivity); 3167 return this; 3168 } 3169 3170 /** 3171 * Get a hint that this Action will launch an {@link Activity} directly, telling the 3172 * platform that it can generate the appropriate transitions 3173 * @return {@code true} if the content intent will launch an activity and transitions 3174 * should be generated, false otherwise. The default value is {@code false} if this was 3175 * never set. 3176 */ getHintLaunchesActivity()3177 public boolean getHintLaunchesActivity() { 3178 return (mFlags & FLAG_HINT_LAUNCHES_ACTIVITY) != 0; 3179 } 3180 3181 /** 3182 * Set a hint that this Action should be displayed inline - i.e. it will have a visual 3183 * representation directly on the notification surface in addition to the expanded 3184 * Notification 3185 * 3186 * @param hintDisplayInline {@code true} if action should be displayed inline, false 3187 * otherwise 3188 * @return this object for method chaining 3189 */ setHintDisplayActionInline( boolean hintDisplayInline)3190 public WearableExtender setHintDisplayActionInline( 3191 boolean hintDisplayInline) { 3192 setFlag(FLAG_HINT_DISPLAY_INLINE, hintDisplayInline); 3193 return this; 3194 } 3195 3196 /** 3197 * Get a hint that this Action should be displayed inline - i.e. it should have a 3198 * visual representation directly on the notification surface in addition to the 3199 * expanded Notification 3200 * 3201 * @return {@code true} if the Action should be displayed inline, {@code false} 3202 * otherwise. The default value is {@code false} if this was never set. 3203 */ getHintDisplayActionInline()3204 public boolean getHintDisplayActionInline() { 3205 return (mFlags & FLAG_HINT_DISPLAY_INLINE) != 0; 3206 } 3207 } 3208 } 3209 3210 3211 /** 3212 * Extender interface for use with {@link Builder#extend}. Extenders may be used to add 3213 * metadata or change options on a notification builder. 3214 */ 3215 public interface Extender { 3216 /** 3217 * Apply this extender to a notification builder. 3218 * @param builder the builder to be modified. 3219 * @return the build object for chaining. 3220 */ extend(Builder builder)3221 Builder extend(Builder builder); 3222 } 3223 3224 /** 3225 * Helper class to add wearable extensions to notifications. 3226 * <p class="note"> See 3227 * <a href="{@docRoot}wear/notifications/creating.html">Creating Notifications 3228 * for Android Wear</a> for more information on how to use this class. 3229 * <p> 3230 * To create a notification with wearable extensions: 3231 * <ol> 3232 * <li>Create a {@link NotificationCompat.Builder}, setting any desired 3233 * properties. 3234 * <li>Create a {@link NotificationCompat.WearableExtender}. 3235 * <li>Set wearable-specific properties using the 3236 * {@code add} and {@code set} methods of {@link NotificationCompat.WearableExtender}. 3237 * <li>Call {@link NotificationCompat.Builder#extend} to apply the extensions to a 3238 * notification. 3239 * <li>Post the notification to the notification 3240 * system with the {@code NotificationManagerCompat.notify(...)} methods 3241 * and not the {@code NotificationManager.notify(...)} methods. 3242 * </ol> 3243 * 3244 * <pre class="prettyprint"> 3245 * Notification notification = new NotificationCompat.Builder(mContext) 3246 * .setContentTitle("New mail from " + sender.toString()) 3247 * .setContentText(subject) 3248 * .setSmallIcon(R.drawable.new_mail) 3249 * .extend(new NotificationCompat.WearableExtender() 3250 * .setContentIcon(R.drawable.new_mail)) 3251 * .build(); 3252 * NotificationManagerCompat.from(mContext).notify(0, notification);</pre> 3253 * 3254 * <p>Wearable extensions can be accessed on an existing notification by using the 3255 * {@code WearableExtender(Notification)} constructor, 3256 * and then using the {@code get} methods to access values. 3257 * 3258 * <pre class="prettyprint"> 3259 * NotificationCompat.WearableExtender wearableExtender = 3260 * new NotificationCompat.WearableExtender(notification); 3261 * List<Notification> pages = wearableExtender.getPages();</pre> 3262 */ 3263 public static final class WearableExtender implements Extender { 3264 /** 3265 * Sentinel value for an action index that is unset. 3266 */ 3267 public static final int UNSET_ACTION_INDEX = -1; 3268 3269 /** 3270 * Size value for use with {@link #setCustomSizePreset} to show this notification with 3271 * default sizing. 3272 * <p>For custom display notifications created using {@link #setDisplayIntent}, 3273 * the default is {@link #SIZE_MEDIUM}. All other notifications size automatically based 3274 * on their content. 3275 */ 3276 public static final int SIZE_DEFAULT = 0; 3277 3278 /** 3279 * Size value for use with {@link #setCustomSizePreset} to show this notification 3280 * with an extra small size. 3281 * <p>This value is only applicable for custom display notifications created using 3282 * {@link #setDisplayIntent}. 3283 */ 3284 public static final int SIZE_XSMALL = 1; 3285 3286 /** 3287 * Size value for use with {@link #setCustomSizePreset} to show this notification 3288 * with a small size. 3289 * <p>This value is only applicable for custom display notifications created using 3290 * {@link #setDisplayIntent}. 3291 */ 3292 public static final int SIZE_SMALL = 2; 3293 3294 /** 3295 * Size value for use with {@link #setCustomSizePreset} to show this notification 3296 * with a medium size. 3297 * <p>This value is only applicable for custom display notifications created using 3298 * {@link #setDisplayIntent}. 3299 */ 3300 public static final int SIZE_MEDIUM = 3; 3301 3302 /** 3303 * Size value for use with {@link #setCustomSizePreset} to show this notification 3304 * with a large size. 3305 * <p>This value is only applicable for custom display notifications created using 3306 * {@link #setDisplayIntent}. 3307 */ 3308 public static final int SIZE_LARGE = 4; 3309 3310 /** 3311 * Size value for use with {@link #setCustomSizePreset} to show this notification 3312 * full screen. 3313 * <p>This value is only applicable for custom display notifications created using 3314 * {@link #setDisplayIntent}. 3315 */ 3316 public static final int SIZE_FULL_SCREEN = 5; 3317 3318 /** 3319 * Sentinel value for use with {@link #setHintScreenTimeout} to keep the screen on for a 3320 * short amount of time when this notification is displayed on the screen. This 3321 * is the default value. 3322 */ 3323 public static final int SCREEN_TIMEOUT_SHORT = 0; 3324 3325 /** 3326 * Sentinel value for use with {@link #setHintScreenTimeout} to keep the screen on 3327 * for a longer amount of time when this notification is displayed on the screen. 3328 */ 3329 public static final int SCREEN_TIMEOUT_LONG = -1; 3330 3331 /** Notification extra which contains wearable extensions */ 3332 private static final String EXTRA_WEARABLE_EXTENSIONS = "android.wearable.EXTENSIONS"; 3333 3334 // Keys within EXTRA_WEARABLE_EXTENSIONS for wearable options. 3335 private static final String KEY_ACTIONS = "actions"; 3336 private static final String KEY_FLAGS = "flags"; 3337 private static final String KEY_DISPLAY_INTENT = "displayIntent"; 3338 private static final String KEY_PAGES = "pages"; 3339 private static final String KEY_BACKGROUND = "background"; 3340 private static final String KEY_CONTENT_ICON = "contentIcon"; 3341 private static final String KEY_CONTENT_ICON_GRAVITY = "contentIconGravity"; 3342 private static final String KEY_CONTENT_ACTION_INDEX = "contentActionIndex"; 3343 private static final String KEY_CUSTOM_SIZE_PRESET = "customSizePreset"; 3344 private static final String KEY_CUSTOM_CONTENT_HEIGHT = "customContentHeight"; 3345 private static final String KEY_GRAVITY = "gravity"; 3346 private static final String KEY_HINT_SCREEN_TIMEOUT = "hintScreenTimeout"; 3347 private static final String KEY_DISMISSAL_ID = "dismissalId"; 3348 private static final String KEY_BRIDGE_TAG = "bridgeTag"; 3349 3350 // Flags bitwise-ored to mFlags 3351 private static final int FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE = 0x1; 3352 private static final int FLAG_HINT_HIDE_ICON = 1 << 1; 3353 private static final int FLAG_HINT_SHOW_BACKGROUND_ONLY = 1 << 2; 3354 private static final int FLAG_START_SCROLL_BOTTOM = 1 << 3; 3355 private static final int FLAG_HINT_AVOID_BACKGROUND_CLIPPING = 1 << 4; 3356 private static final int FLAG_BIG_PICTURE_AMBIENT = 1 << 5; 3357 private static final int FLAG_HINT_CONTENT_INTENT_LAUNCHES_ACTIVITY = 1 << 6; 3358 3359 // Default value for flags integer 3360 private static final int DEFAULT_FLAGS = FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE; 3361 3362 private static final int DEFAULT_CONTENT_ICON_GRAVITY = GravityCompat.END; 3363 private static final int DEFAULT_GRAVITY = Gravity.BOTTOM; 3364 3365 private ArrayList<Action> mActions = new ArrayList<Action>(); 3366 private int mFlags = DEFAULT_FLAGS; 3367 private PendingIntent mDisplayIntent; 3368 private ArrayList<Notification> mPages = new ArrayList<Notification>(); 3369 private Bitmap mBackground; 3370 private int mContentIcon; 3371 private int mContentIconGravity = DEFAULT_CONTENT_ICON_GRAVITY; 3372 private int mContentActionIndex = UNSET_ACTION_INDEX; 3373 private int mCustomSizePreset = SIZE_DEFAULT; 3374 private int mCustomContentHeight; 3375 private int mGravity = DEFAULT_GRAVITY; 3376 private int mHintScreenTimeout; 3377 private String mDismissalId; 3378 private String mBridgeTag; 3379 3380 /** 3381 * Create a {@link NotificationCompat.WearableExtender} with default 3382 * options. 3383 */ WearableExtender()3384 public WearableExtender() { 3385 } 3386 WearableExtender(Notification notification)3387 public WearableExtender(Notification notification) { 3388 Bundle extras = getExtras(notification); 3389 Bundle wearableBundle = extras != null ? extras.getBundle(EXTRA_WEARABLE_EXTENSIONS) 3390 : null; 3391 if (wearableBundle != null) { 3392 final ArrayList<Parcelable> parcelables = 3393 wearableBundle.getParcelableArrayList(KEY_ACTIONS); 3394 if (Build.VERSION.SDK_INT >= 16 && parcelables != null) { 3395 Action[] actions = new Action[parcelables.size()]; 3396 for (int i = 0; i < actions.length; i++) { 3397 if (Build.VERSION.SDK_INT >= 20) { 3398 actions[i] = NotificationCompat.getActionCompatFromAction( 3399 (Notification.Action) parcelables.get(i)); 3400 } else if (Build.VERSION.SDK_INT >= 16) { 3401 actions[i] = NotificationCompatJellybean.getActionFromBundle( 3402 (Bundle) parcelables.get(i)); 3403 } 3404 } 3405 Collections.addAll(mActions, (Action[]) actions); 3406 } 3407 3408 mFlags = wearableBundle.getInt(KEY_FLAGS, DEFAULT_FLAGS); 3409 mDisplayIntent = wearableBundle.getParcelable(KEY_DISPLAY_INTENT); 3410 3411 Notification[] pages = getNotificationArrayFromBundle( 3412 wearableBundle, KEY_PAGES); 3413 if (pages != null) { 3414 Collections.addAll(mPages, pages); 3415 } 3416 3417 mBackground = wearableBundle.getParcelable(KEY_BACKGROUND); 3418 mContentIcon = wearableBundle.getInt(KEY_CONTENT_ICON); 3419 mContentIconGravity = wearableBundle.getInt(KEY_CONTENT_ICON_GRAVITY, 3420 DEFAULT_CONTENT_ICON_GRAVITY); 3421 mContentActionIndex = wearableBundle.getInt(KEY_CONTENT_ACTION_INDEX, 3422 UNSET_ACTION_INDEX); 3423 mCustomSizePreset = wearableBundle.getInt(KEY_CUSTOM_SIZE_PRESET, 3424 SIZE_DEFAULT); 3425 mCustomContentHeight = wearableBundle.getInt(KEY_CUSTOM_CONTENT_HEIGHT); 3426 mGravity = wearableBundle.getInt(KEY_GRAVITY, DEFAULT_GRAVITY); 3427 mHintScreenTimeout = wearableBundle.getInt(KEY_HINT_SCREEN_TIMEOUT); 3428 mDismissalId = wearableBundle.getString(KEY_DISMISSAL_ID); 3429 mBridgeTag = wearableBundle.getString(KEY_BRIDGE_TAG); 3430 } 3431 } 3432 3433 /** 3434 * Apply wearable extensions to a notification that is being built. This is typically 3435 * called by the {@link NotificationCompat.Builder#extend} method of 3436 * {@link NotificationCompat.Builder}. 3437 */ 3438 @Override extend(NotificationCompat.Builder builder)3439 public NotificationCompat.Builder extend(NotificationCompat.Builder builder) { 3440 Bundle wearableBundle = new Bundle(); 3441 3442 if (!mActions.isEmpty()) { 3443 if (Build.VERSION.SDK_INT >= 16) { 3444 ArrayList<Parcelable> parcelables = new ArrayList<>(mActions.size()); 3445 for (Action action : mActions) { 3446 if (Build.VERSION.SDK_INT >= 20) { 3447 parcelables.add( 3448 WearableExtender.getActionFromActionCompat(action)); 3449 } else if (Build.VERSION.SDK_INT >= 16) { 3450 parcelables.add(NotificationCompatJellybean.getBundleForAction(action)); 3451 } 3452 } 3453 wearableBundle.putParcelableArrayList(KEY_ACTIONS, parcelables); 3454 } else { 3455 wearableBundle.putParcelableArrayList(KEY_ACTIONS, null); 3456 } 3457 } 3458 if (mFlags != DEFAULT_FLAGS) { 3459 wearableBundle.putInt(KEY_FLAGS, mFlags); 3460 } 3461 if (mDisplayIntent != null) { 3462 wearableBundle.putParcelable(KEY_DISPLAY_INTENT, mDisplayIntent); 3463 } 3464 if (!mPages.isEmpty()) { 3465 wearableBundle.putParcelableArray(KEY_PAGES, mPages.toArray( 3466 new Notification[mPages.size()])); 3467 } 3468 if (mBackground != null) { 3469 wearableBundle.putParcelable(KEY_BACKGROUND, mBackground); 3470 } 3471 if (mContentIcon != 0) { 3472 wearableBundle.putInt(KEY_CONTENT_ICON, mContentIcon); 3473 } 3474 if (mContentIconGravity != DEFAULT_CONTENT_ICON_GRAVITY) { 3475 wearableBundle.putInt(KEY_CONTENT_ICON_GRAVITY, mContentIconGravity); 3476 } 3477 if (mContentActionIndex != UNSET_ACTION_INDEX) { 3478 wearableBundle.putInt(KEY_CONTENT_ACTION_INDEX, 3479 mContentActionIndex); 3480 } 3481 if (mCustomSizePreset != SIZE_DEFAULT) { 3482 wearableBundle.putInt(KEY_CUSTOM_SIZE_PRESET, mCustomSizePreset); 3483 } 3484 if (mCustomContentHeight != 0) { 3485 wearableBundle.putInt(KEY_CUSTOM_CONTENT_HEIGHT, mCustomContentHeight); 3486 } 3487 if (mGravity != DEFAULT_GRAVITY) { 3488 wearableBundle.putInt(KEY_GRAVITY, mGravity); 3489 } 3490 if (mHintScreenTimeout != 0) { 3491 wearableBundle.putInt(KEY_HINT_SCREEN_TIMEOUT, mHintScreenTimeout); 3492 } 3493 if (mDismissalId != null) { 3494 wearableBundle.putString(KEY_DISMISSAL_ID, mDismissalId); 3495 } 3496 if (mBridgeTag != null) { 3497 wearableBundle.putString(KEY_BRIDGE_TAG, mBridgeTag); 3498 } 3499 3500 builder.getExtras().putBundle(EXTRA_WEARABLE_EXTENSIONS, wearableBundle); 3501 return builder; 3502 } 3503 3504 @RequiresApi(20) getActionFromActionCompat(Action actionCompat)3505 private static Notification.Action getActionFromActionCompat(Action actionCompat) { 3506 Notification.Action.Builder actionBuilder = new Notification.Action.Builder( 3507 actionCompat.getIcon(), actionCompat.getTitle(), 3508 actionCompat.getActionIntent()); 3509 Bundle actionExtras; 3510 if (actionCompat.getExtras() != null) { 3511 actionExtras = new Bundle(actionCompat.getExtras()); 3512 } else { 3513 actionExtras = new Bundle(); 3514 } 3515 actionExtras.putBoolean(NotificationCompatJellybean.EXTRA_ALLOW_GENERATED_REPLIES, 3516 actionCompat.getAllowGeneratedReplies()); 3517 if (Build.VERSION.SDK_INT >= 24) { 3518 actionBuilder.setAllowGeneratedReplies(actionCompat.getAllowGeneratedReplies()); 3519 } 3520 actionBuilder.addExtras(actionExtras); 3521 RemoteInput[] remoteInputCompats = actionCompat.getRemoteInputs(); 3522 if (remoteInputCompats != null) { 3523 android.app.RemoteInput[] remoteInputs = RemoteInput.fromCompat(remoteInputCompats); 3524 for (android.app.RemoteInput remoteInput : remoteInputs) { 3525 actionBuilder.addRemoteInput(remoteInput); 3526 } 3527 } 3528 return actionBuilder.build(); 3529 } 3530 3531 @Override clone()3532 public WearableExtender clone() { 3533 WearableExtender that = new WearableExtender(); 3534 that.mActions = new ArrayList<>(this.mActions); 3535 that.mFlags = this.mFlags; 3536 that.mDisplayIntent = this.mDisplayIntent; 3537 that.mPages = new ArrayList<>(this.mPages); 3538 that.mBackground = this.mBackground; 3539 that.mContentIcon = this.mContentIcon; 3540 that.mContentIconGravity = this.mContentIconGravity; 3541 that.mContentActionIndex = this.mContentActionIndex; 3542 that.mCustomSizePreset = this.mCustomSizePreset; 3543 that.mCustomContentHeight = this.mCustomContentHeight; 3544 that.mGravity = this.mGravity; 3545 that.mHintScreenTimeout = this.mHintScreenTimeout; 3546 that.mDismissalId = this.mDismissalId; 3547 that.mBridgeTag = this.mBridgeTag; 3548 return that; 3549 } 3550 3551 /** 3552 * Add a wearable action to this notification. 3553 * 3554 * <p>When wearable actions are added using this method, the set of actions that 3555 * show on a wearable device splits from devices that only show actions added 3556 * using {@link NotificationCompat.Builder#addAction}. This allows for customization 3557 * of which actions display on different devices. 3558 * 3559 * @param action the action to add to this notification 3560 * @return this object for method chaining 3561 * @see NotificationCompat.Action 3562 */ addAction(Action action)3563 public WearableExtender addAction(Action action) { 3564 mActions.add(action); 3565 return this; 3566 } 3567 3568 /** 3569 * Adds wearable actions to this notification. 3570 * 3571 * <p>When wearable actions are added using this method, the set of actions that 3572 * show on a wearable device splits from devices that only show actions added 3573 * using {@link NotificationCompat.Builder#addAction}. This allows for customization 3574 * of which actions display on different devices. 3575 * 3576 * @param actions the actions to add to this notification 3577 * @return this object for method chaining 3578 * @see NotificationCompat.Action 3579 */ addActions(List<Action> actions)3580 public WearableExtender addActions(List<Action> actions) { 3581 mActions.addAll(actions); 3582 return this; 3583 } 3584 3585 /** 3586 * Clear all wearable actions present on this builder. 3587 * @return this object for method chaining. 3588 * @see #addAction 3589 */ clearActions()3590 public WearableExtender clearActions() { 3591 mActions.clear(); 3592 return this; 3593 } 3594 3595 /** 3596 * Get the wearable actions present on this notification. 3597 */ getActions()3598 public List<Action> getActions() { 3599 return mActions; 3600 } 3601 3602 /** 3603 * Set an intent to launch inside of an activity view when displaying 3604 * this notification. The {@link PendingIntent} provided should be for an activity. 3605 * 3606 * <pre class="prettyprint"> 3607 * Intent displayIntent = new Intent(context, MyDisplayActivity.class); 3608 * PendingIntent displayPendingIntent = PendingIntent.getActivity(context, 3609 * 0, displayIntent, PendingIntent.FLAG_UPDATE_CURRENT); 3610 * Notification notification = new NotificationCompat.Builder(context) 3611 * .extend(new NotificationCompat.WearableExtender() 3612 * .setDisplayIntent(displayPendingIntent) 3613 * .setCustomSizePreset(NotificationCompat.WearableExtender.SIZE_MEDIUM)) 3614 * .build();</pre> 3615 * 3616 * <p>The activity to launch needs to allow embedding, must be exported, and 3617 * should have an empty task affinity. It is also recommended to use the device 3618 * default light theme. 3619 * 3620 * <p>Example AndroidManifest.xml entry: 3621 * <pre class="prettyprint"> 3622 * <activity android:name="com.example.MyDisplayActivity" 3623 * android:exported="true" 3624 * android:allowEmbedded="true" 3625 * android:taskAffinity="" 3626 * android:theme="@android:style/Theme.DeviceDefault.Light" /></pre> 3627 * 3628 * @param intent the {@link PendingIntent} for an activity 3629 * @return this object for method chaining 3630 * @see NotificationCompat.WearableExtender#getDisplayIntent 3631 */ setDisplayIntent(PendingIntent intent)3632 public WearableExtender setDisplayIntent(PendingIntent intent) { 3633 mDisplayIntent = intent; 3634 return this; 3635 } 3636 3637 /** 3638 * Get the intent to launch inside of an activity view when displaying this 3639 * notification. This {@code PendingIntent} should be for an activity. 3640 */ getDisplayIntent()3641 public PendingIntent getDisplayIntent() { 3642 return mDisplayIntent; 3643 } 3644 3645 /** 3646 * Add an additional page of content to display with this notification. The current 3647 * notification forms the first page, and pages added using this function form 3648 * subsequent pages. This field can be used to separate a notification into multiple 3649 * sections. 3650 * 3651 * @param page the notification to add as another page 3652 * @return this object for method chaining 3653 * @see NotificationCompat.WearableExtender#getPages 3654 */ addPage(Notification page)3655 public WearableExtender addPage(Notification page) { 3656 mPages.add(page); 3657 return this; 3658 } 3659 3660 /** 3661 * Add additional pages of content to display with this notification. The current 3662 * notification forms the first page, and pages added using this function form 3663 * subsequent pages. This field can be used to separate a notification into multiple 3664 * sections. 3665 * 3666 * @param pages a list of notifications 3667 * @return this object for method chaining 3668 * @see NotificationCompat.WearableExtender#getPages 3669 */ addPages(List<Notification> pages)3670 public WearableExtender addPages(List<Notification> pages) { 3671 mPages.addAll(pages); 3672 return this; 3673 } 3674 3675 /** 3676 * Clear all additional pages present on this builder. 3677 * @return this object for method chaining. 3678 * @see #addPage 3679 */ clearPages()3680 public WearableExtender clearPages() { 3681 mPages.clear(); 3682 return this; 3683 } 3684 3685 /** 3686 * Get the array of additional pages of content for displaying this notification. The 3687 * current notification forms the first page, and elements within this array form 3688 * subsequent pages. This field can be used to separate a notification into multiple 3689 * sections. 3690 * @return the pages for this notification 3691 */ getPages()3692 public List<Notification> getPages() { 3693 return mPages; 3694 } 3695 3696 /** 3697 * Set a background image to be displayed behind the notification content. 3698 * Contrary to the {@link NotificationCompat.BigPictureStyle}, this background 3699 * will work with any notification style. 3700 * 3701 * @param background the background bitmap 3702 * @return this object for method chaining 3703 * @see NotificationCompat.WearableExtender#getBackground 3704 */ setBackground(Bitmap background)3705 public WearableExtender setBackground(Bitmap background) { 3706 mBackground = background; 3707 return this; 3708 } 3709 3710 /** 3711 * Get a background image to be displayed behind the notification content. 3712 * Contrary to the {@link NotificationCompat.BigPictureStyle}, this background 3713 * will work with any notification style. 3714 * 3715 * @return the background image 3716 * @see NotificationCompat.WearableExtender#setBackground 3717 */ getBackground()3718 public Bitmap getBackground() { 3719 return mBackground; 3720 } 3721 3722 /** 3723 * Set an icon that goes with the content of this notification. 3724 */ setContentIcon(int icon)3725 public WearableExtender setContentIcon(int icon) { 3726 mContentIcon = icon; 3727 return this; 3728 } 3729 3730 /** 3731 * Get an icon that goes with the content of this notification. 3732 */ getContentIcon()3733 public int getContentIcon() { 3734 return mContentIcon; 3735 } 3736 3737 /** 3738 * Set the gravity that the content icon should have within the notification display. 3739 * Supported values include {@link android.view.Gravity#START} and 3740 * {@link android.view.Gravity#END}. The default value is {@link android.view.Gravity#END}. 3741 * @see #setContentIcon 3742 */ setContentIconGravity(int contentIconGravity)3743 public WearableExtender setContentIconGravity(int contentIconGravity) { 3744 mContentIconGravity = contentIconGravity; 3745 return this; 3746 } 3747 3748 /** 3749 * Get the gravity that the content icon should have within the notification display. 3750 * Supported values include {@link android.view.Gravity#START} and 3751 * {@link android.view.Gravity#END}. The default value is {@link android.view.Gravity#END}. 3752 * @see #getContentIcon 3753 */ getContentIconGravity()3754 public int getContentIconGravity() { 3755 return mContentIconGravity; 3756 } 3757 3758 /** 3759 * Set an action from this notification's actions to be clickable with the content of 3760 * this notification. This action will no longer display separately from the 3761 * notification's content. 3762 * 3763 * <p>For notifications with multiple pages, child pages can also have content actions 3764 * set, although the list of available actions comes from the main notification and not 3765 * from the child page's notification. 3766 * 3767 * @param actionIndex The index of the action to hoist onto the current notification page. 3768 * If wearable actions were added to the main notification, this index 3769 * will apply to that list, otherwise it will apply to the regular 3770 * actions list. 3771 */ setContentAction(int actionIndex)3772 public WearableExtender setContentAction(int actionIndex) { 3773 mContentActionIndex = actionIndex; 3774 return this; 3775 } 3776 3777 /** 3778 * Get the index of the notification action, if any, that was specified as being clickable 3779 * with the content of this notification. This action will no longer display separately 3780 * from the notification's content. 3781 * 3782 * <p>For notifications with multiple pages, child pages can also have content actions 3783 * set, although the list of available actions comes from the main notification and not 3784 * from the child page's notification. 3785 * 3786 * <p>If wearable specific actions were added to the main notification, this index will 3787 * apply to that list, otherwise it will apply to the regular actions list. 3788 * 3789 * @return the action index or {@link #UNSET_ACTION_INDEX} if no action was selected. 3790 */ getContentAction()3791 public int getContentAction() { 3792 return mContentActionIndex; 3793 } 3794 3795 /** 3796 * Set the gravity that this notification should have within the available viewport space. 3797 * Supported values include {@link android.view.Gravity#TOP}, 3798 * {@link android.view.Gravity#CENTER_VERTICAL} and {@link android.view.Gravity#BOTTOM}. 3799 * The default value is {@link android.view.Gravity#BOTTOM}. 3800 */ setGravity(int gravity)3801 public WearableExtender setGravity(int gravity) { 3802 mGravity = gravity; 3803 return this; 3804 } 3805 3806 /** 3807 * Get the gravity that this notification should have within the available viewport space. 3808 * Supported values include {@link android.view.Gravity#TOP}, 3809 * {@link android.view.Gravity#CENTER_VERTICAL} and {@link android.view.Gravity#BOTTOM}. 3810 * The default value is {@link android.view.Gravity#BOTTOM}. 3811 */ getGravity()3812 public int getGravity() { 3813 return mGravity; 3814 } 3815 3816 /** 3817 * Set the custom size preset for the display of this notification out of the available 3818 * presets found in {@link NotificationCompat.WearableExtender}, e.g. 3819 * {@link #SIZE_LARGE}. 3820 * <p>Some custom size presets are only applicable for custom display notifications created 3821 * using {@link NotificationCompat.WearableExtender#setDisplayIntent}. Check the 3822 * documentation for the preset in question. See also 3823 * {@link #setCustomContentHeight} and {@link #getCustomSizePreset}. 3824 */ setCustomSizePreset(int sizePreset)3825 public WearableExtender setCustomSizePreset(int sizePreset) { 3826 mCustomSizePreset = sizePreset; 3827 return this; 3828 } 3829 3830 /** 3831 * Get the custom size preset for the display of this notification out of the available 3832 * presets found in {@link NotificationCompat.WearableExtender}, e.g. 3833 * {@link #SIZE_LARGE}. 3834 * <p>Some custom size presets are only applicable for custom display notifications created 3835 * using {@link #setDisplayIntent}. Check the documentation for the preset in question. 3836 * See also {@link #setCustomContentHeight} and {@link #setCustomSizePreset}. 3837 */ getCustomSizePreset()3838 public int getCustomSizePreset() { 3839 return mCustomSizePreset; 3840 } 3841 3842 /** 3843 * Set the custom height in pixels for the display of this notification's content. 3844 * <p>This option is only available for custom display notifications created 3845 * using {@link NotificationCompat.WearableExtender#setDisplayIntent}. See also 3846 * {@link NotificationCompat.WearableExtender#setCustomSizePreset} and 3847 * {@link #getCustomContentHeight}. 3848 */ setCustomContentHeight(int height)3849 public WearableExtender setCustomContentHeight(int height) { 3850 mCustomContentHeight = height; 3851 return this; 3852 } 3853 3854 /** 3855 * Get the custom height in pixels for the display of this notification's content. 3856 * <p>This option is only available for custom display notifications created 3857 * using {@link #setDisplayIntent}. See also {@link #setCustomSizePreset} and 3858 * {@link #setCustomContentHeight}. 3859 */ getCustomContentHeight()3860 public int getCustomContentHeight() { 3861 return mCustomContentHeight; 3862 } 3863 3864 /** 3865 * Set whether the scrolling position for the contents of this notification should start 3866 * at the bottom of the contents instead of the top when the contents are too long to 3867 * display within the screen. Default is false (start scroll at the top). 3868 */ setStartScrollBottom(boolean startScrollBottom)3869 public WearableExtender setStartScrollBottom(boolean startScrollBottom) { 3870 setFlag(FLAG_START_SCROLL_BOTTOM, startScrollBottom); 3871 return this; 3872 } 3873 3874 /** 3875 * Get whether the scrolling position for the contents of this notification should start 3876 * at the bottom of the contents instead of the top when the contents are too long to 3877 * display within the screen. Default is false (start scroll at the top). 3878 */ getStartScrollBottom()3879 public boolean getStartScrollBottom() { 3880 return (mFlags & FLAG_START_SCROLL_BOTTOM) != 0; 3881 } 3882 3883 /** 3884 * Set whether the content intent is available when the wearable device is not connected 3885 * to a companion device. The user can still trigger this intent when the wearable device 3886 * is offline, but a visual hint will indicate that the content intent may not be available. 3887 * Defaults to true. 3888 */ setContentIntentAvailableOffline( boolean contentIntentAvailableOffline)3889 public WearableExtender setContentIntentAvailableOffline( 3890 boolean contentIntentAvailableOffline) { 3891 setFlag(FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE, contentIntentAvailableOffline); 3892 return this; 3893 } 3894 3895 /** 3896 * Get whether the content intent is available when the wearable device is not connected 3897 * to a companion device. The user can still trigger this intent when the wearable device 3898 * is offline, but a visual hint will indicate that the content intent may not be available. 3899 * Defaults to true. 3900 */ getContentIntentAvailableOffline()3901 public boolean getContentIntentAvailableOffline() { 3902 return (mFlags & FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE) != 0; 3903 } 3904 3905 /** 3906 * Set a hint that this notification's icon should not be displayed. 3907 * @param hintHideIcon {@code true} to hide the icon, {@code false} otherwise. 3908 * @return this object for method chaining 3909 */ setHintHideIcon(boolean hintHideIcon)3910 public WearableExtender setHintHideIcon(boolean hintHideIcon) { 3911 setFlag(FLAG_HINT_HIDE_ICON, hintHideIcon); 3912 return this; 3913 } 3914 3915 /** 3916 * Get a hint that this notification's icon should not be displayed. 3917 * @return {@code true} if this icon should not be displayed, false otherwise. 3918 * The default value is {@code false} if this was never set. 3919 */ getHintHideIcon()3920 public boolean getHintHideIcon() { 3921 return (mFlags & FLAG_HINT_HIDE_ICON) != 0; 3922 } 3923 3924 /** 3925 * Set a visual hint that only the background image of this notification should be 3926 * displayed, and other semantic content should be hidden. This hint is only applicable 3927 * to sub-pages added using {@link #addPage}. 3928 */ setHintShowBackgroundOnly(boolean hintShowBackgroundOnly)3929 public WearableExtender setHintShowBackgroundOnly(boolean hintShowBackgroundOnly) { 3930 setFlag(FLAG_HINT_SHOW_BACKGROUND_ONLY, hintShowBackgroundOnly); 3931 return this; 3932 } 3933 3934 /** 3935 * Get a visual hint that only the background image of this notification should be 3936 * displayed, and other semantic content should be hidden. This hint is only applicable 3937 * to sub-pages added using {@link NotificationCompat.WearableExtender#addPage}. 3938 */ getHintShowBackgroundOnly()3939 public boolean getHintShowBackgroundOnly() { 3940 return (mFlags & FLAG_HINT_SHOW_BACKGROUND_ONLY) != 0; 3941 } 3942 3943 /** 3944 * Set a hint that this notification's background should not be clipped if possible, 3945 * and should instead be resized to fully display on the screen, retaining the aspect 3946 * ratio of the image. This can be useful for images like barcodes or qr codes. 3947 * @param hintAvoidBackgroundClipping {@code true} to avoid clipping if possible. 3948 * @return this object for method chaining 3949 */ setHintAvoidBackgroundClipping( boolean hintAvoidBackgroundClipping)3950 public WearableExtender setHintAvoidBackgroundClipping( 3951 boolean hintAvoidBackgroundClipping) { 3952 setFlag(FLAG_HINT_AVOID_BACKGROUND_CLIPPING, hintAvoidBackgroundClipping); 3953 return this; 3954 } 3955 3956 /** 3957 * Get a hint that this notification's background should not be clipped if possible, 3958 * and should instead be resized to fully display on the screen, retaining the aspect 3959 * ratio of the image. This can be useful for images like barcodes or qr codes. 3960 * @return {@code true} if it's ok if the background is clipped on the screen, false 3961 * otherwise. The default value is {@code false} if this was never set. 3962 */ getHintAvoidBackgroundClipping()3963 public boolean getHintAvoidBackgroundClipping() { 3964 return (mFlags & FLAG_HINT_AVOID_BACKGROUND_CLIPPING) != 0; 3965 } 3966 3967 /** 3968 * Set a hint that the screen should remain on for at least this duration when 3969 * this notification is displayed on the screen. 3970 * @param timeout The requested screen timeout in milliseconds. Can also be either 3971 * {@link #SCREEN_TIMEOUT_SHORT} or {@link #SCREEN_TIMEOUT_LONG}. 3972 * @return this object for method chaining 3973 */ setHintScreenTimeout(int timeout)3974 public WearableExtender setHintScreenTimeout(int timeout) { 3975 mHintScreenTimeout = timeout; 3976 return this; 3977 } 3978 3979 /** 3980 * Get the duration, in milliseconds, that the screen should remain on for 3981 * when this notification is displayed. 3982 * @return the duration in milliseconds if > 0, or either one of the sentinel values 3983 * {@link #SCREEN_TIMEOUT_SHORT} or {@link #SCREEN_TIMEOUT_LONG}. 3984 */ getHintScreenTimeout()3985 public int getHintScreenTimeout() { 3986 return mHintScreenTimeout; 3987 } 3988 3989 /** 3990 * Set a hint that this notification's {@link BigPictureStyle} (if present) should be 3991 * converted to low-bit and displayed in ambient mode, especially useful for barcodes and 3992 * qr codes, as well as other simple black-and-white tickets. 3993 * @param hintAmbientBigPicture {@code true} to enable converstion and ambient. 3994 * @return this object for method chaining 3995 */ setHintAmbientBigPicture(boolean hintAmbientBigPicture)3996 public WearableExtender setHintAmbientBigPicture(boolean hintAmbientBigPicture) { 3997 setFlag(FLAG_BIG_PICTURE_AMBIENT, hintAmbientBigPicture); 3998 return this; 3999 } 4000 4001 /** 4002 * Get a hint that this notification's {@link BigPictureStyle} (if present) should be 4003 * converted to low-bit and displayed in ambient mode, especially useful for barcodes and 4004 * qr codes, as well as other simple black-and-white tickets. 4005 * @return {@code true} if it should be displayed in ambient, false otherwise 4006 * otherwise. The default value is {@code false} if this was never set. 4007 */ getHintAmbientBigPicture()4008 public boolean getHintAmbientBigPicture() { 4009 return (mFlags & FLAG_BIG_PICTURE_AMBIENT) != 0; 4010 } 4011 4012 /** 4013 * Set a hint that this notification's content intent will launch an {@link Activity} 4014 * directly, telling the platform that it can generate the appropriate transitions. 4015 * @param hintContentIntentLaunchesActivity {@code true} if the content intent will launch 4016 * an activity and transitions should be generated, false otherwise. 4017 * @return this object for method chaining 4018 */ setHintContentIntentLaunchesActivity( boolean hintContentIntentLaunchesActivity)4019 public WearableExtender setHintContentIntentLaunchesActivity( 4020 boolean hintContentIntentLaunchesActivity) { 4021 setFlag(FLAG_HINT_CONTENT_INTENT_LAUNCHES_ACTIVITY, hintContentIntentLaunchesActivity); 4022 return this; 4023 } 4024 4025 /** 4026 * Get a hint that this notification's content intent will launch an {@link Activity} 4027 * directly, telling the platform that it can generate the appropriate transitions 4028 * @return {@code true} if the content intent will launch an activity and transitions should 4029 * be generated, false otherwise. The default value is {@code false} if this was never set. 4030 */ getHintContentIntentLaunchesActivity()4031 public boolean getHintContentIntentLaunchesActivity() { 4032 return (mFlags & FLAG_HINT_CONTENT_INTENT_LAUNCHES_ACTIVITY) != 0; 4033 } 4034 4035 /** 4036 * Sets the dismissal id for this notification. If a notification is posted with a 4037 * dismissal id, then when that notification is canceled, notifications on other wearables 4038 * and the paired Android phone having that same dismissal id will also be canceled. See 4039 * <a href="{@docRoot}wear/notifications/index.html">Adding Wearable Features to 4040 * Notifications</a> for more information. 4041 * @param dismissalId the dismissal id of the notification. 4042 * @return this object for method chaining 4043 */ setDismissalId(String dismissalId)4044 public WearableExtender setDismissalId(String dismissalId) { 4045 mDismissalId = dismissalId; 4046 return this; 4047 } 4048 4049 /** 4050 * Returns the dismissal id of the notification. 4051 * @return the dismissal id of the notification or null if it has not been set. 4052 */ getDismissalId()4053 public String getDismissalId() { 4054 return mDismissalId; 4055 } 4056 4057 /** 4058 * Sets a bridge tag for this notification. A bridge tag can be set for notifications 4059 * posted from a phone to provide finer-grained control on what notifications are bridged 4060 * to wearables. See <a href="{@docRoot}wear/notifications/index.html">Adding Wearable 4061 * Features to Notifications</a> for more information. 4062 * @param bridgeTag the bridge tag of the notification. 4063 * @return this object for method chaining 4064 */ setBridgeTag(String bridgeTag)4065 public WearableExtender setBridgeTag(String bridgeTag) { 4066 mBridgeTag = bridgeTag; 4067 return this; 4068 } 4069 4070 /** 4071 * Returns the bridge tag of the notification. 4072 * @return the bridge tag or null if not present. 4073 */ getBridgeTag()4074 public String getBridgeTag() { 4075 return mBridgeTag; 4076 } 4077 setFlag(int mask, boolean value)4078 private void setFlag(int mask, boolean value) { 4079 if (value) { 4080 mFlags |= mask; 4081 } else { 4082 mFlags &= ~mask; 4083 } 4084 } 4085 } 4086 4087 /** 4088 * <p>Helper class to add Android Auto extensions to notifications. To create a notification 4089 * with car extensions: 4090 * 4091 * <ol> 4092 * <li>Create an {@link NotificationCompat.Builder}, setting any desired 4093 * properties. 4094 * <li>Create a {@link CarExtender}. 4095 * <li>Set car-specific properties using the {@code add} and {@code set} methods of 4096 * {@link CarExtender}. 4097 * <li>Call {@link android.support.v4.app.NotificationCompat.Builder#extend(NotificationCompat.Extender)} 4098 * to apply the extensions to a notification. 4099 * <li>Post the notification to the notification system with the 4100 * {@code NotificationManagerCompat.notify(...)} methods and not the 4101 * {@code NotificationManager.notify(...)} methods. 4102 * </ol> 4103 * 4104 * <pre class="prettyprint"> 4105 * Notification notification = new NotificationCompat.Builder(context) 4106 * ... 4107 * .extend(new CarExtender() 4108 * .set*(...)) 4109 * .build(); 4110 * </pre> 4111 * 4112 * <p>Car extensions can be accessed on an existing notification by using the 4113 * {@code CarExtender(Notification)} constructor, and then using the {@code get} methods 4114 * to access values. 4115 */ 4116 public static final class CarExtender implements Extender { 4117 private static final String EXTRA_CAR_EXTENDER = "android.car.EXTENSIONS"; 4118 private static final String EXTRA_LARGE_ICON = "large_icon"; 4119 private static final String EXTRA_CONVERSATION = "car_conversation"; 4120 private static final String EXTRA_COLOR = "app_color"; 4121 4122 private static final String KEY_AUTHOR = "author"; 4123 private static final String KEY_TEXT = "text"; 4124 private static final String KEY_MESSAGES = "messages"; 4125 private static final String KEY_REMOTE_INPUT = "remote_input"; 4126 private static final String KEY_ON_REPLY = "on_reply"; 4127 private static final String KEY_ON_READ = "on_read"; 4128 private static final String KEY_PARTICIPANTS = "participants"; 4129 private static final String KEY_TIMESTAMP = "timestamp"; 4130 4131 private Bitmap mLargeIcon; 4132 private UnreadConversation mUnreadConversation; 4133 private int mColor = NotificationCompat.COLOR_DEFAULT; 4134 4135 /** 4136 * Create a {@link CarExtender} with default options. 4137 */ CarExtender()4138 public CarExtender() { 4139 } 4140 4141 /** 4142 * Create a {@link CarExtender} from the CarExtender options of an existing Notification. 4143 * 4144 * @param notification The notification from which to copy options. 4145 */ CarExtender(Notification notification)4146 public CarExtender(Notification notification) { 4147 if (Build.VERSION.SDK_INT < 21) { 4148 return; 4149 } 4150 4151 Bundle carBundle = getExtras(notification) == null 4152 ? null : getExtras(notification).getBundle(EXTRA_CAR_EXTENDER); 4153 if (carBundle != null) { 4154 mLargeIcon = carBundle.getParcelable(EXTRA_LARGE_ICON); 4155 mColor = carBundle.getInt(EXTRA_COLOR, NotificationCompat.COLOR_DEFAULT); 4156 4157 Bundle b = carBundle.getBundle(EXTRA_CONVERSATION); 4158 mUnreadConversation = getUnreadConversationFromBundle(b); 4159 } 4160 } 4161 4162 @RequiresApi(21) getUnreadConversationFromBundle(@ullable Bundle b)4163 private static UnreadConversation getUnreadConversationFromBundle(@Nullable Bundle b) { 4164 if (b == null) { 4165 return null; 4166 } 4167 Parcelable[] parcelableMessages = b.getParcelableArray(KEY_MESSAGES); 4168 String[] messages = null; 4169 if (parcelableMessages != null) { 4170 String[] tmp = new String[parcelableMessages.length]; 4171 boolean success = true; 4172 for (int i = 0; i < tmp.length; i++) { 4173 if (!(parcelableMessages[i] instanceof Bundle)) { 4174 success = false; 4175 break; 4176 } 4177 tmp[i] = ((Bundle) parcelableMessages[i]).getString(KEY_TEXT); 4178 if (tmp[i] == null) { 4179 success = false; 4180 break; 4181 } 4182 } 4183 if (success) { 4184 messages = tmp; 4185 } else { 4186 return null; 4187 } 4188 } 4189 4190 PendingIntent onRead = b.getParcelable(KEY_ON_READ); 4191 PendingIntent onReply = b.getParcelable(KEY_ON_REPLY); 4192 4193 android.app.RemoteInput remoteInput = b.getParcelable(KEY_REMOTE_INPUT); 4194 4195 String[] participants = b.getStringArray(KEY_PARTICIPANTS); 4196 if (participants == null || participants.length != 1) { 4197 return null; 4198 } 4199 4200 RemoteInput remoteInputCompat = remoteInput != null 4201 ? new RemoteInput(remoteInput.getResultKey(), 4202 remoteInput.getLabel(), 4203 remoteInput.getChoices(), 4204 remoteInput.getAllowFreeFormInput(), 4205 remoteInput.getExtras(), 4206 null /* allowedDataTypes */) 4207 : null; 4208 4209 return new UnreadConversation(messages, remoteInputCompat, onReply, 4210 onRead, participants, b.getLong(KEY_TIMESTAMP)); 4211 } 4212 4213 @RequiresApi(21) getBundleForUnreadConversation(@onNull UnreadConversation uc)4214 private static Bundle getBundleForUnreadConversation(@NonNull UnreadConversation uc) { 4215 Bundle b = new Bundle(); 4216 String author = null; 4217 if (uc.getParticipants() != null && uc.getParticipants().length > 1) { 4218 author = uc.getParticipants()[0]; 4219 } 4220 Parcelable[] messages = new Parcelable[uc.getMessages().length]; 4221 for (int i = 0; i < messages.length; i++) { 4222 Bundle m = new Bundle(); 4223 m.putString(KEY_TEXT, uc.getMessages()[i]); 4224 m.putString(KEY_AUTHOR, author); 4225 messages[i] = m; 4226 } 4227 b.putParcelableArray(KEY_MESSAGES, messages); 4228 RemoteInput remoteInputCompat = uc.getRemoteInput(); 4229 if (remoteInputCompat != null) { 4230 android.app.RemoteInput remoteInput = 4231 new android.app.RemoteInput.Builder(remoteInputCompat.getResultKey()) 4232 .setLabel(remoteInputCompat.getLabel()) 4233 .setChoices(remoteInputCompat.getChoices()) 4234 .setAllowFreeFormInput(remoteInputCompat.getAllowFreeFormInput()) 4235 .addExtras(remoteInputCompat.getExtras()) 4236 .build(); 4237 b.putParcelable(KEY_REMOTE_INPUT, remoteInput); 4238 } 4239 b.putParcelable(KEY_ON_REPLY, uc.getReplyPendingIntent()); 4240 b.putParcelable(KEY_ON_READ, uc.getReadPendingIntent()); 4241 b.putStringArray(KEY_PARTICIPANTS, uc.getParticipants()); 4242 b.putLong(KEY_TIMESTAMP, uc.getLatestTimestamp()); 4243 return b; 4244 } 4245 4246 /** 4247 * Apply car extensions to a notification that is being built. This is typically called by 4248 * the {@link android.support.v4.app.NotificationCompat.Builder#extend(NotificationCompat.Extender)} 4249 * method of {@link NotificationCompat.Builder}. 4250 */ 4251 @Override extend(NotificationCompat.Builder builder)4252 public NotificationCompat.Builder extend(NotificationCompat.Builder builder) { 4253 if (Build.VERSION.SDK_INT < 21) { 4254 return builder; 4255 } 4256 4257 Bundle carExtensions = new Bundle(); 4258 4259 if (mLargeIcon != null) { 4260 carExtensions.putParcelable(EXTRA_LARGE_ICON, mLargeIcon); 4261 } 4262 if (mColor != NotificationCompat.COLOR_DEFAULT) { 4263 carExtensions.putInt(EXTRA_COLOR, mColor); 4264 } 4265 4266 if (mUnreadConversation != null) { 4267 Bundle b = getBundleForUnreadConversation(mUnreadConversation); 4268 carExtensions.putBundle(EXTRA_CONVERSATION, b); 4269 } 4270 4271 builder.getExtras().putBundle(EXTRA_CAR_EXTENDER, carExtensions); 4272 return builder; 4273 } 4274 4275 /** 4276 * Sets the accent color to use when Android Auto presents the notification. 4277 * 4278 * Android Auto uses the color set with {@link android.support.v4.app.NotificationCompat.Builder#setColor(int)} 4279 * to accent the displayed notification. However, not all colors are acceptable in an 4280 * automotive setting. This method can be used to override the color provided in the 4281 * notification in such a situation. 4282 */ setColor(@olorInt int color)4283 public CarExtender setColor(@ColorInt int color) { 4284 mColor = color; 4285 return this; 4286 } 4287 4288 /** 4289 * Gets the accent color. 4290 * 4291 * @see #setColor 4292 */ 4293 @ColorInt getColor()4294 public int getColor() { 4295 return mColor; 4296 } 4297 4298 /** 4299 * Sets the large icon of the car notification. 4300 * 4301 * If no large icon is set in the extender, Android Auto will display the icon 4302 * specified by {@link android.support.v4.app.NotificationCompat.Builder#setLargeIcon(android.graphics.Bitmap)} 4303 * 4304 * @param largeIcon The large icon to use in the car notification. 4305 * @return This object for method chaining. 4306 */ setLargeIcon(Bitmap largeIcon)4307 public CarExtender setLargeIcon(Bitmap largeIcon) { 4308 mLargeIcon = largeIcon; 4309 return this; 4310 } 4311 4312 /** 4313 * Gets the large icon used in this car notification, or null if no icon has been set. 4314 * 4315 * @return The large icon for the car notification. 4316 * @see CarExtender#setLargeIcon 4317 */ getLargeIcon()4318 public Bitmap getLargeIcon() { 4319 return mLargeIcon; 4320 } 4321 4322 /** 4323 * Sets the unread conversation in a message notification. 4324 * 4325 * @param unreadConversation The unread part of the conversation this notification conveys. 4326 * @return This object for method chaining. 4327 */ setUnreadConversation(UnreadConversation unreadConversation)4328 public CarExtender setUnreadConversation(UnreadConversation unreadConversation) { 4329 mUnreadConversation = unreadConversation; 4330 return this; 4331 } 4332 4333 /** 4334 * Returns the unread conversation conveyed by this notification. 4335 * @see #setUnreadConversation(UnreadConversation) 4336 */ getUnreadConversation()4337 public UnreadConversation getUnreadConversation() { 4338 return mUnreadConversation; 4339 } 4340 4341 /** 4342 * A class which holds the unread messages from a conversation. 4343 */ 4344 public static class UnreadConversation { 4345 private final String[] mMessages; 4346 private final RemoteInput mRemoteInput; 4347 private final PendingIntent mReplyPendingIntent; 4348 private final PendingIntent mReadPendingIntent; 4349 private final String[] mParticipants; 4350 private final long mLatestTimestamp; 4351 UnreadConversation(String[] messages, RemoteInput remoteInput, PendingIntent replyPendingIntent, PendingIntent readPendingIntent, String[] participants, long latestTimestamp)4352 UnreadConversation(String[] messages, RemoteInput remoteInput, 4353 PendingIntent replyPendingIntent, PendingIntent readPendingIntent, 4354 String[] participants, long latestTimestamp) { 4355 mMessages = messages; 4356 mRemoteInput = remoteInput; 4357 mReadPendingIntent = readPendingIntent; 4358 mReplyPendingIntent = replyPendingIntent; 4359 mParticipants = participants; 4360 mLatestTimestamp = latestTimestamp; 4361 } 4362 4363 /** 4364 * Gets the list of messages conveyed by this notification. 4365 */ getMessages()4366 public String[] getMessages() { 4367 return mMessages; 4368 } 4369 4370 /** 4371 * Gets the remote input that will be used to convey the response to a message list, or 4372 * null if no such remote input exists. 4373 */ getRemoteInput()4374 public RemoteInput getRemoteInput() { 4375 return mRemoteInput; 4376 } 4377 4378 /** 4379 * Gets the pending intent that will be triggered when the user replies to this 4380 * notification. 4381 */ getReplyPendingIntent()4382 public PendingIntent getReplyPendingIntent() { 4383 return mReplyPendingIntent; 4384 } 4385 4386 /** 4387 * Gets the pending intent that Android Auto will send after it reads aloud all messages 4388 * in this object's message list. 4389 */ getReadPendingIntent()4390 public PendingIntent getReadPendingIntent() { 4391 return mReadPendingIntent; 4392 } 4393 4394 /** 4395 * Gets the participants in the conversation. 4396 */ getParticipants()4397 public String[] getParticipants() { 4398 return mParticipants; 4399 } 4400 4401 /** 4402 * Gets the firs participant in the conversation. 4403 */ getParticipant()4404 public String getParticipant() { 4405 return mParticipants.length > 0 ? mParticipants[0] : null; 4406 } 4407 4408 /** 4409 * Gets the timestamp of the conversation. 4410 */ getLatestTimestamp()4411 public long getLatestTimestamp() { 4412 return mLatestTimestamp; 4413 } 4414 4415 /** 4416 * Builder class for {@link CarExtender.UnreadConversation} objects. 4417 */ 4418 public static class Builder { 4419 private final List<String> mMessages = new ArrayList<String>(); 4420 private final String mParticipant; 4421 private RemoteInput mRemoteInput; 4422 private PendingIntent mReadPendingIntent; 4423 private PendingIntent mReplyPendingIntent; 4424 private long mLatestTimestamp; 4425 4426 /** 4427 * Constructs a new builder for {@link CarExtender.UnreadConversation}. 4428 * 4429 * @param name The name of the other participant in the conversation. 4430 */ Builder(String name)4431 public Builder(String name) { 4432 mParticipant = name; 4433 } 4434 4435 /** 4436 * Appends a new unread message to the list of messages for this conversation. 4437 * 4438 * The messages should be added from oldest to newest. 4439 * 4440 * @param message The text of the new unread message. 4441 * @return This object for method chaining. 4442 */ addMessage(String message)4443 public Builder addMessage(String message) { 4444 mMessages.add(message); 4445 return this; 4446 } 4447 4448 /** 4449 * Sets the pending intent and remote input which will convey the reply to this 4450 * notification. 4451 * 4452 * @param pendingIntent The pending intent which will be triggered on a reply. 4453 * @param remoteInput The remote input parcelable which will carry the reply. 4454 * @return This object for method chaining. 4455 * 4456 * @see CarExtender.UnreadConversation#getRemoteInput 4457 * @see CarExtender.UnreadConversation#getReplyPendingIntent 4458 */ setReplyAction( PendingIntent pendingIntent, RemoteInput remoteInput)4459 public Builder setReplyAction( 4460 PendingIntent pendingIntent, RemoteInput remoteInput) { 4461 mRemoteInput = remoteInput; 4462 mReplyPendingIntent = pendingIntent; 4463 4464 return this; 4465 } 4466 4467 /** 4468 * Sets the pending intent that will be sent once the messages in this notification 4469 * are read. 4470 * 4471 * @param pendingIntent The pending intent to use. 4472 * @return This object for method chaining. 4473 */ setReadPendingIntent(PendingIntent pendingIntent)4474 public Builder setReadPendingIntent(PendingIntent pendingIntent) { 4475 mReadPendingIntent = pendingIntent; 4476 return this; 4477 } 4478 4479 /** 4480 * Sets the timestamp of the most recent message in an unread conversation. 4481 * 4482 * If a messaging notification has been posted by your application and has not 4483 * yet been cancelled, posting a later notification with the same id and tag 4484 * but without a newer timestamp may result in Android Auto not displaying a 4485 * heads up notification for the later notification. 4486 * 4487 * @param timestamp The timestamp of the most recent message in the conversation. 4488 * @return This object for method chaining. 4489 */ setLatestTimestamp(long timestamp)4490 public Builder setLatestTimestamp(long timestamp) { 4491 mLatestTimestamp = timestamp; 4492 return this; 4493 } 4494 4495 /** 4496 * Builds a new unread conversation object. 4497 * 4498 * @return The new unread conversation object. 4499 */ build()4500 public UnreadConversation build() { 4501 String[] messages = mMessages.toArray(new String[mMessages.size()]); 4502 String[] participants = { mParticipant }; 4503 return new UnreadConversation(messages, mRemoteInput, mReplyPendingIntent, 4504 mReadPendingIntent, participants, mLatestTimestamp); 4505 } 4506 } 4507 } 4508 } 4509 4510 4511 /** 4512 * Get an array of Notification objects from a parcelable array bundle field. 4513 * Update the bundle to have a typed array so fetches in the future don't need 4514 * to do an array copy. 4515 */ getNotificationArrayFromBundle(Bundle bundle, String key)4516 static Notification[] getNotificationArrayFromBundle(Bundle bundle, String key) { 4517 Parcelable[] array = bundle.getParcelableArray(key); 4518 if (array instanceof Notification[] || array == null) { 4519 return (Notification[]) array; 4520 } 4521 Notification[] typedArray = new Notification[array.length]; 4522 for (int i = 0; i < array.length; i++) { 4523 typedArray[i] = (Notification) array[i]; 4524 } 4525 bundle.putParcelableArray(key, typedArray); 4526 return typedArray; 4527 } 4528 4529 /** 4530 * Gets the {@link Notification#extras} field from a notification in a backwards 4531 * compatible manner. Extras field was supported from JellyBean (Api level 16) 4532 * forwards. This function will return null on older api levels. 4533 */ getExtras(Notification notification)4534 public static Bundle getExtras(Notification notification) { 4535 if (Build.VERSION.SDK_INT >= 19) { 4536 return notification.extras; 4537 } else if (Build.VERSION.SDK_INT >= 16) { 4538 return NotificationCompatJellybean.getExtras(notification); 4539 } else { 4540 return null; 4541 } 4542 } 4543 4544 /** 4545 * Get the number of actions in this notification in a backwards compatible 4546 * manner. Actions were supported from JellyBean (Api level 16) forwards. 4547 */ getActionCount(Notification notification)4548 public static int getActionCount(Notification notification) { 4549 if (Build.VERSION.SDK_INT >= 19) { 4550 return notification.actions != null ? notification.actions.length : 0; 4551 } else if (Build.VERSION.SDK_INT >= 16) { 4552 return NotificationCompatJellybean.getActionCount(notification); 4553 } else { 4554 return 0; 4555 } 4556 } 4557 4558 /** 4559 * Get an action on this notification in a backwards compatible 4560 * manner. Actions were supported from JellyBean (Api level 16) forwards. 4561 * @param notification The notification to inspect. 4562 * @param actionIndex The index of the action to retrieve. 4563 */ getAction(Notification notification, int actionIndex)4564 public static Action getAction(Notification notification, int actionIndex) { 4565 if (Build.VERSION.SDK_INT >= 20) { 4566 return getActionCompatFromAction(notification.actions[actionIndex]); 4567 } else if (Build.VERSION.SDK_INT >= 19) { 4568 Notification.Action action = notification.actions[actionIndex]; 4569 Bundle actionExtras = null; 4570 SparseArray<Bundle> actionExtrasMap = notification.extras.getSparseParcelableArray( 4571 NotificationCompatExtras.EXTRA_ACTION_EXTRAS); 4572 if (actionExtrasMap != null) { 4573 actionExtras = actionExtrasMap.get(actionIndex); 4574 } 4575 return NotificationCompatJellybean.readAction(action.icon, action.title, 4576 action.actionIntent, actionExtras); 4577 } else if (Build.VERSION.SDK_INT >= 16) { 4578 return NotificationCompatJellybean.getAction(notification, actionIndex); 4579 } else { 4580 return null; 4581 } 4582 } 4583 4584 @RequiresApi(20) getActionCompatFromAction(Notification.Action action)4585 static Action getActionCompatFromAction(Notification.Action action) { 4586 final RemoteInput[] remoteInputs; 4587 final android.app.RemoteInput[] srcArray = action.getRemoteInputs(); 4588 if (srcArray == null) { 4589 remoteInputs = null; 4590 } else { 4591 remoteInputs = new RemoteInput[srcArray.length]; 4592 for (int i = 0; i < srcArray.length; i++) { 4593 android.app.RemoteInput src = srcArray[i]; 4594 remoteInputs[i] = new RemoteInput(src.getResultKey(), src.getLabel(), 4595 src.getChoices(), src.getAllowFreeFormInput(), src.getExtras(), null); 4596 } 4597 } 4598 4599 final boolean allowGeneratedReplies; 4600 if (Build.VERSION.SDK_INT >= 24) { 4601 allowGeneratedReplies = action.getExtras().getBoolean( 4602 NotificationCompatJellybean.EXTRA_ALLOW_GENERATED_REPLIES) 4603 || action.getAllowGeneratedReplies(); 4604 } else { 4605 allowGeneratedReplies = action.getExtras().getBoolean( 4606 NotificationCompatJellybean.EXTRA_ALLOW_GENERATED_REPLIES); 4607 } 4608 return new Action(action.icon, action.title, action.actionIntent, 4609 action.getExtras(), remoteInputs, null, allowGeneratedReplies); 4610 } 4611 4612 /** 4613 * Get the category of this notification in a backwards compatible 4614 * manner. 4615 * @param notification The notification to inspect. 4616 */ getCategory(Notification notification)4617 public static String getCategory(Notification notification) { 4618 if (Build.VERSION.SDK_INT >= 21) { 4619 return notification.category; 4620 } else { 4621 return null; 4622 } 4623 } 4624 4625 /** 4626 * Get whether or not this notification is only relevant to the current device. 4627 * 4628 * <p>Some notifications can be bridged to other devices for remote display. 4629 * If this hint is set, it is recommend that this notification not be bridged. 4630 */ getLocalOnly(Notification notification)4631 public static boolean getLocalOnly(Notification notification) { 4632 if (Build.VERSION.SDK_INT >= 20) { 4633 return (notification.flags & Notification.FLAG_LOCAL_ONLY) != 0; 4634 } else if (Build.VERSION.SDK_INT >= 19) { 4635 return notification.extras.getBoolean(NotificationCompatExtras.EXTRA_LOCAL_ONLY); 4636 } else if (Build.VERSION.SDK_INT >= 16) { 4637 return NotificationCompatJellybean.getExtras(notification).getBoolean( 4638 NotificationCompatExtras.EXTRA_LOCAL_ONLY); 4639 } else { 4640 return false; 4641 } 4642 } 4643 4644 /** 4645 * Get the key used to group this notification into a cluster or stack 4646 * with other notifications on devices which support such rendering. 4647 */ getGroup(Notification notification)4648 public static String getGroup(Notification notification) { 4649 if (Build.VERSION.SDK_INT >= 20) { 4650 return notification.getGroup(); 4651 } else if (Build.VERSION.SDK_INT >= 19) { 4652 return notification.extras.getString(NotificationCompatExtras.EXTRA_GROUP_KEY); 4653 } else if (Build.VERSION.SDK_INT >= 16) { 4654 return NotificationCompatJellybean.getExtras(notification).getString( 4655 NotificationCompatExtras.EXTRA_GROUP_KEY); 4656 } else { 4657 return null; 4658 } 4659 } 4660 4661 /** 4662 * Get whether this notification to be the group summary for a group of notifications. 4663 * Grouped notifications may display in a cluster or stack on devices which 4664 * support such rendering. Requires a group key also be set using {@link Builder#setGroup}. 4665 * @return Whether this notification is a group summary. 4666 */ isGroupSummary(Notification notification)4667 public static boolean isGroupSummary(Notification notification) { 4668 if (Build.VERSION.SDK_INT >= 20) { 4669 return (notification.flags & Notification.FLAG_GROUP_SUMMARY) != 0; 4670 } else if (Build.VERSION.SDK_INT >= 19) { 4671 return notification.extras.getBoolean(NotificationCompatExtras.EXTRA_GROUP_SUMMARY); 4672 } else if (Build.VERSION.SDK_INT >= 16) { 4673 return NotificationCompatJellybean.getExtras(notification).getBoolean( 4674 NotificationCompatExtras.EXTRA_GROUP_SUMMARY); 4675 } else { 4676 return false; 4677 } 4678 } 4679 4680 /** 4681 * Get a sort key that orders this notification among other notifications from the 4682 * same package. This can be useful if an external sort was already applied and an app 4683 * would like to preserve this. Notifications will be sorted lexicographically using this 4684 * value, although providing different priorities in addition to providing sort key may 4685 * cause this value to be ignored. 4686 * 4687 * <p>This sort key can also be used to order members of a notification group. See 4688 * {@link Builder#setGroup}. 4689 * 4690 * @see String#compareTo(String) 4691 */ getSortKey(Notification notification)4692 public static String getSortKey(Notification notification) { 4693 if (Build.VERSION.SDK_INT >= 20) { 4694 return notification.getSortKey(); 4695 } else if (Build.VERSION.SDK_INT >= 19) { 4696 return notification.extras.getString(NotificationCompatExtras.EXTRA_SORT_KEY); 4697 } else if (Build.VERSION.SDK_INT >= 16) { 4698 return NotificationCompatJellybean.getExtras(notification).getString( 4699 NotificationCompatExtras.EXTRA_SORT_KEY); 4700 } else { 4701 return null; 4702 } 4703 } 4704 4705 /** 4706 * @return the ID of the channel this notification posts to. 4707 */ getChannelId(Notification notification)4708 public static String getChannelId(Notification notification) { 4709 if (Build.VERSION.SDK_INT >= 26) { 4710 return notification.getChannelId(); 4711 } else { 4712 return null; 4713 } 4714 } 4715 4716 /** 4717 * Returns the time at which this notification should be canceled by the system, if it's not 4718 * canceled already. 4719 */ getTimeoutAfter(Notification notification)4720 public static long getTimeoutAfter(Notification notification) { 4721 if (Build.VERSION.SDK_INT >= 26) { 4722 return notification.getTimeoutAfter(); 4723 } else { 4724 return 0; 4725 } 4726 } 4727 4728 /** 4729 * Returns what icon should be shown for this notification if it is being displayed in a 4730 * Launcher that supports badging. Will be one of {@link #BADGE_ICON_NONE}, 4731 * {@link #BADGE_ICON_SMALL}, or {@link #BADGE_ICON_LARGE}. 4732 */ getBadgeIconType(Notification notification)4733 public static int getBadgeIconType(Notification notification) { 4734 if (Build.VERSION.SDK_INT >= 26) { 4735 return notification.getBadgeIconType(); 4736 } else { 4737 return BADGE_ICON_NONE; 4738 } 4739 } 4740 4741 /** 4742 * Returns the {@link android.support.v4.content.pm.ShortcutInfoCompat#getId() id} that this 4743 * notification supersedes, if any. 4744 */ getShortcutId(Notification notification)4745 public static String getShortcutId(Notification notification) { 4746 if (Build.VERSION.SDK_INT >= 26) { 4747 return notification.getShortcutId(); 4748 } else { 4749 return null; 4750 } 4751 } 4752 4753 /** 4754 * Returns which type of notifications in a group are responsible for audibly alerting the 4755 * user. See {@link #GROUP_ALERT_ALL}, {@link #GROUP_ALERT_CHILDREN}, 4756 * {@link #GROUP_ALERT_SUMMARY}. 4757 */ 4758 @GroupAlertBehavior getGroupAlertBehavior(Notification notification)4759 public static int getGroupAlertBehavior(Notification notification) { 4760 if (Build.VERSION.SDK_INT >= 26) { 4761 return notification.getGroupAlertBehavior(); 4762 } else { 4763 return GROUP_ALERT_ALL; 4764 } 4765 } 4766 } 4767