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