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