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