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