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