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