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