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