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