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