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