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