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