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