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