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