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