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