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