Notification.java revision 4dc008cda2980fabb6acbf8a3b7096d1090ee36f
1/*
2 * Copyright (C) 2007 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.app;
18
19import android.annotation.ColorInt;
20import android.annotation.DrawableRes;
21import android.annotation.IntDef;
22import android.annotation.SdkConstant;
23import android.annotation.SdkConstant.SdkConstantType;
24import android.content.Context;
25import android.content.Intent;
26import android.content.pm.ApplicationInfo;
27import android.content.pm.PackageManager;
28import android.content.pm.PackageManager.NameNotFoundException;
29import android.content.res.ColorStateList;
30import android.graphics.Bitmap;
31import android.graphics.Canvas;
32import android.graphics.PorterDuff;
33import android.graphics.drawable.Drawable;
34import android.graphics.drawable.Icon;
35import android.media.AudioAttributes;
36import android.media.AudioManager;
37import android.media.session.MediaSession;
38import android.net.Uri;
39import android.os.BadParcelableException;
40import android.os.Build;
41import android.os.Bundle;
42import android.os.Parcel;
43import android.os.Parcelable;
44import android.os.SystemClock;
45import android.os.UserHandle;
46import android.text.SpannableStringBuilder;
47import android.text.Spanned;
48import android.text.TextUtils;
49import android.text.style.AbsoluteSizeSpan;
50import android.text.style.CharacterStyle;
51import android.text.style.RelativeSizeSpan;
52import android.text.style.TextAppearanceSpan;
53import android.util.Log;
54import android.util.SparseArray;
55import android.util.TypedValue;
56import android.view.Gravity;
57import android.view.NotificationHeaderView;
58import android.view.View;
59import android.view.ViewGroup;
60import android.widget.ProgressBar;
61import android.widget.RemoteViews;
62
63import com.android.internal.R;
64import com.android.internal.util.NotificationColorUtil;
65
66import java.lang.annotation.Retention;
67import java.lang.annotation.RetentionPolicy;
68import java.lang.reflect.Constructor;
69import java.util.ArrayList;
70import java.util.Arrays;
71import java.util.Collections;
72import java.util.List;
73import java.util.Set;
74
75/**
76 * A class that represents how a persistent notification is to be presented to
77 * the user using the {@link android.app.NotificationManager}.
78 *
79 * <p>The {@link Notification.Builder Notification.Builder} has been added to make it
80 * easier to construct Notifications.</p>
81 *
82 * <div class="special reference">
83 * <h3>Developer Guides</h3>
84 * <p>For a guide to creating notifications, read the
85 * <a href="{@docRoot}guide/topics/ui/notifiers/notifications.html">Status Bar Notifications</a>
86 * developer guide.</p>
87 * </div>
88 */
89public class Notification implements Parcelable
90{
91    private static final String TAG = "Notification";
92
93    /**
94     * An activity that provides a user interface for adjusting notification preferences for its
95     * containing application. Optional but recommended for apps that post
96     * {@link android.app.Notification Notifications}.
97     */
98    @SdkConstant(SdkConstantType.INTENT_CATEGORY)
99    public static final String INTENT_CATEGORY_NOTIFICATION_PREFERENCES
100            = "android.intent.category.NOTIFICATION_PREFERENCES";
101
102    /**
103     * Use all default values (where applicable).
104     */
105    public static final int DEFAULT_ALL = ~0;
106
107    /**
108     * Use the default notification sound. This will ignore any given
109     * {@link #sound}.
110     *
111     * <p>
112     * A notification that is noisy is more likely to be presented as a heads-up notification.
113     * </p>
114     *
115     * @see #defaults
116     */
117
118    public static final int DEFAULT_SOUND = 1;
119
120    /**
121     * Use the default notification vibrate. This will ignore any given
122     * {@link #vibrate}. Using phone vibration requires the
123     * {@link android.Manifest.permission#VIBRATE VIBRATE} permission.
124     *
125     * <p>
126     * A notification that vibrates is more likely to be presented as a heads-up notification.
127     * </p>
128     *
129     * @see #defaults
130     */
131
132    public static final int DEFAULT_VIBRATE = 2;
133
134    /**
135     * Use the default notification lights. This will ignore the
136     * {@link #FLAG_SHOW_LIGHTS} bit, and {@link #ledARGB}, {@link #ledOffMS}, or
137     * {@link #ledOnMS}.
138     *
139     * @see #defaults
140     */
141
142    public static final int DEFAULT_LIGHTS = 4;
143
144    /**
145     * Maximum length of CharSequences accepted by Builder and friends.
146     *
147     * <p>
148     * Avoids spamming the system with overly large strings such as full e-mails.
149     */
150    private static final int MAX_CHARSEQUENCE_LENGTH = 5 * 1024;
151
152    /**
153     * Maximum entries of reply text that are accepted by Builder and friends.
154     */
155    private static final int MAX_REPLY_HISTORY = 5;
156
157    /**
158     * A timestamp related to this notification, in milliseconds since the epoch.
159     *
160     * Default value: {@link System#currentTimeMillis() Now}.
161     *
162     * Choose a timestamp that will be most relevant to the user. For most finite events, this
163     * corresponds to the time the event happened (or will happen, in the case of events that have
164     * yet to occur but about which the user is being informed). Indefinite events should be
165     * timestamped according to when the activity began.
166     *
167     * Some examples:
168     *
169     * <ul>
170     *   <li>Notification of a new chat message should be stamped when the message was received.</li>
171     *   <li>Notification of an ongoing file download (with a progress bar, for example) should be stamped when the download started.</li>
172     *   <li>Notification of a completed file download should be stamped when the download finished.</li>
173     *   <li>Notification of an upcoming meeting should be stamped with the time the meeting will begin (that is, in the future).</li>
174     *   <li>Notification of an ongoing stopwatch (increasing timer) should be stamped with the watch's start time.
175     *   <li>Notification of an ongoing countdown timer should be stamped with the timer's end time.
176     * </ul>
177     *
178     */
179    public long when;
180
181    /**
182     * The resource id of a drawable to use as the icon in the status bar.
183     *
184     * @deprecated Use {@link Builder#setSmallIcon(Icon)} instead.
185     */
186    @Deprecated
187    @DrawableRes
188    public int icon;
189
190    /**
191     * If the icon in the status bar is to have more than one level, you can set this.  Otherwise,
192     * leave it at its default value of 0.
193     *
194     * @see android.widget.ImageView#setImageLevel
195     * @see android.graphics.drawable.Drawable#setLevel
196     */
197    public int iconLevel;
198
199    /**
200     * The number of events that this notification represents. For example, in a new mail
201     * notification, this could be the number of unread messages.
202     *
203     * The system may or may not use this field to modify the appearance of the notification. For
204     * example, before {@link android.os.Build.VERSION_CODES#HONEYCOMB}, this number was
205     * superimposed over the icon in the status bar. Starting with
206     * {@link android.os.Build.VERSION_CODES#HONEYCOMB}, the template used by
207     * {@link Notification.Builder} has displayed the number in the expanded notification view.
208     *
209     * If the number is 0 or negative, it is never shown.
210     */
211    public int number;
212
213    /**
214     * The intent to execute when the expanded status entry is clicked.  If
215     * this is an activity, it must include the
216     * {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK} flag, which requires
217     * that you take care of task management as described in the
218     * <a href="{@docRoot}guide/topics/fundamentals/tasks-and-back-stack.html">Tasks and Back
219     * Stack</a> document.  In particular, make sure to read the notification section
220     * <a href="{@docRoot}guide/topics/ui/notifiers/notifications.html#HandlingNotifications">Handling
221     * Notifications</a> for the correct ways to launch an application from a
222     * notification.
223     */
224    public PendingIntent contentIntent;
225
226    /**
227     * The intent to execute when the notification is explicitly dismissed by the user, either with
228     * the "Clear All" button or by swiping it away individually.
229     *
230     * This probably shouldn't be launching an activity since several of those will be sent
231     * at the same time.
232     */
233    public PendingIntent deleteIntent;
234
235    /**
236     * An intent to launch instead of posting the notification to the status bar.
237     *
238     * <p>
239     * The system UI may choose to display a heads-up notification, instead of
240     * launching this intent, while the user is using the device.
241     * </p>
242     *
243     * @see Notification.Builder#setFullScreenIntent
244     */
245    public PendingIntent fullScreenIntent;
246
247    /**
248     * Text that summarizes this notification for accessibility services.
249     *
250     * As of the L release, this text is no longer shown on screen, but it is still useful to
251     * accessibility services (where it serves as an audible announcement of the notification's
252     * appearance).
253     *
254     * @see #tickerView
255     */
256    public CharSequence tickerText;
257
258    /**
259     * Formerly, a view showing the {@link #tickerText}.
260     *
261     * No longer displayed in the status bar as of API 21.
262     */
263    @Deprecated
264    public RemoteViews tickerView;
265
266    /**
267     * The view that will represent this notification in the notification list (which is pulled
268     * down from the status bar).
269     *
270     * As of N, this field may be null. The notification view is determined by the inputs
271     * to {@link Notification.Builder}; a custom RemoteViews can optionally be
272     * supplied with {@link Notification.Builder#setCustomContentView(RemoteViews)}.
273     */
274    @Deprecated
275    public RemoteViews contentView;
276
277    /**
278     * A large-format version of {@link #contentView}, giving the Notification an
279     * opportunity to show more detail. The system UI may choose to show this
280     * instead of the normal content view at its discretion.
281     *
282     * As of N, this field may be null. The expanded notification view is determined by the
283     * inputs to {@link Notification.Builder}; a custom RemoteViews can optionally be
284     * supplied with {@link Notification.Builder#setCustomBigContentView(RemoteViews)}.
285     */
286    @Deprecated
287    public RemoteViews bigContentView;
288
289
290    /**
291     * A medium-format version of {@link #contentView}, providing the Notification an
292     * opportunity to add action buttons to contentView. At its discretion, the system UI may
293     * choose to show this as a heads-up notification, which will pop up so the user can see
294     * it without leaving their current activity.
295     *
296     * As of N, this field may be null. The heads-up notification view is determined by the
297     * inputs to {@link Notification.Builder}; a custom RemoteViews can optionally be
298     * supplied with {@link Notification.Builder#setCustomHeadsUpContentView(RemoteViews)}.
299     */
300    @Deprecated
301    public RemoteViews headsUpContentView;
302
303    /**
304     * A large bitmap to be shown in the notification content area.
305     *
306     * @deprecated Use {@link Builder#setLargeIcon(Icon)} instead.
307     */
308    @Deprecated
309    public Bitmap largeIcon;
310
311    /**
312     * The sound to play.
313     *
314     * <p>
315     * A notification that is noisy is more likely to be presented as a heads-up notification.
316     * </p>
317     *
318     * <p>
319     * To play the default notification sound, see {@link #defaults}.
320     * </p>
321     */
322    public Uri sound;
323
324    /**
325     * Use this constant as the value for audioStreamType to request that
326     * the default stream type for notifications be used.  Currently the
327     * default stream type is {@link AudioManager#STREAM_NOTIFICATION}.
328     *
329     * @deprecated Use {@link #audioAttributes} instead.
330     */
331    @Deprecated
332    public static final int STREAM_DEFAULT = -1;
333
334    /**
335     * The audio stream type to use when playing the sound.
336     * Should be one of the STREAM_ constants from
337     * {@link android.media.AudioManager}.
338     *
339     * @deprecated Use {@link #audioAttributes} instead.
340     */
341    @Deprecated
342    public int audioStreamType = STREAM_DEFAULT;
343
344    /**
345     * The default value of {@link #audioAttributes}.
346     */
347    public static final AudioAttributes AUDIO_ATTRIBUTES_DEFAULT = new AudioAttributes.Builder()
348            .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
349            .setUsage(AudioAttributes.USAGE_NOTIFICATION)
350            .build();
351
352    /**
353     * The {@link AudioAttributes audio attributes} to use when playing the sound.
354     */
355    public AudioAttributes audioAttributes = AUDIO_ATTRIBUTES_DEFAULT;
356
357    /**
358     * The pattern with which to vibrate.
359     *
360     * <p>
361     * To vibrate the default pattern, see {@link #defaults}.
362     * </p>
363     *
364     * <p>
365     * A notification that vibrates is more likely to be presented as a heads-up notification.
366     * </p>
367     *
368     * @see android.os.Vibrator#vibrate(long[],int)
369     */
370    public long[] vibrate;
371
372    /**
373     * The color of the led.  The hardware will do its best approximation.
374     *
375     * @see #FLAG_SHOW_LIGHTS
376     * @see #flags
377     */
378    @ColorInt
379    public int ledARGB;
380
381    /**
382     * The number of milliseconds for the LED to be on while it's flashing.
383     * The hardware will do its best approximation.
384     *
385     * @see #FLAG_SHOW_LIGHTS
386     * @see #flags
387     */
388    public int ledOnMS;
389
390    /**
391     * The number of milliseconds for the LED to be off while it's flashing.
392     * The hardware will do its best approximation.
393     *
394     * @see #FLAG_SHOW_LIGHTS
395     * @see #flags
396     */
397    public int ledOffMS;
398
399    /**
400     * Specifies which values should be taken from the defaults.
401     * <p>
402     * To set, OR the desired from {@link #DEFAULT_SOUND},
403     * {@link #DEFAULT_VIBRATE}, {@link #DEFAULT_LIGHTS}. For all default
404     * values, use {@link #DEFAULT_ALL}.
405     * </p>
406     */
407    public int defaults;
408
409    /**
410     * Bit to be bitwise-ored into the {@link #flags} field that should be
411     * set if you want the LED on for this notification.
412     * <ul>
413     * <li>To turn the LED off, pass 0 in the alpha channel for colorARGB
414     *      or 0 for both ledOnMS and ledOffMS.</li>
415     * <li>To turn the LED on, pass 1 for ledOnMS and 0 for ledOffMS.</li>
416     * <li>To flash the LED, pass the number of milliseconds that it should
417     *      be on and off to ledOnMS and ledOffMS.</li>
418     * </ul>
419     * <p>
420     * Since hardware varies, you are not guaranteed that any of the values
421     * you pass are honored exactly.  Use the system defaults (TODO) if possible
422     * because they will be set to values that work on any given hardware.
423     * <p>
424     * The alpha channel must be set for forward compatibility.
425     *
426     */
427    public static final int FLAG_SHOW_LIGHTS        = 0x00000001;
428
429    /**
430     * Bit to be bitwise-ored into the {@link #flags} field that should be
431     * set if this notification is in reference to something that is ongoing,
432     * like a phone call.  It should not be set if this notification is in
433     * reference to something that happened at a particular point in time,
434     * like a missed phone call.
435     */
436    public static final int FLAG_ONGOING_EVENT      = 0x00000002;
437
438    /**
439     * Bit to be bitwise-ored into the {@link #flags} field that if set,
440     * the audio will be repeated until the notification is
441     * cancelled or the notification window is opened.
442     */
443    public static final int FLAG_INSISTENT          = 0x00000004;
444
445    /**
446     * Bit to be bitwise-ored into the {@link #flags} field that should be
447     * set if you would only like the sound, vibrate and ticker to be played
448     * if the notification was not already showing.
449     */
450    public static final int FLAG_ONLY_ALERT_ONCE    = 0x00000008;
451
452    /**
453     * Bit to be bitwise-ored into the {@link #flags} field that should be
454     * set if the notification should be canceled when it is clicked by the
455     * user.
456     */
457    public static final int FLAG_AUTO_CANCEL        = 0x00000010;
458
459    /**
460     * Bit to be bitwise-ored into the {@link #flags} field that should be
461     * set if the notification should not be canceled when the user clicks
462     * the Clear all button.
463     */
464    public static final int FLAG_NO_CLEAR           = 0x00000020;
465
466    /**
467     * Bit to be bitwise-ored into the {@link #flags} field that should be
468     * set if this notification represents a currently running service.  This
469     * will normally be set for you by {@link Service#startForeground}.
470     */
471    public static final int FLAG_FOREGROUND_SERVICE = 0x00000040;
472
473    /**
474     * Obsolete flag indicating high-priority notifications; use the priority field instead.
475     *
476     * @deprecated Use {@link #priority} with a positive value.
477     */
478    public static final int FLAG_HIGH_PRIORITY      = 0x00000080;
479
480    /**
481     * Bit to be bitswise-ored into the {@link #flags} field that should be
482     * set if this notification is relevant to the current device only
483     * and it is not recommended that it bridge to other devices.
484     */
485    public static final int FLAG_LOCAL_ONLY         = 0x00000100;
486
487    /**
488     * Bit to be bitswise-ored into the {@link #flags} field that should be
489     * set if this notification is the group summary for a group of notifications.
490     * Grouped notifications may display in a cluster or stack on devices which
491     * support such rendering. Requires a group key also be set using {@link Builder#setGroup}.
492     */
493    public static final int FLAG_GROUP_SUMMARY      = 0x00000200;
494
495    public int flags;
496
497    /** @hide */
498    @IntDef({PRIORITY_DEFAULT,PRIORITY_LOW,PRIORITY_MIN,PRIORITY_HIGH,PRIORITY_MAX})
499    @Retention(RetentionPolicy.SOURCE)
500    public @interface Priority {}
501
502    /**
503     * Default notification {@link #priority}. If your application does not prioritize its own
504     * notifications, use this value for all notifications.
505     */
506    public static final int PRIORITY_DEFAULT = 0;
507
508    /**
509     * Lower {@link #priority}, for items that are less important. The UI may choose to show these
510     * items smaller, or at a different position in the list, compared with your app's
511     * {@link #PRIORITY_DEFAULT} items.
512     */
513    public static final int PRIORITY_LOW = -1;
514
515    /**
516     * Lowest {@link #priority}; these items might not be shown to the user except under special
517     * circumstances, such as detailed notification logs.
518     */
519    public static final int PRIORITY_MIN = -2;
520
521    /**
522     * Higher {@link #priority}, for more important notifications or alerts. The UI may choose to
523     * show these items larger, or at a different position in notification lists, compared with
524     * your app's {@link #PRIORITY_DEFAULT} items.
525     */
526    public static final int PRIORITY_HIGH = 1;
527
528    /**
529     * Highest {@link #priority}, for your application's most important items that require the
530     * user's prompt attention or input.
531     */
532    public static final int PRIORITY_MAX = 2;
533
534    /**
535     * Relative priority for this notification.
536     *
537     * Priority is an indication of how much of the user's valuable attention should be consumed by
538     * this notification. Low-priority notifications may be hidden from the user in certain
539     * situations, while the user might be interrupted for a higher-priority notification. The
540     * system will make a determination about how to interpret this priority when presenting
541     * the notification.
542     *
543     * <p>
544     * A notification that is at least {@link #PRIORITY_HIGH} is more likely to be presented
545     * as a heads-up notification.
546     * </p>
547     *
548     */
549    @Priority
550    public int priority;
551
552    /**
553     * Accent color (an ARGB integer like the constants in {@link android.graphics.Color})
554     * to be applied by the standard Style templates when presenting this notification.
555     *
556     * The current template design constructs a colorful header image by overlaying the
557     * {@link #icon} image (stenciled in white) atop a field of this color. Alpha components are
558     * ignored.
559     */
560    @ColorInt
561    public int color = COLOR_DEFAULT;
562
563    /**
564     * Special value of {@link #color} telling the system not to decorate this notification with
565     * any special color but instead use default colors when presenting this notification.
566     */
567    @ColorInt
568    public static final int COLOR_DEFAULT = 0; // AKA Color.TRANSPARENT
569
570    /**
571     * Sphere of visibility of this notification, which affects how and when the SystemUI reveals
572     * the notification's presence and contents in untrusted situations (namely, on the secure
573     * lockscreen).
574     *
575     * The default level, {@link #VISIBILITY_PRIVATE}, behaves exactly as notifications have always
576     * done on Android: The notification's {@link #icon} and {@link #tickerText} (if available) are
577     * shown in all situations, but the contents are only available if the device is unlocked for
578     * the appropriate user.
579     *
580     * A more permissive policy can be expressed by {@link #VISIBILITY_PUBLIC}; such a notification
581     * can be read even in an "insecure" context (that is, above a secure lockscreen).
582     * To modify the public version of this notification—for example, to redact some portions—see
583     * {@link Builder#setPublicVersion(Notification)}.
584     *
585     * Finally, a notification can be made {@link #VISIBILITY_SECRET}, which will suppress its icon
586     * and ticker until the user has bypassed the lockscreen.
587     */
588    public int visibility;
589
590    /**
591     * Notification visibility: Show this notification in its entirety on all lockscreens.
592     *
593     * {@see #visibility}
594     */
595    public static final int VISIBILITY_PUBLIC = 1;
596
597    /**
598     * Notification visibility: Show this notification on all lockscreens, but conceal sensitive or
599     * private information on secure lockscreens.
600     *
601     * {@see #visibility}
602     */
603    public static final int VISIBILITY_PRIVATE = 0;
604
605    /**
606     * Notification visibility: Do not reveal any part of this notification on a secure lockscreen.
607     *
608     * {@see #visibility}
609     */
610    public static final int VISIBILITY_SECRET = -1;
611
612    /**
613     * Notification category: incoming call (voice or video) or similar synchronous communication request.
614     */
615    public static final String CATEGORY_CALL = "call";
616
617    /**
618     * Notification category: incoming direct message (SMS, instant message, etc.).
619     */
620    public static final String CATEGORY_MESSAGE = "msg";
621
622    /**
623     * Notification category: asynchronous bulk message (email).
624     */
625    public static final String CATEGORY_EMAIL = "email";
626
627    /**
628     * Notification category: calendar event.
629     */
630    public static final String CATEGORY_EVENT = "event";
631
632    /**
633     * Notification category: promotion or advertisement.
634     */
635    public static final String CATEGORY_PROMO = "promo";
636
637    /**
638     * Notification category: alarm or timer.
639     */
640    public static final String CATEGORY_ALARM = "alarm";
641
642    /**
643     * Notification category: progress of a long-running background operation.
644     */
645    public static final String CATEGORY_PROGRESS = "progress";
646
647    /**
648     * Notification category: social network or sharing update.
649     */
650    public static final String CATEGORY_SOCIAL = "social";
651
652    /**
653     * Notification category: error in background operation or authentication status.
654     */
655    public static final String CATEGORY_ERROR = "err";
656
657    /**
658     * Notification category: media transport control for playback.
659     */
660    public static final String CATEGORY_TRANSPORT = "transport";
661
662    /**
663     * Notification category: system or device status update.  Reserved for system use.
664     */
665    public static final String CATEGORY_SYSTEM = "sys";
666
667    /**
668     * Notification category: indication of running background service.
669     */
670    public static final String CATEGORY_SERVICE = "service";
671
672    /**
673     * Notification category: a specific, timely recommendation for a single thing.
674     * For example, a news app might want to recommend a news story it believes the user will
675     * want to read next.
676     */
677    public static final String CATEGORY_RECOMMENDATION = "recommendation";
678
679    /**
680     * Notification category: ongoing information about device or contextual status.
681     */
682    public static final String CATEGORY_STATUS = "status";
683
684    /**
685     * Notification category: user-scheduled reminder.
686     */
687    public static final String CATEGORY_REMINDER = "reminder";
688
689    /**
690     * One of the predefined notification categories (see the <code>CATEGORY_*</code> constants)
691     * that best describes this Notification.  May be used by the system for ranking and filtering.
692     */
693    public String category;
694
695    private String mGroupKey;
696
697    /**
698     * Get the key used to group this notification into a cluster or stack
699     * with other notifications on devices which support such rendering.
700     */
701    public String getGroup() {
702        return mGroupKey;
703    }
704
705    private String mSortKey;
706
707    /**
708     * Get a sort key that orders this notification among other notifications from the
709     * same package. This can be useful if an external sort was already applied and an app
710     * would like to preserve this. Notifications will be sorted lexicographically using this
711     * value, although providing different priorities in addition to providing sort key may
712     * cause this value to be ignored.
713     *
714     * <p>This sort key can also be used to order members of a notification group. See
715     * {@link Builder#setGroup}.
716     *
717     * @see String#compareTo(String)
718     */
719    public String getSortKey() {
720        return mSortKey;
721    }
722
723    /**
724     * Additional semantic data to be carried around with this Notification.
725     * <p>
726     * The extras keys defined here are intended to capture the original inputs to {@link Builder}
727     * APIs, and are intended to be used by
728     * {@link android.service.notification.NotificationListenerService} implementations to extract
729     * detailed information from notification objects.
730     */
731    public Bundle extras = new Bundle();
732
733    /**
734     * {@link #extras} key: this is the title of the notification,
735     * as supplied to {@link Builder#setContentTitle(CharSequence)}.
736     */
737    public static final String EXTRA_TITLE = "android.title";
738
739    /**
740     * {@link #extras} key: this is the title of the notification when shown in expanded form,
741     * e.g. as supplied to {@link BigTextStyle#setBigContentTitle(CharSequence)}.
742     */
743    public static final String EXTRA_TITLE_BIG = EXTRA_TITLE + ".big";
744
745    /**
746     * {@link #extras} key: this is the main text payload, as supplied to
747     * {@link Builder#setContentText(CharSequence)}.
748     */
749    public static final String EXTRA_TEXT = "android.text";
750
751    /**
752     * {@link #extras} key: this is a third line of text, as supplied to
753     * {@link Builder#setSubText(CharSequence)}.
754     */
755    public static final String EXTRA_SUB_TEXT = "android.subText";
756
757    /**
758     * {@link #extras} key: this is the remote input history, as supplied to
759     * {@link Builder#setRemoteInputHistory(CharSequence[])}.
760     */
761    public static final String EXTRA_REMOTE_INPUT_HISTORY = "android.remoteInputHistory";
762
763    /**
764     * {@link #extras} key: this is a small piece of additional text as supplied to
765     * {@link Builder#setContentInfo(CharSequence)}.
766     */
767    public static final String EXTRA_INFO_TEXT = "android.infoText";
768
769    /**
770     * {@link #extras} key: this is a line of summary information intended to be shown
771     * alongside expanded notifications, as supplied to (e.g.)
772     * {@link BigTextStyle#setSummaryText(CharSequence)}.
773     */
774    public static final String EXTRA_SUMMARY_TEXT = "android.summaryText";
775
776    /**
777     * {@link #extras} key: this is the longer text shown in the big form of a
778     * {@link BigTextStyle} notification, as supplied to
779     * {@link BigTextStyle#bigText(CharSequence)}.
780     */
781    public static final String EXTRA_BIG_TEXT = "android.bigText";
782
783    /**
784     * {@link #extras} key: this is the resource ID of the notification's main small icon, as
785     * supplied to {@link Builder#setSmallIcon(int)}.
786     */
787    public static final String EXTRA_SMALL_ICON = "android.icon";
788
789    /**
790     * {@link #extras} key: this is a bitmap to be used instead of the small icon when showing the
791     * notification payload, as
792     * supplied to {@link Builder#setLargeIcon(android.graphics.Bitmap)}.
793     */
794    public static final String EXTRA_LARGE_ICON = "android.largeIcon";
795
796    /**
797     * {@link #extras} key: this is a bitmap to be used instead of the one from
798     * {@link Builder#setLargeIcon(android.graphics.Bitmap)} when the notification is
799     * shown in its expanded form, as supplied to
800     * {@link BigPictureStyle#bigLargeIcon(android.graphics.Bitmap)}.
801     */
802    public static final String EXTRA_LARGE_ICON_BIG = EXTRA_LARGE_ICON + ".big";
803
804    /**
805     * {@link #extras} key: this is the progress value supplied to
806     * {@link Builder#setProgress(int, int, boolean)}.
807     */
808    public static final String EXTRA_PROGRESS = "android.progress";
809
810    /**
811     * {@link #extras} key: this is the maximum value supplied to
812     * {@link Builder#setProgress(int, int, boolean)}.
813     */
814    public static final String EXTRA_PROGRESS_MAX = "android.progressMax";
815
816    /**
817     * {@link #extras} key: whether the progress bar is indeterminate, supplied to
818     * {@link Builder#setProgress(int, int, boolean)}.
819     */
820    public static final String EXTRA_PROGRESS_INDETERMINATE = "android.progressIndeterminate";
821
822    /**
823     * {@link #extras} key: whether {@link #when} should be shown as a count-up timer (specifically
824     * a {@link android.widget.Chronometer}) instead of a timestamp, as supplied to
825     * {@link Builder#setUsesChronometer(boolean)}.
826     */
827    public static final String EXTRA_SHOW_CHRONOMETER = "android.showChronometer";
828
829    /**
830     * {@link #extras} key: whether the chronometer set on the notification should count down
831     * instead of counting up. Is only relevant if key {@link #EXTRA_SHOW_CHRONOMETER} is present.
832     */
833    public static final String EXTRA_CHRONOMETER_COUNTS_DOWN = "android.chronometerCountsDown";
834
835    /**
836     * {@link #extras} key: whether {@link #when} should be shown,
837     * as supplied to {@link Builder#setShowWhen(boolean)}.
838     */
839    public static final String EXTRA_SHOW_WHEN = "android.showWhen";
840
841    /**
842     * {@link #extras} key: this is a bitmap to be shown in {@link BigPictureStyle} expanded
843     * notifications, supplied to {@link BigPictureStyle#bigPicture(android.graphics.Bitmap)}.
844     */
845    public static final String EXTRA_PICTURE = "android.picture";
846
847    /**
848     * {@link #extras} key: An array of CharSequences to show in {@link InboxStyle} expanded
849     * notifications, each of which was supplied to {@link InboxStyle#addLine(CharSequence)}.
850     */
851    public static final String EXTRA_TEXT_LINES = "android.textLines";
852
853    /**
854     * {@link #extras} key: A string representing the name of the specific
855     * {@link android.app.Notification.Style} used to create this notification.
856     */
857    public static final String EXTRA_TEMPLATE = "android.template";
858
859    /**
860     * {@link #extras} key: A String array containing the people that this notification relates to,
861     * each of which was supplied to {@link Builder#addPerson(String)}.
862     */
863    public static final String EXTRA_PEOPLE = "android.people";
864
865    /**
866     * Allow certain system-generated notifications to appear before the device is provisioned.
867     * Only available to notifications coming from the android package.
868     * @hide
869     */
870    public static final String EXTRA_ALLOW_DURING_SETUP = "android.allowDuringSetup";
871
872    /**
873     * {@link #extras} key: A
874     * {@link android.content.ContentUris content URI} pointing to an image that can be displayed
875     * in the background when the notification is selected. The URI must point to an image stream
876     * suitable for passing into
877     * {@link android.graphics.BitmapFactory#decodeStream(java.io.InputStream)
878     * BitmapFactory.decodeStream}; all other content types will be ignored. The content provider
879     * URI used for this purpose must require no permissions to read the image data.
880     */
881    public static final String EXTRA_BACKGROUND_IMAGE_URI = "android.backgroundImageUri";
882
883    /**
884     * {@link #extras} key: A
885     * {@link android.media.session.MediaSession.Token} associated with a
886     * {@link android.app.Notification.MediaStyle} notification.
887     */
888    public static final String EXTRA_MEDIA_SESSION = "android.mediaSession";
889
890    /**
891     * {@link #extras} key: the indices of actions to be shown in the compact view,
892     * as supplied to (e.g.) {@link MediaStyle#setShowActionsInCompactView(int...)}.
893     */
894    public static final String EXTRA_COMPACT_ACTIONS = "android.compactActions";
895
896    /**
897     * {@link #extras} key: the user that built the notification.
898     *
899     * @hide
900     */
901    public static final String EXTRA_ORIGINATING_USERID = "android.originatingUserId";
902
903    /**
904     * @hide
905     */
906    public static final String EXTRA_BUILDER_APPLICATION_INFO = "android.appInfo";
907
908    /**
909     * @hide
910     */
911    public static final String EXTRA_CONTAINS_CUSTOM_VIEW = "android.contains.customView";
912
913    private Icon mSmallIcon;
914    private Icon mLargeIcon;
915
916    /**
917     * Structure to encapsulate a named action that can be shown as part of this notification.
918     * It must include an icon, a label, and a {@link PendingIntent} to be fired when the action is
919     * selected by the user.
920     * <p>
921     * Apps should use {@link Notification.Builder#addAction(int, CharSequence, PendingIntent)}
922     * or {@link Notification.Builder#addAction(Notification.Action)}
923     * to attach actions.
924     */
925    public static class Action implements Parcelable {
926        private final Bundle mExtras;
927        private Icon mIcon;
928        private final RemoteInput[] mRemoteInputs;
929
930        /**
931         * Small icon representing the action.
932         *
933         * @deprecated Use {@link Action#getIcon()} instead.
934         */
935        @Deprecated
936        public int icon;
937
938        /**
939         * Title of the action.
940         */
941        public CharSequence title;
942
943        /**
944         * Intent to send when the user invokes this action. May be null, in which case the action
945         * may be rendered in a disabled presentation by the system UI.
946         */
947        public PendingIntent actionIntent;
948
949        private Action(Parcel in) {
950            if (in.readInt() != 0) {
951                mIcon = Icon.CREATOR.createFromParcel(in);
952                if (mIcon.getType() == Icon.TYPE_RESOURCE) {
953                    icon = mIcon.getResId();
954                }
955            }
956            title = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
957            if (in.readInt() == 1) {
958                actionIntent = PendingIntent.CREATOR.createFromParcel(in);
959            }
960            mExtras = in.readBundle();
961            mRemoteInputs = in.createTypedArray(RemoteInput.CREATOR);
962        }
963
964        /**
965         * @deprecated Use {@link android.app.Notification.Action.Builder}.
966         */
967        @Deprecated
968        public Action(int icon, CharSequence title, PendingIntent intent) {
969            this(Icon.createWithResource("", icon), title, intent, new Bundle(), null);
970        }
971
972        private Action(Icon icon, CharSequence title, PendingIntent intent, Bundle extras,
973                RemoteInput[] remoteInputs) {
974            this.mIcon = icon;
975            if (icon != null && icon.getType() == Icon.TYPE_RESOURCE) {
976                this.icon = icon.getResId();
977            }
978            this.title = title;
979            this.actionIntent = intent;
980            this.mExtras = extras != null ? extras : new Bundle();
981            this.mRemoteInputs = remoteInputs;
982        }
983
984        /**
985         * Return an icon representing the action.
986         */
987        public Icon getIcon() {
988            if (mIcon == null && icon != 0) {
989                // you snuck an icon in here without using the builder; let's try to keep it
990                mIcon = Icon.createWithResource("", icon);
991            }
992            return mIcon;
993        }
994
995        /**
996         * Get additional metadata carried around with this Action.
997         */
998        public Bundle getExtras() {
999            return mExtras;
1000        }
1001
1002        /**
1003         * Get the list of inputs to be collected from the user when this action is sent.
1004         * May return null if no remote inputs were added.
1005         */
1006        public RemoteInput[] getRemoteInputs() {
1007            return mRemoteInputs;
1008        }
1009
1010        /**
1011         * Builder class for {@link Action} objects.
1012         */
1013        public static final class Builder {
1014            private final Icon mIcon;
1015            private final CharSequence mTitle;
1016            private final PendingIntent mIntent;
1017            private final Bundle mExtras;
1018            private ArrayList<RemoteInput> mRemoteInputs;
1019
1020            /**
1021             * Construct a new builder for {@link Action} object.
1022             * @param icon icon to show for this action
1023             * @param title the title of the action
1024             * @param intent the {@link PendingIntent} to fire when users trigger this action
1025             */
1026            @Deprecated
1027            public Builder(int icon, CharSequence title, PendingIntent intent) {
1028                this(Icon.createWithResource("", icon), title, intent, new Bundle(), null);
1029            }
1030
1031            /**
1032             * Construct a new builder for {@link Action} object.
1033             * @param icon icon to show for this action
1034             * @param title the title of the action
1035             * @param intent the {@link PendingIntent} to fire when users trigger this action
1036             */
1037            public Builder(Icon icon, CharSequence title, PendingIntent intent) {
1038                this(icon, title, intent, new Bundle(), null);
1039            }
1040
1041            /**
1042             * Construct a new builder for {@link Action} object using the fields from an
1043             * {@link Action}.
1044             * @param action the action to read fields from.
1045             */
1046            public Builder(Action action) {
1047                this(action.getIcon(), action.title, action.actionIntent, new Bundle(action.mExtras),
1048                        action.getRemoteInputs());
1049            }
1050
1051            private Builder(Icon icon, CharSequence title, PendingIntent intent, Bundle extras,
1052                    RemoteInput[] remoteInputs) {
1053                mIcon = icon;
1054                mTitle = title;
1055                mIntent = intent;
1056                mExtras = extras;
1057                if (remoteInputs != null) {
1058                    mRemoteInputs = new ArrayList<RemoteInput>(remoteInputs.length);
1059                    Collections.addAll(mRemoteInputs, remoteInputs);
1060                }
1061            }
1062
1063            /**
1064             * Merge additional metadata into this builder.
1065             *
1066             * <p>Values within the Bundle will replace existing extras values in this Builder.
1067             *
1068             * @see Notification.Action#extras
1069             */
1070            public Builder addExtras(Bundle extras) {
1071                if (extras != null) {
1072                    mExtras.putAll(extras);
1073                }
1074                return this;
1075            }
1076
1077            /**
1078             * Get the metadata Bundle used by this Builder.
1079             *
1080             * <p>The returned Bundle is shared with this Builder.
1081             */
1082            public Bundle getExtras() {
1083                return mExtras;
1084            }
1085
1086            /**
1087             * Add an input to be collected from the user when this action is sent.
1088             * Response values can be retrieved from the fired intent by using the
1089             * {@link RemoteInput#getResultsFromIntent} function.
1090             * @param remoteInput a {@link RemoteInput} to add to the action
1091             * @return this object for method chaining
1092             */
1093            public Builder addRemoteInput(RemoteInput remoteInput) {
1094                if (mRemoteInputs == null) {
1095                    mRemoteInputs = new ArrayList<RemoteInput>();
1096                }
1097                mRemoteInputs.add(remoteInput);
1098                return this;
1099            }
1100
1101            /**
1102             * Apply an extender to this action builder. Extenders may be used to add
1103             * metadata or change options on this builder.
1104             */
1105            public Builder extend(Extender extender) {
1106                extender.extend(this);
1107                return this;
1108            }
1109
1110            /**
1111             * Combine all of the options that have been set and return a new {@link Action}
1112             * object.
1113             * @return the built action
1114             */
1115            public Action build() {
1116                RemoteInput[] remoteInputs = mRemoteInputs != null
1117                        ? mRemoteInputs.toArray(new RemoteInput[mRemoteInputs.size()]) : null;
1118                return new Action(mIcon, mTitle, mIntent, mExtras, remoteInputs);
1119            }
1120        }
1121
1122        @Override
1123        public Action clone() {
1124            return new Action(
1125                    getIcon(),
1126                    title,
1127                    actionIntent, // safe to alias
1128                    new Bundle(mExtras),
1129                    getRemoteInputs());
1130        }
1131        @Override
1132        public int describeContents() {
1133            return 0;
1134        }
1135        @Override
1136        public void writeToParcel(Parcel out, int flags) {
1137            final Icon ic = getIcon();
1138            if (ic != null) {
1139                out.writeInt(1);
1140                ic.writeToParcel(out, 0);
1141            } else {
1142                out.writeInt(0);
1143            }
1144            TextUtils.writeToParcel(title, out, flags);
1145            if (actionIntent != null) {
1146                out.writeInt(1);
1147                actionIntent.writeToParcel(out, flags);
1148            } else {
1149                out.writeInt(0);
1150            }
1151            out.writeBundle(mExtras);
1152            out.writeTypedArray(mRemoteInputs, flags);
1153        }
1154        public static final Parcelable.Creator<Action> CREATOR =
1155                new Parcelable.Creator<Action>() {
1156            public Action createFromParcel(Parcel in) {
1157                return new Action(in);
1158            }
1159            public Action[] newArray(int size) {
1160                return new Action[size];
1161            }
1162        };
1163
1164        /**
1165         * Extender interface for use with {@link Builder#extend}. Extenders may be used to add
1166         * metadata or change options on an action builder.
1167         */
1168        public interface Extender {
1169            /**
1170             * Apply this extender to a notification action builder.
1171             * @param builder the builder to be modified.
1172             * @return the build object for chaining.
1173             */
1174            public Builder extend(Builder builder);
1175        }
1176
1177        /**
1178         * Wearable extender for notification actions. To add extensions to an action,
1179         * create a new {@link android.app.Notification.Action.WearableExtender} object using
1180         * the {@code WearableExtender()} constructor and apply it to a
1181         * {@link android.app.Notification.Action.Builder} using
1182         * {@link android.app.Notification.Action.Builder#extend}.
1183         *
1184         * <pre class="prettyprint">
1185         * Notification.Action action = new Notification.Action.Builder(
1186         *         R.drawable.archive_all, "Archive all", actionIntent)
1187         *         .extend(new Notification.Action.WearableExtender()
1188         *                 .setAvailableOffline(false))
1189         *         .build();</pre>
1190         */
1191        public static final class WearableExtender implements Extender {
1192            /** Notification action extra which contains wearable extensions */
1193            private static final String EXTRA_WEARABLE_EXTENSIONS = "android.wearable.EXTENSIONS";
1194
1195            // Keys within EXTRA_WEARABLE_EXTENSIONS for wearable options.
1196            private static final String KEY_FLAGS = "flags";
1197            private static final String KEY_IN_PROGRESS_LABEL = "inProgressLabel";
1198            private static final String KEY_CONFIRM_LABEL = "confirmLabel";
1199            private static final String KEY_CANCEL_LABEL = "cancelLabel";
1200
1201            // Flags bitwise-ored to mFlags
1202            private static final int FLAG_AVAILABLE_OFFLINE = 0x1;
1203
1204            // Default value for flags integer
1205            private static final int DEFAULT_FLAGS = FLAG_AVAILABLE_OFFLINE;
1206
1207            private int mFlags = DEFAULT_FLAGS;
1208
1209            private CharSequence mInProgressLabel;
1210            private CharSequence mConfirmLabel;
1211            private CharSequence mCancelLabel;
1212
1213            /**
1214             * Create a {@link android.app.Notification.Action.WearableExtender} with default
1215             * options.
1216             */
1217            public WearableExtender() {
1218            }
1219
1220            /**
1221             * Create a {@link android.app.Notification.Action.WearableExtender} by reading
1222             * wearable options present in an existing notification action.
1223             * @param action the notification action to inspect.
1224             */
1225            public WearableExtender(Action action) {
1226                Bundle wearableBundle = action.getExtras().getBundle(EXTRA_WEARABLE_EXTENSIONS);
1227                if (wearableBundle != null) {
1228                    mFlags = wearableBundle.getInt(KEY_FLAGS, DEFAULT_FLAGS);
1229                    mInProgressLabel = wearableBundle.getCharSequence(KEY_IN_PROGRESS_LABEL);
1230                    mConfirmLabel = wearableBundle.getCharSequence(KEY_CONFIRM_LABEL);
1231                    mCancelLabel = wearableBundle.getCharSequence(KEY_CANCEL_LABEL);
1232                }
1233            }
1234
1235            /**
1236             * Apply wearable extensions to a notification action that is being built. This is
1237             * typically called by the {@link android.app.Notification.Action.Builder#extend}
1238             * method of {@link android.app.Notification.Action.Builder}.
1239             */
1240            @Override
1241            public Action.Builder extend(Action.Builder builder) {
1242                Bundle wearableBundle = new Bundle();
1243
1244                if (mFlags != DEFAULT_FLAGS) {
1245                    wearableBundle.putInt(KEY_FLAGS, mFlags);
1246                }
1247                if (mInProgressLabel != null) {
1248                    wearableBundle.putCharSequence(KEY_IN_PROGRESS_LABEL, mInProgressLabel);
1249                }
1250                if (mConfirmLabel != null) {
1251                    wearableBundle.putCharSequence(KEY_CONFIRM_LABEL, mConfirmLabel);
1252                }
1253                if (mCancelLabel != null) {
1254                    wearableBundle.putCharSequence(KEY_CANCEL_LABEL, mCancelLabel);
1255                }
1256
1257                builder.getExtras().putBundle(EXTRA_WEARABLE_EXTENSIONS, wearableBundle);
1258                return builder;
1259            }
1260
1261            @Override
1262            public WearableExtender clone() {
1263                WearableExtender that = new WearableExtender();
1264                that.mFlags = this.mFlags;
1265                that.mInProgressLabel = this.mInProgressLabel;
1266                that.mConfirmLabel = this.mConfirmLabel;
1267                that.mCancelLabel = this.mCancelLabel;
1268                return that;
1269            }
1270
1271            /**
1272             * Set whether this action is available when the wearable device is not connected to
1273             * a companion device. The user can still trigger this action when the wearable device is
1274             * offline, but a visual hint will indicate that the action may not be available.
1275             * Defaults to true.
1276             */
1277            public WearableExtender setAvailableOffline(boolean availableOffline) {
1278                setFlag(FLAG_AVAILABLE_OFFLINE, availableOffline);
1279                return this;
1280            }
1281
1282            /**
1283             * Get whether this action is available when the wearable device is not connected to
1284             * a companion device. The user can still trigger this action when the wearable device is
1285             * offline, but a visual hint will indicate that the action may not be available.
1286             * Defaults to true.
1287             */
1288            public boolean isAvailableOffline() {
1289                return (mFlags & FLAG_AVAILABLE_OFFLINE) != 0;
1290            }
1291
1292            private void setFlag(int mask, boolean value) {
1293                if (value) {
1294                    mFlags |= mask;
1295                } else {
1296                    mFlags &= ~mask;
1297                }
1298            }
1299
1300            /**
1301             * Set a label to display while the wearable is preparing to automatically execute the
1302             * action. This is usually a 'ing' verb ending in ellipsis like "Sending..."
1303             *
1304             * @param label the label to display while the action is being prepared to execute
1305             * @return this object for method chaining
1306             */
1307            public WearableExtender setInProgressLabel(CharSequence label) {
1308                mInProgressLabel = label;
1309                return this;
1310            }
1311
1312            /**
1313             * Get the label to display while the wearable is preparing to automatically execute
1314             * the action. This is usually a 'ing' verb ending in ellipsis like "Sending..."
1315             *
1316             * @return the label to display while the action is being prepared to execute
1317             */
1318            public CharSequence getInProgressLabel() {
1319                return mInProgressLabel;
1320            }
1321
1322            /**
1323             * Set a label to display to confirm that the action should be executed.
1324             * This is usually an imperative verb like "Send".
1325             *
1326             * @param label the label to confirm the action should be executed
1327             * @return this object for method chaining
1328             */
1329            public WearableExtender setConfirmLabel(CharSequence label) {
1330                mConfirmLabel = label;
1331                return this;
1332            }
1333
1334            /**
1335             * Get the label to display to confirm that the action should be executed.
1336             * This is usually an imperative verb like "Send".
1337             *
1338             * @return the label to confirm the action should be executed
1339             */
1340            public CharSequence getConfirmLabel() {
1341                return mConfirmLabel;
1342            }
1343
1344            /**
1345             * Set a label to display to cancel the action.
1346             * This is usually an imperative verb, like "Cancel".
1347             *
1348             * @param label the label to display to cancel the action
1349             * @return this object for method chaining
1350             */
1351            public WearableExtender setCancelLabel(CharSequence label) {
1352                mCancelLabel = label;
1353                return this;
1354            }
1355
1356            /**
1357             * Get the label to display to cancel the action.
1358             * This is usually an imperative verb like "Cancel".
1359             *
1360             * @return the label to display to cancel the action
1361             */
1362            public CharSequence getCancelLabel() {
1363                return mCancelLabel;
1364            }
1365        }
1366    }
1367
1368    /**
1369     * Array of all {@link Action} structures attached to this notification by
1370     * {@link Builder#addAction(int, CharSequence, PendingIntent)}. Mostly useful for instances of
1371     * {@link android.service.notification.NotificationListenerService} that provide an alternative
1372     * interface for invoking actions.
1373     */
1374    public Action[] actions;
1375
1376    /**
1377     * Replacement version of this notification whose content will be shown
1378     * in an insecure context such as atop a secure keyguard. See {@link #visibility}
1379     * and {@link #VISIBILITY_PUBLIC}.
1380     */
1381    public Notification publicVersion;
1382
1383    /**
1384     * Constructs a Notification object with default values.
1385     * You might want to consider using {@link Builder} instead.
1386     */
1387    public Notification()
1388    {
1389        this.when = System.currentTimeMillis();
1390        this.priority = PRIORITY_DEFAULT;
1391    }
1392
1393    /**
1394     * @hide
1395     */
1396    public Notification(Context context, int icon, CharSequence tickerText, long when,
1397            CharSequence contentTitle, CharSequence contentText, Intent contentIntent)
1398    {
1399        new Builder(context)
1400                .setWhen(when)
1401                .setSmallIcon(icon)
1402                .setTicker(tickerText)
1403                .setContentTitle(contentTitle)
1404                .setContentText(contentText)
1405                .setContentIntent(PendingIntent.getActivity(context, 0, contentIntent, 0))
1406                .buildInto(this);
1407    }
1408
1409    /**
1410     * Constructs a Notification object with the information needed to
1411     * have a status bar icon without the standard expanded view.
1412     *
1413     * @param icon          The resource id of the icon to put in the status bar.
1414     * @param tickerText    The text that flows by in the status bar when the notification first
1415     *                      activates.
1416     * @param when          The time to show in the time field.  In the System.currentTimeMillis
1417     *                      timebase.
1418     *
1419     * @deprecated Use {@link Builder} instead.
1420     */
1421    @Deprecated
1422    public Notification(int icon, CharSequence tickerText, long when)
1423    {
1424        this.icon = icon;
1425        this.tickerText = tickerText;
1426        this.when = when;
1427    }
1428
1429    /**
1430     * Unflatten the notification from a parcel.
1431     */
1432    public Notification(Parcel parcel)
1433    {
1434        int version = parcel.readInt();
1435
1436        when = parcel.readLong();
1437        if (parcel.readInt() != 0) {
1438            mSmallIcon = Icon.CREATOR.createFromParcel(parcel);
1439            if (mSmallIcon.getType() == Icon.TYPE_RESOURCE) {
1440                icon = mSmallIcon.getResId();
1441            }
1442        }
1443        number = parcel.readInt();
1444        if (parcel.readInt() != 0) {
1445            contentIntent = PendingIntent.CREATOR.createFromParcel(parcel);
1446        }
1447        if (parcel.readInt() != 0) {
1448            deleteIntent = PendingIntent.CREATOR.createFromParcel(parcel);
1449        }
1450        if (parcel.readInt() != 0) {
1451            tickerText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
1452        }
1453        if (parcel.readInt() != 0) {
1454            tickerView = RemoteViews.CREATOR.createFromParcel(parcel);
1455        }
1456        if (parcel.readInt() != 0) {
1457            contentView = RemoteViews.CREATOR.createFromParcel(parcel);
1458        }
1459        if (parcel.readInt() != 0) {
1460            mLargeIcon = Icon.CREATOR.createFromParcel(parcel);
1461        }
1462        defaults = parcel.readInt();
1463        flags = parcel.readInt();
1464        if (parcel.readInt() != 0) {
1465            sound = Uri.CREATOR.createFromParcel(parcel);
1466        }
1467
1468        audioStreamType = parcel.readInt();
1469        if (parcel.readInt() != 0) {
1470            audioAttributes = AudioAttributes.CREATOR.createFromParcel(parcel);
1471        }
1472        vibrate = parcel.createLongArray();
1473        ledARGB = parcel.readInt();
1474        ledOnMS = parcel.readInt();
1475        ledOffMS = parcel.readInt();
1476        iconLevel = parcel.readInt();
1477
1478        if (parcel.readInt() != 0) {
1479            fullScreenIntent = PendingIntent.CREATOR.createFromParcel(parcel);
1480        }
1481
1482        priority = parcel.readInt();
1483
1484        category = parcel.readString();
1485
1486        mGroupKey = parcel.readString();
1487
1488        mSortKey = parcel.readString();
1489
1490        extras = parcel.readBundle(); // may be null
1491
1492        actions = parcel.createTypedArray(Action.CREATOR); // may be null
1493
1494        if (parcel.readInt() != 0) {
1495            bigContentView = RemoteViews.CREATOR.createFromParcel(parcel);
1496        }
1497
1498        if (parcel.readInt() != 0) {
1499            headsUpContentView = RemoteViews.CREATOR.createFromParcel(parcel);
1500        }
1501
1502        visibility = parcel.readInt();
1503
1504        if (parcel.readInt() != 0) {
1505            publicVersion = Notification.CREATOR.createFromParcel(parcel);
1506        }
1507
1508        color = parcel.readInt();
1509    }
1510
1511    @Override
1512    public Notification clone() {
1513        Notification that = new Notification();
1514        cloneInto(that, true);
1515        return that;
1516    }
1517
1518    /**
1519     * Copy all (or if heavy is false, all except Bitmaps and RemoteViews) members
1520     * of this into that.
1521     * @hide
1522     */
1523    public void cloneInto(Notification that, boolean heavy) {
1524        that.when = this.when;
1525        that.mSmallIcon = this.mSmallIcon;
1526        that.number = this.number;
1527
1528        // PendingIntents are global, so there's no reason (or way) to clone them.
1529        that.contentIntent = this.contentIntent;
1530        that.deleteIntent = this.deleteIntent;
1531        that.fullScreenIntent = this.fullScreenIntent;
1532
1533        if (this.tickerText != null) {
1534            that.tickerText = this.tickerText.toString();
1535        }
1536        if (heavy && this.tickerView != null) {
1537            that.tickerView = this.tickerView.clone();
1538        }
1539        if (heavy && this.contentView != null) {
1540            that.contentView = this.contentView.clone();
1541        }
1542        if (heavy && this.mLargeIcon != null) {
1543            that.mLargeIcon = this.mLargeIcon;
1544        }
1545        that.iconLevel = this.iconLevel;
1546        that.sound = this.sound; // android.net.Uri is immutable
1547        that.audioStreamType = this.audioStreamType;
1548        if (this.audioAttributes != null) {
1549            that.audioAttributes = new AudioAttributes.Builder(this.audioAttributes).build();
1550        }
1551
1552        final long[] vibrate = this.vibrate;
1553        if (vibrate != null) {
1554            final int N = vibrate.length;
1555            final long[] vib = that.vibrate = new long[N];
1556            System.arraycopy(vibrate, 0, vib, 0, N);
1557        }
1558
1559        that.ledARGB = this.ledARGB;
1560        that.ledOnMS = this.ledOnMS;
1561        that.ledOffMS = this.ledOffMS;
1562        that.defaults = this.defaults;
1563
1564        that.flags = this.flags;
1565
1566        that.priority = this.priority;
1567
1568        that.category = this.category;
1569
1570        that.mGroupKey = this.mGroupKey;
1571
1572        that.mSortKey = this.mSortKey;
1573
1574        if (this.extras != null) {
1575            try {
1576                that.extras = new Bundle(this.extras);
1577                // will unparcel
1578                that.extras.size();
1579            } catch (BadParcelableException e) {
1580                Log.e(TAG, "could not unparcel extras from notification: " + this, e);
1581                that.extras = null;
1582            }
1583        }
1584
1585        if (this.actions != null) {
1586            that.actions = new Action[this.actions.length];
1587            for(int i=0; i<this.actions.length; i++) {
1588                that.actions[i] = this.actions[i].clone();
1589            }
1590        }
1591
1592        if (heavy && this.bigContentView != null) {
1593            that.bigContentView = this.bigContentView.clone();
1594        }
1595
1596        if (heavy && this.headsUpContentView != null) {
1597            that.headsUpContentView = this.headsUpContentView.clone();
1598        }
1599
1600        that.visibility = this.visibility;
1601
1602        if (this.publicVersion != null) {
1603            that.publicVersion = new Notification();
1604            this.publicVersion.cloneInto(that.publicVersion, heavy);
1605        }
1606
1607        that.color = this.color;
1608
1609        if (!heavy) {
1610            that.lightenPayload(); // will clean out extras
1611        }
1612    }
1613
1614    /**
1615     * Removes heavyweight parts of the Notification object for archival or for sending to
1616     * listeners when the full contents are not necessary.
1617     * @hide
1618     */
1619    public final void lightenPayload() {
1620        tickerView = null;
1621        contentView = null;
1622        bigContentView = null;
1623        headsUpContentView = null;
1624        mLargeIcon = null;
1625        if (extras != null && !extras.isEmpty()) {
1626            final Set<String> keyset = extras.keySet();
1627            final int N = keyset.size();
1628            final String[] keys = keyset.toArray(new String[N]);
1629            for (int i=0; i<N; i++) {
1630                final String key = keys[i];
1631                final Object obj = extras.get(key);
1632                if (obj != null &&
1633                    (  obj instanceof Parcelable
1634                    || obj instanceof Parcelable[]
1635                    || obj instanceof SparseArray
1636                    || obj instanceof ArrayList)) {
1637                    extras.remove(key);
1638                }
1639            }
1640        }
1641    }
1642
1643    /**
1644     * Make sure this CharSequence is safe to put into a bundle, which basically
1645     * means it had better not be some custom Parcelable implementation.
1646     * @hide
1647     */
1648    public static CharSequence safeCharSequence(CharSequence cs) {
1649        if (cs == null) return cs;
1650        if (cs.length() > MAX_CHARSEQUENCE_LENGTH) {
1651            cs = cs.subSequence(0, MAX_CHARSEQUENCE_LENGTH);
1652        }
1653        if (cs instanceof Parcelable) {
1654            Log.e(TAG, "warning: " + cs.getClass().getCanonicalName()
1655                    + " instance is a custom Parcelable and not allowed in Notification");
1656            return cs.toString();
1657        }
1658        return removeTextSizeSpans(cs);
1659    }
1660
1661    private static CharSequence removeTextSizeSpans(CharSequence charSequence) {
1662        if (charSequence instanceof Spanned) {
1663            Spanned ss = (Spanned) charSequence;
1664            Object[] spans = ss.getSpans(0, ss.length(), Object.class);
1665            SpannableStringBuilder builder = new SpannableStringBuilder(ss.toString());
1666            for (Object span : spans) {
1667                Object resultSpan = span;
1668                if (resultSpan instanceof CharacterStyle) {
1669                    resultSpan = ((CharacterStyle) span).getUnderlying();
1670                }
1671                if (resultSpan instanceof TextAppearanceSpan) {
1672                    TextAppearanceSpan originalSpan = (TextAppearanceSpan) resultSpan;
1673                    resultSpan = new TextAppearanceSpan(
1674                            originalSpan.getFamily(),
1675                            originalSpan.getTextStyle(),
1676                            -1,
1677                            originalSpan.getTextColor(),
1678                            originalSpan.getLinkTextColor());
1679                } else if (resultSpan instanceof RelativeSizeSpan
1680                        || resultSpan instanceof AbsoluteSizeSpan) {
1681                    continue;
1682                } else {
1683                    resultSpan = span;
1684                }
1685                builder.setSpan(resultSpan, ss.getSpanStart(span), ss.getSpanEnd(span),
1686                        ss.getSpanFlags(span));
1687            }
1688            return builder;
1689        }
1690        return charSequence;
1691    }
1692
1693    public int describeContents() {
1694        return 0;
1695    }
1696
1697    /**
1698     * Flatten this notification into a parcel.
1699     */
1700    public void writeToParcel(Parcel parcel, int flags)
1701    {
1702        parcel.writeInt(1);
1703
1704        parcel.writeLong(when);
1705        if (mSmallIcon == null && icon != 0) {
1706            // you snuck an icon in here without using the builder; let's try to keep it
1707            mSmallIcon = Icon.createWithResource("", icon);
1708        }
1709        if (mSmallIcon != null) {
1710            parcel.writeInt(1);
1711            mSmallIcon.writeToParcel(parcel, 0);
1712        } else {
1713            parcel.writeInt(0);
1714        }
1715        parcel.writeInt(number);
1716        if (contentIntent != null) {
1717            parcel.writeInt(1);
1718            contentIntent.writeToParcel(parcel, 0);
1719        } else {
1720            parcel.writeInt(0);
1721        }
1722        if (deleteIntent != null) {
1723            parcel.writeInt(1);
1724            deleteIntent.writeToParcel(parcel, 0);
1725        } else {
1726            parcel.writeInt(0);
1727        }
1728        if (tickerText != null) {
1729            parcel.writeInt(1);
1730            TextUtils.writeToParcel(tickerText, parcel, flags);
1731        } else {
1732            parcel.writeInt(0);
1733        }
1734        if (tickerView != null) {
1735            parcel.writeInt(1);
1736            tickerView.writeToParcel(parcel, 0);
1737        } else {
1738            parcel.writeInt(0);
1739        }
1740        if (contentView != null) {
1741            parcel.writeInt(1);
1742            contentView.writeToParcel(parcel, 0);
1743        } else {
1744            parcel.writeInt(0);
1745        }
1746        if (mLargeIcon != null) {
1747            parcel.writeInt(1);
1748            mLargeIcon.writeToParcel(parcel, 0);
1749        } else {
1750            parcel.writeInt(0);
1751        }
1752
1753        parcel.writeInt(defaults);
1754        parcel.writeInt(this.flags);
1755
1756        if (sound != null) {
1757            parcel.writeInt(1);
1758            sound.writeToParcel(parcel, 0);
1759        } else {
1760            parcel.writeInt(0);
1761        }
1762        parcel.writeInt(audioStreamType);
1763
1764        if (audioAttributes != null) {
1765            parcel.writeInt(1);
1766            audioAttributes.writeToParcel(parcel, 0);
1767        } else {
1768            parcel.writeInt(0);
1769        }
1770
1771        parcel.writeLongArray(vibrate);
1772        parcel.writeInt(ledARGB);
1773        parcel.writeInt(ledOnMS);
1774        parcel.writeInt(ledOffMS);
1775        parcel.writeInt(iconLevel);
1776
1777        if (fullScreenIntent != null) {
1778            parcel.writeInt(1);
1779            fullScreenIntent.writeToParcel(parcel, 0);
1780        } else {
1781            parcel.writeInt(0);
1782        }
1783
1784        parcel.writeInt(priority);
1785
1786        parcel.writeString(category);
1787
1788        parcel.writeString(mGroupKey);
1789
1790        parcel.writeString(mSortKey);
1791
1792        parcel.writeBundle(extras); // null ok
1793
1794        parcel.writeTypedArray(actions, 0); // null ok
1795
1796        if (bigContentView != null) {
1797            parcel.writeInt(1);
1798            bigContentView.writeToParcel(parcel, 0);
1799        } else {
1800            parcel.writeInt(0);
1801        }
1802
1803        if (headsUpContentView != null) {
1804            parcel.writeInt(1);
1805            headsUpContentView.writeToParcel(parcel, 0);
1806        } else {
1807            parcel.writeInt(0);
1808        }
1809
1810        parcel.writeInt(visibility);
1811
1812        if (publicVersion != null) {
1813            parcel.writeInt(1);
1814            publicVersion.writeToParcel(parcel, 0);
1815        } else {
1816            parcel.writeInt(0);
1817        }
1818
1819        parcel.writeInt(color);
1820    }
1821
1822    /**
1823     * Parcelable.Creator that instantiates Notification objects
1824     */
1825    public static final Parcelable.Creator<Notification> CREATOR
1826            = new Parcelable.Creator<Notification>()
1827    {
1828        public Notification createFromParcel(Parcel parcel)
1829        {
1830            return new Notification(parcel);
1831        }
1832
1833        public Notification[] newArray(int size)
1834        {
1835            return new Notification[size];
1836        }
1837    };
1838
1839    /**
1840     * Sets the {@link #contentView} field to be a view with the standard "Latest Event"
1841     * layout.
1842     *
1843     * <p>Uses the {@link #icon} and {@link #when} fields to set the icon and time fields
1844     * in the view.</p>
1845     * @param context       The context for your application / activity.
1846     * @param contentTitle The title that goes in the expanded entry.
1847     * @param contentText  The text that goes in the expanded entry.
1848     * @param contentIntent The intent to launch when the user clicks the expanded notification.
1849     * If this is an activity, it must include the
1850     * {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK} flag, which requires
1851     * that you take care of task management as described in the
1852     * <a href="{@docRoot}guide/topics/fundamentals/tasks-and-back-stack.html">Tasks and Back
1853     * Stack</a> document.
1854     *
1855     * @deprecated Use {@link Builder} instead.
1856     * @removed
1857     */
1858    @Deprecated
1859    public void setLatestEventInfo(Context context,
1860            CharSequence contentTitle, CharSequence contentText, PendingIntent contentIntent) {
1861        if (context.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1){
1862            Log.e(TAG, "setLatestEventInfo() is deprecated and you should feel deprecated.",
1863                    new Throwable());
1864        }
1865
1866        // ensure that any information already set directly is preserved
1867        final Notification.Builder builder = new Notification.Builder(context, this);
1868
1869        // now apply the latestEventInfo fields
1870        if (contentTitle != null) {
1871            builder.setContentTitle(contentTitle);
1872        }
1873        if (contentText != null) {
1874            builder.setContentText(contentText);
1875        }
1876        builder.setContentIntent(contentIntent);
1877
1878        builder.build(); // callers expect this notification to be ready to use
1879    }
1880
1881    /**
1882     * @hide
1883     */
1884    public static void addFieldsFromContext(Context context, Notification notification) {
1885        if (notification.extras.getParcelable(EXTRA_BUILDER_APPLICATION_INFO) == null) {
1886            notification.extras.putParcelable(EXTRA_BUILDER_APPLICATION_INFO,
1887                    context.getApplicationInfo());
1888        }
1889        if (!notification.extras.containsKey(EXTRA_ORIGINATING_USERID)) {
1890            notification.extras.putInt(EXTRA_ORIGINATING_USERID, context.getUserId());
1891        }
1892    }
1893
1894    @Override
1895    public String toString() {
1896        StringBuilder sb = new StringBuilder();
1897        sb.append("Notification(pri=");
1898        sb.append(priority);
1899        sb.append(" contentView=");
1900        if (contentView != null) {
1901            sb.append(contentView.getPackage());
1902            sb.append("/0x");
1903            sb.append(Integer.toHexString(contentView.getLayoutId()));
1904        } else {
1905            sb.append("null");
1906        }
1907        sb.append(" vibrate=");
1908        if ((this.defaults & DEFAULT_VIBRATE) != 0) {
1909            sb.append("default");
1910        } else if (this.vibrate != null) {
1911            int N = this.vibrate.length-1;
1912            sb.append("[");
1913            for (int i=0; i<N; i++) {
1914                sb.append(this.vibrate[i]);
1915                sb.append(',');
1916            }
1917            if (N != -1) {
1918                sb.append(this.vibrate[N]);
1919            }
1920            sb.append("]");
1921        } else {
1922            sb.append("null");
1923        }
1924        sb.append(" sound=");
1925        if ((this.defaults & DEFAULT_SOUND) != 0) {
1926            sb.append("default");
1927        } else if (this.sound != null) {
1928            sb.append(this.sound.toString());
1929        } else {
1930            sb.append("null");
1931        }
1932        if (this.tickerText != null) {
1933            sb.append(" tick");
1934        }
1935        sb.append(" defaults=0x");
1936        sb.append(Integer.toHexString(this.defaults));
1937        sb.append(" flags=0x");
1938        sb.append(Integer.toHexString(this.flags));
1939        sb.append(String.format(" color=0x%08x", this.color));
1940        if (this.category != null) {
1941            sb.append(" category=");
1942            sb.append(this.category);
1943        }
1944        if (this.mGroupKey != null) {
1945            sb.append(" groupKey=");
1946            sb.append(this.mGroupKey);
1947        }
1948        if (this.mSortKey != null) {
1949            sb.append(" sortKey=");
1950            sb.append(this.mSortKey);
1951        }
1952        if (actions != null) {
1953            sb.append(" actions=");
1954            sb.append(actions.length);
1955        }
1956        sb.append(" vis=");
1957        sb.append(visibilityToString(this.visibility));
1958        if (this.publicVersion != null) {
1959            sb.append(" publicVersion=");
1960            sb.append(publicVersion.toString());
1961        }
1962        sb.append(")");
1963        return sb.toString();
1964    }
1965
1966    /**
1967     * {@hide}
1968     */
1969    public static String visibilityToString(int vis) {
1970        switch (vis) {
1971            case VISIBILITY_PRIVATE:
1972                return "PRIVATE";
1973            case VISIBILITY_PUBLIC:
1974                return "PUBLIC";
1975            case VISIBILITY_SECRET:
1976                return "SECRET";
1977            default:
1978                return "UNKNOWN(" + String.valueOf(vis) + ")";
1979        }
1980    }
1981
1982    /**
1983     * {@hide}
1984     */
1985    public static String priorityToString(@Priority int pri) {
1986        switch (pri) {
1987            case PRIORITY_MIN:
1988                return "MIN";
1989            case PRIORITY_LOW:
1990                return "LOW";
1991            case PRIORITY_DEFAULT:
1992                return "DEFAULT";
1993            case PRIORITY_HIGH:
1994                return "HIGH";
1995            case PRIORITY_MAX:
1996                return "MAX";
1997            default:
1998                return "UNKNOWN(" + String.valueOf(pri) + ")";
1999        }
2000    }
2001
2002    /**
2003     * The small icon representing this notification in the status bar and content view.
2004     *
2005     * @return the small icon representing this notification.
2006     *
2007     * @see Builder#getSmallIcon()
2008     * @see Builder#setSmallIcon(Icon)
2009     */
2010    public Icon getSmallIcon() {
2011        return mSmallIcon;
2012    }
2013
2014    /**
2015     * Used when notifying to clean up legacy small icons.
2016     * @hide
2017     */
2018    public void setSmallIcon(Icon icon) {
2019        mSmallIcon = icon;
2020    }
2021
2022    /**
2023     * The large icon shown in this notification's content view.
2024     * @see Builder#getLargeIcon()
2025     * @see Builder#setLargeIcon(Icon)
2026     */
2027    public Icon getLargeIcon() {
2028        return mLargeIcon;
2029    }
2030
2031    /**
2032     * @hide
2033     */
2034    public boolean isGroupSummary() {
2035        return mGroupKey != null && (flags & FLAG_GROUP_SUMMARY) != 0;
2036    }
2037
2038    /**
2039     * @hide
2040     */
2041    public boolean isGroupChild() {
2042        return mGroupKey != null && (flags & FLAG_GROUP_SUMMARY) == 0;
2043    }
2044
2045    /**
2046     * Builder class for {@link Notification} objects.
2047     *
2048     * Provides a convenient way to set the various fields of a {@link Notification} and generate
2049     * content views using the platform's notification layout template. If your app supports
2050     * versions of Android as old as API level 4, you can instead use
2051     * {@link android.support.v4.app.NotificationCompat.Builder NotificationCompat.Builder},
2052     * available in the <a href="{@docRoot}tools/extras/support-library.html">Android Support
2053     * library</a>.
2054     *
2055     * <p>Example:
2056     *
2057     * <pre class="prettyprint">
2058     * Notification noti = new Notification.Builder(mContext)
2059     *         .setContentTitle(&quot;New mail from &quot; + sender.toString())
2060     *         .setContentText(subject)
2061     *         .setSmallIcon(R.drawable.new_mail)
2062     *         .setLargeIcon(aBitmap)
2063     *         .build();
2064     * </pre>
2065     */
2066    public static class Builder {
2067        /**
2068         * @hide
2069         */
2070        public static final String EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT =
2071                "android.rebuild.contentViewActionCount";
2072        /**
2073         * @hide
2074         */
2075        public static final String EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT
2076                = "android.rebuild.bigViewActionCount";
2077        /**
2078         * @hide
2079         */
2080        public static final String EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT
2081                = "android.rebuild.hudViewActionCount";
2082
2083        private static final int MAX_ACTION_BUTTONS = 3;
2084
2085        private Context mContext;
2086        private Notification mN;
2087        private Bundle mUserExtras = new Bundle();
2088        private Style mStyle;
2089        private ArrayList<Action> mActions = new ArrayList<Action>(MAX_ACTION_BUTTONS);
2090        private ArrayList<String> mPersonList = new ArrayList<String>();
2091        private NotificationColorUtil mColorUtil;
2092        private boolean mColorUtilInited = false;
2093
2094        /**
2095         * Constructs a new Builder with the defaults:
2096         *
2097
2098         * <table>
2099         * <tr><th align=right>priority</th>
2100         *     <td>{@link #PRIORITY_DEFAULT}</td></tr>
2101         * <tr><th align=right>when</th>
2102         *     <td>now ({@link System#currentTimeMillis()})</td></tr>
2103         * <tr><th align=right>audio stream</th>
2104         *     <td>{@link #STREAM_DEFAULT}</td></tr>
2105         * </table>
2106         *
2107
2108         * @param context
2109         *            A {@link Context} that will be used by the Builder to construct the
2110         *            RemoteViews. The Context will not be held past the lifetime of this Builder
2111         *            object.
2112         */
2113        public Builder(Context context) {
2114            this(context, null);
2115        }
2116
2117        /**
2118         * @hide
2119         */
2120        public Builder(Context context, Notification toAdopt) {
2121            mContext = context;
2122
2123            if (toAdopt == null) {
2124                mN = new Notification();
2125                mN.extras.putBoolean(EXTRA_SHOW_WHEN, true);
2126                mN.priority = PRIORITY_DEFAULT;
2127                mN.visibility = VISIBILITY_PRIVATE;
2128            } else {
2129                mN = toAdopt;
2130                if (mN.actions != null) {
2131                    Collections.addAll(mActions, mN.actions);
2132                }
2133
2134                if (mN.extras.containsKey(EXTRA_PEOPLE)) {
2135                    Collections.addAll(mPersonList, mN.extras.getStringArray(EXTRA_PEOPLE));
2136                }
2137
2138                String templateClass = mN.extras.getString(EXTRA_TEMPLATE);
2139                if (!TextUtils.isEmpty(templateClass)) {
2140                    final Class<? extends Style> styleClass
2141                            = getNotificationStyleClass(templateClass);
2142                    if (styleClass == null) {
2143                        Log.d(TAG, "Unknown style class: " + templateClass);
2144                    } else {
2145                        try {
2146                            final Constructor<? extends Style> ctor = styleClass.getConstructor();
2147                            ctor.setAccessible(true);
2148                            final Style style = ctor.newInstance();
2149                            style.restoreFromExtras(mN.extras);
2150
2151                            if (style != null) {
2152                                setStyle(style);
2153                            }
2154                        } catch (Throwable t) {
2155                            Log.e(TAG, "Could not create Style", t);
2156                        }
2157                    }
2158                }
2159
2160            }
2161        }
2162
2163        private NotificationColorUtil getColorUtil() {
2164            if (!mColorUtilInited) {
2165                mColorUtilInited = true;
2166                if (mContext.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.LOLLIPOP) {
2167                    mColorUtil = NotificationColorUtil.getInstance(mContext);
2168                }
2169            }
2170            return mColorUtil;
2171        }
2172
2173        /**
2174         * Add a timestamp pertaining to the notification (usually the time the event occurred).
2175         * It will be shown in the notification content view by default; use
2176         * {@link #setShowWhen(boolean) setShowWhen} to control this.
2177         *
2178         * @see Notification#when
2179         */
2180        public Builder setWhen(long when) {
2181            mN.when = when;
2182            return this;
2183        }
2184
2185        /**
2186         * Control whether the timestamp set with {@link #setWhen(long) setWhen} is shown
2187         * in the content view.
2188         */
2189        public Builder setShowWhen(boolean show) {
2190            mN.extras.putBoolean(EXTRA_SHOW_WHEN, show);
2191            return this;
2192        }
2193
2194        /**
2195         * Show the {@link Notification#when} field as a stopwatch.
2196         *
2197         * Instead of presenting <code>when</code> as a timestamp, the notification will show an
2198         * automatically updating display of the minutes and seconds since <code>when</code>.
2199         *
2200         * Useful when showing an elapsed time (like an ongoing phone call).
2201         *
2202         * The counter can also be set to count down to <code>when</code> when using
2203         * {@link #setChronometerCountsDown(boolean)}.
2204         *
2205         * @see android.widget.Chronometer
2206         * @see Notification#when
2207         * @see #setChronometerCountsDown(boolean)
2208         */
2209        public Builder setUsesChronometer(boolean b) {
2210            mN.extras.putBoolean(EXTRA_SHOW_CHRONOMETER, b);
2211            return this;
2212        }
2213
2214        /**
2215         * Sets the Chronometer to count down instead of counting up.
2216         *
2217         * <p>This is only relevant if {@link #setUsesChronometer(boolean)} has been set to true.
2218         * If it isn't set the chronometer will count up.
2219         *
2220         * @see #setUsesChronometer(boolean)
2221         */
2222        public Builder setChronometerCountsDown(boolean countsDown) {
2223            mN.extras.putBoolean(EXTRA_CHRONOMETER_COUNTS_DOWN, countsDown);
2224            return this;
2225        }
2226
2227        /**
2228         * Set the small icon resource, which will be used to represent the notification in the
2229         * status bar.
2230         *
2231
2232         * The platform template for the expanded view will draw this icon in the left, unless a
2233         * {@link #setLargeIcon(Bitmap) large icon} has also been specified, in which case the small
2234         * icon will be moved to the right-hand side.
2235         *
2236
2237         * @param icon
2238         *            A resource ID in the application's package of the drawable to use.
2239         * @see Notification#icon
2240         */
2241        public Builder setSmallIcon(@DrawableRes int icon) {
2242            return setSmallIcon(icon != 0
2243                    ? Icon.createWithResource(mContext, icon)
2244                    : null);
2245        }
2246
2247        /**
2248         * A variant of {@link #setSmallIcon(int) setSmallIcon(int)} that takes an additional
2249         * level parameter for when the icon is a {@link android.graphics.drawable.LevelListDrawable
2250         * LevelListDrawable}.
2251         *
2252         * @param icon A resource ID in the application's package of the drawable to use.
2253         * @param level The level to use for the icon.
2254         *
2255         * @see Notification#icon
2256         * @see Notification#iconLevel
2257         */
2258        public Builder setSmallIcon(@DrawableRes int icon, int level) {
2259            mN.iconLevel = level;
2260            return setSmallIcon(icon);
2261        }
2262
2263        /**
2264         * Set the small icon, which will be used to represent the notification in the
2265         * status bar and content view (unless overriden there by a
2266         * {@link #setLargeIcon(Bitmap) large icon}).
2267         *
2268         * @param icon An Icon object to use.
2269         * @see Notification#icon
2270         */
2271        public Builder setSmallIcon(Icon icon) {
2272            mN.setSmallIcon(icon);
2273            if (icon != null && icon.getType() == Icon.TYPE_RESOURCE) {
2274                mN.icon = icon.getResId();
2275            }
2276            return this;
2277        }
2278
2279        /**
2280         * Set the first line of text in the platform notification template.
2281         */
2282        public Builder setContentTitle(CharSequence title) {
2283            mN.extras.putCharSequence(EXTRA_TITLE, safeCharSequence(title));
2284            return this;
2285        }
2286
2287        /**
2288         * Set the second line of text in the platform notification template.
2289         */
2290        public Builder setContentText(CharSequence text) {
2291            mN.extras.putCharSequence(EXTRA_TEXT, safeCharSequence(text));
2292            return this;
2293        }
2294
2295        /**
2296         * Set the third line of text in the platform notification template.
2297         * Don't use if you're also using {@link #setProgress(int, int, boolean)}; they occupy the
2298         * same location in the standard template.
2299         */
2300        public Builder setSubText(CharSequence text) {
2301            mN.extras.putCharSequence(EXTRA_SUB_TEXT, safeCharSequence(text));
2302            return this;
2303        }
2304
2305        /**
2306         * Set the remote input history.
2307         *
2308         * This should be set to the most recent inputs that have been sent
2309         * through a {@link RemoteInput} of this Notification and cleared once the it is no
2310         * longer relevant (e.g. for chat notifications once the other party has responded).
2311         *
2312         * The most recent input must be stored at the 0 index, the second most recent at the
2313         * 1 index, etc. Note that the system will limit both how far back the inputs will be shown
2314         * and how much of each individual input is shown.
2315         *
2316         * <p>Note: The reply text will only be shown on notifications that have least one action
2317         * with a {@code RemoteInput}.</p>
2318         */
2319        public Builder setRemoteInputHistory(CharSequence[] text) {
2320            if (text == null) {
2321                mN.extras.putCharSequenceArray(EXTRA_REMOTE_INPUT_HISTORY, null);
2322            } else {
2323                final int N = Math.min(MAX_REPLY_HISTORY, text.length);
2324                CharSequence[] safe = new CharSequence[N];
2325                for (int i = 0; i < N; i++) {
2326                    safe[i] = safeCharSequence(text[i]);
2327                }
2328                mN.extras.putCharSequenceArray(EXTRA_REMOTE_INPUT_HISTORY, safe);
2329            }
2330            return this;
2331        }
2332
2333        /**
2334         * Set the large number at the right-hand side of the notification.  This is
2335         * equivalent to setContentInfo, although it might show the number in a different
2336         * font size for readability.
2337         */
2338        public Builder setNumber(int number) {
2339            mN.number = number;
2340            return this;
2341        }
2342
2343        /**
2344         * A small piece of additional information pertaining to this notification.
2345         *
2346         * The platform template will draw this on the last line of the notification, at the far
2347         * right (to the right of a smallIcon if it has been placed there).
2348         */
2349        public Builder setContentInfo(CharSequence info) {
2350            mN.extras.putCharSequence(EXTRA_INFO_TEXT, safeCharSequence(info));
2351            return this;
2352        }
2353
2354        /**
2355         * Set the progress this notification represents.
2356         *
2357         * The platform template will represent this using a {@link ProgressBar}.
2358         */
2359        public Builder setProgress(int max, int progress, boolean indeterminate) {
2360            mN.extras.putInt(EXTRA_PROGRESS, progress);
2361            mN.extras.putInt(EXTRA_PROGRESS_MAX, max);
2362            mN.extras.putBoolean(EXTRA_PROGRESS_INDETERMINATE, indeterminate);
2363            return this;
2364        }
2365
2366        /**
2367         * Supply a custom RemoteViews to use instead of the platform template.
2368         *
2369         * Use {@link #setCustomContentView(RemoteViews)} instead.
2370         */
2371        @Deprecated
2372        public Builder setContent(RemoteViews views) {
2373            return setCustomContentView(views);
2374        }
2375
2376        /**
2377         * Supply custom RemoteViews to use instead of the platform template.
2378         *
2379         * This will override the layout that would otherwise be constructed by this Builder
2380         * object.
2381         */
2382        public Builder setCustomContentView(RemoteViews contentView) {
2383            mN.contentView = contentView;
2384            return this;
2385        }
2386
2387        /**
2388         * Supply custom RemoteViews to use instead of the platform template in the expanded form.
2389         *
2390         * This will override the expanded layout that would otherwise be constructed by this
2391         * Builder object.
2392         */
2393        public Builder setCustomBigContentView(RemoteViews contentView) {
2394            mN.bigContentView = contentView;
2395            return this;
2396        }
2397
2398        /**
2399         * Supply custom RemoteViews to use instead of the platform template in the heads up dialog.
2400         *
2401         * This will override the heads-up layout that would otherwise be constructed by this
2402         * Builder object.
2403         */
2404        public Builder setCustomHeadsUpContentView(RemoteViews contentView) {
2405            mN.headsUpContentView = contentView;
2406            return this;
2407        }
2408
2409        /**
2410         * Supply a {@link PendingIntent} to be sent when the notification is clicked.
2411         *
2412         * As of {@link android.os.Build.VERSION_CODES#HONEYCOMB}, if this field is unset and you
2413         * have specified a custom RemoteViews with {@link #setContent(RemoteViews)}, you can use
2414         * {@link RemoteViews#setOnClickPendingIntent RemoteViews.setOnClickPendingIntent(int,PendingIntent)}
2415         * to assign PendingIntents to individual views in that custom layout (i.e., to create
2416         * clickable buttons inside the notification view).
2417         *
2418         * @see Notification#contentIntent Notification.contentIntent
2419         */
2420        public Builder setContentIntent(PendingIntent intent) {
2421            mN.contentIntent = intent;
2422            return this;
2423        }
2424
2425        /**
2426         * Supply a {@link PendingIntent} to send when the notification is cleared explicitly by the user.
2427         *
2428         * @see Notification#deleteIntent
2429         */
2430        public Builder setDeleteIntent(PendingIntent intent) {
2431            mN.deleteIntent = intent;
2432            return this;
2433        }
2434
2435        /**
2436         * An intent to launch instead of posting the notification to the status bar.
2437         * Only for use with extremely high-priority notifications demanding the user's
2438         * <strong>immediate</strong> attention, such as an incoming phone call or
2439         * alarm clock that the user has explicitly set to a particular time.
2440         * If this facility is used for something else, please give the user an option
2441         * to turn it off and use a normal notification, as this can be extremely
2442         * disruptive.
2443         *
2444         * <p>
2445         * The system UI may choose to display a heads-up notification, instead of
2446         * launching this intent, while the user is using the device.
2447         * </p>
2448         *
2449         * @param intent The pending intent to launch.
2450         * @param highPriority Passing true will cause this notification to be sent
2451         *          even if other notifications are suppressed.
2452         *
2453         * @see Notification#fullScreenIntent
2454         */
2455        public Builder setFullScreenIntent(PendingIntent intent, boolean highPriority) {
2456            mN.fullScreenIntent = intent;
2457            setFlag(FLAG_HIGH_PRIORITY, highPriority);
2458            return this;
2459        }
2460
2461        /**
2462         * Set the "ticker" text which is sent to accessibility services.
2463         *
2464         * @see Notification#tickerText
2465         */
2466        public Builder setTicker(CharSequence tickerText) {
2467            mN.tickerText = safeCharSequence(tickerText);
2468            return this;
2469        }
2470
2471        /**
2472         * Obsolete version of {@link #setTicker(CharSequence)}.
2473         *
2474         */
2475        @Deprecated
2476        public Builder setTicker(CharSequence tickerText, RemoteViews views) {
2477            setTicker(tickerText);
2478            // views is ignored
2479            return this;
2480        }
2481
2482        /**
2483         * Add a large icon to the notification content view.
2484         *
2485         * In the platform template, this image will be shown on the left of the notification view
2486         * in place of the {@link #setSmallIcon(Icon) small icon} (which will be placed in a small
2487         * badge atop the large icon).
2488         */
2489        public Builder setLargeIcon(Bitmap b) {
2490            return setLargeIcon(b != null ? Icon.createWithBitmap(b) : null);
2491        }
2492
2493        /**
2494         * Add a large icon to the notification content view.
2495         *
2496         * In the platform template, this image will be shown on the left of the notification view
2497         * in place of the {@link #setSmallIcon(Icon) small icon} (which will be placed in a small
2498         * badge atop the large icon).
2499         */
2500        public Builder setLargeIcon(Icon icon) {
2501            mN.mLargeIcon = icon;
2502            mN.extras.putParcelable(EXTRA_LARGE_ICON, icon);
2503            return this;
2504        }
2505
2506        /**
2507         * Set the sound to play.
2508         *
2509         * It will be played using the {@link #AUDIO_ATTRIBUTES_DEFAULT default audio attributes}
2510         * for notifications.
2511         *
2512         * <p>
2513         * A notification that is noisy is more likely to be presented as a heads-up notification.
2514         * </p>
2515         *
2516         * @see Notification#sound
2517         */
2518        public Builder setSound(Uri sound) {
2519            mN.sound = sound;
2520            mN.audioAttributes = AUDIO_ATTRIBUTES_DEFAULT;
2521            return this;
2522        }
2523
2524        /**
2525         * Set the sound to play, along with a specific stream on which to play it.
2526         *
2527         * See {@link android.media.AudioManager} for the <code>STREAM_</code> constants.
2528         *
2529         * <p>
2530         * A notification that is noisy is more likely to be presented as a heads-up notification.
2531         * </p>
2532         * @deprecated use {@link #setSound(Uri, AudioAttributes)} instead.
2533         * @see Notification#sound
2534         */
2535        @Deprecated
2536        public Builder setSound(Uri sound, int streamType) {
2537            mN.sound = sound;
2538            mN.audioStreamType = streamType;
2539            return this;
2540        }
2541
2542        /**
2543         * Set the sound to play, along with specific {@link AudioAttributes audio attributes} to
2544         * use during playback.
2545         *
2546         * <p>
2547         * A notification that is noisy is more likely to be presented as a heads-up notification.
2548         * </p>
2549         *
2550         * @see Notification#sound
2551         */
2552        public Builder setSound(Uri sound, AudioAttributes audioAttributes) {
2553            mN.sound = sound;
2554            mN.audioAttributes = audioAttributes;
2555            return this;
2556        }
2557
2558        /**
2559         * Set the vibration pattern to use.
2560         *
2561         * See {@link android.os.Vibrator#vibrate(long[], int)} for a discussion of the
2562         * <code>pattern</code> parameter.
2563         *
2564         * <p>
2565         * A notification that vibrates is more likely to be presented as a heads-up notification.
2566         * </p>
2567         *
2568         * @see Notification#vibrate
2569         */
2570        public Builder setVibrate(long[] pattern) {
2571            mN.vibrate = pattern;
2572            return this;
2573        }
2574
2575        /**
2576         * Set the desired color for the indicator LED on the device, as well as the
2577         * blink duty cycle (specified in milliseconds).
2578         *
2579
2580         * Not all devices will honor all (or even any) of these values.
2581         *
2582
2583         * @see Notification#ledARGB
2584         * @see Notification#ledOnMS
2585         * @see Notification#ledOffMS
2586         */
2587        public Builder setLights(@ColorInt int argb, int onMs, int offMs) {
2588            mN.ledARGB = argb;
2589            mN.ledOnMS = onMs;
2590            mN.ledOffMS = offMs;
2591            if (onMs != 0 || offMs != 0) {
2592                mN.flags |= FLAG_SHOW_LIGHTS;
2593            }
2594            return this;
2595        }
2596
2597        /**
2598         * Set whether this is an "ongoing" notification.
2599         *
2600
2601         * Ongoing notifications cannot be dismissed by the user, so your application or service
2602         * must take care of canceling them.
2603         *
2604
2605         * They are typically used to indicate a background task that the user is actively engaged
2606         * with (e.g., playing music) or is pending in some way and therefore occupying the device
2607         * (e.g., a file download, sync operation, active network connection).
2608         *
2609
2610         * @see Notification#FLAG_ONGOING_EVENT
2611         * @see Service#setForeground(boolean)
2612         */
2613        public Builder setOngoing(boolean ongoing) {
2614            setFlag(FLAG_ONGOING_EVENT, ongoing);
2615            return this;
2616        }
2617
2618        /**
2619         * Set this flag if you would only like the sound, vibrate
2620         * and ticker to be played if the notification is not already showing.
2621         *
2622         * @see Notification#FLAG_ONLY_ALERT_ONCE
2623         */
2624        public Builder setOnlyAlertOnce(boolean onlyAlertOnce) {
2625            setFlag(FLAG_ONLY_ALERT_ONCE, onlyAlertOnce);
2626            return this;
2627        }
2628
2629        /**
2630         * Make this notification automatically dismissed when the user touches it. The
2631         * PendingIntent set with {@link #setDeleteIntent} will be sent when this happens.
2632         *
2633         * @see Notification#FLAG_AUTO_CANCEL
2634         */
2635        public Builder setAutoCancel(boolean autoCancel) {
2636            setFlag(FLAG_AUTO_CANCEL, autoCancel);
2637            return this;
2638        }
2639
2640        /**
2641         * Set whether or not this notification should not bridge to other devices.
2642         *
2643         * <p>Some notifications can be bridged to other devices for remote display.
2644         * This hint can be set to recommend this notification not be bridged.
2645         */
2646        public Builder setLocalOnly(boolean localOnly) {
2647            setFlag(FLAG_LOCAL_ONLY, localOnly);
2648            return this;
2649        }
2650
2651        /**
2652         * Set which notification properties will be inherited from system defaults.
2653         * <p>
2654         * The value should be one or more of the following fields combined with
2655         * bitwise-or:
2656         * {@link #DEFAULT_SOUND}, {@link #DEFAULT_VIBRATE}, {@link #DEFAULT_LIGHTS}.
2657         * <p>
2658         * For all default values, use {@link #DEFAULT_ALL}.
2659         */
2660        public Builder setDefaults(int defaults) {
2661            mN.defaults = defaults;
2662            return this;
2663        }
2664
2665        /**
2666         * Set the priority of this notification.
2667         *
2668         * @see Notification#priority
2669         */
2670        public Builder setPriority(@Priority int pri) {
2671            mN.priority = pri;
2672            return this;
2673        }
2674
2675        /**
2676         * Set the notification category.
2677         *
2678         * @see Notification#category
2679         */
2680        public Builder setCategory(String category) {
2681            mN.category = category;
2682            return this;
2683        }
2684
2685        /**
2686         * Add a person that is relevant to this notification.
2687         *
2688         * <P>
2689         * Depending on user preferences, this annotation may allow the notification to pass
2690         * through interruption filters, and to appear more prominently in the user interface.
2691         * </P>
2692         *
2693         * <P>
2694         * The person should be specified by the {@code String} representation of a
2695         * {@link android.provider.ContactsContract.Contacts#CONTENT_LOOKUP_URI}.
2696         * </P>
2697         *
2698         * <P>The system will also attempt to resolve {@code mailto:} and {@code tel:} schema
2699         * URIs.  The path part of these URIs must exist in the contacts database, in the
2700         * appropriate column, or the reference will be discarded as invalid. Telephone schema
2701         * URIs will be resolved by {@link android.provider.ContactsContract.PhoneLookup}.
2702         * </P>
2703         *
2704         * @param uri A URI for the person.
2705         * @see Notification#EXTRA_PEOPLE
2706         */
2707        public Builder addPerson(String uri) {
2708            mPersonList.add(uri);
2709            return this;
2710        }
2711
2712        /**
2713         * Set this notification to be part of a group of notifications sharing the same key.
2714         * Grouped notifications may display in a cluster or stack on devices which
2715         * support such rendering.
2716         *
2717         * <p>To make this notification the summary for its group, also call
2718         * {@link #setGroupSummary}. A sort order can be specified for group members by using
2719         * {@link #setSortKey}.
2720         * @param groupKey The group key of the group.
2721         * @return this object for method chaining
2722         */
2723        public Builder setGroup(String groupKey) {
2724            mN.mGroupKey = groupKey;
2725            return this;
2726        }
2727
2728        /**
2729         * Set this notification to be the group summary for a group of notifications.
2730         * Grouped notifications may display in a cluster or stack on devices which
2731         * support such rendering. Requires a group key also be set using {@link #setGroup}.
2732         * @param isGroupSummary Whether this notification should be a group summary.
2733         * @return this object for method chaining
2734         */
2735        public Builder setGroupSummary(boolean isGroupSummary) {
2736            setFlag(FLAG_GROUP_SUMMARY, isGroupSummary);
2737            return this;
2738        }
2739
2740        /**
2741         * Set a sort key that orders this notification among other notifications from the
2742         * same package. This can be useful if an external sort was already applied and an app
2743         * would like to preserve this. Notifications will be sorted lexicographically using this
2744         * value, although providing different priorities in addition to providing sort key may
2745         * cause this value to be ignored.
2746         *
2747         * <p>This sort key can also be used to order members of a notification group. See
2748         * {@link #setGroup}.
2749         *
2750         * @see String#compareTo(String)
2751         */
2752        public Builder setSortKey(String sortKey) {
2753            mN.mSortKey = sortKey;
2754            return this;
2755        }
2756
2757        /**
2758         * Merge additional metadata into this notification.
2759         *
2760         * <p>Values within the Bundle will replace existing extras values in this Builder.
2761         *
2762         * @see Notification#extras
2763         */
2764        public Builder addExtras(Bundle extras) {
2765            if (extras != null) {
2766                mUserExtras.putAll(extras);
2767            }
2768            return this;
2769        }
2770
2771        /**
2772         * Set metadata for this notification.
2773         *
2774         * <p>A reference to the Bundle is held for the lifetime of this Builder, and the Bundle's
2775         * current contents are copied into the Notification each time {@link #build()} is
2776         * called.
2777         *
2778         * <p>Replaces any existing extras values with those from the provided Bundle.
2779         * Use {@link #addExtras} to merge in metadata instead.
2780         *
2781         * @see Notification#extras
2782         */
2783        public Builder setExtras(Bundle extras) {
2784            if (extras != null) {
2785                mUserExtras = extras;
2786            }
2787            return this;
2788        }
2789
2790        /**
2791         * Get the current metadata Bundle used by this notification Builder.
2792         *
2793         * <p>The returned Bundle is shared with this Builder.
2794         *
2795         * <p>The current contents of this Bundle are copied into the Notification each time
2796         * {@link #build()} is called.
2797         *
2798         * @see Notification#extras
2799         */
2800        public Bundle getExtras() {
2801            return mUserExtras;
2802        }
2803
2804        private Bundle getAllExtras() {
2805            final Bundle saveExtras = (Bundle) mUserExtras.clone();
2806            saveExtras.putAll(mN.extras);
2807            return saveExtras;
2808        }
2809
2810        /**
2811         * Add an action to this notification. Actions are typically displayed by
2812         * the system as a button adjacent to the notification content.
2813         * <p>
2814         * Every action must have an icon (32dp square and matching the
2815         * <a href="{@docRoot}design/style/iconography.html#action-bar">Holo
2816         * Dark action bar</a> visual style), a textual label, and a {@link PendingIntent}.
2817         * <p>
2818         * A notification in its expanded form can display up to 3 actions, from left to right in
2819         * the order they were added. Actions will not be displayed when the notification is
2820         * collapsed, however, so be sure that any essential functions may be accessed by the user
2821         * in some other way (for example, in the Activity pointed to by {@link #contentIntent}).
2822         *
2823         * @param icon Resource ID of a drawable that represents the action.
2824         * @param title Text describing the action.
2825         * @param intent PendingIntent to be fired when the action is invoked.
2826         *
2827         * @deprecated Use {@link #addAction(Action)} instead.
2828         */
2829        @Deprecated
2830        public Builder addAction(int icon, CharSequence title, PendingIntent intent) {
2831            mActions.add(new Action(icon, safeCharSequence(title), intent));
2832            return this;
2833        }
2834
2835        /**
2836         * Add an action to this notification. Actions are typically displayed by
2837         * the system as a button adjacent to the notification content.
2838         * <p>
2839         * Every action must have an icon (32dp square and matching the
2840         * <a href="{@docRoot}design/style/iconography.html#action-bar">Holo
2841         * Dark action bar</a> visual style), a textual label, and a {@link PendingIntent}.
2842         * <p>
2843         * A notification in its expanded form can display up to 3 actions, from left to right in
2844         * the order they were added. Actions will not be displayed when the notification is
2845         * collapsed, however, so be sure that any essential functions may be accessed by the user
2846         * in some other way (for example, in the Activity pointed to by {@link #contentIntent}).
2847         *
2848         * @param action The action to add.
2849         */
2850        public Builder addAction(Action action) {
2851            mActions.add(action);
2852            return this;
2853        }
2854
2855        /**
2856         * Alter the complete list of actions attached to this notification.
2857         * @see #addAction(Action).
2858         *
2859         * @param actions
2860         * @return
2861         */
2862        public Builder setActions(Action... actions) {
2863            mActions.clear();
2864            for (int i = 0; i < actions.length; i++) {
2865                mActions.add(actions[i]);
2866            }
2867            return this;
2868        }
2869
2870        /**
2871         * Add a rich notification style to be applied at build time.
2872         *
2873         * @param style Object responsible for modifying the notification style.
2874         */
2875        public Builder setStyle(Style style) {
2876            if (mStyle != style) {
2877                mStyle = style;
2878                if (mStyle != null) {
2879                    mStyle.setBuilder(this);
2880                    mN.extras.putString(EXTRA_TEMPLATE, style.getClass().getName());
2881                }  else {
2882                    mN.extras.remove(EXTRA_TEMPLATE);
2883                }
2884            }
2885            return this;
2886        }
2887
2888        /**
2889         * Specify the value of {@link #visibility}.
2890         *
2891         * @param visibility One of {@link #VISIBILITY_PRIVATE} (the default),
2892         * {@link #VISIBILITY_SECRET}, or {@link #VISIBILITY_PUBLIC}.
2893         *
2894         * @return The same Builder.
2895         */
2896        public Builder setVisibility(int visibility) {
2897            mN.visibility = visibility;
2898            return this;
2899        }
2900
2901        /**
2902         * Supply a replacement Notification whose contents should be shown in insecure contexts
2903         * (i.e. atop the secure lockscreen). See {@link #visibility} and {@link #VISIBILITY_PUBLIC}.
2904         * @param n A replacement notification, presumably with some or all info redacted.
2905         * @return The same Builder.
2906         */
2907        public Builder setPublicVersion(Notification n) {
2908            if (n != null) {
2909                mN.publicVersion = new Notification();
2910                n.cloneInto(mN.publicVersion, /*heavy=*/ true);
2911            } else {
2912                mN.publicVersion = null;
2913            }
2914            return this;
2915        }
2916
2917        /**
2918         * Apply an extender to this notification builder. Extenders may be used to add
2919         * metadata or change options on this builder.
2920         */
2921        public Builder extend(Extender extender) {
2922            extender.extend(this);
2923            return this;
2924        }
2925
2926        /**
2927         * @hide
2928         */
2929        public void setFlag(int mask, boolean value) {
2930            if (value) {
2931                mN.flags |= mask;
2932            } else {
2933                mN.flags &= ~mask;
2934            }
2935        }
2936
2937        /**
2938         * Sets {@link Notification#color}.
2939         *
2940         * @param argb The accent color to use
2941         *
2942         * @return The same Builder.
2943         */
2944        public Builder setColor(@ColorInt int argb) {
2945            mN.color = argb;
2946            sanitizeColor();
2947            return this;
2948        }
2949
2950        private Drawable getProfileBadgeDrawable() {
2951            // Note: This assumes that the current user can read the profile badge of the
2952            // originating user.
2953            return mContext.getPackageManager().getUserBadgeForDensityNoBackground(
2954                    new UserHandle(mContext.getUserId()), 0);
2955        }
2956
2957        private Bitmap getProfileBadge() {
2958            Drawable badge = getProfileBadgeDrawable();
2959            if (badge == null) {
2960                return null;
2961            }
2962            final int size = mContext.getResources().getDimensionPixelSize(
2963                    R.dimen.notification_badge_size);
2964            Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
2965            Canvas canvas = new Canvas(bitmap);
2966            badge.setBounds(0, 0, size, size);
2967            badge.draw(canvas);
2968            return bitmap;
2969        }
2970
2971        private void bindProfileBadge(RemoteViews contentView) {
2972            Bitmap profileBadge = getProfileBadge();
2973
2974            if (profileBadge != null) {
2975                contentView.setImageViewBitmap(R.id.profile_badge, profileBadge);
2976                contentView.setViewVisibility(R.id.profile_badge, View.VISIBLE);
2977            }
2978        }
2979
2980        private void resetStandardTemplate(RemoteViews contentView) {
2981            resetNotificationHeader(contentView);
2982            resetContentMargins(contentView);
2983            contentView.setViewVisibility(R.id.right_icon, View.GONE);
2984            contentView.setViewVisibility(R.id.title, View.GONE);
2985            contentView.setTextViewText(R.id.title, null);
2986            contentView.setViewVisibility(R.id.text, View.GONE);
2987            contentView.setTextViewText(R.id.text, null);
2988            contentView.setViewVisibility(R.id.text_line_1, View.GONE);
2989            contentView.setTextViewText(R.id.text_line_1, null);
2990            contentView.setViewVisibility(R.id.progress, View.GONE);
2991        }
2992
2993        /**
2994         * Resets the notification header to its original state
2995         */
2996        private void resetNotificationHeader(RemoteViews contentView) {
2997            contentView.setImageViewResource(R.id.icon, 0);
2998            contentView.setBoolean(R.id.notification_header, "setExpanded", false);
2999            contentView.setTextViewText(R.id.app_name_text, null);
3000            contentView.setViewVisibility(R.id.chronometer, View.GONE);
3001            contentView.setViewVisibility(R.id.header_sub_text, View.GONE);
3002            contentView.setViewVisibility(R.id.header_content_info, View.GONE);
3003            contentView.setViewVisibility(R.id.sub_text_divider, View.GONE);
3004            contentView.setViewVisibility(R.id.content_info_divider, View.GONE);
3005            contentView.setViewVisibility(R.id.time_divider, View.GONE);
3006            contentView.setImageViewIcon(R.id.profile_badge, null);
3007            contentView.setViewVisibility(R.id.profile_badge, View.GONE);
3008        }
3009
3010        private void resetContentMargins(RemoteViews contentView) {
3011            contentView.setViewLayoutMarginEnd(R.id.line1, 0);
3012            contentView.setViewLayoutMarginEnd(R.id.text, 0);
3013        }
3014
3015        private RemoteViews applyStandardTemplate(int resId) {
3016            return applyStandardTemplate(resId, true /* hasProgress */);
3017        }
3018
3019        /**
3020         * @param hasProgress whether the progress bar should be shown and set
3021         */
3022        private RemoteViews applyStandardTemplate(int resId, boolean hasProgress) {
3023            RemoteViews contentView = new BuilderRemoteViews(mContext.getApplicationInfo(), resId);
3024
3025            resetStandardTemplate(contentView);
3026
3027            final Bundle ex = mN.extras;
3028
3029            bindNotificationHeader(contentView);
3030            bindLargeIcon(contentView);
3031            if (ex.getCharSequence(EXTRA_TITLE) != null) {
3032                contentView.setViewVisibility(R.id.title, View.VISIBLE);
3033                contentView.setTextViewText(R.id.title,
3034                        processLegacyText(ex.getCharSequence(EXTRA_TITLE)));
3035            }
3036            boolean showProgress = handleProgressBar(hasProgress, contentView, ex);
3037            if (ex.getCharSequence(EXTRA_TEXT) != null) {
3038                int textId = showProgress ? com.android.internal.R.id.text_line_1
3039                        : com.android.internal.R.id.text;
3040                contentView.setTextViewText(textId, processLegacyText(
3041                        ex.getCharSequence(EXTRA_TEXT)));
3042                contentView.setViewVisibility(textId, View.VISIBLE);
3043            }
3044
3045            setContentMinHeight(contentView, showProgress || mN.mLargeIcon != null);
3046
3047            return contentView;
3048        }
3049
3050        /**
3051         * @param remoteView the remote view to update the minheight in
3052         * @param hasMinHeight does it have a mimHeight
3053         * @hide
3054         */
3055        void setContentMinHeight(RemoteViews remoteView, boolean hasMinHeight) {
3056            int minHeight = 0;
3057            if (hasMinHeight) {
3058                // we need to set the minHeight of the notification
3059                minHeight = mContext.getResources().getDimensionPixelSize(
3060                        com.android.internal.R.dimen.notification_min_content_height);
3061            }
3062            remoteView.setInt(R.id.notification_main_column, "setMinimumHeight", minHeight);
3063        }
3064
3065        private boolean handleProgressBar(boolean hasProgress, RemoteViews contentView, Bundle ex) {
3066            final int max = ex.getInt(EXTRA_PROGRESS_MAX, 0);
3067            final int progress = ex.getInt(EXTRA_PROGRESS, 0);
3068            final boolean ind = ex.getBoolean(EXTRA_PROGRESS_INDETERMINATE);
3069            if (hasProgress && (max != 0 || ind)) {
3070                contentView.setViewVisibility(com.android.internal.R.id.progress, View.VISIBLE);
3071                contentView.setProgressBar(
3072                        R.id.progress, max, progress, ind);
3073                contentView.setProgressBackgroundTintList(
3074                        R.id.progress, ColorStateList.valueOf(mContext.getColor(
3075                                R.color.notification_progress_background_color)));
3076                if (mN.color != COLOR_DEFAULT) {
3077                    ColorStateList colorStateList = ColorStateList.valueOf(mN.color);
3078                    contentView.setProgressTintList(R.id.progress, colorStateList);
3079                    contentView.setProgressIndeterminateTintList(R.id.progress, colorStateList);
3080                }
3081                return true;
3082            } else {
3083                contentView.setViewVisibility(R.id.progress, View.GONE);
3084                return false;
3085            }
3086        }
3087
3088        private void bindLargeIcon(RemoteViews contentView) {
3089            if (mN.mLargeIcon != null) {
3090                contentView.setViewVisibility(R.id.right_icon, View.VISIBLE);
3091                contentView.setImageViewIcon(R.id.right_icon, mN.mLargeIcon);
3092                processLargeLegacyIcon(mN.mLargeIcon, contentView);
3093                int endMargin = mContext.getResources().getDimensionPixelSize(
3094                        R.dimen.notification_content_picture_margin);
3095                contentView.setViewLayoutMarginEnd(R.id.line1, endMargin);
3096                contentView.setViewLayoutMarginEnd(R.id.text, endMargin);
3097                contentView.setViewLayoutMarginEnd(R.id.progress, endMargin);
3098            }
3099        }
3100
3101        private void bindNotificationHeader(RemoteViews contentView) {
3102            bindSmallIcon(contentView);
3103            bindHeaderAppName(contentView);
3104            bindHeaderSubText(contentView);
3105            bindContentInfo(contentView);
3106            bindHeaderChronometerAndTime(contentView);
3107            bindExpandButton(contentView);
3108            bindProfileBadge(contentView);
3109        }
3110
3111        private void bindContentInfo(RemoteViews contentView) {
3112            boolean visible = false;
3113            if (mN.extras.getCharSequence(EXTRA_INFO_TEXT) != null) {
3114                contentView.setTextViewText(R.id.header_content_info,
3115                        processLegacyText(mN.extras.getCharSequence(EXTRA_INFO_TEXT)));
3116                contentView.setViewVisibility(R.id.header_content_info, View.VISIBLE);
3117                visible = true;
3118            } else if (mN.number > 0) {
3119                final int tooBig = mContext.getResources().getInteger(
3120                        R.integer.status_bar_notification_info_maxnum);
3121                if (mN.number > tooBig) {
3122                    contentView.setTextViewText(R.id.header_content_info, processLegacyText(
3123                            mContext.getResources().getString(
3124                                    R.string.status_bar_notification_info_overflow)));
3125                } else {
3126                    contentView.setTextViewText(R.id.header_content_info,
3127                            processLegacyText(String.valueOf(mN.number)));
3128                }
3129                contentView.setViewVisibility(R.id.header_content_info, View.VISIBLE);
3130                visible = true;
3131            }
3132            if (visible) {
3133                contentView.setViewVisibility(R.id.content_info_divider, View.VISIBLE);
3134            }
3135        }
3136
3137        private void bindExpandButton(RemoteViews contentView) {
3138            contentView.setDrawableParameters(R.id.expand_button, false, -1, resolveColor(),
3139                    PorterDuff.Mode.SRC_ATOP, -1);
3140            contentView.setInt(R.id.notification_header, "setOriginalNotificationColor",
3141                    resolveColor());
3142        }
3143
3144        private void bindHeaderChronometerAndTime(RemoteViews contentView) {
3145            if (showsTimeOrChronometer()) {
3146                contentView.setViewVisibility(R.id.time_divider, View.VISIBLE);
3147                if (mN.extras.getBoolean(EXTRA_SHOW_CHRONOMETER)) {
3148                    contentView.setViewVisibility(R.id.chronometer, View.VISIBLE);
3149                    contentView.setLong(R.id.chronometer, "setBase",
3150                            mN.when + (SystemClock.elapsedRealtime() - System.currentTimeMillis()));
3151                    contentView.setBoolean(R.id.chronometer, "setStarted", true);
3152                    boolean countsDown = mN.extras.getBoolean(EXTRA_CHRONOMETER_COUNTS_DOWN);
3153                    contentView.setChronometerCountsDown(R.id.chronometer, countsDown);
3154                } else {
3155                    contentView.setViewVisibility(R.id.time, View.VISIBLE);
3156                    contentView.setLong(R.id.time, "setTime", mN.when);
3157                }
3158            }
3159        }
3160
3161        private void bindHeaderSubText(RemoteViews contentView) {
3162            CharSequence subText = mN.extras.getCharSequence(EXTRA_SUB_TEXT);
3163            if (subText == null && mStyle != null && mStyle.mSummaryTextSet
3164                    && mStyle.hasSummaryInHeader()) {
3165                subText = mStyle.mSummaryText;
3166            }
3167            if (subText != null) {
3168                // TODO: Remove the span entirely to only have the string with propper formating.
3169                contentView.setTextViewText(R.id.header_sub_text, processLegacyText(subText));
3170                contentView.setViewVisibility(R.id.header_sub_text, View.VISIBLE);
3171                contentView.setViewVisibility(R.id.sub_text_divider, View.VISIBLE);
3172            }
3173        }
3174
3175        private void bindHeaderAppName(RemoteViews contentView) {
3176            CharSequence appName = mContext.getPackageManager()
3177                    .getApplicationLabel(mContext.getApplicationInfo());
3178
3179            if (TextUtils.isEmpty(appName)) {
3180                return;
3181            }
3182            contentView.setTextViewText(R.id.app_name_text, appName);
3183            contentView.setTextColor(R.id.app_name_text, resolveColor());
3184        }
3185
3186        private void bindSmallIcon(RemoteViews contentView) {
3187            contentView.setImageViewIcon(R.id.icon, mN.mSmallIcon);
3188            processSmallIconColor(mN.mSmallIcon, contentView);
3189        }
3190
3191        /**
3192         * @return true if the built notification will show the time or the chronometer; false
3193         *         otherwise
3194         */
3195        private boolean showsTimeOrChronometer() {
3196            return mN.when != 0 && mN.extras.getBoolean(EXTRA_SHOW_WHEN);
3197        }
3198
3199        private void resetStandardTemplateWithActions(RemoteViews big) {
3200            big.setViewVisibility(R.id.actions, View.GONE);
3201            big.removeAllViews(R.id.actions);
3202
3203            big.setViewVisibility(R.id.notification_material_reply_container, View.GONE);
3204            big.setTextViewText(R.id.notification_material_reply_text_1, null);
3205
3206            big.setViewVisibility(R.id.notification_material_reply_text_2, View.GONE);
3207            big.setTextViewText(R.id.notification_material_reply_text_2, null);
3208            big.setViewVisibility(R.id.notification_material_reply_text_3, View.GONE);
3209            big.setTextViewText(R.id.notification_material_reply_text_3, null);
3210        }
3211
3212        private RemoteViews applyStandardTemplateWithActions(int layoutId) {
3213            RemoteViews big = applyStandardTemplate(layoutId);
3214
3215            resetStandardTemplateWithActions(big);
3216
3217            boolean validRemoteInput = false;
3218
3219            int N = mActions.size();
3220            if (N > 0) {
3221                big.setViewVisibility(R.id.actions, View.VISIBLE);
3222                if (N>MAX_ACTION_BUTTONS) N=MAX_ACTION_BUTTONS;
3223                for (int i=0; i<N; i++) {
3224                    Action action = mActions.get(i);
3225                    validRemoteInput |= hasValidRemoteInput(action);
3226
3227                    final RemoteViews button = generateActionButton(action);
3228                    if (i == N - 1) {
3229                        button.setViewLayoutWidth(com.android.internal.R.id.action0,
3230                                ViewGroup.LayoutParams.MATCH_PARENT);
3231                    }
3232                    big.addView(R.id.actions, button);
3233                }
3234            }
3235
3236            CharSequence[] replyText = mN.extras.getCharSequenceArray(EXTRA_REMOTE_INPUT_HISTORY);
3237            if (validRemoteInput && replyText != null
3238                    && replyText.length > 0 && !TextUtils.isEmpty(replyText[0])) {
3239                big.setViewVisibility(R.id.notification_material_reply_container, View.VISIBLE);
3240                big.setTextViewText(R.id.notification_material_reply_text_1, replyText[0]);
3241
3242                if (replyText.length > 1 && !TextUtils.isEmpty(replyText[1])) {
3243                    big.setViewVisibility(R.id.notification_material_reply_text_2, View.VISIBLE);
3244                    big.setTextViewText(R.id.notification_material_reply_text_2, replyText[1]);
3245
3246                    if (replyText.length > 2 && !TextUtils.isEmpty(replyText[2])) {
3247                        big.setViewVisibility(
3248                                R.id.notification_material_reply_text_3, View.VISIBLE);
3249                        big.setTextViewText(R.id.notification_material_reply_text_3, replyText[2]);
3250                    }
3251                }
3252            }
3253
3254            return big;
3255        }
3256
3257        private boolean hasValidRemoteInput(Action action) {
3258            if (TextUtils.isEmpty(action.title) || action.actionIntent == null) {
3259                // Weird actions
3260                return false;
3261            }
3262
3263            RemoteInput[] remoteInputs = action.getRemoteInputs();
3264            if (remoteInputs == null) {
3265                return false;
3266            }
3267
3268            for (RemoteInput r : remoteInputs) {
3269                CharSequence[] choices = r.getChoices();
3270                if (r.getAllowFreeFormInput() || (choices != null && choices.length != 0)) {
3271                    return true;
3272                }
3273            }
3274            return false;
3275        }
3276
3277        /**
3278         * Construct a RemoteViews for the final 1U notification layout. In order:
3279         *   1. Custom contentView from the caller
3280         *   2. Style's proposed content view
3281         *   3. Standard template view
3282         */
3283        public RemoteViews createContentView() {
3284            if (mN.contentView != null && (mStyle == null || !mStyle.displayCustomViewInline())) {
3285                return mN.contentView;
3286            } else if (mStyle != null) {
3287                final RemoteViews styleView = mStyle.makeContentView();
3288                if (styleView != null) {
3289                    return styleView;
3290                }
3291            }
3292            return applyStandardTemplate(getBaseLayoutResource());
3293        }
3294
3295        /**
3296         * Construct a RemoteViews for the final big notification layout.
3297         */
3298        public RemoteViews createBigContentView() {
3299            RemoteViews result = null;
3300            if (mN.bigContentView != null
3301                    && (mStyle == null || !mStyle.displayCustomViewInline())) {
3302                return mN.bigContentView;
3303            } else if (mStyle != null) {
3304                result = mStyle.makeBigContentView();
3305                hideLine1Text(result);
3306            } else if (mActions.size() != 0) {
3307                result = applyStandardTemplateWithActions(getBigBaseLayoutResource());
3308            }
3309            adaptNotificationHeaderForBigContentView(result);
3310            return result;
3311        }
3312
3313        /**
3314         * Construct a RemoteViews for the final notification header only
3315         *
3316         * @hide
3317         */
3318        public RemoteViews makeNotificationHeader() {
3319            RemoteViews header = new BuilderRemoteViews(mContext.getApplicationInfo(),
3320                    R.layout.notification_template_header);
3321            resetNotificationHeader(header);
3322            bindNotificationHeader(header);
3323            return header;
3324        }
3325
3326        private void hideLine1Text(RemoteViews result) {
3327            if (result != null) {
3328                result.setViewVisibility(R.id.text_line_1, View.GONE);
3329            }
3330        }
3331
3332        private void adaptNotificationHeaderForBigContentView(RemoteViews result) {
3333            if (result != null) {
3334                result.setBoolean(R.id.notification_header, "setExpanded", true);
3335            }
3336        }
3337
3338        /**
3339         * Construct a RemoteViews for the final heads-up notification layout.
3340         */
3341        public RemoteViews createHeadsUpContentView() {
3342            if (mN.headsUpContentView != null
3343                    && (mStyle == null ||  !mStyle.displayCustomViewInline())) {
3344                return mN.headsUpContentView;
3345            } else if (mStyle != null) {
3346                    final RemoteViews styleView = mStyle.makeHeadsUpContentView();
3347                    if (styleView != null) {
3348                        return styleView;
3349                    }
3350            } else if (mActions.size() == 0) {
3351                return null;
3352            }
3353
3354            return applyStandardTemplateWithActions(getBigBaseLayoutResource());
3355        }
3356
3357        /**
3358         * Construct a RemoteViews for the display in public contexts like on the lockscreen.
3359         *
3360         * @hide
3361         */
3362        public RemoteViews makePublicContentView() {
3363            if (mN.publicVersion != null) {
3364                final Builder builder = recoverBuilder(mContext, mN.publicVersion);
3365                return builder.createContentView();
3366            }
3367            Bundle savedBundle = mN.extras;
3368            Style style = mStyle;
3369            mStyle = null;
3370            Icon largeIcon = mN.mLargeIcon;
3371            mN.mLargeIcon = null;
3372            Bundle publicExtras = new Bundle();
3373            publicExtras.putBoolean(EXTRA_SHOW_WHEN,
3374                    savedBundle.getBoolean(EXTRA_SHOW_WHEN));
3375            publicExtras.putBoolean(EXTRA_SHOW_CHRONOMETER,
3376                    savedBundle.getBoolean(EXTRA_SHOW_CHRONOMETER));
3377            publicExtras.putBoolean(EXTRA_CHRONOMETER_COUNTS_DOWN,
3378                    savedBundle.getBoolean(EXTRA_CHRONOMETER_COUNTS_DOWN));
3379            publicExtras.putCharSequence(EXTRA_TITLE,
3380                    mContext.getString(R.string.notification_hidden_text));
3381            mN.extras = publicExtras;
3382            final RemoteViews publicView = applyStandardTemplate(getBaseLayoutResource());
3383            mN.extras = savedBundle;
3384            mN.mLargeIcon = largeIcon;
3385            mStyle = style;
3386            return publicView;
3387        }
3388
3389
3390
3391        private RemoteViews generateActionButton(Action action) {
3392            final boolean tombstone = (action.actionIntent == null);
3393            RemoteViews button = new BuilderRemoteViews(mContext.getApplicationInfo(),
3394                    tombstone ? getActionTombstoneLayoutResource()
3395                              : getActionLayoutResource());
3396            final Icon ai = action.getIcon();
3397            button.setTextViewText(R.id.action0, processLegacyText(action.title));
3398            if (!tombstone) {
3399                button.setOnClickPendingIntent(R.id.action0, action.actionIntent);
3400            }
3401            button.setContentDescription(R.id.action0, action.title);
3402            if (action.mRemoteInputs != null) {
3403                button.setRemoteInputs(R.id.action0, action.mRemoteInputs);
3404            }
3405            if (mN.color != COLOR_DEFAULT) {
3406                button.setTextColor(R.id.action0, mN.color);
3407            }
3408            return button;
3409        }
3410
3411        /**
3412         * @return Whether we are currently building a notification from a legacy (an app that
3413         *         doesn't create material notifications by itself) app.
3414         */
3415        private boolean isLegacy() {
3416            return getColorUtil() != null;
3417        }
3418
3419        private CharSequence processLegacyText(CharSequence charSequence) {
3420            if (isLegacy()) {
3421                return getColorUtil().invertCharSequenceColors(charSequence);
3422            } else {
3423                return charSequence;
3424            }
3425        }
3426
3427        /**
3428         * Apply any necessariy colors to the small icon
3429         */
3430        private void processSmallIconColor(Icon smallIcon, RemoteViews contentView) {
3431            boolean colorable = !isLegacy() || getColorUtil().isGrayscaleIcon(mContext, smallIcon);
3432            if (colorable) {
3433                contentView.setDrawableParameters(R.id.icon, false, -1, resolveColor(),
3434                        PorterDuff.Mode.SRC_ATOP, -1);
3435
3436            }
3437            contentView.setInt(R.id.notification_header, "setOriginalIconColor",
3438                    colorable ? resolveColor() : NotificationHeaderView.NO_COLOR);
3439        }
3440
3441        /**
3442         * Make the largeIcon dark if it's a fake smallIcon (that is,
3443         * if it's grayscale).
3444         */
3445        // TODO: also check bounds, transparency, that sort of thing.
3446        private void processLargeLegacyIcon(Icon largeIcon, RemoteViews contentView) {
3447            if (largeIcon != null && isLegacy()
3448                    && getColorUtil().isGrayscaleIcon(mContext, largeIcon)) {
3449                // resolve color will fall back to the default when legacy
3450                contentView.setDrawableParameters(R.id.icon, false, -1, resolveColor(),
3451                        PorterDuff.Mode.SRC_ATOP, -1);
3452            }
3453        }
3454
3455        private void sanitizeColor() {
3456            if (mN.color != COLOR_DEFAULT) {
3457                mN.color |= 0xFF000000; // no alpha for custom colors
3458            }
3459        }
3460
3461        int resolveColor() {
3462            if (mN.color == COLOR_DEFAULT) {
3463                return mContext.getColor(R.color.notification_icon_default_color);
3464            }
3465            return mN.color;
3466        }
3467
3468        /**
3469         * Apply the unstyled operations and return a new {@link Notification} object.
3470         * @hide
3471         */
3472        public Notification buildUnstyled() {
3473            if (mActions.size() > 0) {
3474                mN.actions = new Action[mActions.size()];
3475                mActions.toArray(mN.actions);
3476            }
3477            if (!mPersonList.isEmpty()) {
3478                mN.extras.putStringArray(EXTRA_PEOPLE,
3479                        mPersonList.toArray(new String[mPersonList.size()]));
3480            }
3481            if (mN.bigContentView != null || mN.contentView != null
3482                    || mN.headsUpContentView != null) {
3483                mN.extras.putBoolean(EXTRA_CONTAINS_CUSTOM_VIEW, true);
3484            }
3485            return mN;
3486        }
3487
3488        /**
3489         * Creates a Builder from an existing notification so further changes can be made.
3490         * @param context The context for your application / activity.
3491         * @param n The notification to create a Builder from.
3492         */
3493        public static Notification.Builder recoverBuilder(Context context, Notification n) {
3494            // Re-create notification context so we can access app resources.
3495            ApplicationInfo applicationInfo = n.extras.getParcelable(
3496                    EXTRA_BUILDER_APPLICATION_INFO);
3497            Context builderContext;
3498            if (applicationInfo != null) {
3499                try {
3500                    builderContext = context.createApplicationContext(applicationInfo,
3501                            Context.CONTEXT_RESTRICTED);
3502                } catch (NameNotFoundException e) {
3503                    Log.e(TAG, "ApplicationInfo " + applicationInfo + " not found");
3504                    builderContext = context;  // try with our context
3505                }
3506            } else {
3507                builderContext = context; // try with given context
3508            }
3509
3510            return new Builder(builderContext, n);
3511        }
3512
3513        private static Class<? extends Style> getNotificationStyleClass(String templateClass) {
3514            Class<? extends Style>[] classes = new Class[] {
3515                    BigTextStyle.class, BigPictureStyle.class, InboxStyle.class, MediaStyle.class,
3516                    DecoratedCustomViewStyle.class, DecoratedMediaCustomViewStyle.class };
3517            for (Class<? extends Style> innerClass : classes) {
3518                if (templateClass.equals(innerClass.getName())) {
3519                    return innerClass;
3520                }
3521            }
3522            return null;
3523        }
3524
3525        /**
3526         * @deprecated Use {@link #build()} instead.
3527         */
3528        @Deprecated
3529        public Notification getNotification() {
3530            return build();
3531        }
3532
3533        /**
3534         * Combine all of the options that have been set and return a new {@link Notification}
3535         * object.
3536         */
3537        public Notification build() {
3538            // first, add any extras from the calling code
3539            if (mUserExtras != null) {
3540                mN.extras = getAllExtras();
3541            }
3542
3543            // lazy stuff from mContext; see comment in Builder(Context, Notification)
3544            Notification.addFieldsFromContext(mContext, mN);
3545
3546            buildUnstyled();
3547
3548            if (mStyle != null) {
3549                mStyle.buildStyled(mN);
3550            }
3551
3552            if (mContext.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N
3553                    && (mStyle == null || !mStyle.displayCustomViewInline())) {
3554                if (mN.contentView == null) {
3555                    mN.contentView = createContentView();
3556                    mN.extras.putInt(EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT,
3557                            mN.contentView.getSequenceNumber());
3558                }
3559                if (mN.bigContentView == null) {
3560                    mN.bigContentView = createBigContentView();
3561                    if (mN.bigContentView != null) {
3562                        mN.extras.putInt(EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT,
3563                                mN.bigContentView.getSequenceNumber());
3564                    }
3565                }
3566                if (mN.headsUpContentView == null) {
3567                    mN.headsUpContentView = createHeadsUpContentView();
3568                    if (mN.headsUpContentView != null) {
3569                        mN.extras.putInt(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT,
3570                                mN.headsUpContentView.getSequenceNumber());
3571                    }
3572                }
3573            }
3574
3575            if ((mN.defaults & DEFAULT_LIGHTS) != 0) {
3576                mN.flags |= FLAG_SHOW_LIGHTS;
3577            }
3578
3579            return mN;
3580        }
3581
3582        /**
3583         * Apply this Builder to an existing {@link Notification} object.
3584         *
3585         * @hide
3586         */
3587        public Notification buildInto(Notification n) {
3588            build().cloneInto(n, true);
3589            return n;
3590        }
3591
3592        /**
3593         * Removes RemoteViews that were created for compatibility from {@param n}, if they did not
3594         * change.
3595         *
3596         * @return {@param n}, if no stripping is needed, otherwise a stripped clone of {@param n}.
3597         *
3598         * @hide
3599         */
3600        public static Notification maybeCloneStrippedForDelivery(Notification n) {
3601            String templateClass = n.extras.getString(EXTRA_TEMPLATE);
3602
3603            // Only strip views for known Styles because we won't know how to
3604            // re-create them otherwise.
3605            if (!TextUtils.isEmpty(templateClass)
3606                    && getNotificationStyleClass(templateClass) == null) {
3607                return n;
3608            }
3609
3610            // Only strip unmodified BuilderRemoteViews.
3611            boolean stripContentView = n.contentView instanceof BuilderRemoteViews &&
3612                    n.extras.getInt(EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT, -1) ==
3613                            n.contentView.getSequenceNumber();
3614            boolean stripBigContentView = n.bigContentView instanceof BuilderRemoteViews &&
3615                    n.extras.getInt(EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT, -1) ==
3616                            n.bigContentView.getSequenceNumber();
3617            boolean stripHeadsUpContentView = n.headsUpContentView instanceof BuilderRemoteViews &&
3618                    n.extras.getInt(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT, -1) ==
3619                            n.headsUpContentView.getSequenceNumber();
3620
3621            // Nothing to do here, no need to clone.
3622            if (!stripContentView && !stripBigContentView && !stripHeadsUpContentView) {
3623                return n;
3624            }
3625
3626            Notification clone = n.clone();
3627            if (stripContentView) {
3628                clone.contentView = null;
3629                clone.extras.remove(EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT);
3630            }
3631            if (stripBigContentView) {
3632                clone.bigContentView = null;
3633                clone.extras.remove(EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT);
3634            }
3635            if (stripHeadsUpContentView) {
3636                clone.headsUpContentView = null;
3637                clone.extras.remove(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT);
3638            }
3639            return clone;
3640        }
3641
3642        private int getBaseLayoutResource() {
3643            return R.layout.notification_template_material_base;
3644        }
3645
3646        private int getBigBaseLayoutResource() {
3647            return R.layout.notification_template_material_big_base;
3648        }
3649
3650        private int getBigPictureLayoutResource() {
3651            return R.layout.notification_template_material_big_picture;
3652        }
3653
3654        private int getBigTextLayoutResource() {
3655            return R.layout.notification_template_material_big_text;
3656        }
3657
3658        private int getInboxLayoutResource() {
3659            return R.layout.notification_template_material_inbox;
3660        }
3661
3662        private int getActionLayoutResource() {
3663            return R.layout.notification_material_action;
3664        }
3665
3666        private int getActionTombstoneLayoutResource() {
3667            return R.layout.notification_material_action_tombstone;
3668        }
3669    }
3670
3671    /**
3672     * An object that can apply a rich notification style to a {@link Notification.Builder}
3673     * object.
3674     */
3675    public static abstract class Style {
3676        private CharSequence mBigContentTitle;
3677
3678        /**
3679         * @hide
3680         */
3681        protected CharSequence mSummaryText = null;
3682
3683        /**
3684         * @hide
3685         */
3686        protected boolean mSummaryTextSet = false;
3687
3688        protected Builder mBuilder;
3689
3690        /**
3691         * Overrides ContentTitle in the big form of the template.
3692         * This defaults to the value passed to setContentTitle().
3693         */
3694        protected void internalSetBigContentTitle(CharSequence title) {
3695            mBigContentTitle = title;
3696        }
3697
3698        /**
3699         * Set the first line of text after the detail section in the big form of the template.
3700         */
3701        protected void internalSetSummaryText(CharSequence cs) {
3702            mSummaryText = cs;
3703            mSummaryTextSet = true;
3704        }
3705
3706        public void setBuilder(Builder builder) {
3707            if (mBuilder != builder) {
3708                mBuilder = builder;
3709                if (mBuilder != null) {
3710                    mBuilder.setStyle(this);
3711                }
3712            }
3713        }
3714
3715        protected void checkBuilder() {
3716            if (mBuilder == null) {
3717                throw new IllegalArgumentException("Style requires a valid Builder object");
3718            }
3719        }
3720
3721        protected RemoteViews getStandardView(int layoutId) {
3722            checkBuilder();
3723
3724            // Nasty.
3725            CharSequence oldBuilderContentTitle =
3726                    mBuilder.getAllExtras().getCharSequence(EXTRA_TITLE);
3727            if (mBigContentTitle != null) {
3728                mBuilder.setContentTitle(mBigContentTitle);
3729            }
3730
3731            RemoteViews contentView = mBuilder.applyStandardTemplateWithActions(layoutId);
3732
3733            mBuilder.getAllExtras().putCharSequence(EXTRA_TITLE, oldBuilderContentTitle);
3734
3735            if (mBigContentTitle != null && mBigContentTitle.equals("")) {
3736                contentView.setViewVisibility(R.id.line1, View.GONE);
3737            } else {
3738                contentView.setViewVisibility(R.id.line1, View.VISIBLE);
3739            }
3740
3741            return contentView;
3742        }
3743
3744        /**
3745         * Construct a Style-specific RemoteViews for the final 1U notification layout.
3746         * The default implementation has nothing additional to add.
3747         * @hide
3748         */
3749        public RemoteViews makeContentView() {
3750            return null;
3751        }
3752
3753        /**
3754         * Construct a Style-specific RemoteViews for the final big notification layout.
3755         * @hide
3756         */
3757        public RemoteViews makeBigContentView() {
3758            return null;
3759        }
3760
3761        /**
3762         * Construct a Style-specific RemoteViews for the final HUN layout.
3763         * @hide
3764         */
3765        public RemoteViews makeHeadsUpContentView() {
3766            return null;
3767        }
3768
3769        /**
3770         * Apply any style-specific extras to this notification before shipping it out.
3771         * @hide
3772         */
3773        public void addExtras(Bundle extras) {
3774            if (mSummaryTextSet) {
3775                extras.putCharSequence(EXTRA_SUMMARY_TEXT, mSummaryText);
3776            }
3777            if (mBigContentTitle != null) {
3778                extras.putCharSequence(EXTRA_TITLE_BIG, mBigContentTitle);
3779            }
3780            extras.putString(EXTRA_TEMPLATE, this.getClass().getName());
3781        }
3782
3783        /**
3784         * Reconstruct the internal state of this Style object from extras.
3785         * @hide
3786         */
3787        protected void restoreFromExtras(Bundle extras) {
3788            if (extras.containsKey(EXTRA_SUMMARY_TEXT)) {
3789                mSummaryText = extras.getCharSequence(EXTRA_SUMMARY_TEXT);
3790                mSummaryTextSet = true;
3791            }
3792            if (extras.containsKey(EXTRA_TITLE_BIG)) {
3793                mBigContentTitle = extras.getCharSequence(EXTRA_TITLE_BIG);
3794            }
3795        }
3796
3797
3798        /**
3799         * @hide
3800         */
3801        public Notification buildStyled(Notification wip) {
3802            addExtras(wip.extras);
3803            return wip;
3804        }
3805
3806        /**
3807         * @hide
3808         */
3809        public void purgeResources() {}
3810
3811        /**
3812         * Calls {@link android.app.Notification.Builder#build()} on the Builder this Style is
3813         * attached to.
3814         *
3815         * @return the fully constructed Notification.
3816         */
3817        public Notification build() {
3818            checkBuilder();
3819            return mBuilder.build();
3820        }
3821
3822        /**
3823         * @hide
3824         * @return true if the style positions the progress bar on the second line; false if the
3825         *         style hides the progress bar
3826         */
3827        protected boolean hasProgress() {
3828            return true;
3829        }
3830
3831        /**
3832         * @hide
3833         * @return Whether we should put the summary be put into the notification header
3834         */
3835        public boolean hasSummaryInHeader() {
3836            return true;
3837        }
3838
3839        /**
3840         * @hide
3841         * @return Whether custom content views are displayed inline in the style
3842         */
3843        public boolean displayCustomViewInline() {
3844            return false;
3845        }
3846    }
3847
3848    /**
3849     * Helper class for generating large-format notifications that include a large image attachment.
3850     *
3851     * Here's how you'd set the <code>BigPictureStyle</code> on a notification:
3852     * <pre class="prettyprint">
3853     * Notification notif = new Notification.Builder(mContext)
3854     *     .setContentTitle(&quot;New photo from &quot; + sender.toString())
3855     *     .setContentText(subject)
3856     *     .setSmallIcon(R.drawable.new_post)
3857     *     .setLargeIcon(aBitmap)
3858     *     .setStyle(new Notification.BigPictureStyle()
3859     *         .bigPicture(aBigBitmap))
3860     *     .build();
3861     * </pre>
3862     *
3863     * @see Notification#bigContentView
3864     */
3865    public static class BigPictureStyle extends Style {
3866        private Bitmap mPicture;
3867        private Icon mBigLargeIcon;
3868        private boolean mBigLargeIconSet = false;
3869
3870        public BigPictureStyle() {
3871        }
3872
3873        public BigPictureStyle(Builder builder) {
3874            setBuilder(builder);
3875        }
3876
3877        /**
3878         * Overrides ContentTitle in the big form of the template.
3879         * This defaults to the value passed to setContentTitle().
3880         */
3881        public BigPictureStyle setBigContentTitle(CharSequence title) {
3882            internalSetBigContentTitle(safeCharSequence(title));
3883            return this;
3884        }
3885
3886        /**
3887         * Set the first line of text after the detail section in the big form of the template.
3888         */
3889        public BigPictureStyle setSummaryText(CharSequence cs) {
3890            internalSetSummaryText(safeCharSequence(cs));
3891            return this;
3892        }
3893
3894        /**
3895         * Provide the bitmap to be used as the payload for the BigPicture notification.
3896         */
3897        public BigPictureStyle bigPicture(Bitmap b) {
3898            mPicture = b;
3899            return this;
3900        }
3901
3902        /**
3903         * Override the large icon when the big notification is shown.
3904         */
3905        public BigPictureStyle bigLargeIcon(Bitmap b) {
3906            return bigLargeIcon(b != null ? Icon.createWithBitmap(b) : null);
3907        }
3908
3909        /**
3910         * Override the large icon when the big notification is shown.
3911         */
3912        public BigPictureStyle bigLargeIcon(Icon icon) {
3913            mBigLargeIconSet = true;
3914            mBigLargeIcon = icon;
3915            return this;
3916        }
3917
3918        /** @hide */
3919        public static final int MIN_ASHMEM_BITMAP_SIZE = 128 * (1 << 10);
3920
3921        /**
3922         * @hide
3923         */
3924        @Override
3925        public void purgeResources() {
3926            super.purgeResources();
3927            if (mPicture != null &&
3928                mPicture.isMutable() &&
3929                mPicture.getAllocationByteCount() >= MIN_ASHMEM_BITMAP_SIZE) {
3930                mPicture = mPicture.createAshmemBitmap();
3931            }
3932            if (mBigLargeIcon != null) {
3933                mBigLargeIcon.convertToAshmem();
3934            }
3935        }
3936
3937        /**
3938         * @hide
3939         */
3940        public RemoteViews makeBigContentView() {
3941            // Replace mN.mLargeIcon with mBigLargeIcon if mBigLargeIconSet
3942            // This covers the following cases:
3943            //   1. mBigLargeIconSet -> mBigLargeIcon (null or non-null) applies, overrides
3944            //          mN.mLargeIcon
3945            //   2. !mBigLargeIconSet -> mN.mLargeIcon applies
3946            Icon oldLargeIcon = null;
3947            if (mBigLargeIconSet) {
3948                oldLargeIcon = mBuilder.mN.mLargeIcon;
3949                mBuilder.mN.mLargeIcon = mBigLargeIcon;
3950            }
3951
3952            RemoteViews contentView = getStandardView(mBuilder.getBigPictureLayoutResource());
3953            if (mSummaryTextSet) {
3954                contentView.setTextViewText(R.id.text, mBuilder.processLegacyText(mSummaryText));
3955                contentView.setViewVisibility(R.id.text, View.VISIBLE);
3956            }
3957            mBuilder.setContentMinHeight(contentView, mBuilder.mN.mLargeIcon != null);
3958
3959            if (mBigLargeIconSet) {
3960                mBuilder.mN.mLargeIcon = oldLargeIcon;
3961            }
3962
3963            contentView.setImageViewBitmap(R.id.big_picture, mPicture);
3964            return contentView;
3965        }
3966
3967        /**
3968         * @hide
3969         */
3970        public void addExtras(Bundle extras) {
3971            super.addExtras(extras);
3972
3973            if (mBigLargeIconSet) {
3974                extras.putParcelable(EXTRA_LARGE_ICON_BIG, mBigLargeIcon);
3975            }
3976            extras.putParcelable(EXTRA_PICTURE, mPicture);
3977        }
3978
3979        /**
3980         * @hide
3981         */
3982        @Override
3983        protected void restoreFromExtras(Bundle extras) {
3984            super.restoreFromExtras(extras);
3985
3986            if (extras.containsKey(EXTRA_LARGE_ICON_BIG)) {
3987                mBigLargeIconSet = true;
3988                mBigLargeIcon = extras.getParcelable(EXTRA_LARGE_ICON_BIG);
3989            }
3990            mPicture = extras.getParcelable(EXTRA_PICTURE);
3991        }
3992
3993        /**
3994         * @hide
3995         */
3996        @Override
3997        public boolean hasSummaryInHeader() {
3998            return false;
3999        }
4000    }
4001
4002    /**
4003     * Helper class for generating large-format notifications that include a lot of text.
4004     *
4005     * Here's how you'd set the <code>BigTextStyle</code> on a notification:
4006     * <pre class="prettyprint">
4007     * Notification notif = new Notification.Builder(mContext)
4008     *     .setContentTitle(&quot;New mail from &quot; + sender.toString())
4009     *     .setContentText(subject)
4010     *     .setSmallIcon(R.drawable.new_mail)
4011     *     .setLargeIcon(aBitmap)
4012     *     .setStyle(new Notification.BigTextStyle()
4013     *         .bigText(aVeryLongString))
4014     *     .build();
4015     * </pre>
4016     *
4017     * @see Notification#bigContentView
4018     */
4019    public static class BigTextStyle extends Style {
4020
4021        private static final int MAX_LINES = 13;
4022        private static final int LINES_CONSUMED_BY_ACTIONS = 4;
4023
4024        private CharSequence mBigText;
4025
4026        public BigTextStyle() {
4027        }
4028
4029        public BigTextStyle(Builder builder) {
4030            setBuilder(builder);
4031        }
4032
4033        /**
4034         * Overrides ContentTitle in the big form of the template.
4035         * This defaults to the value passed to setContentTitle().
4036         */
4037        public BigTextStyle setBigContentTitle(CharSequence title) {
4038            internalSetBigContentTitle(safeCharSequence(title));
4039            return this;
4040        }
4041
4042        /**
4043         * Set the first line of text after the detail section in the big form of the template.
4044         */
4045        public BigTextStyle setSummaryText(CharSequence cs) {
4046            internalSetSummaryText(safeCharSequence(cs));
4047            return this;
4048        }
4049
4050        /**
4051         * Provide the longer text to be displayed in the big form of the
4052         * template in place of the content text.
4053         */
4054        public BigTextStyle bigText(CharSequence cs) {
4055            mBigText = safeCharSequence(cs);
4056            return this;
4057        }
4058
4059        /**
4060         * @hide
4061         */
4062        public void addExtras(Bundle extras) {
4063            super.addExtras(extras);
4064
4065            extras.putCharSequence(EXTRA_BIG_TEXT, mBigText);
4066        }
4067
4068        /**
4069         * @hide
4070         */
4071        @Override
4072        protected void restoreFromExtras(Bundle extras) {
4073            super.restoreFromExtras(extras);
4074
4075            mBigText = extras.getCharSequence(EXTRA_BIG_TEXT);
4076        }
4077
4078        /**
4079         * @hide
4080         */
4081        public RemoteViews makeBigContentView() {
4082
4083            // Nasty
4084            CharSequence oldBuilderContentText =
4085                    mBuilder.getAllExtras().getCharSequence(EXTRA_TEXT);
4086            mBuilder.getAllExtras().putCharSequence(EXTRA_TEXT, null);
4087
4088            RemoteViews contentView = getStandardView(mBuilder.getBigTextLayoutResource());
4089
4090            mBuilder.getAllExtras().putCharSequence(EXTRA_TEXT, oldBuilderContentText);
4091
4092            CharSequence bigTextText = mBuilder.processLegacyText(mBigText);
4093            contentView.setTextViewText(R.id.big_text, bigTextText);
4094            contentView.setViewVisibility(R.id.big_text,
4095                    TextUtils.isEmpty(bigTextText) ? View.GONE : View.VISIBLE);
4096            contentView.setInt(R.id.big_text, "setMaxLines", calculateMaxLines());
4097            contentView.setBoolean(R.id.big_text, "setHasImage", mBuilder.mN.mLargeIcon != null);
4098
4099            return contentView;
4100        }
4101
4102        private int calculateMaxLines() {
4103            int lineCount = MAX_LINES;
4104            boolean hasActions = mBuilder.mActions.size() > 0;
4105            if (hasActions) {
4106                lineCount -= LINES_CONSUMED_BY_ACTIONS;
4107            }
4108            return lineCount;
4109        }
4110    }
4111
4112    /**
4113     * Helper class for generating large-format notifications that include a list of (up to 5) strings.
4114     *
4115     * Here's how you'd set the <code>InboxStyle</code> on a notification:
4116     * <pre class="prettyprint">
4117     * Notification notif = new Notification.Builder(mContext)
4118     *     .setContentTitle(&quot;5 New mails from &quot; + sender.toString())
4119     *     .setContentText(subject)
4120     *     .setSmallIcon(R.drawable.new_mail)
4121     *     .setLargeIcon(aBitmap)
4122     *     .setStyle(new Notification.InboxStyle()
4123     *         .addLine(str1)
4124     *         .addLine(str2)
4125     *         .setContentTitle(&quot;&quot;)
4126     *         .setSummaryText(&quot;+3 more&quot;))
4127     *     .build();
4128     * </pre>
4129     *
4130     * @see Notification#bigContentView
4131     */
4132    public static class InboxStyle extends Style {
4133        private ArrayList<CharSequence> mTexts = new ArrayList<CharSequence>(5);
4134
4135        public InboxStyle() {
4136        }
4137
4138        public InboxStyle(Builder builder) {
4139            setBuilder(builder);
4140        }
4141
4142        /**
4143         * Overrides ContentTitle in the big form of the template.
4144         * This defaults to the value passed to setContentTitle().
4145         */
4146        public InboxStyle setBigContentTitle(CharSequence title) {
4147            internalSetBigContentTitle(safeCharSequence(title));
4148            return this;
4149        }
4150
4151        /**
4152         * Set the first line of text after the detail section in the big form of the template.
4153         */
4154        public InboxStyle setSummaryText(CharSequence cs) {
4155            internalSetSummaryText(safeCharSequence(cs));
4156            return this;
4157        }
4158
4159        /**
4160         * Append a line to the digest section of the Inbox notification.
4161         */
4162        public InboxStyle addLine(CharSequence cs) {
4163            mTexts.add(safeCharSequence(cs));
4164            return this;
4165        }
4166
4167        /**
4168         * @hide
4169         */
4170        public void addExtras(Bundle extras) {
4171            super.addExtras(extras);
4172
4173            CharSequence[] a = new CharSequence[mTexts.size()];
4174            extras.putCharSequenceArray(EXTRA_TEXT_LINES, mTexts.toArray(a));
4175        }
4176
4177        /**
4178         * @hide
4179         */
4180        @Override
4181        protected void restoreFromExtras(Bundle extras) {
4182            super.restoreFromExtras(extras);
4183
4184            mTexts.clear();
4185            if (extras.containsKey(EXTRA_TEXT_LINES)) {
4186                Collections.addAll(mTexts, extras.getCharSequenceArray(EXTRA_TEXT_LINES));
4187            }
4188        }
4189
4190        /**
4191         * @hide
4192         */
4193        public RemoteViews makeBigContentView() {
4194            // Remove the content text so it disappears unless you have a summary
4195            // Nasty
4196            CharSequence oldBuilderContentText = mBuilder.mN.extras.getCharSequence(EXTRA_TEXT);
4197            mBuilder.getAllExtras().putCharSequence(EXTRA_TEXT, null);
4198
4199            RemoteViews contentView = getStandardView(mBuilder.getInboxLayoutResource());
4200
4201            mBuilder.getAllExtras().putCharSequence(EXTRA_TEXT, oldBuilderContentText);
4202
4203            int[] rowIds = {R.id.inbox_text0, R.id.inbox_text1, R.id.inbox_text2, R.id.inbox_text3,
4204                    R.id.inbox_text4, R.id.inbox_text5, R.id.inbox_text6};
4205
4206            // Make sure all rows are gone in case we reuse a view.
4207            for (int rowId : rowIds) {
4208                contentView.setViewVisibility(rowId, View.GONE);
4209            }
4210
4211            final boolean largeText =
4212                    mBuilder.mContext.getResources().getConfiguration().fontScale > 1f;
4213            final float subTextSize = mBuilder.mContext.getResources().getDimensionPixelSize(
4214                    R.dimen.notification_subtext_size);
4215            int i=0;
4216            final float density = mBuilder.mContext.getResources().getDisplayMetrics().density;
4217            int topPadding = (int) (5 * density);
4218            int bottomPadding = mBuilder.mContext.getResources().getDimensionPixelSize(
4219                    com.android.internal.R.dimen.notification_content_margin_bottom);
4220            boolean first = true;
4221            while (i < mTexts.size() && i < rowIds.length) {
4222                CharSequence str = mTexts.get(i);
4223                if (str != null && !str.equals("")) {
4224                    contentView.setViewVisibility(rowIds[i], View.VISIBLE);
4225                    contentView.setTextViewText(rowIds[i], mBuilder.processLegacyText(str));
4226                    if (largeText) {
4227                        contentView.setTextViewTextSize(rowIds[i], TypedValue.COMPLEX_UNIT_PX,
4228                                subTextSize);
4229                    }
4230                    contentView.setViewPadding(rowIds[i], 0, topPadding, 0,
4231                            i == rowIds.length - 1 || i == mTexts.size() - 1 ? bottomPadding : 0);
4232                    handleInboxImageMargin(contentView, rowIds[i], first);
4233                    first = false;
4234                }
4235                i++;
4236            }
4237
4238
4239            return contentView;
4240        }
4241
4242        private void handleInboxImageMargin(RemoteViews contentView, int id, boolean first) {
4243            int endMargin = 0;
4244            if (first) {
4245                final int max = mBuilder.mN.extras.getInt(EXTRA_PROGRESS_MAX, 0);
4246                final boolean ind = mBuilder.mN.extras.getBoolean(EXTRA_PROGRESS_INDETERMINATE);
4247                boolean hasProgress = max != 0 || ind;
4248                if (mBuilder.mN.mLargeIcon != null && !hasProgress) {
4249                    endMargin = mBuilder.mContext.getResources().getDimensionPixelSize(
4250                            R.dimen.notification_content_picture_margin);
4251                }
4252            }
4253            contentView.setViewLayoutMarginEnd(id, endMargin);
4254        }
4255    }
4256
4257    /**
4258     * Notification style for media playback notifications.
4259     *
4260     * In the expanded form, {@link Notification#bigContentView}, up to 5
4261     * {@link Notification.Action}s specified with
4262     * {@link Notification.Builder#addAction(Action) addAction} will be
4263     * shown as icon-only pushbuttons, suitable for transport controls. The Bitmap given to
4264     * {@link Notification.Builder#setLargeIcon(android.graphics.Bitmap) setLargeIcon()} will be
4265     * treated as album artwork.
4266     *
4267     * Unlike the other styles provided here, MediaStyle can also modify the standard-size
4268     * {@link Notification#contentView}; by providing action indices to
4269     * {@link #setShowActionsInCompactView(int...)} you can promote up to 3 actions to be displayed
4270     * in the standard view alongside the usual content.
4271     *
4272     * Notifications created with MediaStyle will have their category set to
4273     * {@link Notification#CATEGORY_TRANSPORT CATEGORY_TRANSPORT} unless you set a different
4274     * category using {@link Notification.Builder#setCategory(String) setCategory()}.
4275     *
4276     * Finally, if you attach a {@link android.media.session.MediaSession.Token} using
4277     * {@link android.app.Notification.MediaStyle#setMediaSession(MediaSession.Token)},
4278     * the System UI can identify this as a notification representing an active media session
4279     * and respond accordingly (by showing album artwork in the lockscreen, for example).
4280     *
4281     * To use this style with your Notification, feed it to
4282     * {@link Notification.Builder#setStyle(android.app.Notification.Style)} like so:
4283     * <pre class="prettyprint">
4284     * Notification noti = new Notification.Builder()
4285     *     .setSmallIcon(R.drawable.ic_stat_player)
4286     *     .setContentTitle(&quot;Track title&quot;)
4287     *     .setContentText(&quot;Artist - Album&quot;)
4288     *     .setLargeIcon(albumArtBitmap))
4289     *     .setStyle(<b>new Notification.MediaStyle()</b>
4290     *         .setMediaSession(mySession))
4291     *     .build();
4292     * </pre>
4293     *
4294     * @see Notification#bigContentView
4295     */
4296    public static class MediaStyle extends Style {
4297        static final int MAX_MEDIA_BUTTONS_IN_COMPACT = 3;
4298        static final int MAX_MEDIA_BUTTONS = 5;
4299
4300        private int[] mActionsToShowInCompact = null;
4301        private MediaSession.Token mToken;
4302
4303        public MediaStyle() {
4304        }
4305
4306        public MediaStyle(Builder builder) {
4307            setBuilder(builder);
4308        }
4309
4310        /**
4311         * Request up to 3 actions (by index in the order of addition) to be shown in the compact
4312         * notification view.
4313         *
4314         * @param actions the indices of the actions to show in the compact notification view
4315         */
4316        public MediaStyle setShowActionsInCompactView(int...actions) {
4317            mActionsToShowInCompact = actions;
4318            return this;
4319        }
4320
4321        /**
4322         * Attach a {@link android.media.session.MediaSession.Token} to this Notification
4323         * to provide additional playback information and control to the SystemUI.
4324         */
4325        public MediaStyle setMediaSession(MediaSession.Token token) {
4326            mToken = token;
4327            return this;
4328        }
4329
4330        /**
4331         * @hide
4332         */
4333        @Override
4334        public Notification buildStyled(Notification wip) {
4335            super.buildStyled(wip);
4336            if (wip.category == null) {
4337                wip.category = Notification.CATEGORY_TRANSPORT;
4338            }
4339            return wip;
4340        }
4341
4342        /**
4343         * @hide
4344         */
4345        @Override
4346        public RemoteViews makeContentView() {
4347            return makeMediaContentView();
4348        }
4349
4350        /**
4351         * @hide
4352         */
4353        @Override
4354        public RemoteViews makeBigContentView() {
4355            return makeMediaBigContentView();
4356        }
4357
4358        /**
4359         * @hide
4360         */
4361        @Override
4362        public RemoteViews makeHeadsUpContentView() {
4363            RemoteViews expanded = makeMediaBigContentView();
4364            return expanded != null ? expanded : makeMediaContentView();
4365        }
4366
4367        /** @hide */
4368        @Override
4369        public void addExtras(Bundle extras) {
4370            super.addExtras(extras);
4371
4372            if (mToken != null) {
4373                extras.putParcelable(EXTRA_MEDIA_SESSION, mToken);
4374            }
4375            if (mActionsToShowInCompact != null) {
4376                extras.putIntArray(EXTRA_COMPACT_ACTIONS, mActionsToShowInCompact);
4377            }
4378        }
4379
4380        /**
4381         * @hide
4382         */
4383        @Override
4384        protected void restoreFromExtras(Bundle extras) {
4385            super.restoreFromExtras(extras);
4386
4387            if (extras.containsKey(EXTRA_MEDIA_SESSION)) {
4388                mToken = extras.getParcelable(EXTRA_MEDIA_SESSION);
4389            }
4390            if (extras.containsKey(EXTRA_COMPACT_ACTIONS)) {
4391                mActionsToShowInCompact = extras.getIntArray(EXTRA_COMPACT_ACTIONS);
4392            }
4393        }
4394
4395        private RemoteViews generateMediaActionButton(Action action, int color) {
4396            final boolean tombstone = (action.actionIntent == null);
4397            RemoteViews button = new BuilderRemoteViews(mBuilder.mContext.getApplicationInfo(),
4398                    R.layout.notification_material_media_action);
4399            button.setImageViewIcon(R.id.action0, action.getIcon());
4400            button.setDrawableParameters(R.id.action0, false, -1, color, PorterDuff.Mode.SRC_ATOP,
4401                    -1);
4402            if (!tombstone) {
4403                button.setOnClickPendingIntent(R.id.action0, action.actionIntent);
4404            }
4405            button.setContentDescription(R.id.action0, action.title);
4406            return button;
4407        }
4408
4409        private RemoteViews makeMediaContentView() {
4410            RemoteViews view = mBuilder.applyStandardTemplate(
4411                    R.layout.notification_template_material_media, false /* hasProgress */);
4412
4413            final int numActions = mBuilder.mActions.size();
4414            final int N = mActionsToShowInCompact == null
4415                    ? 0
4416                    : Math.min(mActionsToShowInCompact.length, MAX_MEDIA_BUTTONS_IN_COMPACT);
4417            if (N > 0) {
4418                view.removeAllViews(com.android.internal.R.id.media_actions);
4419                for (int i = 0; i < N; i++) {
4420                    if (i >= numActions) {
4421                        throw new IllegalArgumentException(String.format(
4422                                "setShowActionsInCompactView: action %d out of bounds (max %d)",
4423                                i, numActions - 1));
4424                    }
4425
4426                    final Action action = mBuilder.mActions.get(mActionsToShowInCompact[i]);
4427                    final RemoteViews button = generateMediaActionButton(action,
4428                            mBuilder.resolveColor());
4429                    view.addView(com.android.internal.R.id.media_actions, button);
4430                }
4431            }
4432            handleImage(view);
4433            // handle the content margin
4434            int endMargin = mBuilder.mContext.getResources().getDimensionPixelSize(
4435                    R.dimen.notification_content_margin_end);;
4436            if (mBuilder.mN.mLargeIcon != null) {
4437                endMargin += mBuilder.mContext.getResources().getDimensionPixelSize(
4438                        R.dimen.notification_content_picture_margin);
4439            }
4440            view.setViewLayoutMarginEnd(R.id.notification_main_column, endMargin);
4441            return view;
4442        }
4443
4444        private RemoteViews makeMediaBigContentView() {
4445            final int actionCount = Math.min(mBuilder.mActions.size(), MAX_MEDIA_BUTTONS);
4446            // Dont add an expanded view if there is no more content to be revealed
4447            int actionsInCompact = mActionsToShowInCompact == null
4448                    ? 0
4449                    : Math.min(mActionsToShowInCompact.length, MAX_MEDIA_BUTTONS_IN_COMPACT);
4450            if (mBuilder.mN.mLargeIcon == null && actionCount <= actionsInCompact) {
4451                return null;
4452            }
4453            RemoteViews big = mBuilder.applyStandardTemplate(
4454                    R.layout.notification_template_material_big_media,
4455                    false);
4456
4457            if (actionCount > 0) {
4458                big.removeAllViews(com.android.internal.R.id.media_actions);
4459                for (int i = 0; i < actionCount; i++) {
4460                    final RemoteViews button = generateMediaActionButton(mBuilder.mActions.get(i),
4461                            mBuilder.resolveColor());
4462                    big.addView(com.android.internal.R.id.media_actions, button);
4463                }
4464            }
4465            handleImage(big);
4466            return big;
4467        }
4468
4469        private void handleImage(RemoteViews contentView) {
4470            if (mBuilder.mN.mLargeIcon != null) {
4471                contentView.setViewLayoutMarginEnd(R.id.line1, 0);
4472                contentView.setViewLayoutMarginEnd(R.id.text, 0);
4473            }
4474        }
4475
4476        /**
4477         * @hide
4478         */
4479        @Override
4480        protected boolean hasProgress() {
4481            return false;
4482        }
4483    }
4484
4485    /**
4486     * Notification style for custom views that are decorated by the system
4487     *
4488     * <p>Instead of providing a notification that is completely custom, a developer can set this
4489     * style and still obtain system decorations like the notification header with the expand
4490     * affordance and actions.
4491     *
4492     * <p>Use {@link android.app.Notification.Builder#setCustomContentView(RemoteViews)},
4493     * {@link android.app.Notification.Builder#setCustomBigContentView(RemoteViews)} and
4494     * {@link android.app.Notification.Builder#setCustomHeadsUpContentView(RemoteViews)} to set the
4495     * corresponding custom views to display.
4496     *
4497     * To use this style with your Notification, feed it to
4498     * {@link Notification.Builder#setStyle(android.app.Notification.Style)} like so:
4499     * <pre class="prettyprint">
4500     * Notification noti = new Notification.Builder()
4501     *     .setSmallIcon(R.drawable.ic_stat_player)
4502     *     .setLargeIcon(albumArtBitmap))
4503     *     .setCustomContentView(contentView);
4504     *     .setStyle(<b>new Notification.DecoratedCustomViewStyle()</b>)
4505     *     .build();
4506     * </pre>
4507     */
4508    public static class DecoratedCustomViewStyle extends Style {
4509
4510        public DecoratedCustomViewStyle() {
4511        }
4512
4513        public DecoratedCustomViewStyle(Builder builder) {
4514            setBuilder(builder);
4515        }
4516
4517        /**
4518         * @hide
4519         */
4520        public boolean displayCustomViewInline() {
4521            return true;
4522        }
4523
4524        /**
4525         * @hide
4526         */
4527        @Override
4528        public RemoteViews makeContentView() {
4529            return makeStandardTemplateWithCustomContent(mBuilder.mN.contentView);
4530        }
4531
4532        /**
4533         * @hide
4534         */
4535        @Override
4536        public RemoteViews makeBigContentView() {
4537            return makeDecoratedBigContentView();
4538        }
4539
4540        /**
4541         * @hide
4542         */
4543        @Override
4544        public RemoteViews makeHeadsUpContentView() {
4545            return makeDecoratedHeadsUpContentView();
4546        }
4547
4548        private RemoteViews makeDecoratedHeadsUpContentView() {
4549            RemoteViews headsUpContentView = mBuilder.mN.headsUpContentView == null
4550                    ? mBuilder.mN.contentView
4551                    : mBuilder.mN.headsUpContentView;
4552            if (mBuilder.mActions.size() == 0) {
4553               return makeStandardTemplateWithCustomContent(headsUpContentView);
4554            }
4555            RemoteViews remoteViews = mBuilder.applyStandardTemplateWithActions(
4556                        mBuilder.getBigBaseLayoutResource());
4557            buildIntoRemoteViewContent(remoteViews, headsUpContentView);
4558            return remoteViews;
4559        }
4560
4561        private RemoteViews makeStandardTemplateWithCustomContent(RemoteViews customContent) {
4562            RemoteViews remoteViews = mBuilder.applyStandardTemplate(
4563                    mBuilder.getBaseLayoutResource());
4564            buildIntoRemoteViewContent(remoteViews, customContent);
4565            return remoteViews;
4566        }
4567
4568        private RemoteViews makeDecoratedBigContentView() {
4569            RemoteViews bigContentView = mBuilder.mN.bigContentView == null
4570                    ? mBuilder.mN.contentView
4571                    : mBuilder.mN.bigContentView;
4572            if (mBuilder.mActions.size() == 0) {
4573                return makeStandardTemplateWithCustomContent(bigContentView);
4574            }
4575            RemoteViews remoteViews = mBuilder.applyStandardTemplateWithActions(
4576                    mBuilder.getBigBaseLayoutResource());
4577            buildIntoRemoteViewContent(remoteViews, bigContentView);
4578            return remoteViews;
4579        }
4580
4581        private void buildIntoRemoteViewContent(RemoteViews remoteViews,
4582                RemoteViews customContent) {
4583            remoteViews.removeAllViews(R.id.notification_main_column);
4584            // Need to clone customContent before adding, because otherwise it can no longer be
4585            // parceled independently of remoteViews.
4586            if (customContent != null) {
4587                customContent = customContent.clone();
4588            }
4589            remoteViews.addView(R.id.notification_main_column, customContent);
4590            // also update the end margin if there is an image
4591            int endMargin = mBuilder.mContext.getResources().getDimensionPixelSize(
4592                    R.dimen.notification_content_margin_end);
4593            if (mBuilder.mN.mLargeIcon != null) {
4594                endMargin += mBuilder.mContext.getResources().getDimensionPixelSize(
4595                        R.dimen.notification_content_picture_margin);
4596            }
4597            remoteViews.setViewLayoutMarginEnd(R.id.notification_main_column, endMargin);
4598        }
4599    }
4600
4601    /**
4602     * Notification style for media custom views that are decorated by the system
4603     *
4604     * <p>Instead of providing a media notification that is completely custom, a developer can set
4605     * this style and still obtain system decorations like the notification header with the expand
4606     * affordance and actions.
4607     *
4608     * <p>Use {@link android.app.Notification.Builder#setCustomContentView(RemoteViews)},
4609     * {@link android.app.Notification.Builder#setCustomBigContentView(RemoteViews)} and
4610     * {@link android.app.Notification.Builder#setCustomHeadsUpContentView(RemoteViews)} to set the
4611     * corresponding custom views to display.
4612     *
4613     * To use this style with your Notification, feed it to
4614     * {@link Notification.Builder#setStyle(android.app.Notification.Style)} like so:
4615     * <pre class="prettyprint">
4616     * Notification noti = new Notification.Builder()
4617     *     .setSmallIcon(R.drawable.ic_stat_player)
4618     *     .setLargeIcon(albumArtBitmap))
4619     *     .setCustomContentView(contentView);
4620     *     .setStyle(<b>new Notification.DecoratedMediaCustomViewStyle()</b>
4621     *          .setMediaSession(mySession))
4622     *     .build();
4623     * </pre>
4624     *
4625     * @see android.app.Notification.DecoratedCustomViewStyle
4626     * @see android.app.Notification.MediaStyle
4627     */
4628    public static class DecoratedMediaCustomViewStyle extends MediaStyle {
4629
4630        public DecoratedMediaCustomViewStyle() {
4631        }
4632
4633        public DecoratedMediaCustomViewStyle(Builder builder) {
4634            setBuilder(builder);
4635        }
4636
4637        /**
4638         * @hide
4639         */
4640        public boolean displayCustomViewInline() {
4641            return true;
4642        }
4643
4644        /**
4645         * @hide
4646         */
4647        @Override
4648        public RemoteViews makeContentView() {
4649            RemoteViews remoteViews = super.makeContentView();
4650            return buildIntoRemoteView(remoteViews, R.id.notification_content_container,
4651                    mBuilder.mN.contentView);
4652        }
4653
4654        /**
4655         * @hide
4656         */
4657        @Override
4658        public RemoteViews makeBigContentView() {
4659            RemoteViews customRemoteView = mBuilder.mN.bigContentView != null
4660                    ? mBuilder.mN.bigContentView
4661                    : mBuilder.mN.contentView;
4662            return makeBigContentViewWithCustomContent(customRemoteView);
4663        }
4664
4665        private RemoteViews makeBigContentViewWithCustomContent(RemoteViews customRemoteView) {
4666            RemoteViews remoteViews = super.makeBigContentView();
4667            if (remoteViews != null) {
4668                return buildIntoRemoteView(remoteViews, R.id.notification_main_column,
4669                        customRemoteView);
4670            } else if (customRemoteView != mBuilder.mN.contentView){
4671                remoteViews = super.makeContentView();
4672                return buildIntoRemoteView(remoteViews, R.id.notification_content_container,
4673                        customRemoteView);
4674            } else {
4675                return null;
4676            }
4677        }
4678
4679        /**
4680         * @hide
4681         */
4682        @Override
4683        public RemoteViews makeHeadsUpContentView() {
4684            RemoteViews customRemoteView = mBuilder.mN.headsUpContentView != null
4685                    ? mBuilder.mN.headsUpContentView
4686                    : mBuilder.mN.contentView;
4687            return makeBigContentViewWithCustomContent(customRemoteView);
4688        }
4689
4690        private RemoteViews buildIntoRemoteView(RemoteViews remoteViews, int id,
4691                RemoteViews customContent) {
4692            remoteViews.removeAllViews(id);
4693            // Need to clone customContent before adding, because otherwise it can no longer be
4694            // parceled independently of remoteViews.
4695            if (customContent != null) {
4696                customContent = customContent.clone();
4697            }
4698            remoteViews.addView(id, customContent);
4699            return remoteViews;
4700        }
4701    }
4702
4703    // When adding a new Style subclass here, don't forget to update
4704    // Builder.getNotificationStyleClass.
4705
4706    /**
4707     * Extender interface for use with {@link Builder#extend}. Extenders may be used to add
4708     * metadata or change options on a notification builder.
4709     */
4710    public interface Extender {
4711        /**
4712         * Apply this extender to a notification builder.
4713         * @param builder the builder to be modified.
4714         * @return the build object for chaining.
4715         */
4716        public Builder extend(Builder builder);
4717    }
4718
4719    /**
4720     * Helper class to add wearable extensions to notifications.
4721     * <p class="note"> See
4722     * <a href="{@docRoot}wear/notifications/creating.html">Creating Notifications
4723     * for Android Wear</a> for more information on how to use this class.
4724     * <p>
4725     * To create a notification with wearable extensions:
4726     * <ol>
4727     *   <li>Create a {@link android.app.Notification.Builder}, setting any desired
4728     *   properties.
4729     *   <li>Create a {@link android.app.Notification.WearableExtender}.
4730     *   <li>Set wearable-specific properties using the
4731     *   {@code add} and {@code set} methods of {@link android.app.Notification.WearableExtender}.
4732     *   <li>Call {@link android.app.Notification.Builder#extend} to apply the extensions to a
4733     *   notification.
4734     *   <li>Post the notification to the notification system with the
4735     *   {@code NotificationManager.notify(...)} methods.
4736     * </ol>
4737     *
4738     * <pre class="prettyprint">
4739     * Notification notif = new Notification.Builder(mContext)
4740     *         .setContentTitle(&quot;New mail from &quot; + sender.toString())
4741     *         .setContentText(subject)
4742     *         .setSmallIcon(R.drawable.new_mail)
4743     *         .extend(new Notification.WearableExtender()
4744     *                 .setContentIcon(R.drawable.new_mail))
4745     *         .build();
4746     * NotificationManager notificationManger =
4747     *         (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
4748     * notificationManger.notify(0, notif);</pre>
4749     *
4750     * <p>Wearable extensions can be accessed on an existing notification by using the
4751     * {@code WearableExtender(Notification)} constructor,
4752     * and then using the {@code get} methods to access values.
4753     *
4754     * <pre class="prettyprint">
4755     * Notification.WearableExtender wearableExtender = new Notification.WearableExtender(
4756     *         notification);
4757     * List&lt;Notification&gt; pages = wearableExtender.getPages();</pre>
4758     */
4759    public static final class WearableExtender implements Extender {
4760        /**
4761         * Sentinel value for an action index that is unset.
4762         */
4763        public static final int UNSET_ACTION_INDEX = -1;
4764
4765        /**
4766         * Size value for use with {@link #setCustomSizePreset} to show this notification with
4767         * default sizing.
4768         * <p>For custom display notifications created using {@link #setDisplayIntent},
4769         * the default is {@link #SIZE_MEDIUM}. All other notifications size automatically based
4770         * on their content.
4771         */
4772        public static final int SIZE_DEFAULT = 0;
4773
4774        /**
4775         * Size value for use with {@link #setCustomSizePreset} to show this notification
4776         * with an extra small size.
4777         * <p>This value is only applicable for custom display notifications created using
4778         * {@link #setDisplayIntent}.
4779         */
4780        public static final int SIZE_XSMALL = 1;
4781
4782        /**
4783         * Size value for use with {@link #setCustomSizePreset} to show this notification
4784         * with a small size.
4785         * <p>This value is only applicable for custom display notifications created using
4786         * {@link #setDisplayIntent}.
4787         */
4788        public static final int SIZE_SMALL = 2;
4789
4790        /**
4791         * Size value for use with {@link #setCustomSizePreset} to show this notification
4792         * with a medium size.
4793         * <p>This value is only applicable for custom display notifications created using
4794         * {@link #setDisplayIntent}.
4795         */
4796        public static final int SIZE_MEDIUM = 3;
4797
4798        /**
4799         * Size value for use with {@link #setCustomSizePreset} to show this notification
4800         * with a large size.
4801         * <p>This value is only applicable for custom display notifications created using
4802         * {@link #setDisplayIntent}.
4803         */
4804        public static final int SIZE_LARGE = 4;
4805
4806        /**
4807         * Size value for use with {@link #setCustomSizePreset} to show this notification
4808         * full screen.
4809         * <p>This value is only applicable for custom display notifications created using
4810         * {@link #setDisplayIntent}.
4811         */
4812        public static final int SIZE_FULL_SCREEN = 5;
4813
4814        /**
4815         * Sentinel value for use with {@link #setHintScreenTimeout} to keep the screen on for a
4816         * short amount of time when this notification is displayed on the screen. This
4817         * is the default value.
4818         */
4819        public static final int SCREEN_TIMEOUT_SHORT = 0;
4820
4821        /**
4822         * Sentinel value for use with {@link #setHintScreenTimeout} to keep the screen on
4823         * for a longer amount of time when this notification is displayed on the screen.
4824         */
4825        public static final int SCREEN_TIMEOUT_LONG = -1;
4826
4827        /** Notification extra which contains wearable extensions */
4828        private static final String EXTRA_WEARABLE_EXTENSIONS = "android.wearable.EXTENSIONS";
4829
4830        // Keys within EXTRA_WEARABLE_EXTENSIONS for wearable options.
4831        private static final String KEY_ACTIONS = "actions";
4832        private static final String KEY_FLAGS = "flags";
4833        private static final String KEY_DISPLAY_INTENT = "displayIntent";
4834        private static final String KEY_PAGES = "pages";
4835        private static final String KEY_BACKGROUND = "background";
4836        private static final String KEY_CONTENT_ICON = "contentIcon";
4837        private static final String KEY_CONTENT_ICON_GRAVITY = "contentIconGravity";
4838        private static final String KEY_CONTENT_ACTION_INDEX = "contentActionIndex";
4839        private static final String KEY_CUSTOM_SIZE_PRESET = "customSizePreset";
4840        private static final String KEY_CUSTOM_CONTENT_HEIGHT = "customContentHeight";
4841        private static final String KEY_GRAVITY = "gravity";
4842        private static final String KEY_HINT_SCREEN_TIMEOUT = "hintScreenTimeout";
4843
4844        // Flags bitwise-ored to mFlags
4845        private static final int FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE = 0x1;
4846        private static final int FLAG_HINT_HIDE_ICON = 1 << 1;
4847        private static final int FLAG_HINT_SHOW_BACKGROUND_ONLY = 1 << 2;
4848        private static final int FLAG_START_SCROLL_BOTTOM = 1 << 3;
4849        private static final int FLAG_HINT_AVOID_BACKGROUND_CLIPPING = 1 << 4;
4850
4851        // Default value for flags integer
4852        private static final int DEFAULT_FLAGS = FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE;
4853
4854        private static final int DEFAULT_CONTENT_ICON_GRAVITY = Gravity.END;
4855        private static final int DEFAULT_GRAVITY = Gravity.BOTTOM;
4856
4857        private ArrayList<Action> mActions = new ArrayList<Action>();
4858        private int mFlags = DEFAULT_FLAGS;
4859        private PendingIntent mDisplayIntent;
4860        private ArrayList<Notification> mPages = new ArrayList<Notification>();
4861        private Bitmap mBackground;
4862        private int mContentIcon;
4863        private int mContentIconGravity = DEFAULT_CONTENT_ICON_GRAVITY;
4864        private int mContentActionIndex = UNSET_ACTION_INDEX;
4865        private int mCustomSizePreset = SIZE_DEFAULT;
4866        private int mCustomContentHeight;
4867        private int mGravity = DEFAULT_GRAVITY;
4868        private int mHintScreenTimeout;
4869
4870        /**
4871         * Create a {@link android.app.Notification.WearableExtender} with default
4872         * options.
4873         */
4874        public WearableExtender() {
4875        }
4876
4877        public WearableExtender(Notification notif) {
4878            Bundle wearableBundle = notif.extras.getBundle(EXTRA_WEARABLE_EXTENSIONS);
4879            if (wearableBundle != null) {
4880                List<Action> actions = wearableBundle.getParcelableArrayList(KEY_ACTIONS);
4881                if (actions != null) {
4882                    mActions.addAll(actions);
4883                }
4884
4885                mFlags = wearableBundle.getInt(KEY_FLAGS, DEFAULT_FLAGS);
4886                mDisplayIntent = wearableBundle.getParcelable(KEY_DISPLAY_INTENT);
4887
4888                Notification[] pages = getNotificationArrayFromBundle(
4889                        wearableBundle, KEY_PAGES);
4890                if (pages != null) {
4891                    Collections.addAll(mPages, pages);
4892                }
4893
4894                mBackground = wearableBundle.getParcelable(KEY_BACKGROUND);
4895                mContentIcon = wearableBundle.getInt(KEY_CONTENT_ICON);
4896                mContentIconGravity = wearableBundle.getInt(KEY_CONTENT_ICON_GRAVITY,
4897                        DEFAULT_CONTENT_ICON_GRAVITY);
4898                mContentActionIndex = wearableBundle.getInt(KEY_CONTENT_ACTION_INDEX,
4899                        UNSET_ACTION_INDEX);
4900                mCustomSizePreset = wearableBundle.getInt(KEY_CUSTOM_SIZE_PRESET,
4901                        SIZE_DEFAULT);
4902                mCustomContentHeight = wearableBundle.getInt(KEY_CUSTOM_CONTENT_HEIGHT);
4903                mGravity = wearableBundle.getInt(KEY_GRAVITY, DEFAULT_GRAVITY);
4904                mHintScreenTimeout = wearableBundle.getInt(KEY_HINT_SCREEN_TIMEOUT);
4905            }
4906        }
4907
4908        /**
4909         * Apply wearable extensions to a notification that is being built. This is typically
4910         * called by the {@link android.app.Notification.Builder#extend} method of
4911         * {@link android.app.Notification.Builder}.
4912         */
4913        @Override
4914        public Notification.Builder extend(Notification.Builder builder) {
4915            Bundle wearableBundle = new Bundle();
4916
4917            if (!mActions.isEmpty()) {
4918                wearableBundle.putParcelableArrayList(KEY_ACTIONS, mActions);
4919            }
4920            if (mFlags != DEFAULT_FLAGS) {
4921                wearableBundle.putInt(KEY_FLAGS, mFlags);
4922            }
4923            if (mDisplayIntent != null) {
4924                wearableBundle.putParcelable(KEY_DISPLAY_INTENT, mDisplayIntent);
4925            }
4926            if (!mPages.isEmpty()) {
4927                wearableBundle.putParcelableArray(KEY_PAGES, mPages.toArray(
4928                        new Notification[mPages.size()]));
4929            }
4930            if (mBackground != null) {
4931                wearableBundle.putParcelable(KEY_BACKGROUND, mBackground);
4932            }
4933            if (mContentIcon != 0) {
4934                wearableBundle.putInt(KEY_CONTENT_ICON, mContentIcon);
4935            }
4936            if (mContentIconGravity != DEFAULT_CONTENT_ICON_GRAVITY) {
4937                wearableBundle.putInt(KEY_CONTENT_ICON_GRAVITY, mContentIconGravity);
4938            }
4939            if (mContentActionIndex != UNSET_ACTION_INDEX) {
4940                wearableBundle.putInt(KEY_CONTENT_ACTION_INDEX,
4941                        mContentActionIndex);
4942            }
4943            if (mCustomSizePreset != SIZE_DEFAULT) {
4944                wearableBundle.putInt(KEY_CUSTOM_SIZE_PRESET, mCustomSizePreset);
4945            }
4946            if (mCustomContentHeight != 0) {
4947                wearableBundle.putInt(KEY_CUSTOM_CONTENT_HEIGHT, mCustomContentHeight);
4948            }
4949            if (mGravity != DEFAULT_GRAVITY) {
4950                wearableBundle.putInt(KEY_GRAVITY, mGravity);
4951            }
4952            if (mHintScreenTimeout != 0) {
4953                wearableBundle.putInt(KEY_HINT_SCREEN_TIMEOUT, mHintScreenTimeout);
4954            }
4955
4956            builder.getExtras().putBundle(EXTRA_WEARABLE_EXTENSIONS, wearableBundle);
4957            return builder;
4958        }
4959
4960        @Override
4961        public WearableExtender clone() {
4962            WearableExtender that = new WearableExtender();
4963            that.mActions = new ArrayList<Action>(this.mActions);
4964            that.mFlags = this.mFlags;
4965            that.mDisplayIntent = this.mDisplayIntent;
4966            that.mPages = new ArrayList<Notification>(this.mPages);
4967            that.mBackground = this.mBackground;
4968            that.mContentIcon = this.mContentIcon;
4969            that.mContentIconGravity = this.mContentIconGravity;
4970            that.mContentActionIndex = this.mContentActionIndex;
4971            that.mCustomSizePreset = this.mCustomSizePreset;
4972            that.mCustomContentHeight = this.mCustomContentHeight;
4973            that.mGravity = this.mGravity;
4974            that.mHintScreenTimeout = this.mHintScreenTimeout;
4975            return that;
4976        }
4977
4978        /**
4979         * Add a wearable action to this notification.
4980         *
4981         * <p>When wearable actions are added using this method, the set of actions that
4982         * show on a wearable device splits from devices that only show actions added
4983         * using {@link android.app.Notification.Builder#addAction}. This allows for customization
4984         * of which actions display on different devices.
4985         *
4986         * @param action the action to add to this notification
4987         * @return this object for method chaining
4988         * @see android.app.Notification.Action
4989         */
4990        public WearableExtender addAction(Action action) {
4991            mActions.add(action);
4992            return this;
4993        }
4994
4995        /**
4996         * Adds wearable actions to this notification.
4997         *
4998         * <p>When wearable actions are added using this method, the set of actions that
4999         * show on a wearable device splits from devices that only show actions added
5000         * using {@link android.app.Notification.Builder#addAction}. This allows for customization
5001         * of which actions display on different devices.
5002         *
5003         * @param actions the actions to add to this notification
5004         * @return this object for method chaining
5005         * @see android.app.Notification.Action
5006         */
5007        public WearableExtender addActions(List<Action> actions) {
5008            mActions.addAll(actions);
5009            return this;
5010        }
5011
5012        /**
5013         * Clear all wearable actions present on this builder.
5014         * @return this object for method chaining.
5015         * @see #addAction
5016         */
5017        public WearableExtender clearActions() {
5018            mActions.clear();
5019            return this;
5020        }
5021
5022        /**
5023         * Get the wearable actions present on this notification.
5024         */
5025        public List<Action> getActions() {
5026            return mActions;
5027        }
5028
5029        /**
5030         * Set an intent to launch inside of an activity view when displaying
5031         * this notification. The {@link PendingIntent} provided should be for an activity.
5032         *
5033         * <pre class="prettyprint">
5034         * Intent displayIntent = new Intent(context, MyDisplayActivity.class);
5035         * PendingIntent displayPendingIntent = PendingIntent.getActivity(context,
5036         *         0, displayIntent, PendingIntent.FLAG_UPDATE_CURRENT);
5037         * Notification notif = new Notification.Builder(context)
5038         *         .extend(new Notification.WearableExtender()
5039         *                 .setDisplayIntent(displayPendingIntent)
5040         *                 .setCustomSizePreset(Notification.WearableExtender.SIZE_MEDIUM))
5041         *         .build();</pre>
5042         *
5043         * <p>The activity to launch needs to allow embedding, must be exported, and
5044         * should have an empty task affinity. It is also recommended to use the device
5045         * default light theme.
5046         *
5047         * <p>Example AndroidManifest.xml entry:
5048         * <pre class="prettyprint">
5049         * &lt;activity android:name=&quot;com.example.MyDisplayActivity&quot;
5050         *     android:exported=&quot;true&quot;
5051         *     android:allowEmbedded=&quot;true&quot;
5052         *     android:taskAffinity=&quot;&quot;
5053         *     android:theme=&quot;@android:style/Theme.DeviceDefault.Light&quot; /&gt;</pre>
5054         *
5055         * @param intent the {@link PendingIntent} for an activity
5056         * @return this object for method chaining
5057         * @see android.app.Notification.WearableExtender#getDisplayIntent
5058         */
5059        public WearableExtender setDisplayIntent(PendingIntent intent) {
5060            mDisplayIntent = intent;
5061            return this;
5062        }
5063
5064        /**
5065         * Get the intent to launch inside of an activity view when displaying this
5066         * notification. This {@code PendingIntent} should be for an activity.
5067         */
5068        public PendingIntent getDisplayIntent() {
5069            return mDisplayIntent;
5070        }
5071
5072        /**
5073         * Add an additional page of content to display with this notification. The current
5074         * notification forms the first page, and pages added using this function form
5075         * subsequent pages. This field can be used to separate a notification into multiple
5076         * sections.
5077         *
5078         * @param page the notification to add as another page
5079         * @return this object for method chaining
5080         * @see android.app.Notification.WearableExtender#getPages
5081         */
5082        public WearableExtender addPage(Notification page) {
5083            mPages.add(page);
5084            return this;
5085        }
5086
5087        /**
5088         * Add additional pages of content to display with this notification. The current
5089         * notification forms the first page, and pages added using this function form
5090         * subsequent pages. This field can be used to separate a notification into multiple
5091         * sections.
5092         *
5093         * @param pages a list of notifications
5094         * @return this object for method chaining
5095         * @see android.app.Notification.WearableExtender#getPages
5096         */
5097        public WearableExtender addPages(List<Notification> pages) {
5098            mPages.addAll(pages);
5099            return this;
5100        }
5101
5102        /**
5103         * Clear all additional pages present on this builder.
5104         * @return this object for method chaining.
5105         * @see #addPage
5106         */
5107        public WearableExtender clearPages() {
5108            mPages.clear();
5109            return this;
5110        }
5111
5112        /**
5113         * Get the array of additional pages of content for displaying this notification. The
5114         * current notification forms the first page, and elements within this array form
5115         * subsequent pages. This field can be used to separate a notification into multiple
5116         * sections.
5117         * @return the pages for this notification
5118         */
5119        public List<Notification> getPages() {
5120            return mPages;
5121        }
5122
5123        /**
5124         * Set a background image to be displayed behind the notification content.
5125         * Contrary to the {@link android.app.Notification.BigPictureStyle}, this background
5126         * will work with any notification style.
5127         *
5128         * @param background the background bitmap
5129         * @return this object for method chaining
5130         * @see android.app.Notification.WearableExtender#getBackground
5131         */
5132        public WearableExtender setBackground(Bitmap background) {
5133            mBackground = background;
5134            return this;
5135        }
5136
5137        /**
5138         * Get a background image to be displayed behind the notification content.
5139         * Contrary to the {@link android.app.Notification.BigPictureStyle}, this background
5140         * will work with any notification style.
5141         *
5142         * @return the background image
5143         * @see android.app.Notification.WearableExtender#setBackground
5144         */
5145        public Bitmap getBackground() {
5146            return mBackground;
5147        }
5148
5149        /**
5150         * Set an icon that goes with the content of this notification.
5151         */
5152        public WearableExtender setContentIcon(int icon) {
5153            mContentIcon = icon;
5154            return this;
5155        }
5156
5157        /**
5158         * Get an icon that goes with the content of this notification.
5159         */
5160        public int getContentIcon() {
5161            return mContentIcon;
5162        }
5163
5164        /**
5165         * Set the gravity that the content icon should have within the notification display.
5166         * Supported values include {@link android.view.Gravity#START} and
5167         * {@link android.view.Gravity#END}. The default value is {@link android.view.Gravity#END}.
5168         * @see #setContentIcon
5169         */
5170        public WearableExtender setContentIconGravity(int contentIconGravity) {
5171            mContentIconGravity = contentIconGravity;
5172            return this;
5173        }
5174
5175        /**
5176         * Get the gravity that the content icon should have within the notification display.
5177         * Supported values include {@link android.view.Gravity#START} and
5178         * {@link android.view.Gravity#END}. The default value is {@link android.view.Gravity#END}.
5179         * @see #getContentIcon
5180         */
5181        public int getContentIconGravity() {
5182            return mContentIconGravity;
5183        }
5184
5185        /**
5186         * Set an action from this notification's actions to be clickable with the content of
5187         * this notification. This action will no longer display separately from the
5188         * notification's content.
5189         *
5190         * <p>For notifications with multiple pages, child pages can also have content actions
5191         * set, although the list of available actions comes from the main notification and not
5192         * from the child page's notification.
5193         *
5194         * @param actionIndex The index of the action to hoist onto the current notification page.
5195         *                    If wearable actions were added to the main notification, this index
5196         *                    will apply to that list, otherwise it will apply to the regular
5197         *                    actions list.
5198         */
5199        public WearableExtender setContentAction(int actionIndex) {
5200            mContentActionIndex = actionIndex;
5201            return this;
5202        }
5203
5204        /**
5205         * Get the index of the notification action, if any, that was specified as being clickable
5206         * with the content of this notification. This action will no longer display separately
5207         * from the notification's content.
5208         *
5209         * <p>For notifications with multiple pages, child pages can also have content actions
5210         * set, although the list of available actions comes from the main notification and not
5211         * from the child page's notification.
5212         *
5213         * <p>If wearable specific actions were added to the main notification, this index will
5214         * apply to that list, otherwise it will apply to the regular actions list.
5215         *
5216         * @return the action index or {@link #UNSET_ACTION_INDEX} if no action was selected.
5217         */
5218        public int getContentAction() {
5219            return mContentActionIndex;
5220        }
5221
5222        /**
5223         * Set the gravity that this notification should have within the available viewport space.
5224         * Supported values include {@link android.view.Gravity#TOP},
5225         * {@link android.view.Gravity#CENTER_VERTICAL} and {@link android.view.Gravity#BOTTOM}.
5226         * The default value is {@link android.view.Gravity#BOTTOM}.
5227         */
5228        public WearableExtender setGravity(int gravity) {
5229            mGravity = gravity;
5230            return this;
5231        }
5232
5233        /**
5234         * Get the gravity that this notification should have within the available viewport space.
5235         * Supported values include {@link android.view.Gravity#TOP},
5236         * {@link android.view.Gravity#CENTER_VERTICAL} and {@link android.view.Gravity#BOTTOM}.
5237         * The default value is {@link android.view.Gravity#BOTTOM}.
5238         */
5239        public int getGravity() {
5240            return mGravity;
5241        }
5242
5243        /**
5244         * Set the custom size preset for the display of this notification out of the available
5245         * presets found in {@link android.app.Notification.WearableExtender}, e.g.
5246         * {@link #SIZE_LARGE}.
5247         * <p>Some custom size presets are only applicable for custom display notifications created
5248         * using {@link android.app.Notification.WearableExtender#setDisplayIntent}. Check the
5249         * documentation for the preset in question. See also
5250         * {@link #setCustomContentHeight} and {@link #getCustomSizePreset}.
5251         */
5252        public WearableExtender setCustomSizePreset(int sizePreset) {
5253            mCustomSizePreset = sizePreset;
5254            return this;
5255        }
5256
5257        /**
5258         * Get the custom size preset for the display of this notification out of the available
5259         * presets found in {@link android.app.Notification.WearableExtender}, e.g.
5260         * {@link #SIZE_LARGE}.
5261         * <p>Some custom size presets are only applicable for custom display notifications created
5262         * using {@link #setDisplayIntent}. Check the documentation for the preset in question.
5263         * See also {@link #setCustomContentHeight} and {@link #setCustomSizePreset}.
5264         */
5265        public int getCustomSizePreset() {
5266            return mCustomSizePreset;
5267        }
5268
5269        /**
5270         * Set the custom height in pixels for the display of this notification's content.
5271         * <p>This option is only available for custom display notifications created
5272         * using {@link android.app.Notification.WearableExtender#setDisplayIntent}. See also
5273         * {@link android.app.Notification.WearableExtender#setCustomSizePreset} and
5274         * {@link #getCustomContentHeight}.
5275         */
5276        public WearableExtender setCustomContentHeight(int height) {
5277            mCustomContentHeight = height;
5278            return this;
5279        }
5280
5281        /**
5282         * Get the custom height in pixels for the display of this notification's content.
5283         * <p>This option is only available for custom display notifications created
5284         * using {@link #setDisplayIntent}. See also {@link #setCustomSizePreset} and
5285         * {@link #setCustomContentHeight}.
5286         */
5287        public int getCustomContentHeight() {
5288            return mCustomContentHeight;
5289        }
5290
5291        /**
5292         * Set whether the scrolling position for the contents of this notification should start
5293         * at the bottom of the contents instead of the top when the contents are too long to
5294         * display within the screen.  Default is false (start scroll at the top).
5295         */
5296        public WearableExtender setStartScrollBottom(boolean startScrollBottom) {
5297            setFlag(FLAG_START_SCROLL_BOTTOM, startScrollBottom);
5298            return this;
5299        }
5300
5301        /**
5302         * Get whether the scrolling position for the contents of this notification should start
5303         * at the bottom of the contents instead of the top when the contents are too long to
5304         * display within the screen. Default is false (start scroll at the top).
5305         */
5306        public boolean getStartScrollBottom() {
5307            return (mFlags & FLAG_START_SCROLL_BOTTOM) != 0;
5308        }
5309
5310        /**
5311         * Set whether the content intent is available when the wearable device is not connected
5312         * to a companion device.  The user can still trigger this intent when the wearable device
5313         * is offline, but a visual hint will indicate that the content intent may not be available.
5314         * Defaults to true.
5315         */
5316        public WearableExtender setContentIntentAvailableOffline(
5317                boolean contentIntentAvailableOffline) {
5318            setFlag(FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE, contentIntentAvailableOffline);
5319            return this;
5320        }
5321
5322        /**
5323         * Get whether the content intent is available when the wearable device is not connected
5324         * to a companion device.  The user can still trigger this intent when the wearable device
5325         * is offline, but a visual hint will indicate that the content intent may not be available.
5326         * Defaults to true.
5327         */
5328        public boolean getContentIntentAvailableOffline() {
5329            return (mFlags & FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE) != 0;
5330        }
5331
5332        /**
5333         * Set a hint that this notification's icon should not be displayed.
5334         * @param hintHideIcon {@code true} to hide the icon, {@code false} otherwise.
5335         * @return this object for method chaining
5336         */
5337        public WearableExtender setHintHideIcon(boolean hintHideIcon) {
5338            setFlag(FLAG_HINT_HIDE_ICON, hintHideIcon);
5339            return this;
5340        }
5341
5342        /**
5343         * Get a hint that this notification's icon should not be displayed.
5344         * @return {@code true} if this icon should not be displayed, false otherwise.
5345         * The default value is {@code false} if this was never set.
5346         */
5347        public boolean getHintHideIcon() {
5348            return (mFlags & FLAG_HINT_HIDE_ICON) != 0;
5349        }
5350
5351        /**
5352         * Set a visual hint that only the background image of this notification should be
5353         * displayed, and other semantic content should be hidden. This hint is only applicable
5354         * to sub-pages added using {@link #addPage}.
5355         */
5356        public WearableExtender setHintShowBackgroundOnly(boolean hintShowBackgroundOnly) {
5357            setFlag(FLAG_HINT_SHOW_BACKGROUND_ONLY, hintShowBackgroundOnly);
5358            return this;
5359        }
5360
5361        /**
5362         * Get a visual hint that only the background image of this notification should be
5363         * displayed, and other semantic content should be hidden. This hint is only applicable
5364         * to sub-pages added using {@link android.app.Notification.WearableExtender#addPage}.
5365         */
5366        public boolean getHintShowBackgroundOnly() {
5367            return (mFlags & FLAG_HINT_SHOW_BACKGROUND_ONLY) != 0;
5368        }
5369
5370        /**
5371         * Set a hint that this notification's background should not be clipped if possible,
5372         * and should instead be resized to fully display on the screen, retaining the aspect
5373         * ratio of the image. This can be useful for images like barcodes or qr codes.
5374         * @param hintAvoidBackgroundClipping {@code true} to avoid clipping if possible.
5375         * @return this object for method chaining
5376         */
5377        public WearableExtender setHintAvoidBackgroundClipping(
5378                boolean hintAvoidBackgroundClipping) {
5379            setFlag(FLAG_HINT_AVOID_BACKGROUND_CLIPPING, hintAvoidBackgroundClipping);
5380            return this;
5381        }
5382
5383        /**
5384         * Get a hint that this notification's background should not be clipped if possible,
5385         * and should instead be resized to fully display on the screen, retaining the aspect
5386         * ratio of the image. This can be useful for images like barcodes or qr codes.
5387         * @return {@code true} if it's ok if the background is clipped on the screen, false
5388         * otherwise. The default value is {@code false} if this was never set.
5389         */
5390        public boolean getHintAvoidBackgroundClipping() {
5391            return (mFlags & FLAG_HINT_AVOID_BACKGROUND_CLIPPING) != 0;
5392        }
5393
5394        /**
5395         * Set a hint that the screen should remain on for at least this duration when
5396         * this notification is displayed on the screen.
5397         * @param timeout The requested screen timeout in milliseconds. Can also be either
5398         *     {@link #SCREEN_TIMEOUT_SHORT} or {@link #SCREEN_TIMEOUT_LONG}.
5399         * @return this object for method chaining
5400         */
5401        public WearableExtender setHintScreenTimeout(int timeout) {
5402            mHintScreenTimeout = timeout;
5403            return this;
5404        }
5405
5406        /**
5407         * Get the duration, in milliseconds, that the screen should remain on for
5408         * when this notification is displayed.
5409         * @return the duration in milliseconds if > 0, or either one of the sentinel values
5410         *     {@link #SCREEN_TIMEOUT_SHORT} or {@link #SCREEN_TIMEOUT_LONG}.
5411         */
5412        public int getHintScreenTimeout() {
5413            return mHintScreenTimeout;
5414        }
5415
5416        private void setFlag(int mask, boolean value) {
5417            if (value) {
5418                mFlags |= mask;
5419            } else {
5420                mFlags &= ~mask;
5421            }
5422        }
5423    }
5424
5425    /**
5426     * <p>Helper class to add Android Auto extensions to notifications. To create a notification
5427     * with car extensions:
5428     *
5429     * <ol>
5430     *  <li>Create an {@link Notification.Builder}, setting any desired
5431     *  properties.
5432     *  <li>Create a {@link CarExtender}.
5433     *  <li>Set car-specific properties using the {@code add} and {@code set} methods of
5434     *  {@link CarExtender}.
5435     *  <li>Call {@link Notification.Builder#extend(Notification.Extender)}
5436     *  to apply the extensions to a notification.
5437     * </ol>
5438     *
5439     * <pre class="prettyprint">
5440     * Notification notification = new Notification.Builder(context)
5441     *         ...
5442     *         .extend(new CarExtender()
5443     *                 .set*(...))
5444     *         .build();
5445     * </pre>
5446     *
5447     * <p>Car extensions can be accessed on an existing notification by using the
5448     * {@code CarExtender(Notification)} constructor, and then using the {@code get} methods
5449     * to access values.
5450     */
5451    public static final class CarExtender implements Extender {
5452        private static final String TAG = "CarExtender";
5453
5454        private static final String EXTRA_CAR_EXTENDER = "android.car.EXTENSIONS";
5455        private static final String EXTRA_LARGE_ICON = "large_icon";
5456        private static final String EXTRA_CONVERSATION = "car_conversation";
5457        private static final String EXTRA_COLOR = "app_color";
5458
5459        private Bitmap mLargeIcon;
5460        private UnreadConversation mUnreadConversation;
5461        private int mColor = Notification.COLOR_DEFAULT;
5462
5463        /**
5464         * Create a {@link CarExtender} with default options.
5465         */
5466        public CarExtender() {
5467        }
5468
5469        /**
5470         * Create a {@link CarExtender} from the CarExtender options of an existing Notification.
5471         *
5472         * @param notif The notification from which to copy options.
5473         */
5474        public CarExtender(Notification notif) {
5475            Bundle carBundle = notif.extras == null ?
5476                    null : notif.extras.getBundle(EXTRA_CAR_EXTENDER);
5477            if (carBundle != null) {
5478                mLargeIcon = carBundle.getParcelable(EXTRA_LARGE_ICON);
5479                mColor = carBundle.getInt(EXTRA_COLOR, Notification.COLOR_DEFAULT);
5480
5481                Bundle b = carBundle.getBundle(EXTRA_CONVERSATION);
5482                mUnreadConversation = UnreadConversation.getUnreadConversationFromBundle(b);
5483            }
5484        }
5485
5486        /**
5487         * Apply car extensions to a notification that is being built. This is typically called by
5488         * the {@link Notification.Builder#extend(Notification.Extender)}
5489         * method of {@link Notification.Builder}.
5490         */
5491        @Override
5492        public Notification.Builder extend(Notification.Builder builder) {
5493            Bundle carExtensions = new Bundle();
5494
5495            if (mLargeIcon != null) {
5496                carExtensions.putParcelable(EXTRA_LARGE_ICON, mLargeIcon);
5497            }
5498            if (mColor != Notification.COLOR_DEFAULT) {
5499                carExtensions.putInt(EXTRA_COLOR, mColor);
5500            }
5501
5502            if (mUnreadConversation != null) {
5503                Bundle b = mUnreadConversation.getBundleForUnreadConversation();
5504                carExtensions.putBundle(EXTRA_CONVERSATION, b);
5505            }
5506
5507            builder.getExtras().putBundle(EXTRA_CAR_EXTENDER, carExtensions);
5508            return builder;
5509        }
5510
5511        /**
5512         * Sets the accent color to use when Android Auto presents the notification.
5513         *
5514         * Android Auto uses the color set with {@link Notification.Builder#setColor(int)}
5515         * to accent the displayed notification. However, not all colors are acceptable in an
5516         * automotive setting. This method can be used to override the color provided in the
5517         * notification in such a situation.
5518         */
5519        public CarExtender setColor(@ColorInt int color) {
5520            mColor = color;
5521            return this;
5522        }
5523
5524        /**
5525         * Gets the accent color.
5526         *
5527         * @see #setColor
5528         */
5529        @ColorInt
5530        public int getColor() {
5531            return mColor;
5532        }
5533
5534        /**
5535         * Sets the large icon of the car notification.
5536         *
5537         * If no large icon is set in the extender, Android Auto will display the icon
5538         * specified by {@link Notification.Builder#setLargeIcon(android.graphics.Bitmap)}
5539         *
5540         * @param largeIcon The large icon to use in the car notification.
5541         * @return This object for method chaining.
5542         */
5543        public CarExtender setLargeIcon(Bitmap largeIcon) {
5544            mLargeIcon = largeIcon;
5545            return this;
5546        }
5547
5548        /**
5549         * Gets the large icon used in this car notification, or null if no icon has been set.
5550         *
5551         * @return The large icon for the car notification.
5552         * @see CarExtender#setLargeIcon
5553         */
5554        public Bitmap getLargeIcon() {
5555            return mLargeIcon;
5556        }
5557
5558        /**
5559         * Sets the unread conversation in a message notification.
5560         *
5561         * @param unreadConversation The unread part of the conversation this notification conveys.
5562         * @return This object for method chaining.
5563         */
5564        public CarExtender setUnreadConversation(UnreadConversation unreadConversation) {
5565            mUnreadConversation = unreadConversation;
5566            return this;
5567        }
5568
5569        /**
5570         * Returns the unread conversation conveyed by this notification.
5571         * @see #setUnreadConversation(UnreadConversation)
5572         */
5573        public UnreadConversation getUnreadConversation() {
5574            return mUnreadConversation;
5575        }
5576
5577        /**
5578         * A class which holds the unread messages from a conversation.
5579         */
5580        public static class UnreadConversation {
5581            private static final String KEY_AUTHOR = "author";
5582            private static final String KEY_TEXT = "text";
5583            private static final String KEY_MESSAGES = "messages";
5584            private static final String KEY_REMOTE_INPUT = "remote_input";
5585            private static final String KEY_ON_REPLY = "on_reply";
5586            private static final String KEY_ON_READ = "on_read";
5587            private static final String KEY_PARTICIPANTS = "participants";
5588            private static final String KEY_TIMESTAMP = "timestamp";
5589
5590            private final String[] mMessages;
5591            private final RemoteInput mRemoteInput;
5592            private final PendingIntent mReplyPendingIntent;
5593            private final PendingIntent mReadPendingIntent;
5594            private final String[] mParticipants;
5595            private final long mLatestTimestamp;
5596
5597            UnreadConversation(String[] messages, RemoteInput remoteInput,
5598                    PendingIntent replyPendingIntent, PendingIntent readPendingIntent,
5599                    String[] participants, long latestTimestamp) {
5600                mMessages = messages;
5601                mRemoteInput = remoteInput;
5602                mReadPendingIntent = readPendingIntent;
5603                mReplyPendingIntent = replyPendingIntent;
5604                mParticipants = participants;
5605                mLatestTimestamp = latestTimestamp;
5606            }
5607
5608            /**
5609             * Gets the list of messages conveyed by this notification.
5610             */
5611            public String[] getMessages() {
5612                return mMessages;
5613            }
5614
5615            /**
5616             * Gets the remote input that will be used to convey the response to a message list, or
5617             * null if no such remote input exists.
5618             */
5619            public RemoteInput getRemoteInput() {
5620                return mRemoteInput;
5621            }
5622
5623            /**
5624             * Gets the pending intent that will be triggered when the user replies to this
5625             * notification.
5626             */
5627            public PendingIntent getReplyPendingIntent() {
5628                return mReplyPendingIntent;
5629            }
5630
5631            /**
5632             * Gets the pending intent that Android Auto will send after it reads aloud all messages
5633             * in this object's message list.
5634             */
5635            public PendingIntent getReadPendingIntent() {
5636                return mReadPendingIntent;
5637            }
5638
5639            /**
5640             * Gets the participants in the conversation.
5641             */
5642            public String[] getParticipants() {
5643                return mParticipants;
5644            }
5645
5646            /**
5647             * Gets the firs participant in the conversation.
5648             */
5649            public String getParticipant() {
5650                return mParticipants.length > 0 ? mParticipants[0] : null;
5651            }
5652
5653            /**
5654             * Gets the timestamp of the conversation.
5655             */
5656            public long getLatestTimestamp() {
5657                return mLatestTimestamp;
5658            }
5659
5660            Bundle getBundleForUnreadConversation() {
5661                Bundle b = new Bundle();
5662                String author = null;
5663                if (mParticipants != null && mParticipants.length > 1) {
5664                    author = mParticipants[0];
5665                }
5666                Parcelable[] messages = new Parcelable[mMessages.length];
5667                for (int i = 0; i < messages.length; i++) {
5668                    Bundle m = new Bundle();
5669                    m.putString(KEY_TEXT, mMessages[i]);
5670                    m.putString(KEY_AUTHOR, author);
5671                    messages[i] = m;
5672                }
5673                b.putParcelableArray(KEY_MESSAGES, messages);
5674                if (mRemoteInput != null) {
5675                    b.putParcelable(KEY_REMOTE_INPUT, mRemoteInput);
5676                }
5677                b.putParcelable(KEY_ON_REPLY, mReplyPendingIntent);
5678                b.putParcelable(KEY_ON_READ, mReadPendingIntent);
5679                b.putStringArray(KEY_PARTICIPANTS, mParticipants);
5680                b.putLong(KEY_TIMESTAMP, mLatestTimestamp);
5681                return b;
5682            }
5683
5684            static UnreadConversation getUnreadConversationFromBundle(Bundle b) {
5685                if (b == null) {
5686                    return null;
5687                }
5688                Parcelable[] parcelableMessages = b.getParcelableArray(KEY_MESSAGES);
5689                String[] messages = null;
5690                if (parcelableMessages != null) {
5691                    String[] tmp = new String[parcelableMessages.length];
5692                    boolean success = true;
5693                    for (int i = 0; i < tmp.length; i++) {
5694                        if (!(parcelableMessages[i] instanceof Bundle)) {
5695                            success = false;
5696                            break;
5697                        }
5698                        tmp[i] = ((Bundle) parcelableMessages[i]).getString(KEY_TEXT);
5699                        if (tmp[i] == null) {
5700                            success = false;
5701                            break;
5702                        }
5703                    }
5704                    if (success) {
5705                        messages = tmp;
5706                    } else {
5707                        return null;
5708                    }
5709                }
5710
5711                PendingIntent onRead = b.getParcelable(KEY_ON_READ);
5712                PendingIntent onReply = b.getParcelable(KEY_ON_REPLY);
5713
5714                RemoteInput remoteInput = b.getParcelable(KEY_REMOTE_INPUT);
5715
5716                String[] participants = b.getStringArray(KEY_PARTICIPANTS);
5717                if (participants == null || participants.length != 1) {
5718                    return null;
5719                }
5720
5721                return new UnreadConversation(messages,
5722                        remoteInput,
5723                        onReply,
5724                        onRead,
5725                        participants, b.getLong(KEY_TIMESTAMP));
5726            }
5727        };
5728
5729        /**
5730         * Builder class for {@link CarExtender.UnreadConversation} objects.
5731         */
5732        public static class Builder {
5733            private final List<String> mMessages = new ArrayList<String>();
5734            private final String mParticipant;
5735            private RemoteInput mRemoteInput;
5736            private PendingIntent mReadPendingIntent;
5737            private PendingIntent mReplyPendingIntent;
5738            private long mLatestTimestamp;
5739
5740            /**
5741             * Constructs a new builder for {@link CarExtender.UnreadConversation}.
5742             *
5743             * @param name The name of the other participant in the conversation.
5744             */
5745            public Builder(String name) {
5746                mParticipant = name;
5747            }
5748
5749            /**
5750             * Appends a new unread message to the list of messages for this conversation.
5751             *
5752             * The messages should be added from oldest to newest.
5753             *
5754             * @param message The text of the new unread message.
5755             * @return This object for method chaining.
5756             */
5757            public Builder addMessage(String message) {
5758                mMessages.add(message);
5759                return this;
5760            }
5761
5762            /**
5763             * Sets the pending intent and remote input which will convey the reply to this
5764             * notification.
5765             *
5766             * @param pendingIntent The pending intent which will be triggered on a reply.
5767             * @param remoteInput The remote input parcelable which will carry the reply.
5768             * @return This object for method chaining.
5769             *
5770             * @see CarExtender.UnreadConversation#getRemoteInput
5771             * @see CarExtender.UnreadConversation#getReplyPendingIntent
5772             */
5773            public Builder setReplyAction(
5774                    PendingIntent pendingIntent, RemoteInput remoteInput) {
5775                mRemoteInput = remoteInput;
5776                mReplyPendingIntent = pendingIntent;
5777
5778                return this;
5779            }
5780
5781            /**
5782             * Sets the pending intent that will be sent once the messages in this notification
5783             * are read.
5784             *
5785             * @param pendingIntent The pending intent to use.
5786             * @return This object for method chaining.
5787             */
5788            public Builder setReadPendingIntent(PendingIntent pendingIntent) {
5789                mReadPendingIntent = pendingIntent;
5790                return this;
5791            }
5792
5793            /**
5794             * Sets the timestamp of the most recent message in an unread conversation.
5795             *
5796             * If a messaging notification has been posted by your application and has not
5797             * yet been cancelled, posting a later notification with the same id and tag
5798             * but without a newer timestamp may result in Android Auto not displaying a
5799             * heads up notification for the later notification.
5800             *
5801             * @param timestamp The timestamp of the most recent message in the conversation.
5802             * @return This object for method chaining.
5803             */
5804            public Builder setLatestTimestamp(long timestamp) {
5805                mLatestTimestamp = timestamp;
5806                return this;
5807            }
5808
5809            /**
5810             * Builds a new unread conversation object.
5811             *
5812             * @return The new unread conversation object.
5813             */
5814            public UnreadConversation build() {
5815                String[] messages = mMessages.toArray(new String[mMessages.size()]);
5816                String[] participants = { mParticipant };
5817                return new UnreadConversation(messages, mRemoteInput, mReplyPendingIntent,
5818                        mReadPendingIntent, participants, mLatestTimestamp);
5819            }
5820        }
5821    }
5822
5823    /**
5824     * Get an array of Notification objects from a parcelable array bundle field.
5825     * Update the bundle to have a typed array so fetches in the future don't need
5826     * to do an array copy.
5827     */
5828    private static Notification[] getNotificationArrayFromBundle(Bundle bundle, String key) {
5829        Parcelable[] array = bundle.getParcelableArray(key);
5830        if (array instanceof Notification[] || array == null) {
5831            return (Notification[]) array;
5832        }
5833        Notification[] typedArray = Arrays.copyOf(array, array.length,
5834                Notification[].class);
5835        bundle.putParcelableArray(key, typedArray);
5836        return typedArray;
5837    }
5838
5839    private static class BuilderRemoteViews extends RemoteViews {
5840        public BuilderRemoteViews(Parcel parcel) {
5841            super(parcel);
5842        }
5843
5844        public BuilderRemoteViews(ApplicationInfo appInfo, int layoutId) {
5845            super(appInfo, layoutId);
5846        }
5847
5848        @Override
5849        public BuilderRemoteViews clone() {
5850            Parcel p = Parcel.obtain();
5851            writeToParcel(p, 0);
5852            p.setDataPosition(0);
5853            BuilderRemoteViews brv = new BuilderRemoteViews(p);
5854            p.recycle();
5855            return brv;
5856        }
5857    }
5858}
5859