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