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(&quot;New photo from &quot; + 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(&quot;New mail from &quot; + 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(&quot;2 new messages with &quot; + 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          * &lt; {@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(&quot;5 New mails from &quot; + 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(&quot;&quot;)
5194      *         .setSummaryText(&quot;+3 more&quot;))
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(&quot;New mail from &quot; + 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&lt;Notification&gt; 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          * &lt;activity android:name=&quot;com.example.MyDisplayActivity&quot;
6951          *     android:exported=&quot;true&quot;
6952          *     android:allowEmbedded=&quot;true&quot;
6953          *     android:taskAffinity=&quot;&quot;
6954          *     android:theme=&quot;@android:style/Theme.DeviceDefault.Light&quot; /&gt;</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