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