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