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