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