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