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