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