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