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 android.annotation.Dimension.DP;
20 import static android.graphics.drawable.Icon.TYPE_URI;
21 import static android.graphics.drawable.Icon.TYPE_URI_ADAPTIVE_BITMAP;
22 
23 import static com.android.internal.util.ContrastColorUtil.satisfiesTextContrast;
24 
25 import android.annotation.ColorInt;
26 import android.annotation.DimenRes;
27 import android.annotation.Dimension;
28 import android.annotation.DrawableRes;
29 import android.annotation.IdRes;
30 import android.annotation.IntDef;
31 import android.annotation.NonNull;
32 import android.annotation.Nullable;
33 import android.annotation.RequiresPermission;
34 import android.annotation.SdkConstant;
35 import android.annotation.SdkConstant.SdkConstantType;
36 import android.annotation.SuppressLint;
37 import android.annotation.SystemApi;
38 import android.compat.annotation.UnsupportedAppUsage;
39 import android.content.Context;
40 import android.content.Intent;
41 import android.content.LocusId;
42 import android.content.pm.ApplicationInfo;
43 import android.content.pm.PackageManager;
44 import android.content.pm.PackageManager.NameNotFoundException;
45 import android.content.pm.ShortcutInfo;
46 import android.content.res.ColorStateList;
47 import android.content.res.Configuration;
48 import android.content.res.Resources;
49 import android.content.res.TypedArray;
50 import android.graphics.Bitmap;
51 import android.graphics.Canvas;
52 import android.graphics.Color;
53 import android.graphics.PorterDuff;
54 import android.graphics.drawable.Drawable;
55 import android.graphics.drawable.Icon;
56 import android.media.AudioAttributes;
57 import android.media.AudioManager;
58 import android.media.PlayerBase;
59 import android.media.session.MediaSession;
60 import android.net.Uri;
61 import android.os.BadParcelableException;
62 import android.os.Build;
63 import android.os.Bundle;
64 import android.os.IBinder;
65 import android.os.Parcel;
66 import android.os.Parcelable;
67 import android.os.SystemClock;
68 import android.os.SystemProperties;
69 import android.os.UserHandle;
70 import android.text.BidiFormatter;
71 import android.text.SpannableStringBuilder;
72 import android.text.Spanned;
73 import android.text.TextUtils;
74 import android.text.style.AbsoluteSizeSpan;
75 import android.text.style.CharacterStyle;
76 import android.text.style.ForegroundColorSpan;
77 import android.text.style.RelativeSizeSpan;
78 import android.text.style.TextAppearanceSpan;
79 import android.util.ArraySet;
80 import android.util.Log;
81 import android.util.Pair;
82 import android.util.SparseArray;
83 import android.util.proto.ProtoOutputStream;
84 import android.view.Gravity;
85 import android.view.NotificationHeaderView;
86 import android.view.View;
87 import android.view.ViewGroup;
88 import android.view.contentcapture.ContentCaptureContext;
89 import android.widget.ProgressBar;
90 import android.widget.RemoteViews;
91 
92 import com.android.internal.R;
93 import com.android.internal.annotations.VisibleForTesting;
94 import com.android.internal.util.ArrayUtils;
95 import com.android.internal.util.ContrastColorUtil;
96 
97 import java.lang.annotation.Retention;
98 import java.lang.annotation.RetentionPolicy;
99 import java.lang.reflect.Array;
100 import java.lang.reflect.Constructor;
101 import java.util.ArrayList;
102 import java.util.Collections;
103 import java.util.List;
104 import java.util.Objects;
105 import java.util.Set;
106 import java.util.function.Consumer;
107 
108 /**
109  * A class that represents how a persistent notification is to be presented to
110  * the user using the {@link android.app.NotificationManager}.
111  *
112  * <p>The {@link Notification.Builder Notification.Builder} has been added to make it
113  * easier to construct Notifications.</p>
114  *
115  * <div class="special reference">
116  * <h3>Developer Guides</h3>
117  * <p>For a guide to creating notifications, read the
118  * <a href="{@docRoot}guide/topics/ui/notifiers/notifications.html">Status Bar Notifications</a>
119  * developer guide.</p>
120  * </div>
121  */
122 public class Notification implements Parcelable
123 {
124     private static final String TAG = "Notification";
125 
126     /**
127      * An activity that provides a user interface for adjusting notification preferences for its
128      * containing application.
129      */
130     @SdkConstant(SdkConstantType.INTENT_CATEGORY)
131     public static final String INTENT_CATEGORY_NOTIFICATION_PREFERENCES
132             = "android.intent.category.NOTIFICATION_PREFERENCES";
133 
134     /**
135      * Optional extra for {@link #INTENT_CATEGORY_NOTIFICATION_PREFERENCES}. If provided, will
136      * contain a {@link NotificationChannel#getId() channel id} that can be used to narrow down
137      * what settings should be shown in the target app.
138      */
139     public static final String EXTRA_CHANNEL_ID = "android.intent.extra.CHANNEL_ID";
140 
141     /**
142      * Optional extra for {@link #INTENT_CATEGORY_NOTIFICATION_PREFERENCES}. If provided, will
143      * contain a {@link NotificationChannelGroup#getId() group id} that can be used to narrow down
144      * what settings should be shown in the target app.
145      */
146     public static final String EXTRA_CHANNEL_GROUP_ID = "android.intent.extra.CHANNEL_GROUP_ID";
147 
148     /**
149      * Optional extra for {@link #INTENT_CATEGORY_NOTIFICATION_PREFERENCES}. If provided, will
150      * contain the tag provided to {@link NotificationManager#notify(String, int, Notification)}
151      * that can be used to narrow down what settings should be shown in the target app.
152      */
153     public static final String EXTRA_NOTIFICATION_TAG = "android.intent.extra.NOTIFICATION_TAG";
154 
155     /**
156      * Optional extra for {@link #INTENT_CATEGORY_NOTIFICATION_PREFERENCES}. If provided, will
157      * contain the id provided to {@link NotificationManager#notify(String, int, Notification)}
158      * that can be used to narrow down what settings should be shown in the target app.
159      */
160     public static final String EXTRA_NOTIFICATION_ID = "android.intent.extra.NOTIFICATION_ID";
161 
162     /**
163      * Use all default values (where applicable).
164      */
165     public static final int DEFAULT_ALL = ~0;
166 
167     /**
168      * Use the default notification sound. This will ignore any given
169      * {@link #sound}.
170      *
171      * <p>
172      * A notification that is noisy is more likely to be presented as a heads-up notification.
173      * </p>
174      *
175      * @see #defaults
176      */
177 
178     public static final int DEFAULT_SOUND = 1;
179 
180     /**
181      * Use the default notification vibrate. This will ignore any given
182      * {@link #vibrate}. Using phone vibration requires the
183      * {@link android.Manifest.permission#VIBRATE VIBRATE} permission.
184      *
185      * <p>
186      * A notification that vibrates is more likely to be presented as a heads-up notification.
187      * </p>
188      *
189      * @see #defaults
190      */
191 
192     public static final int DEFAULT_VIBRATE = 2;
193 
194     /**
195      * Use the default notification lights. This will ignore the
196      * {@link #FLAG_SHOW_LIGHTS} bit, and {@link #ledARGB}, {@link #ledOffMS}, or
197      * {@link #ledOnMS}.
198      *
199      * @see #defaults
200      */
201 
202     public static final int DEFAULT_LIGHTS = 4;
203 
204     /**
205      * Maximum length of CharSequences accepted by Builder and friends.
206      *
207      * <p>
208      * Avoids spamming the system with overly large strings such as full e-mails.
209      */
210     private static final int MAX_CHARSEQUENCE_LENGTH = 5 * 1024;
211 
212     /**
213      * Maximum entries of reply text that are accepted by Builder and friends.
214      */
215     private static final int MAX_REPLY_HISTORY = 5;
216 
217     /**
218      * Maximum number of (generic) action buttons in a notification (contextual action buttons are
219      * handled separately).
220      * @hide
221      */
222     public static final int MAX_ACTION_BUTTONS = 3;
223 
224     /**
225      * If the notification contained an unsent draft for a RemoteInput when the user clicked on it,
226      * we're adding the draft as a String extra to the {@link #contentIntent} using this key.
227      *
228      * <p>Apps may use this extra to prepopulate text fields in the app, where the user usually
229      * sends messages.</p>
230      */
231     public static final String EXTRA_REMOTE_INPUT_DRAFT = "android.remoteInputDraft";
232 
233     /**
234      * A timestamp related to this notification, in milliseconds since the epoch.
235      *
236      * Default value: {@link System#currentTimeMillis() Now}.
237      *
238      * Choose a timestamp that will be most relevant to the user. For most finite events, this
239      * corresponds to the time the event happened (or will happen, in the case of events that have
240      * yet to occur but about which the user is being informed). Indefinite events should be
241      * timestamped according to when the activity began.
242      *
243      * Some examples:
244      *
245      * <ul>
246      *   <li>Notification of a new chat message should be stamped when the message was received.</li>
247      *   <li>Notification of an ongoing file download (with a progress bar, for example) should be stamped when the download started.</li>
248      *   <li>Notification of a completed file download should be stamped when the download finished.</li>
249      *   <li>Notification of an upcoming meeting should be stamped with the time the meeting will begin (that is, in the future).</li>
250      *   <li>Notification of an ongoing stopwatch (increasing timer) should be stamped with the watch's start time.
251      *   <li>Notification of an ongoing countdown timer should be stamped with the timer's end time.
252      * </ul>
253      *
254      * For apps targeting {@link android.os.Build.VERSION_CODES#N} and above, this time is not shown
255      * anymore by default and must be opted into by using
256      * {@link android.app.Notification.Builder#setShowWhen(boolean)}
257      */
258     public long when;
259 
260     /**
261      * The creation time of the notification
262      */
263     private long creationTime;
264 
265     /**
266      * The resource id of a drawable to use as the icon in the status bar.
267      *
268      * @deprecated Use {@link Builder#setSmallIcon(Icon)} instead.
269      */
270     @Deprecated
271     @DrawableRes
272     public int icon;
273 
274     /**
275      * If the icon in the status bar is to have more than one level, you can set this.  Otherwise,
276      * leave it at its default value of 0.
277      *
278      * @see android.widget.ImageView#setImageLevel
279      * @see android.graphics.drawable.Drawable#setLevel
280      */
281     public int iconLevel;
282 
283     /**
284      * The number of events that this notification represents. For example, in a new mail
285      * notification, this could be the number of unread messages.
286      *
287      * The system may or may not use this field to modify the appearance of the notification.
288      * Starting with {@link android.os.Build.VERSION_CODES#O}, the number may be displayed as a
289      * badge icon in Launchers that support badging.
290      */
291     public int number = 0;
292 
293     /**
294      * The intent to execute when the expanded status entry is clicked.  If
295      * this is an activity, it must include the
296      * {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK} flag, which requires
297      * that you take care of task management as described in the
298      * <a href="{@docRoot}guide/topics/fundamentals/tasks-and-back-stack.html">Tasks and Back
299      * Stack</a> document.  In particular, make sure to read the notification section
300      * <a href="{@docRoot}guide/topics/ui/notifiers/notifications.html#HandlingNotifications">Handling
301      * Notifications</a> for the correct ways to launch an application from a
302      * notification.
303      */
304     public PendingIntent contentIntent;
305 
306     /**
307      * The intent to execute when the notification is explicitly dismissed by the user, either with
308      * the "Clear All" button or by swiping it away individually.
309      *
310      * This probably shouldn't be launching an activity since several of those will be sent
311      * at the same time.
312      */
313     public PendingIntent deleteIntent;
314 
315     /**
316      * An intent to launch instead of posting the notification to the status bar.
317      *
318      * <p>
319      * The system UI may choose to display a heads-up notification, instead of
320      * launching this intent, while the user is using the device.
321      * </p>
322      *
323      * @see Notification.Builder#setFullScreenIntent
324      */
325     public PendingIntent fullScreenIntent;
326 
327     /**
328      * Text that summarizes this notification for accessibility services.
329      *
330      * As of the L release, this text is no longer shown on screen, but it is still useful to
331      * accessibility services (where it serves as an audible announcement of the notification's
332      * appearance).
333      *
334      * @see #tickerView
335      */
336     public CharSequence tickerText;
337 
338     /**
339      * Formerly, a view showing the {@link #tickerText}.
340      *
341      * No longer displayed in the status bar as of API 21.
342      */
343     @Deprecated
344     public RemoteViews tickerView;
345 
346     /**
347      * The view that will represent this notification in the notification list (which is pulled
348      * down from the status bar).
349      *
350      * As of N, this field may be null. The notification view is determined by the inputs
351      * to {@link Notification.Builder}; a custom RemoteViews can optionally be
352      * supplied with {@link Notification.Builder#setCustomContentView(RemoteViews)}.
353      */
354     @Deprecated
355     public RemoteViews contentView;
356 
357     /**
358      * A large-format version of {@link #contentView}, giving the Notification an
359      * opportunity to show more detail. The system UI may choose to show this
360      * instead of the normal content view at its discretion.
361      *
362      * As of N, this field may be null. The expanded notification view is determined by the
363      * inputs to {@link Notification.Builder}; a custom RemoteViews can optionally be
364      * supplied with {@link Notification.Builder#setCustomBigContentView(RemoteViews)}.
365      */
366     @Deprecated
367     public RemoteViews bigContentView;
368 
369 
370     /**
371      * A medium-format version of {@link #contentView}, providing the Notification an
372      * opportunity to add action buttons to contentView. At its discretion, the system UI may
373      * choose to show this as a heads-up notification, which will pop up so the user can see
374      * it without leaving their current activity.
375      *
376      * As of N, this field may be null. The heads-up notification view is determined by the
377      * inputs to {@link Notification.Builder}; a custom RemoteViews can optionally be
378      * supplied with {@link Notification.Builder#setCustomHeadsUpContentView(RemoteViews)}.
379      */
380     @Deprecated
381     public RemoteViews headsUpContentView;
382 
383     private boolean mUsesStandardHeader;
384 
385     private static final ArraySet<Integer> STANDARD_LAYOUTS = new ArraySet<>();
386     static {
387         STANDARD_LAYOUTS.add(R.layout.notification_template_material_base);
388         STANDARD_LAYOUTS.add(R.layout.notification_template_material_big_base);
389         STANDARD_LAYOUTS.add(R.layout.notification_template_material_big_picture);
390         STANDARD_LAYOUTS.add(R.layout.notification_template_material_big_text);
391         STANDARD_LAYOUTS.add(R.layout.notification_template_material_inbox);
392         STANDARD_LAYOUTS.add(R.layout.notification_template_material_messaging);
393         STANDARD_LAYOUTS.add(R.layout.notification_template_material_conversation);
394         STANDARD_LAYOUTS.add(R.layout.notification_template_material_media);
395         STANDARD_LAYOUTS.add(R.layout.notification_template_material_big_media);
396         STANDARD_LAYOUTS.add(R.layout.notification_template_header);
397     }
398 
399     /**
400      * A large bitmap to be shown in the notification content area.
401      *
402      * @deprecated Use {@link Builder#setLargeIcon(Icon)} instead.
403      */
404     @Deprecated
405     public Bitmap largeIcon;
406 
407     /**
408      * The sound to play.
409      *
410      * <p>
411      * A notification that is noisy is more likely to be presented as a heads-up notification.
412      * </p>
413      *
414      * <p>
415      * To play the default notification sound, see {@link #defaults}.
416      * </p>
417      * @deprecated use {@link NotificationChannel#getSound()}.
418      */
419     @Deprecated
420     public Uri sound;
421 
422     /**
423      * Use this constant as the value for audioStreamType to request that
424      * the default stream type for notifications be used.  Currently the
425      * default stream type is {@link AudioManager#STREAM_NOTIFICATION}.
426      *
427      * @deprecated Use {@link NotificationChannel#getAudioAttributes()} instead.
428      */
429     @Deprecated
430     public static final int STREAM_DEFAULT = -1;
431 
432     /**
433      * The audio stream type to use when playing the sound.
434      * Should be one of the STREAM_ constants from
435      * {@link android.media.AudioManager}.
436      *
437      * @deprecated Use {@link #audioAttributes} instead.
438      */
439     @Deprecated
440     public int audioStreamType = STREAM_DEFAULT;
441 
442     /**
443      * The default value of {@link #audioAttributes}.
444      */
445     public static final AudioAttributes AUDIO_ATTRIBUTES_DEFAULT = new AudioAttributes.Builder()
446             .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
447             .setUsage(AudioAttributes.USAGE_NOTIFICATION)
448             .build();
449 
450     /**
451      * The {@link AudioAttributes audio attributes} to use when playing the sound.
452      *
453      * @deprecated use {@link NotificationChannel#getAudioAttributes()} instead.
454      */
455     @Deprecated
456     public AudioAttributes audioAttributes = AUDIO_ATTRIBUTES_DEFAULT;
457 
458     /**
459      * The pattern with which to vibrate.
460      *
461      * <p>
462      * To vibrate the default pattern, see {@link #defaults}.
463      * </p>
464      *
465      * @see android.os.Vibrator#vibrate(long[],int)
466      * @deprecated use {@link NotificationChannel#getVibrationPattern()}.
467      */
468     @Deprecated
469     public long[] vibrate;
470 
471     /**
472      * The color of the led.  The hardware will do its best approximation.
473      *
474      * @see #FLAG_SHOW_LIGHTS
475      * @see #flags
476      * @deprecated use {@link NotificationChannel#shouldShowLights()}.
477      */
478     @ColorInt
479     @Deprecated
480     public int ledARGB;
481 
482     /**
483      * The number of milliseconds for the LED to be on while it's flashing.
484      * The hardware will do its best approximation.
485      *
486      * @see #FLAG_SHOW_LIGHTS
487      * @see #flags
488      * @deprecated use {@link NotificationChannel#shouldShowLights()}.
489      */
490     @Deprecated
491     public int ledOnMS;
492 
493     /**
494      * The number of milliseconds for the LED to be off while it's flashing.
495      * The hardware will do its best approximation.
496      *
497      * @see #FLAG_SHOW_LIGHTS
498      * @see #flags
499      *
500      * @deprecated use {@link NotificationChannel#shouldShowLights()}.
501      */
502     @Deprecated
503     public int ledOffMS;
504 
505     /**
506      * Specifies which values should be taken from the defaults.
507      * <p>
508      * To set, OR the desired from {@link #DEFAULT_SOUND},
509      * {@link #DEFAULT_VIBRATE}, {@link #DEFAULT_LIGHTS}. For all default
510      * values, use {@link #DEFAULT_ALL}.
511      * </p>
512      *
513      * @deprecated use {@link NotificationChannel#getSound()} and
514      * {@link NotificationChannel#shouldShowLights()} and
515      * {@link NotificationChannel#shouldVibrate()}.
516      */
517     @Deprecated
518     public int defaults;
519 
520     /**
521      * Bit to be bitwise-ored into the {@link #flags} field that should be
522      * set if you want the LED on for this notification.
523      * <ul>
524      * <li>To turn the LED off, pass 0 in the alpha channel for colorARGB
525      *      or 0 for both ledOnMS and ledOffMS.</li>
526      * <li>To turn the LED on, pass 1 for ledOnMS and 0 for ledOffMS.</li>
527      * <li>To flash the LED, pass the number of milliseconds that it should
528      *      be on and off to ledOnMS and ledOffMS.</li>
529      * </ul>
530      * <p>
531      * Since hardware varies, you are not guaranteed that any of the values
532      * you pass are honored exactly.  Use the system defaults if possible
533      * because they will be set to values that work on any given hardware.
534      * <p>
535      * The alpha channel must be set for forward compatibility.
536      *
537      * @deprecated use {@link NotificationChannel#shouldShowLights()}.
538      */
539     @Deprecated
540     public static final int FLAG_SHOW_LIGHTS        = 0x00000001;
541 
542     /**
543      * Bit to be bitwise-ored into the {@link #flags} field that should be
544      * set if this notification is in reference to something that is ongoing,
545      * like a phone call.  It should not be set if this notification is in
546      * reference to something that happened at a particular point in time,
547      * like a missed phone call.
548      */
549     public static final int FLAG_ONGOING_EVENT      = 0x00000002;
550 
551     /**
552      * Bit to be bitwise-ored into the {@link #flags} field that if set,
553      * the audio will be repeated until the notification is
554      * cancelled or the notification window is opened.
555      */
556     public static final int FLAG_INSISTENT          = 0x00000004;
557 
558     /**
559      * Bit to be bitwise-ored into the {@link #flags} field that should be
560      * set if you would only like the sound, vibrate and ticker to be played
561      * if the notification was not already showing.
562      */
563     public static final int FLAG_ONLY_ALERT_ONCE    = 0x00000008;
564 
565     /**
566      * Bit to be bitwise-ored into the {@link #flags} field that should be
567      * set if the notification should be canceled when it is clicked by the
568      * user.
569      */
570     public static final int FLAG_AUTO_CANCEL        = 0x00000010;
571 
572     /**
573      * Bit to be bitwise-ored into the {@link #flags} field that should be
574      * set if the notification should not be canceled when the user clicks
575      * the Clear all button.
576      */
577     public static final int FLAG_NO_CLEAR           = 0x00000020;
578 
579     /**
580      * Bit to be bitwise-ored into the {@link #flags} field that should be
581      * set if this notification represents a currently running service.  This
582      * will normally be set for you by {@link Service#startForeground}.
583      */
584     public static final int FLAG_FOREGROUND_SERVICE = 0x00000040;
585 
586     /**
587      * Obsolete flag indicating high-priority notifications; use the priority field instead.
588      *
589      * @deprecated Use {@link #priority} with a positive value.
590      */
591     @Deprecated
592     public static final int FLAG_HIGH_PRIORITY      = 0x00000080;
593 
594     /**
595      * Bit to be bitswise-ored into the {@link #flags} field that should be
596      * set if this notification is relevant to the current device only
597      * and it is not recommended that it bridge to other devices.
598      */
599     public static final int FLAG_LOCAL_ONLY         = 0x00000100;
600 
601     /**
602      * Bit to be bitswise-ored into the {@link #flags} field that should be
603      * set if this notification is the group summary for a group of notifications.
604      * Grouped notifications may display in a cluster or stack on devices which
605      * support such rendering. Requires a group key also be set using {@link Builder#setGroup}.
606      */
607     public static final int FLAG_GROUP_SUMMARY      = 0x00000200;
608 
609     /**
610      * Bit to be bitswise-ored into the {@link #flags} field that should be
611      * set if this notification is the group summary for an auto-group of notifications.
612      *
613      * @hide
614      */
615     @SystemApi
616     public static final int FLAG_AUTOGROUP_SUMMARY  = 0x00000400;
617 
618     /**
619      * @hide
620      */
621     public static final int FLAG_CAN_COLORIZE = 0x00000800;
622 
623     /**
624      * Bit to be bitswised-ored into the {@link #flags} field that should be
625      * set by the system if this notification is showing as a bubble.
626      *
627      * Applications cannot set this flag directly; they should instead call
628      * {@link Notification.Builder#setBubbleMetadata(BubbleMetadata)} to
629      * request that a notification be displayed as a bubble, and then check
630      * this flag to see whether that request was honored by the system.
631      */
632     public static final int FLAG_BUBBLE = 0x00001000;
633 
634     /** @hide */
635     @IntDef({FLAG_SHOW_LIGHTS, FLAG_ONGOING_EVENT, FLAG_INSISTENT, FLAG_ONLY_ALERT_ONCE,
636             FLAG_AUTO_CANCEL, FLAG_NO_CLEAR, FLAG_FOREGROUND_SERVICE, FLAG_HIGH_PRIORITY,
637             FLAG_LOCAL_ONLY, FLAG_GROUP_SUMMARY, FLAG_AUTOGROUP_SUMMARY, FLAG_BUBBLE})
638     @Retention(RetentionPolicy.SOURCE)
639     public @interface NotificationFlags{};
640 
641     public int flags;
642 
643     /** @hide */
644     @IntDef(prefix = { "PRIORITY_" }, value = {
645             PRIORITY_DEFAULT,
646             PRIORITY_LOW,
647             PRIORITY_MIN,
648             PRIORITY_HIGH,
649             PRIORITY_MAX
650     })
651     @Retention(RetentionPolicy.SOURCE)
652     public @interface Priority {}
653 
654     /**
655      * Default notification {@link #priority}. If your application does not prioritize its own
656      * notifications, use this value for all notifications.
657      *
658      * @deprecated use {@link NotificationManager#IMPORTANCE_DEFAULT} instead.
659      */
660     @Deprecated
661     public static final int PRIORITY_DEFAULT = 0;
662 
663     /**
664      * Lower {@link #priority}, for items that are less important. The UI may choose to show these
665      * items smaller, or at a different position in the list, compared with your app's
666      * {@link #PRIORITY_DEFAULT} items.
667      *
668      * @deprecated use {@link NotificationManager#IMPORTANCE_LOW} instead.
669      */
670     @Deprecated
671     public static final int PRIORITY_LOW = -1;
672 
673     /**
674      * Lowest {@link #priority}; these items might not be shown to the user except under special
675      * circumstances, such as detailed notification logs.
676      *
677      * @deprecated use {@link NotificationManager#IMPORTANCE_MIN} instead.
678      */
679     @Deprecated
680     public static final int PRIORITY_MIN = -2;
681 
682     /**
683      * Higher {@link #priority}, for more important notifications or alerts. The UI may choose to
684      * show these items larger, or at a different position in notification lists, compared with
685      * your app's {@link #PRIORITY_DEFAULT} items.
686      *
687      * @deprecated use {@link NotificationManager#IMPORTANCE_HIGH} instead.
688      */
689     @Deprecated
690     public static final int PRIORITY_HIGH = 1;
691 
692     /**
693      * Highest {@link #priority}, for your application's most important items that require the
694      * user's prompt attention or input.
695      *
696      * @deprecated use {@link NotificationManager#IMPORTANCE_HIGH} instead.
697      */
698     @Deprecated
699     public static final int PRIORITY_MAX = 2;
700 
701     /**
702      * Relative priority for this notification.
703      *
704      * Priority is an indication of how much of the user's valuable attention should be consumed by
705      * this notification. Low-priority notifications may be hidden from the user in certain
706      * situations, while the user might be interrupted for a higher-priority notification. The
707      * system will make a determination about how to interpret this priority when presenting
708      * the notification.
709      *
710      * <p>
711      * A notification that is at least {@link #PRIORITY_HIGH} is more likely to be presented
712      * as a heads-up notification.
713      * </p>
714      *
715      * @deprecated use {@link NotificationChannel#getImportance()} instead.
716      */
717     @Priority
718     @Deprecated
719     public int priority;
720 
721     /**
722      * Accent color (an ARGB integer like the constants in {@link android.graphics.Color})
723      * to be applied by the standard Style templates when presenting this notification.
724      *
725      * The current template design constructs a colorful header image by overlaying the
726      * {@link #icon} image (stenciled in white) atop a field of this color. Alpha components are
727      * ignored.
728      */
729     @ColorInt
730     public int color = COLOR_DEFAULT;
731 
732     /**
733      * Special value of {@link #color} telling the system not to decorate this notification with
734      * any special color but instead use default colors when presenting this notification.
735      */
736     @ColorInt
737     public static final int COLOR_DEFAULT = 0; // AKA Color.TRANSPARENT
738 
739     /**
740      * Special value of {@link #color} used as a place holder for an invalid color.
741      * @hide
742      */
743     @ColorInt
744     public static final int COLOR_INVALID = 1;
745 
746     /**
747      * Sphere of visibility of this notification, which affects how and when the SystemUI reveals
748      * the notification's presence and contents in untrusted situations (namely, on the secure
749      * lockscreen).
750      *
751      * The default level, {@link #VISIBILITY_PRIVATE}, behaves exactly as notifications have always
752      * done on Android: The notification's {@link #icon} and {@link #tickerText} (if available) are
753      * shown in all situations, but the contents are only available if the device is unlocked for
754      * the appropriate user.
755      *
756      * A more permissive policy can be expressed by {@link #VISIBILITY_PUBLIC}; such a notification
757      * can be read even in an "insecure" context (that is, above a secure lockscreen).
758      * To modify the public version of this notification—for example, to redact some portions—see
759      * {@link Builder#setPublicVersion(Notification)}.
760      *
761      * Finally, a notification can be made {@link #VISIBILITY_SECRET}, which will suppress its icon
762      * and ticker until the user has bypassed the lockscreen.
763      */
764     public @Visibility int visibility;
765 
766     /** @hide */
767     @IntDef(prefix = { "VISIBILITY_" }, value = {
768             VISIBILITY_PUBLIC,
769             VISIBILITY_PRIVATE,
770             VISIBILITY_SECRET,
771     })
772     @Retention(RetentionPolicy.SOURCE)
773     public @interface Visibility {}
774 
775     /**
776      * Notification visibility: Show this notification in its entirety on all lockscreens.
777      *
778      * {@see #visibility}
779      */
780     public static final int VISIBILITY_PUBLIC = 1;
781 
782     /**
783      * Notification visibility: Show this notification on all lockscreens, but conceal sensitive or
784      * private information on secure lockscreens.
785      *
786      * {@see #visibility}
787      */
788     public static final int VISIBILITY_PRIVATE = 0;
789 
790     /**
791      * Notification visibility: Do not reveal any part of this notification on a secure lockscreen.
792      *
793      * {@see #visibility}
794      */
795     public static final int VISIBILITY_SECRET = -1;
796 
797     /**
798      * Notification category: incoming call (voice or video) or similar synchronous communication request.
799      */
800     public static final String CATEGORY_CALL = "call";
801 
802     /**
803      * Notification category: map turn-by-turn navigation.
804      */
805     public static final String CATEGORY_NAVIGATION = "navigation";
806 
807     /**
808      * Notification category: incoming direct message (SMS, instant message, etc.).
809      */
810     public static final String CATEGORY_MESSAGE = "msg";
811 
812     /**
813      * Notification category: asynchronous bulk message (email).
814      */
815     public static final String CATEGORY_EMAIL = "email";
816 
817     /**
818      * Notification category: calendar event.
819      */
820     public static final String CATEGORY_EVENT = "event";
821 
822     /**
823      * Notification category: promotion or advertisement.
824      */
825     public static final String CATEGORY_PROMO = "promo";
826 
827     /**
828      * Notification category: alarm or timer.
829      */
830     public static final String CATEGORY_ALARM = "alarm";
831 
832     /**
833      * Notification category: progress of a long-running background operation.
834      */
835     public static final String CATEGORY_PROGRESS = "progress";
836 
837     /**
838      * Notification category: social network or sharing update.
839      */
840     public static final String CATEGORY_SOCIAL = "social";
841 
842     /**
843      * Notification category: error in background operation or authentication status.
844      */
845     public static final String CATEGORY_ERROR = "err";
846 
847     /**
848      * Notification category: media transport control for playback.
849      */
850     public static final String CATEGORY_TRANSPORT = "transport";
851 
852     /**
853      * Notification category: system or device status update.  Reserved for system use.
854      */
855     public static final String CATEGORY_SYSTEM = "sys";
856 
857     /**
858      * Notification category: indication of running background service.
859      */
860     public static final String CATEGORY_SERVICE = "service";
861 
862     /**
863      * Notification category: a specific, timely recommendation for a single thing.
864      * For example, a news app might want to recommend a news story it believes the user will
865      * want to read next.
866      */
867     public static final String CATEGORY_RECOMMENDATION = "recommendation";
868 
869     /**
870      * Notification category: ongoing information about device or contextual status.
871      */
872     public static final String CATEGORY_STATUS = "status";
873 
874     /**
875      * Notification category: user-scheduled reminder.
876      */
877     public static final String CATEGORY_REMINDER = "reminder";
878 
879     /**
880      * Notification category: extreme car emergencies.
881      * @hide
882      */
883     @SystemApi
884     public static final String CATEGORY_CAR_EMERGENCY = "car_emergency";
885 
886     /**
887      * Notification category: car warnings.
888      * @hide
889      */
890     @SystemApi
891     public static final String CATEGORY_CAR_WARNING = "car_warning";
892 
893     /**
894      * Notification category: general car system information.
895      * @hide
896      */
897     @SystemApi
898     public static final String CATEGORY_CAR_INFORMATION = "car_information";
899 
900     /**
901      * One of the predefined notification categories (see the <code>CATEGORY_*</code> constants)
902      * that best describes this Notification.  May be used by the system for ranking and filtering.
903      */
904     public String category;
905 
906     @UnsupportedAppUsage
907     private String mGroupKey;
908 
909     /**
910      * Get the key used to group this notification into a cluster or stack
911      * with other notifications on devices which support such rendering.
912      */
getGroup()913     public String getGroup() {
914         return mGroupKey;
915     }
916 
917     private String mSortKey;
918 
919     /**
920      * Get a sort key that orders this notification among other notifications from the
921      * same package. This can be useful if an external sort was already applied and an app
922      * would like to preserve this. Notifications will be sorted lexicographically using this
923      * value, although providing different priorities in addition to providing sort key may
924      * cause this value to be ignored.
925      *
926      * <p>This sort key can also be used to order members of a notification group. See
927      * {@link Builder#setGroup}.
928      *
929      * @see String#compareTo(String)
930      */
getSortKey()931     public String getSortKey() {
932         return mSortKey;
933     }
934 
935     /**
936      * Additional semantic data to be carried around with this Notification.
937      * <p>
938      * The extras keys defined here are intended to capture the original inputs to {@link Builder}
939      * APIs, and are intended to be used by
940      * {@link android.service.notification.NotificationListenerService} implementations to extract
941      * detailed information from notification objects.
942      */
943     public Bundle extras = new Bundle();
944 
945     /**
946      * All pending intents in the notification as the system needs to be able to access them but
947      * touching the extras bundle in the system process is not safe because the bundle may contain
948      * custom parcelable objects.
949      *
950      * @hide
951      */
952     @UnsupportedAppUsage
953     public ArraySet<PendingIntent> allPendingIntents;
954 
955     /**
956      * Token identifying the notification that is applying doze/bgcheck whitelisting to the
957      * pending intents inside of it, so only those will get the behavior.
958      *
959      * @hide
960      */
961     private IBinder mWhitelistToken;
962 
963     /**
964      * Must be set by a process to start associating tokens with Notification objects
965      * coming in to it.  This is set by NotificationManagerService.
966      *
967      * @hide
968      */
969     static public IBinder processWhitelistToken;
970 
971     /**
972      * {@link #extras} key: this is the title of the notification,
973      * as supplied to {@link Builder#setContentTitle(CharSequence)}.
974      */
975     public static final String EXTRA_TITLE = "android.title";
976 
977     /**
978      * {@link #extras} key: this is the title of the notification when shown in expanded form,
979      * e.g. as supplied to {@link BigTextStyle#setBigContentTitle(CharSequence)}.
980      */
981     public static final String EXTRA_TITLE_BIG = EXTRA_TITLE + ".big";
982 
983     /**
984      * {@link #extras} key: this is the main text payload, as supplied to
985      * {@link Builder#setContentText(CharSequence)}.
986      */
987     public static final String EXTRA_TEXT = "android.text";
988 
989     /**
990      * {@link #extras} key: this is a third line of text, as supplied to
991      * {@link Builder#setSubText(CharSequence)}.
992      */
993     public static final String EXTRA_SUB_TEXT = "android.subText";
994 
995     /**
996      * {@link #extras} key: this is the remote input history, as supplied to
997      * {@link Builder#setRemoteInputHistory(CharSequence[])}.
998      *
999      * Apps can fill this through {@link Builder#setRemoteInputHistory(CharSequence[])}
1000      * with the most recent inputs that have been sent through a {@link RemoteInput} of this
1001      * Notification and are expected to clear it once the it is no longer relevant (e.g. for chat
1002      * notifications once the other party has responded).
1003      *
1004      * The extra with this key is of type CharSequence[] and contains the most recent entry at
1005      * the 0 index, the second most recent at the 1 index, etc.
1006      *
1007      * @see Builder#setRemoteInputHistory(CharSequence[])
1008      */
1009     public static final String EXTRA_REMOTE_INPUT_HISTORY = "android.remoteInputHistory";
1010 
1011 
1012     /**
1013      * {@link #extras} key: this is a remote input history which can include media messages
1014      * in addition to text, as supplied to
1015      * {@link Builder#setRemoteInputHistory(RemoteInputHistoryItem[])} or
1016      * {@link Builder#setRemoteInputHistory(CharSequence[])}.
1017      *
1018      * SystemUI can populate this through
1019      * {@link Builder#setRemoteInputHistory(RemoteInputHistoryItem[])} with the most recent inputs
1020      * that have been sent through a {@link RemoteInput} of this Notification. These items can
1021      * represent either media content (specified by a URI and a MIME type) or a text message
1022      * (described by a CharSequence).
1023      *
1024      * To maintain compatibility, this can also be set by apps with
1025      * {@link Builder#setRemoteInputHistory(CharSequence[])}, which will create a
1026      * {@link RemoteInputHistoryItem} for each of the provided text-only messages.
1027      *
1028      * The extra with this key is of type {@link RemoteInputHistoryItem[]} and contains the most
1029      * recent entry at the 0 index, the second most recent at the 1 index, etc.
1030      *
1031      * @see Builder#setRemoteInputHistory(RemoteInputHistoryItem[])
1032      * @hide
1033      */
1034     public static final String EXTRA_REMOTE_INPUT_HISTORY_ITEMS = "android.remoteInputHistoryItems";
1035 
1036     /**
1037      * {@link #extras} key: boolean as supplied to
1038      * {@link Builder#setShowRemoteInputSpinner(boolean)}.
1039      *
1040      * If set to true, then the view displaying the remote input history from
1041      * {@link Builder#setRemoteInputHistory(CharSequence[])} will have a progress spinner.
1042      *
1043      * @see Builder#setShowRemoteInputSpinner(boolean)
1044      * @hide
1045      */
1046     public static final String EXTRA_SHOW_REMOTE_INPUT_SPINNER = "android.remoteInputSpinner";
1047 
1048     /**
1049      * {@link #extras} key: boolean as supplied to
1050      * {@link Builder#setHideSmartReplies(boolean)}.
1051      *
1052      * If set to true, then any smart reply buttons will be hidden.
1053      *
1054      * @see Builder#setHideSmartReplies(boolean)
1055      * @hide
1056      */
1057     public static final String EXTRA_HIDE_SMART_REPLIES = "android.hideSmartReplies";
1058 
1059     /**
1060      * {@link #extras} key: this is a small piece of additional text as supplied to
1061      * {@link Builder#setContentInfo(CharSequence)}.
1062      */
1063     public static final String EXTRA_INFO_TEXT = "android.infoText";
1064 
1065     /**
1066      * {@link #extras} key: this is a line of summary information intended to be shown
1067      * alongside expanded notifications, as supplied to (e.g.)
1068      * {@link BigTextStyle#setSummaryText(CharSequence)}.
1069      */
1070     public static final String EXTRA_SUMMARY_TEXT = "android.summaryText";
1071 
1072     /**
1073      * {@link #extras} key: this is the longer text shown in the big form of a
1074      * {@link BigTextStyle} notification, as supplied to
1075      * {@link BigTextStyle#bigText(CharSequence)}.
1076      */
1077     public static final String EXTRA_BIG_TEXT = "android.bigText";
1078 
1079     /**
1080      * {@link #extras} key: this is the resource ID of the notification's main small icon, as
1081      * supplied to {@link Builder#setSmallIcon(int)}.
1082      *
1083      * @deprecated Use {@link #getSmallIcon()}, which supports a wider variety of icon sources.
1084      */
1085     @Deprecated
1086     public static final String EXTRA_SMALL_ICON = "android.icon";
1087 
1088     /**
1089      * {@link #extras} key: this is a bitmap to be used instead of the small icon when showing the
1090      * notification payload, as
1091      * supplied to {@link Builder#setLargeIcon(android.graphics.Bitmap)}.
1092      *
1093      * @deprecated Use {@link #getLargeIcon()}, which supports a wider variety of icon sources.
1094      */
1095     @Deprecated
1096     public static final String EXTRA_LARGE_ICON = "android.largeIcon";
1097 
1098     /**
1099      * {@link #extras} key: this is a bitmap to be used instead of the one from
1100      * {@link Builder#setLargeIcon(android.graphics.Bitmap)} when the notification is
1101      * shown in its expanded form, as supplied to
1102      * {@link BigPictureStyle#bigLargeIcon(android.graphics.Bitmap)}.
1103      */
1104     public static final String EXTRA_LARGE_ICON_BIG = EXTRA_LARGE_ICON + ".big";
1105 
1106     /**
1107      * {@link #extras} key: this is the progress value supplied to
1108      * {@link Builder#setProgress(int, int, boolean)}.
1109      */
1110     public static final String EXTRA_PROGRESS = "android.progress";
1111 
1112     /**
1113      * {@link #extras} key: this is the maximum value supplied to
1114      * {@link Builder#setProgress(int, int, boolean)}.
1115      */
1116     public static final String EXTRA_PROGRESS_MAX = "android.progressMax";
1117 
1118     /**
1119      * {@link #extras} key: whether the progress bar is indeterminate, supplied to
1120      * {@link Builder#setProgress(int, int, boolean)}.
1121      */
1122     public static final String EXTRA_PROGRESS_INDETERMINATE = "android.progressIndeterminate";
1123 
1124     /**
1125      * {@link #extras} key: whether {@link #when} should be shown as a count-up timer (specifically
1126      * a {@link android.widget.Chronometer}) instead of a timestamp, as supplied to
1127      * {@link Builder#setUsesChronometer(boolean)}.
1128      */
1129     public static final String EXTRA_SHOW_CHRONOMETER = "android.showChronometer";
1130 
1131     /**
1132      * {@link #extras} key: whether the chronometer set on the notification should count down
1133      * instead of counting up. Is only relevant if key {@link #EXTRA_SHOW_CHRONOMETER} is present.
1134      * This extra is a boolean. The default is false.
1135      */
1136     public static final String EXTRA_CHRONOMETER_COUNT_DOWN = "android.chronometerCountDown";
1137 
1138     /**
1139      * {@link #extras} key: whether {@link #when} should be shown,
1140      * as supplied to {@link Builder#setShowWhen(boolean)}.
1141      */
1142     public static final String EXTRA_SHOW_WHEN = "android.showWhen";
1143 
1144     /**
1145      * {@link #extras} key: this is a bitmap to be shown in {@link BigPictureStyle} expanded
1146      * notifications, supplied to {@link BigPictureStyle#bigPicture(android.graphics.Bitmap)}.
1147      */
1148     public static final String EXTRA_PICTURE = "android.picture";
1149 
1150     /**
1151      * {@link #extras} key: An array of CharSequences to show in {@link InboxStyle} expanded
1152      * notifications, each of which was supplied to {@link InboxStyle#addLine(CharSequence)}.
1153      */
1154     public static final String EXTRA_TEXT_LINES = "android.textLines";
1155 
1156     /**
1157      * {@link #extras} key: A string representing the name of the specific
1158      * {@link android.app.Notification.Style} used to create this notification.
1159      */
1160     public static final String EXTRA_TEMPLATE = "android.template";
1161 
1162     /**
1163      * {@link #extras} key: A String array containing the people that this notification relates to,
1164      * each of which was supplied to {@link Builder#addPerson(String)}.
1165      *
1166      * @deprecated the actual objects are now in {@link #EXTRA_PEOPLE_LIST}
1167      */
1168     public static final String EXTRA_PEOPLE = "android.people";
1169 
1170     /**
1171      * {@link #extras} key: An arrayList of {@link Person} objects containing the people that
1172      * this notification relates to.
1173      */
1174     public static final String EXTRA_PEOPLE_LIST = "android.people.list";
1175 
1176     /**
1177      * Allow certain system-generated notifications to appear before the device is provisioned.
1178      * Only available to notifications coming from the android package.
1179      * @hide
1180      */
1181     @SystemApi
1182     @RequiresPermission(android.Manifest.permission.NOTIFICATION_DURING_SETUP)
1183     public static final String EXTRA_ALLOW_DURING_SETUP = "android.allowDuringSetup";
1184 
1185     /**
1186      * {@link #extras} key:
1187      * flat {@link String} representation of a {@link android.content.ContentUris content URI}
1188      * pointing to an image that can be displayed in the background when the notification is
1189      * selected. Used on television platforms. The URI must point to an image stream suitable for
1190      * passing into {@link android.graphics.BitmapFactory#decodeStream(java.io.InputStream)
1191      * BitmapFactory.decodeStream}; all other content types will be ignored.
1192      */
1193     public static final String EXTRA_BACKGROUND_IMAGE_URI = "android.backgroundImageUri";
1194 
1195     /**
1196      * {@link #extras} key: A
1197      * {@link android.media.session.MediaSession.Token} associated with a
1198      * {@link android.app.Notification.MediaStyle} notification.
1199      */
1200     public static final String EXTRA_MEDIA_SESSION = "android.mediaSession";
1201 
1202     /**
1203      * {@link #extras} key: the indices of actions to be shown in the compact view,
1204      * as supplied to (e.g.) {@link MediaStyle#setShowActionsInCompactView(int...)}.
1205      */
1206     public static final String EXTRA_COMPACT_ACTIONS = "android.compactActions";
1207 
1208     /**
1209      * {@link #extras} key: the username to be displayed for all messages sent by the user including
1210      * direct replies
1211      * {@link android.app.Notification.MessagingStyle} notification. This extra is a
1212      * {@link CharSequence}
1213      *
1214      * @deprecated use {@link #EXTRA_MESSAGING_PERSON}
1215      */
1216     public static final String EXTRA_SELF_DISPLAY_NAME = "android.selfDisplayName";
1217 
1218     /**
1219      * {@link #extras} key: the person to be displayed for all messages sent by the user including
1220      * direct replies
1221      * {@link android.app.Notification.MessagingStyle} notification. This extra is a
1222      * {@link Person}
1223      */
1224     public static final String EXTRA_MESSAGING_PERSON = "android.messagingUser";
1225 
1226     /**
1227      * {@link #extras} key: a {@link CharSequence} to be displayed as the title to a conversation
1228      * represented by a {@link android.app.Notification.MessagingStyle}
1229      */
1230     public static final String EXTRA_CONVERSATION_TITLE = "android.conversationTitle";
1231 
1232     /** @hide */
1233     public static final String EXTRA_CONVERSATION_ICON = "android.conversationIcon";
1234 
1235     /** @hide */
1236     public static final String EXTRA_CONVERSATION_UNREAD_MESSAGE_COUNT =
1237             "android.conversationUnreadMessageCount";
1238 
1239     /**
1240      * {@link #extras} key: an array of {@link android.app.Notification.MessagingStyle.Message}
1241      * bundles provided by a
1242      * {@link android.app.Notification.MessagingStyle} notification. This extra is a parcelable
1243      * array of bundles.
1244      */
1245     public static final String EXTRA_MESSAGES = "android.messages";
1246 
1247     /**
1248      * {@link #extras} key: an array of
1249      * {@link android.app.Notification.MessagingStyle#addHistoricMessage historic}
1250      * {@link android.app.Notification.MessagingStyle.Message} bundles provided by a
1251      * {@link android.app.Notification.MessagingStyle} notification. This extra is a parcelable
1252      * array of bundles.
1253      */
1254     public static final String EXTRA_HISTORIC_MESSAGES = "android.messages.historic";
1255 
1256     /**
1257      * {@link #extras} key: whether the {@link android.app.Notification.MessagingStyle} notification
1258      * represents a group conversation.
1259      */
1260     public static final String EXTRA_IS_GROUP_CONVERSATION = "android.isGroupConversation";
1261 
1262     /**
1263      * {@link #extras} key: whether the notification should be colorized as
1264      * supplied to {@link Builder#setColorized(boolean)}.
1265      */
1266     public static final String EXTRA_COLORIZED = "android.colorized";
1267 
1268     /**
1269      * @hide
1270      */
1271     public static final String EXTRA_BUILDER_APPLICATION_INFO = "android.appInfo";
1272 
1273     /**
1274      * @hide
1275      */
1276     public static final String EXTRA_CONTAINS_CUSTOM_VIEW = "android.contains.customView";
1277 
1278     /**
1279      * @hide
1280      */
1281     public static final String EXTRA_REDUCED_IMAGES = "android.reduced.images";
1282 
1283     /**
1284      * {@link #extras} key: the audio contents of this notification.
1285      *
1286      * This is for use when rendering the notification on an audio-focused interface;
1287      * the audio contents are a complete sound sample that contains the contents/body of the
1288      * notification. This may be used in substitute of a Text-to-Speech reading of the
1289      * notification. For example if the notification represents a voice message this should point
1290      * to the audio of that message.
1291      *
1292      * The data stored under this key should be a String representation of a Uri that contains the
1293      * audio contents in one of the following formats: WAV, PCM 16-bit, AMR-WB.
1294      *
1295      * This extra is unnecessary if you are using {@code MessagingStyle} since each {@code Message}
1296      * has a field for holding data URI. That field can be used for audio.
1297      * See {@code Message#setData}.
1298      *
1299      * Example usage:
1300      * <pre>
1301      * {@code
1302      * Notification.Builder myBuilder = (build your Notification as normal);
1303      * myBuilder.getExtras().putString(EXTRA_AUDIO_CONTENTS_URI, myAudioUri.toString());
1304      * }
1305      * </pre>
1306      */
1307     public static final String EXTRA_AUDIO_CONTENTS_URI = "android.audioContents";
1308 
1309     /** @hide */
1310     @SystemApi
1311     @RequiresPermission(android.Manifest.permission.SUBSTITUTE_NOTIFICATION_APP_NAME)
1312     public static final String EXTRA_SUBSTITUTE_APP_NAME = "android.substName";
1313 
1314     /**
1315      * This is set on the notifications shown by system_server about apps running foreground
1316      * services. It indicates that the notification should be shown
1317      * only if any of the given apps do not already have a properly tagged
1318      * {@link #FLAG_FOREGROUND_SERVICE} notification currently visible to the user.
1319      * This is a string array of all package names of the apps.
1320      * @hide
1321      */
1322     public static final String EXTRA_FOREGROUND_APPS = "android.foregroundApps";
1323 
1324     @UnsupportedAppUsage
1325     private Icon mSmallIcon;
1326     @UnsupportedAppUsage
1327     private Icon mLargeIcon;
1328 
1329     @UnsupportedAppUsage
1330     private String mChannelId;
1331     private long mTimeout;
1332 
1333     private String mShortcutId;
1334     private LocusId mLocusId;
1335     private CharSequence mSettingsText;
1336 
1337     private BubbleMetadata mBubbleMetadata;
1338 
1339     /** @hide */
1340     @IntDef(prefix = { "GROUP_ALERT_" }, value = {
1341             GROUP_ALERT_ALL, GROUP_ALERT_CHILDREN, GROUP_ALERT_SUMMARY
1342     })
1343     @Retention(RetentionPolicy.SOURCE)
1344     public @interface GroupAlertBehavior {}
1345 
1346     /**
1347      * Constant for {@link Builder#setGroupAlertBehavior(int)}, meaning that all notifications in a
1348      * group with sound or vibration ought to make sound or vibrate (respectively), so this
1349      * notification will not be muted when it is in a group.
1350      */
1351     public static final int GROUP_ALERT_ALL = 0;
1352 
1353     /**
1354      * Constant for {@link Builder#setGroupAlertBehavior(int)}, meaning that all children
1355      * notification in a group should be silenced (no sound or vibration) even if they are posted
1356      * to a {@link NotificationChannel} that has sound and/or vibration. Use this constant to
1357      * mute this notification if this notification is a group child. This must be applied to all
1358      * children notifications you want to mute.
1359      *
1360      * <p> For example, you might want to use this constant if you post a number of children
1361      * notifications at once (say, after a periodic sync), and only need to notify the user
1362      * audibly once.
1363      */
1364     public static final int GROUP_ALERT_SUMMARY = 1;
1365 
1366     /**
1367      * Constant for {@link Builder#setGroupAlertBehavior(int)}, meaning that the summary
1368      * notification in a group should be silenced (no sound or vibration) even if they are
1369      * posted to a {@link NotificationChannel} that has sound and/or vibration. Use this constant
1370      * to mute this notification if this notification is a group summary.
1371      *
1372      * <p>For example, you might want to use this constant if only the children notifications
1373      * in your group have content and the summary is only used to visually group notifications
1374      * rather than to alert the user that new information is available.
1375      */
1376     public static final int GROUP_ALERT_CHILDREN = 2;
1377 
1378     private int mGroupAlertBehavior = GROUP_ALERT_ALL;
1379 
1380     /**
1381      * If this notification is being shown as a badge, always show as a number.
1382      */
1383     public static final int BADGE_ICON_NONE = 0;
1384 
1385     /**
1386      * If this notification is being shown as a badge, use the {@link #getSmallIcon()} to
1387      * represent this notification.
1388      */
1389     public static final int BADGE_ICON_SMALL = 1;
1390 
1391     /**
1392      * If this notification is being shown as a badge, use the {@link #getLargeIcon()} to
1393      * represent this notification.
1394      */
1395     public static final int BADGE_ICON_LARGE = 2;
1396     private int mBadgeIcon = BADGE_ICON_NONE;
1397 
1398     /**
1399      * Determines whether the platform can generate contextual actions for a notification.
1400      */
1401     private boolean mAllowSystemGeneratedContextualActions = true;
1402 
1403     /**
1404      * Structure to encapsulate a named action that can be shown as part of this notification.
1405      * It must include an icon, a label, and a {@link PendingIntent} to be fired when the action is
1406      * selected by the user.
1407      * <p>
1408      * Apps should use {@link Notification.Builder#addAction(int, CharSequence, PendingIntent)}
1409      * or {@link Notification.Builder#addAction(Notification.Action)}
1410      * to attach actions.
1411      */
1412     public static class Action implements Parcelable {
1413         /**
1414          * {@link #extras} key: Keys to a {@link Parcelable} {@link ArrayList} of
1415          * {@link RemoteInput}s.
1416          *
1417          * This is intended for {@link RemoteInput}s that only accept data, meaning
1418          * {@link RemoteInput#getAllowFreeFormInput} is false, {@link RemoteInput#getChoices}
1419          * is null or empty, and {@link RemoteInput#getAllowedDataTypes} is non-null and not
1420          * empty. These {@link RemoteInput}s will be ignored by devices that do not
1421          * support non-text-based {@link RemoteInput}s. See {@link Builder#build}.
1422          *
1423          * You can test if a RemoteInput matches these constraints using
1424          * {@link RemoteInput#isDataOnly}.
1425          */
1426         private static final String EXTRA_DATA_ONLY_INPUTS = "android.extra.DATA_ONLY_INPUTS";
1427 
1428         /**
1429          * {@link }: No semantic action defined.
1430          */
1431         public static final int SEMANTIC_ACTION_NONE = 0;
1432 
1433         /**
1434          * {@code SemanticAction}: Reply to a conversation, chat, group, or wherever replies
1435          * may be appropriate.
1436          */
1437         public static final int SEMANTIC_ACTION_REPLY = 1;
1438 
1439         /**
1440          * {@code SemanticAction}: Mark content as read.
1441          */
1442         public static final int SEMANTIC_ACTION_MARK_AS_READ = 2;
1443 
1444         /**
1445          * {@code SemanticAction}: Mark content as unread.
1446          */
1447         public static final int SEMANTIC_ACTION_MARK_AS_UNREAD = 3;
1448 
1449         /**
1450          * {@code SemanticAction}: Delete the content associated with the notification. This
1451          * could mean deleting an email, message, etc.
1452          */
1453         public static final int SEMANTIC_ACTION_DELETE = 4;
1454 
1455         /**
1456          * {@code SemanticAction}: Archive the content associated with the notification. This
1457          * could mean archiving an email, message, etc.
1458          */
1459         public static final int SEMANTIC_ACTION_ARCHIVE = 5;
1460 
1461         /**
1462          * {@code SemanticAction}: Mute the content associated with the notification. This could
1463          * mean silencing a conversation or currently playing media.
1464          */
1465         public static final int SEMANTIC_ACTION_MUTE = 6;
1466 
1467         /**
1468          * {@code SemanticAction}: Unmute the content associated with the notification. This could
1469          * mean un-silencing a conversation or currently playing media.
1470          */
1471         public static final int SEMANTIC_ACTION_UNMUTE = 7;
1472 
1473         /**
1474          * {@code SemanticAction}: Mark content with a thumbs up.
1475          */
1476         public static final int SEMANTIC_ACTION_THUMBS_UP = 8;
1477 
1478         /**
1479          * {@code SemanticAction}: Mark content with a thumbs down.
1480          */
1481         public static final int SEMANTIC_ACTION_THUMBS_DOWN = 9;
1482 
1483         /**
1484          * {@code SemanticAction}: Call a contact, group, etc.
1485          */
1486         public static final int SEMANTIC_ACTION_CALL = 10;
1487 
1488         private final Bundle mExtras;
1489         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
1490         private Icon mIcon;
1491         private final RemoteInput[] mRemoteInputs;
1492         private boolean mAllowGeneratedReplies = true;
1493         private final @SemanticAction int mSemanticAction;
1494         private final boolean mIsContextual;
1495 
1496         /**
1497          * Small icon representing the action.
1498          *
1499          * @deprecated Use {@link Action#getIcon()} instead.
1500          */
1501         @Deprecated
1502         public int icon;
1503 
1504         /**
1505          * Title of the action.
1506          */
1507         public CharSequence title;
1508 
1509         /**
1510          * Intent to send when the user invokes this action. May be null, in which case the action
1511          * may be rendered in a disabled presentation by the system UI.
1512          */
1513         public PendingIntent actionIntent;
1514 
Action(Parcel in)1515         private Action(Parcel in) {
1516             if (in.readInt() != 0) {
1517                 mIcon = Icon.CREATOR.createFromParcel(in);
1518                 if (mIcon.getType() == Icon.TYPE_RESOURCE) {
1519                     icon = mIcon.getResId();
1520                 }
1521             }
1522             title = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
1523             if (in.readInt() == 1) {
1524                 actionIntent = PendingIntent.CREATOR.createFromParcel(in);
1525             }
1526             mExtras = Bundle.setDefusable(in.readBundle(), true);
1527             mRemoteInputs = in.createTypedArray(RemoteInput.CREATOR);
1528             mAllowGeneratedReplies = in.readInt() == 1;
1529             mSemanticAction = in.readInt();
1530             mIsContextual = in.readInt() == 1;
1531         }
1532 
1533         /**
1534          * @deprecated Use {@link android.app.Notification.Action.Builder}.
1535          */
1536         @Deprecated
Action(int icon, CharSequence title, PendingIntent intent)1537         public Action(int icon, CharSequence title, PendingIntent intent) {
1538             this(Icon.createWithResource("", icon), title, intent, new Bundle(), null, true,
1539                     SEMANTIC_ACTION_NONE, false /* isContextual */);
1540         }
1541 
1542         /** Keep in sync with {@link Notification.Action.Builder#Builder(Action)}! */
Action(Icon icon, CharSequence title, PendingIntent intent, Bundle extras, RemoteInput[] remoteInputs, boolean allowGeneratedReplies, @SemanticAction int semanticAction, boolean isContextual)1543         private Action(Icon icon, CharSequence title, PendingIntent intent, Bundle extras,
1544                 RemoteInput[] remoteInputs, boolean allowGeneratedReplies,
1545                        @SemanticAction int semanticAction, boolean isContextual) {
1546             this.mIcon = icon;
1547             if (icon != null && icon.getType() == Icon.TYPE_RESOURCE) {
1548                 this.icon = icon.getResId();
1549             }
1550             this.title = title;
1551             this.actionIntent = intent;
1552             this.mExtras = extras != null ? extras : new Bundle();
1553             this.mRemoteInputs = remoteInputs;
1554             this.mAllowGeneratedReplies = allowGeneratedReplies;
1555             this.mSemanticAction = semanticAction;
1556             this.mIsContextual = isContextual;
1557         }
1558 
1559         /**
1560          * Return an icon representing the action.
1561          */
getIcon()1562         public Icon getIcon() {
1563             if (mIcon == null && icon != 0) {
1564                 // you snuck an icon in here without using the builder; let's try to keep it
1565                 mIcon = Icon.createWithResource("", icon);
1566             }
1567             return mIcon;
1568         }
1569 
1570         /**
1571          * Get additional metadata carried around with this Action.
1572          */
getExtras()1573         public Bundle getExtras() {
1574             return mExtras;
1575         }
1576 
1577         /**
1578          * Return whether the platform should automatically generate possible replies for this
1579          * {@link Action}
1580          */
getAllowGeneratedReplies()1581         public boolean getAllowGeneratedReplies() {
1582             return mAllowGeneratedReplies;
1583         }
1584 
1585         /**
1586          * Get the list of inputs to be collected from the user when this action is sent.
1587          * May return null if no remote inputs were added. Only returns inputs which accept
1588          * a text input. For inputs which only accept data use {@link #getDataOnlyRemoteInputs}.
1589          */
getRemoteInputs()1590         public RemoteInput[] getRemoteInputs() {
1591             return mRemoteInputs;
1592         }
1593 
1594         /**
1595          * Returns the {@code SemanticAction} associated with this {@link Action}. A
1596          * {@code SemanticAction} denotes what an {@link Action}'s {@link PendingIntent} will do
1597          * (eg. reply, mark as read, delete, etc).
1598          */
getSemanticAction()1599         public @SemanticAction int getSemanticAction() {
1600             return mSemanticAction;
1601         }
1602 
1603         /**
1604          * Returns whether this is a contextual Action, i.e. whether the action is dependent on the
1605          * notification message body. An example of a contextual action could be an action opening a
1606          * map application with an address shown in the notification.
1607          */
isContextual()1608         public boolean isContextual() {
1609             return mIsContextual;
1610         }
1611 
1612         /**
1613          * Get the list of inputs to be collected from the user that ONLY accept data when this
1614          * action is sent. These remote inputs are guaranteed to return true on a call to
1615          * {@link RemoteInput#isDataOnly}.
1616          *
1617          * Returns null if there are no data-only remote inputs.
1618          *
1619          * This method exists so that legacy RemoteInput collectors that pre-date the addition
1620          * of non-textual RemoteInputs do not access these remote inputs.
1621          */
getDataOnlyRemoteInputs()1622         public RemoteInput[] getDataOnlyRemoteInputs() {
1623             return getParcelableArrayFromBundle(mExtras, EXTRA_DATA_ONLY_INPUTS, RemoteInput.class);
1624         }
1625 
1626         /**
1627          * Builder class for {@link Action} objects.
1628          */
1629         public static final class Builder {
1630             @Nullable private final Icon mIcon;
1631             @Nullable private final CharSequence mTitle;
1632             @Nullable private final PendingIntent mIntent;
1633             private boolean mAllowGeneratedReplies = true;
1634             @NonNull private final Bundle mExtras;
1635             @Nullable private ArrayList<RemoteInput> mRemoteInputs;
1636             private @SemanticAction int mSemanticAction;
1637             private boolean mIsContextual;
1638 
1639             /**
1640              * Construct a new builder for {@link Action} object.
1641              * @param icon icon to show for this action
1642              * @param title the title of the action
1643              * @param intent the {@link PendingIntent} to fire when users trigger this action
1644              */
1645             @Deprecated
Builder(int icon, CharSequence title, PendingIntent intent)1646             public Builder(int icon, CharSequence title, PendingIntent intent) {
1647                 this(Icon.createWithResource("", icon), title, intent);
1648             }
1649 
1650             /**
1651              * Construct a new builder for {@link Action} object.
1652              * @param icon icon to show for this action
1653              * @param title the title of the action
1654              * @param intent the {@link PendingIntent} to fire when users trigger this action
1655              */
Builder(Icon icon, CharSequence title, PendingIntent intent)1656             public Builder(Icon icon, CharSequence title, PendingIntent intent) {
1657                 this(icon, title, intent, new Bundle(), null, true, SEMANTIC_ACTION_NONE);
1658             }
1659 
1660             /**
1661              * Construct a new builder for {@link Action} object using the fields from an
1662              * {@link Action}.
1663              * @param action the action to read fields from.
1664              */
Builder(Action action)1665             public Builder(Action action) {
1666                 this(action.getIcon(), action.title, action.actionIntent,
1667                         new Bundle(action.mExtras), action.getRemoteInputs(),
1668                         action.getAllowGeneratedReplies(), action.getSemanticAction());
1669             }
1670 
Builder(@ullable Icon icon, @Nullable CharSequence title, @Nullable PendingIntent intent, @NonNull Bundle extras, @Nullable RemoteInput[] remoteInputs, boolean allowGeneratedReplies, @SemanticAction int semanticAction)1671             private Builder(@Nullable Icon icon, @Nullable CharSequence title,
1672                     @Nullable PendingIntent intent, @NonNull Bundle extras,
1673                     @Nullable RemoteInput[] remoteInputs, boolean allowGeneratedReplies,
1674                     @SemanticAction int semanticAction) {
1675                 mIcon = icon;
1676                 mTitle = title;
1677                 mIntent = intent;
1678                 mExtras = extras;
1679                 if (remoteInputs != null) {
1680                     mRemoteInputs = new ArrayList<RemoteInput>(remoteInputs.length);
1681                     Collections.addAll(mRemoteInputs, remoteInputs);
1682                 }
1683                 mAllowGeneratedReplies = allowGeneratedReplies;
1684                 mSemanticAction = semanticAction;
1685             }
1686 
1687             /**
1688              * Merge additional metadata into this builder.
1689              *
1690              * <p>Values within the Bundle will replace existing extras values in this Builder.
1691              *
1692              * @see Notification.Action#extras
1693              */
1694             @NonNull
addExtras(Bundle extras)1695             public Builder addExtras(Bundle extras) {
1696                 if (extras != null) {
1697                     mExtras.putAll(extras);
1698                 }
1699                 return this;
1700             }
1701 
1702             /**
1703              * Get the metadata Bundle used by this Builder.
1704              *
1705              * <p>The returned Bundle is shared with this Builder.
1706              */
1707             @NonNull
getExtras()1708             public Bundle getExtras() {
1709                 return mExtras;
1710             }
1711 
1712             /**
1713              * Add an input to be collected from the user when this action is sent.
1714              * Response values can be retrieved from the fired intent by using the
1715              * {@link RemoteInput#getResultsFromIntent} function.
1716              * @param remoteInput a {@link RemoteInput} to add to the action
1717              * @return this object for method chaining
1718              */
1719             @NonNull
addRemoteInput(RemoteInput remoteInput)1720             public Builder addRemoteInput(RemoteInput remoteInput) {
1721                 if (mRemoteInputs == null) {
1722                     mRemoteInputs = new ArrayList<RemoteInput>();
1723                 }
1724                 mRemoteInputs.add(remoteInput);
1725                 return this;
1726             }
1727 
1728             /**
1729              * Set whether the platform should automatically generate possible replies to add to
1730              * {@link RemoteInput#getChoices()}. If the {@link Action} doesn't have a
1731              * {@link RemoteInput}, this has no effect.
1732              * @param allowGeneratedReplies {@code true} to allow generated replies, {@code false}
1733              * otherwise
1734              * @return this object for method chaining
1735              * The default value is {@code true}
1736              */
1737             @NonNull
setAllowGeneratedReplies(boolean allowGeneratedReplies)1738             public Builder setAllowGeneratedReplies(boolean allowGeneratedReplies) {
1739                 mAllowGeneratedReplies = allowGeneratedReplies;
1740                 return this;
1741             }
1742 
1743             /**
1744              * Sets the {@code SemanticAction} for this {@link Action}. A
1745              * {@code SemanticAction} denotes what an {@link Action}'s
1746              * {@link PendingIntent} will do (eg. reply, mark as read, delete, etc).
1747              * @param semanticAction a SemanticAction defined within {@link Action} with
1748              * {@code SEMANTIC_ACTION_} prefixes
1749              * @return this object for method chaining
1750              */
1751             @NonNull
setSemanticAction(@emanticAction int semanticAction)1752             public Builder setSemanticAction(@SemanticAction int semanticAction) {
1753                 mSemanticAction = semanticAction;
1754                 return this;
1755             }
1756 
1757             /**
1758              * Sets whether this {@link Action} is a contextual action, i.e. whether the action is
1759              * dependent on the notification message body. An example of a contextual action could
1760              * be an action opening a map application with an address shown in the notification.
1761              */
1762             @NonNull
setContextual(boolean isContextual)1763             public Builder setContextual(boolean isContextual) {
1764                 mIsContextual = isContextual;
1765                 return this;
1766             }
1767 
1768             /**
1769              * Apply an extender to this action builder. Extenders may be used to add
1770              * metadata or change options on this builder.
1771              */
1772             @NonNull
extend(Extender extender)1773             public Builder extend(Extender extender) {
1774                 extender.extend(this);
1775                 return this;
1776             }
1777 
1778             /**
1779              * Throws an NPE if we are building a contextual action missing one of the fields
1780              * necessary to display the action.
1781              */
checkContextualActionNullFields()1782             private void checkContextualActionNullFields() {
1783                 if (!mIsContextual) return;
1784 
1785                 if (mIcon == null) {
1786                     throw new NullPointerException("Contextual Actions must contain a valid icon");
1787                 }
1788 
1789                 if (mIntent == null) {
1790                     throw new NullPointerException(
1791                             "Contextual Actions must contain a valid PendingIntent");
1792                 }
1793             }
1794 
1795             /**
1796              * Combine all of the options that have been set and return a new {@link Action}
1797              * object.
1798              * @return the built action
1799              */
1800             @NonNull
build()1801             public Action build() {
1802                 checkContextualActionNullFields();
1803 
1804                 ArrayList<RemoteInput> dataOnlyInputs = new ArrayList<>();
1805                 RemoteInput[] previousDataInputs = getParcelableArrayFromBundle(
1806                         mExtras, EXTRA_DATA_ONLY_INPUTS, RemoteInput.class);
1807                 if (previousDataInputs != null) {
1808                     for (RemoteInput input : previousDataInputs) {
1809                         dataOnlyInputs.add(input);
1810                     }
1811                 }
1812                 List<RemoteInput> textInputs = new ArrayList<>();
1813                 if (mRemoteInputs != null) {
1814                     for (RemoteInput input : mRemoteInputs) {
1815                         if (input.isDataOnly()) {
1816                             dataOnlyInputs.add(input);
1817                         } else {
1818                             textInputs.add(input);
1819                         }
1820                     }
1821                 }
1822                 if (!dataOnlyInputs.isEmpty()) {
1823                     RemoteInput[] dataInputsArr =
1824                             dataOnlyInputs.toArray(new RemoteInput[dataOnlyInputs.size()]);
1825                     mExtras.putParcelableArray(EXTRA_DATA_ONLY_INPUTS, dataInputsArr);
1826                 }
1827                 RemoteInput[] textInputsArr = textInputs.isEmpty()
1828                         ? null : textInputs.toArray(new RemoteInput[textInputs.size()]);
1829                 return new Action(mIcon, mTitle, mIntent, mExtras, textInputsArr,
1830                         mAllowGeneratedReplies, mSemanticAction, mIsContextual);
1831             }
1832         }
1833 
1834         @Override
clone()1835         public Action clone() {
1836             return new Action(
1837                     getIcon(),
1838                     title,
1839                     actionIntent, // safe to alias
1840                     mExtras == null ? new Bundle() : new Bundle(mExtras),
1841                     getRemoteInputs(),
1842                     getAllowGeneratedReplies(),
1843                     getSemanticAction(),
1844                     isContextual());
1845         }
1846 
1847         @Override
describeContents()1848         public int describeContents() {
1849             return 0;
1850         }
1851 
1852         @Override
writeToParcel(Parcel out, int flags)1853         public void writeToParcel(Parcel out, int flags) {
1854             final Icon ic = getIcon();
1855             if (ic != null) {
1856                 out.writeInt(1);
1857                 ic.writeToParcel(out, 0);
1858             } else {
1859                 out.writeInt(0);
1860             }
1861             TextUtils.writeToParcel(title, out, flags);
1862             if (actionIntent != null) {
1863                 out.writeInt(1);
1864                 actionIntent.writeToParcel(out, flags);
1865             } else {
1866                 out.writeInt(0);
1867             }
1868             out.writeBundle(mExtras);
1869             out.writeTypedArray(mRemoteInputs, flags);
1870             out.writeInt(mAllowGeneratedReplies ? 1 : 0);
1871             out.writeInt(mSemanticAction);
1872             out.writeInt(mIsContextual ? 1 : 0);
1873         }
1874 
1875         public static final @android.annotation.NonNull Parcelable.Creator<Action> CREATOR =
1876                 new Parcelable.Creator<Action>() {
1877             public Action createFromParcel(Parcel in) {
1878                 return new Action(in);
1879             }
1880             public Action[] newArray(int size) {
1881                 return new Action[size];
1882             }
1883         };
1884 
1885         /**
1886          * Extender interface for use with {@link Builder#extend}. Extenders may be used to add
1887          * metadata or change options on an action builder.
1888          */
1889         public interface Extender {
1890             /**
1891              * Apply this extender to a notification action builder.
1892              * @param builder the builder to be modified.
1893              * @return the build object for chaining.
1894              */
extend(Builder builder)1895             public Builder extend(Builder builder);
1896         }
1897 
1898         /**
1899          * Wearable extender for notification actions. To add extensions to an action,
1900          * create a new {@link android.app.Notification.Action.WearableExtender} object using
1901          * the {@code WearableExtender()} constructor and apply it to a
1902          * {@link android.app.Notification.Action.Builder} using
1903          * {@link android.app.Notification.Action.Builder#extend}.
1904          *
1905          * <pre class="prettyprint">
1906          * Notification.Action action = new Notification.Action.Builder(
1907          *         R.drawable.archive_all, "Archive all", actionIntent)
1908          *         .extend(new Notification.Action.WearableExtender()
1909          *                 .setAvailableOffline(false))
1910          *         .build();</pre>
1911          */
1912         public static final class WearableExtender implements Extender {
1913             /** Notification action extra which contains wearable extensions */
1914             private static final String EXTRA_WEARABLE_EXTENSIONS = "android.wearable.EXTENSIONS";
1915 
1916             // Keys within EXTRA_WEARABLE_EXTENSIONS for wearable options.
1917             private static final String KEY_FLAGS = "flags";
1918             private static final String KEY_IN_PROGRESS_LABEL = "inProgressLabel";
1919             private static final String KEY_CONFIRM_LABEL = "confirmLabel";
1920             private static final String KEY_CANCEL_LABEL = "cancelLabel";
1921 
1922             // Flags bitwise-ored to mFlags
1923             private static final int FLAG_AVAILABLE_OFFLINE = 0x1;
1924             private static final int FLAG_HINT_LAUNCHES_ACTIVITY = 1 << 1;
1925             private static final int FLAG_HINT_DISPLAY_INLINE = 1 << 2;
1926 
1927             // Default value for flags integer
1928             private static final int DEFAULT_FLAGS = FLAG_AVAILABLE_OFFLINE;
1929 
1930             private int mFlags = DEFAULT_FLAGS;
1931 
1932             private CharSequence mInProgressLabel;
1933             private CharSequence mConfirmLabel;
1934             private CharSequence mCancelLabel;
1935 
1936             /**
1937              * Create a {@link android.app.Notification.Action.WearableExtender} with default
1938              * options.
1939              */
WearableExtender()1940             public WearableExtender() {
1941             }
1942 
1943             /**
1944              * Create a {@link android.app.Notification.Action.WearableExtender} by reading
1945              * wearable options present in an existing notification action.
1946              * @param action the notification action to inspect.
1947              */
WearableExtender(Action action)1948             public WearableExtender(Action action) {
1949                 Bundle wearableBundle = action.getExtras().getBundle(EXTRA_WEARABLE_EXTENSIONS);
1950                 if (wearableBundle != null) {
1951                     mFlags = wearableBundle.getInt(KEY_FLAGS, DEFAULT_FLAGS);
1952                     mInProgressLabel = wearableBundle.getCharSequence(KEY_IN_PROGRESS_LABEL);
1953                     mConfirmLabel = wearableBundle.getCharSequence(KEY_CONFIRM_LABEL);
1954                     mCancelLabel = wearableBundle.getCharSequence(KEY_CANCEL_LABEL);
1955                 }
1956             }
1957 
1958             /**
1959              * Apply wearable extensions to a notification action that is being built. This is
1960              * typically called by the {@link android.app.Notification.Action.Builder#extend}
1961              * method of {@link android.app.Notification.Action.Builder}.
1962              */
1963             @Override
extend(Action.Builder builder)1964             public Action.Builder extend(Action.Builder builder) {
1965                 Bundle wearableBundle = new Bundle();
1966 
1967                 if (mFlags != DEFAULT_FLAGS) {
1968                     wearableBundle.putInt(KEY_FLAGS, mFlags);
1969                 }
1970                 if (mInProgressLabel != null) {
1971                     wearableBundle.putCharSequence(KEY_IN_PROGRESS_LABEL, mInProgressLabel);
1972                 }
1973                 if (mConfirmLabel != null) {
1974                     wearableBundle.putCharSequence(KEY_CONFIRM_LABEL, mConfirmLabel);
1975                 }
1976                 if (mCancelLabel != null) {
1977                     wearableBundle.putCharSequence(KEY_CANCEL_LABEL, mCancelLabel);
1978                 }
1979 
1980                 builder.getExtras().putBundle(EXTRA_WEARABLE_EXTENSIONS, wearableBundle);
1981                 return builder;
1982             }
1983 
1984             @Override
clone()1985             public WearableExtender clone() {
1986                 WearableExtender that = new WearableExtender();
1987                 that.mFlags = this.mFlags;
1988                 that.mInProgressLabel = this.mInProgressLabel;
1989                 that.mConfirmLabel = this.mConfirmLabel;
1990                 that.mCancelLabel = this.mCancelLabel;
1991                 return that;
1992             }
1993 
1994             /**
1995              * Set whether this action is available when the wearable device is not connected to
1996              * a companion device. The user can still trigger this action when the wearable device is
1997              * offline, but a visual hint will indicate that the action may not be available.
1998              * Defaults to true.
1999              */
setAvailableOffline(boolean availableOffline)2000             public WearableExtender setAvailableOffline(boolean availableOffline) {
2001                 setFlag(FLAG_AVAILABLE_OFFLINE, availableOffline);
2002                 return this;
2003             }
2004 
2005             /**
2006              * Get whether this action is available when the wearable device is not connected to
2007              * a companion device. The user can still trigger this action when the wearable device is
2008              * offline, but a visual hint will indicate that the action may not be available.
2009              * Defaults to true.
2010              */
isAvailableOffline()2011             public boolean isAvailableOffline() {
2012                 return (mFlags & FLAG_AVAILABLE_OFFLINE) != 0;
2013             }
2014 
setFlag(int mask, boolean value)2015             private void setFlag(int mask, boolean value) {
2016                 if (value) {
2017                     mFlags |= mask;
2018                 } else {
2019                     mFlags &= ~mask;
2020                 }
2021             }
2022 
2023             /**
2024              * Set a label to display while the wearable is preparing to automatically execute the
2025              * action. This is usually a 'ing' verb ending in ellipsis like "Sending..."
2026              *
2027              * @param label the label to display while the action is being prepared to execute
2028              * @return this object for method chaining
2029              */
2030             @Deprecated
setInProgressLabel(CharSequence label)2031             public WearableExtender setInProgressLabel(CharSequence label) {
2032                 mInProgressLabel = label;
2033                 return this;
2034             }
2035 
2036             /**
2037              * Get the label to display while the wearable is preparing to automatically execute
2038              * the action. This is usually a 'ing' verb ending in ellipsis like "Sending..."
2039              *
2040              * @return the label to display while the action is being prepared to execute
2041              */
2042             @Deprecated
getInProgressLabel()2043             public CharSequence getInProgressLabel() {
2044                 return mInProgressLabel;
2045             }
2046 
2047             /**
2048              * Set a label to display to confirm that the action should be executed.
2049              * This is usually an imperative verb like "Send".
2050              *
2051              * @param label the label to confirm the action should be executed
2052              * @return this object for method chaining
2053              */
2054             @Deprecated
setConfirmLabel(CharSequence label)2055             public WearableExtender setConfirmLabel(CharSequence label) {
2056                 mConfirmLabel = label;
2057                 return this;
2058             }
2059 
2060             /**
2061              * Get the label to display to confirm that the action should be executed.
2062              * This is usually an imperative verb like "Send".
2063              *
2064              * @return the label to confirm the action should be executed
2065              */
2066             @Deprecated
getConfirmLabel()2067             public CharSequence getConfirmLabel() {
2068                 return mConfirmLabel;
2069             }
2070 
2071             /**
2072              * Set a label to display to cancel the action.
2073              * This is usually an imperative verb, like "Cancel".
2074              *
2075              * @param label the label to display to cancel the action
2076              * @return this object for method chaining
2077              */
2078             @Deprecated
setCancelLabel(CharSequence label)2079             public WearableExtender setCancelLabel(CharSequence label) {
2080                 mCancelLabel = label;
2081                 return this;
2082             }
2083 
2084             /**
2085              * Get the label to display to cancel the action.
2086              * This is usually an imperative verb like "Cancel".
2087              *
2088              * @return the label to display to cancel the action
2089              */
2090             @Deprecated
getCancelLabel()2091             public CharSequence getCancelLabel() {
2092                 return mCancelLabel;
2093             }
2094 
2095             /**
2096              * Set a hint that this Action will launch an {@link Activity} directly, telling the
2097              * platform that it can generate the appropriate transitions.
2098              * @param hintLaunchesActivity {@code true} if the content intent will launch
2099              * an activity and transitions should be generated, false otherwise.
2100              * @return this object for method chaining
2101              */
setHintLaunchesActivity( boolean hintLaunchesActivity)2102             public WearableExtender setHintLaunchesActivity(
2103                     boolean hintLaunchesActivity) {
2104                 setFlag(FLAG_HINT_LAUNCHES_ACTIVITY, hintLaunchesActivity);
2105                 return this;
2106             }
2107 
2108             /**
2109              * Get a hint that this Action will launch an {@link Activity} directly, telling the
2110              * platform that it can generate the appropriate transitions
2111              * @return {@code true} if the content intent will launch an activity and transitions
2112              * should be generated, false otherwise. The default value is {@code false} if this was
2113              * never set.
2114              */
getHintLaunchesActivity()2115             public boolean getHintLaunchesActivity() {
2116                 return (mFlags & FLAG_HINT_LAUNCHES_ACTIVITY) != 0;
2117             }
2118 
2119             /**
2120              * Set a hint that this Action should be displayed inline.
2121              *
2122              * @param hintDisplayInline {@code true} if action should be displayed inline, false
2123              *        otherwise
2124              * @return this object for method chaining
2125              */
setHintDisplayActionInline( boolean hintDisplayInline)2126             public WearableExtender setHintDisplayActionInline(
2127                     boolean hintDisplayInline) {
2128                 setFlag(FLAG_HINT_DISPLAY_INLINE, hintDisplayInline);
2129                 return this;
2130             }
2131 
2132             /**
2133              * Get a hint that this Action should be displayed inline.
2134              *
2135              * @return {@code true} if the Action should be displayed inline, {@code false}
2136              *         otherwise. The default value is {@code false} if this was never set.
2137              */
getHintDisplayActionInline()2138             public boolean getHintDisplayActionInline() {
2139                 return (mFlags & FLAG_HINT_DISPLAY_INLINE) != 0;
2140             }
2141         }
2142 
2143         /**
2144          * Provides meaning to an {@link Action} that hints at what the associated
2145          * {@link PendingIntent} will do. For example, an {@link Action} with a
2146          * {@link PendingIntent} that replies to a text message notification may have the
2147          * {@link #SEMANTIC_ACTION_REPLY} {@code SemanticAction} set within it.
2148          *
2149          * @hide
2150          */
2151         @IntDef(prefix = { "SEMANTIC_ACTION_" }, value = {
2152                 SEMANTIC_ACTION_NONE,
2153                 SEMANTIC_ACTION_REPLY,
2154                 SEMANTIC_ACTION_MARK_AS_READ,
2155                 SEMANTIC_ACTION_MARK_AS_UNREAD,
2156                 SEMANTIC_ACTION_DELETE,
2157                 SEMANTIC_ACTION_ARCHIVE,
2158                 SEMANTIC_ACTION_MUTE,
2159                 SEMANTIC_ACTION_UNMUTE,
2160                 SEMANTIC_ACTION_THUMBS_UP,
2161                 SEMANTIC_ACTION_THUMBS_DOWN,
2162                 SEMANTIC_ACTION_CALL
2163         })
2164         @Retention(RetentionPolicy.SOURCE)
2165         public @interface SemanticAction {}
2166     }
2167 
2168     /**
2169      * Array of all {@link Action} structures attached to this notification by
2170      * {@link Builder#addAction(int, CharSequence, PendingIntent)}. Mostly useful for instances of
2171      * {@link android.service.notification.NotificationListenerService} that provide an alternative
2172      * interface for invoking actions.
2173      */
2174     public Action[] actions;
2175 
2176     /**
2177      * Replacement version of this notification whose content will be shown
2178      * in an insecure context such as atop a secure keyguard. See {@link #visibility}
2179      * and {@link #VISIBILITY_PUBLIC}.
2180      */
2181     public Notification publicVersion;
2182 
2183     /**
2184      * Constructs a Notification object with default values.
2185      * You might want to consider using {@link Builder} instead.
2186      */
Notification()2187     public Notification()
2188     {
2189         this.when = System.currentTimeMillis();
2190         this.creationTime = System.currentTimeMillis();
2191         this.priority = PRIORITY_DEFAULT;
2192     }
2193 
2194     /**
2195      * @hide
2196      */
2197     @UnsupportedAppUsage
Notification(Context context, int icon, CharSequence tickerText, long when, CharSequence contentTitle, CharSequence contentText, Intent contentIntent)2198     public Notification(Context context, int icon, CharSequence tickerText, long when,
2199             CharSequence contentTitle, CharSequence contentText, Intent contentIntent)
2200     {
2201         new Builder(context)
2202                 .setWhen(when)
2203                 .setSmallIcon(icon)
2204                 .setTicker(tickerText)
2205                 .setContentTitle(contentTitle)
2206                 .setContentText(contentText)
2207                 .setContentIntent(PendingIntent.getActivity(context, 0, contentIntent, 0))
2208                 .buildInto(this);
2209     }
2210 
2211     /**
2212      * Constructs a Notification object with the information needed to
2213      * have a status bar icon without the standard expanded view.
2214      *
2215      * @param icon          The resource id of the icon to put in the status bar.
2216      * @param tickerText    The text that flows by in the status bar when the notification first
2217      *                      activates.
2218      * @param when          The time to show in the time field.  In the System.currentTimeMillis
2219      *                      timebase.
2220      *
2221      * @deprecated Use {@link Builder} instead.
2222      */
2223     @Deprecated
Notification(int icon, CharSequence tickerText, long when)2224     public Notification(int icon, CharSequence tickerText, long when)
2225     {
2226         this.icon = icon;
2227         this.tickerText = tickerText;
2228         this.when = when;
2229         this.creationTime = System.currentTimeMillis();
2230     }
2231 
2232     /**
2233      * Unflatten the notification from a parcel.
2234      */
2235     @SuppressWarnings("unchecked")
Notification(Parcel parcel)2236     public Notification(Parcel parcel) {
2237         // IMPORTANT: Add unmarshaling code in readFromParcel as the pending
2238         // intents in extras are always written as the last entry.
2239         readFromParcelImpl(parcel);
2240         // Must be read last!
2241         allPendingIntents = (ArraySet<PendingIntent>) parcel.readArraySet(null);
2242     }
2243 
readFromParcelImpl(Parcel parcel)2244     private void readFromParcelImpl(Parcel parcel)
2245     {
2246         int version = parcel.readInt();
2247 
2248         mWhitelistToken = parcel.readStrongBinder();
2249         if (mWhitelistToken == null) {
2250             mWhitelistToken = processWhitelistToken;
2251         }
2252         // Propagate this token to all pending intents that are unmarshalled from the parcel.
2253         parcel.setClassCookie(PendingIntent.class, mWhitelistToken);
2254 
2255         when = parcel.readLong();
2256         creationTime = parcel.readLong();
2257         if (parcel.readInt() != 0) {
2258             mSmallIcon = Icon.CREATOR.createFromParcel(parcel);
2259             if (mSmallIcon.getType() == Icon.TYPE_RESOURCE) {
2260                 icon = mSmallIcon.getResId();
2261             }
2262         }
2263         number = parcel.readInt();
2264         if (parcel.readInt() != 0) {
2265             contentIntent = PendingIntent.CREATOR.createFromParcel(parcel);
2266         }
2267         if (parcel.readInt() != 0) {
2268             deleteIntent = PendingIntent.CREATOR.createFromParcel(parcel);
2269         }
2270         if (parcel.readInt() != 0) {
2271             tickerText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
2272         }
2273         if (parcel.readInt() != 0) {
2274             tickerView = RemoteViews.CREATOR.createFromParcel(parcel);
2275         }
2276         if (parcel.readInt() != 0) {
2277             contentView = RemoteViews.CREATOR.createFromParcel(parcel);
2278         }
2279         if (parcel.readInt() != 0) {
2280             mLargeIcon = Icon.CREATOR.createFromParcel(parcel);
2281         }
2282         defaults = parcel.readInt();
2283         flags = parcel.readInt();
2284         if (parcel.readInt() != 0) {
2285             sound = Uri.CREATOR.createFromParcel(parcel);
2286         }
2287 
2288         audioStreamType = parcel.readInt();
2289         if (parcel.readInt() != 0) {
2290             audioAttributes = AudioAttributes.CREATOR.createFromParcel(parcel);
2291         }
2292         vibrate = parcel.createLongArray();
2293         ledARGB = parcel.readInt();
2294         ledOnMS = parcel.readInt();
2295         ledOffMS = parcel.readInt();
2296         iconLevel = parcel.readInt();
2297 
2298         if (parcel.readInt() != 0) {
2299             fullScreenIntent = PendingIntent.CREATOR.createFromParcel(parcel);
2300         }
2301 
2302         priority = parcel.readInt();
2303 
2304         category = parcel.readString8();
2305 
2306         mGroupKey = parcel.readString8();
2307 
2308         mSortKey = parcel.readString8();
2309 
2310         extras = Bundle.setDefusable(parcel.readBundle(), true); // may be null
2311         fixDuplicateExtras();
2312 
2313         actions = parcel.createTypedArray(Action.CREATOR); // may be null
2314 
2315         if (parcel.readInt() != 0) {
2316             bigContentView = RemoteViews.CREATOR.createFromParcel(parcel);
2317         }
2318 
2319         if (parcel.readInt() != 0) {
2320             headsUpContentView = RemoteViews.CREATOR.createFromParcel(parcel);
2321         }
2322 
2323         visibility = parcel.readInt();
2324 
2325         if (parcel.readInt() != 0) {
2326             publicVersion = Notification.CREATOR.createFromParcel(parcel);
2327         }
2328 
2329         color = parcel.readInt();
2330 
2331         if (parcel.readInt() != 0) {
2332             mChannelId = parcel.readString8();
2333         }
2334         mTimeout = parcel.readLong();
2335 
2336         if (parcel.readInt() != 0) {
2337             mShortcutId = parcel.readString8();
2338         }
2339 
2340         if (parcel.readInt() != 0) {
2341             mLocusId = LocusId.CREATOR.createFromParcel(parcel);
2342         }
2343 
2344         mBadgeIcon = parcel.readInt();
2345 
2346         if (parcel.readInt() != 0) {
2347             mSettingsText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
2348         }
2349 
2350         mGroupAlertBehavior = parcel.readInt();
2351         if (parcel.readInt() != 0) {
2352             mBubbleMetadata = BubbleMetadata.CREATOR.createFromParcel(parcel);
2353         }
2354 
2355         mAllowSystemGeneratedContextualActions = parcel.readBoolean();
2356     }
2357 
2358     @Override
clone()2359     public Notification clone() {
2360         Notification that = new Notification();
2361         cloneInto(that, true);
2362         return that;
2363     }
2364 
2365     /**
2366      * Copy all (or if heavy is false, all except Bitmaps and RemoteViews) members
2367      * of this into that.
2368      * @hide
2369      */
cloneInto(Notification that, boolean heavy)2370     public void cloneInto(Notification that, boolean heavy) {
2371         that.mWhitelistToken = this.mWhitelistToken;
2372         that.when = this.when;
2373         that.creationTime = this.creationTime;
2374         that.mSmallIcon = this.mSmallIcon;
2375         that.number = this.number;
2376 
2377         // PendingIntents are global, so there's no reason (or way) to clone them.
2378         that.contentIntent = this.contentIntent;
2379         that.deleteIntent = this.deleteIntent;
2380         that.fullScreenIntent = this.fullScreenIntent;
2381 
2382         if (this.tickerText != null) {
2383             that.tickerText = this.tickerText.toString();
2384         }
2385         if (heavy && this.tickerView != null) {
2386             that.tickerView = this.tickerView.clone();
2387         }
2388         if (heavy && this.contentView != null) {
2389             that.contentView = this.contentView.clone();
2390         }
2391         if (heavy && this.mLargeIcon != null) {
2392             that.mLargeIcon = this.mLargeIcon;
2393         }
2394         that.iconLevel = this.iconLevel;
2395         that.sound = this.sound; // android.net.Uri is immutable
2396         that.audioStreamType = this.audioStreamType;
2397         if (this.audioAttributes != null) {
2398             that.audioAttributes = new AudioAttributes.Builder(this.audioAttributes).build();
2399         }
2400 
2401         final long[] vibrate = this.vibrate;
2402         if (vibrate != null) {
2403             final int N = vibrate.length;
2404             final long[] vib = that.vibrate = new long[N];
2405             System.arraycopy(vibrate, 0, vib, 0, N);
2406         }
2407 
2408         that.ledARGB = this.ledARGB;
2409         that.ledOnMS = this.ledOnMS;
2410         that.ledOffMS = this.ledOffMS;
2411         that.defaults = this.defaults;
2412 
2413         that.flags = this.flags;
2414 
2415         that.priority = this.priority;
2416 
2417         that.category = this.category;
2418 
2419         that.mGroupKey = this.mGroupKey;
2420 
2421         that.mSortKey = this.mSortKey;
2422 
2423         if (this.extras != null) {
2424             try {
2425                 that.extras = new Bundle(this.extras);
2426                 // will unparcel
2427                 that.extras.size();
2428             } catch (BadParcelableException e) {
2429                 Log.e(TAG, "could not unparcel extras from notification: " + this, e);
2430                 that.extras = null;
2431             }
2432         }
2433 
2434         if (!ArrayUtils.isEmpty(allPendingIntents)) {
2435             that.allPendingIntents = new ArraySet<>(allPendingIntents);
2436         }
2437 
2438         if (this.actions != null) {
2439             that.actions = new Action[this.actions.length];
2440             for(int i=0; i<this.actions.length; i++) {
2441                 if ( this.actions[i] != null) {
2442                     that.actions[i] = this.actions[i].clone();
2443                 }
2444             }
2445         }
2446 
2447         if (heavy && this.bigContentView != null) {
2448             that.bigContentView = this.bigContentView.clone();
2449         }
2450 
2451         if (heavy && this.headsUpContentView != null) {
2452             that.headsUpContentView = this.headsUpContentView.clone();
2453         }
2454 
2455         that.visibility = this.visibility;
2456 
2457         if (this.publicVersion != null) {
2458             that.publicVersion = new Notification();
2459             this.publicVersion.cloneInto(that.publicVersion, heavy);
2460         }
2461 
2462         that.color = this.color;
2463 
2464         that.mChannelId = this.mChannelId;
2465         that.mTimeout = this.mTimeout;
2466         that.mShortcutId = this.mShortcutId;
2467         that.mLocusId = this.mLocusId;
2468         that.mBadgeIcon = this.mBadgeIcon;
2469         that.mSettingsText = this.mSettingsText;
2470         that.mGroupAlertBehavior = this.mGroupAlertBehavior;
2471         that.mBubbleMetadata = this.mBubbleMetadata;
2472         that.mAllowSystemGeneratedContextualActions = this.mAllowSystemGeneratedContextualActions;
2473 
2474         if (!heavy) {
2475             that.lightenPayload(); // will clean out extras
2476         }
2477     }
2478 
2479     /**
2480      * Note all {@link Uri} that are referenced internally, with the expectation
2481      * that Uri permission grants will need to be issued to ensure the recipient
2482      * of this object is able to render its contents.
2483      *
2484      * @hide
2485      */
visitUris(@onNull Consumer<Uri> visitor)2486     public void visitUris(@NonNull Consumer<Uri> visitor) {
2487         visitor.accept(sound);
2488 
2489         if (tickerView != null) tickerView.visitUris(visitor);
2490         if (contentView != null) contentView.visitUris(visitor);
2491         if (bigContentView != null) bigContentView.visitUris(visitor);
2492         if (headsUpContentView != null) headsUpContentView.visitUris(visitor);
2493 
2494         if (extras != null) {
2495             visitor.accept(extras.getParcelable(EXTRA_AUDIO_CONTENTS_URI));
2496             if (extras.containsKey(EXTRA_BACKGROUND_IMAGE_URI)) {
2497                 visitor.accept(Uri.parse(extras.getString(EXTRA_BACKGROUND_IMAGE_URI)));
2498             }
2499 
2500             ArrayList<Person> people = extras.getParcelableArrayList(EXTRA_PEOPLE_LIST);
2501             if (people != null && !people.isEmpty()) {
2502                 for (Person p : people) {
2503                     if (p.getIconUri() != null) {
2504                         visitor.accept(p.getIconUri());
2505                     }
2506                 }
2507             }
2508 
2509             final Person person = extras.getParcelable(EXTRA_MESSAGING_PERSON);
2510             if (person != null && person.getIconUri() != null) {
2511                 visitor.accept(person.getIconUri());
2512             }
2513         }
2514 
2515         if (MessagingStyle.class.equals(getNotificationStyle()) && extras != null) {
2516             final Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES);
2517             if (!ArrayUtils.isEmpty(messages)) {
2518                 for (MessagingStyle.Message message : MessagingStyle.Message
2519                         .getMessagesFromBundleArray(messages)) {
2520                     visitor.accept(message.getDataUri());
2521 
2522                     Person senderPerson = message.getSenderPerson();
2523                     if (senderPerson != null && senderPerson.getIconUri() != null) {
2524                         visitor.accept(senderPerson.getIconUri());
2525                     }
2526                 }
2527             }
2528 
2529             final Parcelable[] historic = extras.getParcelableArray(EXTRA_HISTORIC_MESSAGES);
2530             if (!ArrayUtils.isEmpty(historic)) {
2531                 for (MessagingStyle.Message message : MessagingStyle.Message
2532                         .getMessagesFromBundleArray(historic)) {
2533                     visitor.accept(message.getDataUri());
2534 
2535                     Person senderPerson = message.getSenderPerson();
2536                     if (senderPerson != null && senderPerson.getIconUri() != null) {
2537                         visitor.accept(senderPerson.getIconUri());
2538                     }
2539                 }
2540             }
2541         }
2542 
2543         if (mBubbleMetadata != null && mBubbleMetadata.getIcon() != null) {
2544             final Icon icon = mBubbleMetadata.getIcon();
2545             final int iconType = icon.getType();
2546             if (iconType == TYPE_URI_ADAPTIVE_BITMAP || iconType == TYPE_URI) {
2547                 visitor.accept(icon.getUri());
2548             }
2549         }
2550     }
2551 
2552     /**
2553      * Removes heavyweight parts of the Notification object for archival or for sending to
2554      * listeners when the full contents are not necessary.
2555      * @hide
2556      */
lightenPayload()2557     public final void lightenPayload() {
2558         tickerView = null;
2559         contentView = null;
2560         bigContentView = null;
2561         headsUpContentView = null;
2562         mLargeIcon = null;
2563         if (extras != null && !extras.isEmpty()) {
2564             final Set<String> keyset = extras.keySet();
2565             final int N = keyset.size();
2566             final String[] keys = keyset.toArray(new String[N]);
2567             for (int i=0; i<N; i++) {
2568                 final String key = keys[i];
2569                 if (TvExtender.EXTRA_TV_EXTENDER.equals(key)) {
2570                     continue;
2571                 }
2572                 final Object obj = extras.get(key);
2573                 if (obj != null &&
2574                     (  obj instanceof Parcelable
2575                     || obj instanceof Parcelable[]
2576                     || obj instanceof SparseArray
2577                     || obj instanceof ArrayList)) {
2578                     extras.remove(key);
2579                 }
2580             }
2581         }
2582     }
2583 
2584     /**
2585      * Make sure this CharSequence is safe to put into a bundle, which basically
2586      * means it had better not be some custom Parcelable implementation.
2587      * @hide
2588      */
safeCharSequence(CharSequence cs)2589     public static CharSequence safeCharSequence(CharSequence cs) {
2590         if (cs == null) return cs;
2591         if (cs.length() > MAX_CHARSEQUENCE_LENGTH) {
2592             cs = cs.subSequence(0, MAX_CHARSEQUENCE_LENGTH);
2593         }
2594         if (cs instanceof Parcelable) {
2595             Log.e(TAG, "warning: " + cs.getClass().getCanonicalName()
2596                     + " instance is a custom Parcelable and not allowed in Notification");
2597             return cs.toString();
2598         }
2599         return removeTextSizeSpans(cs);
2600     }
2601 
removeTextSizeSpans(CharSequence charSequence)2602     private static CharSequence removeTextSizeSpans(CharSequence charSequence) {
2603         if (charSequence instanceof Spanned) {
2604             Spanned ss = (Spanned) charSequence;
2605             Object[] spans = ss.getSpans(0, ss.length(), Object.class);
2606             SpannableStringBuilder builder = new SpannableStringBuilder(ss.toString());
2607             for (Object span : spans) {
2608                 Object resultSpan = span;
2609                 if (resultSpan instanceof CharacterStyle) {
2610                     resultSpan = ((CharacterStyle) span).getUnderlying();
2611                 }
2612                 if (resultSpan instanceof TextAppearanceSpan) {
2613                     TextAppearanceSpan originalSpan = (TextAppearanceSpan) resultSpan;
2614                     resultSpan = new TextAppearanceSpan(
2615                             originalSpan.getFamily(),
2616                             originalSpan.getTextStyle(),
2617                             -1,
2618                             originalSpan.getTextColor(),
2619                             originalSpan.getLinkTextColor());
2620                 } else if (resultSpan instanceof RelativeSizeSpan
2621                         || resultSpan instanceof AbsoluteSizeSpan) {
2622                     continue;
2623                 } else {
2624                     resultSpan = span;
2625                 }
2626                 builder.setSpan(resultSpan, ss.getSpanStart(span), ss.getSpanEnd(span),
2627                         ss.getSpanFlags(span));
2628             }
2629             return builder;
2630         }
2631         return charSequence;
2632     }
2633 
describeContents()2634     public int describeContents() {
2635         return 0;
2636     }
2637 
2638     /**
2639      * Flatten this notification into a parcel.
2640      */
writeToParcel(Parcel parcel, int flags)2641     public void writeToParcel(Parcel parcel, int flags) {
2642         // We need to mark all pending intents getting into the notification
2643         // system as being put there to later allow the notification ranker
2644         // to launch them and by doing so add the app to the battery saver white
2645         // list for a short period of time. The problem is that the system
2646         // cannot look into the extras as there may be parcelables there that
2647         // the platform does not know how to handle. To go around that we have
2648         // an explicit list of the pending intents in the extras bundle.
2649         final boolean collectPendingIntents = (allPendingIntents == null);
2650         if (collectPendingIntents) {
2651             PendingIntent.setOnMarshaledListener(
2652                     (PendingIntent intent, Parcel out, int outFlags) -> {
2653                 if (parcel == out) {
2654                     synchronized (this) {
2655                         if (allPendingIntents == null) {
2656                             allPendingIntents = new ArraySet<>();
2657                         }
2658                         allPendingIntents.add(intent);
2659                     }
2660                 }
2661             });
2662         }
2663         try {
2664             // IMPORTANT: Add marshaling code in writeToParcelImpl as we
2665             // want to intercept all pending events written to the parcel.
2666             writeToParcelImpl(parcel, flags);
2667             synchronized (this) {
2668                 // Must be written last!
2669                 parcel.writeArraySet(allPendingIntents);
2670             }
2671         } finally {
2672             if (collectPendingIntents) {
2673                 PendingIntent.setOnMarshaledListener(null);
2674             }
2675         }
2676     }
2677 
writeToParcelImpl(Parcel parcel, int flags)2678     private void writeToParcelImpl(Parcel parcel, int flags) {
2679         parcel.writeInt(1);
2680 
2681         parcel.writeStrongBinder(mWhitelistToken);
2682         parcel.writeLong(when);
2683         parcel.writeLong(creationTime);
2684         if (mSmallIcon == null && icon != 0) {
2685             // you snuck an icon in here without using the builder; let's try to keep it
2686             mSmallIcon = Icon.createWithResource("", icon);
2687         }
2688         if (mSmallIcon != null) {
2689             parcel.writeInt(1);
2690             mSmallIcon.writeToParcel(parcel, 0);
2691         } else {
2692             parcel.writeInt(0);
2693         }
2694         parcel.writeInt(number);
2695         if (contentIntent != null) {
2696             parcel.writeInt(1);
2697             contentIntent.writeToParcel(parcel, 0);
2698         } else {
2699             parcel.writeInt(0);
2700         }
2701         if (deleteIntent != null) {
2702             parcel.writeInt(1);
2703             deleteIntent.writeToParcel(parcel, 0);
2704         } else {
2705             parcel.writeInt(0);
2706         }
2707         if (tickerText != null) {
2708             parcel.writeInt(1);
2709             TextUtils.writeToParcel(tickerText, parcel, flags);
2710         } else {
2711             parcel.writeInt(0);
2712         }
2713         if (tickerView != null) {
2714             parcel.writeInt(1);
2715             tickerView.writeToParcel(parcel, 0);
2716         } else {
2717             parcel.writeInt(0);
2718         }
2719         if (contentView != null) {
2720             parcel.writeInt(1);
2721             contentView.writeToParcel(parcel, 0);
2722         } else {
2723             parcel.writeInt(0);
2724         }
2725         if (mLargeIcon == null && largeIcon != null) {
2726             // you snuck an icon in here without using the builder; let's try to keep it
2727             mLargeIcon = Icon.createWithBitmap(largeIcon);
2728         }
2729         if (mLargeIcon != null) {
2730             parcel.writeInt(1);
2731             mLargeIcon.writeToParcel(parcel, 0);
2732         } else {
2733             parcel.writeInt(0);
2734         }
2735 
2736         parcel.writeInt(defaults);
2737         parcel.writeInt(this.flags);
2738 
2739         if (sound != null) {
2740             parcel.writeInt(1);
2741             sound.writeToParcel(parcel, 0);
2742         } else {
2743             parcel.writeInt(0);
2744         }
2745         parcel.writeInt(audioStreamType);
2746 
2747         if (audioAttributes != null) {
2748             parcel.writeInt(1);
2749             audioAttributes.writeToParcel(parcel, 0);
2750         } else {
2751             parcel.writeInt(0);
2752         }
2753 
2754         parcel.writeLongArray(vibrate);
2755         parcel.writeInt(ledARGB);
2756         parcel.writeInt(ledOnMS);
2757         parcel.writeInt(ledOffMS);
2758         parcel.writeInt(iconLevel);
2759 
2760         if (fullScreenIntent != null) {
2761             parcel.writeInt(1);
2762             fullScreenIntent.writeToParcel(parcel, 0);
2763         } else {
2764             parcel.writeInt(0);
2765         }
2766 
2767         parcel.writeInt(priority);
2768 
2769         parcel.writeString8(category);
2770 
2771         parcel.writeString8(mGroupKey);
2772 
2773         parcel.writeString8(mSortKey);
2774 
2775         parcel.writeBundle(extras); // null ok
2776 
2777         parcel.writeTypedArray(actions, 0); // null ok
2778 
2779         if (bigContentView != null) {
2780             parcel.writeInt(1);
2781             bigContentView.writeToParcel(parcel, 0);
2782         } else {
2783             parcel.writeInt(0);
2784         }
2785 
2786         if (headsUpContentView != null) {
2787             parcel.writeInt(1);
2788             headsUpContentView.writeToParcel(parcel, 0);
2789         } else {
2790             parcel.writeInt(0);
2791         }
2792 
2793         parcel.writeInt(visibility);
2794 
2795         if (publicVersion != null) {
2796             parcel.writeInt(1);
2797             publicVersion.writeToParcel(parcel, 0);
2798         } else {
2799             parcel.writeInt(0);
2800         }
2801 
2802         parcel.writeInt(color);
2803 
2804         if (mChannelId != null) {
2805             parcel.writeInt(1);
2806             parcel.writeString8(mChannelId);
2807         } else {
2808             parcel.writeInt(0);
2809         }
2810         parcel.writeLong(mTimeout);
2811 
2812         if (mShortcutId != null) {
2813             parcel.writeInt(1);
2814             parcel.writeString8(mShortcutId);
2815         } else {
2816             parcel.writeInt(0);
2817         }
2818 
2819         if (mLocusId != null) {
2820             parcel.writeInt(1);
2821             mLocusId.writeToParcel(parcel, 0);
2822         } else {
2823             parcel.writeInt(0);
2824         }
2825 
2826         parcel.writeInt(mBadgeIcon);
2827 
2828         if (mSettingsText != null) {
2829             parcel.writeInt(1);
2830             TextUtils.writeToParcel(mSettingsText, parcel, flags);
2831         } else {
2832             parcel.writeInt(0);
2833         }
2834 
2835         parcel.writeInt(mGroupAlertBehavior);
2836 
2837         if (mBubbleMetadata != null) {
2838             parcel.writeInt(1);
2839             mBubbleMetadata.writeToParcel(parcel, 0);
2840         } else {
2841             parcel.writeInt(0);
2842         }
2843 
2844         parcel.writeBoolean(mAllowSystemGeneratedContextualActions);
2845 
2846         // mUsesStandardHeader is not written because it should be recomputed in listeners
2847     }
2848 
2849     /**
2850      * Parcelable.Creator that instantiates Notification objects
2851      */
2852     public static final @android.annotation.NonNull Parcelable.Creator<Notification> CREATOR
2853             = new Parcelable.Creator<Notification>()
2854     {
2855         public Notification createFromParcel(Parcel parcel)
2856         {
2857             return new Notification(parcel);
2858         }
2859 
2860         public Notification[] newArray(int size)
2861         {
2862             return new Notification[size];
2863         }
2864     };
2865 
2866     /**
2867      * @hide
2868      */
areActionsVisiblyDifferent(Notification first, Notification second)2869     public static boolean areActionsVisiblyDifferent(Notification first, Notification second) {
2870         Notification.Action[] firstAs = first.actions;
2871         Notification.Action[] secondAs = second.actions;
2872         if (firstAs == null && secondAs != null || firstAs != null && secondAs == null) {
2873             return true;
2874         }
2875         if (firstAs != null && secondAs != null) {
2876             if (firstAs.length != secondAs.length) {
2877                 return true;
2878             }
2879             for (int i = 0; i < firstAs.length; i++) {
2880                 if (!Objects.equals(String.valueOf(firstAs[i].title),
2881                         String.valueOf(secondAs[i].title))) {
2882                     return true;
2883                 }
2884                 RemoteInput[] firstRs = firstAs[i].getRemoteInputs();
2885                 RemoteInput[] secondRs = secondAs[i].getRemoteInputs();
2886                 if (firstRs == null) {
2887                     firstRs = new RemoteInput[0];
2888                 }
2889                 if (secondRs == null) {
2890                     secondRs = new RemoteInput[0];
2891                 }
2892                 if (firstRs.length != secondRs.length) {
2893                     return true;
2894                 }
2895                 for (int j = 0; j < firstRs.length; j++) {
2896                     if (!Objects.equals(String.valueOf(firstRs[j].getLabel()),
2897                             String.valueOf(secondRs[j].getLabel()))) {
2898                         return true;
2899                     }
2900                 }
2901             }
2902         }
2903         return false;
2904     }
2905 
2906     /**
2907      * @hide
2908      */
areStyledNotificationsVisiblyDifferent(Builder first, Builder second)2909     public static boolean areStyledNotificationsVisiblyDifferent(Builder first, Builder second) {
2910         if (first.getStyle() == null) {
2911             return second.getStyle() != null;
2912         }
2913         if (second.getStyle() == null) {
2914             return true;
2915         }
2916         return first.getStyle().areNotificationsVisiblyDifferent(second.getStyle());
2917     }
2918 
2919     /**
2920      * @hide
2921      */
areRemoteViewsChanged(Builder first, Builder second)2922     public static boolean areRemoteViewsChanged(Builder first, Builder second) {
2923         if (!Objects.equals(first.usesStandardHeader(), second.usesStandardHeader())) {
2924             return true;
2925         }
2926 
2927         if (areRemoteViewsChanged(first.mN.contentView, second.mN.contentView)) {
2928             return true;
2929         }
2930         if (areRemoteViewsChanged(first.mN.bigContentView, second.mN.bigContentView)) {
2931             return true;
2932         }
2933         if (areRemoteViewsChanged(first.mN.headsUpContentView, second.mN.headsUpContentView)) {
2934             return true;
2935         }
2936 
2937         return false;
2938     }
2939 
areRemoteViewsChanged(RemoteViews first, RemoteViews second)2940     private static boolean areRemoteViewsChanged(RemoteViews first, RemoteViews second) {
2941         if (first == null && second == null) {
2942             return false;
2943         }
2944         if (first == null && second != null || first != null && second == null) {
2945             return true;
2946         }
2947 
2948         if (!Objects.equals(first.getLayoutId(), second.getLayoutId())) {
2949             return true;
2950         }
2951 
2952         if (!Objects.equals(first.getSequenceNumber(), second.getSequenceNumber())) {
2953             return true;
2954         }
2955 
2956         return false;
2957     }
2958 
2959     /**
2960      * Parcelling creates multiple copies of objects in {@code extras}. Fix them.
2961      * <p>
2962      * For backwards compatibility {@code extras} holds some references to "real" member data such
2963      * as {@link getLargeIcon()} which is mirrored by {@link #EXTRA_LARGE_ICON}. This is mostly
2964      * fine as long as the object stays in one process.
2965      * <p>
2966      * However, once the notification goes into a parcel each reference gets marshalled separately,
2967      * wasting memory. Especially with large images on Auto and TV, this is worth fixing.
2968      */
fixDuplicateExtras()2969     private void fixDuplicateExtras() {
2970         if (extras != null) {
2971             fixDuplicateExtra(mLargeIcon, EXTRA_LARGE_ICON);
2972         }
2973     }
2974 
2975     /**
2976      * If we find an extra that's exactly the same as one of the "real" fields but refers to a
2977      * separate object, replace it with the field's version to avoid holding duplicate copies.
2978      */
fixDuplicateExtra(@ullable Parcelable original, @NonNull String extraName)2979     private void fixDuplicateExtra(@Nullable Parcelable original, @NonNull String extraName) {
2980         if (original != null && extras.getParcelable(extraName) != null) {
2981             extras.putParcelable(extraName, original);
2982         }
2983     }
2984 
2985     /**
2986      * Sets the {@link #contentView} field to be a view with the standard "Latest Event"
2987      * layout.
2988      *
2989      * <p>Uses the {@link #icon} and {@link #when} fields to set the icon and time fields
2990      * in the view.</p>
2991      * @param context       The context for your application / activity.
2992      * @param contentTitle The title that goes in the expanded entry.
2993      * @param contentText  The text that goes in the expanded entry.
2994      * @param contentIntent The intent to launch when the user clicks the expanded notification.
2995      * If this is an activity, it must include the
2996      * {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK} flag, which requires
2997      * that you take care of task management as described in the
2998      * <a href="{@docRoot}guide/topics/fundamentals/tasks-and-back-stack.html">Tasks and Back
2999      * Stack</a> document.
3000      *
3001      * @deprecated Use {@link Builder} instead.
3002      * @removed
3003      */
3004     @Deprecated
setLatestEventInfo(Context context, CharSequence contentTitle, CharSequence contentText, PendingIntent contentIntent)3005     public void setLatestEventInfo(Context context,
3006             CharSequence contentTitle, CharSequence contentText, PendingIntent contentIntent) {
3007         if (context.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1){
3008             Log.e(TAG, "setLatestEventInfo() is deprecated and you should feel deprecated.",
3009                     new Throwable());
3010         }
3011 
3012         if (context.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N) {
3013             extras.putBoolean(EXTRA_SHOW_WHEN, true);
3014         }
3015 
3016         // ensure that any information already set directly is preserved
3017         final Notification.Builder builder = new Notification.Builder(context, this);
3018 
3019         // now apply the latestEventInfo fields
3020         if (contentTitle != null) {
3021             builder.setContentTitle(contentTitle);
3022         }
3023         if (contentText != null) {
3024             builder.setContentText(contentText);
3025         }
3026         builder.setContentIntent(contentIntent);
3027 
3028         builder.build(); // callers expect this notification to be ready to use
3029     }
3030 
3031     /**
3032      * @hide
3033      */
addFieldsFromContext(Context context, Notification notification)3034     public static void addFieldsFromContext(Context context, Notification notification) {
3035         addFieldsFromContext(context.getApplicationInfo(), notification);
3036     }
3037 
3038     /**
3039      * @hide
3040      */
addFieldsFromContext(ApplicationInfo ai, Notification notification)3041     public static void addFieldsFromContext(ApplicationInfo ai, Notification notification) {
3042         notification.extras.putParcelable(EXTRA_BUILDER_APPLICATION_INFO, ai);
3043     }
3044 
3045     /**
3046      * @hide
3047      */
dumpDebug(ProtoOutputStream proto, long fieldId)3048     public void dumpDebug(ProtoOutputStream proto, long fieldId) {
3049         long token = proto.start(fieldId);
3050         proto.write(NotificationProto.CHANNEL_ID, getChannelId());
3051         proto.write(NotificationProto.HAS_TICKER_TEXT, this.tickerText != null);
3052         proto.write(NotificationProto.FLAGS, this.flags);
3053         proto.write(NotificationProto.COLOR, this.color);
3054         proto.write(NotificationProto.CATEGORY, this.category);
3055         proto.write(NotificationProto.GROUP_KEY, this.mGroupKey);
3056         proto.write(NotificationProto.SORT_KEY, this.mSortKey);
3057         if (this.actions != null) {
3058             proto.write(NotificationProto.ACTION_LENGTH, this.actions.length);
3059         }
3060         if (this.visibility >= VISIBILITY_SECRET && this.visibility <= VISIBILITY_PUBLIC) {
3061             proto.write(NotificationProto.VISIBILITY, this.visibility);
3062         }
3063         if (publicVersion != null) {
3064             publicVersion.dumpDebug(proto, NotificationProto.PUBLIC_VERSION);
3065         }
3066         proto.end(token);
3067     }
3068 
3069     @Override
toString()3070     public String toString() {
3071         StringBuilder sb = new StringBuilder();
3072         sb.append("Notification(channel=");
3073         sb.append(getChannelId());
3074         sb.append(" shortcut=");
3075         sb.append(getShortcutId());
3076         sb.append(" contentView=");
3077         if (contentView != null) {
3078             sb.append(contentView.getPackage());
3079             sb.append("/0x");
3080             sb.append(Integer.toHexString(contentView.getLayoutId()));
3081         } else {
3082             sb.append("null");
3083         }
3084         sb.append(" vibrate=");
3085         if ((this.defaults & DEFAULT_VIBRATE) != 0) {
3086             sb.append("default");
3087         } else if (this.vibrate != null) {
3088             int N = this.vibrate.length-1;
3089             sb.append("[");
3090             for (int i=0; i<N; i++) {
3091                 sb.append(this.vibrate[i]);
3092                 sb.append(',');
3093             }
3094             if (N != -1) {
3095                 sb.append(this.vibrate[N]);
3096             }
3097             sb.append("]");
3098         } else {
3099             sb.append("null");
3100         }
3101         sb.append(" sound=");
3102         if ((this.defaults & DEFAULT_SOUND) != 0) {
3103             sb.append("default");
3104         } else if (this.sound != null) {
3105             sb.append(this.sound.toString());
3106         } else {
3107             sb.append("null");
3108         }
3109         if (this.tickerText != null) {
3110             sb.append(" tick");
3111         }
3112         sb.append(" defaults=0x");
3113         sb.append(Integer.toHexString(this.defaults));
3114         sb.append(" flags=0x");
3115         sb.append(Integer.toHexString(this.flags));
3116         sb.append(String.format(" color=0x%08x", this.color));
3117         if (this.category != null) {
3118             sb.append(" category=");
3119             sb.append(this.category);
3120         }
3121         if (this.mGroupKey != null) {
3122             sb.append(" groupKey=");
3123             sb.append(this.mGroupKey);
3124         }
3125         if (this.mSortKey != null) {
3126             sb.append(" sortKey=");
3127             sb.append(this.mSortKey);
3128         }
3129         if (actions != null) {
3130             sb.append(" actions=");
3131             sb.append(actions.length);
3132         }
3133         sb.append(" vis=");
3134         sb.append(visibilityToString(this.visibility));
3135         if (this.publicVersion != null) {
3136             sb.append(" publicVersion=");
3137             sb.append(publicVersion.toString());
3138         }
3139         if (this.mLocusId != null) {
3140             sb.append(" locusId=");
3141             sb.append(this.mLocusId); // LocusId.toString() is PII safe.
3142         }
3143         sb.append(")");
3144         return sb.toString();
3145     }
3146 
3147     /**
3148      * {@hide}
3149      */
visibilityToString(int vis)3150     public static String visibilityToString(int vis) {
3151         switch (vis) {
3152             case VISIBILITY_PRIVATE:
3153                 return "PRIVATE";
3154             case VISIBILITY_PUBLIC:
3155                 return "PUBLIC";
3156             case VISIBILITY_SECRET:
3157                 return "SECRET";
3158             default:
3159                 return "UNKNOWN(" + String.valueOf(vis) + ")";
3160         }
3161     }
3162 
3163     /**
3164      * {@hide}
3165      */
priorityToString(@riority int pri)3166     public static String priorityToString(@Priority int pri) {
3167         switch (pri) {
3168             case PRIORITY_MIN:
3169                 return "MIN";
3170             case PRIORITY_LOW:
3171                 return "LOW";
3172             case PRIORITY_DEFAULT:
3173                 return "DEFAULT";
3174             case PRIORITY_HIGH:
3175                 return "HIGH";
3176             case PRIORITY_MAX:
3177                 return "MAX";
3178             default:
3179                 return "UNKNOWN(" + String.valueOf(pri) + ")";
3180         }
3181     }
3182 
3183     /**
3184      * @hide
3185      */
hasCompletedProgress()3186     public boolean hasCompletedProgress() {
3187         // not a progress notification; can't be complete
3188         if (!extras.containsKey(EXTRA_PROGRESS)
3189                 || !extras.containsKey(EXTRA_PROGRESS_MAX)) {
3190             return false;
3191         }
3192         // many apps use max 0 for 'indeterminate'; not complete
3193         if (extras.getInt(EXTRA_PROGRESS_MAX) == 0) {
3194             return false;
3195         }
3196         return extras.getInt(EXTRA_PROGRESS) == extras.getInt(EXTRA_PROGRESS_MAX);
3197     }
3198 
3199     /** @removed */
3200     @Deprecated
getChannel()3201     public String getChannel() {
3202         return mChannelId;
3203     }
3204 
3205     /**
3206      * Returns the id of the channel this notification posts to.
3207      */
getChannelId()3208     public String getChannelId() {
3209         return mChannelId;
3210     }
3211 
3212     /** @removed */
3213     @Deprecated
getTimeout()3214     public long getTimeout() {
3215         return mTimeout;
3216     }
3217 
3218     /**
3219      * Returns the duration from posting after which this notification should be canceled by the
3220      * system, if it's not canceled already.
3221      */
getTimeoutAfter()3222     public long getTimeoutAfter() {
3223         return mTimeout;
3224     }
3225 
3226     /**
3227      * Returns what icon should be shown for this notification if it is being displayed in a
3228      * Launcher that supports badging. Will be one of {@link #BADGE_ICON_NONE},
3229      * {@link #BADGE_ICON_SMALL}, or {@link #BADGE_ICON_LARGE}.
3230      */
getBadgeIconType()3231     public int getBadgeIconType() {
3232         return mBadgeIcon;
3233     }
3234 
3235     /**
3236      * Returns the {@link ShortcutInfo#getId() id} that this notification supersedes, if any.
3237      *
3238      * <p>Used by some Launchers that display notification content to hide shortcuts that duplicate
3239      * notifications.
3240      */
getShortcutId()3241     public String getShortcutId() {
3242         return mShortcutId;
3243     }
3244 
3245     /**
3246      * Gets the {@link LocusId} associated with this notification.
3247      *
3248      * <p>Used by the device's intelligence services to correlate objects (such as
3249      * {@link ShortcutInfo} and {@link ContentCaptureContext}) that are correlated.
3250      */
3251     @Nullable
getLocusId()3252     public LocusId getLocusId() {
3253         return mLocusId;
3254     }
3255 
3256     /**
3257      * Returns the settings text provided to {@link Builder#setSettingsText(CharSequence)}.
3258      */
getSettingsText()3259     public CharSequence getSettingsText() {
3260         return mSettingsText;
3261     }
3262 
3263     /**
3264      * Returns which type of notifications in a group are responsible for audibly alerting the
3265      * user. See {@link #GROUP_ALERT_ALL}, {@link #GROUP_ALERT_CHILDREN},
3266      * {@link #GROUP_ALERT_SUMMARY}.
3267      */
getGroupAlertBehavior()3268     public @GroupAlertBehavior int getGroupAlertBehavior() {
3269         return mGroupAlertBehavior;
3270     }
3271 
3272     /**
3273      * Returns the bubble metadata that will be used to display app content in a floating window
3274      * over the existing foreground activity.
3275      */
3276     @Nullable
getBubbleMetadata()3277     public BubbleMetadata getBubbleMetadata() {
3278         return mBubbleMetadata;
3279     }
3280 
3281     /**
3282      * Sets the {@link BubbleMetadata} for this notification.
3283      * @hide
3284      */
setBubbleMetadata(BubbleMetadata data)3285     public void setBubbleMetadata(BubbleMetadata data) {
3286         mBubbleMetadata = data;
3287     }
3288 
3289     /**
3290      * Returns whether the platform is allowed (by the app developer) to generate contextual actions
3291      * for this notification.
3292      */
getAllowSystemGeneratedContextualActions()3293     public boolean getAllowSystemGeneratedContextualActions() {
3294         return mAllowSystemGeneratedContextualActions;
3295     }
3296 
3297     /**
3298      * The small icon representing this notification in the status bar and content view.
3299      *
3300      * @return the small icon representing this notification.
3301      *
3302      * @see Builder#getSmallIcon()
3303      * @see Builder#setSmallIcon(Icon)
3304      */
getSmallIcon()3305     public Icon getSmallIcon() {
3306         return mSmallIcon;
3307     }
3308 
3309     /**
3310      * Used when notifying to clean up legacy small icons.
3311      * @hide
3312      */
3313     @UnsupportedAppUsage
setSmallIcon(Icon icon)3314     public void setSmallIcon(Icon icon) {
3315         mSmallIcon = icon;
3316     }
3317 
3318     /**
3319      * The large icon shown in this notification's content view.
3320      * @see Builder#getLargeIcon()
3321      * @see Builder#setLargeIcon(Icon)
3322      */
getLargeIcon()3323     public Icon getLargeIcon() {
3324         return mLargeIcon;
3325     }
3326 
3327     /**
3328      * @hide
3329      */
3330     @UnsupportedAppUsage
isGroupSummary()3331     public boolean isGroupSummary() {
3332         return mGroupKey != null && (flags & FLAG_GROUP_SUMMARY) != 0;
3333     }
3334 
3335     /**
3336      * @hide
3337      */
3338     @UnsupportedAppUsage
isGroupChild()3339     public boolean isGroupChild() {
3340         return mGroupKey != null && (flags & FLAG_GROUP_SUMMARY) == 0;
3341     }
3342 
3343     /**
3344      * @hide
3345      */
suppressAlertingDueToGrouping()3346     public boolean suppressAlertingDueToGrouping() {
3347         if (isGroupSummary()
3348                 && getGroupAlertBehavior() == Notification.GROUP_ALERT_CHILDREN) {
3349             return true;
3350         } else if (isGroupChild()
3351                 && getGroupAlertBehavior() == Notification.GROUP_ALERT_SUMMARY) {
3352             return true;
3353         }
3354         return false;
3355     }
3356 
3357 
3358     /**
3359      * Finds and returns a remote input and its corresponding action.
3360      *
3361      * @param requiresFreeform requires the remoteinput to allow freeform or not.
3362      * @return the result pair, {@code null} if no result is found.
3363      */
3364     @Nullable
findRemoteInputActionPair(boolean requiresFreeform)3365     public Pair<RemoteInput, Action> findRemoteInputActionPair(boolean requiresFreeform) {
3366         if (actions == null) {
3367             return null;
3368         }
3369         for (Notification.Action action : actions) {
3370             if (action.getRemoteInputs() == null) {
3371                 continue;
3372             }
3373             RemoteInput resultRemoteInput = null;
3374             for (RemoteInput remoteInput : action.getRemoteInputs()) {
3375                 if (remoteInput.getAllowFreeFormInput() || !requiresFreeform) {
3376                     resultRemoteInput = remoteInput;
3377                 }
3378             }
3379             if (resultRemoteInput != null) {
3380                 return Pair.create(resultRemoteInput, action);
3381             }
3382         }
3383         return null;
3384     }
3385 
3386     /**
3387      * Returns the actions that are contextual (that is, suggested because of the content of the
3388      * notification) out of the actions in this notification.
3389      */
getContextualActions()3390     public @NonNull List<Notification.Action> getContextualActions() {
3391         if (actions == null) return Collections.emptyList();
3392 
3393         List<Notification.Action> contextualActions = new ArrayList<>();
3394         for (Notification.Action action : actions) {
3395             if (action.isContextual()) {
3396                 contextualActions.add(action);
3397             }
3398         }
3399         return contextualActions;
3400     }
3401 
3402     /**
3403      * Builder class for {@link Notification} objects.
3404      *
3405      * Provides a convenient way to set the various fields of a {@link Notification} and generate
3406      * content views using the platform's notification layout template. If your app supports
3407      * versions of Android as old as API level 4, you can instead use
3408      * {@link android.support.v4.app.NotificationCompat.Builder NotificationCompat.Builder},
3409      * available in the <a href="{@docRoot}tools/extras/support-library.html">Android Support
3410      * library</a>.
3411      *
3412      * <p>Example:
3413      *
3414      * <pre class="prettyprint">
3415      * Notification noti = new Notification.Builder(mContext)
3416      *         .setContentTitle(&quot;New mail from &quot; + sender.toString())
3417      *         .setContentText(subject)
3418      *         .setSmallIcon(R.drawable.new_mail)
3419      *         .setLargeIcon(aBitmap)
3420      *         .build();
3421      * </pre>
3422      */
3423     public static class Builder {
3424         /**
3425          * @hide
3426          */
3427         public static final String EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT =
3428                 "android.rebuild.contentViewActionCount";
3429         /**
3430          * @hide
3431          */
3432         public static final String EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT
3433                 = "android.rebuild.bigViewActionCount";
3434         /**
3435          * @hide
3436          */
3437         public static final String EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT
3438                 = "android.rebuild.hudViewActionCount";
3439 
3440         private static final boolean USE_ONLY_TITLE_IN_LOW_PRIORITY_SUMMARY =
3441                 SystemProperties.getBoolean("notifications.only_title", true);
3442 
3443         /**
3444          * The lightness difference that has to be added to the primary text color to obtain the
3445          * secondary text color when the background is light.
3446          */
3447         private static final int LIGHTNESS_TEXT_DIFFERENCE_LIGHT = 20;
3448 
3449         /**
3450          * The lightness difference that has to be added to the primary text color to obtain the
3451          * secondary text color when the background is dark.
3452          * A bit less then the above value, since it looks better on dark backgrounds.
3453          */
3454         private static final int LIGHTNESS_TEXT_DIFFERENCE_DARK = -10;
3455 
3456         private Context mContext;
3457         private Notification mN;
3458         private Bundle mUserExtras = new Bundle();
3459         private Style mStyle;
3460         @UnsupportedAppUsage
3461         private ArrayList<Action> mActions = new ArrayList<Action>(MAX_ACTION_BUTTONS);
3462         private ArrayList<Person> mPersonList = new ArrayList<>();
3463         private ContrastColorUtil mColorUtil;
3464         private boolean mIsLegacy;
3465         private boolean mIsLegacyInitialized;
3466 
3467         /**
3468          * Caches a contrast-enhanced version of {@link #mCachedContrastColorIsFor}.
3469          */
3470         private int mCachedContrastColor = COLOR_INVALID;
3471         private int mCachedContrastColorIsFor = COLOR_INVALID;
3472 
3473         /**
3474          * A neutral color color that can be used for icons.
3475          */
3476         private int mNeutralColor = COLOR_INVALID;
3477 
3478         /**
3479          * Caches an instance of StandardTemplateParams. Note that this may have been used before,
3480          * so make sure to call {@link StandardTemplateParams#reset()} before using it.
3481          */
3482         StandardTemplateParams mParams = new StandardTemplateParams();
3483         private int mTextColorsAreForBackground = COLOR_INVALID;
3484         private int mPrimaryTextColor = COLOR_INVALID;
3485         private int mSecondaryTextColor = COLOR_INVALID;
3486         private int mBackgroundColor = COLOR_INVALID;
3487         private int mForegroundColor = COLOR_INVALID;
3488         /**
3489          * A temporary location where actions are stored. If != null the view originally has action
3490          * but doesn't have any for this inflation.
3491          */
3492         private ArrayList<Action> mOriginalActions;
3493         private boolean mRebuildStyledRemoteViews;
3494 
3495         private boolean mTintActionButtons;
3496         private boolean mInNightMode;
3497 
3498         /**
3499          * Constructs a new Builder with the defaults:
3500          *
3501          * @param context
3502          *            A {@link Context} that will be used by the Builder to construct the
3503          *            RemoteViews. The Context will not be held past the lifetime of this Builder
3504          *            object.
3505          * @param channelId
3506          *            The constructed Notification will be posted on this
3507          *            {@link NotificationChannel}. To use a NotificationChannel, it must first be
3508          *            created using {@link NotificationManager#createNotificationChannel}.
3509          */
Builder(Context context, String channelId)3510         public Builder(Context context, String channelId) {
3511             this(context, (Notification) null);
3512             mN.mChannelId = channelId;
3513         }
3514 
3515         /**
3516          * @deprecated use {@link #Builder(Context, String)}
3517          * instead. All posted Notifications must specify a NotificationChannel Id.
3518          */
3519         @Deprecated
Builder(Context context)3520         public Builder(Context context) {
3521             this(context, (Notification) null);
3522         }
3523 
3524         /**
3525          * @hide
3526          */
Builder(Context context, Notification toAdopt)3527         public Builder(Context context, Notification toAdopt) {
3528             mContext = context;
3529             Resources res = mContext.getResources();
3530             mTintActionButtons = res.getBoolean(R.bool.config_tintNotificationActionButtons);
3531 
3532             if (res.getBoolean(R.bool.config_enableNightMode)) {
3533                 Configuration currentConfig = res.getConfiguration();
3534                 mInNightMode = (currentConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK)
3535                         == Configuration.UI_MODE_NIGHT_YES;
3536             }
3537 
3538             if (toAdopt == null) {
3539                 mN = new Notification();
3540                 if (context.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N) {
3541                     mN.extras.putBoolean(EXTRA_SHOW_WHEN, true);
3542                 }
3543                 mN.priority = PRIORITY_DEFAULT;
3544                 mN.visibility = VISIBILITY_PRIVATE;
3545             } else {
3546                 mN = toAdopt;
3547                 if (mN.actions != null) {
3548                     Collections.addAll(mActions, mN.actions);
3549                 }
3550 
3551                 if (mN.extras.containsKey(EXTRA_PEOPLE_LIST)) {
3552                     ArrayList<Person> people = mN.extras.getParcelableArrayList(EXTRA_PEOPLE_LIST);
3553                     mPersonList.addAll(people);
3554                 }
3555 
3556                 if (mN.getSmallIcon() == null && mN.icon != 0) {
3557                     setSmallIcon(mN.icon);
3558                 }
3559 
3560                 if (mN.getLargeIcon() == null && mN.largeIcon != null) {
3561                     setLargeIcon(mN.largeIcon);
3562                 }
3563 
3564                 String templateClass = mN.extras.getString(EXTRA_TEMPLATE);
3565                 if (!TextUtils.isEmpty(templateClass)) {
3566                     final Class<? extends Style> styleClass
3567                             = getNotificationStyleClass(templateClass);
3568                     if (styleClass == null) {
3569                         Log.d(TAG, "Unknown style class: " + templateClass);
3570                     } else {
3571                         try {
3572                             final Constructor<? extends Style> ctor =
3573                                     styleClass.getDeclaredConstructor();
3574                             ctor.setAccessible(true);
3575                             final Style style = ctor.newInstance();
3576                             style.restoreFromExtras(mN.extras);
3577 
3578                             if (style != null) {
3579                                 setStyle(style);
3580                             }
3581                         } catch (Throwable t) {
3582                             Log.e(TAG, "Could not create Style", t);
3583                         }
3584                     }
3585                 }
3586             }
3587         }
3588 
getColorUtil()3589         private ContrastColorUtil getColorUtil() {
3590             if (mColorUtil == null) {
3591                 mColorUtil = ContrastColorUtil.getInstance(mContext);
3592             }
3593             return mColorUtil;
3594         }
3595 
3596         /**
3597          * From Android 11, messaging notifications (those that use {@link MessagingStyle}) that
3598          * use this method to link to a published long-lived sharing shortcut may appear in a
3599          * dedicated Conversation section of the shade and may show configuration options that
3600          * are unique to conversations. This behavior should be reserved for person to person(s)
3601          * conversations where there is a likely social obligation for an individual to respond.
3602          * <p>
3603          * For example, the following are some examples of notifications that belong in the
3604          * conversation space:
3605          * <ul>
3606          * <li>1:1 conversations between two individuals</li>
3607          * <li>Group conversations between individuals where everyone can contribute</li>
3608          * </ul>
3609          * And the following are some examples of notifications that do not belong in the
3610          * conversation space:
3611          * <ul>
3612          * <li>Advertisements from a bot (even if personal and contextualized)</li>
3613          * <li>Engagement notifications from a bot</li>
3614          * <li>Directional conversations where there is an active speaker and many passive
3615          * individuals</li>
3616          * <li>Stream / posting updates from other individuals</li>
3617          * <li>Email, document comments, or other conversation types that are not real-time</li>
3618          * </ul>
3619          * </p>
3620          *
3621          * <p>
3622          * Additionally, this method can be used for all types of notifications to mark this
3623          * notification as duplicative of a Launcher shortcut. Launchers that show badges or
3624          * notification content may then suppress the shortcut in favor of the content of this
3625          * notification.
3626          * <p>
3627          * If this notification has {@link BubbleMetadata} attached that was created with
3628          * a shortcutId a check will be performed to ensure the shortcutId supplied to bubble
3629          * metadata matches the shortcutId set here, if one was set. If the shortcutId's were
3630          * specified but do not match, an exception is thrown.
3631          *
3632          * @param shortcutId the {@link ShortcutInfo#getId() id} of the shortcut this notification
3633          *                   is linked to
3634          *
3635          * @see BubbleMetadata.Builder#Builder(String)
3636          */
3637         @NonNull
setShortcutId(String shortcutId)3638         public Builder setShortcutId(String shortcutId) {
3639             mN.mShortcutId = shortcutId;
3640             return this;
3641         }
3642 
3643         /**
3644          * Sets the {@link LocusId} associated with this notification.
3645          *
3646          * <p>This method should be called when the {@link LocusId} is used in other places (such
3647          * as {@link ShortcutInfo} and {@link ContentCaptureContext}) so the device's intelligence
3648          * services can correlate them.
3649          */
3650         @NonNull
setLocusId(@ullable LocusId locusId)3651         public Builder setLocusId(@Nullable LocusId locusId) {
3652             mN.mLocusId = locusId;
3653             return this;
3654         }
3655 
3656         /**
3657          * Sets which icon to display as a badge for this notification.
3658          *
3659          * Must be one of {@link #BADGE_ICON_NONE}, {@link #BADGE_ICON_SMALL},
3660          * {@link #BADGE_ICON_LARGE}.
3661          *
3662          * Note: This value might be ignored, for launchers that don't support badge icons.
3663          */
3664         @NonNull
setBadgeIconType(int icon)3665         public Builder setBadgeIconType(int icon) {
3666             mN.mBadgeIcon = icon;
3667             return this;
3668         }
3669 
3670         /**
3671          * Sets the group alert behavior for this notification. Use this method to mute this
3672          * notification if alerts for this notification's group should be handled by a different
3673          * notification. This is only applicable for notifications that belong to a
3674          * {@link #setGroup(String) group}. This must be called on all notifications you want to
3675          * mute. For example, if you want only the summary of your group to make noise, all
3676          * children in the group should have the group alert behavior {@link #GROUP_ALERT_SUMMARY}.
3677          *
3678          * <p> The default value is {@link #GROUP_ALERT_ALL}.</p>
3679          */
3680         @NonNull
setGroupAlertBehavior(@roupAlertBehavior int groupAlertBehavior)3681         public Builder setGroupAlertBehavior(@GroupAlertBehavior int groupAlertBehavior) {
3682             mN.mGroupAlertBehavior = groupAlertBehavior;
3683             return this;
3684         }
3685 
3686         /**
3687          * Sets the {@link BubbleMetadata} that will be used to display app content in a floating
3688          * window over the existing foreground activity.
3689          *
3690          * <p>This data will be ignored unless the notification is posted to a channel that
3691          * allows {@link NotificationChannel#canBubble() bubbles}.</p>
3692          *
3693          * <p>Notifications allowed to bubble that have valid bubble metadata will display in
3694          * collapsed state outside of the notification shade on unlocked devices. When a user
3695          * interacts with the collapsed state, the bubble intent will be invoked and displayed.</p>
3696          */
3697         @NonNull
setBubbleMetadata(@ullable BubbleMetadata data)3698         public Builder setBubbleMetadata(@Nullable BubbleMetadata data) {
3699             mN.mBubbleMetadata = data;
3700             return this;
3701         }
3702 
3703         /** @removed */
3704         @Deprecated
setChannel(String channelId)3705         public Builder setChannel(String channelId) {
3706             mN.mChannelId = channelId;
3707             return this;
3708         }
3709 
3710         /**
3711          * Specifies the channel the notification should be delivered on.
3712          */
3713         @NonNull
setChannelId(String channelId)3714         public Builder setChannelId(String channelId) {
3715             mN.mChannelId = channelId;
3716             return this;
3717         }
3718 
3719         /** @removed */
3720         @Deprecated
setTimeout(long durationMs)3721         public Builder setTimeout(long durationMs) {
3722             mN.mTimeout = durationMs;
3723             return this;
3724         }
3725 
3726         /**
3727          * Specifies a duration in milliseconds after which this notification should be canceled,
3728          * if it is not already canceled.
3729          */
3730         @NonNull
setTimeoutAfter(long durationMs)3731         public Builder setTimeoutAfter(long durationMs) {
3732             mN.mTimeout = durationMs;
3733             return this;
3734         }
3735 
3736         /**
3737          * Add a timestamp pertaining to the notification (usually the time the event occurred).
3738          *
3739          * For apps targeting {@link android.os.Build.VERSION_CODES#N} and above, this time is not
3740          * shown anymore by default and must be opted into by using
3741          * {@link android.app.Notification.Builder#setShowWhen(boolean)}
3742          *
3743          * @see Notification#when
3744          */
3745         @NonNull
setWhen(long when)3746         public Builder setWhen(long when) {
3747             mN.when = when;
3748             return this;
3749         }
3750 
3751         /**
3752          * Control whether the timestamp set with {@link #setWhen(long) setWhen} is shown
3753          * in the content view.
3754          * For apps targeting {@link android.os.Build.VERSION_CODES#N} and above, this defaults to
3755          * {@code false}. For earlier apps, the default is {@code true}.
3756          */
3757         @NonNull
setShowWhen(boolean show)3758         public Builder setShowWhen(boolean show) {
3759             mN.extras.putBoolean(EXTRA_SHOW_WHEN, show);
3760             return this;
3761         }
3762 
3763         /**
3764          * Show the {@link Notification#when} field as a stopwatch.
3765          *
3766          * Instead of presenting <code>when</code> as a timestamp, the notification will show an
3767          * automatically updating display of the minutes and seconds since <code>when</code>.
3768          *
3769          * Useful when showing an elapsed time (like an ongoing phone call).
3770          *
3771          * The counter can also be set to count down to <code>when</code> when using
3772          * {@link #setChronometerCountDown(boolean)}.
3773          *
3774          * @see android.widget.Chronometer
3775          * @see Notification#when
3776          * @see #setChronometerCountDown(boolean)
3777          */
3778         @NonNull
setUsesChronometer(boolean b)3779         public Builder setUsesChronometer(boolean b) {
3780             mN.extras.putBoolean(EXTRA_SHOW_CHRONOMETER, b);
3781             return this;
3782         }
3783 
3784         /**
3785          * Sets the Chronometer to count down instead of counting up.
3786          *
3787          * <p>This is only relevant if {@link #setUsesChronometer(boolean)} has been set to true.
3788          * If it isn't set the chronometer will count up.
3789          *
3790          * @see #setUsesChronometer(boolean)
3791          */
3792         @NonNull
setChronometerCountDown(boolean countDown)3793         public Builder setChronometerCountDown(boolean countDown) {
3794             mN.extras.putBoolean(EXTRA_CHRONOMETER_COUNT_DOWN, countDown);
3795             return this;
3796         }
3797 
3798         /**
3799          * Set the small icon resource, which will be used to represent the notification in the
3800          * status bar.
3801          *
3802 
3803          * The platform template for the expanded view will draw this icon in the left, unless a
3804          * {@link #setLargeIcon(Bitmap) large icon} has also been specified, in which case the small
3805          * icon will be moved to the right-hand side.
3806          *
3807 
3808          * @param icon
3809          *            A resource ID in the application's package of the drawable to use.
3810          * @see Notification#icon
3811          */
3812         @NonNull
setSmallIcon(@rawableRes int icon)3813         public Builder setSmallIcon(@DrawableRes int icon) {
3814             return setSmallIcon(icon != 0
3815                     ? Icon.createWithResource(mContext, icon)
3816                     : null);
3817         }
3818 
3819         /**
3820          * A variant of {@link #setSmallIcon(int) setSmallIcon(int)} that takes an additional
3821          * level parameter for when the icon is a {@link android.graphics.drawable.LevelListDrawable
3822          * LevelListDrawable}.
3823          *
3824          * @param icon A resource ID in the application's package of the drawable to use.
3825          * @param level The level to use for the icon.
3826          *
3827          * @see Notification#icon
3828          * @see Notification#iconLevel
3829          */
3830         @NonNull
setSmallIcon(@rawableRes int icon, int level)3831         public Builder setSmallIcon(@DrawableRes int icon, int level) {
3832             mN.iconLevel = level;
3833             return setSmallIcon(icon);
3834         }
3835 
3836         /**
3837          * Set the small icon, which will be used to represent the notification in the
3838          * status bar and content view (unless overridden there by a
3839          * {@link #setLargeIcon(Bitmap) large icon}).
3840          *
3841          * @param icon An Icon object to use.
3842          * @see Notification#icon
3843          */
3844         @NonNull
setSmallIcon(Icon icon)3845         public Builder setSmallIcon(Icon icon) {
3846             mN.setSmallIcon(icon);
3847             if (icon != null && icon.getType() == Icon.TYPE_RESOURCE) {
3848                 mN.icon = icon.getResId();
3849             }
3850             return this;
3851         }
3852 
3853         /**
3854          * Set the first line of text in the platform notification template.
3855          */
3856         @NonNull
setContentTitle(CharSequence title)3857         public Builder setContentTitle(CharSequence title) {
3858             mN.extras.putCharSequence(EXTRA_TITLE, safeCharSequence(title));
3859             return this;
3860         }
3861 
3862         /**
3863          * Set the second line of text in the platform notification template.
3864          */
3865         @NonNull
setContentText(CharSequence text)3866         public Builder setContentText(CharSequence text) {
3867             mN.extras.putCharSequence(EXTRA_TEXT, safeCharSequence(text));
3868             return this;
3869         }
3870 
3871         /**
3872          * This provides some additional information that is displayed in the notification. No
3873          * guarantees are given where exactly it is displayed.
3874          *
3875          * <p>This information should only be provided if it provides an essential
3876          * benefit to the understanding of the notification. The more text you provide the
3877          * less readable it becomes. For example, an email client should only provide the account
3878          * name here if more than one email account has been added.</p>
3879          *
3880          * <p>As of {@link android.os.Build.VERSION_CODES#N} this information is displayed in the
3881          * notification header area.
3882          *
3883          * On Android versions before {@link android.os.Build.VERSION_CODES#N}
3884          * this will be shown in the third line of text in the platform notification template.
3885          * You should not be using {@link #setProgress(int, int, boolean)} at the
3886          * same time on those versions; they occupy the same place.
3887          * </p>
3888          */
3889         @NonNull
setSubText(CharSequence text)3890         public Builder setSubText(CharSequence text) {
3891             mN.extras.putCharSequence(EXTRA_SUB_TEXT, safeCharSequence(text));
3892             return this;
3893         }
3894 
3895         /**
3896          * Provides text that will appear as a link to your application's settings.
3897          *
3898          * <p>This text does not appear within notification {@link Style templates} but may
3899          * appear when the user uses an affordance to learn more about the notification.
3900          * Additionally, this text will not appear unless you provide a valid link target by
3901          * handling {@link #INTENT_CATEGORY_NOTIFICATION_PREFERENCES}.
3902          *
3903          * <p>This text is meant to be concise description about what the user can customize
3904          * when they click on this link. The recommended maximum length is 40 characters.
3905          * @param text
3906          * @return
3907          */
3908         @NonNull
setSettingsText(CharSequence text)3909         public Builder setSettingsText(CharSequence text) {
3910             mN.mSettingsText = safeCharSequence(text);
3911             return this;
3912         }
3913 
3914         /**
3915          * Set the remote input history.
3916          *
3917          * This should be set to the most recent inputs that have been sent
3918          * through a {@link RemoteInput} of this Notification and cleared once the it is no
3919          * longer relevant (e.g. for chat notifications once the other party has responded).
3920          *
3921          * The most recent input must be stored at the 0 index, the second most recent at the
3922          * 1 index, etc. Note that the system will limit both how far back the inputs will be shown
3923          * and how much of each individual input is shown.
3924          *
3925          * <p>Note: The reply text will only be shown on notifications that have least one action
3926          * with a {@code RemoteInput}.</p>
3927          */
3928         @NonNull
setRemoteInputHistory(CharSequence[] text)3929         public Builder setRemoteInputHistory(CharSequence[] text) {
3930             if (text == null) {
3931                 mN.extras.putCharSequenceArray(EXTRA_REMOTE_INPUT_HISTORY, null);
3932             } else {
3933                 final int itemCount = Math.min(MAX_REPLY_HISTORY, text.length);
3934                 CharSequence[] safe = new CharSequence[itemCount];
3935                 RemoteInputHistoryItem[] items = new RemoteInputHistoryItem[itemCount];
3936                 for (int i = 0; i < itemCount; i++) {
3937                     safe[i] = safeCharSequence(text[i]);
3938                     items[i] = new RemoteInputHistoryItem(text[i]);
3939                 }
3940                 mN.extras.putCharSequenceArray(EXTRA_REMOTE_INPUT_HISTORY, safe);
3941 
3942                 // Also add these messages as structured history items.
3943                 mN.extras.putParcelableArray(EXTRA_REMOTE_INPUT_HISTORY_ITEMS, items);
3944             }
3945             return this;
3946         }
3947 
3948         /**
3949          * Set the remote input history, with support for embedding URIs and mime types for
3950          * images and other media.
3951          * @hide
3952          */
3953         @NonNull
setRemoteInputHistory(RemoteInputHistoryItem[] items)3954         public Builder setRemoteInputHistory(RemoteInputHistoryItem[] items) {
3955             if (items == null) {
3956                 mN.extras.putParcelableArray(EXTRA_REMOTE_INPUT_HISTORY_ITEMS, null);
3957             } else {
3958                 final int itemCount = Math.min(MAX_REPLY_HISTORY, items.length);
3959                 RemoteInputHistoryItem[] history = new RemoteInputHistoryItem[itemCount];
3960                 for (int i = 0; i < itemCount; i++) {
3961                     history[i] = items[i];
3962                 }
3963                 mN.extras.putParcelableArray(EXTRA_REMOTE_INPUT_HISTORY_ITEMS, history);
3964             }
3965             return this;
3966         }
3967 
3968         /**
3969          * Sets whether remote history entries view should have a spinner.
3970          * @hide
3971          */
3972         @NonNull
setShowRemoteInputSpinner(boolean showSpinner)3973         public Builder setShowRemoteInputSpinner(boolean showSpinner) {
3974             mN.extras.putBoolean(EXTRA_SHOW_REMOTE_INPUT_SPINNER, showSpinner);
3975             return this;
3976         }
3977 
3978         /**
3979          * Sets whether smart reply buttons should be hidden.
3980          * @hide
3981          */
3982         @NonNull
setHideSmartReplies(boolean hideSmartReplies)3983         public Builder setHideSmartReplies(boolean hideSmartReplies) {
3984             mN.extras.putBoolean(EXTRA_HIDE_SMART_REPLIES, hideSmartReplies);
3985             return this;
3986         }
3987 
3988         /**
3989          * Sets the number of items this notification represents. May be displayed as a badge count
3990          * for Launchers that support badging.
3991          */
3992         @NonNull
setNumber(int number)3993         public Builder setNumber(int number) {
3994             mN.number = number;
3995             return this;
3996         }
3997 
3998         /**
3999          * A small piece of additional information pertaining to this notification.
4000          *
4001          * The platform template will draw this on the last line of the notification, at the far
4002          * right (to the right of a smallIcon if it has been placed there).
4003          *
4004          * @deprecated use {@link #setSubText(CharSequence)} instead to set a text in the header.
4005          * For legacy apps targeting a version below {@link android.os.Build.VERSION_CODES#N} this
4006          * field will still show up, but the subtext will take precedence.
4007          */
4008         @Deprecated
setContentInfo(CharSequence info)4009         public Builder setContentInfo(CharSequence info) {
4010             mN.extras.putCharSequence(EXTRA_INFO_TEXT, safeCharSequence(info));
4011             return this;
4012         }
4013 
4014         /**
4015          * Set the progress this notification represents.
4016          *
4017          * The platform template will represent this using a {@link ProgressBar}.
4018          */
4019         @NonNull
setProgress(int max, int progress, boolean indeterminate)4020         public Builder setProgress(int max, int progress, boolean indeterminate) {
4021             mN.extras.putInt(EXTRA_PROGRESS, progress);
4022             mN.extras.putInt(EXTRA_PROGRESS_MAX, max);
4023             mN.extras.putBoolean(EXTRA_PROGRESS_INDETERMINATE, indeterminate);
4024             return this;
4025         }
4026 
4027         /**
4028          * Supply a custom RemoteViews to use instead of the platform template.
4029          *
4030          * Use {@link #setCustomContentView(RemoteViews)} instead.
4031          */
4032         @Deprecated
setContent(RemoteViews views)4033         public Builder setContent(RemoteViews views) {
4034             return setCustomContentView(views);
4035         }
4036 
4037         /**
4038          * Supply custom RemoteViews to use instead of the platform template.
4039          *
4040          * This will override the layout that would otherwise be constructed by this Builder
4041          * object.
4042          */
4043         @NonNull
setCustomContentView(RemoteViews contentView)4044         public Builder setCustomContentView(RemoteViews contentView) {
4045             mN.contentView = contentView;
4046             return this;
4047         }
4048 
4049         /**
4050          * Supply custom RemoteViews to use instead of the platform template in the expanded form.
4051          *
4052          * This will override the expanded layout that would otherwise be constructed by this
4053          * Builder object.
4054          */
4055         @NonNull
setCustomBigContentView(RemoteViews contentView)4056         public Builder setCustomBigContentView(RemoteViews contentView) {
4057             mN.bigContentView = contentView;
4058             return this;
4059         }
4060 
4061         /**
4062          * Supply custom RemoteViews to use instead of the platform template in the heads up dialog.
4063          *
4064          * This will override the heads-up layout that would otherwise be constructed by this
4065          * Builder object.
4066          */
4067         @NonNull
setCustomHeadsUpContentView(RemoteViews contentView)4068         public Builder setCustomHeadsUpContentView(RemoteViews contentView) {
4069             mN.headsUpContentView = contentView;
4070             return this;
4071         }
4072 
4073         /**
4074          * Supply a {@link PendingIntent} to be sent when the notification is clicked.
4075          *
4076          * As of {@link android.os.Build.VERSION_CODES#HONEYCOMB}, if this field is unset and you
4077          * have specified a custom RemoteViews with {@link #setContent(RemoteViews)}, you can use
4078          * {@link RemoteViews#setOnClickPendingIntent RemoteViews.setOnClickPendingIntent(int,PendingIntent)}
4079          * to assign PendingIntents to individual views in that custom layout (i.e., to create
4080          * clickable buttons inside the notification view).
4081          *
4082          * @see Notification#contentIntent Notification.contentIntent
4083          */
4084         @NonNull
setContentIntent(PendingIntent intent)4085         public Builder setContentIntent(PendingIntent intent) {
4086             mN.contentIntent = intent;
4087             return this;
4088         }
4089 
4090         /**
4091          * Supply a {@link PendingIntent} to send when the notification is cleared explicitly by the user.
4092          *
4093          * @see Notification#deleteIntent
4094          */
4095         @NonNull
setDeleteIntent(PendingIntent intent)4096         public Builder setDeleteIntent(PendingIntent intent) {
4097             mN.deleteIntent = intent;
4098             return this;
4099         }
4100 
4101         /**
4102          * An intent to launch instead of posting the notification to the status bar.
4103          * Only for use with extremely high-priority notifications demanding the user's
4104          * <strong>immediate</strong> attention, such as an incoming phone call or
4105          * alarm clock that the user has explicitly set to a particular time.
4106          * If this facility is used for something else, please give the user an option
4107          * to turn it off and use a normal notification, as this can be extremely
4108          * disruptive.
4109          *
4110          * <p>
4111          * The system UI may choose to display a heads-up notification, instead of
4112          * launching this intent, while the user is using the device.
4113          * </p>
4114          * <p>Apps targeting {@link Build.VERSION_CODES#Q} and above will have to request
4115          * a permission ({@link android.Manifest.permission#USE_FULL_SCREEN_INTENT}) in order to
4116          * use full screen intents.</p>
4117          *
4118          * @param intent The pending intent to launch.
4119          * @param highPriority Passing true will cause this notification to be sent
4120          *          even if other notifications are suppressed.
4121          *
4122          * @see Notification#fullScreenIntent
4123          */
4124         @NonNull
setFullScreenIntent(PendingIntent intent, boolean highPriority)4125         public Builder setFullScreenIntent(PendingIntent intent, boolean highPriority) {
4126             mN.fullScreenIntent = intent;
4127             setFlag(FLAG_HIGH_PRIORITY, highPriority);
4128             return this;
4129         }
4130 
4131         /**
4132          * Set the "ticker" text which is sent to accessibility services.
4133          *
4134          * @see Notification#tickerText
4135          */
4136         @NonNull
setTicker(CharSequence tickerText)4137         public Builder setTicker(CharSequence tickerText) {
4138             mN.tickerText = safeCharSequence(tickerText);
4139             return this;
4140         }
4141 
4142         /**
4143          * Obsolete version of {@link #setTicker(CharSequence)}.
4144          *
4145          */
4146         @Deprecated
setTicker(CharSequence tickerText, RemoteViews views)4147         public Builder setTicker(CharSequence tickerText, RemoteViews views) {
4148             setTicker(tickerText);
4149             // views is ignored
4150             return this;
4151         }
4152 
4153         /**
4154          * Add a large icon to the notification content view.
4155          *
4156          * In the platform template, this image will be shown on the left of the notification view
4157          * in place of the {@link #setSmallIcon(Icon) small icon} (which will be placed in a small
4158          * badge atop the large icon).
4159          */
4160         @NonNull
setLargeIcon(Bitmap b)4161         public Builder setLargeIcon(Bitmap b) {
4162             return setLargeIcon(b != null ? Icon.createWithBitmap(b) : null);
4163         }
4164 
4165         /**
4166          * Add a large icon to the notification content view.
4167          *
4168          * In the platform template, this image will be shown on the left of the notification view
4169          * in place of the {@link #setSmallIcon(Icon) small icon} (which will be placed in a small
4170          * badge atop the large icon).
4171          */
4172         @NonNull
setLargeIcon(Icon icon)4173         public Builder setLargeIcon(Icon icon) {
4174             mN.mLargeIcon = icon;
4175             mN.extras.putParcelable(EXTRA_LARGE_ICON, icon);
4176             return this;
4177         }
4178 
4179         /**
4180          * Set the sound to play.
4181          *
4182          * It will be played using the {@link #AUDIO_ATTRIBUTES_DEFAULT default audio attributes}
4183          * for notifications.
4184          *
4185          * @deprecated use {@link NotificationChannel#setSound(Uri, AudioAttributes)} instead.
4186          */
4187         @Deprecated
setSound(Uri sound)4188         public Builder setSound(Uri sound) {
4189             mN.sound = sound;
4190             mN.audioAttributes = AUDIO_ATTRIBUTES_DEFAULT;
4191             return this;
4192         }
4193 
4194         /**
4195          * Set the sound to play, along with a specific stream on which to play it.
4196          *
4197          * See {@link android.media.AudioManager} for the <code>STREAM_</code> constants.
4198          *
4199          * @deprecated use {@link NotificationChannel#setSound(Uri, AudioAttributes)}.
4200          */
4201         @Deprecated
setSound(Uri sound, int streamType)4202         public Builder setSound(Uri sound, int streamType) {
4203             PlayerBase.deprecateStreamTypeForPlayback(streamType, "Notification", "setSound()");
4204             mN.sound = sound;
4205             mN.audioStreamType = streamType;
4206             return this;
4207         }
4208 
4209         /**
4210          * Set the sound to play, along with specific {@link AudioAttributes audio attributes} to
4211          * use during playback.
4212          *
4213          * @deprecated use {@link NotificationChannel#setSound(Uri, AudioAttributes)} instead.
4214          * @see Notification#sound
4215          */
4216         @Deprecated
setSound(Uri sound, AudioAttributes audioAttributes)4217         public Builder setSound(Uri sound, AudioAttributes audioAttributes) {
4218             mN.sound = sound;
4219             mN.audioAttributes = audioAttributes;
4220             return this;
4221         }
4222 
4223         /**
4224          * Set the vibration pattern to use.
4225          *
4226          * See {@link android.os.Vibrator#vibrate(long[], int)} for a discussion of the
4227          * <code>pattern</code> parameter.
4228          *
4229          * <p>
4230          * A notification that vibrates is more likely to be presented as a heads-up notification.
4231          * </p>
4232          *
4233          * @deprecated use {@link NotificationChannel#setVibrationPattern(long[])} instead.
4234          * @see Notification#vibrate
4235          */
4236         @Deprecated
setVibrate(long[] pattern)4237         public Builder setVibrate(long[] pattern) {
4238             mN.vibrate = pattern;
4239             return this;
4240         }
4241 
4242         /**
4243          * Set the desired color for the indicator LED on the device, as well as the
4244          * blink duty cycle (specified in milliseconds).
4245          *
4246 
4247          * Not all devices will honor all (or even any) of these values.
4248          *
4249          * @deprecated use {@link NotificationChannel#enableLights(boolean)} instead.
4250          * @see Notification#ledARGB
4251          * @see Notification#ledOnMS
4252          * @see Notification#ledOffMS
4253          */
4254         @Deprecated
setLights(@olorInt int argb, int onMs, int offMs)4255         public Builder setLights(@ColorInt int argb, int onMs, int offMs) {
4256             mN.ledARGB = argb;
4257             mN.ledOnMS = onMs;
4258             mN.ledOffMS = offMs;
4259             if (onMs != 0 || offMs != 0) {
4260                 mN.flags |= FLAG_SHOW_LIGHTS;
4261             }
4262             return this;
4263         }
4264 
4265         /**
4266          * Set whether this is an "ongoing" notification.
4267          *
4268 
4269          * Ongoing notifications cannot be dismissed by the user, so your application or service
4270          * must take care of canceling them.
4271          *
4272 
4273          * They are typically used to indicate a background task that the user is actively engaged
4274          * with (e.g., playing music) or is pending in some way and therefore occupying the device
4275          * (e.g., a file download, sync operation, active network connection).
4276          *
4277 
4278          * @see Notification#FLAG_ONGOING_EVENT
4279          */
4280         @NonNull
setOngoing(boolean ongoing)4281         public Builder setOngoing(boolean ongoing) {
4282             setFlag(FLAG_ONGOING_EVENT, ongoing);
4283             return this;
4284         }
4285 
4286         /**
4287          * Set whether this notification should be colorized. When set, the color set with
4288          * {@link #setColor(int)} will be used as the background color of this notification.
4289          * <p>
4290          * This should only be used for high priority ongoing tasks like navigation, an ongoing
4291          * call, or other similarly high-priority events for the user.
4292          * <p>
4293          * For most styles, the coloring will only be applied if the notification is for a
4294          * foreground service notification.
4295          * However, for {@link MediaStyle} and {@link DecoratedMediaCustomViewStyle} notifications
4296          * that have a media session attached there is no such requirement.
4297          *
4298          * @see #setColor(int)
4299          * @see MediaStyle#setMediaSession(MediaSession.Token)
4300          */
4301         @NonNull
setColorized(boolean colorize)4302         public Builder setColorized(boolean colorize) {
4303             mN.extras.putBoolean(EXTRA_COLORIZED, colorize);
4304             return this;
4305         }
4306 
4307         /**
4308          * Set this flag if you would only like the sound, vibrate
4309          * and ticker to be played if the notification is not already showing.
4310          *
4311          * @see Notification#FLAG_ONLY_ALERT_ONCE
4312          */
4313         @NonNull
setOnlyAlertOnce(boolean onlyAlertOnce)4314         public Builder setOnlyAlertOnce(boolean onlyAlertOnce) {
4315             setFlag(FLAG_ONLY_ALERT_ONCE, onlyAlertOnce);
4316             return this;
4317         }
4318 
4319         /**
4320          * Make this notification automatically dismissed when the user touches it.
4321          *
4322          * @see Notification#FLAG_AUTO_CANCEL
4323          */
4324         @NonNull
setAutoCancel(boolean autoCancel)4325         public Builder setAutoCancel(boolean autoCancel) {
4326             setFlag(FLAG_AUTO_CANCEL, autoCancel);
4327             return this;
4328         }
4329 
4330         /**
4331          * Set whether or not this notification should not bridge to other devices.
4332          *
4333          * <p>Some notifications can be bridged to other devices for remote display.
4334          * This hint can be set to recommend this notification not be bridged.
4335          */
4336         @NonNull
setLocalOnly(boolean localOnly)4337         public Builder setLocalOnly(boolean localOnly) {
4338             setFlag(FLAG_LOCAL_ONLY, localOnly);
4339             return this;
4340         }
4341 
4342         /**
4343          * Set which notification properties will be inherited from system defaults.
4344          * <p>
4345          * The value should be one or more of the following fields combined with
4346          * bitwise-or:
4347          * {@link #DEFAULT_SOUND}, {@link #DEFAULT_VIBRATE}, {@link #DEFAULT_LIGHTS}.
4348          * <p>
4349          * For all default values, use {@link #DEFAULT_ALL}.
4350          *
4351          * @deprecated use {@link NotificationChannel#enableVibration(boolean)} and
4352          * {@link NotificationChannel#enableLights(boolean)} and
4353          * {@link NotificationChannel#setSound(Uri, AudioAttributes)} instead.
4354          */
4355         @Deprecated
setDefaults(int defaults)4356         public Builder setDefaults(int defaults) {
4357             mN.defaults = defaults;
4358             return this;
4359         }
4360 
4361         /**
4362          * Set the priority of this notification.
4363          *
4364          * @see Notification#priority
4365          * @deprecated use {@link NotificationChannel#setImportance(int)} instead.
4366          */
4367         @Deprecated
setPriority(@riority int pri)4368         public Builder setPriority(@Priority int pri) {
4369             mN.priority = pri;
4370             return this;
4371         }
4372 
4373         /**
4374          * Set the notification category.
4375          *
4376          * @see Notification#category
4377          */
4378         @NonNull
setCategory(String category)4379         public Builder setCategory(String category) {
4380             mN.category = category;
4381             return this;
4382         }
4383 
4384         /**
4385          * Add a person that is relevant to this notification.
4386          *
4387          * <P>
4388          * Depending on user preferences, this annotation may allow the notification to pass
4389          * through interruption filters, if this notification is of category {@link #CATEGORY_CALL}
4390          * or {@link #CATEGORY_MESSAGE}. The addition of people may also cause this notification to
4391          * appear more prominently in the user interface.
4392          * </P>
4393          *
4394          * <P>
4395          * The person should be specified by the {@code String} representation of a
4396          * {@link android.provider.ContactsContract.Contacts#CONTENT_LOOKUP_URI}.
4397          * </P>
4398          *
4399          * <P>The system will also attempt to resolve {@code mailto:} and {@code tel:} schema
4400          * URIs.  The path part of these URIs must exist in the contacts database, in the
4401          * appropriate column, or the reference will be discarded as invalid. Telephone schema
4402          * URIs will be resolved by {@link android.provider.ContactsContract.PhoneLookup}.
4403          * It is also possible to provide a URI with the schema {@code name:} in order to uniquely
4404          * identify a person without an entry in the contacts database.
4405          * </P>
4406          *
4407          * @param uri A URI for the person.
4408          * @see Notification#EXTRA_PEOPLE
4409          * @deprecated use {@link #addPerson(Person)}
4410          */
addPerson(String uri)4411         public Builder addPerson(String uri) {
4412             addPerson(new Person.Builder().setUri(uri).build());
4413             return this;
4414         }
4415 
4416         /**
4417          * Add a person that is relevant to this notification.
4418          *
4419          * <P>
4420          * Depending on user preferences, this annotation may allow the notification to pass
4421          * through interruption filters, if this notification is of category {@link #CATEGORY_CALL}
4422          * or {@link #CATEGORY_MESSAGE}. The addition of people may also cause this notification to
4423          * appear more prominently in the user interface.
4424          * </P>
4425          *
4426          * <P>
4427          * A person should usually contain a uri in order to benefit from the ranking boost.
4428          * However, even if no uri is provided, it's beneficial to provide other people in the
4429          * notification, such that listeners and voice only devices can announce and handle them
4430          * properly.
4431          * </P>
4432          *
4433          * @param person the person to add.
4434          * @see Notification#EXTRA_PEOPLE_LIST
4435          */
4436         @NonNull
addPerson(Person person)4437         public Builder addPerson(Person person) {
4438             mPersonList.add(person);
4439             return this;
4440         }
4441 
4442         /**
4443          * Set this notification to be part of a group of notifications sharing the same key.
4444          * Grouped notifications may display in a cluster or stack on devices which
4445          * support such rendering.
4446          *
4447          * <p>To make this notification the summary for its group, also call
4448          * {@link #setGroupSummary}. A sort order can be specified for group members by using
4449          * {@link #setSortKey}.
4450          * @param groupKey The group key of the group.
4451          * @return this object for method chaining
4452          */
4453         @NonNull
setGroup(String groupKey)4454         public Builder setGroup(String groupKey) {
4455             mN.mGroupKey = groupKey;
4456             return this;
4457         }
4458 
4459         /**
4460          * Set this notification to be the group summary for a group of notifications.
4461          * Grouped notifications may display in a cluster or stack on devices which
4462          * support such rendering. If thereRequires a group key also be set using {@link #setGroup}.
4463          * The group summary may be suppressed if too few notifications are included in the group.
4464          * @param isGroupSummary Whether this notification should be a group summary.
4465          * @return this object for method chaining
4466          */
4467         @NonNull
setGroupSummary(boolean isGroupSummary)4468         public Builder setGroupSummary(boolean isGroupSummary) {
4469             setFlag(FLAG_GROUP_SUMMARY, isGroupSummary);
4470             return this;
4471         }
4472 
4473         /**
4474          * Set a sort key that orders this notification among other notifications from the
4475          * same package. This can be useful if an external sort was already applied and an app
4476          * would like to preserve this. Notifications will be sorted lexicographically using this
4477          * value, although providing different priorities in addition to providing sort key may
4478          * cause this value to be ignored.
4479          *
4480          * <p>This sort key can also be used to order members of a notification group. See
4481          * {@link #setGroup}.
4482          *
4483          * @see String#compareTo(String)
4484          */
4485         @NonNull
setSortKey(String sortKey)4486         public Builder setSortKey(String sortKey) {
4487             mN.mSortKey = sortKey;
4488             return this;
4489         }
4490 
4491         /**
4492          * Merge additional metadata into this notification.
4493          *
4494          * <p>Values within the Bundle will replace existing extras values in this Builder.
4495          *
4496          * @see Notification#extras
4497          */
4498         @NonNull
addExtras(Bundle extras)4499         public Builder addExtras(Bundle extras) {
4500             if (extras != null) {
4501                 mUserExtras.putAll(extras);
4502             }
4503             return this;
4504         }
4505 
4506         /**
4507          * Set metadata for this notification.
4508          *
4509          * <p>A reference to the Bundle is held for the lifetime of this Builder, and the Bundle's
4510          * current contents are copied into the Notification each time {@link #build()} is
4511          * called.
4512          *
4513          * <p>Replaces any existing extras values with those from the provided Bundle.
4514          * Use {@link #addExtras} to merge in metadata instead.
4515          *
4516          * @see Notification#extras
4517          */
4518         @NonNull
setExtras(Bundle extras)4519         public Builder setExtras(Bundle extras) {
4520             if (extras != null) {
4521                 mUserExtras = extras;
4522             }
4523             return this;
4524         }
4525 
4526         /**
4527          * Get the current metadata Bundle used by this notification Builder.
4528          *
4529          * <p>The returned Bundle is shared with this Builder.
4530          *
4531          * <p>The current contents of this Bundle are copied into the Notification each time
4532          * {@link #build()} is called.
4533          *
4534          * @see Notification#extras
4535          */
getExtras()4536         public Bundle getExtras() {
4537             return mUserExtras;
4538         }
4539 
getAllExtras()4540         private Bundle getAllExtras() {
4541             final Bundle saveExtras = (Bundle) mUserExtras.clone();
4542             saveExtras.putAll(mN.extras);
4543             return saveExtras;
4544         }
4545 
4546         /**
4547          * Add an action to this notification. Actions are typically displayed by
4548          * the system as a button adjacent to the notification content.
4549          * <p>
4550          * Every action must have an icon (32dp square and matching the
4551          * <a href="{@docRoot}design/style/iconography.html#action-bar">Holo
4552          * Dark action bar</a> visual style), a textual label, and a {@link PendingIntent}.
4553          * <p>
4554          * A notification in its expanded form can display up to 3 actions, from left to right in
4555          * the order they were added. Actions will not be displayed when the notification is
4556          * collapsed, however, so be sure that any essential functions may be accessed by the user
4557          * in some other way (for example, in the Activity pointed to by {@link #contentIntent}).
4558          *
4559          * @param icon Resource ID of a drawable that represents the action.
4560          * @param title Text describing the action.
4561          * @param intent PendingIntent to be fired when the action is invoked.
4562          *
4563          * @deprecated Use {@link #addAction(Action)} instead.
4564          */
4565         @Deprecated
addAction(int icon, CharSequence title, PendingIntent intent)4566         public Builder addAction(int icon, CharSequence title, PendingIntent intent) {
4567             mActions.add(new Action(icon, safeCharSequence(title), intent));
4568             return this;
4569         }
4570 
4571         /**
4572          * Add an action to this notification. Actions are typically displayed by
4573          * the system as a button adjacent to the notification content.
4574          * <p>
4575          * Every action must have an icon (32dp square and matching the
4576          * <a href="{@docRoot}design/style/iconography.html#action-bar">Holo
4577          * Dark action bar</a> visual style), a textual label, and a {@link PendingIntent}.
4578          * <p>
4579          * A notification in its expanded form can display up to 3 actions, from left to right in
4580          * the order they were added. Actions will not be displayed when the notification is
4581          * collapsed, however, so be sure that any essential functions may be accessed by the user
4582          * in some other way (for example, in the Activity pointed to by {@link #contentIntent}).
4583          *
4584          * @param action The action to add.
4585          */
4586         @NonNull
addAction(Action action)4587         public Builder addAction(Action action) {
4588             if (action != null) {
4589                 mActions.add(action);
4590             }
4591             return this;
4592         }
4593 
4594         /**
4595          * Alter the complete list of actions attached to this notification.
4596          * @see #addAction(Action).
4597          *
4598          * @param actions
4599          * @return
4600          */
4601         @NonNull
setActions(Action... actions)4602         public Builder setActions(Action... actions) {
4603             mActions.clear();
4604             for (int i = 0; i < actions.length; i++) {
4605                 if (actions[i] != null) {
4606                     mActions.add(actions[i]);
4607                 }
4608             }
4609             return this;
4610         }
4611 
4612         /**
4613          * Add a rich notification style to be applied at build time.
4614          *
4615          * @param style Object responsible for modifying the notification style.
4616          */
4617         @NonNull
setStyle(Style style)4618         public Builder setStyle(Style style) {
4619             if (mStyle != style) {
4620                 mStyle = style;
4621                 if (mStyle != null) {
4622                     mStyle.setBuilder(this);
4623                     mN.extras.putString(EXTRA_TEMPLATE, style.getClass().getName());
4624                 }  else {
4625                     mN.extras.remove(EXTRA_TEMPLATE);
4626                 }
4627             }
4628             return this;
4629         }
4630 
4631         /**
4632          * Returns the style set by {@link #setStyle(Style)}.
4633          */
getStyle()4634         public Style getStyle() {
4635             return mStyle;
4636         }
4637 
4638         /**
4639          * Specify the value of {@link #visibility}.
4640          *
4641          * @return The same Builder.
4642          */
4643         @NonNull
setVisibility(@isibility int visibility)4644         public Builder setVisibility(@Visibility int visibility) {
4645             mN.visibility = visibility;
4646             return this;
4647         }
4648 
4649         /**
4650          * Supply a replacement Notification whose contents should be shown in insecure contexts
4651          * (i.e. atop the secure lockscreen). See {@link #visibility} and {@link #VISIBILITY_PUBLIC}.
4652          * @param n A replacement notification, presumably with some or all info redacted.
4653          * @return The same Builder.
4654          */
4655         @NonNull
setPublicVersion(Notification n)4656         public Builder setPublicVersion(Notification n) {
4657             if (n != null) {
4658                 mN.publicVersion = new Notification();
4659                 n.cloneInto(mN.publicVersion, /*heavy=*/ true);
4660             } else {
4661                 mN.publicVersion = null;
4662             }
4663             return this;
4664         }
4665 
4666         /**
4667          * Apply an extender to this notification builder. Extenders may be used to add
4668          * metadata or change options on this builder.
4669          */
4670         @NonNull
extend(Extender extender)4671         public Builder extend(Extender extender) {
4672             extender.extend(this);
4673             return this;
4674         }
4675 
4676         /**
4677          * Set the value for a notification flag
4678          *
4679          * @param mask Bit mask of the flag
4680          * @param value Status (on/off) of the flag
4681          *
4682          * @return The same Builder.
4683          */
4684         @NonNull
setFlag(@otificationFlags int mask, boolean value)4685         public Builder setFlag(@NotificationFlags int mask, boolean value) {
4686             if (value) {
4687                 mN.flags |= mask;
4688             } else {
4689                 mN.flags &= ~mask;
4690             }
4691             return this;
4692         }
4693 
4694         /**
4695          * Sets {@link Notification#color}.
4696          *
4697          * @param argb The accent color to use
4698          *
4699          * @return The same Builder.
4700          */
4701         @NonNull
setColor(@olorInt int argb)4702         public Builder setColor(@ColorInt int argb) {
4703             mN.color = argb;
4704             sanitizeColor();
4705             return this;
4706         }
4707 
getProfileBadgeDrawable()4708         private Drawable getProfileBadgeDrawable() {
4709             if (mContext.getUserId() == UserHandle.USER_SYSTEM) {
4710                 // This user can never be a badged profile,
4711                 // and also includes USER_ALL system notifications.
4712                 return null;
4713             }
4714             // Note: This assumes that the current user can read the profile badge of the
4715             // originating user.
4716             return mContext.getPackageManager().getUserBadgeForDensityNoBackground(
4717                     new UserHandle(mContext.getUserId()), 0);
4718         }
4719 
getProfileBadge()4720         private Bitmap getProfileBadge() {
4721             Drawable badge = getProfileBadgeDrawable();
4722             if (badge == null) {
4723                 return null;
4724             }
4725             final int size = mContext.getResources().getDimensionPixelSize(
4726                     R.dimen.notification_badge_size);
4727             Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
4728             Canvas canvas = new Canvas(bitmap);
4729             badge.setBounds(0, 0, size, size);
4730             badge.draw(canvas);
4731             return bitmap;
4732         }
4733 
bindProfileBadge(RemoteViews contentView, StandardTemplateParams p)4734         private void bindProfileBadge(RemoteViews contentView, StandardTemplateParams p) {
4735             Bitmap profileBadge = getProfileBadge();
4736 
4737             if (profileBadge != null) {
4738                 contentView.setImageViewBitmap(R.id.profile_badge, profileBadge);
4739                 contentView.setViewVisibility(R.id.profile_badge, View.VISIBLE);
4740                 if (isColorized(p)) {
4741                     contentView.setDrawableTint(R.id.profile_badge, false,
4742                             getPrimaryTextColor(p), PorterDuff.Mode.SRC_ATOP);
4743                 }
4744             }
4745         }
4746 
bindAlertedIcon(RemoteViews contentView, StandardTemplateParams p)4747         private void bindAlertedIcon(RemoteViews contentView, StandardTemplateParams p) {
4748             contentView.setDrawableTint(
4749                     R.id.alerted_icon,
4750                     false /* targetBackground */,
4751                     getNeutralColor(p),
4752                     PorterDuff.Mode.SRC_ATOP);
4753         }
4754 
4755         /**
4756          * @hide
4757          */
usesStandardHeader()4758         public boolean usesStandardHeader() {
4759             if (mN.mUsesStandardHeader) {
4760                 return true;
4761             }
4762             if (mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.N) {
4763                 if (mN.contentView == null && mN.bigContentView == null) {
4764                     return true;
4765                 }
4766             }
4767             boolean contentViewUsesHeader = mN.contentView == null
4768                     || STANDARD_LAYOUTS.contains(mN.contentView.getLayoutId());
4769             boolean bigContentViewUsesHeader = mN.bigContentView == null
4770                     || STANDARD_LAYOUTS.contains(mN.bigContentView.getLayoutId());
4771             return contentViewUsesHeader && bigContentViewUsesHeader;
4772         }
4773 
resetStandardTemplate(RemoteViews contentView)4774         private void resetStandardTemplate(RemoteViews contentView) {
4775             resetNotificationHeader(contentView);
4776             contentView.setViewVisibility(R.id.right_icon, View.GONE);
4777             contentView.setViewVisibility(R.id.title, View.GONE);
4778             contentView.setTextViewText(R.id.title, null);
4779             contentView.setViewVisibility(R.id.text, View.GONE);
4780             contentView.setTextViewText(R.id.text, null);
4781             contentView.setViewVisibility(R.id.text_line_1, View.GONE);
4782             contentView.setTextViewText(R.id.text_line_1, null);
4783         }
4784 
4785         /**
4786          * Resets the notification header to its original state
4787          */
resetNotificationHeader(RemoteViews contentView)4788         private void resetNotificationHeader(RemoteViews contentView) {
4789             // Small icon doesn't need to be reset, as it's always set. Resetting would prevent
4790             // re-using the drawable when the notification is updated.
4791             contentView.setBoolean(R.id.notification_header, "setExpanded", false);
4792             contentView.setTextViewText(R.id.app_name_text, null);
4793             contentView.setViewVisibility(R.id.chronometer, View.GONE);
4794             contentView.setViewVisibility(R.id.header_text, View.GONE);
4795             contentView.setTextViewText(R.id.header_text, null);
4796             contentView.setViewVisibility(R.id.header_text_secondary, View.GONE);
4797             contentView.setTextViewText(R.id.header_text_secondary, null);
4798             contentView.setViewVisibility(R.id.header_text_divider, View.GONE);
4799             contentView.setViewVisibility(R.id.header_text_secondary_divider, View.GONE);
4800             contentView.setViewVisibility(R.id.time_divider, View.GONE);
4801             contentView.setViewVisibility(R.id.time, View.GONE);
4802             contentView.setImageViewIcon(R.id.profile_badge, null);
4803             contentView.setViewVisibility(R.id.profile_badge, View.GONE);
4804             contentView.setViewVisibility(R.id.alerted_icon, View.GONE);
4805             mN.mUsesStandardHeader = false;
4806         }
4807 
applyStandardTemplate(int resId, TemplateBindResult result)4808         private RemoteViews applyStandardTemplate(int resId, TemplateBindResult result) {
4809             return applyStandardTemplate(resId, mParams.reset().fillTextsFrom(this),
4810                     result);
4811         }
4812 
applyStandardTemplate(int resId, StandardTemplateParams p, TemplateBindResult result)4813         private RemoteViews applyStandardTemplate(int resId, StandardTemplateParams p,
4814                 TemplateBindResult result) {
4815             RemoteViews contentView = new BuilderRemoteViews(mContext.getApplicationInfo(), resId);
4816 
4817             resetStandardTemplate(contentView);
4818 
4819             final Bundle ex = mN.extras;
4820             updateBackgroundColor(contentView, p);
4821             bindNotificationHeader(contentView, p);
4822             bindLargeIconAndReply(contentView, p, result);
4823             boolean showProgress = handleProgressBar(contentView, ex, p);
4824             if (p.title != null) {
4825                 contentView.setViewVisibility(R.id.title, View.VISIBLE);
4826                 contentView.setTextViewText(R.id.title, processTextSpans(p.title));
4827                 setTextViewColorPrimary(contentView, R.id.title, p);
4828                 contentView.setViewLayoutWidth(R.id.title, showProgress
4829                         ? ViewGroup.LayoutParams.WRAP_CONTENT
4830                         : ViewGroup.LayoutParams.MATCH_PARENT);
4831             }
4832             if (p.text != null) {
4833                 int textId = showProgress ? com.android.internal.R.id.text_line_1
4834                         : com.android.internal.R.id.text;
4835                 contentView.setTextViewText(textId, processTextSpans(p.text));
4836                 setTextViewColorSecondary(contentView, textId, p);
4837                 contentView.setViewVisibility(textId, View.VISIBLE);
4838             }
4839 
4840             setContentMinHeight(contentView, showProgress || mN.hasLargeIcon());
4841 
4842             return contentView;
4843         }
4844 
processTextSpans(CharSequence text)4845         private CharSequence processTextSpans(CharSequence text) {
4846             if (hasForegroundColor() || mInNightMode) {
4847                 return ContrastColorUtil.clearColorSpans(text);
4848             }
4849             return text;
4850         }
4851 
setTextViewColorPrimary(RemoteViews contentView, int id, StandardTemplateParams p)4852         private void setTextViewColorPrimary(RemoteViews contentView, int id,
4853                 StandardTemplateParams p) {
4854             ensureColors(p);
4855             contentView.setTextColor(id, mPrimaryTextColor);
4856         }
4857 
hasForegroundColor()4858         private boolean hasForegroundColor() {
4859             return mForegroundColor != COLOR_INVALID;
4860         }
4861 
4862         /**
4863          * Return the primary text color using the existing template params
4864          * @hide
4865          */
4866         @VisibleForTesting
getPrimaryTextColor()4867         public int getPrimaryTextColor() {
4868             return getPrimaryTextColor(mParams);
4869         }
4870 
4871         /**
4872          * @param p the template params to inflate this with
4873          * @return the primary text color
4874          * @hide
4875          */
4876         @VisibleForTesting
getPrimaryTextColor(StandardTemplateParams p)4877         public int getPrimaryTextColor(StandardTemplateParams p) {
4878             ensureColors(p);
4879             return mPrimaryTextColor;
4880         }
4881 
4882         /**
4883          * Return the secondary text color using the existing template params
4884          * @hide
4885          */
4886         @VisibleForTesting
getSecondaryTextColor()4887         public int getSecondaryTextColor() {
4888             return getSecondaryTextColor(mParams);
4889         }
4890 
4891         /**
4892          * @param p the template params to inflate this with
4893          * @return the secondary text color
4894          * @hide
4895          */
4896         @VisibleForTesting
getSecondaryTextColor(StandardTemplateParams p)4897         public int getSecondaryTextColor(StandardTemplateParams p) {
4898             ensureColors(p);
4899             return mSecondaryTextColor;
4900         }
4901 
setTextViewColorSecondary(RemoteViews contentView, int id, StandardTemplateParams p)4902         private void setTextViewColorSecondary(RemoteViews contentView, int id,
4903                 StandardTemplateParams p) {
4904             ensureColors(p);
4905             contentView.setTextColor(id, mSecondaryTextColor);
4906         }
4907 
ensureColors(StandardTemplateParams p)4908         private void ensureColors(StandardTemplateParams p) {
4909             int backgroundColor = getBackgroundColor(p);
4910             if (mPrimaryTextColor == COLOR_INVALID
4911                     || mSecondaryTextColor == COLOR_INVALID
4912                     || mTextColorsAreForBackground != backgroundColor) {
4913                 mTextColorsAreForBackground = backgroundColor;
4914                 if (!hasForegroundColor() || !isColorized(p)) {
4915                     mPrimaryTextColor = ContrastColorUtil.resolvePrimaryColor(mContext,
4916                             backgroundColor, mInNightMode);
4917                     mSecondaryTextColor = ContrastColorUtil.resolveSecondaryColor(mContext,
4918                             backgroundColor, mInNightMode);
4919                     if (backgroundColor != COLOR_DEFAULT && isColorized(p)) {
4920                         mPrimaryTextColor = ContrastColorUtil.findAlphaToMeetContrast(
4921                                 mPrimaryTextColor, backgroundColor, 4.5);
4922                         mSecondaryTextColor = ContrastColorUtil.findAlphaToMeetContrast(
4923                                 mSecondaryTextColor, backgroundColor, 4.5);
4924                     }
4925                 } else {
4926                     double backLum = ContrastColorUtil.calculateLuminance(backgroundColor);
4927                     double textLum = ContrastColorUtil.calculateLuminance(mForegroundColor);
4928                     double contrast = ContrastColorUtil.calculateContrast(mForegroundColor,
4929                             backgroundColor);
4930                     // We only respect the given colors if worst case Black or White still has
4931                     // contrast
4932                     boolean backgroundLight = backLum > textLum
4933                                     && satisfiesTextContrast(backgroundColor, Color.BLACK)
4934                             || backLum <= textLum
4935                                     && !satisfiesTextContrast(backgroundColor, Color.WHITE);
4936                     if (contrast < 4.5f) {
4937                         if (backgroundLight) {
4938                             mSecondaryTextColor = ContrastColorUtil.findContrastColor(
4939                                     mForegroundColor,
4940                                     backgroundColor,
4941                                     true /* findFG */,
4942                                     4.5f);
4943                             mPrimaryTextColor = ContrastColorUtil.changeColorLightness(
4944                                     mSecondaryTextColor, -LIGHTNESS_TEXT_DIFFERENCE_LIGHT);
4945                         } else {
4946                             mSecondaryTextColor =
4947                                     ContrastColorUtil.findContrastColorAgainstDark(
4948                                     mForegroundColor,
4949                                     backgroundColor,
4950                                     true /* findFG */,
4951                                     4.5f);
4952                             mPrimaryTextColor = ContrastColorUtil.changeColorLightness(
4953                                     mSecondaryTextColor, -LIGHTNESS_TEXT_DIFFERENCE_DARK);
4954                         }
4955                     } else {
4956                         mPrimaryTextColor = mForegroundColor;
4957                         mSecondaryTextColor = ContrastColorUtil.changeColorLightness(
4958                                 mPrimaryTextColor, backgroundLight ? LIGHTNESS_TEXT_DIFFERENCE_LIGHT
4959                                         : LIGHTNESS_TEXT_DIFFERENCE_DARK);
4960                         if (ContrastColorUtil.calculateContrast(mSecondaryTextColor,
4961                                 backgroundColor) < 4.5f) {
4962                             // oh well the secondary is not good enough
4963                             if (backgroundLight) {
4964                                 mSecondaryTextColor = ContrastColorUtil.findContrastColor(
4965                                         mSecondaryTextColor,
4966                                         backgroundColor,
4967                                         true /* findFG */,
4968                                         4.5f);
4969                             } else {
4970                                 mSecondaryTextColor
4971                                         = ContrastColorUtil.findContrastColorAgainstDark(
4972                                         mSecondaryTextColor,
4973                                         backgroundColor,
4974                                         true /* findFG */,
4975                                         4.5f);
4976                             }
4977                             mPrimaryTextColor = ContrastColorUtil.changeColorLightness(
4978                                     mSecondaryTextColor, backgroundLight
4979                                             ? -LIGHTNESS_TEXT_DIFFERENCE_LIGHT
4980                                             : -LIGHTNESS_TEXT_DIFFERENCE_DARK);
4981                         }
4982                     }
4983                 }
4984             }
4985         }
4986 
updateBackgroundColor(RemoteViews contentView, StandardTemplateParams p)4987         private void updateBackgroundColor(RemoteViews contentView,
4988                 StandardTemplateParams p) {
4989             if (isColorized(p)) {
4990                 contentView.setInt(R.id.status_bar_latest_event_content, "setBackgroundColor",
4991                         getBackgroundColor(p));
4992             } else {
4993                 // Clear it!
4994                 contentView.setInt(R.id.status_bar_latest_event_content, "setBackgroundResource",
4995                         0);
4996             }
4997         }
4998 
4999         /**
5000          * @param remoteView the remote view to update the minheight in
5001          * @param hasMinHeight does it have a mimHeight
5002          * @hide
5003          */
setContentMinHeight(RemoteViews remoteView, boolean hasMinHeight)5004         void setContentMinHeight(RemoteViews remoteView, boolean hasMinHeight) {
5005             int minHeight = 0;
5006             if (hasMinHeight) {
5007                 // we need to set the minHeight of the notification
5008                 minHeight = mContext.getResources().getDimensionPixelSize(
5009                         com.android.internal.R.dimen.notification_min_content_height);
5010             }
5011             remoteView.setInt(R.id.notification_main_column, "setMinimumHeight", minHeight);
5012         }
5013 
handleProgressBar(RemoteViews contentView, Bundle ex, StandardTemplateParams p)5014         private boolean handleProgressBar(RemoteViews contentView, Bundle ex,
5015                 StandardTemplateParams p) {
5016             final int max = ex.getInt(EXTRA_PROGRESS_MAX, 0);
5017             final int progress = ex.getInt(EXTRA_PROGRESS, 0);
5018             final boolean ind = ex.getBoolean(EXTRA_PROGRESS_INDETERMINATE);
5019             if (p.hasProgress && (max != 0 || ind)) {
5020                 contentView.setViewVisibility(com.android.internal.R.id.progress, View.VISIBLE);
5021                 contentView.setProgressBar(
5022                         R.id.progress, max, progress, ind);
5023                 contentView.setProgressBackgroundTintList(
5024                         R.id.progress, ColorStateList.valueOf(mContext.getColor(
5025                                 R.color.notification_progress_background_color)));
5026                 if (getRawColor(p) != COLOR_DEFAULT) {
5027                     int color = isColorized(p) ? getPrimaryTextColor(p) : resolveContrastColor(p);
5028                     ColorStateList colorStateList = ColorStateList.valueOf(color);
5029                     contentView.setProgressTintList(R.id.progress, colorStateList);
5030                     contentView.setProgressIndeterminateTintList(R.id.progress, colorStateList);
5031                 }
5032                 return true;
5033             } else {
5034                 contentView.setViewVisibility(R.id.progress, View.GONE);
5035                 return false;
5036             }
5037         }
5038 
bindLargeIconAndReply(RemoteViews contentView, StandardTemplateParams p, TemplateBindResult result)5039         private void bindLargeIconAndReply(RemoteViews contentView, StandardTemplateParams p,
5040                 TemplateBindResult result) {
5041             boolean largeIconShown = bindLargeIcon(contentView, p);
5042             boolean replyIconShown = bindReplyIcon(contentView, p);
5043             boolean iconContainerVisible = largeIconShown || replyIconShown;
5044             contentView.setViewVisibility(R.id.right_icon_container,
5045                     iconContainerVisible ? View.VISIBLE : View.GONE);
5046             int marginEnd = calculateMarginEnd(largeIconShown, replyIconShown);
5047             contentView.setViewLayoutMarginEnd(R.id.line1, marginEnd);
5048             contentView.setViewLayoutMarginEnd(R.id.text, marginEnd);
5049             contentView.setViewLayoutMarginEnd(R.id.progress, marginEnd);
5050             if (result != null) {
5051                 result.setIconMarginEnd(marginEnd);
5052                 result.setRightIconContainerVisible(iconContainerVisible);
5053             }
5054         }
5055 
calculateMarginEnd(boolean largeIconShown, boolean replyIconShown)5056         private int calculateMarginEnd(boolean largeIconShown, boolean replyIconShown) {
5057             int marginEnd = 0;
5058             int contentMargin = mContext.getResources().getDimensionPixelSize(
5059                     R.dimen.notification_content_margin_end);
5060             int iconSize = mContext.getResources().getDimensionPixelSize(
5061                     R.dimen.notification_right_icon_size);
5062             if (replyIconShown) {
5063                 // The size of the reply icon
5064                 marginEnd += iconSize;
5065 
5066                 int replyInset = mContext.getResources().getDimensionPixelSize(
5067                         R.dimen.notification_reply_inset);
5068                 // We're subtracting the inset of the reply icon to make sure it's
5069                 // aligned nicely on the right, and remove it from the following padding
5070                 marginEnd -= replyInset * 2;
5071             }
5072             if (largeIconShown) {
5073                 // adding size of the right icon
5074                 marginEnd += iconSize;
5075 
5076                 if (replyIconShown) {
5077                     // We also add some padding to the reply icon if it's around
5078                     marginEnd += contentMargin;
5079                 }
5080             }
5081             if (replyIconShown || largeIconShown) {
5082                 // The padding to the content
5083                 marginEnd += contentMargin;
5084             }
5085             return marginEnd;
5086         }
5087 
5088         /**
5089          * Bind the large icon.
5090          * @return if the largeIcon is visible
5091          */
bindLargeIcon(RemoteViews contentView, StandardTemplateParams p)5092         private boolean bindLargeIcon(RemoteViews contentView, StandardTemplateParams p) {
5093             if (mN.mLargeIcon == null && mN.largeIcon != null) {
5094                 mN.mLargeIcon = Icon.createWithBitmap(mN.largeIcon);
5095             }
5096             boolean showLargeIcon = mN.mLargeIcon != null && !p.hideLargeIcon;
5097             if (showLargeIcon) {
5098                 contentView.setViewVisibility(R.id.right_icon, View.VISIBLE);
5099                 contentView.setImageViewIcon(R.id.right_icon, mN.mLargeIcon);
5100                 processLargeLegacyIcon(mN.mLargeIcon, contentView, p);
5101             }
5102             return showLargeIcon;
5103         }
5104 
5105         /**
5106          * Bind the reply icon.
5107          * @return if the reply icon is visible
5108          */
bindReplyIcon(RemoteViews contentView, StandardTemplateParams p)5109         private boolean bindReplyIcon(RemoteViews contentView, StandardTemplateParams p) {
5110             boolean actionVisible = !p.hideReplyIcon;
5111             Action action = null;
5112             if (actionVisible) {
5113                 action = findReplyAction();
5114                 actionVisible = action != null;
5115             }
5116             if (actionVisible) {
5117                 contentView.setViewVisibility(R.id.reply_icon_action, View.VISIBLE);
5118                 contentView.setDrawableTint(R.id.reply_icon_action,
5119                         false /* targetBackground */,
5120                         getNeutralColor(p),
5121                         PorterDuff.Mode.SRC_ATOP);
5122                 contentView.setOnClickPendingIntent(R.id.reply_icon_action, action.actionIntent);
5123                 contentView.setRemoteInputs(R.id.reply_icon_action, action.mRemoteInputs);
5124             } else {
5125                 contentView.setRemoteInputs(R.id.reply_icon_action, null);
5126             }
5127             contentView.setViewVisibility(R.id.reply_icon_action,
5128                     actionVisible ? View.VISIBLE : View.GONE);
5129             return actionVisible;
5130         }
5131 
findReplyAction()5132         private Action findReplyAction() {
5133             ArrayList<Action> actions = mActions;
5134             if (mOriginalActions != null) {
5135                 actions = mOriginalActions;
5136             }
5137             int numActions = actions.size();
5138             for (int i = 0; i < numActions; i++) {
5139                 Action action = actions.get(i);
5140                 if (hasValidRemoteInput(action)) {
5141                     return action;
5142                 }
5143             }
5144             return null;
5145         }
5146 
bindNotificationHeader(RemoteViews contentView, StandardTemplateParams p)5147         private void bindNotificationHeader(RemoteViews contentView, StandardTemplateParams p) {
5148             bindSmallIcon(contentView, p);
5149             bindHeaderAppName(contentView, p);
5150             bindHeaderText(contentView, p);
5151             bindHeaderTextSecondary(contentView, p);
5152             bindHeaderChronometerAndTime(contentView, p);
5153             bindProfileBadge(contentView, p);
5154             bindAlertedIcon(contentView, p);
5155             bindActivePermissions(contentView, p);
5156             bindExpandButton(contentView, p);
5157             mN.mUsesStandardHeader = true;
5158         }
5159 
bindActivePermissions(RemoteViews contentView, StandardTemplateParams p)5160         private void bindActivePermissions(RemoteViews contentView, StandardTemplateParams p) {
5161             int color = getNeutralColor(p);
5162             contentView.setDrawableTint(R.id.camera, false, color, PorterDuff.Mode.SRC_ATOP);
5163             contentView.setDrawableTint(R.id.mic, false, color, PorterDuff.Mode.SRC_ATOP);
5164             contentView.setDrawableTint(R.id.overlay, false, color, PorterDuff.Mode.SRC_ATOP);
5165         }
5166 
bindExpandButton(RemoteViews contentView, StandardTemplateParams p)5167         private void bindExpandButton(RemoteViews contentView, StandardTemplateParams p) {
5168             int color = isColorized(p) ? getPrimaryTextColor(p) : getSecondaryTextColor(p);
5169             contentView.setDrawableTint(R.id.expand_button, false, color,
5170                     PorterDuff.Mode.SRC_ATOP);
5171             contentView.setInt(R.id.expand_button, "setOriginalNotificationColor",
5172                     color);
5173         }
5174 
bindHeaderChronometerAndTime(RemoteViews contentView, StandardTemplateParams p)5175         private void bindHeaderChronometerAndTime(RemoteViews contentView,
5176                 StandardTemplateParams p) {
5177             if (showsTimeOrChronometer()) {
5178                 contentView.setViewVisibility(R.id.time_divider, View.VISIBLE);
5179                 setTextViewColorSecondary(contentView, R.id.time_divider, p);
5180                 if (mN.extras.getBoolean(EXTRA_SHOW_CHRONOMETER)) {
5181                     contentView.setViewVisibility(R.id.chronometer, View.VISIBLE);
5182                     contentView.setLong(R.id.chronometer, "setBase",
5183                             mN.when + (SystemClock.elapsedRealtime() - System.currentTimeMillis()));
5184                     contentView.setBoolean(R.id.chronometer, "setStarted", true);
5185                     boolean countsDown = mN.extras.getBoolean(EXTRA_CHRONOMETER_COUNT_DOWN);
5186                     contentView.setChronometerCountDown(R.id.chronometer, countsDown);
5187                     setTextViewColorSecondary(contentView, R.id.chronometer, p);
5188                 } else {
5189                     contentView.setViewVisibility(R.id.time, View.VISIBLE);
5190                     contentView.setLong(R.id.time, "setTime", mN.when);
5191                     setTextViewColorSecondary(contentView, R.id.time, p);
5192                 }
5193             } else {
5194                 // We still want a time to be set but gone, such that we can show and hide it
5195                 // on demand in case it's a child notification without anything in the header
5196                 contentView.setLong(R.id.time, "setTime", mN.when != 0 ? mN.when : mN.creationTime);
5197             }
5198         }
5199 
bindHeaderText(RemoteViews contentView, StandardTemplateParams p)5200         private void bindHeaderText(RemoteViews contentView, StandardTemplateParams p) {
5201             CharSequence summaryText = p.summaryText;
5202             if (summaryText == null && mStyle != null && mStyle.mSummaryTextSet
5203                     && mStyle.hasSummaryInHeader()) {
5204                 summaryText = mStyle.mSummaryText;
5205             }
5206             if (summaryText == null
5207                     && mContext.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N
5208                     && mN.extras.getCharSequence(EXTRA_INFO_TEXT) != null) {
5209                 summaryText = mN.extras.getCharSequence(EXTRA_INFO_TEXT);
5210             }
5211             if (summaryText != null) {
5212                 // TODO: Remove the span entirely to only have the string with propper formating.
5213                 contentView.setTextViewText(R.id.header_text, processTextSpans(
5214                         processLegacyText(summaryText)));
5215                 setTextViewColorSecondary(contentView, R.id.header_text, p);
5216                 contentView.setViewVisibility(R.id.header_text, View.VISIBLE);
5217                 contentView.setViewVisibility(R.id.header_text_divider, View.VISIBLE);
5218                 setTextViewColorSecondary(contentView, R.id.header_text_divider, p);
5219             }
5220         }
5221 
bindHeaderTextSecondary(RemoteViews contentView, StandardTemplateParams p)5222         private void bindHeaderTextSecondary(RemoteViews contentView, StandardTemplateParams p) {
5223             if (!TextUtils.isEmpty(p.headerTextSecondary)) {
5224                 contentView.setTextViewText(R.id.header_text_secondary, processTextSpans(
5225                         processLegacyText(p.headerTextSecondary)));
5226                 setTextViewColorSecondary(contentView, R.id.header_text_secondary, p);
5227                 contentView.setViewVisibility(R.id.header_text_secondary, View.VISIBLE);
5228                 contentView.setViewVisibility(R.id.header_text_secondary_divider, View.VISIBLE);
5229                 setTextViewColorSecondary(contentView, R.id.header_text_secondary_divider, p);
5230             }
5231         }
5232 
5233         /**
5234          * @hide
5235          */
5236         @UnsupportedAppUsage
loadHeaderAppName()5237         public String loadHeaderAppName() {
5238             CharSequence name = null;
5239             final PackageManager pm = mContext.getPackageManager();
5240             if (mN.extras.containsKey(EXTRA_SUBSTITUTE_APP_NAME)) {
5241                 // only system packages which lump together a bunch of unrelated stuff
5242                 // may substitute a different name to make the purpose of the
5243                 // notification more clear. the correct package label should always
5244                 // be accessible via SystemUI.
5245                 final String pkg = mContext.getPackageName();
5246                 final String subName = mN.extras.getString(EXTRA_SUBSTITUTE_APP_NAME);
5247                 if (PackageManager.PERMISSION_GRANTED == pm.checkPermission(
5248                         android.Manifest.permission.SUBSTITUTE_NOTIFICATION_APP_NAME, pkg)) {
5249                     name = subName;
5250                 } else {
5251                     Log.w(TAG, "warning: pkg "
5252                             + pkg + " attempting to substitute app name '" + subName
5253                             + "' without holding perm "
5254                             + android.Manifest.permission.SUBSTITUTE_NOTIFICATION_APP_NAME);
5255                 }
5256             }
5257             if (TextUtils.isEmpty(name)) {
5258                 name = pm.getApplicationLabel(mContext.getApplicationInfo());
5259             }
5260             if (TextUtils.isEmpty(name)) {
5261                 // still nothing?
5262                 return null;
5263             }
5264 
5265             return String.valueOf(name);
5266         }
bindHeaderAppName(RemoteViews contentView, StandardTemplateParams p)5267         private void bindHeaderAppName(RemoteViews contentView, StandardTemplateParams p) {
5268             contentView.setTextViewText(R.id.app_name_text, loadHeaderAppName());
5269             if (isColorized(p)) {
5270                 setTextViewColorPrimary(contentView, R.id.app_name_text, p);
5271             } else {
5272                 contentView.setTextColor(R.id.app_name_text, getSecondaryTextColor(p));
5273             }
5274         }
5275 
isColorized(StandardTemplateParams p)5276         private boolean isColorized(StandardTemplateParams p) {
5277             return p.allowColorization && mN.isColorized();
5278         }
5279 
bindSmallIcon(RemoteViews contentView, StandardTemplateParams p)5280         private void bindSmallIcon(RemoteViews contentView, StandardTemplateParams p) {
5281             if (mN.mSmallIcon == null && mN.icon != 0) {
5282                 mN.mSmallIcon = Icon.createWithResource(mContext, mN.icon);
5283             }
5284             contentView.setImageViewIcon(R.id.icon, mN.mSmallIcon);
5285             contentView.setInt(R.id.icon, "setImageLevel", mN.iconLevel);
5286             processSmallIconColor(mN.mSmallIcon, contentView, p);
5287         }
5288 
5289         /**
5290          * @return true if the built notification will show the time or the chronometer; false
5291          *         otherwise
5292          */
showsTimeOrChronometer()5293         private boolean showsTimeOrChronometer() {
5294             return mN.showsTime() || mN.showsChronometer();
5295         }
5296 
resetStandardTemplateWithActions(RemoteViews big)5297         private void resetStandardTemplateWithActions(RemoteViews big) {
5298             // actions_container is only reset when there are no actions to avoid focus issues with
5299             // remote inputs.
5300             big.setViewVisibility(R.id.actions, View.GONE);
5301             big.removeAllViews(R.id.actions);
5302 
5303             big.setViewVisibility(R.id.notification_material_reply_container, View.GONE);
5304             big.setTextViewText(R.id.notification_material_reply_text_1, null);
5305             big.setViewVisibility(R.id.notification_material_reply_text_1_container, View.GONE);
5306             big.setViewVisibility(R.id.notification_material_reply_progress, View.GONE);
5307 
5308             big.setViewVisibility(R.id.notification_material_reply_text_2, View.GONE);
5309             big.setTextViewText(R.id.notification_material_reply_text_2, null);
5310             big.setViewVisibility(R.id.notification_material_reply_text_3, View.GONE);
5311             big.setTextViewText(R.id.notification_material_reply_text_3, null);
5312 
5313             big.setViewLayoutMarginBottomDimen(R.id.notification_action_list_margin_target,
5314                     R.dimen.notification_content_margin);
5315         }
5316 
applyStandardTemplateWithActions(int layoutId, TemplateBindResult result)5317         private RemoteViews applyStandardTemplateWithActions(int layoutId,
5318                 TemplateBindResult result) {
5319             return applyStandardTemplateWithActions(layoutId, mParams.reset().fillTextsFrom(this),
5320                     result);
5321         }
5322 
filterOutContextualActions( List<Notification.Action> actions)5323         private static List<Notification.Action> filterOutContextualActions(
5324                 List<Notification.Action> actions) {
5325             List<Notification.Action> nonContextualActions = new ArrayList<>();
5326             for (Notification.Action action : actions) {
5327                 if (!action.isContextual()) {
5328                     nonContextualActions.add(action);
5329                 }
5330             }
5331             return nonContextualActions;
5332         }
5333 
applyStandardTemplateWithActions(int layoutId, StandardTemplateParams p, TemplateBindResult result)5334         private RemoteViews applyStandardTemplateWithActions(int layoutId,
5335                 StandardTemplateParams p, TemplateBindResult result) {
5336             RemoteViews big = applyStandardTemplate(layoutId, p, result);
5337 
5338             resetStandardTemplateWithActions(big);
5339 
5340             boolean validRemoteInput = false;
5341 
5342             // In the UI contextual actions appear separately from the standard actions, so we
5343             // filter them out here.
5344             List<Notification.Action> nonContextualActions = filterOutContextualActions(mActions);
5345 
5346             int N = nonContextualActions.size();
5347             boolean emphazisedMode = mN.fullScreenIntent != null;
5348             big.setBoolean(R.id.actions, "setEmphasizedMode", emphazisedMode);
5349             if (N > 0) {
5350                 big.setViewVisibility(R.id.actions_container, View.VISIBLE);
5351                 big.setViewVisibility(R.id.actions, View.VISIBLE);
5352                 big.setViewLayoutMarginBottomDimen(R.id.notification_action_list_margin_target, 0);
5353                 if (N>MAX_ACTION_BUTTONS) N=MAX_ACTION_BUTTONS;
5354                 for (int i=0; i<N; i++) {
5355                     Action action = nonContextualActions.get(i);
5356 
5357                     boolean actionHasValidInput = hasValidRemoteInput(action);
5358                     validRemoteInput |= actionHasValidInput;
5359 
5360                     final RemoteViews button = generateActionButton(action, emphazisedMode, p);
5361                     if (actionHasValidInput && !emphazisedMode) {
5362                         // Clear the drawable
5363                         button.setInt(R.id.action0, "setBackgroundResource", 0);
5364                     }
5365                     big.addView(R.id.actions, button);
5366                 }
5367             } else {
5368                 big.setViewVisibility(R.id.actions_container, View.GONE);
5369             }
5370 
5371             RemoteInputHistoryItem[] replyText = getParcelableArrayFromBundle(
5372                     mN.extras, EXTRA_REMOTE_INPUT_HISTORY_ITEMS, RemoteInputHistoryItem.class);
5373             if (validRemoteInput && replyText != null && replyText.length > 0
5374                     && !TextUtils.isEmpty(replyText[0].getText())
5375                     && p.maxRemoteInputHistory > 0) {
5376                 boolean showSpinner = mN.extras.getBoolean(EXTRA_SHOW_REMOTE_INPUT_SPINNER);
5377                 big.setViewVisibility(R.id.notification_material_reply_container, View.VISIBLE);
5378                 big.setViewVisibility(R.id.notification_material_reply_text_1_container,
5379                         View.VISIBLE);
5380                 big.setTextViewText(R.id.notification_material_reply_text_1,
5381                         processTextSpans(replyText[0].getText()));
5382                 setTextViewColorSecondary(big, R.id.notification_material_reply_text_1, p);
5383                 big.setViewVisibility(R.id.notification_material_reply_progress,
5384                         showSpinner ? View.VISIBLE : View.GONE);
5385                 big.setProgressIndeterminateTintList(
5386                         R.id.notification_material_reply_progress,
5387                         ColorStateList.valueOf(
5388                                 isColorized(p) ? getPrimaryTextColor(p) : resolveContrastColor(p)));
5389 
5390                 if (replyText.length > 1 && !TextUtils.isEmpty(replyText[1].getText())
5391                         && p.maxRemoteInputHistory > 1) {
5392                     big.setViewVisibility(R.id.notification_material_reply_text_2, View.VISIBLE);
5393                     big.setTextViewText(R.id.notification_material_reply_text_2,
5394                             processTextSpans(replyText[1].getText()));
5395                     setTextViewColorSecondary(big, R.id.notification_material_reply_text_2, p);
5396 
5397                     if (replyText.length > 2 && !TextUtils.isEmpty(replyText[2].getText())
5398                             && p.maxRemoteInputHistory > 2) {
5399                         big.setViewVisibility(
5400                                 R.id.notification_material_reply_text_3, View.VISIBLE);
5401                         big.setTextViewText(R.id.notification_material_reply_text_3,
5402                                 processTextSpans(replyText[2].getText()));
5403                         setTextViewColorSecondary(big, R.id.notification_material_reply_text_3, p);
5404                     }
5405                 }
5406             }
5407 
5408             return big;
5409         }
5410 
hasValidRemoteInput(Action action)5411         private boolean hasValidRemoteInput(Action action) {
5412             if (TextUtils.isEmpty(action.title) || action.actionIntent == null) {
5413                 // Weird actions
5414                 return false;
5415             }
5416 
5417             RemoteInput[] remoteInputs = action.getRemoteInputs();
5418             if (remoteInputs == null) {
5419                 return false;
5420             }
5421 
5422             for (RemoteInput r : remoteInputs) {
5423                 CharSequence[] choices = r.getChoices();
5424                 if (r.getAllowFreeFormInput() || (choices != null && choices.length != 0)) {
5425                     return true;
5426                 }
5427             }
5428             return false;
5429         }
5430 
5431         /**
5432          * Construct a RemoteViews for the final 1U notification layout. In order:
5433          *   1. Custom contentView from the caller
5434          *   2. Style's proposed content view
5435          *   3. Standard template view
5436          */
createContentView()5437         public RemoteViews createContentView() {
5438             return createContentView(false /* increasedheight */ );
5439         }
5440 
5441         /**
5442          * Construct a RemoteViews for the smaller content view.
5443          *
5444          *   @param increasedHeight true if this layout be created with an increased height. Some
5445          *   styles may support showing more then just that basic 1U size
5446          *   and the system may decide to render important notifications
5447          *   slightly bigger even when collapsed.
5448          *
5449          *   @hide
5450          */
createContentView(boolean increasedHeight)5451         public RemoteViews createContentView(boolean increasedHeight) {
5452             if (mN.contentView != null && useExistingRemoteView()) {
5453                 return mN.contentView;
5454             } else if (mStyle != null) {
5455                 final RemoteViews styleView = mStyle.makeContentView(increasedHeight);
5456                 if (styleView != null) {
5457                     return styleView;
5458                 }
5459             }
5460             return applyStandardTemplate(getBaseLayoutResource(), null /* result */);
5461         }
5462 
useExistingRemoteView()5463         private boolean useExistingRemoteView() {
5464             return mStyle == null || (!mStyle.displayCustomViewInline()
5465                     && !mRebuildStyledRemoteViews);
5466         }
5467 
5468         /**
5469          * Construct a RemoteViews for the final big notification layout.
5470          */
createBigContentView()5471         public RemoteViews createBigContentView() {
5472             RemoteViews result = null;
5473             if (mN.bigContentView != null && useExistingRemoteView()) {
5474                 return mN.bigContentView;
5475             } else if (mStyle != null) {
5476                 result = mStyle.makeBigContentView();
5477                 hideLine1Text(result);
5478             } else if (mActions.size() != 0) {
5479                 result = applyStandardTemplateWithActions(getBigBaseLayoutResource(),
5480                         null /* result */);
5481             }
5482             makeHeaderExpanded(result);
5483             return result;
5484         }
5485 
5486         /**
5487          * Construct a RemoteViews for the final notification header only. This will not be
5488          * colorized.
5489          *
5490          * @hide
5491          */
makeNotificationHeader()5492         public RemoteViews makeNotificationHeader() {
5493             return makeNotificationHeader(mParams.reset().fillTextsFrom(this));
5494         }
5495 
5496         /**
5497          * Construct a RemoteViews for the final notification header only. This will not be
5498          * colorized.
5499          *
5500          * @param p the template params to inflate this with
5501          */
makeNotificationHeader(StandardTemplateParams p)5502         private RemoteViews makeNotificationHeader(StandardTemplateParams p) {
5503             // Headers on their own are never colorized
5504             p.disallowColorization();
5505             RemoteViews header = new BuilderRemoteViews(mContext.getApplicationInfo(),
5506                     R.layout.notification_template_header);
5507             resetNotificationHeader(header);
5508             bindNotificationHeader(header, p);
5509             return header;
5510         }
5511 
5512         /**
5513          * Construct a RemoteViews for the ambient version of the notification.
5514          *
5515          * @hide
5516          */
makeAmbientNotification()5517         public RemoteViews makeAmbientNotification() {
5518             RemoteViews headsUpContentView = createHeadsUpContentView(false /* increasedHeight */);
5519             if (headsUpContentView != null) {
5520                 return headsUpContentView;
5521             }
5522             return createContentView();
5523         }
5524 
hideLine1Text(RemoteViews result)5525         private void hideLine1Text(RemoteViews result) {
5526             if (result != null) {
5527                 result.setViewVisibility(R.id.text_line_1, View.GONE);
5528             }
5529         }
5530 
5531         /**
5532          * Adapt the Notification header if this view is used as an expanded view.
5533          *
5534          * @hide
5535          */
makeHeaderExpanded(RemoteViews result)5536         public static void makeHeaderExpanded(RemoteViews result) {
5537             if (result != null) {
5538                 result.setBoolean(R.id.notification_header, "setExpanded", true);
5539             }
5540         }
5541 
5542         /**
5543          * Construct a RemoteViews for the final heads-up notification layout.
5544          *
5545          * @param increasedHeight true if this layout be created with an increased height. Some
5546          * styles may support showing more then just that basic 1U size
5547          * and the system may decide to render important notifications
5548          * slightly bigger even when collapsed.
5549          *
5550          * @hide
5551          */
createHeadsUpContentView(boolean increasedHeight)5552         public RemoteViews createHeadsUpContentView(boolean increasedHeight) {
5553             if (mN.headsUpContentView != null && useExistingRemoteView()) {
5554                 return mN.headsUpContentView;
5555             } else if (mStyle != null) {
5556                 final RemoteViews styleView = mStyle.makeHeadsUpContentView(increasedHeight);
5557                 if (styleView != null) {
5558                     return styleView;
5559                 }
5560             } else if (mActions.size() == 0) {
5561                 return null;
5562             }
5563 
5564             // We only want at most a single remote input history to be shown here, otherwise
5565             // the content would become squished.
5566             StandardTemplateParams p = mParams.reset().fillTextsFrom(this)
5567                     .setMaxRemoteInputHistory(1);
5568             return applyStandardTemplateWithActions(getBigBaseLayoutResource(),
5569                     p,
5570                     null /* result */);
5571         }
5572 
5573         /**
5574          * Construct a RemoteViews for the final heads-up notification layout.
5575          */
createHeadsUpContentView()5576         public RemoteViews createHeadsUpContentView() {
5577             return createHeadsUpContentView(false /* useIncreasedHeight */);
5578         }
5579 
5580         /**
5581          * Construct a RemoteViews for the display in public contexts like on the lockscreen.
5582          *
5583          * @param isLowPriority is this notification low priority
5584          * @hide
5585          */
5586         @UnsupportedAppUsage
makePublicContentView(boolean isLowPriority)5587         public RemoteViews makePublicContentView(boolean isLowPriority) {
5588             if (mN.publicVersion != null) {
5589                 final Builder builder = recoverBuilder(mContext, mN.publicVersion);
5590                 return builder.createContentView();
5591             }
5592             Bundle savedBundle = mN.extras;
5593             Style style = mStyle;
5594             mStyle = null;
5595             Icon largeIcon = mN.mLargeIcon;
5596             mN.mLargeIcon = null;
5597             Bitmap largeIconLegacy = mN.largeIcon;
5598             mN.largeIcon = null;
5599             ArrayList<Action> actions = mActions;
5600             mActions = new ArrayList<>();
5601             Bundle publicExtras = new Bundle();
5602             publicExtras.putBoolean(EXTRA_SHOW_WHEN,
5603                     savedBundle.getBoolean(EXTRA_SHOW_WHEN));
5604             publicExtras.putBoolean(EXTRA_SHOW_CHRONOMETER,
5605                     savedBundle.getBoolean(EXTRA_SHOW_CHRONOMETER));
5606             publicExtras.putBoolean(EXTRA_CHRONOMETER_COUNT_DOWN,
5607                     savedBundle.getBoolean(EXTRA_CHRONOMETER_COUNT_DOWN));
5608             String appName = savedBundle.getString(EXTRA_SUBSTITUTE_APP_NAME);
5609             if (appName != null) {
5610                 publicExtras.putString(EXTRA_SUBSTITUTE_APP_NAME, appName);
5611             }
5612             mN.extras = publicExtras;
5613             RemoteViews view;
5614             StandardTemplateParams params = mParams.reset().fillTextsFrom(this);
5615             if (isLowPriority) {
5616                 params.forceDefaultColor();
5617             }
5618             view = makeNotificationHeader(params);
5619             view.setBoolean(R.id.notification_header, "setExpandOnlyOnButton", true);
5620             mN.extras = savedBundle;
5621             mN.mLargeIcon = largeIcon;
5622             mN.largeIcon = largeIconLegacy;
5623             mActions = actions;
5624             mStyle = style;
5625             return view;
5626         }
5627 
5628         /**
5629          * Construct a content view for the display when low - priority
5630          *
5631          * @param useRegularSubtext uses the normal subtext set if there is one available. Otherwise
5632          *                          a new subtext is created consisting of the content of the
5633          *                          notification.
5634          * @hide
5635          */
makeLowPriorityContentView(boolean useRegularSubtext)5636         public RemoteViews makeLowPriorityContentView(boolean useRegularSubtext) {
5637             StandardTemplateParams p = mParams.reset()
5638                     .forceDefaultColor()
5639                     .fillTextsFrom(this);
5640             if (!useRegularSubtext || TextUtils.isEmpty(mParams.summaryText)) {
5641                 p.summaryText(createSummaryText());
5642             }
5643             RemoteViews header = makeNotificationHeader(p);
5644             header.setBoolean(R.id.notification_header, "setAcceptAllTouches", true);
5645             return header;
5646         }
5647 
createSummaryText()5648         private CharSequence createSummaryText() {
5649             CharSequence titleText = mN.extras.getCharSequence(Notification.EXTRA_TITLE);
5650             if (USE_ONLY_TITLE_IN_LOW_PRIORITY_SUMMARY) {
5651                 return titleText;
5652             }
5653             SpannableStringBuilder summary = new SpannableStringBuilder();
5654             if (titleText == null) {
5655                 titleText = mN.extras.getCharSequence(Notification.EXTRA_TITLE_BIG);
5656             }
5657             BidiFormatter bidi = BidiFormatter.getInstance();
5658             if (titleText != null) {
5659                 summary.append(bidi.unicodeWrap(titleText));
5660             }
5661             CharSequence contentText = mN.extras.getCharSequence(Notification.EXTRA_TEXT);
5662             if (titleText != null && contentText != null) {
5663                 summary.append(bidi.unicodeWrap(mContext.getText(
5664                         R.string.notification_header_divider_symbol_with_spaces)));
5665             }
5666             if (contentText != null) {
5667                 summary.append(bidi.unicodeWrap(contentText));
5668             }
5669             return summary;
5670         }
5671 
generateActionButton(Action action, boolean emphazisedMode, StandardTemplateParams p)5672         private RemoteViews generateActionButton(Action action, boolean emphazisedMode,
5673                 StandardTemplateParams p) {
5674             final boolean tombstone = (action.actionIntent == null);
5675             RemoteViews button = new BuilderRemoteViews(mContext.getApplicationInfo(),
5676                     emphazisedMode ? getEmphasizedActionLayoutResource()
5677                             : tombstone ? getActionTombstoneLayoutResource()
5678                                     : getActionLayoutResource());
5679             if (!tombstone) {
5680                 button.setOnClickPendingIntent(R.id.action0, action.actionIntent);
5681             }
5682             button.setContentDescription(R.id.action0, action.title);
5683             if (action.mRemoteInputs != null) {
5684                 button.setRemoteInputs(R.id.action0, action.mRemoteInputs);
5685             }
5686             if (emphazisedMode) {
5687                 // change the background bgColor
5688                 CharSequence title = action.title;
5689                 ColorStateList[] outResultColor = null;
5690                 int background = resolveBackgroundColor(p);
5691                 if (isLegacy()) {
5692                     title = ContrastColorUtil.clearColorSpans(title);
5693                 } else {
5694                     outResultColor = new ColorStateList[1];
5695                     title = ensureColorSpanContrast(title, background, outResultColor);
5696                 }
5697                 button.setTextViewText(R.id.action0, processTextSpans(title));
5698                 setTextViewColorPrimary(button, R.id.action0, p);
5699                 int rippleColor;
5700                 boolean hasColorOverride = outResultColor != null && outResultColor[0] != null;
5701                 if (hasColorOverride) {
5702                     // There's a span spanning the full text, let's take it and use it as the
5703                     // background color
5704                     background = outResultColor[0].getDefaultColor();
5705                     int textColor = ContrastColorUtil.resolvePrimaryColor(mContext,
5706                             background, mInNightMode);
5707                     button.setTextColor(R.id.action0, textColor);
5708                     rippleColor = textColor;
5709                 } else if (getRawColor(p) != COLOR_DEFAULT && !isColorized(p)
5710                         && mTintActionButtons && !mInNightMode) {
5711                     rippleColor = resolveContrastColor(p);
5712                     button.setTextColor(R.id.action0, rippleColor);
5713                 } else {
5714                     rippleColor = getPrimaryTextColor(p);
5715                 }
5716                 // We only want about 20% alpha for the ripple
5717                 rippleColor = (rippleColor & 0x00ffffff) | 0x33000000;
5718                 button.setColorStateList(R.id.action0, "setRippleColor",
5719                         ColorStateList.valueOf(rippleColor));
5720                 button.setColorStateList(R.id.action0, "setButtonBackground",
5721                         ColorStateList.valueOf(background));
5722                 button.setBoolean(R.id.action0, "setHasStroke", !hasColorOverride);
5723             } else {
5724                 button.setTextViewText(R.id.action0, processTextSpans(
5725                         processLegacyText(action.title)));
5726                 if (isColorized(p)) {
5727                     setTextViewColorPrimary(button, R.id.action0, p);
5728                 } else if (getRawColor(p) != COLOR_DEFAULT && mTintActionButtons) {
5729                     button.setTextColor(R.id.action0, resolveContrastColor(p));
5730                 }
5731             }
5732             button.setIntTag(R.id.action0, R.id.notification_action_index_tag,
5733                     mActions.indexOf(action));
5734             return button;
5735         }
5736 
5737         /**
5738          * Ensures contrast on color spans against a background color. also returns the color of the
5739          * text if a span was found that spans over the whole text.
5740          *
5741          * @param charSequence the charSequence on which the spans are
5742          * @param background the background color to ensure the contrast against
5743          * @param outResultColor an array in which a color will be returned as the first element if
5744          *                    there exists a full length color span.
5745          * @return the contrasted charSequence
5746          */
ensureColorSpanContrast(CharSequence charSequence, int background, ColorStateList[] outResultColor)5747         private CharSequence ensureColorSpanContrast(CharSequence charSequence, int background,
5748                 ColorStateList[] outResultColor) {
5749             if (charSequence instanceof Spanned) {
5750                 Spanned ss = (Spanned) charSequence;
5751                 Object[] spans = ss.getSpans(0, ss.length(), Object.class);
5752                 SpannableStringBuilder builder = new SpannableStringBuilder(ss.toString());
5753                 for (Object span : spans) {
5754                     Object resultSpan = span;
5755                     int spanStart = ss.getSpanStart(span);
5756                     int spanEnd = ss.getSpanEnd(span);
5757                     boolean fullLength = (spanEnd - spanStart) == charSequence.length();
5758                     if (resultSpan instanceof CharacterStyle) {
5759                         resultSpan = ((CharacterStyle) span).getUnderlying();
5760                     }
5761                     if (resultSpan instanceof TextAppearanceSpan) {
5762                         TextAppearanceSpan originalSpan = (TextAppearanceSpan) resultSpan;
5763                         ColorStateList textColor = originalSpan.getTextColor();
5764                         if (textColor != null) {
5765                             int[] colors = textColor.getColors();
5766                             int[] newColors = new int[colors.length];
5767                             for (int i = 0; i < newColors.length; i++) {
5768                                 newColors[i] = ContrastColorUtil.ensureLargeTextContrast(
5769                                         colors[i], background, mInNightMode);
5770                             }
5771                             textColor = new ColorStateList(textColor.getStates().clone(),
5772                                     newColors);
5773                             if (fullLength) {
5774                                 outResultColor[0] = textColor;
5775                                 // Let's drop the color from the span
5776                                 textColor = null;
5777                             }
5778                             resultSpan = new TextAppearanceSpan(
5779                                     originalSpan.getFamily(),
5780                                     originalSpan.getTextStyle(),
5781                                     originalSpan.getTextSize(),
5782                                     textColor,
5783                                     originalSpan.getLinkTextColor());
5784                         }
5785                     } else if (resultSpan instanceof ForegroundColorSpan) {
5786                         ForegroundColorSpan originalSpan = (ForegroundColorSpan) resultSpan;
5787                         int foregroundColor = originalSpan.getForegroundColor();
5788                         foregroundColor = ContrastColorUtil.ensureLargeTextContrast(
5789                                 foregroundColor, background, mInNightMode);
5790                         if (fullLength) {
5791                             outResultColor[0] = ColorStateList.valueOf(foregroundColor);
5792                             resultSpan = null;
5793                         } else {
5794                             resultSpan = new ForegroundColorSpan(foregroundColor);
5795                         }
5796                     } else {
5797                         resultSpan = span;
5798                     }
5799                     if (resultSpan != null) {
5800                         builder.setSpan(resultSpan, spanStart, spanEnd, ss.getSpanFlags(span));
5801                     }
5802                 }
5803                 return builder;
5804             }
5805             return charSequence;
5806         }
5807 
5808         /**
5809          * @return Whether we are currently building a notification from a legacy (an app that
5810          *         doesn't create material notifications by itself) app.
5811          */
isLegacy()5812         private boolean isLegacy() {
5813             if (!mIsLegacyInitialized) {
5814                 mIsLegacy = mContext.getApplicationInfo().targetSdkVersion
5815                         < Build.VERSION_CODES.LOLLIPOP;
5816                 mIsLegacyInitialized = true;
5817             }
5818             return mIsLegacy;
5819         }
5820 
5821         private CharSequence processLegacyText(CharSequence charSequence) {
5822             boolean isAlreadyLightText = isLegacy() || textColorsNeedInversion();
5823             if (isAlreadyLightText) {
5824                 return getColorUtil().invertCharSequenceColors(charSequence);
5825             } else {
5826                 return charSequence;
5827             }
5828         }
5829 
5830         /**
5831          * Apply any necessariy colors to the small icon
5832          */
5833         private void processSmallIconColor(Icon smallIcon, RemoteViews contentView,
5834                 StandardTemplateParams p) {
5835             boolean colorable = !isLegacy() || getColorUtil().isGrayscaleIcon(mContext, smallIcon);
5836             int color;
5837             if (isColorized(p)) {
5838                 color = getPrimaryTextColor(p);
5839             } else {
5840                 color = resolveContrastColor(p);
5841             }
5842             if (colorable) {
5843                 contentView.setDrawableTint(R.id.icon, false, color,
5844                         PorterDuff.Mode.SRC_ATOP);
5845 
5846             }
5847             contentView.setInt(R.id.icon, "setOriginalIconColor",
5848                     colorable ? color : NotificationHeaderView.NO_COLOR);
5849         }
5850 
5851         /**
5852          * Make the largeIcon dark if it's a fake smallIcon (that is,
5853          * if it's grayscale).
5854          */
5855         // TODO: also check bounds, transparency, that sort of thing.
5856         private void processLargeLegacyIcon(Icon largeIcon, RemoteViews contentView,
5857                 StandardTemplateParams p) {
5858             if (largeIcon != null && isLegacy()
5859                     && getColorUtil().isGrayscaleIcon(mContext, largeIcon)) {
5860                 // resolve color will fall back to the default when legacy
5861                 contentView.setDrawableTint(R.id.icon, false, resolveContrastColor(p),
5862                         PorterDuff.Mode.SRC_ATOP);
5863             }
5864         }
5865 
5866         private void sanitizeColor() {
5867             if (mN.color != COLOR_DEFAULT) {
5868                 mN.color |= 0xFF000000; // no alpha for custom colors
5869             }
5870         }
5871 
5872         int resolveContrastColor(StandardTemplateParams p) {
5873             int rawColor = getRawColor(p);
5874             if (mCachedContrastColorIsFor == rawColor && mCachedContrastColor != COLOR_INVALID) {
5875                 return mCachedContrastColor;
5876             }
5877 
5878             int color;
5879             int background = mContext.getColor(
5880                     com.android.internal.R.color.notification_material_background_color);
5881             if (rawColor == COLOR_DEFAULT) {
5882                 ensureColors(p);
5883                 color = ContrastColorUtil.resolveDefaultColor(mContext, background, mInNightMode);
5884             } else {
5885                 color = ContrastColorUtil.resolveContrastColor(mContext, rawColor,
5886                         background, mInNightMode);
5887             }
5888             if (Color.alpha(color) < 255) {
5889                 // alpha doesn't go well for color filters, so let's blend it manually
5890                 color = ContrastColorUtil.compositeColors(color, background);
5891             }
5892             mCachedContrastColorIsFor = rawColor;
5893             return mCachedContrastColor = color;
5894         }
5895 
5896         /**
5897          * Return the raw color of this Notification, which doesn't necessarily satisfy contrast.
5898          *
5899          * @see #resolveContrastColor(StandardTemplateParams) for the contrasted color
5900          * @param p the template params to inflate this with
5901          */
5902         private int getRawColor(StandardTemplateParams p) {
5903             if (p.forceDefaultColor) {
5904                 return COLOR_DEFAULT;
5905             }
5906             return mN.color;
5907         }
5908 
5909         int resolveNeutralColor() {
5910             if (mNeutralColor != COLOR_INVALID) {
5911                 return mNeutralColor;
5912             }
5913             int background = mContext.getColor(
5914                     com.android.internal.R.color.notification_material_background_color);
5915             mNeutralColor = ContrastColorUtil.resolveDefaultColor(mContext, background,
5916                     mInNightMode);
5917             if (Color.alpha(mNeutralColor) < 255) {
5918                 // alpha doesn't go well for color filters, so let's blend it manually
5919                 mNeutralColor = ContrastColorUtil.compositeColors(mNeutralColor, background);
5920             }
5921             return mNeutralColor;
5922         }
5923 
5924         /**
5925          * Apply the unstyled operations and return a new {@link Notification} object.
5926          * @hide
5927          */
5928         @NonNull
5929         public Notification buildUnstyled() {
5930             if (mActions.size() > 0) {
5931                 mN.actions = new Action[mActions.size()];
5932                 mActions.toArray(mN.actions);
5933             }
5934             if (!mPersonList.isEmpty()) {
5935                 mN.extras.putParcelableArrayList(EXTRA_PEOPLE_LIST, mPersonList);
5936             }
5937             if (mN.bigContentView != null || mN.contentView != null
5938                     || mN.headsUpContentView != null) {
5939                 mN.extras.putBoolean(EXTRA_CONTAINS_CUSTOM_VIEW, true);
5940             }
5941             return mN;
5942         }
5943 
5944         /**
5945          * Creates a Builder from an existing notification so further changes can be made.
5946          * @param context The context for your application / activity.
5947          * @param n The notification to create a Builder from.
5948          */
5949         @NonNull
5950         public static Notification.Builder recoverBuilder(Context context, Notification n) {
5951             // Re-create notification context so we can access app resources.
5952             ApplicationInfo applicationInfo = n.extras.getParcelable(
5953                     EXTRA_BUILDER_APPLICATION_INFO);
5954             Context builderContext;
5955             if (applicationInfo != null) {
5956                 try {
5957                     builderContext = context.createApplicationContext(applicationInfo,
5958                             Context.CONTEXT_RESTRICTED);
5959                 } catch (NameNotFoundException e) {
5960                     Log.e(TAG, "ApplicationInfo " + applicationInfo + " not found");
5961                     builderContext = context;  // try with our context
5962                 }
5963             } else {
5964                 builderContext = context; // try with given context
5965             }
5966 
5967             return new Builder(builderContext, n);
5968         }
5969 
5970         /**
5971          * Determines whether the platform can generate contextual actions for a notification.
5972          * By default this is true.
5973          */
5974         @NonNull
5975         public Builder setAllowSystemGeneratedContextualActions(boolean allowed) {
5976             mN.mAllowSystemGeneratedContextualActions = allowed;
5977             return this;
5978         }
5979 
5980         /**
5981          * @deprecated Use {@link #build()} instead.
5982          */
5983         @Deprecated
5984         public Notification getNotification() {
5985             return build();
5986         }
5987 
5988         /**
5989          * Combine all of the options that have been set and return a new {@link Notification}
5990          * object.
5991          *
5992          * If this notification has {@link BubbleMetadata} attached that was created with
5993          * a shortcutId a check will be performed to ensure the shortcutId supplied to bubble
5994          * metadata matches the shortcutId set on the  notification builder, if one was set.
5995          * If the shortcutId's were specified but do not match, an exception is thrown here.
5996          *
5997          * @see BubbleMetadata.Builder#Builder(String)
5998          * @see #setShortcutId(String)
5999          */
6000         @NonNull
6001         public Notification build() {
6002             // Check shortcut id matches
6003             if (mN.mShortcutId != null
6004                     && mN.mBubbleMetadata != null
6005                     && mN.mBubbleMetadata.getShortcutId() != null
6006                     && !mN.mShortcutId.equals(mN.mBubbleMetadata.getShortcutId())) {
6007                 throw new IllegalArgumentException(
6008                         "Notification and BubbleMetadata shortcut id's don't match,"
6009                                 + " notification: " + mN.mShortcutId
6010                                 + " vs bubble: " + mN.mBubbleMetadata.getShortcutId());
6011             }
6012 
6013             // first, add any extras from the calling code
6014             if (mUserExtras != null) {
6015                 mN.extras = getAllExtras();
6016             }
6017 
6018             mN.creationTime = System.currentTimeMillis();
6019 
6020             // lazy stuff from mContext; see comment in Builder(Context, Notification)
6021             Notification.addFieldsFromContext(mContext, mN);
6022 
6023             buildUnstyled();
6024 
6025             if (mStyle != null) {
6026                 mStyle.reduceImageSizes(mContext);
6027                 mStyle.purgeResources();
6028                 mStyle.validate(mContext);
6029                 mStyle.buildStyled(mN);
6030             }
6031             mN.reduceImageSizes(mContext);
6032 
6033             if (mContext.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N
6034                     && (useExistingRemoteView())) {
6035                 if (mN.contentView == null) {
6036                     mN.contentView = createContentView();
6037                     mN.extras.putInt(EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT,
6038                             mN.contentView.getSequenceNumber());
6039                 }
6040                 if (mN.bigContentView == null) {
6041                     mN.bigContentView = createBigContentView();
6042                     if (mN.bigContentView != null) {
6043                         mN.extras.putInt(EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT,
6044                                 mN.bigContentView.getSequenceNumber());
6045                     }
6046                 }
6047                 if (mN.headsUpContentView == null) {
6048                     mN.headsUpContentView = createHeadsUpContentView();
6049                     if (mN.headsUpContentView != null) {
6050                         mN.extras.putInt(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT,
6051                                 mN.headsUpContentView.getSequenceNumber());
6052                     }
6053                 }
6054             }
6055 
6056             if ((mN.defaults & DEFAULT_LIGHTS) != 0) {
6057                 mN.flags |= FLAG_SHOW_LIGHTS;
6058             }
6059 
6060             mN.allPendingIntents = null;
6061 
6062             return mN;
6063         }
6064 
6065         /**
6066          * Apply this Builder to an existing {@link Notification} object.
6067          *
6068          * @hide
6069          */
6070         @NonNull
6071         public Notification buildInto(@NonNull Notification n) {
6072             build().cloneInto(n, true);
6073             return n;
6074         }
6075 
6076         /**
6077          * Removes RemoteViews that were created for compatibility from {@param n}, if they did not
6078          * change.
6079          *
6080          * @return {@param n}, if no stripping is needed, otherwise a stripped clone of {@param n}.
6081          *
6082          * @hide
6083          */
6084         public static Notification maybeCloneStrippedForDelivery(Notification n) {
6085             String templateClass = n.extras.getString(EXTRA_TEMPLATE);
6086 
6087             // Only strip views for known Styles because we won't know how to
6088             // re-create them otherwise.
6089             if (!TextUtils.isEmpty(templateClass)
6090                     && getNotificationStyleClass(templateClass) == null) {
6091                 return n;
6092             }
6093 
6094             // Only strip unmodified BuilderRemoteViews.
6095             boolean stripContentView = n.contentView instanceof BuilderRemoteViews &&
6096                     n.extras.getInt(EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT, -1) ==
6097                             n.contentView.getSequenceNumber();
6098             boolean stripBigContentView = n.bigContentView instanceof BuilderRemoteViews &&
6099                     n.extras.getInt(EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT, -1) ==
6100                             n.bigContentView.getSequenceNumber();
6101             boolean stripHeadsUpContentView = n.headsUpContentView instanceof BuilderRemoteViews &&
6102                     n.extras.getInt(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT, -1) ==
6103                             n.headsUpContentView.getSequenceNumber();
6104 
6105             // Nothing to do here, no need to clone.
6106             if (!stripContentView && !stripBigContentView && !stripHeadsUpContentView) {
6107                 return n;
6108             }
6109 
6110             Notification clone = n.clone();
6111             if (stripContentView) {
6112                 clone.contentView = null;
6113                 clone.extras.remove(EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT);
6114             }
6115             if (stripBigContentView) {
6116                 clone.bigContentView = null;
6117                 clone.extras.remove(EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT);
6118             }
6119             if (stripHeadsUpContentView) {
6120                 clone.headsUpContentView = null;
6121                 clone.extras.remove(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT);
6122             }
6123             return clone;
6124         }
6125 
6126         @UnsupportedAppUsage
6127         private int getBaseLayoutResource() {
6128             return R.layout.notification_template_material_base;
6129         }
6130 
6131         private int getBigBaseLayoutResource() {
6132             return R.layout.notification_template_material_big_base;
6133         }
6134 
6135         private int getBigPictureLayoutResource() {
6136             return R.layout.notification_template_material_big_picture;
6137         }
6138 
6139         private int getBigTextLayoutResource() {
6140             return R.layout.notification_template_material_big_text;
6141         }
6142 
6143         private int getInboxLayoutResource() {
6144             return R.layout.notification_template_material_inbox;
6145         }
6146 
6147         private int getMessagingLayoutResource() {
6148             return R.layout.notification_template_material_messaging;
6149         }
6150 
6151         private int getConversationLayoutResource() {
6152             return R.layout.notification_template_material_conversation;
6153         }
6154 
6155         private int getActionLayoutResource() {
6156             return R.layout.notification_material_action;
6157         }
6158 
6159         private int getEmphasizedActionLayoutResource() {
6160             return R.layout.notification_material_action_emphasized;
6161         }
6162 
6163         private int getActionTombstoneLayoutResource() {
6164             return R.layout.notification_material_action_tombstone;
6165         }
6166 
6167         private int getBackgroundColor(StandardTemplateParams p) {
6168             if (isColorized(p)) {
6169                 return mBackgroundColor != COLOR_INVALID ? mBackgroundColor : getRawColor(p);
6170             } else {
6171                 return COLOR_DEFAULT;
6172             }
6173         }
6174 
6175         /**
6176          * Gets a neutral color that can be used for icons or similar that should not stand out.
6177          * @param p the template params to inflate this with
6178          */
6179         private int getNeutralColor(StandardTemplateParams p) {
6180             if (isColorized(p)) {
6181                 return getSecondaryTextColor(p);
6182             } else {
6183                 return resolveNeutralColor();
6184             }
6185         }
6186 
6187         /**
6188          * Same as getBackgroundColor but also resolved the default color to the background.
6189          * @param p the template params to inflate this with
6190          */
6191         private int resolveBackgroundColor(StandardTemplateParams p) {
6192             int backgroundColor = getBackgroundColor(p);
6193             if (backgroundColor == COLOR_DEFAULT) {
6194                 backgroundColor = mContext.getColor(
6195                         com.android.internal.R.color.notification_material_background_color);
6196             }
6197             return backgroundColor;
6198         }
6199 
6200         private boolean shouldTintActionButtons() {
6201             return mTintActionButtons;
6202         }
6203 
6204         private boolean textColorsNeedInversion() {
6205             if (mStyle == null || !MediaStyle.class.equals(mStyle.getClass())) {
6206                 return false;
6207             }
6208             int targetSdkVersion = mContext.getApplicationInfo().targetSdkVersion;
6209             return targetSdkVersion > Build.VERSION_CODES.M
6210                     && targetSdkVersion < Build.VERSION_CODES.O;
6211         }
6212 
6213         /**
6214          * Set a color palette to be used as the background and textColors
6215          *
6216          * @param backgroundColor the color to be used as the background
6217          * @param foregroundColor the color to be used as the foreground
6218          *
6219          * @hide
6220          */
6221         public void setColorPalette(int backgroundColor, int foregroundColor) {
6222             mBackgroundColor = backgroundColor;
6223             mForegroundColor = foregroundColor;
6224             mTextColorsAreForBackground = COLOR_INVALID;
6225             ensureColors(mParams.reset().fillTextsFrom(this));
6226         }
6227 
6228         /**
6229          * Forces all styled remoteViews to be built from scratch and not use any cached
6230          * RemoteViews.
6231          * This is needed for legacy apps that are baking in their remoteviews into the
6232          * notification.
6233          *
6234          * @hide
6235          */
6236         public void setRebuildStyledRemoteViews(boolean rebuild) {
6237             mRebuildStyledRemoteViews = rebuild;
6238         }
6239 
6240         /**
6241          * Get the text that should be displayed in the statusBar when heads upped. This is
6242          * usually just the app name, but may be different depending on the style.
6243          *
6244          * @param publicMode If true, return a text that is safe to display in public.
6245          *
6246          * @hide
6247          */
6248         public CharSequence getHeadsUpStatusBarText(boolean publicMode) {
6249             if (mStyle != null && !publicMode) {
6250                 CharSequence text = mStyle.getHeadsUpStatusBarText();
6251                 if (!TextUtils.isEmpty(text)) {
6252                     return text;
6253                 }
6254             }
6255             return loadHeaderAppName();
6256         }
6257 
6258         /**
6259          * @return if this builder uses a template
6260          *
6261          * @hide
6262          */
6263         public boolean usesTemplate() {
6264             return (mN.contentView == null && mN.headsUpContentView == null
6265                     && mN.bigContentView == null)
6266                     || (mStyle != null && mStyle.displayCustomViewInline());
6267         }
6268     }
6269 
6270     /**
6271      * Reduces the image sizes to conform to a maximum allowed size. This also processes all custom
6272      * remote views.
6273      *
6274      * @hide
6275      */
6276     void reduceImageSizes(Context context) {
6277         if (extras.getBoolean(EXTRA_REDUCED_IMAGES)) {
6278             return;
6279         }
6280         boolean isLowRam = ActivityManager.isLowRamDeviceStatic();
6281         if (mLargeIcon != null || largeIcon != null) {
6282             Resources resources = context.getResources();
6283             Class<? extends Style> style = getNotificationStyle();
6284             int maxWidth = resources.getDimensionPixelSize(isLowRam
6285                     ? R.dimen.notification_right_icon_size_low_ram
6286                     : R.dimen.notification_right_icon_size);
6287             int maxHeight = maxWidth;
6288             if (MediaStyle.class.equals(style)
6289                     || DecoratedMediaCustomViewStyle.class.equals(style)) {
6290                 maxHeight = resources.getDimensionPixelSize(isLowRam
6291                         ? R.dimen.notification_media_image_max_height_low_ram
6292                         : R.dimen.notification_media_image_max_height);
6293                 maxWidth = resources.getDimensionPixelSize(isLowRam
6294                         ? R.dimen.notification_media_image_max_width_low_ram
6295                         : R.dimen.notification_media_image_max_width);
6296             }
6297             if (mLargeIcon != null) {
6298                 mLargeIcon.scaleDownIfNecessary(maxWidth, maxHeight);
6299             }
6300             if (largeIcon != null) {
6301                 largeIcon = Icon.scaleDownIfNecessary(largeIcon, maxWidth, maxHeight);
6302             }
6303         }
6304         reduceImageSizesForRemoteView(contentView, context, isLowRam);
6305         reduceImageSizesForRemoteView(headsUpContentView, context, isLowRam);
6306         reduceImageSizesForRemoteView(bigContentView, context, isLowRam);
6307         extras.putBoolean(EXTRA_REDUCED_IMAGES, true);
6308     }
6309 
6310     private void reduceImageSizesForRemoteView(RemoteViews remoteView, Context context,
6311             boolean isLowRam) {
6312         if (remoteView != null) {
6313             Resources resources = context.getResources();
6314             int maxWidth = resources.getDimensionPixelSize(isLowRam
6315                     ? R.dimen.notification_custom_view_max_image_width_low_ram
6316                     : R.dimen.notification_custom_view_max_image_width);
6317             int maxHeight = resources.getDimensionPixelSize(isLowRam
6318                     ? R.dimen.notification_custom_view_max_image_height_low_ram
6319                     : R.dimen.notification_custom_view_max_image_height);
6320             remoteView.reduceImageSizes(maxWidth, maxHeight);
6321         }
6322     }
6323 
6324     /**
6325      * @return whether this notification is a foreground service notification
6326      * @hide
6327      */
6328     public boolean isForegroundService() {
6329         return (flags & Notification.FLAG_FOREGROUND_SERVICE) != 0;
6330     }
6331 
6332     /**
6333      * @return whether this notification has a media session attached
6334      * @hide
6335      */
6336     public boolean hasMediaSession() {
6337         return extras.getParcelable(Notification.EXTRA_MEDIA_SESSION) != null;
6338     }
6339 
6340     /**
6341      * @return the style class of this notification
6342      * @hide
6343      */
6344     public Class<? extends Notification.Style> getNotificationStyle() {
6345         String templateClass = extras.getString(Notification.EXTRA_TEMPLATE);
6346 
6347         if (!TextUtils.isEmpty(templateClass)) {
6348             return Notification.getNotificationStyleClass(templateClass);
6349         }
6350         return null;
6351     }
6352 
6353     /**
6354      * @return true if this notification is colorized.
6355      *
6356      * @hide
6357      */
6358     public boolean isColorized() {
6359         if (isColorizedMedia()) {
6360             return true;
6361         }
6362         return extras.getBoolean(EXTRA_COLORIZED)
6363                 && (hasColorizedPermission() || isForegroundService());
6364     }
6365 
6366     /**
6367      * Returns whether an app can colorize due to the android.permission.USE_COLORIZED_NOTIFICATIONS
6368      * permission. The permission is checked when a notification is enqueued.
6369      */
6370     private boolean hasColorizedPermission() {
6371         return (flags & Notification.FLAG_CAN_COLORIZE) != 0;
6372     }
6373 
6374     /**
6375      * @return true if this notification is colorized and it is a media notification
6376      *
6377      * @hide
6378      */
6379     public boolean isColorizedMedia() {
6380         Class<? extends Style> style = getNotificationStyle();
6381         if (MediaStyle.class.equals(style)) {
6382             Boolean colorized = (Boolean) extras.get(EXTRA_COLORIZED);
6383             if ((colorized == null || colorized) && hasMediaSession()) {
6384                 return true;
6385             }
6386         } else if (DecoratedMediaCustomViewStyle.class.equals(style)) {
6387             if (extras.getBoolean(EXTRA_COLORIZED) && hasMediaSession()) {
6388                 return true;
6389             }
6390         }
6391         return false;
6392     }
6393 
6394 
6395     /**
6396      * @return true if this is a media notification
6397      *
6398      * @hide
6399      */
6400     public boolean isMediaNotification() {
6401         Class<? extends Style> style = getNotificationStyle();
6402         if (MediaStyle.class.equals(style)) {
6403             return true;
6404         } else if (DecoratedMediaCustomViewStyle.class.equals(style)) {
6405             return true;
6406         }
6407         return false;
6408     }
6409 
6410     /**
6411      * @return true if this notification is showing as a bubble
6412      *
6413      * @hide
6414      */
6415     public boolean isBubbleNotification() {
6416         return (flags & Notification.FLAG_BUBBLE) != 0;
6417     }
6418 
6419     private boolean hasLargeIcon() {
6420         return mLargeIcon != null || largeIcon != null;
6421     }
6422 
6423     /**
6424      * @return true if the notification will show the time; false otherwise
6425      * @hide
6426      */
6427     public boolean showsTime() {
6428         return when != 0 && extras.getBoolean(EXTRA_SHOW_WHEN);
6429     }
6430 
6431     /**
6432      * @return true if the notification will show a chronometer; false otherwise
6433      * @hide
6434      */
6435     public boolean showsChronometer() {
6436         return when != 0 && extras.getBoolean(EXTRA_SHOW_CHRONOMETER);
6437     }
6438 
6439     /**
6440      * @removed
6441      */
6442     @SystemApi
6443     public static Class<? extends Style> getNotificationStyleClass(String templateClass) {
6444         Class<? extends Style>[] classes = new Class[] {
6445                 BigTextStyle.class, BigPictureStyle.class, InboxStyle.class, MediaStyle.class,
6446                 DecoratedCustomViewStyle.class, DecoratedMediaCustomViewStyle.class,
6447                 MessagingStyle.class };
6448         for (Class<? extends Style> innerClass : classes) {
6449             if (templateClass.equals(innerClass.getName())) {
6450                 return innerClass;
6451             }
6452         }
6453         return null;
6454     }
6455 
6456     /**
6457      * An object that can apply a rich notification style to a {@link Notification.Builder}
6458      * object.
6459      */
6460     public static abstract class Style {
6461 
6462         /**
6463          * The number of items allowed simulatanously in the remote input history.
6464          * @hide
6465          */
6466         static final int MAX_REMOTE_INPUT_HISTORY_LINES = 3;
6467         private CharSequence mBigContentTitle;
6468 
6469         /**
6470          * @hide
6471          */
6472         protected CharSequence mSummaryText = null;
6473 
6474         /**
6475          * @hide
6476          */
6477         protected boolean mSummaryTextSet = false;
6478 
6479         protected Builder mBuilder;
6480 
6481         /**
6482          * Overrides ContentTitle in the big form of the template.
6483          * This defaults to the value passed to setContentTitle().
6484          */
6485         protected void internalSetBigContentTitle(CharSequence title) {
6486             mBigContentTitle = title;
6487         }
6488 
6489         /**
6490          * Set the first line of text after the detail section in the big form of the template.
6491          */
6492         protected void internalSetSummaryText(CharSequence cs) {
6493             mSummaryText = cs;
6494             mSummaryTextSet = true;
6495         }
6496 
6497         public void setBuilder(Builder builder) {
6498             if (mBuilder != builder) {
6499                 mBuilder = builder;
6500                 if (mBuilder != null) {
6501                     mBuilder.setStyle(this);
6502                 }
6503             }
6504         }
6505 
6506         protected void checkBuilder() {
6507             if (mBuilder == null) {
6508                 throw new IllegalArgumentException("Style requires a valid Builder object");
6509             }
6510         }
6511 
6512         protected RemoteViews getStandardView(int layoutId) {
6513             StandardTemplateParams p = mBuilder.mParams.reset().fillTextsFrom(mBuilder);
6514             return getStandardView(layoutId, p, null);
6515         }
6516 
6517 
6518         /**
6519          * Get the standard view for this style.
6520          *
6521          * @param layoutId The layout id to use.
6522          * @param p the params for this inflation.
6523          * @param result The result where template bind information is saved.
6524          * @return A remoteView for this style.
6525          * @hide
6526          */
6527         protected RemoteViews getStandardView(int layoutId, StandardTemplateParams p,
6528                 TemplateBindResult result) {
6529             checkBuilder();
6530 
6531             if (mBigContentTitle != null) {
6532                 p.title = mBigContentTitle;
6533             }
6534 
6535             RemoteViews contentView = mBuilder.applyStandardTemplateWithActions(layoutId, p,
6536                     result);
6537 
6538             if (mBigContentTitle != null && mBigContentTitle.equals("")) {
6539                 contentView.setViewVisibility(R.id.line1, View.GONE);
6540             } else {
6541                 contentView.setViewVisibility(R.id.line1, View.VISIBLE);
6542             }
6543 
6544             return contentView;
6545         }
6546 
6547         /**
6548          * Construct a Style-specific RemoteViews for the collapsed notification layout.
6549          * The default implementation has nothing additional to add.
6550          *
6551          * @param increasedHeight true if this layout be created with an increased height.
6552          * @hide
6553          */
6554         public RemoteViews makeContentView(boolean increasedHeight) {
6555             return null;
6556         }
6557 
6558         /**
6559          * Construct a Style-specific RemoteViews for the final big notification layout.
6560          * @hide
6561          */
6562         public RemoteViews makeBigContentView() {
6563             return null;
6564         }
6565 
6566         /**
6567          * Construct a Style-specific RemoteViews for the final HUN layout.
6568          *
6569          * @param increasedHeight true if this layout be created with an increased height.
6570          * @hide
6571          */
6572         public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
6573             return null;
6574         }
6575 
6576         /**
6577          * Apply any style-specific extras to this notification before shipping it out.
6578          * @hide
6579          */
6580         public void addExtras(Bundle extras) {
6581             if (mSummaryTextSet) {
6582                 extras.putCharSequence(EXTRA_SUMMARY_TEXT, mSummaryText);
6583             }
6584             if (mBigContentTitle != null) {
6585                 extras.putCharSequence(EXTRA_TITLE_BIG, mBigContentTitle);
6586             }
6587             extras.putString(EXTRA_TEMPLATE, this.getClass().getName());
6588         }
6589 
6590         /**
6591          * Reconstruct the internal state of this Style object from extras.
6592          * @hide
6593          */
6594         protected void restoreFromExtras(Bundle extras) {
6595             if (extras.containsKey(EXTRA_SUMMARY_TEXT)) {
6596                 mSummaryText = extras.getCharSequence(EXTRA_SUMMARY_TEXT);
6597                 mSummaryTextSet = true;
6598             }
6599             if (extras.containsKey(EXTRA_TITLE_BIG)) {
6600                 mBigContentTitle = extras.getCharSequence(EXTRA_TITLE_BIG);
6601             }
6602         }
6603 
6604 
6605         /**
6606          * @hide
6607          */
6608         public Notification buildStyled(Notification wip) {
6609             addExtras(wip.extras);
6610             return wip;
6611         }
6612 
6613         /**
6614          * @hide
6615          */
6616         public void purgeResources() {}
6617 
6618         /**
6619          * Calls {@link android.app.Notification.Builder#build()} on the Builder this Style is
6620          * attached to.
6621          *
6622          * @return the fully constructed Notification.
6623          */
6624         public Notification build() {
6625             checkBuilder();
6626             return mBuilder.build();
6627         }
6628 
6629         /**
6630          * @hide
6631          * @return true if the style positions the progress bar on the second line; false if the
6632          *         style hides the progress bar
6633          */
6634         protected boolean hasProgress() {
6635             return true;
6636         }
6637 
6638         /**
6639          * @hide
6640          * @return Whether we should put the summary be put into the notification header
6641          */
6642         public boolean hasSummaryInHeader() {
6643             return true;
6644         }
6645 
6646         /**
6647          * @hide
6648          * @return Whether custom content views are displayed inline in the style
6649          */
6650         public boolean displayCustomViewInline() {
6651             return false;
6652         }
6653 
6654         /**
6655          * Reduces the image sizes contained in this style.
6656          *
6657          * @hide
6658          */
6659         public void reduceImageSizes(Context context) {
6660         }
6661 
6662         /**
6663          * Validate that this style was properly composed. This is called at build time.
6664          * @hide
6665          */
6666         public void validate(Context context) {
6667         }
6668 
6669         /**
6670          * @hide
6671          */
6672         public abstract boolean areNotificationsVisiblyDifferent(Style other);
6673 
6674         /**
6675          * @return the text that should be displayed in the statusBar when heads-upped.
6676          * If {@code null} is returned, the default implementation will be used.
6677          *
6678          * @hide
6679          */
6680         public CharSequence getHeadsUpStatusBarText() {
6681             return null;
6682         }
6683     }
6684 
6685     /**
6686      * Helper class for generating large-format notifications that include a large image attachment.
6687      *
6688      * Here's how you'd set the <code>BigPictureStyle</code> on a notification:
6689      * <pre class="prettyprint">
6690      * Notification notif = new Notification.Builder(mContext)
6691      *     .setContentTitle(&quot;New photo from &quot; + sender.toString())
6692      *     .setContentText(subject)
6693      *     .setSmallIcon(R.drawable.new_post)
6694      *     .setLargeIcon(aBitmap)
6695      *     .setStyle(new Notification.BigPictureStyle()
6696      *         .bigPicture(aBigBitmap))
6697      *     .build();
6698      * </pre>
6699      *
6700      * @see Notification#bigContentView
6701      */
6702     public static class BigPictureStyle extends Style {
6703         private Bitmap mPicture;
6704         private Icon mBigLargeIcon;
6705         private boolean mBigLargeIconSet = false;
6706 
6707         public BigPictureStyle() {
6708         }
6709 
6710         /**
6711          * @deprecated use {@code BigPictureStyle()}.
6712          */
6713         @Deprecated
6714         public BigPictureStyle(Builder builder) {
6715             setBuilder(builder);
6716         }
6717 
6718         /**
6719          * Overrides ContentTitle in the big form of the template.
6720          * This defaults to the value passed to setContentTitle().
6721          */
6722         public BigPictureStyle setBigContentTitle(CharSequence title) {
6723             internalSetBigContentTitle(safeCharSequence(title));
6724             return this;
6725         }
6726 
6727         /**
6728          * Set the first line of text after the detail section in the big form of the template.
6729          */
6730         public BigPictureStyle setSummaryText(CharSequence cs) {
6731             internalSetSummaryText(safeCharSequence(cs));
6732             return this;
6733         }
6734 
6735         /**
6736          * @hide
6737          */
6738         public Bitmap getBigPicture() {
6739             return mPicture;
6740         }
6741 
6742         /**
6743          * Provide the bitmap to be used as the payload for the BigPicture notification.
6744          */
6745         public BigPictureStyle bigPicture(Bitmap b) {
6746             mPicture = b;
6747             return this;
6748         }
6749 
6750         /**
6751          * Override the large icon when the big notification is shown.
6752          */
6753         public BigPictureStyle bigLargeIcon(Bitmap b) {
6754             return bigLargeIcon(b != null ? Icon.createWithBitmap(b) : null);
6755         }
6756 
6757         /**
6758          * Override the large icon when the big notification is shown.
6759          */
6760         public BigPictureStyle bigLargeIcon(Icon icon) {
6761             mBigLargeIconSet = true;
6762             mBigLargeIcon = icon;
6763             return this;
6764         }
6765 
6766         /** @hide */
6767         public static final int MIN_ASHMEM_BITMAP_SIZE = 128 * (1 << 10);
6768 
6769         /**
6770          * @hide
6771          */
6772         @Override
6773         public void purgeResources() {
6774             super.purgeResources();
6775             if (mPicture != null &&
6776                 mPicture.isMutable() &&
6777                 mPicture.getAllocationByteCount() >= MIN_ASHMEM_BITMAP_SIZE) {
6778                 mPicture = mPicture.createAshmemBitmap();
6779             }
6780             if (mBigLargeIcon != null) {
6781                 mBigLargeIcon.convertToAshmem();
6782             }
6783         }
6784 
6785         /**
6786          * @hide
6787          */
6788         @Override
6789         public void reduceImageSizes(Context context) {
6790             super.reduceImageSizes(context);
6791             Resources resources = context.getResources();
6792             boolean isLowRam = ActivityManager.isLowRamDeviceStatic();
6793             if (mPicture != null) {
6794                 int maxPictureWidth = resources.getDimensionPixelSize(isLowRam
6795                         ? R.dimen.notification_big_picture_max_height_low_ram
6796                         : R.dimen.notification_big_picture_max_height);
6797                 int maxPictureHeight = resources.getDimensionPixelSize(isLowRam
6798                         ? R.dimen.notification_big_picture_max_width_low_ram
6799                         : R.dimen.notification_big_picture_max_width);
6800                 mPicture = Icon.scaleDownIfNecessary(mPicture, maxPictureWidth, maxPictureHeight);
6801             }
6802             if (mBigLargeIcon != null) {
6803                 int rightIconSize = resources.getDimensionPixelSize(isLowRam
6804                         ? R.dimen.notification_right_icon_size_low_ram
6805                         : R.dimen.notification_right_icon_size);
6806                 mBigLargeIcon.scaleDownIfNecessary(rightIconSize, rightIconSize);
6807             }
6808         }
6809 
6810         /**
6811          * @hide
6812          */
6813         public RemoteViews makeBigContentView() {
6814             // Replace mN.mLargeIcon with mBigLargeIcon if mBigLargeIconSet
6815             // This covers the following cases:
6816             //   1. mBigLargeIconSet -> mBigLargeIcon (null or non-null) applies, overrides
6817             //          mN.mLargeIcon
6818             //   2. !mBigLargeIconSet -> mN.mLargeIcon applies
6819             Icon oldLargeIcon = null;
6820             Bitmap largeIconLegacy = null;
6821             if (mBigLargeIconSet) {
6822                 oldLargeIcon = mBuilder.mN.mLargeIcon;
6823                 mBuilder.mN.mLargeIcon = mBigLargeIcon;
6824                 // The legacy largeIcon might not allow us to clear the image, as it's taken in
6825                 // replacement if the other one is null. Because we're restoring these legacy icons
6826                 // for old listeners, this is in general non-null.
6827                 largeIconLegacy = mBuilder.mN.largeIcon;
6828                 mBuilder.mN.largeIcon = null;
6829             }
6830 
6831             StandardTemplateParams p = mBuilder.mParams.reset().fillTextsFrom(mBuilder);
6832             RemoteViews contentView = getStandardView(mBuilder.getBigPictureLayoutResource(),
6833                     p, null /* result */);
6834             if (mSummaryTextSet) {
6835                 contentView.setTextViewText(R.id.text, mBuilder.processTextSpans(
6836                         mBuilder.processLegacyText(mSummaryText)));
6837                 mBuilder.setTextViewColorSecondary(contentView, R.id.text, p);
6838                 contentView.setViewVisibility(R.id.text, View.VISIBLE);
6839             }
6840             mBuilder.setContentMinHeight(contentView, mBuilder.mN.hasLargeIcon());
6841 
6842             if (mBigLargeIconSet) {
6843                 mBuilder.mN.mLargeIcon = oldLargeIcon;
6844                 mBuilder.mN.largeIcon = largeIconLegacy;
6845             }
6846 
6847             contentView.setImageViewBitmap(R.id.big_picture, mPicture);
6848             return contentView;
6849         }
6850 
6851         /**
6852          * @hide
6853          */
6854         public void addExtras(Bundle extras) {
6855             super.addExtras(extras);
6856 
6857             if (mBigLargeIconSet) {
6858                 extras.putParcelable(EXTRA_LARGE_ICON_BIG, mBigLargeIcon);
6859             }
6860             extras.putParcelable(EXTRA_PICTURE, mPicture);
6861         }
6862 
6863         /**
6864          * @hide
6865          */
6866         @Override
6867         protected void restoreFromExtras(Bundle extras) {
6868             super.restoreFromExtras(extras);
6869 
6870             if (extras.containsKey(EXTRA_LARGE_ICON_BIG)) {
6871                 mBigLargeIconSet = true;
6872                 mBigLargeIcon = extras.getParcelable(EXTRA_LARGE_ICON_BIG);
6873             }
6874             mPicture = extras.getParcelable(EXTRA_PICTURE);
6875         }
6876 
6877         /**
6878          * @hide
6879          */
6880         @Override
6881         public boolean hasSummaryInHeader() {
6882             return false;
6883         }
6884 
6885         /**
6886          * @hide
6887          * Note that we aren't actually comparing the contents of the bitmaps here, so this
6888          * is only doing a cursory inspection. Bitmaps of equal size will appear the same.
6889          */
6890         @Override
6891         public boolean areNotificationsVisiblyDifferent(Style other) {
6892             if (other == null || getClass() != other.getClass()) {
6893                 return true;
6894             }
6895             BigPictureStyle otherS = (BigPictureStyle) other;
6896             return areBitmapsObviouslyDifferent(getBigPicture(), otherS.getBigPicture());
6897         }
6898 
6899         private static boolean areBitmapsObviouslyDifferent(Bitmap a, Bitmap b) {
6900             if (a == b) {
6901                 return false;
6902             }
6903             if (a == null || b == null) {
6904                 return true;
6905             }
6906             return a.getWidth() != b.getWidth()
6907                     || a.getHeight() != b.getHeight()
6908                     || a.getConfig() != b.getConfig()
6909                     || a.getGenerationId() != b.getGenerationId();
6910         }
6911     }
6912 
6913     /**
6914      * Helper class for generating large-format notifications that include a lot of text.
6915      *
6916      * Here's how you'd set the <code>BigTextStyle</code> on a notification:
6917      * <pre class="prettyprint">
6918      * Notification notif = new Notification.Builder(mContext)
6919      *     .setContentTitle(&quot;New mail from &quot; + sender.toString())
6920      *     .setContentText(subject)
6921      *     .setSmallIcon(R.drawable.new_mail)
6922      *     .setLargeIcon(aBitmap)
6923      *     .setStyle(new Notification.BigTextStyle()
6924      *         .bigText(aVeryLongString))
6925      *     .build();
6926      * </pre>
6927      *
6928      * @see Notification#bigContentView
6929      */
6930     public static class BigTextStyle extends Style {
6931 
6932         private CharSequence mBigText;
6933 
6934         public BigTextStyle() {
6935         }
6936 
6937         /**
6938          * @deprecated use {@code BigTextStyle()}.
6939          */
6940         @Deprecated
6941         public BigTextStyle(Builder builder) {
6942             setBuilder(builder);
6943         }
6944 
6945         /**
6946          * Overrides ContentTitle in the big form of the template.
6947          * This defaults to the value passed to setContentTitle().
6948          */
6949         public BigTextStyle setBigContentTitle(CharSequence title) {
6950             internalSetBigContentTitle(safeCharSequence(title));
6951             return this;
6952         }
6953 
6954         /**
6955          * Set the first line of text after the detail section in the big form of the template.
6956          */
6957         public BigTextStyle setSummaryText(CharSequence cs) {
6958             internalSetSummaryText(safeCharSequence(cs));
6959             return this;
6960         }
6961 
6962         /**
6963          * Provide the longer text to be displayed in the big form of the
6964          * template in place of the content text.
6965          */
6966         public BigTextStyle bigText(CharSequence cs) {
6967             mBigText = safeCharSequence(cs);
6968             return this;
6969         }
6970 
6971         /**
6972          * @hide
6973          */
6974         public CharSequence getBigText() {
6975             return mBigText;
6976         }
6977 
6978         /**
6979          * @hide
6980          */
6981         public void addExtras(Bundle extras) {
6982             super.addExtras(extras);
6983 
6984             extras.putCharSequence(EXTRA_BIG_TEXT, mBigText);
6985         }
6986 
6987         /**
6988          * @hide
6989          */
6990         @Override
6991         protected void restoreFromExtras(Bundle extras) {
6992             super.restoreFromExtras(extras);
6993 
6994             mBigText = extras.getCharSequence(EXTRA_BIG_TEXT);
6995         }
6996 
6997         /**
6998          * @param increasedHeight true if this layout be created with an increased height.
6999          *
7000          * @hide
7001          */
7002         @Override
7003         public RemoteViews makeContentView(boolean increasedHeight) {
7004             if (increasedHeight) {
7005                 mBuilder.mOriginalActions = mBuilder.mActions;
7006                 mBuilder.mActions = new ArrayList<>();
7007                 RemoteViews remoteViews = makeBigContentView();
7008                 mBuilder.mActions = mBuilder.mOriginalActions;
7009                 mBuilder.mOriginalActions = null;
7010                 return remoteViews;
7011             }
7012             return super.makeContentView(increasedHeight);
7013         }
7014 
7015         /**
7016          * @hide
7017          */
7018         @Override
7019         public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
7020             if (increasedHeight && mBuilder.mActions.size() > 0) {
7021                 return makeBigContentView();
7022             }
7023             return super.makeHeadsUpContentView(increasedHeight);
7024         }
7025 
7026         /**
7027          * @hide
7028          */
7029         public RemoteViews makeBigContentView() {
7030             StandardTemplateParams p = mBuilder.mParams.reset().fillTextsFrom(mBuilder).text(null);
7031             TemplateBindResult result = new TemplateBindResult();
7032             RemoteViews contentView = getStandardView(mBuilder.getBigTextLayoutResource(), p,
7033                     result);
7034             contentView.setInt(R.id.big_text, "setImageEndMargin", result.getIconMarginEnd());
7035 
7036             CharSequence bigTextText = mBuilder.processLegacyText(mBigText);
7037             if (TextUtils.isEmpty(bigTextText)) {
7038                 // In case the bigtext is null / empty fall back to the normal text to avoid a weird
7039                 // experience
7040                 bigTextText = mBuilder.processLegacyText(
7041                         mBuilder.getAllExtras().getCharSequence(EXTRA_TEXT));
7042             }
7043             contentView.setTextViewText(R.id.big_text, mBuilder.processTextSpans(bigTextText));
7044             mBuilder.setTextViewColorSecondary(contentView, R.id.big_text, p);
7045             contentView.setViewVisibility(R.id.big_text,
7046                     TextUtils.isEmpty(bigTextText) ? View.GONE : View.VISIBLE);
7047             contentView.setBoolean(R.id.big_text, "setHasImage",
7048                     result.isRightIconContainerVisible());
7049 
7050             return contentView;
7051         }
7052 
7053         /**
7054          * @hide
7055          * Spans are ignored when comparing text for visual difference.
7056          */
7057         @Override
7058         public boolean areNotificationsVisiblyDifferent(Style other) {
7059             if (other == null || getClass() != other.getClass()) {
7060                 return true;
7061             }
7062             BigTextStyle newS = (BigTextStyle) other;
7063             return !Objects.equals(String.valueOf(getBigText()), String.valueOf(newS.getBigText()));
7064         }
7065 
7066     }
7067 
7068     /**
7069      * Helper class for generating large-format notifications that include multiple back-and-forth
7070      * messages of varying types between any number of people.
7071      *
7072      * <p>
7073      * If the platform does not provide large-format notifications, this method has no effect. The
7074      * user will always see the normal notification view.
7075      *
7076      * <p>
7077      * If the app is targeting Android P and above, it is required to use the {@link Person}
7078      * class in order to get an optimal rendering of the notification and its avatars. For
7079      * conversations involving multiple people, the app should also make sure that it marks the
7080      * conversation as a group with {@link #setGroupConversation(boolean)}.
7081      *
7082      * <p>
7083      * This class is a "rebuilder": It attaches to a Builder object and modifies its behavior.
7084      * Here's an example of how this may be used:
7085      * <pre class="prettyprint">
7086      *
7087      * Person user = new Person.Builder().setIcon(userIcon).setName(userName).build();
7088      * MessagingStyle style = new MessagingStyle(user)
7089      *      .addMessage(messages[1].getText(), messages[1].getTime(), messages[1].getPerson())
7090      *      .addMessage(messages[2].getText(), messages[2].getTime(), messages[2].getPerson())
7091      *      .setGroupConversation(hasMultiplePeople());
7092      *
7093      * Notification noti = new Notification.Builder()
7094      *     .setContentTitle(&quot;2 new messages with &quot; + sender.toString())
7095      *     .setContentText(subject)
7096      *     .setSmallIcon(R.drawable.new_message)
7097      *     .setLargeIcon(aBitmap)
7098      *     .setStyle(style)
7099      *     .build();
7100      * </pre>
7101      */
7102     public static class MessagingStyle extends Style {
7103 
7104         /**
7105          * The maximum number of messages that will be retained in the Notification itself (the
7106          * number displayed is up to the platform).
7107          */
7108         public static final int MAXIMUM_RETAINED_MESSAGES = 25;
7109 
7110 
7111         /** @hide */
7112         public static final int CONVERSATION_TYPE_LEGACY = 0;
7113         /** @hide */
7114         public static final int CONVERSATION_TYPE_NORMAL = 1;
7115         /** @hide */
7116         public static final int CONVERSATION_TYPE_IMPORTANT = 2;
7117 
7118         /** @hide */
7119         @IntDef(prefix = {"CONVERSATION_TYPE_"}, value = {
7120                 CONVERSATION_TYPE_LEGACY, CONVERSATION_TYPE_NORMAL, CONVERSATION_TYPE_IMPORTANT
7121         })
7122         @Retention(RetentionPolicy.SOURCE)
7123         public @interface ConversationType {}
7124 
7125         @NonNull Person mUser;
7126         @Nullable CharSequence mConversationTitle;
7127         @Nullable Icon mShortcutIcon;
7128         List<Message> mMessages = new ArrayList<>();
7129         List<Message> mHistoricMessages = new ArrayList<>();
7130         boolean mIsGroupConversation;
7131         @ConversationType int mConversationType = CONVERSATION_TYPE_LEGACY;
7132         int mUnreadMessageCount;
7133 
7134         MessagingStyle() {
7135         }
7136 
7137         /**
7138          * @param userDisplayName Required - the name to be displayed for any replies sent by the
7139          * user before the posting app reposts the notification with those messages after they've
7140          * been actually sent and in previous messages sent by the user added in
7141          * {@link #addMessage(Notification.MessagingStyle.Message)}
7142          *
7143          * @deprecated use {@code MessagingStyle(Person)}
7144          */
7145         public MessagingStyle(@NonNull CharSequence userDisplayName) {
7146             this(new Person.Builder().setName(userDisplayName).build());
7147         }
7148 
7149         /**
7150          * @param user Required - The person displayed for any messages that are sent by the
7151          * user. Any messages added with {@link #addMessage(Notification.MessagingStyle.Message)}
7152          * who don't have a Person associated with it will be displayed as if they were sent
7153          * by this user. The user also needs to have a valid name associated with it, which will
7154          * be enforced starting in Android P.
7155          */
7156         public MessagingStyle(@NonNull Person user) {
7157             mUser = user;
7158         }
7159 
7160         /**
7161          * Validate that this style was properly composed. This is called at build time.
7162          * @hide
7163          */
7164         @Override
7165         public void validate(Context context) {
7166             super.validate(context);
7167             if (context.getApplicationInfo().targetSdkVersion
7168                     >= Build.VERSION_CODES.P && (mUser == null || mUser.getName() == null)) {
7169                 throw new RuntimeException("User must be valid and have a name.");
7170             }
7171         }
7172 
7173         /**
7174          * @return the text that should be displayed in the statusBar when heads upped.
7175          * If {@code null} is returned, the default implementation will be used.
7176          *
7177          * @hide
7178          */
7179         @Override
7180         public CharSequence getHeadsUpStatusBarText() {
7181             CharSequence conversationTitle = !TextUtils.isEmpty(super.mBigContentTitle)
7182                     ? super.mBigContentTitle
7183                     : mConversationTitle;
7184             if (mConversationType == CONVERSATION_TYPE_LEGACY
7185                     && !TextUtils.isEmpty(conversationTitle) && !hasOnlyWhiteSpaceSenders()) {
7186                 return conversationTitle;
7187             }
7188             return null;
7189         }
7190 
7191         /**
7192          * @return the user to be displayed for any replies sent by the user
7193          */
7194         @NonNull
7195         public Person getUser() {
7196             return mUser;
7197         }
7198 
7199         /**
7200          * Returns the name to be displayed for any replies sent by the user
7201          *
7202          * @deprecated use {@link #getUser()} instead
7203          */
7204         public CharSequence getUserDisplayName() {
7205             return mUser.getName();
7206         }
7207 
7208         /**
7209          * Sets the title to be displayed on this conversation. May be set to {@code null}.
7210          *
7211          * <p>Starting in {@link Build.VERSION_CODES#R, this conversation title will be ignored if a
7212          * valid shortcutId is added via {@link Notification.Builder#setShortcutId(String)}. In this
7213          * case, {@link ShortcutInfo#getLongLabel()} (or, if missing,
7214          * {@link ShortcutInfo#getShortLabel()}) will be shown as the conversation title
7215          * instead.
7216          *
7217          * <p>This API's behavior was changed in SDK version {@link Build.VERSION_CODES#P}. If your
7218          * application's target version is less than {@link Build.VERSION_CODES#P}, setting a
7219          * conversation title to a non-null value will make {@link #isGroupConversation()} return
7220          * {@code true} and passing {@code null} will make it return {@code false}. In
7221          * {@link Build.VERSION_CODES#P} and beyond, use {@link #setGroupConversation(boolean)}
7222          * to set group conversation status.
7223          *
7224          * @param conversationTitle Title displayed for this conversation
7225          * @return this object for method chaining
7226          */
7227         public MessagingStyle setConversationTitle(@Nullable CharSequence conversationTitle) {
7228             mConversationTitle = conversationTitle;
7229             return this;
7230         }
7231 
7232         /**
7233          * Return the title to be displayed on this conversation. May return {@code null}.
7234          */
7235         @Nullable
7236         public CharSequence getConversationTitle() {
7237             return mConversationTitle;
7238         }
7239 
7240         /**
7241          * Sets the icon to be displayed on the conversation, derived from the shortcutId.
7242          *
7243          * @hide
7244          */
7245         public MessagingStyle setShortcutIcon(@Nullable Icon conversationIcon) {
7246             mShortcutIcon = conversationIcon;
7247             return this;
7248         }
7249 
7250         /**
7251          * Return the icon to be displayed on this conversation, derived from the shortcutId. May
7252          * return {@code null}.
7253          *
7254          * @hide
7255          */
7256         @Nullable
7257         public Icon getShortcutIcon() {
7258             return mShortcutIcon;
7259         }
7260 
7261         /**
7262          * Sets the conversation type of this MessageStyle notification.
7263          * {@link #CONVERSATION_TYPE_LEGACY} will use the "older" layout from pre-R,
7264          * {@link #CONVERSATION_TYPE_NORMAL} will use the new "conversation" layout, and
7265          * {@link #CONVERSATION_TYPE_IMPORTANT} will add additional "important" treatments.
7266          *
7267          * @hide
7268          */
7269         public MessagingStyle setConversationType(@ConversationType int conversationType) {
7270             mConversationType = conversationType;
7271             return this;
7272         }
7273 
7274         /** @hide */
7275         @ConversationType
7276         public int getConversationType() {
7277             return mConversationType;
7278         }
7279 
7280         /** @hide */
7281         public int getUnreadMessageCount() {
7282             return mUnreadMessageCount;
7283         }
7284 
7285         /** @hide */
7286         public MessagingStyle setUnreadMessageCount(int unreadMessageCount) {
7287             mUnreadMessageCount = unreadMessageCount;
7288             return this;
7289         }
7290 
7291         /**
7292          * Adds a message for display by this notification. Convenience call for a simple
7293          * {@link Message} in {@link #addMessage(Notification.MessagingStyle.Message)}.
7294          * @param text A {@link CharSequence} to be displayed as the message content
7295          * @param timestamp Time at which the message arrived
7296          * @param sender A {@link CharSequence} to be used for displaying the name of the
7297          * sender. Should be <code>null</code> for messages by the current user, in which case
7298          * the platform will insert {@link #getUserDisplayName()}.
7299          * Should be unique amongst all individuals in the conversation, and should be
7300          * consistent during re-posts of the notification.
7301          *
7302          * @see Message#Message(CharSequence, long, CharSequence)
7303          *
7304          * @return this object for method chaining
7305          *
7306          * @deprecated use {@link #addMessage(CharSequence, long, Person)}
7307          */
7308         public MessagingStyle addMessage(CharSequence text, long timestamp, CharSequence sender) {
7309             return addMessage(text, timestamp,
7310                     sender == null ? null : new Person.Builder().setName(sender).build());
7311         }
7312 
7313         /**
7314          * Adds a message for display by this notification. Convenience call for a simple
7315          * {@link Message} in {@link #addMessage(Notification.MessagingStyle.Message)}.
7316          * @param text A {@link CharSequence} to be displayed as the message content
7317          * @param timestamp Time at which the message arrived
7318          * @param sender The {@link Person} who sent the message.
7319          * Should be <code>null</code> for messages by the current user, in which case
7320          * the platform will insert the user set in {@code MessagingStyle(Person)}.
7321          *
7322          * @see Message#Message(CharSequence, long, CharSequence)
7323          *
7324          * @return this object for method chaining
7325          */
7326         public MessagingStyle addMessage(@NonNull CharSequence text, long timestamp,
7327                 @Nullable Person sender) {
7328             return addMessage(new Message(text, timestamp, sender));
7329         }
7330 
7331         /**
7332          * Adds a {@link Message} for display in this notification.
7333          *
7334          * <p>The messages should be added in chronologic order, i.e. the oldest first,
7335          * the newest last.
7336          *
7337          * @param message The {@link Message} to be displayed
7338          * @return this object for method chaining
7339          */
7340         public MessagingStyle addMessage(Message message) {
7341             mMessages.add(message);
7342             if (mMessages.size() > MAXIMUM_RETAINED_MESSAGES) {
7343                 mMessages.remove(0);
7344             }
7345             return this;
7346         }
7347 
7348         /**
7349          * Adds a {@link Message} for historic context in this notification.
7350          *
7351          * <p>Messages should be added as historic if they are not the main subject of the
7352          * notification but may give context to a conversation. The system may choose to present
7353          * them only when relevant, e.g. when replying to a message through a {@link RemoteInput}.
7354          *
7355          * <p>The messages should be added in chronologic order, i.e. the oldest first,
7356          * the newest last.
7357          *
7358          * @param message The historic {@link Message} to be added
7359          * @return this object for method chaining
7360          */
7361         public MessagingStyle addHistoricMessage(Message message) {
7362             mHistoricMessages.add(message);
7363             if (mHistoricMessages.size() > MAXIMUM_RETAINED_MESSAGES) {
7364                 mHistoricMessages.remove(0);
7365             }
7366             return this;
7367         }
7368 
7369         /**
7370          * Gets the list of {@code Message} objects that represent the notification
7371          */
getMessages()7372         public List<Message> getMessages() {
7373             return mMessages;
7374         }
7375 
7376         /**
7377          * Gets the list of historic {@code Message}s in the notification.
7378          */
getHistoricMessages()7379         public List<Message> getHistoricMessages() {
7380             return mHistoricMessages;
7381         }
7382 
7383         /**
7384          * Sets whether this conversation notification represents a group. If the app is targeting
7385          * Android P, this is required if the app wants to display the largeIcon set with
7386          * {@link Notification.Builder#setLargeIcon(Bitmap)}, otherwise it will be hidden.
7387          *
7388          * @param isGroupConversation {@code true} if the conversation represents a group,
7389          * {@code false} otherwise.
7390          * @return this object for method chaining
7391          */
setGroupConversation(boolean isGroupConversation)7392         public MessagingStyle setGroupConversation(boolean isGroupConversation) {
7393             mIsGroupConversation = isGroupConversation;
7394             return this;
7395         }
7396 
7397         /**
7398          * Returns {@code true} if this notification represents a group conversation, otherwise
7399          * {@code false}.
7400          *
7401          * <p> If the application that generated this {@link MessagingStyle} targets an SDK version
7402          * less than {@link Build.VERSION_CODES#P}, this method becomes dependent on whether or
7403          * not the conversation title is set; returning {@code true} if the conversation title is
7404          * a non-null value, or {@code false} otherwise. From {@link Build.VERSION_CODES#P} forward,
7405          * this method returns what's set by {@link #setGroupConversation(boolean)} allowing for
7406          * named, non-group conversations.
7407          *
7408          * @see #setConversationTitle(CharSequence)
7409          */
isGroupConversation()7410         public boolean isGroupConversation() {
7411             // When target SDK version is < P, a non-null conversation title dictates if this is
7412             // as group conversation.
7413             if (mBuilder != null
7414                     && mBuilder.mContext.getApplicationInfo().targetSdkVersion
7415                             < Build.VERSION_CODES.P) {
7416                 return mConversationTitle != null;
7417             }
7418 
7419             return mIsGroupConversation;
7420         }
7421 
7422         /**
7423          * @hide
7424          */
7425         @Override
addExtras(Bundle extras)7426         public void addExtras(Bundle extras) {
7427             super.addExtras(extras);
7428             if (mUser != null) {
7429                 // For legacy usages
7430                 extras.putCharSequence(EXTRA_SELF_DISPLAY_NAME, mUser.getName());
7431                 extras.putParcelable(EXTRA_MESSAGING_PERSON, mUser);
7432             }
7433             if (mConversationTitle != null) {
7434                 extras.putCharSequence(EXTRA_CONVERSATION_TITLE, mConversationTitle);
7435             }
7436             if (!mMessages.isEmpty()) { extras.putParcelableArray(EXTRA_MESSAGES,
7437                     Message.getBundleArrayForMessages(mMessages));
7438             }
7439             if (!mHistoricMessages.isEmpty()) { extras.putParcelableArray(EXTRA_HISTORIC_MESSAGES,
7440                     Message.getBundleArrayForMessages(mHistoricMessages));
7441             }
7442             if (mShortcutIcon != null) {
7443                 extras.putParcelable(EXTRA_CONVERSATION_ICON, mShortcutIcon);
7444             }
7445             extras.putInt(EXTRA_CONVERSATION_UNREAD_MESSAGE_COUNT, mUnreadMessageCount);
7446 
7447             fixTitleAndTextExtras(extras);
7448             extras.putBoolean(EXTRA_IS_GROUP_CONVERSATION, mIsGroupConversation);
7449         }
7450 
fixTitleAndTextExtras(Bundle extras)7451         private void fixTitleAndTextExtras(Bundle extras) {
7452             Message m = findLatestIncomingMessage();
7453             CharSequence text = (m == null) ? null : m.mText;
7454             CharSequence sender = m == null ? null
7455                     : m.mSender == null || TextUtils.isEmpty(m.mSender.getName())
7456                             ? mUser.getName() : m.mSender.getName();
7457             CharSequence title;
7458             if (!TextUtils.isEmpty(mConversationTitle)) {
7459                 if (!TextUtils.isEmpty(sender)) {
7460                     BidiFormatter bidi = BidiFormatter.getInstance();
7461                     title = mBuilder.mContext.getString(
7462                             com.android.internal.R.string.notification_messaging_title_template,
7463                             bidi.unicodeWrap(mConversationTitle), bidi.unicodeWrap(sender));
7464                 } else {
7465                     title = mConversationTitle;
7466                 }
7467             } else {
7468                 title = sender;
7469             }
7470 
7471             if (title != null) {
7472                 extras.putCharSequence(EXTRA_TITLE, title);
7473             }
7474             if (text != null) {
7475                 extras.putCharSequence(EXTRA_TEXT, text);
7476             }
7477         }
7478 
7479         /**
7480          * @hide
7481          */
7482         @Override
restoreFromExtras(Bundle extras)7483         protected void restoreFromExtras(Bundle extras) {
7484             super.restoreFromExtras(extras);
7485 
7486             mUser = extras.getParcelable(EXTRA_MESSAGING_PERSON);
7487             if (mUser == null) {
7488                 CharSequence displayName = extras.getCharSequence(EXTRA_SELF_DISPLAY_NAME);
7489                 mUser = new Person.Builder().setName(displayName).build();
7490             }
7491             mConversationTitle = extras.getCharSequence(EXTRA_CONVERSATION_TITLE);
7492             Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES);
7493             mMessages = Message.getMessagesFromBundleArray(messages);
7494             Parcelable[] histMessages = extras.getParcelableArray(EXTRA_HISTORIC_MESSAGES);
7495             mHistoricMessages = Message.getMessagesFromBundleArray(histMessages);
7496             mIsGroupConversation = extras.getBoolean(EXTRA_IS_GROUP_CONVERSATION);
7497             mUnreadMessageCount = extras.getInt(EXTRA_CONVERSATION_UNREAD_MESSAGE_COUNT);
7498             mShortcutIcon = extras.getParcelable(EXTRA_CONVERSATION_ICON);
7499         }
7500 
7501         /**
7502          * @hide
7503          */
7504         @Override
makeContentView(boolean increasedHeight)7505         public RemoteViews makeContentView(boolean increasedHeight) {
7506             mBuilder.mOriginalActions = mBuilder.mActions;
7507             mBuilder.mActions = new ArrayList<>();
7508             RemoteViews remoteViews = makeMessagingView(true /* isCollapsed */,
7509                     false /* hideLargeIcon */);
7510             mBuilder.mActions = mBuilder.mOriginalActions;
7511             mBuilder.mOriginalActions = null;
7512             return remoteViews;
7513         }
7514 
7515         /**
7516          * @hide
7517          * Spans are ignored when comparing text for visual difference.
7518          */
7519         @Override
areNotificationsVisiblyDifferent(Style other)7520         public boolean areNotificationsVisiblyDifferent(Style other) {
7521             if (other == null || getClass() != other.getClass()) {
7522                 return true;
7523             }
7524             MessagingStyle newS = (MessagingStyle) other;
7525             List<MessagingStyle.Message> oldMs = getMessages();
7526             List<MessagingStyle.Message> newMs = newS.getMessages();
7527 
7528             if (oldMs == null || newMs == null) {
7529                 newMs = new ArrayList<>();
7530             }
7531 
7532             int n = oldMs.size();
7533             if (n != newMs.size()) {
7534                 return true;
7535             }
7536             for (int i = 0; i < n; i++) {
7537                 MessagingStyle.Message oldM = oldMs.get(i);
7538                 MessagingStyle.Message newM = newMs.get(i);
7539                 if (!Objects.equals(
7540                         String.valueOf(oldM.getText()),
7541                         String.valueOf(newM.getText()))) {
7542                     return true;
7543                 }
7544                 if (!Objects.equals(oldM.getDataUri(), newM.getDataUri())) {
7545                     return true;
7546                 }
7547                 String oldSender = String.valueOf(oldM.getSenderPerson() == null
7548                         ? oldM.getSender()
7549                         : oldM.getSenderPerson().getName());
7550                 String newSender = String.valueOf(newM.getSenderPerson() == null
7551                         ? newM.getSender()
7552                         : newM.getSenderPerson().getName());
7553                 if (!Objects.equals(oldSender, newSender)) {
7554                     return true;
7555                 }
7556 
7557                 String oldKey = oldM.getSenderPerson() == null
7558                         ? null : oldM.getSenderPerson().getKey();
7559                 String newKey = newM.getSenderPerson() == null
7560                         ? null : newM.getSenderPerson().getKey();
7561                 if (!Objects.equals(oldKey, newKey)) {
7562                     return true;
7563                 }
7564                 // Other fields (like timestamp) intentionally excluded
7565             }
7566             return false;
7567         }
7568 
findLatestIncomingMessage()7569         private Message findLatestIncomingMessage() {
7570             return findLatestIncomingMessage(mMessages);
7571         }
7572 
7573         /**
7574          * @hide
7575          */
7576         @Nullable
findLatestIncomingMessage( List<Message> messages)7577         public static Message findLatestIncomingMessage(
7578                 List<Message> messages) {
7579             for (int i = messages.size() - 1; i >= 0; i--) {
7580                 Message m = messages.get(i);
7581                 // Incoming messages have a non-empty sender.
7582                 if (m.mSender != null && !TextUtils.isEmpty(m.mSender.getName())) {
7583                     return m;
7584                 }
7585             }
7586             if (!messages.isEmpty()) {
7587                 // No incoming messages, fall back to outgoing message
7588                 return messages.get(messages.size() - 1);
7589             }
7590             return null;
7591         }
7592 
7593         /**
7594          * @hide
7595          */
7596         @Override
makeBigContentView()7597         public RemoteViews makeBigContentView() {
7598             return makeMessagingView(false /* isCollapsed */, true /* hideLargeIcon */);
7599         }
7600 
7601         /**
7602          * Create a messaging layout.
7603          *
7604          * @param isCollapsed Should this use the collapsed layout
7605          * @param hideRightIcons Should the reply affordance be shown at the end of the notification
7606          * @return the created remoteView.
7607          */
7608         @NonNull
makeMessagingView(boolean isCollapsed, boolean hideRightIcons)7609         private RemoteViews makeMessagingView(boolean isCollapsed, boolean hideRightIcons) {
7610             CharSequence conversationTitle = !TextUtils.isEmpty(super.mBigContentTitle)
7611                     ? super.mBigContentTitle
7612                     : mConversationTitle;
7613             boolean atLeastP = mBuilder.mContext.getApplicationInfo().targetSdkVersion
7614                     >= Build.VERSION_CODES.P;
7615             boolean isOneToOne;
7616             CharSequence nameReplacement = null;
7617             if (!atLeastP) {
7618                 isOneToOne = TextUtils.isEmpty(conversationTitle);
7619                 if (hasOnlyWhiteSpaceSenders()) {
7620                     isOneToOne = true;
7621                     nameReplacement = conversationTitle;
7622                     conversationTitle = null;
7623                 }
7624             } else {
7625                 isOneToOne = !isGroupConversation();
7626             }
7627             boolean isConversationLayout = mConversationType != CONVERSATION_TYPE_LEGACY;
7628             boolean isImportantConversation = mConversationType == CONVERSATION_TYPE_IMPORTANT;
7629             Icon largeIcon = mBuilder.mN.mLargeIcon;
7630             TemplateBindResult bindResult = new TemplateBindResult();
7631             StandardTemplateParams p = mBuilder.mParams.reset()
7632                     .hasProgress(false)
7633                     .title(conversationTitle)
7634                     .text(null)
7635                     .hideLargeIcon(hideRightIcons || isOneToOne)
7636                     .hideReplyIcon(hideRightIcons)
7637                     .headerTextSecondary(conversationTitle);
7638             RemoteViews contentView = mBuilder.applyStandardTemplateWithActions(
7639                     isConversationLayout
7640                             ? mBuilder.getConversationLayoutResource()
7641                             : mBuilder.getMessagingLayoutResource(),
7642                     p,
7643                     bindResult);
7644 
7645             addExtras(mBuilder.mN.extras);
7646             if (!isConversationLayout) {
7647                 // also update the end margin if there is an image
7648                 contentView.setViewLayoutMarginEnd(R.id.notification_messaging,
7649                         bindResult.getIconMarginEnd());
7650             }
7651             contentView.setInt(R.id.status_bar_latest_event_content, "setLayoutColor",
7652                     mBuilder.isColorized(p)
7653                             ? mBuilder.getPrimaryTextColor(p)
7654                             : mBuilder.resolveContrastColor(p));
7655             contentView.setInt(R.id.status_bar_latest_event_content, "setSenderTextColor",
7656                     mBuilder.getPrimaryTextColor(p));
7657             contentView.setInt(R.id.status_bar_latest_event_content, "setMessageTextColor",
7658                     mBuilder.getSecondaryTextColor(p));
7659             contentView.setInt(R.id.status_bar_latest_event_content,
7660                     "setNotificationBackgroundColor",
7661                     mBuilder.resolveBackgroundColor(p));
7662             contentView.setBoolean(R.id.status_bar_latest_event_content, "setIsCollapsed",
7663                     isCollapsed);
7664             contentView.setIcon(R.id.status_bar_latest_event_content, "setAvatarReplacement",
7665                     mBuilder.mN.mLargeIcon);
7666             contentView.setCharSequence(R.id.status_bar_latest_event_content, "setNameReplacement",
7667                     nameReplacement);
7668             contentView.setBoolean(R.id.status_bar_latest_event_content, "setIsOneToOne",
7669                     isOneToOne);
7670             contentView.setCharSequence(R.id.status_bar_latest_event_content,
7671                     "setConversationTitle", conversationTitle);
7672             if (isConversationLayout) {
7673                 contentView.setIcon(R.id.status_bar_latest_event_content,
7674                         "setShortcutIcon", mShortcutIcon);
7675                 contentView.setBoolean(R.id.status_bar_latest_event_content,
7676                         "setIsImportantConversation", isImportantConversation);
7677             }
7678             contentView.setIcon(R.id.status_bar_latest_event_content, "setLargeIcon",
7679                     largeIcon);
7680             contentView.setBundle(R.id.status_bar_latest_event_content, "setData",
7681                     mBuilder.mN.extras);
7682             return contentView;
7683         }
7684 
hasOnlyWhiteSpaceSenders()7685         private boolean hasOnlyWhiteSpaceSenders() {
7686             for (int i = 0; i < mMessages.size(); i++) {
7687                 Message m = mMessages.get(i);
7688                 Person sender = m.getSenderPerson();
7689                 if (sender != null && !isWhiteSpace(sender.getName())) {
7690                     return false;
7691                 }
7692             }
7693             return true;
7694         }
7695 
isWhiteSpace(CharSequence sender)7696         private boolean isWhiteSpace(CharSequence sender) {
7697             if (TextUtils.isEmpty(sender)) {
7698                 return true;
7699             }
7700             if (sender.toString().matches("^\\s*$")) {
7701                 return true;
7702             }
7703             // Let's check if we only have 0 whitespace chars. Some apps did this as a workaround
7704             // For the presentation that we had.
7705             for (int i = 0; i < sender.length(); i++) {
7706                 char c = sender.charAt(i);
7707                 if (c != '\u200B') {
7708                     return false;
7709                 }
7710             }
7711             return true;
7712         }
7713 
createConversationTitleFromMessages()7714         private CharSequence createConversationTitleFromMessages() {
7715             ArraySet<CharSequence> names = new ArraySet<>();
7716             for (int i = 0; i < mMessages.size(); i++) {
7717                 Message m = mMessages.get(i);
7718                 Person sender = m.getSenderPerson();
7719                 if (sender != null) {
7720                     names.add(sender.getName());
7721                 }
7722             }
7723             SpannableStringBuilder title = new SpannableStringBuilder();
7724             int size = names.size();
7725             for (int i = 0; i < size; i++) {
7726                 CharSequence name = names.valueAt(i);
7727                 if (!TextUtils.isEmpty(title)) {
7728                     title.append(", ");
7729                 }
7730                 title.append(BidiFormatter.getInstance().unicodeWrap(name));
7731             }
7732             return title;
7733         }
7734 
7735         /**
7736          * @hide
7737          */
7738         @Override
makeHeadsUpContentView(boolean increasedHeight)7739         public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
7740             RemoteViews remoteViews = makeMessagingView(true /* isCollapsed */,
7741                     true /* hideLargeIcon */);
7742             if (mConversationType == CONVERSATION_TYPE_LEGACY) {
7743                 remoteViews.setInt(R.id.notification_messaging, "setMaxDisplayedLines", 1);
7744             }
7745             return remoteViews;
7746         }
7747 
makeFontColorSpan(int color)7748         private static TextAppearanceSpan makeFontColorSpan(int color) {
7749             return new TextAppearanceSpan(null, 0, 0,
7750                     ColorStateList.valueOf(color), null);
7751         }
7752 
7753         public static final class Message {
7754             /** @hide */
7755             public static final String KEY_TEXT = "text";
7756             static final String KEY_TIMESTAMP = "time";
7757             static final String KEY_SENDER = "sender";
7758             static final String KEY_SENDER_PERSON = "sender_person";
7759             static final String KEY_DATA_MIME_TYPE = "type";
7760             static final String KEY_DATA_URI= "uri";
7761             static final String KEY_EXTRAS_BUNDLE = "extras";
7762             static final String KEY_REMOTE_INPUT_HISTORY = "remote_input_history";
7763 
7764             private final CharSequence mText;
7765             private final long mTimestamp;
7766             @Nullable
7767             private final Person mSender;
7768             /** True if this message was generated from the extra
7769              *  {@link Notification#EXTRA_REMOTE_INPUT_HISTORY_ITEMS}
7770              */
7771             private final boolean mRemoteInputHistory;
7772 
7773             private Bundle mExtras = new Bundle();
7774             private String mDataMimeType;
7775             private Uri mDataUri;
7776 
7777             /**
7778              * Constructor
7779              * @param text A {@link CharSequence} to be displayed as the message content
7780              * @param timestamp Time at which the message arrived
7781              * @param sender A {@link CharSequence} to be used for displaying the name of the
7782              * sender. Should be <code>null</code> for messages by the current user, in which case
7783              * the platform will insert {@link MessagingStyle#getUserDisplayName()}.
7784              * Should be unique amongst all individuals in the conversation, and should be
7785              * consistent during re-posts of the notification.
7786              *
7787              *  @deprecated use {@code Message(CharSequence, long, Person)}
7788              */
Message(CharSequence text, long timestamp, CharSequence sender)7789             public Message(CharSequence text, long timestamp, CharSequence sender){
7790                 this(text, timestamp, sender == null ? null
7791                         : new Person.Builder().setName(sender).build());
7792             }
7793 
7794             /**
7795              * Constructor
7796              * @param text A {@link CharSequence} to be displayed as the message content
7797              * @param timestamp Time at which the message arrived
7798              * @param sender The {@link Person} who sent the message.
7799              * Should be <code>null</code> for messages by the current user, in which case
7800              * the platform will insert the user set in {@code MessagingStyle(Person)}.
7801              * <p>
7802              * The person provided should contain an Icon, set with
7803              * {@link Person.Builder#setIcon(Icon)} and also have a name provided
7804              * with {@link Person.Builder#setName(CharSequence)}. If multiple users have the same
7805              * name, consider providing a key with {@link Person.Builder#setKey(String)} in order
7806              * to differentiate between the different users.
7807              * </p>
7808              */
Message(@onNull CharSequence text, long timestamp, @Nullable Person sender)7809             public Message(@NonNull CharSequence text, long timestamp, @Nullable Person sender) {
7810                 this(text, timestamp, sender, false /* remoteHistory */);
7811             }
7812 
7813             /**
7814              * Constructor
7815              * @param text A {@link CharSequence} to be displayed as the message content
7816              * @param timestamp Time at which the message arrived
7817              * @param sender The {@link Person} who sent the message.
7818              * Should be <code>null</code> for messages by the current user, in which case
7819              * the platform will insert the user set in {@code MessagingStyle(Person)}.
7820              * @param remoteInputHistory True if the messages was generated from the extra
7821              * {@link Notification#EXTRA_REMOTE_INPUT_HISTORY_ITEMS}.
7822              * <p>
7823              * The person provided should contain an Icon, set with
7824              * {@link Person.Builder#setIcon(Icon)} and also have a name provided
7825              * with {@link Person.Builder#setName(CharSequence)}. If multiple users have the same
7826              * name, consider providing a key with {@link Person.Builder#setKey(String)} in order
7827              * to differentiate between the different users.
7828              * </p>
7829              * @hide
7830              */
Message(@onNull CharSequence text, long timestamp, @Nullable Person sender, boolean remoteInputHistory)7831             public Message(@NonNull CharSequence text, long timestamp, @Nullable Person sender,
7832                     boolean remoteInputHistory) {
7833                 mText = text;
7834                 mTimestamp = timestamp;
7835                 mSender = sender;
7836                 mRemoteInputHistory = remoteInputHistory;
7837             }
7838 
7839             /**
7840              * Sets a binary blob of data and an associated MIME type for a message. In the case
7841              * where the platform doesn't support the MIME type, the original text provided in the
7842              * constructor will be used.
7843              * @param dataMimeType The MIME type of the content. See
7844              * <a href="{@docRoot}notifications/messaging.html"> for the list of supported MIME
7845              * types on Android and Android Wear.
7846              * @param dataUri The uri containing the content whose type is given by the MIME type.
7847              * <p class="note">
7848              * <ol>
7849              *   <li>Notification Listeners including the System UI need permission to access the
7850              *       data the Uri points to. The recommended ways to do this are:</li>
7851              *   <li>Store the data in your own ContentProvider, making sure that other apps have
7852              *       the correct permission to access your provider. The preferred mechanism for
7853              *       providing access is to use per-URI permissions which are temporary and only
7854              *       grant access to the receiving application. An easy way to create a
7855              *       ContentProvider like this is to use the FileProvider helper class.</li>
7856              *   <li>Use the system MediaStore. The MediaStore is primarily aimed at video, audio
7857              *       and image MIME types, however beginning with Android 3.0 (API level 11) it can
7858              *       also store non-media types (see MediaStore.Files for more info). Files can be
7859              *       inserted into the MediaStore using scanFile() after which a content:// style
7860              *       Uri suitable for sharing is passed to the provided onScanCompleted() callback.
7861              *       Note that once added to the system MediaStore the content is accessible to any
7862              *       app on the device.</li>
7863              * </ol>
7864              * @return this object for method chaining
7865              */
setData(String dataMimeType, Uri dataUri)7866             public Message setData(String dataMimeType, Uri dataUri) {
7867                 mDataMimeType = dataMimeType;
7868                 mDataUri = dataUri;
7869                 return this;
7870             }
7871 
7872             /**
7873              * Get the text to be used for this message, or the fallback text if a type and content
7874              * Uri have been set
7875              */
getText()7876             public CharSequence getText() {
7877                 return mText;
7878             }
7879 
7880             /**
7881              * Get the time at which this message arrived
7882              */
getTimestamp()7883             public long getTimestamp() {
7884                 return mTimestamp;
7885             }
7886 
7887             /**
7888              * Get the extras Bundle for this message.
7889              */
getExtras()7890             public Bundle getExtras() {
7891                 return mExtras;
7892             }
7893 
7894             /**
7895              * Get the text used to display the contact's name in the messaging experience
7896              *
7897              * @deprecated use {@link #getSenderPerson()}
7898              */
getSender()7899             public CharSequence getSender() {
7900                 return mSender == null ? null : mSender.getName();
7901             }
7902 
7903             /**
7904              * Get the sender associated with this message.
7905              */
7906             @Nullable
getSenderPerson()7907             public Person getSenderPerson() {
7908                 return mSender;
7909             }
7910 
7911             /**
7912              * Get the MIME type of the data pointed to by the Uri
7913              */
getDataMimeType()7914             public String getDataMimeType() {
7915                 return mDataMimeType;
7916             }
7917 
7918             /**
7919              * Get the Uri pointing to the content of the message. Can be null, in which case
7920              * {@see #getText()} is used.
7921              */
getDataUri()7922             public Uri getDataUri() {
7923                 return mDataUri;
7924             }
7925 
7926             /**
7927              * @return True if the message was generated from
7928              * {@link Notification#EXTRA_REMOTE_INPUT_HISTORY_ITEMS}.
7929              * @hide
7930              */
isRemoteInputHistory()7931             public boolean isRemoteInputHistory() {
7932                 return mRemoteInputHistory;
7933             }
7934 
7935             /**
7936              * @hide
7937              */
7938             @VisibleForTesting
toBundle()7939             public Bundle toBundle() {
7940                 Bundle bundle = new Bundle();
7941                 if (mText != null) {
7942                     bundle.putCharSequence(KEY_TEXT, mText);
7943                 }
7944                 bundle.putLong(KEY_TIMESTAMP, mTimestamp);
7945                 if (mSender != null) {
7946                     // Legacy listeners need this
7947                     bundle.putCharSequence(KEY_SENDER, mSender.getName());
7948                     bundle.putParcelable(KEY_SENDER_PERSON, mSender);
7949                 }
7950                 if (mDataMimeType != null) {
7951                     bundle.putString(KEY_DATA_MIME_TYPE, mDataMimeType);
7952                 }
7953                 if (mDataUri != null) {
7954                     bundle.putParcelable(KEY_DATA_URI, mDataUri);
7955                 }
7956                 if (mExtras != null) {
7957                     bundle.putBundle(KEY_EXTRAS_BUNDLE, mExtras);
7958                 }
7959                 if (mRemoteInputHistory) {
7960                     bundle.putBoolean(KEY_REMOTE_INPUT_HISTORY, mRemoteInputHistory);
7961                 }
7962                 return bundle;
7963             }
7964 
getBundleArrayForMessages(List<Message> messages)7965             static Bundle[] getBundleArrayForMessages(List<Message> messages) {
7966                 Bundle[] bundles = new Bundle[messages.size()];
7967                 final int N = messages.size();
7968                 for (int i = 0; i < N; i++) {
7969                     bundles[i] = messages.get(i).toBundle();
7970                 }
7971                 return bundles;
7972             }
7973 
7974             /**
7975              * Returns a list of messages read from the given bundle list, e.g.
7976              * {@link #EXTRA_MESSAGES} or {@link #EXTRA_HISTORIC_MESSAGES}.
7977              */
7978             @NonNull
getMessagesFromBundleArray(@ullable Parcelable[] bundles)7979             public static List<Message> getMessagesFromBundleArray(@Nullable Parcelable[] bundles) {
7980                 if (bundles == null) {
7981                     return new ArrayList<>();
7982                 }
7983                 List<Message> messages = new ArrayList<>(bundles.length);
7984                 for (int i = 0; i < bundles.length; i++) {
7985                     if (bundles[i] instanceof Bundle) {
7986                         Message message = getMessageFromBundle((Bundle)bundles[i]);
7987                         if (message != null) {
7988                             messages.add(message);
7989                         }
7990                     }
7991                 }
7992                 return messages;
7993             }
7994 
7995             /**
7996              * Returns the message that is stored in the bundle (e.g. one of the values in the lists
7997              * in {@link #EXTRA_MESSAGES} or {@link #EXTRA_HISTORIC_MESSAGES}) or null if the
7998              * message couldn't be resolved.
7999              * @hide
8000              */
8001             @Nullable
getMessageFromBundle(@onNull Bundle bundle)8002             public static Message getMessageFromBundle(@NonNull Bundle bundle) {
8003                 try {
8004                     if (!bundle.containsKey(KEY_TEXT) || !bundle.containsKey(KEY_TIMESTAMP)) {
8005                         return null;
8006                     } else {
8007 
8008                         Person senderPerson = bundle.getParcelable(KEY_SENDER_PERSON);
8009                         if (senderPerson == null) {
8010                             // Legacy apps that use compat don't actually provide the sender objects
8011                             // We need to fix the compat version to provide people / use
8012                             // the native api instead
8013                             CharSequence senderName = bundle.getCharSequence(KEY_SENDER);
8014                             if (senderName != null) {
8015                                 senderPerson = new Person.Builder().setName(senderName).build();
8016                             }
8017                         }
8018                         Message message = new Message(bundle.getCharSequence(KEY_TEXT),
8019                                 bundle.getLong(KEY_TIMESTAMP),
8020                                 senderPerson,
8021                                 bundle.getBoolean(KEY_REMOTE_INPUT_HISTORY, false));
8022                         if (bundle.containsKey(KEY_DATA_MIME_TYPE) &&
8023                                 bundle.containsKey(KEY_DATA_URI)) {
8024                             message.setData(bundle.getString(KEY_DATA_MIME_TYPE),
8025                                     (Uri) bundle.getParcelable(KEY_DATA_URI));
8026                         }
8027                         if (bundle.containsKey(KEY_EXTRAS_BUNDLE)) {
8028                             message.getExtras().putAll(bundle.getBundle(KEY_EXTRAS_BUNDLE));
8029                         }
8030                         return message;
8031                     }
8032                 } catch (ClassCastException e) {
8033                     return null;
8034                 }
8035             }
8036         }
8037     }
8038 
8039     /**
8040      * Helper class for generating large-format notifications that include a list of (up to 5) strings.
8041      *
8042      * Here's how you'd set the <code>InboxStyle</code> on a notification:
8043      * <pre class="prettyprint">
8044      * Notification notif = new Notification.Builder(mContext)
8045      *     .setContentTitle(&quot;5 New mails from &quot; + sender.toString())
8046      *     .setContentText(subject)
8047      *     .setSmallIcon(R.drawable.new_mail)
8048      *     .setLargeIcon(aBitmap)
8049      *     .setStyle(new Notification.InboxStyle()
8050      *         .addLine(str1)
8051      *         .addLine(str2)
8052      *         .setContentTitle(&quot;&quot;)
8053      *         .setSummaryText(&quot;+3 more&quot;))
8054      *     .build();
8055      * </pre>
8056      *
8057      * @see Notification#bigContentView
8058      */
8059     public static class InboxStyle extends Style {
8060 
8061         /**
8062          * The number of lines of remote input history allowed until we start reducing lines.
8063          */
8064         private static final int NUMBER_OF_HISTORY_ALLOWED_UNTIL_REDUCTION = 1;
8065         private ArrayList<CharSequence> mTexts = new ArrayList<CharSequence>(5);
8066 
InboxStyle()8067         public InboxStyle() {
8068         }
8069 
8070         /**
8071          * @deprecated use {@code InboxStyle()}.
8072          */
8073         @Deprecated
InboxStyle(Builder builder)8074         public InboxStyle(Builder builder) {
8075             setBuilder(builder);
8076         }
8077 
8078         /**
8079          * Overrides ContentTitle in the big form of the template.
8080          * This defaults to the value passed to setContentTitle().
8081          */
setBigContentTitle(CharSequence title)8082         public InboxStyle setBigContentTitle(CharSequence title) {
8083             internalSetBigContentTitle(safeCharSequence(title));
8084             return this;
8085         }
8086 
8087         /**
8088          * Set the first line of text after the detail section in the big form of the template.
8089          */
setSummaryText(CharSequence cs)8090         public InboxStyle setSummaryText(CharSequence cs) {
8091             internalSetSummaryText(safeCharSequence(cs));
8092             return this;
8093         }
8094 
8095         /**
8096          * Append a line to the digest section of the Inbox notification.
8097          */
addLine(CharSequence cs)8098         public InboxStyle addLine(CharSequence cs) {
8099             mTexts.add(safeCharSequence(cs));
8100             return this;
8101         }
8102 
8103         /**
8104          * @hide
8105          */
getLines()8106         public ArrayList<CharSequence> getLines() {
8107             return mTexts;
8108         }
8109 
8110         /**
8111          * @hide
8112          */
addExtras(Bundle extras)8113         public void addExtras(Bundle extras) {
8114             super.addExtras(extras);
8115 
8116             CharSequence[] a = new CharSequence[mTexts.size()];
8117             extras.putCharSequenceArray(EXTRA_TEXT_LINES, mTexts.toArray(a));
8118         }
8119 
8120         /**
8121          * @hide
8122          */
8123         @Override
restoreFromExtras(Bundle extras)8124         protected void restoreFromExtras(Bundle extras) {
8125             super.restoreFromExtras(extras);
8126 
8127             mTexts.clear();
8128             if (extras.containsKey(EXTRA_TEXT_LINES)) {
8129                 Collections.addAll(mTexts, extras.getCharSequenceArray(EXTRA_TEXT_LINES));
8130             }
8131         }
8132 
8133         /**
8134          * @hide
8135          */
makeBigContentView()8136         public RemoteViews makeBigContentView() {
8137             StandardTemplateParams p = mBuilder.mParams.reset().fillTextsFrom(mBuilder).text(null);
8138             TemplateBindResult result = new TemplateBindResult();
8139             RemoteViews contentView = getStandardView(mBuilder.getInboxLayoutResource(), p, result);
8140 
8141             int[] rowIds = {R.id.inbox_text0, R.id.inbox_text1, R.id.inbox_text2, R.id.inbox_text3,
8142                     R.id.inbox_text4, R.id.inbox_text5, R.id.inbox_text6};
8143 
8144             // Make sure all rows are gone in case we reuse a view.
8145             for (int rowId : rowIds) {
8146                 contentView.setViewVisibility(rowId, View.GONE);
8147             }
8148 
8149             int i=0;
8150             int topPadding = mBuilder.mContext.getResources().getDimensionPixelSize(
8151                     R.dimen.notification_inbox_item_top_padding);
8152             boolean first = true;
8153             int onlyViewId = 0;
8154             int maxRows = rowIds.length;
8155             if (mBuilder.mActions.size() > 0) {
8156                 maxRows--;
8157             }
8158             RemoteInputHistoryItem[] remoteInputHistory = getParcelableArrayFromBundle(
8159                     mBuilder.mN.extras, EXTRA_REMOTE_INPUT_HISTORY_ITEMS,
8160                     RemoteInputHistoryItem.class);
8161             if (remoteInputHistory != null
8162                     && remoteInputHistory.length > NUMBER_OF_HISTORY_ALLOWED_UNTIL_REDUCTION) {
8163                 // Let's remove some messages to make room for the remote input history.
8164                 // 1 is always able to fit, but let's remove them if they are 2 or 3
8165                 int numRemoteInputs = Math.min(remoteInputHistory.length,
8166                         MAX_REMOTE_INPUT_HISTORY_LINES);
8167                 int totalNumRows = mTexts.size() + numRemoteInputs
8168                         - NUMBER_OF_HISTORY_ALLOWED_UNTIL_REDUCTION;
8169                 if (totalNumRows > maxRows) {
8170                     int overflow = totalNumRows - maxRows;
8171                     if (mTexts.size() > maxRows) {
8172                         // Heuristic: if the Texts don't fit anyway, we'll rather drop the last
8173                         // few messages, even with the remote input
8174                         maxRows -= overflow;
8175                     } else  {
8176                         // otherwise we drop the first messages
8177                         i = overflow;
8178                     }
8179                 }
8180             }
8181             while (i < mTexts.size() && i < maxRows) {
8182                 CharSequence str = mTexts.get(i);
8183                 if (!TextUtils.isEmpty(str)) {
8184                     contentView.setViewVisibility(rowIds[i], View.VISIBLE);
8185                     contentView.setTextViewText(rowIds[i],
8186                             mBuilder.processTextSpans(mBuilder.processLegacyText(str)));
8187                     mBuilder.setTextViewColorSecondary(contentView, rowIds[i], p);
8188                     contentView.setViewPadding(rowIds[i], 0, topPadding, 0, 0);
8189                     handleInboxImageMargin(contentView, rowIds[i], first,
8190                             result.getIconMarginEnd());
8191                     if (first) {
8192                         onlyViewId = rowIds[i];
8193                     } else {
8194                         onlyViewId = 0;
8195                     }
8196                     first = false;
8197                 }
8198                 i++;
8199             }
8200             if (onlyViewId != 0) {
8201                 // We only have 1 entry, lets make it look like the normal Text of a Bigtext
8202                 topPadding = mBuilder.mContext.getResources().getDimensionPixelSize(
8203                         R.dimen.notification_text_margin_top);
8204                 contentView.setViewPadding(onlyViewId, 0, topPadding, 0, 0);
8205             }
8206 
8207             return contentView;
8208         }
8209 
8210         /**
8211          * @hide
8212          */
8213         @Override
areNotificationsVisiblyDifferent(Style other)8214         public boolean areNotificationsVisiblyDifferent(Style other) {
8215             if (other == null || getClass() != other.getClass()) {
8216                 return true;
8217             }
8218             InboxStyle newS = (InboxStyle) other;
8219 
8220             final ArrayList<CharSequence> myLines = getLines();
8221             final ArrayList<CharSequence> newLines = newS.getLines();
8222             final int n = myLines.size();
8223             if (n != newLines.size()) {
8224                 return true;
8225             }
8226 
8227             for (int i = 0; i < n; i++) {
8228                 if (!Objects.equals(
8229                         String.valueOf(myLines.get(i)),
8230                         String.valueOf(newLines.get(i)))) {
8231                     return true;
8232                 }
8233             }
8234             return false;
8235         }
8236 
handleInboxImageMargin(RemoteViews contentView, int id, boolean first, int marginEndValue)8237         private void handleInboxImageMargin(RemoteViews contentView, int id, boolean first,
8238                 int marginEndValue) {
8239             int endMargin = 0;
8240             if (first) {
8241                 final int max = mBuilder.mN.extras.getInt(EXTRA_PROGRESS_MAX, 0);
8242                 final boolean ind = mBuilder.mN.extras.getBoolean(EXTRA_PROGRESS_INDETERMINATE);
8243                 boolean hasProgress = max != 0 || ind;
8244                 if (!hasProgress) {
8245                     endMargin = marginEndValue;
8246                 }
8247             }
8248             contentView.setViewLayoutMarginEnd(id, endMargin);
8249         }
8250     }
8251 
8252     /**
8253      * Notification style for media playback notifications.
8254      *
8255      * In the expanded form, {@link Notification#bigContentView}, up to 5
8256      * {@link Notification.Action}s specified with
8257      * {@link Notification.Builder#addAction(Action) addAction} will be
8258      * shown as icon-only pushbuttons, suitable for transport controls. The Bitmap given to
8259      * {@link Notification.Builder#setLargeIcon(android.graphics.Bitmap) setLargeIcon()} will be
8260      * treated as album artwork.
8261      * <p>
8262      * Unlike the other styles provided here, MediaStyle can also modify the standard-size
8263      * {@link Notification#contentView}; by providing action indices to
8264      * {@link #setShowActionsInCompactView(int...)} you can promote up to 3 actions to be displayed
8265      * in the standard view alongside the usual content.
8266      * <p>
8267      * Notifications created with MediaStyle will have their category set to
8268      * {@link Notification#CATEGORY_TRANSPORT CATEGORY_TRANSPORT} unless you set a different
8269      * category using {@link Notification.Builder#setCategory(String) setCategory()}.
8270      * <p>
8271      * Finally, if you attach a {@link android.media.session.MediaSession.Token} using
8272      * {@link android.app.Notification.MediaStyle#setMediaSession(MediaSession.Token)},
8273      * the System UI can identify this as a notification representing an active media session
8274      * and respond accordingly (by showing album artwork in the lockscreen, for example).
8275      *
8276      * <p>
8277      * Starting at {@link android.os.Build.VERSION_CODES#O Android O} any notification that has a
8278      * media session attached with {@link #setMediaSession(MediaSession.Token)} will be colorized.
8279      * You can opt-out of this behavior by using {@link Notification.Builder#setColorized(boolean)}.
8280      * <p>
8281      *
8282      * To use this style with your Notification, feed it to
8283      * {@link Notification.Builder#setStyle(android.app.Notification.Style)} like so:
8284      * <pre class="prettyprint">
8285      * Notification noti = new Notification.Builder()
8286      *     .setSmallIcon(R.drawable.ic_stat_player)
8287      *     .setContentTitle(&quot;Track title&quot;)
8288      *     .setContentText(&quot;Artist - Album&quot;)
8289      *     .setLargeIcon(albumArtBitmap))
8290      *     .setStyle(<b>new Notification.MediaStyle()</b>
8291      *         .setMediaSession(mySession))
8292      *     .build();
8293      * </pre>
8294      *
8295      * @see Notification#bigContentView
8296      * @see Notification.Builder#setColorized(boolean)
8297      */
8298     public static class MediaStyle extends Style {
8299         // Changing max media buttons requires also changing templates
8300         // (notification_template_material_media and notification_template_material_big_media).
8301         static final int MAX_MEDIA_BUTTONS_IN_COMPACT = 3;
8302         static final int MAX_MEDIA_BUTTONS = 5;
8303         @IdRes private static final int[] MEDIA_BUTTON_IDS = {
8304                 R.id.action0,
8305                 R.id.action1,
8306                 R.id.action2,
8307                 R.id.action3,
8308                 R.id.action4,
8309         };
8310 
8311         private int[] mActionsToShowInCompact = null;
8312         private MediaSession.Token mToken;
8313 
MediaStyle()8314         public MediaStyle() {
8315         }
8316 
8317         /**
8318          * @deprecated use {@code MediaStyle()}.
8319          */
8320         @Deprecated
MediaStyle(Builder builder)8321         public MediaStyle(Builder builder) {
8322             setBuilder(builder);
8323         }
8324 
8325         /**
8326          * Request up to 3 actions (by index in the order of addition) to be shown in the compact
8327          * notification view.
8328          *
8329          * @param actions the indices of the actions to show in the compact notification view
8330          */
setShowActionsInCompactView(int...actions)8331         public MediaStyle setShowActionsInCompactView(int...actions) {
8332             mActionsToShowInCompact = actions;
8333             return this;
8334         }
8335 
8336         /**
8337          * Attach a {@link android.media.session.MediaSession.Token} to this Notification
8338          * to provide additional playback information and control to the SystemUI.
8339          */
setMediaSession(MediaSession.Token token)8340         public MediaStyle setMediaSession(MediaSession.Token token) {
8341             mToken = token;
8342             return this;
8343         }
8344 
8345         /**
8346          * @hide
8347          */
8348         @Override
8349         @UnsupportedAppUsage
buildStyled(Notification wip)8350         public Notification buildStyled(Notification wip) {
8351             super.buildStyled(wip);
8352             if (wip.category == null) {
8353                 wip.category = Notification.CATEGORY_TRANSPORT;
8354             }
8355             return wip;
8356         }
8357 
8358         /**
8359          * @hide
8360          */
8361         @Override
makeContentView(boolean increasedHeight)8362         public RemoteViews makeContentView(boolean increasedHeight) {
8363             return makeMediaContentView();
8364         }
8365 
8366         /**
8367          * @hide
8368          */
8369         @Override
makeBigContentView()8370         public RemoteViews makeBigContentView() {
8371             return makeMediaBigContentView();
8372         }
8373 
8374         /**
8375          * @hide
8376          */
8377         @Override
makeHeadsUpContentView(boolean increasedHeight)8378         public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
8379             return makeMediaContentView();
8380         }
8381 
8382         /** @hide */
8383         @Override
addExtras(Bundle extras)8384         public void addExtras(Bundle extras) {
8385             super.addExtras(extras);
8386 
8387             if (mToken != null) {
8388                 extras.putParcelable(EXTRA_MEDIA_SESSION, mToken);
8389             }
8390             if (mActionsToShowInCompact != null) {
8391                 extras.putIntArray(EXTRA_COMPACT_ACTIONS, mActionsToShowInCompact);
8392             }
8393         }
8394 
8395         /**
8396          * @hide
8397          */
8398         @Override
restoreFromExtras(Bundle extras)8399         protected void restoreFromExtras(Bundle extras) {
8400             super.restoreFromExtras(extras);
8401 
8402             if (extras.containsKey(EXTRA_MEDIA_SESSION)) {
8403                 mToken = extras.getParcelable(EXTRA_MEDIA_SESSION);
8404             }
8405             if (extras.containsKey(EXTRA_COMPACT_ACTIONS)) {
8406                 mActionsToShowInCompact = extras.getIntArray(EXTRA_COMPACT_ACTIONS);
8407             }
8408         }
8409 
8410         /**
8411          * @hide
8412          */
8413         @Override
areNotificationsVisiblyDifferent(Style other)8414         public boolean areNotificationsVisiblyDifferent(Style other) {
8415             if (other == null || getClass() != other.getClass()) {
8416                 return true;
8417             }
8418             // All fields to compare are on the Notification object
8419             return false;
8420         }
8421 
bindMediaActionButton(RemoteViews container, @IdRes int buttonId, Action action, StandardTemplateParams p)8422         private void bindMediaActionButton(RemoteViews container, @IdRes int buttonId,
8423                 Action action, StandardTemplateParams p) {
8424             final boolean tombstone = (action.actionIntent == null);
8425             container.setViewVisibility(buttonId, View.VISIBLE);
8426             if (buttonId != R.id.media_seamless) {
8427                 container.setImageViewIcon(buttonId, action.getIcon());
8428             }
8429 
8430             // If the action buttons should not be tinted, then just use the default
8431             // notification color. Otherwise, just use the passed-in color.
8432             Resources resources = mBuilder.mContext.getResources();
8433             Configuration currentConfig = resources.getConfiguration();
8434             boolean inNightMode = (currentConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK)
8435                     == Configuration.UI_MODE_NIGHT_YES;
8436             int tintColor = mBuilder.shouldTintActionButtons() || mBuilder.isColorized(p)
8437                     ? getActionColor(p)
8438                     : ContrastColorUtil.resolveColor(mBuilder.mContext,
8439                             Notification.COLOR_DEFAULT, inNightMode);
8440 
8441             container.setDrawableTint(buttonId, false, tintColor,
8442                     PorterDuff.Mode.SRC_ATOP);
8443 
8444             final TypedArray typedArray = mBuilder.mContext.obtainStyledAttributes(
8445                     new int[]{ android.R.attr.colorControlHighlight });
8446             int rippleAlpha = Color.alpha(typedArray.getColor(0, 0));
8447             typedArray.recycle();
8448             int rippleColor = Color.argb(rippleAlpha, Color.red(tintColor), Color.green(tintColor),
8449                     Color.blue(tintColor));
8450             container.setRippleDrawableColor(buttonId, ColorStateList.valueOf(rippleColor));
8451 
8452             if (!tombstone) {
8453                 container.setOnClickPendingIntent(buttonId, action.actionIntent);
8454             }
8455             container.setContentDescription(buttonId, action.title);
8456         }
8457 
makeMediaContentView()8458         private RemoteViews makeMediaContentView() {
8459             StandardTemplateParams p = mBuilder.mParams.reset().hasProgress(false).fillTextsFrom(
8460                     mBuilder);
8461             RemoteViews view = mBuilder.applyStandardTemplate(
8462                     R.layout.notification_template_material_media, p,
8463                     null /* result */);
8464 
8465             final int numActions = mBuilder.mActions.size();
8466             final int numActionsToShow = mActionsToShowInCompact == null
8467                     ? 0
8468                     : Math.min(mActionsToShowInCompact.length, MAX_MEDIA_BUTTONS_IN_COMPACT);
8469             if (numActionsToShow > numActions) {
8470                 throw new IllegalArgumentException(String.format(
8471                         "setShowActionsInCompactView: action %d out of bounds (max %d)",
8472                         numActions, numActions - 1));
8473             }
8474             for (int i = 0; i < MAX_MEDIA_BUTTONS_IN_COMPACT; i++) {
8475                 if (i < numActionsToShow) {
8476                     final Action action = mBuilder.mActions.get(mActionsToShowInCompact[i]);
8477                     bindMediaActionButton(view, MEDIA_BUTTON_IDS[i], action, p);
8478                 } else {
8479                     view.setViewVisibility(MEDIA_BUTTON_IDS[i], View.GONE);
8480                 }
8481             }
8482             bindMediaActionButton(view, R.id.media_seamless, new Action(
8483                     R.drawable.ic_media_seamless, mBuilder.mContext.getString(
8484                             com.android.internal.R.string.ext_media_seamless_action), null), p);
8485             view.setViewVisibility(R.id.media_seamless, View.GONE);
8486             handleImage(view);
8487             // handle the content margin
8488             int endMargin = R.dimen.notification_content_margin_end;
8489             if (mBuilder.mN.hasLargeIcon()) {
8490                 endMargin = R.dimen.notification_media_image_margin_end;
8491             }
8492             view.setViewLayoutMarginEndDimen(R.id.notification_main_column, endMargin);
8493             return view;
8494         }
8495 
getActionColor(StandardTemplateParams p)8496         private int getActionColor(StandardTemplateParams p) {
8497             return mBuilder.isColorized(p) ? mBuilder.getPrimaryTextColor(p)
8498                     : mBuilder.resolveContrastColor(p);
8499         }
8500 
makeMediaBigContentView()8501         private RemoteViews makeMediaBigContentView() {
8502             final int actionCount = Math.min(mBuilder.mActions.size(), MAX_MEDIA_BUTTONS);
8503             // Dont add an expanded view if there is no more content to be revealed
8504             int actionsInCompact = mActionsToShowInCompact == null
8505                     ? 0
8506                     : Math.min(mActionsToShowInCompact.length, MAX_MEDIA_BUTTONS_IN_COMPACT);
8507             if (!mBuilder.mN.hasLargeIcon() && actionCount <= actionsInCompact) {
8508                 return null;
8509             }
8510             StandardTemplateParams p = mBuilder.mParams.reset().hasProgress(false).fillTextsFrom(
8511                     mBuilder);
8512             RemoteViews big = mBuilder.applyStandardTemplate(
8513                     R.layout.notification_template_material_big_media, p , null /* result */);
8514 
8515             for (int i = 0; i < MAX_MEDIA_BUTTONS; i++) {
8516                 if (i < actionCount) {
8517                     bindMediaActionButton(big, MEDIA_BUTTON_IDS[i], mBuilder.mActions.get(i), p);
8518                 } else {
8519                     big.setViewVisibility(MEDIA_BUTTON_IDS[i], View.GONE);
8520                 }
8521             }
8522             bindMediaActionButton(big, R.id.media_seamless, new Action(R.drawable.ic_media_seamless,
8523                     mBuilder.mContext.getString(
8524                             com.android.internal.R.string.ext_media_seamless_action), null), p);
8525             big.setViewVisibility(R.id.media_seamless, View.GONE);
8526             handleImage(big);
8527             return big;
8528         }
8529 
handleImage(RemoteViews contentView)8530         private void handleImage(RemoteViews contentView) {
8531             if (mBuilder.mN.hasLargeIcon()) {
8532                 contentView.setViewLayoutMarginEndDimen(R.id.line1, 0);
8533                 contentView.setViewLayoutMarginEndDimen(R.id.text, 0);
8534             }
8535         }
8536 
8537         /**
8538          * @hide
8539          */
8540         @Override
hasProgress()8541         protected boolean hasProgress() {
8542             return false;
8543         }
8544     }
8545 
8546     /**
8547      * Notification style for custom views that are decorated by the system
8548      *
8549      * <p>Instead of providing a notification that is completely custom, a developer can set this
8550      * style and still obtain system decorations like the notification header with the expand
8551      * affordance and actions.
8552      *
8553      * <p>Use {@link android.app.Notification.Builder#setCustomContentView(RemoteViews)},
8554      * {@link android.app.Notification.Builder#setCustomBigContentView(RemoteViews)} and
8555      * {@link android.app.Notification.Builder#setCustomHeadsUpContentView(RemoteViews)} to set the
8556      * corresponding custom views to display.
8557      *
8558      * To use this style with your Notification, feed it to
8559      * {@link Notification.Builder#setStyle(android.app.Notification.Style)} like so:
8560      * <pre class="prettyprint">
8561      * Notification noti = new Notification.Builder()
8562      *     .setSmallIcon(R.drawable.ic_stat_player)
8563      *     .setLargeIcon(albumArtBitmap))
8564      *     .setCustomContentView(contentView);
8565      *     .setStyle(<b>new Notification.DecoratedCustomViewStyle()</b>)
8566      *     .build();
8567      * </pre>
8568      */
8569     public static class DecoratedCustomViewStyle extends Style {
8570 
DecoratedCustomViewStyle()8571         public DecoratedCustomViewStyle() {
8572         }
8573 
8574         /**
8575          * @hide
8576          */
displayCustomViewInline()8577         public boolean displayCustomViewInline() {
8578             return true;
8579         }
8580 
8581         /**
8582          * @hide
8583          */
8584         @Override
makeContentView(boolean increasedHeight)8585         public RemoteViews makeContentView(boolean increasedHeight) {
8586             return makeStandardTemplateWithCustomContent(mBuilder.mN.contentView);
8587         }
8588 
8589         /**
8590          * @hide
8591          */
8592         @Override
makeBigContentView()8593         public RemoteViews makeBigContentView() {
8594             return makeDecoratedBigContentView();
8595         }
8596 
8597         /**
8598          * @hide
8599          */
8600         @Override
makeHeadsUpContentView(boolean increasedHeight)8601         public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
8602             return makeDecoratedHeadsUpContentView();
8603         }
8604 
makeDecoratedHeadsUpContentView()8605         private RemoteViews makeDecoratedHeadsUpContentView() {
8606             RemoteViews headsUpContentView = mBuilder.mN.headsUpContentView == null
8607                     ? mBuilder.mN.contentView
8608                     : mBuilder.mN.headsUpContentView;
8609             if (mBuilder.mActions.size() == 0) {
8610                return makeStandardTemplateWithCustomContent(headsUpContentView);
8611             }
8612             TemplateBindResult result = new TemplateBindResult();
8613             RemoteViews remoteViews = mBuilder.applyStandardTemplateWithActions(
8614                         mBuilder.getBigBaseLayoutResource(), result);
8615             buildIntoRemoteViewContent(remoteViews, headsUpContentView, result);
8616             return remoteViews;
8617         }
8618 
makeStandardTemplateWithCustomContent(RemoteViews customContent)8619         private RemoteViews makeStandardTemplateWithCustomContent(RemoteViews customContent) {
8620             TemplateBindResult result = new TemplateBindResult();
8621             RemoteViews remoteViews = mBuilder.applyStandardTemplate(
8622                     mBuilder.getBaseLayoutResource(), result);
8623             buildIntoRemoteViewContent(remoteViews, customContent, result);
8624             return remoteViews;
8625         }
8626 
makeDecoratedBigContentView()8627         private RemoteViews makeDecoratedBigContentView() {
8628             RemoteViews bigContentView = mBuilder.mN.bigContentView == null
8629                     ? mBuilder.mN.contentView
8630                     : mBuilder.mN.bigContentView;
8631             if (mBuilder.mActions.size() == 0) {
8632                 return makeStandardTemplateWithCustomContent(bigContentView);
8633             }
8634             TemplateBindResult result = new TemplateBindResult();
8635             RemoteViews remoteViews = mBuilder.applyStandardTemplateWithActions(
8636                     mBuilder.getBigBaseLayoutResource(), result);
8637             buildIntoRemoteViewContent(remoteViews, bigContentView, result);
8638             return remoteViews;
8639         }
8640 
buildIntoRemoteViewContent(RemoteViews remoteViews, RemoteViews customContent, TemplateBindResult result)8641         private void buildIntoRemoteViewContent(RemoteViews remoteViews,
8642                 RemoteViews customContent, TemplateBindResult result) {
8643             int childIndex = -1;
8644             if (customContent != null) {
8645                 // Need to clone customContent before adding, because otherwise it can no longer be
8646                 // parceled independently of remoteViews.
8647                 customContent = customContent.clone();
8648                 remoteViews.removeAllViewsExceptId(R.id.notification_main_column, R.id.progress);
8649                 remoteViews.addView(R.id.notification_main_column, customContent, 0 /* index */);
8650                 remoteViews.addFlags(RemoteViews.FLAG_REAPPLY_DISALLOWED);
8651                 childIndex = 0;
8652             }
8653             remoteViews.setIntTag(R.id.notification_main_column,
8654                     com.android.internal.R.id.notification_custom_view_index_tag,
8655                     childIndex);
8656             // also update the end margin if there is an image
8657             Resources resources = mBuilder.mContext.getResources();
8658             int endMargin = resources.getDimensionPixelSize(
8659                     R.dimen.notification_content_margin_end) + result.getIconMarginEnd();
8660             remoteViews.setViewLayoutMarginEnd(R.id.notification_main_column, endMargin);
8661         }
8662 
8663         /**
8664          * @hide
8665          */
8666         @Override
areNotificationsVisiblyDifferent(Style other)8667         public boolean areNotificationsVisiblyDifferent(Style other) {
8668             if (other == null || getClass() != other.getClass()) {
8669                 return true;
8670             }
8671             // Comparison done for all custom RemoteViews, independent of style
8672             return false;
8673         }
8674     }
8675 
8676     /**
8677      * Notification style for media custom views that are decorated by the system
8678      *
8679      * <p>Instead of providing a media notification that is completely custom, a developer can set
8680      * this style and still obtain system decorations like the notification header with the expand
8681      * affordance and actions.
8682      *
8683      * <p>Use {@link android.app.Notification.Builder#setCustomContentView(RemoteViews)},
8684      * {@link android.app.Notification.Builder#setCustomBigContentView(RemoteViews)} and
8685      * {@link android.app.Notification.Builder#setCustomHeadsUpContentView(RemoteViews)} to set the
8686      * corresponding custom views to display.
8687      * <p>
8688      * Contrary to {@link MediaStyle} a developer has to opt-in to the colorizing of the
8689      * notification by using {@link Notification.Builder#setColorized(boolean)}.
8690      * <p>
8691      * To use this style with your Notification, feed it to
8692      * {@link Notification.Builder#setStyle(android.app.Notification.Style)} like so:
8693      * <pre class="prettyprint">
8694      * Notification noti = new Notification.Builder()
8695      *     .setSmallIcon(R.drawable.ic_stat_player)
8696      *     .setLargeIcon(albumArtBitmap))
8697      *     .setCustomContentView(contentView);
8698      *     .setStyle(<b>new Notification.DecoratedMediaCustomViewStyle()</b>
8699      *          .setMediaSession(mySession))
8700      *     .build();
8701      * </pre>
8702      *
8703      * @see android.app.Notification.DecoratedCustomViewStyle
8704      * @see android.app.Notification.MediaStyle
8705      */
8706     public static class DecoratedMediaCustomViewStyle extends MediaStyle {
8707 
DecoratedMediaCustomViewStyle()8708         public DecoratedMediaCustomViewStyle() {
8709         }
8710 
8711         /**
8712          * @hide
8713          */
displayCustomViewInline()8714         public boolean displayCustomViewInline() {
8715             return true;
8716         }
8717 
8718         /**
8719          * @hide
8720          */
8721         @Override
makeContentView(boolean increasedHeight)8722         public RemoteViews makeContentView(boolean increasedHeight) {
8723             RemoteViews remoteViews = super.makeContentView(false /* increasedHeight */);
8724             return buildIntoRemoteView(remoteViews, R.id.notification_content_container,
8725                     mBuilder.mN.contentView);
8726         }
8727 
8728         /**
8729          * @hide
8730          */
8731         @Override
makeBigContentView()8732         public RemoteViews makeBigContentView() {
8733             RemoteViews customRemoteView = mBuilder.mN.bigContentView != null
8734                     ? mBuilder.mN.bigContentView
8735                     : mBuilder.mN.contentView;
8736             return makeBigContentViewWithCustomContent(customRemoteView);
8737         }
8738 
makeBigContentViewWithCustomContent(RemoteViews customRemoteView)8739         private RemoteViews makeBigContentViewWithCustomContent(RemoteViews customRemoteView) {
8740             RemoteViews remoteViews = super.makeBigContentView();
8741             if (remoteViews != null) {
8742                 return buildIntoRemoteView(remoteViews, R.id.notification_main_column,
8743                         customRemoteView);
8744             } else if (customRemoteView != mBuilder.mN.contentView){
8745                 remoteViews = super.makeContentView(false /* increasedHeight */);
8746                 return buildIntoRemoteView(remoteViews, R.id.notification_content_container,
8747                         customRemoteView);
8748             } else {
8749                 return null;
8750             }
8751         }
8752 
8753         /**
8754          * @hide
8755          */
8756         @Override
makeHeadsUpContentView(boolean increasedHeight)8757         public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
8758             RemoteViews customRemoteView = mBuilder.mN.headsUpContentView != null
8759                     ? mBuilder.mN.headsUpContentView
8760                     : mBuilder.mN.contentView;
8761             return makeBigContentViewWithCustomContent(customRemoteView);
8762         }
8763 
8764         /**
8765          * @hide
8766          */
8767         @Override
areNotificationsVisiblyDifferent(Style other)8768         public boolean areNotificationsVisiblyDifferent(Style other) {
8769             if (other == null || getClass() != other.getClass()) {
8770                 return true;
8771             }
8772             // Comparison done for all custom RemoteViews, independent of style
8773             return false;
8774         }
8775 
buildIntoRemoteView(RemoteViews remoteViews, int id, RemoteViews customContent)8776         private RemoteViews buildIntoRemoteView(RemoteViews remoteViews, int id,
8777                 RemoteViews customContent) {
8778             if (customContent != null) {
8779                 // Need to clone customContent before adding, because otherwise it can no longer be
8780                 // parceled independently of remoteViews.
8781                 customContent = customContent.clone();
8782                 customContent.overrideTextColors(mBuilder.getPrimaryTextColor(mBuilder.mParams));
8783                 remoteViews.removeAllViews(id);
8784                 remoteViews.addView(id, customContent);
8785                 remoteViews.addFlags(RemoteViews.FLAG_REAPPLY_DISALLOWED);
8786             }
8787             return remoteViews;
8788         }
8789     }
8790 
8791     /**
8792      * Encapsulates the information needed to display a notification as a bubble.
8793      *
8794      * <p>A bubble is used to display app content in a floating window over the existing
8795      * foreground activity. A bubble has a collapsed state represented by an icon and an
8796      * expanded state that displays an activity. These may be defined via
8797      * {@link Builder#Builder(PendingIntent, Icon)} or they may
8798      * be defined via an existing shortcut using {@link Builder#Builder(String)}.
8799      * </p>
8800      *
8801      * <b>Notifications with a valid and allowed bubble will display in collapsed state
8802      * outside of the notification shade on unlocked devices. When a user interacts with the
8803      * collapsed bubble, the bubble activity will be invoked and displayed.</b>
8804      *
8805      * @see Notification.Builder#setBubbleMetadata(BubbleMetadata)
8806      */
8807     public static final class BubbleMetadata implements Parcelable {
8808 
8809         private PendingIntent mPendingIntent;
8810         private PendingIntent mDeleteIntent;
8811         private Icon mIcon;
8812         private int mDesiredHeight;
8813         @DimenRes private int mDesiredHeightResId;
8814         private int mFlags;
8815         private String mShortcutId;
8816 
8817         /**
8818          * If set and the app creating the bubble is in the foreground, the bubble will be posted
8819          * in its expanded state.
8820          *
8821          * <p>This flag has no effect if the app posting the bubble is not in the foreground.
8822          * The app is considered foreground if it is visible and on the screen, note that
8823          * a foreground service does not qualify.
8824          * </p>
8825          *
8826          * <p>Generally this flag should only be set if the user has performed an action to request
8827          * or create a bubble.</p>
8828          *
8829          * @hide
8830          */
8831         public static final int FLAG_AUTO_EXPAND_BUBBLE = 0x00000001;
8832 
8833         /**
8834          * Indicates whether the notification associated with the bubble is being visually
8835          * suppressed from the notification shade. When <code>true</code> the notification is
8836          * hidden, when <code>false</code> the notification shows as normal.
8837          *
8838          * <p>Apps sending bubbles may set this flag so that the bubble is posted <b>without</b>
8839          * the associated notification in the notification shade.</p>
8840          *
8841          * <p>Apps sending bubbles can only apply this flag when the app is in the foreground,
8842          * otherwise the flag is not respected. The app is considered foreground if it is visible
8843          * and on the screen, note that a foreground service does not qualify.</p>
8844          *
8845          * <p>Generally this flag should only be set by the app if the user has performed an
8846          * action to request or create a bubble, or if the user has seen the content in the
8847          * notification and the notification is no longer relevant. </p>
8848          *
8849          * <p>The system will also update this flag with <code>true</code> to hide the notification
8850          * from the user once the bubble has been expanded. </p>
8851          *
8852          * @hide
8853          */
8854         public static final int FLAG_SUPPRESS_NOTIFICATION = 0x00000002;
8855 
BubbleMetadata(PendingIntent expandIntent, PendingIntent deleteIntent, Icon icon, int height, @DimenRes int heightResId, String shortcutId)8856         private BubbleMetadata(PendingIntent expandIntent, PendingIntent deleteIntent,
8857                 Icon icon, int height, @DimenRes int heightResId, String shortcutId) {
8858             mPendingIntent = expandIntent;
8859             mIcon = icon;
8860             mDesiredHeight = height;
8861             mDesiredHeightResId = heightResId;
8862             mDeleteIntent = deleteIntent;
8863             mShortcutId = shortcutId;
8864         }
8865 
BubbleMetadata(Parcel in)8866         private BubbleMetadata(Parcel in) {
8867             if (in.readInt() != 0) {
8868                 mPendingIntent = PendingIntent.CREATOR.createFromParcel(in);
8869             }
8870             if (in.readInt() != 0) {
8871                 mIcon = Icon.CREATOR.createFromParcel(in);
8872             }
8873             mDesiredHeight = in.readInt();
8874             mFlags = in.readInt();
8875             if (in.readInt() != 0) {
8876                 mDeleteIntent = PendingIntent.CREATOR.createFromParcel(in);
8877             }
8878             mDesiredHeightResId = in.readInt();
8879             if (in.readInt() != 0) {
8880                 mShortcutId = in.readString8();
8881             }
8882         }
8883 
8884         /**
8885          * @return the shortcut id used for this bubble if created via
8886          * {@link Builder#Builder(String)} or null if created
8887          * via {@link Builder#Builder(PendingIntent, Icon)}.
8888          */
8889         @Nullable
getShortcutId()8890         public String getShortcutId() {
8891             return mShortcutId;
8892         }
8893 
8894         /**
8895          * @return the pending intent used to populate the floating window for this bubble, or
8896          * null if this bubble is created via {@link Builder#Builder(String)}.
8897          */
8898         @SuppressLint("InvalidNullConversion")
8899         @Nullable
getIntent()8900         public PendingIntent getIntent() {
8901             return mPendingIntent;
8902         }
8903 
8904         /**
8905          * @deprecated use {@link #getIntent()} instead.
8906          * @removed Removed from the R SDK but was never publicly stable.
8907          */
8908         @Nullable
8909         @Deprecated
getBubbleIntent()8910         public PendingIntent getBubbleIntent() {
8911             return mPendingIntent;
8912         }
8913 
8914         /**
8915          * @return the pending intent to send when the bubble is dismissed by a user, if one exists.
8916          */
8917         @Nullable
getDeleteIntent()8918         public PendingIntent getDeleteIntent() {
8919             return mDeleteIntent;
8920         }
8921 
8922         /**
8923          * @return the icon that will be displayed for this bubble when it is collapsed, or null
8924          * if the bubble is created via {@link Builder#Builder(String)}.
8925          */
8926         @SuppressLint("InvalidNullConversion")
8927         @Nullable
getIcon()8928         public Icon getIcon() {
8929             return mIcon;
8930         }
8931 
8932         /**
8933          * @deprecated use {@link #getIcon()} instead.
8934          * @removed Removed from the R SDK but was never publicly stable.
8935          */
8936         @Nullable
8937         @Deprecated
getBubbleIcon()8938         public Icon getBubbleIcon() {
8939             return mIcon;
8940         }
8941 
8942         /**
8943          * @return the ideal height, in DPs, for the floating window that app content defined by
8944          * {@link #getIntent()} for this bubble. A value of 0 indicates a desired height has
8945          * not been set.
8946          */
8947         @Dimension(unit = DP)
getDesiredHeight()8948         public int getDesiredHeight() {
8949             return mDesiredHeight;
8950         }
8951 
8952         /**
8953          * @return the resId of ideal height for the floating window that app content defined by
8954          * {@link #getIntent()} for this bubble. A value of 0 indicates a res value has not
8955          * been provided for the desired height.
8956          */
8957         @DimenRes
getDesiredHeightResId()8958         public int getDesiredHeightResId() {
8959             return mDesiredHeightResId;
8960         }
8961 
8962         /**
8963          * @return whether this bubble should auto expand when it is posted.
8964          *
8965          * @see BubbleMetadata.Builder#setAutoExpandBubble(boolean)
8966          */
getAutoExpandBubble()8967         public boolean getAutoExpandBubble() {
8968             return (mFlags & FLAG_AUTO_EXPAND_BUBBLE) != 0;
8969         }
8970 
8971         /**
8972          * Indicates whether the notification associated with the bubble is being visually
8973          * suppressed from the notification shade. When <code>true</code> the notification is
8974          * hidden, when <code>false</code> the notification shows as normal.
8975          *
8976          * <p>Apps sending bubbles may set this flag so that the bubble is posted <b>without</b>
8977          * the associated notification in the notification shade.</p>
8978          *
8979          * <p>Apps sending bubbles can only apply this flag when the app is in the foreground,
8980          * otherwise the flag is not respected. The app is considered foreground if it is visible
8981          * and on the screen, note that a foreground service does not qualify.</p>
8982          *
8983          * <p>Generally the app should only set this flag if the user has performed an
8984          * action to request or create a bubble, or if the user has seen the content in the
8985          * notification and the notification is no longer relevant. </p>
8986          *
8987          * <p>The system will update this flag with <code>true</code> to hide the notification
8988          * from the user once the bubble has been expanded.</p>
8989          *
8990          * @return whether this bubble should suppress the notification when it is posted.
8991          *
8992          * @see BubbleMetadata.Builder#setSuppressNotification(boolean)
8993          */
isNotificationSuppressed()8994         public boolean isNotificationSuppressed() {
8995             return (mFlags & FLAG_SUPPRESS_NOTIFICATION) != 0;
8996         }
8997 
8998         public static final @android.annotation.NonNull Parcelable.Creator<BubbleMetadata> CREATOR =
8999                 new Parcelable.Creator<BubbleMetadata>() {
9000 
9001                     @Override
9002                     public BubbleMetadata createFromParcel(Parcel source) {
9003                         return new BubbleMetadata(source);
9004                     }
9005 
9006                     @Override
9007                     public BubbleMetadata[] newArray(int size) {
9008                         return new BubbleMetadata[size];
9009                     }
9010                 };
9011 
9012         @Override
describeContents()9013         public int describeContents() {
9014             return 0;
9015         }
9016 
9017         @Override
writeToParcel(Parcel out, int flags)9018         public void writeToParcel(Parcel out, int flags) {
9019             out.writeInt(mPendingIntent != null ? 1 : 0);
9020             if (mPendingIntent != null) {
9021                 mPendingIntent.writeToParcel(out, 0);
9022             }
9023             out.writeInt(mIcon != null ? 1 : 0);
9024             if (mIcon != null) {
9025                 mIcon.writeToParcel(out, 0);
9026             }
9027             out.writeInt(mDesiredHeight);
9028             out.writeInt(mFlags);
9029             out.writeInt(mDeleteIntent != null ? 1 : 0);
9030             if (mDeleteIntent != null) {
9031                 mDeleteIntent.writeToParcel(out, 0);
9032             }
9033             out.writeInt(mDesiredHeightResId);
9034             out.writeInt(TextUtils.isEmpty(mShortcutId) ? 0 : 1);
9035             if (!TextUtils.isEmpty(mShortcutId)) {
9036                 out.writeString8(mShortcutId);
9037             }
9038         }
9039 
9040         /**
9041          * @hide
9042          */
setFlags(int flags)9043         public void setFlags(int flags) {
9044             mFlags = flags;
9045         }
9046 
9047         /**
9048          * @hide
9049          */
getFlags()9050         public int getFlags() {
9051             return mFlags;
9052         }
9053 
9054         /**
9055          * Builder to construct a {@link BubbleMetadata} object.
9056          */
9057         public static final class Builder {
9058 
9059             private PendingIntent mPendingIntent;
9060             private Icon mIcon;
9061             private int mDesiredHeight;
9062             @DimenRes private int mDesiredHeightResId;
9063             private int mFlags;
9064             private PendingIntent mDeleteIntent;
9065             private String mShortcutId;
9066 
9067             /**
9068              * @deprecated use {@link Builder#Builder(String)} for a bubble created via a
9069              * {@link ShortcutInfo} or {@link Builder#Builder(PendingIntent, Icon)} for a bubble
9070              * created via a {@link PendingIntent}.
9071              */
9072             @Deprecated
Builder()9073             public Builder() {
9074             }
9075 
9076             /**
9077              * Creates a {@link BubbleMetadata.Builder} based on a {@link ShortcutInfo}. To create
9078              * a shortcut bubble, ensure that the shortcut associated with the provided
9079              * {@param shortcutId} is published as a dynamic shortcut that was built with
9080              * {@link ShortcutInfo.Builder#setLongLived(boolean)} being true, otherwise your
9081              * notification will not be able to bubble.
9082              *
9083              * <p>The shortcut icon will be used to represent the bubble when it is collapsed.</p>
9084              *
9085              * <p>The shortcut activity will be used when the bubble is expanded. This will display
9086              * the shortcut activity in a floating window over the existing foreground activity.</p>
9087              *
9088              * <p>If the shortcut has not been published when the bubble notification is sent,
9089              * no bubble will be produced. If the shortcut is deleted while the bubble is active,
9090              * the bubble will be removed.</p>
9091              *
9092              * @throws NullPointerException if shortcutId is null.
9093              *
9094              * @see ShortcutInfo
9095              * @see ShortcutInfo.Builder#setLongLived(boolean)
9096              * @see android.content.pm.ShortcutManager#addDynamicShortcuts(List)
9097              */
Builder(@onNull String shortcutId)9098             public Builder(@NonNull String shortcutId) {
9099                 if (TextUtils.isEmpty(shortcutId)) {
9100                     throw new NullPointerException("Bubble requires a non-null shortcut id");
9101                 }
9102                 mShortcutId = shortcutId;
9103             }
9104 
9105             /**
9106              * Creates a {@link BubbleMetadata.Builder} based on the provided intent and icon.
9107              *
9108              * <p>The icon will be used to represent the bubble when it is collapsed. An icon
9109              * should be representative of the content within the bubble. If your app produces
9110              * multiple bubbles, the icon should be unique for each of them.</p>
9111              *
9112              * <p>The intent that will be used when the bubble is expanded. This will display the
9113              * app content in a floating window over the existing foreground activity. The intent
9114              * should point to a resizable activity. </p>
9115              *
9116              * @throws NullPointerException if intent is null.
9117              * @throws NullPointerException if icon is null.
9118              */
Builder(@onNull PendingIntent intent, @NonNull Icon icon)9119             public Builder(@NonNull PendingIntent intent, @NonNull Icon icon) {
9120                 if (intent == null) {
9121                     throw new NullPointerException("Bubble requires non-null pending intent");
9122                 }
9123                 if (icon == null) {
9124                     throw new NullPointerException("Bubbles require non-null icon");
9125                 }
9126                 if (icon.getType() != TYPE_URI_ADAPTIVE_BITMAP
9127                         && icon.getType() != TYPE_URI) {
9128                     Log.w(TAG, "Bubbles work best with icons of TYPE_URI or "
9129                             + "TYPE_URI_ADAPTIVE_BITMAP. "
9130                             + "In the future, using an icon of this type will be required.");
9131                 }
9132                 mPendingIntent = intent;
9133                 mIcon = icon;
9134             }
9135 
9136             /**
9137              * @deprecated use {@link Builder#Builder(String)} instead.
9138              * @removed Removed from the R SDK but was never publicly stable.
9139              */
9140             @NonNull
9141             @Deprecated
createShortcutBubble(@onNull String shortcutId)9142             public BubbleMetadata.Builder createShortcutBubble(@NonNull String shortcutId) {
9143                 if (!TextUtils.isEmpty(shortcutId)) {
9144                     // If shortcut id is set, we don't use these if they were previously set.
9145                     mPendingIntent = null;
9146                     mIcon = null;
9147                 }
9148                 mShortcutId = shortcutId;
9149                 return this;
9150             }
9151 
9152             /**
9153              * @deprecated use {@link Builder#Builder(PendingIntent, Icon)} instead.
9154              * @removed Removed from the R SDK but was never publicly stable.
9155              */
9156             @NonNull
9157             @Deprecated
createIntentBubble(@onNull PendingIntent intent, @NonNull Icon icon)9158             public BubbleMetadata.Builder createIntentBubble(@NonNull PendingIntent intent,
9159                     @NonNull Icon icon) {
9160                 if (intent == null) {
9161                     throw new IllegalArgumentException("Bubble requires non-null pending intent");
9162                 }
9163                 if (icon == null) {
9164                     throw new IllegalArgumentException("Bubbles require non-null icon");
9165                 }
9166                 if (icon.getType() != TYPE_URI_ADAPTIVE_BITMAP
9167                         && icon.getType() != TYPE_URI) {
9168                     Log.w(TAG, "Bubbles work best with icons of TYPE_URI or "
9169                             + "TYPE_URI_ADAPTIVE_BITMAP. "
9170                             + "In the future, using an icon of this type will be required.");
9171                 }
9172                 mShortcutId = null;
9173                 mPendingIntent = intent;
9174                 mIcon = icon;
9175                 return this;
9176             }
9177 
9178             /**
9179              * Sets the intent for the bubble.
9180              *
9181              * <p>The intent that will be used when the bubble is expanded. This will display the
9182              * app content in a floating window over the existing foreground activity. The intent
9183              * should point to a resizable activity. </p>
9184              *
9185              * @throws NullPointerException  if intent is null.
9186              * @throws IllegalStateException if this builder was created via
9187              *                               {@link Builder#Builder(String)}.
9188              */
9189             @NonNull
setIntent(@onNull PendingIntent intent)9190             public BubbleMetadata.Builder setIntent(@NonNull PendingIntent intent) {
9191                 if (mShortcutId != null) {
9192                     throw new IllegalStateException("Created as a shortcut bubble, cannot set a "
9193                             + "PendingIntent. Consider using "
9194                             + "BubbleMetadata.Builder(PendingIntent,Icon) instead.");
9195                 }
9196                 if (intent == null) {
9197                     throw new NullPointerException("Bubble requires non-null pending intent");
9198                 }
9199                 mPendingIntent = intent;
9200                 return this;
9201             }
9202 
9203             /**
9204              * Sets the icon for the bubble. Can only be used if the bubble was created
9205              * via {@link Builder#Builder(PendingIntent, Icon)}.
9206              *
9207              * <p>The icon will be used to represent the bubble when it is collapsed. An icon
9208              * should be representative of the content within the bubble. If your app produces
9209              * multiple bubbles, the icon should be unique for each of them.</p>
9210              *
9211              * <p>It is recommended to use an {@link Icon} of type {@link Icon#TYPE_URI}
9212              * or {@link Icon#TYPE_URI_ADAPTIVE_BITMAP}</p>
9213              *
9214              * @throws NullPointerException  if icon is null.
9215              * @throws IllegalStateException if this builder was created via
9216              *                               {@link Builder#Builder(String)}.
9217              */
9218             @NonNull
setIcon(@onNull Icon icon)9219             public BubbleMetadata.Builder setIcon(@NonNull Icon icon) {
9220                 if (mShortcutId != null) {
9221                     throw new IllegalStateException("Created as a shortcut bubble, cannot set an "
9222                             + "Icon. Consider using "
9223                             + "BubbleMetadata.Builder(PendingIntent,Icon) instead.");
9224                 }
9225                 if (icon == null) {
9226                     throw new NullPointerException("Bubbles require non-null icon");
9227                 }
9228                 if (icon.getType() != TYPE_URI_ADAPTIVE_BITMAP
9229                         && icon.getType() != TYPE_URI) {
9230                     Log.w(TAG, "Bubbles work best with icons of TYPE_URI or "
9231                             + "TYPE_URI_ADAPTIVE_BITMAP. "
9232                             + "In the future, using an icon of this type will be required.");
9233                 }
9234                 mIcon = icon;
9235                 return this;
9236             }
9237 
9238             /**
9239              * Sets the desired height in DPs for the expanded content of the bubble.
9240              *
9241              * <p>This height may not be respected if there is not enough space on the screen or if
9242              * the provided height is too small to be useful.</p>
9243              *
9244              * <p>If {@link #setDesiredHeightResId(int)} was previously called on this builder, the
9245              * previous value set will be cleared after calling this method, and this value will
9246              * be used instead.</p>
9247              *
9248              * <p>A desired height (in DPs or via resID) is optional.</p>
9249              *
9250              * @see #setDesiredHeightResId(int)
9251              */
9252             @NonNull
setDesiredHeight(@imensionunit = DP) int height)9253             public BubbleMetadata.Builder setDesiredHeight(@Dimension(unit = DP) int height) {
9254                 mDesiredHeight = Math.max(height, 0);
9255                 mDesiredHeightResId = 0;
9256                 return this;
9257             }
9258 
9259 
9260             /**
9261              * Sets the desired height via resId for the expanded content of the bubble.
9262              *
9263              * <p>This height may not be respected if there is not enough space on the screen or if
9264              * the provided height is too small to be useful.</p>
9265              *
9266              * <p>If {@link #setDesiredHeight(int)} was previously called on this builder, the
9267              * previous value set will be cleared after calling this method, and this value will
9268              * be used instead.</p>
9269              *
9270              * <p>A desired height (in DPs or via resID) is optional.</p>
9271              *
9272              * @see #setDesiredHeight(int)
9273              */
9274             @NonNull
setDesiredHeightResId(@imenRes int heightResId)9275             public BubbleMetadata.Builder setDesiredHeightResId(@DimenRes int heightResId) {
9276                 mDesiredHeightResId = heightResId;
9277                 mDesiredHeight = 0;
9278                 return this;
9279             }
9280 
9281             /**
9282              * Sets whether the bubble will be posted in its expanded state.
9283              *
9284              * <p>This flag has no effect if the app posting the bubble is not in the foreground.
9285              * The app is considered foreground if it is visible and on the screen, note that
9286              * a foreground service does not qualify.
9287              * </p>
9288              *
9289              * <p>Generally, this flag should only be set if the user has performed an action to
9290              * request or create a bubble.</p>
9291              *
9292              * <p>Setting this flag is optional; it defaults to false.</p>
9293              */
9294             @NonNull
setAutoExpandBubble(boolean shouldExpand)9295             public BubbleMetadata.Builder setAutoExpandBubble(boolean shouldExpand) {
9296                 setFlag(FLAG_AUTO_EXPAND_BUBBLE, shouldExpand);
9297                 return this;
9298             }
9299 
9300             /**
9301              * Sets whether the bubble will be posted <b>without</b> the associated notification in
9302              * the notification shade.
9303              *
9304              * <p>This flag has no effect if the app posting the bubble is not in the foreground.
9305              * The app is considered foreground if it is visible and on the screen, note that
9306              * a foreground service does not qualify.
9307              * </p>
9308              *
9309              * <p>Generally, this flag should only be set if the user has performed an action to
9310              * request or create a bubble, or if the user has seen the content in the notification
9311              * and the notification is no longer relevant.</p>
9312              *
9313              * <p>Setting this flag is optional; it defaults to false.</p>
9314              */
9315             @NonNull
setSuppressNotification(boolean shouldSuppressNotif)9316             public BubbleMetadata.Builder setSuppressNotification(boolean shouldSuppressNotif) {
9317                 setFlag(FLAG_SUPPRESS_NOTIFICATION, shouldSuppressNotif);
9318                 return this;
9319             }
9320 
9321             /**
9322              * Sets an intent to send when this bubble is explicitly removed by the user.
9323              *
9324              * <p>Setting a delete intent is optional.</p>
9325              */
9326             @NonNull
setDeleteIntent(@ullable PendingIntent deleteIntent)9327             public BubbleMetadata.Builder setDeleteIntent(@Nullable PendingIntent deleteIntent) {
9328                 mDeleteIntent = deleteIntent;
9329                 return this;
9330             }
9331 
9332             /**
9333              * Creates the {@link BubbleMetadata} defined by this builder.
9334              *
9335              * @throws NullPointerException if required elements have not been set.
9336              */
9337             @NonNull
build()9338             public BubbleMetadata build() {
9339                 if (mShortcutId == null && mPendingIntent == null) {
9340                     throw new NullPointerException(
9341                             "Must supply pending intent or shortcut to bubble");
9342                 }
9343                 if (mShortcutId == null && mIcon == null) {
9344                     throw new NullPointerException(
9345                             "Must supply an icon or shortcut for the bubble");
9346                 }
9347                 BubbleMetadata data = new BubbleMetadata(mPendingIntent, mDeleteIntent,
9348                         mIcon, mDesiredHeight, mDesiredHeightResId, mShortcutId);
9349                 data.setFlags(mFlags);
9350                 return data;
9351             }
9352 
9353             /**
9354              * @hide
9355              */
setFlag(int mask, boolean value)9356             public BubbleMetadata.Builder setFlag(int mask, boolean value) {
9357                 if (value) {
9358                     mFlags |= mask;
9359                 } else {
9360                     mFlags &= ~mask;
9361                 }
9362                 return this;
9363             }
9364         }
9365     }
9366 
9367 
9368     // When adding a new Style subclass here, don't forget to update
9369     // Builder.getNotificationStyleClass.
9370 
9371     /**
9372      * Extender interface for use with {@link Builder#extend}. Extenders may be used to add
9373      * metadata or change options on a notification builder.
9374      */
9375     public interface Extender {
9376         /**
9377          * Apply this extender to a notification builder.
9378          * @param builder the builder to be modified.
9379          * @return the build object for chaining.
9380          */
extend(Builder builder)9381         public Builder extend(Builder builder);
9382     }
9383 
9384     /**
9385      * Helper class to add wearable extensions to notifications.
9386      * <p class="note"> See
9387      * <a href="{@docRoot}wear/notifications/creating.html">Creating Notifications
9388      * for Android Wear</a> for more information on how to use this class.
9389      * <p>
9390      * To create a notification with wearable extensions:
9391      * <ol>
9392      *   <li>Create a {@link android.app.Notification.Builder}, setting any desired
9393      *   properties.
9394      *   <li>Create a {@link android.app.Notification.WearableExtender}.
9395      *   <li>Set wearable-specific properties using the
9396      *   {@code add} and {@code set} methods of {@link android.app.Notification.WearableExtender}.
9397      *   <li>Call {@link android.app.Notification.Builder#extend} to apply the extensions to a
9398      *   notification.
9399      *   <li>Post the notification to the notification system with the
9400      *   {@code NotificationManager.notify(...)} methods.
9401      * </ol>
9402      *
9403      * <pre class="prettyprint">
9404      * Notification notif = new Notification.Builder(mContext)
9405      *         .setContentTitle(&quot;New mail from &quot; + sender.toString())
9406      *         .setContentText(subject)
9407      *         .setSmallIcon(R.drawable.new_mail)
9408      *         .extend(new Notification.WearableExtender()
9409      *                 .setContentIcon(R.drawable.new_mail))
9410      *         .build();
9411      * NotificationManager notificationManger =
9412      *         (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
9413      * notificationManger.notify(0, notif);</pre>
9414      *
9415      * <p>Wearable extensions can be accessed on an existing notification by using the
9416      * {@code WearableExtender(Notification)} constructor,
9417      * and then using the {@code get} methods to access values.
9418      *
9419      * <pre class="prettyprint">
9420      * Notification.WearableExtender wearableExtender = new Notification.WearableExtender(
9421      *         notification);
9422      * List&lt;Notification&gt; pages = wearableExtender.getPages();</pre>
9423      */
9424     public static final class WearableExtender implements Extender {
9425         /**
9426          * Sentinel value for an action index that is unset.
9427          */
9428         public static final int UNSET_ACTION_INDEX = -1;
9429 
9430         /**
9431          * Size value for use with {@link #setCustomSizePreset} to show this notification with
9432          * default sizing.
9433          * <p>For custom display notifications created using {@link #setDisplayIntent},
9434          * the default is {@link #SIZE_MEDIUM}. All other notifications size automatically based
9435          * on their content.
9436          *
9437          * @deprecated Display intents are no longer supported.
9438          */
9439         @Deprecated
9440         public static final int SIZE_DEFAULT = 0;
9441 
9442         /**
9443          * Size value for use with {@link #setCustomSizePreset} to show this notification
9444          * with an extra small size.
9445          * <p>This value is only applicable for custom display notifications created using
9446          * {@link #setDisplayIntent}.
9447          *
9448          * @deprecated Display intents are no longer supported.
9449          */
9450         @Deprecated
9451         public static final int SIZE_XSMALL = 1;
9452 
9453         /**
9454          * Size value for use with {@link #setCustomSizePreset} to show this notification
9455          * with a small size.
9456          * <p>This value is only applicable for custom display notifications created using
9457          * {@link #setDisplayIntent}.
9458          *
9459          * @deprecated Display intents are no longer supported.
9460          */
9461         @Deprecated
9462         public static final int SIZE_SMALL = 2;
9463 
9464         /**
9465          * Size value for use with {@link #setCustomSizePreset} to show this notification
9466          * with a medium size.
9467          * <p>This value is only applicable for custom display notifications created using
9468          * {@link #setDisplayIntent}.
9469          *
9470          * @deprecated Display intents are no longer supported.
9471          */
9472         @Deprecated
9473         public static final int SIZE_MEDIUM = 3;
9474 
9475         /**
9476          * Size value for use with {@link #setCustomSizePreset} to show this notification
9477          * with a large size.
9478          * <p>This value is only applicable for custom display notifications created using
9479          * {@link #setDisplayIntent}.
9480          *
9481          * @deprecated Display intents are no longer supported.
9482          */
9483         @Deprecated
9484         public static final int SIZE_LARGE = 4;
9485 
9486         /**
9487          * Size value for use with {@link #setCustomSizePreset} to show this notification
9488          * full screen.
9489          * <p>This value is only applicable for custom display notifications created using
9490          * {@link #setDisplayIntent}.
9491          *
9492          * @deprecated Display intents are no longer supported.
9493          */
9494         @Deprecated
9495         public static final int SIZE_FULL_SCREEN = 5;
9496 
9497         /**
9498          * Sentinel value for use with {@link #setHintScreenTimeout} to keep the screen on for a
9499          * short amount of time when this notification is displayed on the screen. This
9500          * is the default value.
9501          *
9502          * @deprecated This feature is no longer supported.
9503          */
9504         @Deprecated
9505         public static final int SCREEN_TIMEOUT_SHORT = 0;
9506 
9507         /**
9508          * Sentinel value for use with {@link #setHintScreenTimeout} to keep the screen on
9509          * for a longer amount of time when this notification is displayed on the screen.
9510          *
9511          * @deprecated This feature is no longer supported.
9512          */
9513         @Deprecated
9514         public static final int SCREEN_TIMEOUT_LONG = -1;
9515 
9516         /** Notification extra which contains wearable extensions */
9517         private static final String EXTRA_WEARABLE_EXTENSIONS = "android.wearable.EXTENSIONS";
9518 
9519         // Keys within EXTRA_WEARABLE_EXTENSIONS for wearable options.
9520         private static final String KEY_ACTIONS = "actions";
9521         private static final String KEY_FLAGS = "flags";
9522         private static final String KEY_DISPLAY_INTENT = "displayIntent";
9523         private static final String KEY_PAGES = "pages";
9524         private static final String KEY_BACKGROUND = "background";
9525         private static final String KEY_CONTENT_ICON = "contentIcon";
9526         private static final String KEY_CONTENT_ICON_GRAVITY = "contentIconGravity";
9527         private static final String KEY_CONTENT_ACTION_INDEX = "contentActionIndex";
9528         private static final String KEY_CUSTOM_SIZE_PRESET = "customSizePreset";
9529         private static final String KEY_CUSTOM_CONTENT_HEIGHT = "customContentHeight";
9530         private static final String KEY_GRAVITY = "gravity";
9531         private static final String KEY_HINT_SCREEN_TIMEOUT = "hintScreenTimeout";
9532         private static final String KEY_DISMISSAL_ID = "dismissalId";
9533         private static final String KEY_BRIDGE_TAG = "bridgeTag";
9534 
9535         // Flags bitwise-ored to mFlags
9536         private static final int FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE = 0x1;
9537         private static final int FLAG_HINT_HIDE_ICON = 1 << 1;
9538         private static final int FLAG_HINT_SHOW_BACKGROUND_ONLY = 1 << 2;
9539         private static final int FLAG_START_SCROLL_BOTTOM = 1 << 3;
9540         private static final int FLAG_HINT_AVOID_BACKGROUND_CLIPPING = 1 << 4;
9541         private static final int FLAG_BIG_PICTURE_AMBIENT = 1 << 5;
9542         private static final int FLAG_HINT_CONTENT_INTENT_LAUNCHES_ACTIVITY = 1 << 6;
9543 
9544         // Default value for flags integer
9545         private static final int DEFAULT_FLAGS = FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE;
9546 
9547         private static final int DEFAULT_CONTENT_ICON_GRAVITY = Gravity.END;
9548         private static final int DEFAULT_GRAVITY = Gravity.BOTTOM;
9549 
9550         private ArrayList<Action> mActions = new ArrayList<Action>();
9551         private int mFlags = DEFAULT_FLAGS;
9552         private PendingIntent mDisplayIntent;
9553         private ArrayList<Notification> mPages = new ArrayList<Notification>();
9554         private Bitmap mBackground;
9555         private int mContentIcon;
9556         private int mContentIconGravity = DEFAULT_CONTENT_ICON_GRAVITY;
9557         private int mContentActionIndex = UNSET_ACTION_INDEX;
9558         private int mCustomSizePreset = SIZE_DEFAULT;
9559         private int mCustomContentHeight;
9560         private int mGravity = DEFAULT_GRAVITY;
9561         private int mHintScreenTimeout;
9562         private String mDismissalId;
9563         private String mBridgeTag;
9564 
9565         /**
9566          * Create a {@link android.app.Notification.WearableExtender} with default
9567          * options.
9568          */
WearableExtender()9569         public WearableExtender() {
9570         }
9571 
WearableExtender(Notification notif)9572         public WearableExtender(Notification notif) {
9573             Bundle wearableBundle = notif.extras.getBundle(EXTRA_WEARABLE_EXTENSIONS);
9574             if (wearableBundle != null) {
9575                 List<Action> actions = wearableBundle.getParcelableArrayList(KEY_ACTIONS);
9576                 if (actions != null) {
9577                     mActions.addAll(actions);
9578                 }
9579 
9580                 mFlags = wearableBundle.getInt(KEY_FLAGS, DEFAULT_FLAGS);
9581                 mDisplayIntent = wearableBundle.getParcelable(KEY_DISPLAY_INTENT);
9582 
9583                 Notification[] pages = getParcelableArrayFromBundle(
9584                         wearableBundle, KEY_PAGES, Notification.class);
9585                 if (pages != null) {
9586                     Collections.addAll(mPages, pages);
9587                 }
9588 
9589                 mBackground = wearableBundle.getParcelable(KEY_BACKGROUND);
9590                 mContentIcon = wearableBundle.getInt(KEY_CONTENT_ICON);
9591                 mContentIconGravity = wearableBundle.getInt(KEY_CONTENT_ICON_GRAVITY,
9592                         DEFAULT_CONTENT_ICON_GRAVITY);
9593                 mContentActionIndex = wearableBundle.getInt(KEY_CONTENT_ACTION_INDEX,
9594                         UNSET_ACTION_INDEX);
9595                 mCustomSizePreset = wearableBundle.getInt(KEY_CUSTOM_SIZE_PRESET,
9596                         SIZE_DEFAULT);
9597                 mCustomContentHeight = wearableBundle.getInt(KEY_CUSTOM_CONTENT_HEIGHT);
9598                 mGravity = wearableBundle.getInt(KEY_GRAVITY, DEFAULT_GRAVITY);
9599                 mHintScreenTimeout = wearableBundle.getInt(KEY_HINT_SCREEN_TIMEOUT);
9600                 mDismissalId = wearableBundle.getString(KEY_DISMISSAL_ID);
9601                 mBridgeTag = wearableBundle.getString(KEY_BRIDGE_TAG);
9602             }
9603         }
9604 
9605         /**
9606          * Apply wearable extensions to a notification that is being built. This is typically
9607          * called by the {@link android.app.Notification.Builder#extend} method of
9608          * {@link android.app.Notification.Builder}.
9609          */
9610         @Override
extend(Notification.Builder builder)9611         public Notification.Builder extend(Notification.Builder builder) {
9612             Bundle wearableBundle = new Bundle();
9613 
9614             if (!mActions.isEmpty()) {
9615                 wearableBundle.putParcelableArrayList(KEY_ACTIONS, mActions);
9616             }
9617             if (mFlags != DEFAULT_FLAGS) {
9618                 wearableBundle.putInt(KEY_FLAGS, mFlags);
9619             }
9620             if (mDisplayIntent != null) {
9621                 wearableBundle.putParcelable(KEY_DISPLAY_INTENT, mDisplayIntent);
9622             }
9623             if (!mPages.isEmpty()) {
9624                 wearableBundle.putParcelableArray(KEY_PAGES, mPages.toArray(
9625                         new Notification[mPages.size()]));
9626             }
9627             if (mBackground != null) {
9628                 wearableBundle.putParcelable(KEY_BACKGROUND, mBackground);
9629             }
9630             if (mContentIcon != 0) {
9631                 wearableBundle.putInt(KEY_CONTENT_ICON, mContentIcon);
9632             }
9633             if (mContentIconGravity != DEFAULT_CONTENT_ICON_GRAVITY) {
9634                 wearableBundle.putInt(KEY_CONTENT_ICON_GRAVITY, mContentIconGravity);
9635             }
9636             if (mContentActionIndex != UNSET_ACTION_INDEX) {
9637                 wearableBundle.putInt(KEY_CONTENT_ACTION_INDEX,
9638                         mContentActionIndex);
9639             }
9640             if (mCustomSizePreset != SIZE_DEFAULT) {
9641                 wearableBundle.putInt(KEY_CUSTOM_SIZE_PRESET, mCustomSizePreset);
9642             }
9643             if (mCustomContentHeight != 0) {
9644                 wearableBundle.putInt(KEY_CUSTOM_CONTENT_HEIGHT, mCustomContentHeight);
9645             }
9646             if (mGravity != DEFAULT_GRAVITY) {
9647                 wearableBundle.putInt(KEY_GRAVITY, mGravity);
9648             }
9649             if (mHintScreenTimeout != 0) {
9650                 wearableBundle.putInt(KEY_HINT_SCREEN_TIMEOUT, mHintScreenTimeout);
9651             }
9652             if (mDismissalId != null) {
9653                 wearableBundle.putString(KEY_DISMISSAL_ID, mDismissalId);
9654             }
9655             if (mBridgeTag != null) {
9656                 wearableBundle.putString(KEY_BRIDGE_TAG, mBridgeTag);
9657             }
9658 
9659             builder.getExtras().putBundle(EXTRA_WEARABLE_EXTENSIONS, wearableBundle);
9660             return builder;
9661         }
9662 
9663         @Override
clone()9664         public WearableExtender clone() {
9665             WearableExtender that = new WearableExtender();
9666             that.mActions = new ArrayList<Action>(this.mActions);
9667             that.mFlags = this.mFlags;
9668             that.mDisplayIntent = this.mDisplayIntent;
9669             that.mPages = new ArrayList<Notification>(this.mPages);
9670             that.mBackground = this.mBackground;
9671             that.mContentIcon = this.mContentIcon;
9672             that.mContentIconGravity = this.mContentIconGravity;
9673             that.mContentActionIndex = this.mContentActionIndex;
9674             that.mCustomSizePreset = this.mCustomSizePreset;
9675             that.mCustomContentHeight = this.mCustomContentHeight;
9676             that.mGravity = this.mGravity;
9677             that.mHintScreenTimeout = this.mHintScreenTimeout;
9678             that.mDismissalId = this.mDismissalId;
9679             that.mBridgeTag = this.mBridgeTag;
9680             return that;
9681         }
9682 
9683         /**
9684          * Add a wearable action to this notification.
9685          *
9686          * <p>When wearable actions are added using this method, the set of actions that
9687          * show on a wearable device splits from devices that only show actions added
9688          * using {@link android.app.Notification.Builder#addAction}. This allows for customization
9689          * of which actions display on different devices.
9690          *
9691          * @param action the action to add to this notification
9692          * @return this object for method chaining
9693          * @see android.app.Notification.Action
9694          */
addAction(Action action)9695         public WearableExtender addAction(Action action) {
9696             mActions.add(action);
9697             return this;
9698         }
9699 
9700         /**
9701          * Adds wearable actions to this notification.
9702          *
9703          * <p>When wearable actions are added using this method, the set of actions that
9704          * show on a wearable device splits from devices that only show actions added
9705          * using {@link android.app.Notification.Builder#addAction}. This allows for customization
9706          * of which actions display on different devices.
9707          *
9708          * @param actions the actions to add to this notification
9709          * @return this object for method chaining
9710          * @see android.app.Notification.Action
9711          */
addActions(List<Action> actions)9712         public WearableExtender addActions(List<Action> actions) {
9713             mActions.addAll(actions);
9714             return this;
9715         }
9716 
9717         /**
9718          * Clear all wearable actions present on this builder.
9719          * @return this object for method chaining.
9720          * @see #addAction
9721          */
clearActions()9722         public WearableExtender clearActions() {
9723             mActions.clear();
9724             return this;
9725         }
9726 
9727         /**
9728          * Get the wearable actions present on this notification.
9729          */
getActions()9730         public List<Action> getActions() {
9731             return mActions;
9732         }
9733 
9734         /**
9735          * Set an intent to launch inside of an activity view when displaying
9736          * this notification. The {@link PendingIntent} provided should be for an activity.
9737          *
9738          * <pre class="prettyprint">
9739          * Intent displayIntent = new Intent(context, MyDisplayActivity.class);
9740          * PendingIntent displayPendingIntent = PendingIntent.getActivity(context,
9741          *         0, displayIntent, PendingIntent.FLAG_UPDATE_CURRENT);
9742          * Notification notif = new Notification.Builder(context)
9743          *         .extend(new Notification.WearableExtender()
9744          *                 .setDisplayIntent(displayPendingIntent)
9745          *                 .setCustomSizePreset(Notification.WearableExtender.SIZE_MEDIUM))
9746          *         .build();</pre>
9747          *
9748          * <p>The activity to launch needs to allow embedding, must be exported, and
9749          * should have an empty task affinity. It is also recommended to use the device
9750          * default light theme.
9751          *
9752          * <p>Example AndroidManifest.xml entry:
9753          * <pre class="prettyprint">
9754          * &lt;activity android:name=&quot;com.example.MyDisplayActivity&quot;
9755          *     android:exported=&quot;true&quot;
9756          *     android:allowEmbedded=&quot;true&quot;
9757          *     android:taskAffinity=&quot;&quot;
9758          *     android:theme=&quot;@android:style/Theme.DeviceDefault.Light&quot; /&gt;</pre>
9759          *
9760          * @param intent the {@link PendingIntent} for an activity
9761          * @return this object for method chaining
9762          * @see android.app.Notification.WearableExtender#getDisplayIntent
9763          * @deprecated Display intents are no longer supported.
9764          */
9765         @Deprecated
setDisplayIntent(PendingIntent intent)9766         public WearableExtender setDisplayIntent(PendingIntent intent) {
9767             mDisplayIntent = intent;
9768             return this;
9769         }
9770 
9771         /**
9772          * Get the intent to launch inside of an activity view when displaying this
9773          * notification. This {@code PendingIntent} should be for an activity.
9774          *
9775          * @deprecated Display intents are no longer supported.
9776          */
9777         @Deprecated
getDisplayIntent()9778         public PendingIntent getDisplayIntent() {
9779             return mDisplayIntent;
9780         }
9781 
9782         /**
9783          * Add an additional page of content to display with this notification. The current
9784          * notification forms the first page, and pages added using this function form
9785          * subsequent pages. This field can be used to separate a notification into multiple
9786          * sections.
9787          *
9788          * @param page the notification to add as another page
9789          * @return this object for method chaining
9790          * @see android.app.Notification.WearableExtender#getPages
9791          * @deprecated Multiple content pages are no longer supported.
9792          */
9793         @Deprecated
addPage(Notification page)9794         public WearableExtender addPage(Notification page) {
9795             mPages.add(page);
9796             return this;
9797         }
9798 
9799         /**
9800          * Add additional pages of content to display with this notification. The current
9801          * notification forms the first page, and pages added using this function form
9802          * subsequent pages. This field can be used to separate a notification into multiple
9803          * sections.
9804          *
9805          * @param pages a list of notifications
9806          * @return this object for method chaining
9807          * @see android.app.Notification.WearableExtender#getPages
9808          * @deprecated Multiple content pages are no longer supported.
9809          */
9810         @Deprecated
addPages(List<Notification> pages)9811         public WearableExtender addPages(List<Notification> pages) {
9812             mPages.addAll(pages);
9813             return this;
9814         }
9815 
9816         /**
9817          * Clear all additional pages present on this builder.
9818          * @return this object for method chaining.
9819          * @see #addPage
9820          * @deprecated Multiple content pages are no longer supported.
9821          */
9822         @Deprecated
clearPages()9823         public WearableExtender clearPages() {
9824             mPages.clear();
9825             return this;
9826         }
9827 
9828         /**
9829          * Get the array of additional pages of content for displaying this notification. The
9830          * current notification forms the first page, and elements within this array form
9831          * subsequent pages. This field can be used to separate a notification into multiple
9832          * sections.
9833          * @return the pages for this notification
9834          * @deprecated Multiple content pages are no longer supported.
9835          */
9836         @Deprecated
getPages()9837         public List<Notification> getPages() {
9838             return mPages;
9839         }
9840 
9841         /**
9842          * Set a background image to be displayed behind the notification content.
9843          * Contrary to the {@link android.app.Notification.BigPictureStyle}, this background
9844          * will work with any notification style.
9845          *
9846          * @param background the background bitmap
9847          * @return this object for method chaining
9848          * @see android.app.Notification.WearableExtender#getBackground
9849          * @deprecated Background images are no longer supported.
9850          */
9851         @Deprecated
setBackground(Bitmap background)9852         public WearableExtender setBackground(Bitmap background) {
9853             mBackground = background;
9854             return this;
9855         }
9856 
9857         /**
9858          * Get a background image to be displayed behind the notification content.
9859          * Contrary to the {@link android.app.Notification.BigPictureStyle}, this background
9860          * will work with any notification style.
9861          *
9862          * @return the background image
9863          * @see android.app.Notification.WearableExtender#setBackground
9864          * @deprecated Background images are no longer supported.
9865          */
9866         @Deprecated
getBackground()9867         public Bitmap getBackground() {
9868             return mBackground;
9869         }
9870 
9871         /**
9872          * Set an icon that goes with the content of this notification.
9873          */
9874         @Deprecated
setContentIcon(int icon)9875         public WearableExtender setContentIcon(int icon) {
9876             mContentIcon = icon;
9877             return this;
9878         }
9879 
9880         /**
9881          * Get an icon that goes with the content of this notification.
9882          */
9883         @Deprecated
getContentIcon()9884         public int getContentIcon() {
9885             return mContentIcon;
9886         }
9887 
9888         /**
9889          * Set the gravity that the content icon should have within the notification display.
9890          * Supported values include {@link android.view.Gravity#START} and
9891          * {@link android.view.Gravity#END}. The default value is {@link android.view.Gravity#END}.
9892          * @see #setContentIcon
9893          */
9894         @Deprecated
setContentIconGravity(int contentIconGravity)9895         public WearableExtender setContentIconGravity(int contentIconGravity) {
9896             mContentIconGravity = contentIconGravity;
9897             return this;
9898         }
9899 
9900         /**
9901          * Get the gravity that the content icon should have within the notification display.
9902          * Supported values include {@link android.view.Gravity#START} and
9903          * {@link android.view.Gravity#END}. The default value is {@link android.view.Gravity#END}.
9904          * @see #getContentIcon
9905          */
9906         @Deprecated
getContentIconGravity()9907         public int getContentIconGravity() {
9908             return mContentIconGravity;
9909         }
9910 
9911         /**
9912          * Set an action from this notification's actions as the primary action. If the action has a
9913          * {@link RemoteInput} associated with it, shortcuts to the options for that input are shown
9914          * directly on the notification.
9915          *
9916          * @param actionIndex The index of the primary action.
9917          *                    If wearable actions were added to the main notification, this index
9918          *                    will apply to that list, otherwise it will apply to the regular
9919          *                    actions list.
9920          */
setContentAction(int actionIndex)9921         public WearableExtender setContentAction(int actionIndex) {
9922             mContentActionIndex = actionIndex;
9923             return this;
9924         }
9925 
9926         /**
9927          * Get the index of the notification action, if any, that was specified as the primary
9928          * action.
9929          *
9930          * <p>If wearable specific actions were added to the main notification, this index will
9931          * apply to that list, otherwise it will apply to the regular actions list.
9932          *
9933          * @return the action index or {@link #UNSET_ACTION_INDEX} if no action was selected.
9934          */
getContentAction()9935         public int getContentAction() {
9936             return mContentActionIndex;
9937         }
9938 
9939         /**
9940          * Set the gravity that this notification should have within the available viewport space.
9941          * Supported values include {@link android.view.Gravity#TOP},
9942          * {@link android.view.Gravity#CENTER_VERTICAL} and {@link android.view.Gravity#BOTTOM}.
9943          * The default value is {@link android.view.Gravity#BOTTOM}.
9944          */
9945         @Deprecated
setGravity(int gravity)9946         public WearableExtender setGravity(int gravity) {
9947             mGravity = gravity;
9948             return this;
9949         }
9950 
9951         /**
9952          * Get the gravity that this notification should have within the available viewport space.
9953          * Supported values include {@link android.view.Gravity#TOP},
9954          * {@link android.view.Gravity#CENTER_VERTICAL} and {@link android.view.Gravity#BOTTOM}.
9955          * The default value is {@link android.view.Gravity#BOTTOM}.
9956          */
9957         @Deprecated
getGravity()9958         public int getGravity() {
9959             return mGravity;
9960         }
9961 
9962         /**
9963          * Set the custom size preset for the display of this notification out of the available
9964          * presets found in {@link android.app.Notification.WearableExtender}, e.g.
9965          * {@link #SIZE_LARGE}.
9966          * <p>Some custom size presets are only applicable for custom display notifications created
9967          * using {@link android.app.Notification.WearableExtender#setDisplayIntent}. Check the
9968          * documentation for the preset in question. See also
9969          * {@link #setCustomContentHeight} and {@link #getCustomSizePreset}.
9970          */
9971         @Deprecated
setCustomSizePreset(int sizePreset)9972         public WearableExtender setCustomSizePreset(int sizePreset) {
9973             mCustomSizePreset = sizePreset;
9974             return this;
9975         }
9976 
9977         /**
9978          * Get the custom size preset for the display of this notification out of the available
9979          * presets found in {@link android.app.Notification.WearableExtender}, e.g.
9980          * {@link #SIZE_LARGE}.
9981          * <p>Some custom size presets are only applicable for custom display notifications created
9982          * using {@link #setDisplayIntent}. Check the documentation for the preset in question.
9983          * See also {@link #setCustomContentHeight} and {@link #setCustomSizePreset}.
9984          */
9985         @Deprecated
getCustomSizePreset()9986         public int getCustomSizePreset() {
9987             return mCustomSizePreset;
9988         }
9989 
9990         /**
9991          * Set the custom height in pixels for the display of this notification's content.
9992          * <p>This option is only available for custom display notifications created
9993          * using {@link android.app.Notification.WearableExtender#setDisplayIntent}. See also
9994          * {@link android.app.Notification.WearableExtender#setCustomSizePreset} and
9995          * {@link #getCustomContentHeight}.
9996          */
9997         @Deprecated
setCustomContentHeight(int height)9998         public WearableExtender setCustomContentHeight(int height) {
9999             mCustomContentHeight = height;
10000             return this;
10001         }
10002 
10003         /**
10004          * Get the custom height in pixels for the display of this notification's content.
10005          * <p>This option is only available for custom display notifications created
10006          * using {@link #setDisplayIntent}. See also {@link #setCustomSizePreset} and
10007          * {@link #setCustomContentHeight}.
10008          */
10009         @Deprecated
getCustomContentHeight()10010         public int getCustomContentHeight() {
10011             return mCustomContentHeight;
10012         }
10013 
10014         /**
10015          * Set whether the scrolling position for the contents of this notification should start
10016          * at the bottom of the contents instead of the top when the contents are too long to
10017          * display within the screen.  Default is false (start scroll at the top).
10018          */
setStartScrollBottom(boolean startScrollBottom)10019         public WearableExtender setStartScrollBottom(boolean startScrollBottom) {
10020             setFlag(FLAG_START_SCROLL_BOTTOM, startScrollBottom);
10021             return this;
10022         }
10023 
10024         /**
10025          * Get whether the scrolling position for the contents of this notification should start
10026          * at the bottom of the contents instead of the top when the contents are too long to
10027          * display within the screen. Default is false (start scroll at the top).
10028          */
getStartScrollBottom()10029         public boolean getStartScrollBottom() {
10030             return (mFlags & FLAG_START_SCROLL_BOTTOM) != 0;
10031         }
10032 
10033         /**
10034          * Set whether the content intent is available when the wearable device is not connected
10035          * to a companion device.  The user can still trigger this intent when the wearable device
10036          * is offline, but a visual hint will indicate that the content intent may not be available.
10037          * Defaults to true.
10038          */
setContentIntentAvailableOffline( boolean contentIntentAvailableOffline)10039         public WearableExtender setContentIntentAvailableOffline(
10040                 boolean contentIntentAvailableOffline) {
10041             setFlag(FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE, contentIntentAvailableOffline);
10042             return this;
10043         }
10044 
10045         /**
10046          * Get whether the content intent is available when the wearable device is not connected
10047          * to a companion device.  The user can still trigger this intent when the wearable device
10048          * is offline, but a visual hint will indicate that the content intent may not be available.
10049          * Defaults to true.
10050          */
getContentIntentAvailableOffline()10051         public boolean getContentIntentAvailableOffline() {
10052             return (mFlags & FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE) != 0;
10053         }
10054 
10055         /**
10056          * Set a hint that this notification's icon should not be displayed.
10057          * @param hintHideIcon {@code true} to hide the icon, {@code false} otherwise.
10058          * @return this object for method chaining
10059          */
10060         @Deprecated
setHintHideIcon(boolean hintHideIcon)10061         public WearableExtender setHintHideIcon(boolean hintHideIcon) {
10062             setFlag(FLAG_HINT_HIDE_ICON, hintHideIcon);
10063             return this;
10064         }
10065 
10066         /**
10067          * Get a hint that this notification's icon should not be displayed.
10068          * @return {@code true} if this icon should not be displayed, false otherwise.
10069          * The default value is {@code false} if this was never set.
10070          */
10071         @Deprecated
getHintHideIcon()10072         public boolean getHintHideIcon() {
10073             return (mFlags & FLAG_HINT_HIDE_ICON) != 0;
10074         }
10075 
10076         /**
10077          * Set a visual hint that only the background image of this notification should be
10078          * displayed, and other semantic content should be hidden. This hint is only applicable
10079          * to sub-pages added using {@link #addPage}.
10080          */
10081         @Deprecated
setHintShowBackgroundOnly(boolean hintShowBackgroundOnly)10082         public WearableExtender setHintShowBackgroundOnly(boolean hintShowBackgroundOnly) {
10083             setFlag(FLAG_HINT_SHOW_BACKGROUND_ONLY, hintShowBackgroundOnly);
10084             return this;
10085         }
10086 
10087         /**
10088          * Get a visual hint that only the background image of this notification should be
10089          * displayed, and other semantic content should be hidden. This hint is only applicable
10090          * to sub-pages added using {@link android.app.Notification.WearableExtender#addPage}.
10091          */
10092         @Deprecated
getHintShowBackgroundOnly()10093         public boolean getHintShowBackgroundOnly() {
10094             return (mFlags & FLAG_HINT_SHOW_BACKGROUND_ONLY) != 0;
10095         }
10096 
10097         /**
10098          * Set a hint that this notification's background should not be clipped if possible,
10099          * and should instead be resized to fully display on the screen, retaining the aspect
10100          * ratio of the image. This can be useful for images like barcodes or qr codes.
10101          * @param hintAvoidBackgroundClipping {@code true} to avoid clipping if possible.
10102          * @return this object for method chaining
10103          */
10104         @Deprecated
setHintAvoidBackgroundClipping( boolean hintAvoidBackgroundClipping)10105         public WearableExtender setHintAvoidBackgroundClipping(
10106                 boolean hintAvoidBackgroundClipping) {
10107             setFlag(FLAG_HINT_AVOID_BACKGROUND_CLIPPING, hintAvoidBackgroundClipping);
10108             return this;
10109         }
10110 
10111         /**
10112          * Get a hint that this notification's background should not be clipped if possible,
10113          * and should instead be resized to fully display on the screen, retaining the aspect
10114          * ratio of the image. This can be useful for images like barcodes or qr codes.
10115          * @return {@code true} if it's ok if the background is clipped on the screen, false
10116          * otherwise. The default value is {@code false} if this was never set.
10117          */
10118         @Deprecated
getHintAvoidBackgroundClipping()10119         public boolean getHintAvoidBackgroundClipping() {
10120             return (mFlags & FLAG_HINT_AVOID_BACKGROUND_CLIPPING) != 0;
10121         }
10122 
10123         /**
10124          * Set a hint that the screen should remain on for at least this duration when
10125          * this notification is displayed on the screen.
10126          * @param timeout The requested screen timeout in milliseconds. Can also be either
10127          *     {@link #SCREEN_TIMEOUT_SHORT} or {@link #SCREEN_TIMEOUT_LONG}.
10128          * @return this object for method chaining
10129          */
10130         @Deprecated
setHintScreenTimeout(int timeout)10131         public WearableExtender setHintScreenTimeout(int timeout) {
10132             mHintScreenTimeout = timeout;
10133             return this;
10134         }
10135 
10136         /**
10137          * Get the duration, in milliseconds, that the screen should remain on for
10138          * when this notification is displayed.
10139          * @return the duration in milliseconds if > 0, or either one of the sentinel values
10140          *     {@link #SCREEN_TIMEOUT_SHORT} or {@link #SCREEN_TIMEOUT_LONG}.
10141          */
10142         @Deprecated
getHintScreenTimeout()10143         public int getHintScreenTimeout() {
10144             return mHintScreenTimeout;
10145         }
10146 
10147         /**
10148          * Set a hint that this notification's {@link BigPictureStyle} (if present) should be
10149          * converted to low-bit and displayed in ambient mode, especially useful for barcodes and
10150          * qr codes, as well as other simple black-and-white tickets.
10151          * @param hintAmbientBigPicture {@code true} to enable converstion and ambient.
10152          * @return this object for method chaining
10153          * @deprecated This feature is no longer supported.
10154          */
10155         @Deprecated
setHintAmbientBigPicture(boolean hintAmbientBigPicture)10156         public WearableExtender setHintAmbientBigPicture(boolean hintAmbientBigPicture) {
10157             setFlag(FLAG_BIG_PICTURE_AMBIENT, hintAmbientBigPicture);
10158             return this;
10159         }
10160 
10161         /**
10162          * Get a hint that this notification's {@link BigPictureStyle} (if present) should be
10163          * converted to low-bit and displayed in ambient mode, especially useful for barcodes and
10164          * qr codes, as well as other simple black-and-white tickets.
10165          * @return {@code true} if it should be displayed in ambient, false otherwise
10166          * otherwise. The default value is {@code false} if this was never set.
10167          * @deprecated This feature is no longer supported.
10168          */
10169         @Deprecated
getHintAmbientBigPicture()10170         public boolean getHintAmbientBigPicture() {
10171             return (mFlags & FLAG_BIG_PICTURE_AMBIENT) != 0;
10172         }
10173 
10174         /**
10175          * Set a hint that this notification's content intent will launch an {@link Activity}
10176          * directly, telling the platform that it can generate the appropriate transitions.
10177          * @param hintContentIntentLaunchesActivity {@code true} if the content intent will launch
10178          * an activity and transitions should be generated, false otherwise.
10179          * @return this object for method chaining
10180          */
setHintContentIntentLaunchesActivity( boolean hintContentIntentLaunchesActivity)10181         public WearableExtender setHintContentIntentLaunchesActivity(
10182                 boolean hintContentIntentLaunchesActivity) {
10183             setFlag(FLAG_HINT_CONTENT_INTENT_LAUNCHES_ACTIVITY, hintContentIntentLaunchesActivity);
10184             return this;
10185         }
10186 
10187         /**
10188          * Get a hint that this notification's content intent will launch an {@link Activity}
10189          * directly, telling the platform that it can generate the appropriate transitions
10190          * @return {@code true} if the content intent will launch an activity and transitions should
10191          * be generated, false otherwise. The default value is {@code false} if this was never set.
10192          */
getHintContentIntentLaunchesActivity()10193         public boolean getHintContentIntentLaunchesActivity() {
10194             return (mFlags & FLAG_HINT_CONTENT_INTENT_LAUNCHES_ACTIVITY) != 0;
10195         }
10196 
10197         /**
10198          * Sets the dismissal id for this notification. If a notification is posted with a
10199          * dismissal id, then when that notification is canceled, notifications on other wearables
10200          * and the paired Android phone having that same dismissal id will also be canceled. See
10201          * <a href="{@docRoot}wear/notifications/index.html">Adding Wearable Features to
10202          * Notifications</a> for more information.
10203          * @param dismissalId the dismissal id of the notification.
10204          * @return this object for method chaining
10205          */
setDismissalId(String dismissalId)10206         public WearableExtender setDismissalId(String dismissalId) {
10207             mDismissalId = dismissalId;
10208             return this;
10209         }
10210 
10211         /**
10212          * Returns the dismissal id of the notification.
10213          * @return the dismissal id of the notification or null if it has not been set.
10214          */
getDismissalId()10215         public String getDismissalId() {
10216             return mDismissalId;
10217         }
10218 
10219         /**
10220          * Sets a bridge tag for this notification. A bridge tag can be set for notifications
10221          * posted from a phone to provide finer-grained control on what notifications are bridged
10222          * to wearables. See <a href="{@docRoot}wear/notifications/index.html">Adding Wearable
10223          * Features to Notifications</a> for more information.
10224          * @param bridgeTag the bridge tag of the notification.
10225          * @return this object for method chaining
10226          */
setBridgeTag(String bridgeTag)10227         public WearableExtender setBridgeTag(String bridgeTag) {
10228             mBridgeTag = bridgeTag;
10229             return this;
10230         }
10231 
10232         /**
10233          * Returns the bridge tag of the notification.
10234          * @return the bridge tag or null if not present.
10235          */
getBridgeTag()10236         public String getBridgeTag() {
10237             return mBridgeTag;
10238         }
10239 
setFlag(int mask, boolean value)10240         private void setFlag(int mask, boolean value) {
10241             if (value) {
10242                 mFlags |= mask;
10243             } else {
10244                 mFlags &= ~mask;
10245             }
10246         }
10247     }
10248 
10249     /**
10250      * <p>Helper class to add Android Auto extensions to notifications. To create a notification
10251      * with car extensions:
10252      *
10253      * <ol>
10254      *  <li>Create an {@link Notification.Builder}, setting any desired
10255      *  properties.
10256      *  <li>Create a {@link CarExtender}.
10257      *  <li>Set car-specific properties using the {@code add} and {@code set} methods of
10258      *  {@link CarExtender}.
10259      *  <li>Call {@link Notification.Builder#extend(Notification.Extender)}
10260      *  to apply the extensions to a notification.
10261      * </ol>
10262      *
10263      * <pre class="prettyprint">
10264      * Notification notification = new Notification.Builder(context)
10265      *         ...
10266      *         .extend(new CarExtender()
10267      *                 .set*(...))
10268      *         .build();
10269      * </pre>
10270      *
10271      * <p>Car extensions can be accessed on an existing notification by using the
10272      * {@code CarExtender(Notification)} constructor, and then using the {@code get} methods
10273      * to access values.
10274      */
10275     public static final class CarExtender implements Extender {
10276         private static final String TAG = "CarExtender";
10277 
10278         private static final String EXTRA_CAR_EXTENDER = "android.car.EXTENSIONS";
10279         private static final String EXTRA_LARGE_ICON = "large_icon";
10280         private static final String EXTRA_CONVERSATION = "car_conversation";
10281         private static final String EXTRA_COLOR = "app_color";
10282 
10283         private Bitmap mLargeIcon;
10284         private UnreadConversation mUnreadConversation;
10285         private int mColor = Notification.COLOR_DEFAULT;
10286 
10287         /**
10288          * Create a {@link CarExtender} with default options.
10289          */
CarExtender()10290         public CarExtender() {
10291         }
10292 
10293         /**
10294          * Create a {@link CarExtender} from the CarExtender options of an existing Notification.
10295          *
10296          * @param notif The notification from which to copy options.
10297          */
CarExtender(Notification notif)10298         public CarExtender(Notification notif) {
10299             Bundle carBundle = notif.extras == null ?
10300                     null : notif.extras.getBundle(EXTRA_CAR_EXTENDER);
10301             if (carBundle != null) {
10302                 mLargeIcon = carBundle.getParcelable(EXTRA_LARGE_ICON);
10303                 mColor = carBundle.getInt(EXTRA_COLOR, Notification.COLOR_DEFAULT);
10304 
10305                 Bundle b = carBundle.getBundle(EXTRA_CONVERSATION);
10306                 mUnreadConversation = UnreadConversation.getUnreadConversationFromBundle(b);
10307             }
10308         }
10309 
10310         /**
10311          * Apply car extensions to a notification that is being built. This is typically called by
10312          * the {@link Notification.Builder#extend(Notification.Extender)}
10313          * method of {@link Notification.Builder}.
10314          */
10315         @Override
extend(Notification.Builder builder)10316         public Notification.Builder extend(Notification.Builder builder) {
10317             Bundle carExtensions = new Bundle();
10318 
10319             if (mLargeIcon != null) {
10320                 carExtensions.putParcelable(EXTRA_LARGE_ICON, mLargeIcon);
10321             }
10322             if (mColor != Notification.COLOR_DEFAULT) {
10323                 carExtensions.putInt(EXTRA_COLOR, mColor);
10324             }
10325 
10326             if (mUnreadConversation != null) {
10327                 Bundle b = mUnreadConversation.getBundleForUnreadConversation();
10328                 carExtensions.putBundle(EXTRA_CONVERSATION, b);
10329             }
10330 
10331             builder.getExtras().putBundle(EXTRA_CAR_EXTENDER, carExtensions);
10332             return builder;
10333         }
10334 
10335         /**
10336          * Sets the accent color to use when Android Auto presents the notification.
10337          *
10338          * Android Auto uses the color set with {@link Notification.Builder#setColor(int)}
10339          * to accent the displayed notification. However, not all colors are acceptable in an
10340          * automotive setting. This method can be used to override the color provided in the
10341          * notification in such a situation.
10342          */
setColor(@olorInt int color)10343         public CarExtender setColor(@ColorInt int color) {
10344             mColor = color;
10345             return this;
10346         }
10347 
10348         /**
10349          * Gets the accent color.
10350          *
10351          * @see #setColor
10352          */
10353         @ColorInt
getColor()10354         public int getColor() {
10355             return mColor;
10356         }
10357 
10358         /**
10359          * Sets the large icon of the car notification.
10360          *
10361          * If no large icon is set in the extender, Android Auto will display the icon
10362          * specified by {@link Notification.Builder#setLargeIcon(android.graphics.Bitmap)}
10363          *
10364          * @param largeIcon The large icon to use in the car notification.
10365          * @return This object for method chaining.
10366          */
setLargeIcon(Bitmap largeIcon)10367         public CarExtender setLargeIcon(Bitmap largeIcon) {
10368             mLargeIcon = largeIcon;
10369             return this;
10370         }
10371 
10372         /**
10373          * Gets the large icon used in this car notification, or null if no icon has been set.
10374          *
10375          * @return The large icon for the car notification.
10376          * @see CarExtender#setLargeIcon
10377          */
getLargeIcon()10378         public Bitmap getLargeIcon() {
10379             return mLargeIcon;
10380         }
10381 
10382         /**
10383          * Sets the unread conversation in a message notification.
10384          *
10385          * @param unreadConversation The unread part of the conversation this notification conveys.
10386          * @return This object for method chaining.
10387          */
setUnreadConversation(UnreadConversation unreadConversation)10388         public CarExtender setUnreadConversation(UnreadConversation unreadConversation) {
10389             mUnreadConversation = unreadConversation;
10390             return this;
10391         }
10392 
10393         /**
10394          * Returns the unread conversation conveyed by this notification.
10395          * @see #setUnreadConversation(UnreadConversation)
10396          */
getUnreadConversation()10397         public UnreadConversation getUnreadConversation() {
10398             return mUnreadConversation;
10399         }
10400 
10401         /**
10402          * A class which holds the unread messages from a conversation.
10403          */
10404         public static class UnreadConversation {
10405             private static final String KEY_AUTHOR = "author";
10406             private static final String KEY_TEXT = "text";
10407             private static final String KEY_MESSAGES = "messages";
10408             private static final String KEY_REMOTE_INPUT = "remote_input";
10409             private static final String KEY_ON_REPLY = "on_reply";
10410             private static final String KEY_ON_READ = "on_read";
10411             private static final String KEY_PARTICIPANTS = "participants";
10412             private static final String KEY_TIMESTAMP = "timestamp";
10413 
10414             private final String[] mMessages;
10415             private final RemoteInput mRemoteInput;
10416             private final PendingIntent mReplyPendingIntent;
10417             private final PendingIntent mReadPendingIntent;
10418             private final String[] mParticipants;
10419             private final long mLatestTimestamp;
10420 
UnreadConversation(String[] messages, RemoteInput remoteInput, PendingIntent replyPendingIntent, PendingIntent readPendingIntent, String[] participants, long latestTimestamp)10421             UnreadConversation(String[] messages, RemoteInput remoteInput,
10422                     PendingIntent replyPendingIntent, PendingIntent readPendingIntent,
10423                     String[] participants, long latestTimestamp) {
10424                 mMessages = messages;
10425                 mRemoteInput = remoteInput;
10426                 mReadPendingIntent = readPendingIntent;
10427                 mReplyPendingIntent = replyPendingIntent;
10428                 mParticipants = participants;
10429                 mLatestTimestamp = latestTimestamp;
10430             }
10431 
10432             /**
10433              * Gets the list of messages conveyed by this notification.
10434              */
getMessages()10435             public String[] getMessages() {
10436                 return mMessages;
10437             }
10438 
10439             /**
10440              * Gets the remote input that will be used to convey the response to a message list, or
10441              * null if no such remote input exists.
10442              */
getRemoteInput()10443             public RemoteInput getRemoteInput() {
10444                 return mRemoteInput;
10445             }
10446 
10447             /**
10448              * Gets the pending intent that will be triggered when the user replies to this
10449              * notification.
10450              */
getReplyPendingIntent()10451             public PendingIntent getReplyPendingIntent() {
10452                 return mReplyPendingIntent;
10453             }
10454 
10455             /**
10456              * Gets the pending intent that Android Auto will send after it reads aloud all messages
10457              * in this object's message list.
10458              */
getReadPendingIntent()10459             public PendingIntent getReadPendingIntent() {
10460                 return mReadPendingIntent;
10461             }
10462 
10463             /**
10464              * Gets the participants in the conversation.
10465              */
getParticipants()10466             public String[] getParticipants() {
10467                 return mParticipants;
10468             }
10469 
10470             /**
10471              * Gets the firs participant in the conversation.
10472              */
getParticipant()10473             public String getParticipant() {
10474                 return mParticipants.length > 0 ? mParticipants[0] : null;
10475             }
10476 
10477             /**
10478              * Gets the timestamp of the conversation.
10479              */
getLatestTimestamp()10480             public long getLatestTimestamp() {
10481                 return mLatestTimestamp;
10482             }
10483 
getBundleForUnreadConversation()10484             Bundle getBundleForUnreadConversation() {
10485                 Bundle b = new Bundle();
10486                 String author = null;
10487                 if (mParticipants != null && mParticipants.length > 1) {
10488                     author = mParticipants[0];
10489                 }
10490                 Parcelable[] messages = new Parcelable[mMessages.length];
10491                 for (int i = 0; i < messages.length; i++) {
10492                     Bundle m = new Bundle();
10493                     m.putString(KEY_TEXT, mMessages[i]);
10494                     m.putString(KEY_AUTHOR, author);
10495                     messages[i] = m;
10496                 }
10497                 b.putParcelableArray(KEY_MESSAGES, messages);
10498                 if (mRemoteInput != null) {
10499                     b.putParcelable(KEY_REMOTE_INPUT, mRemoteInput);
10500                 }
10501                 b.putParcelable(KEY_ON_REPLY, mReplyPendingIntent);
10502                 b.putParcelable(KEY_ON_READ, mReadPendingIntent);
10503                 b.putStringArray(KEY_PARTICIPANTS, mParticipants);
10504                 b.putLong(KEY_TIMESTAMP, mLatestTimestamp);
10505                 return b;
10506             }
10507 
getUnreadConversationFromBundle(Bundle b)10508             static UnreadConversation getUnreadConversationFromBundle(Bundle b) {
10509                 if (b == null) {
10510                     return null;
10511                 }
10512                 Parcelable[] parcelableMessages = b.getParcelableArray(KEY_MESSAGES);
10513                 String[] messages = null;
10514                 if (parcelableMessages != null) {
10515                     String[] tmp = new String[parcelableMessages.length];
10516                     boolean success = true;
10517                     for (int i = 0; i < tmp.length; i++) {
10518                         if (!(parcelableMessages[i] instanceof Bundle)) {
10519                             success = false;
10520                             break;
10521                         }
10522                         tmp[i] = ((Bundle) parcelableMessages[i]).getString(KEY_TEXT);
10523                         if (tmp[i] == null) {
10524                             success = false;
10525                             break;
10526                         }
10527                     }
10528                     if (success) {
10529                         messages = tmp;
10530                     } else {
10531                         return null;
10532                     }
10533                 }
10534 
10535                 PendingIntent onRead = b.getParcelable(KEY_ON_READ);
10536                 PendingIntent onReply = b.getParcelable(KEY_ON_REPLY);
10537 
10538                 RemoteInput remoteInput = b.getParcelable(KEY_REMOTE_INPUT);
10539 
10540                 String[] participants = b.getStringArray(KEY_PARTICIPANTS);
10541                 if (participants == null || participants.length != 1) {
10542                     return null;
10543                 }
10544 
10545                 return new UnreadConversation(messages,
10546                         remoteInput,
10547                         onReply,
10548                         onRead,
10549                         participants, b.getLong(KEY_TIMESTAMP));
10550             }
10551         };
10552 
10553         /**
10554          * Builder class for {@link CarExtender.UnreadConversation} objects.
10555          */
10556         public static class Builder {
10557             private final List<String> mMessages = new ArrayList<String>();
10558             private final String mParticipant;
10559             private RemoteInput mRemoteInput;
10560             private PendingIntent mReadPendingIntent;
10561             private PendingIntent mReplyPendingIntent;
10562             private long mLatestTimestamp;
10563 
10564             /**
10565              * Constructs a new builder for {@link CarExtender.UnreadConversation}.
10566              *
10567              * @param name The name of the other participant in the conversation.
10568              */
Builder(String name)10569             public Builder(String name) {
10570                 mParticipant = name;
10571             }
10572 
10573             /**
10574              * Appends a new unread message to the list of messages for this conversation.
10575              *
10576              * The messages should be added from oldest to newest.
10577              *
10578              * @param message The text of the new unread message.
10579              * @return This object for method chaining.
10580              */
addMessage(String message)10581             public Builder addMessage(String message) {
10582                 mMessages.add(message);
10583                 return this;
10584             }
10585 
10586             /**
10587              * Sets the pending intent and remote input which will convey the reply to this
10588              * notification.
10589              *
10590              * @param pendingIntent The pending intent which will be triggered on a reply.
10591              * @param remoteInput The remote input parcelable which will carry the reply.
10592              * @return This object for method chaining.
10593              *
10594              * @see CarExtender.UnreadConversation#getRemoteInput
10595              * @see CarExtender.UnreadConversation#getReplyPendingIntent
10596              */
setReplyAction( PendingIntent pendingIntent, RemoteInput remoteInput)10597             public Builder setReplyAction(
10598                     PendingIntent pendingIntent, RemoteInput remoteInput) {
10599                 mRemoteInput = remoteInput;
10600                 mReplyPendingIntent = pendingIntent;
10601 
10602                 return this;
10603             }
10604 
10605             /**
10606              * Sets the pending intent that will be sent once the messages in this notification
10607              * are read.
10608              *
10609              * @param pendingIntent The pending intent to use.
10610              * @return This object for method chaining.
10611              */
setReadPendingIntent(PendingIntent pendingIntent)10612             public Builder setReadPendingIntent(PendingIntent pendingIntent) {
10613                 mReadPendingIntent = pendingIntent;
10614                 return this;
10615             }
10616 
10617             /**
10618              * Sets the timestamp of the most recent message in an unread conversation.
10619              *
10620              * If a messaging notification has been posted by your application and has not
10621              * yet been cancelled, posting a later notification with the same id and tag
10622              * but without a newer timestamp may result in Android Auto not displaying a
10623              * heads up notification for the later notification.
10624              *
10625              * @param timestamp The timestamp of the most recent message in the conversation.
10626              * @return This object for method chaining.
10627              */
setLatestTimestamp(long timestamp)10628             public Builder setLatestTimestamp(long timestamp) {
10629                 mLatestTimestamp = timestamp;
10630                 return this;
10631             }
10632 
10633             /**
10634              * Builds a new unread conversation object.
10635              *
10636              * @return The new unread conversation object.
10637              */
build()10638             public UnreadConversation build() {
10639                 String[] messages = mMessages.toArray(new String[mMessages.size()]);
10640                 String[] participants = { mParticipant };
10641                 return new UnreadConversation(messages, mRemoteInput, mReplyPendingIntent,
10642                         mReadPendingIntent, participants, mLatestTimestamp);
10643             }
10644         }
10645     }
10646 
10647     /**
10648      * <p>Helper class to add Android TV extensions to notifications. To create a notification
10649      * with a TV extension:
10650      *
10651      * <ol>
10652      *  <li>Create an {@link Notification.Builder}, setting any desired properties.
10653      *  <li>Create a {@link TvExtender}.
10654      *  <li>Set TV-specific properties using the {@code set} methods of
10655      *  {@link TvExtender}.
10656      *  <li>Call {@link Notification.Builder#extend(Notification.Extender)}
10657      *  to apply the extension to a notification.
10658      * </ol>
10659      *
10660      * <pre class="prettyprint">
10661      * Notification notification = new Notification.Builder(context)
10662      *         ...
10663      *         .extend(new TvExtender()
10664      *                 .set*(...))
10665      *         .build();
10666      * </pre>
10667      *
10668      * <p>TV extensions can be accessed on an existing notification by using the
10669      * {@code TvExtender(Notification)} constructor, and then using the {@code get} methods
10670      * to access values.
10671      *
10672      * @hide
10673      */
10674     @SystemApi
10675     public static final class TvExtender implements Extender {
10676         private static final String TAG = "TvExtender";
10677 
10678         private static final String EXTRA_TV_EXTENDER = "android.tv.EXTENSIONS";
10679         private static final String EXTRA_FLAGS = "flags";
10680         private static final String EXTRA_CONTENT_INTENT = "content_intent";
10681         private static final String EXTRA_DELETE_INTENT = "delete_intent";
10682         private static final String EXTRA_CHANNEL_ID = "channel_id";
10683         private static final String EXTRA_SUPPRESS_SHOW_OVER_APPS = "suppressShowOverApps";
10684 
10685         // Flags bitwise-ored to mFlags
10686         private static final int FLAG_AVAILABLE_ON_TV = 0x1;
10687 
10688         private int mFlags;
10689         private String mChannelId;
10690         private PendingIntent mContentIntent;
10691         private PendingIntent mDeleteIntent;
10692         private boolean mSuppressShowOverApps;
10693 
10694         /**
10695          * Create a {@link TvExtender} with default options.
10696          */
TvExtender()10697         public TvExtender() {
10698             mFlags = FLAG_AVAILABLE_ON_TV;
10699         }
10700 
10701         /**
10702          * Create a {@link TvExtender} from the TvExtender options of an existing Notification.
10703          *
10704          * @param notif The notification from which to copy options.
10705          */
TvExtender(Notification notif)10706         public TvExtender(Notification notif) {
10707             Bundle bundle = notif.extras == null ?
10708                 null : notif.extras.getBundle(EXTRA_TV_EXTENDER);
10709             if (bundle != null) {
10710                 mFlags = bundle.getInt(EXTRA_FLAGS);
10711                 mChannelId = bundle.getString(EXTRA_CHANNEL_ID);
10712                 mSuppressShowOverApps = bundle.getBoolean(EXTRA_SUPPRESS_SHOW_OVER_APPS);
10713                 mContentIntent = bundle.getParcelable(EXTRA_CONTENT_INTENT);
10714                 mDeleteIntent = bundle.getParcelable(EXTRA_DELETE_INTENT);
10715             }
10716         }
10717 
10718         /**
10719          * Apply a TV extension to a notification that is being built. This is typically called by
10720          * the {@link Notification.Builder#extend(Notification.Extender)}
10721          * method of {@link Notification.Builder}.
10722          */
10723         @Override
extend(Notification.Builder builder)10724         public Notification.Builder extend(Notification.Builder builder) {
10725             Bundle bundle = new Bundle();
10726 
10727             bundle.putInt(EXTRA_FLAGS, mFlags);
10728             bundle.putString(EXTRA_CHANNEL_ID, mChannelId);
10729             bundle.putBoolean(EXTRA_SUPPRESS_SHOW_OVER_APPS, mSuppressShowOverApps);
10730             if (mContentIntent != null) {
10731                 bundle.putParcelable(EXTRA_CONTENT_INTENT, mContentIntent);
10732             }
10733 
10734             if (mDeleteIntent != null) {
10735                 bundle.putParcelable(EXTRA_DELETE_INTENT, mDeleteIntent);
10736             }
10737 
10738             builder.getExtras().putBundle(EXTRA_TV_EXTENDER, bundle);
10739             return builder;
10740         }
10741 
10742         /**
10743          * Returns true if this notification should be shown on TV. This method return true
10744          * if the notification was extended with a TvExtender.
10745          */
isAvailableOnTv()10746         public boolean isAvailableOnTv() {
10747             return (mFlags & FLAG_AVAILABLE_ON_TV) != 0;
10748         }
10749 
10750         /**
10751          * Specifies the channel the notification should be delivered on when shown on TV.
10752          * It can be different from the channel that the notification is delivered to when
10753          * posting on a non-TV device.
10754          */
setChannel(String channelId)10755         public TvExtender setChannel(String channelId) {
10756             mChannelId = channelId;
10757             return this;
10758         }
10759 
10760         /**
10761          * Specifies the channel the notification should be delivered on when shown on TV.
10762          * It can be different from the channel that the notification is delivered to when
10763          * posting on a non-TV device.
10764          */
setChannelId(String channelId)10765         public TvExtender setChannelId(String channelId) {
10766             mChannelId = channelId;
10767             return this;
10768         }
10769 
10770         /** @removed */
10771         @Deprecated
getChannel()10772         public String getChannel() {
10773             return mChannelId;
10774         }
10775 
10776         /**
10777          * Returns the id of the channel this notification posts to on TV.
10778          */
getChannelId()10779         public String getChannelId() {
10780             return mChannelId;
10781         }
10782 
10783         /**
10784          * Supplies a {@link PendingIntent} to be sent when the notification is selected on TV.
10785          * If provided, it is used instead of the content intent specified
10786          * at the level of Notification.
10787          */
setContentIntent(PendingIntent intent)10788         public TvExtender setContentIntent(PendingIntent intent) {
10789             mContentIntent = intent;
10790             return this;
10791         }
10792 
10793         /**
10794          * Returns the TV-specific content intent.  If this method returns null, the
10795          * main content intent on the notification should be used.
10796          *
10797          * @see {@link Notification#contentIntent}
10798          */
getContentIntent()10799         public PendingIntent getContentIntent() {
10800             return mContentIntent;
10801         }
10802 
10803         /**
10804          * Supplies a {@link PendingIntent} to send when the notification is cleared explicitly
10805          * by the user on TV.  If provided, it is used instead of the delete intent specified
10806          * at the level of Notification.
10807          */
setDeleteIntent(PendingIntent intent)10808         public TvExtender setDeleteIntent(PendingIntent intent) {
10809             mDeleteIntent = intent;
10810             return this;
10811         }
10812 
10813         /**
10814          * Returns the TV-specific delete intent.  If this method returns null, the
10815          * main delete intent on the notification should be used.
10816          *
10817          * @see {@link Notification#deleteIntent}
10818          */
getDeleteIntent()10819         public PendingIntent getDeleteIntent() {
10820             return mDeleteIntent;
10821         }
10822 
10823         /**
10824          * Specifies whether this notification should suppress showing a message over top of apps
10825          * outside of the launcher.
10826          */
setSuppressShowOverApps(boolean suppress)10827         public TvExtender setSuppressShowOverApps(boolean suppress) {
10828             mSuppressShowOverApps = suppress;
10829             return this;
10830         }
10831 
10832         /**
10833          * Returns true if this notification should not show messages over top of apps
10834          * outside of the launcher.
10835          */
getSuppressShowOverApps()10836         public boolean getSuppressShowOverApps() {
10837             return mSuppressShowOverApps;
10838         }
10839     }
10840 
10841     /**
10842      * Get an array of Parcelable objects from a parcelable array bundle field.
10843      * Update the bundle to have a typed array so fetches in the future don't need
10844      * to do an array copy.
10845      */
10846     @Nullable
getParcelableArrayFromBundle( Bundle bundle, String key, Class<T> itemClass)10847     private static <T extends Parcelable> T[] getParcelableArrayFromBundle(
10848             Bundle bundle, String key, Class<T> itemClass) {
10849         final Parcelable[] array = bundle.getParcelableArray(key);
10850         final Class<?> arrayClass = Array.newInstance(itemClass, 0).getClass();
10851         if (arrayClass.isInstance(array) || array == null) {
10852             return (T[]) array;
10853         }
10854         final T[] typedArray = (T[]) Array.newInstance(itemClass, array.length);
10855         for (int i = 0; i < array.length; i++) {
10856             typedArray[i] = (T) array[i];
10857         }
10858         bundle.putParcelableArray(key, typedArray);
10859         return typedArray;
10860     }
10861 
10862     private static class BuilderRemoteViews extends RemoteViews {
BuilderRemoteViews(Parcel parcel)10863         public BuilderRemoteViews(Parcel parcel) {
10864             super(parcel);
10865         }
10866 
BuilderRemoteViews(ApplicationInfo appInfo, int layoutId)10867         public BuilderRemoteViews(ApplicationInfo appInfo, int layoutId) {
10868             super(appInfo, layoutId);
10869         }
10870 
10871         @Override
clone()10872         public BuilderRemoteViews clone() {
10873             Parcel p = Parcel.obtain();
10874             writeToParcel(p, 0);
10875             p.setDataPosition(0);
10876             BuilderRemoteViews brv = new BuilderRemoteViews(p);
10877             p.recycle();
10878             return brv;
10879         }
10880 
10881         /**
10882          * Override and return true, since {@link RemoteViews#onLoadClass(Class)} is not overridden.
10883          *
10884          * @see RemoteViews#shouldUseStaticFilter()
10885          */
10886         @Override
shouldUseStaticFilter()10887         protected boolean shouldUseStaticFilter() {
10888             return true;
10889         }
10890     }
10891 
10892     /**
10893      * A result object where information about the template that was created is saved.
10894      */
10895     private static class TemplateBindResult {
10896         int mIconMarginEnd;
10897         boolean mRightIconContainerVisible;
10898 
10899         /**
10900          * Get the margin end that needs to be added to any fields that may overlap
10901          * with the right actions.
10902          */
getIconMarginEnd()10903         public int getIconMarginEnd() {
10904             return mIconMarginEnd;
10905         }
10906 
10907         /**
10908          * Is the icon container visible on the right size because of the reply button or the
10909          * right icon.
10910          */
isRightIconContainerVisible()10911         public boolean isRightIconContainerVisible() {
10912             return mRightIconContainerVisible;
10913         }
10914 
setIconMarginEnd(int iconMarginEnd)10915         public void setIconMarginEnd(int iconMarginEnd) {
10916             this.mIconMarginEnd = iconMarginEnd;
10917         }
10918 
setRightIconContainerVisible(boolean iconContainerVisible)10919         public void setRightIconContainerVisible(boolean iconContainerVisible) {
10920             mRightIconContainerVisible = iconContainerVisible;
10921         }
10922     }
10923 
10924     private static class StandardTemplateParams {
10925         boolean hasProgress = true;
10926         CharSequence title;
10927         CharSequence text;
10928         CharSequence headerTextSecondary;
10929         CharSequence summaryText;
10930         int maxRemoteInputHistory = Style.MAX_REMOTE_INPUT_HISTORY_LINES;
10931         boolean hideLargeIcon;
10932         boolean hideReplyIcon;
10933         boolean allowColorization  = true;
10934         boolean forceDefaultColor = false;
10935 
reset()10936         final StandardTemplateParams reset() {
10937             hasProgress = true;
10938             title = null;
10939             text = null;
10940             summaryText = null;
10941             headerTextSecondary = null;
10942             maxRemoteInputHistory = Style.MAX_REMOTE_INPUT_HISTORY_LINES;
10943             allowColorization = true;
10944             forceDefaultColor = false;
10945             return this;
10946         }
10947 
hasProgress(boolean hasProgress)10948         final StandardTemplateParams hasProgress(boolean hasProgress) {
10949             this.hasProgress = hasProgress;
10950             return this;
10951         }
10952 
title(CharSequence title)10953         final StandardTemplateParams title(CharSequence title) {
10954             this.title = title;
10955             return this;
10956         }
10957 
text(CharSequence text)10958         final StandardTemplateParams text(CharSequence text) {
10959             this.text = text;
10960             return this;
10961         }
10962 
summaryText(CharSequence text)10963         final StandardTemplateParams summaryText(CharSequence text) {
10964             this.summaryText = text;
10965             return this;
10966         }
10967 
headerTextSecondary(CharSequence text)10968         final StandardTemplateParams headerTextSecondary(CharSequence text) {
10969             this.headerTextSecondary = text;
10970             return this;
10971         }
10972 
hideLargeIcon(boolean hideLargeIcon)10973         final StandardTemplateParams hideLargeIcon(boolean hideLargeIcon) {
10974             this.hideLargeIcon = hideLargeIcon;
10975             return this;
10976         }
10977 
hideReplyIcon(boolean hideReplyIcon)10978         final StandardTemplateParams hideReplyIcon(boolean hideReplyIcon) {
10979             this.hideReplyIcon = hideReplyIcon;
10980             return this;
10981         }
10982 
disallowColorization()10983         final StandardTemplateParams disallowColorization() {
10984             this.allowColorization = false;
10985             return this;
10986         }
10987 
forceDefaultColor()10988         final StandardTemplateParams forceDefaultColor() {
10989             this.forceDefaultColor = true;
10990             return this;
10991         }
10992 
fillTextsFrom(Builder b)10993         final StandardTemplateParams fillTextsFrom(Builder b) {
10994             Bundle extras = b.mN.extras;
10995             this.title = b.processLegacyText(extras.getCharSequence(EXTRA_TITLE));
10996             this.text = b.processLegacyText(extras.getCharSequence(EXTRA_TEXT));
10997             this.summaryText = extras.getCharSequence(EXTRA_SUB_TEXT);
10998             return this;
10999         }
11000 
11001         /**
11002          * Set the maximum lines of remote input history lines allowed.
11003          * @param maxRemoteInputHistory The number of lines.
11004          * @return The builder for method chaining.
11005          */
setMaxRemoteInputHistory(int maxRemoteInputHistory)11006         public StandardTemplateParams setMaxRemoteInputHistory(int maxRemoteInputHistory) {
11007             this.maxRemoteInputHistory = maxRemoteInputHistory;
11008             return this;
11009         }
11010     }
11011 }
11012