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