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 java.util.Objects.requireNonNull;
24 
25 import android.annotation.ColorInt;
26 import android.annotation.ColorRes;
27 import android.annotation.DimenRes;
28 import android.annotation.Dimension;
29 import android.annotation.DrawableRes;
30 import android.annotation.IdRes;
31 import android.annotation.IntDef;
32 import android.annotation.NonNull;
33 import android.annotation.Nullable;
34 import android.annotation.RequiresPermission;
35 import android.annotation.SdkConstant;
36 import android.annotation.SdkConstant.SdkConstantType;
37 import android.annotation.StringRes;
38 import android.annotation.StyleableRes;
39 import android.annotation.SuppressLint;
40 import android.annotation.SystemApi;
41 import android.annotation.TestApi;
42 import android.compat.annotation.UnsupportedAppUsage;
43 import android.content.Context;
44 import android.content.Intent;
45 import android.content.LocusId;
46 import android.content.pm.ApplicationInfo;
47 import android.content.pm.PackageManager;
48 import android.content.pm.PackageManager.NameNotFoundException;
49 import android.content.pm.ShortcutInfo;
50 import android.content.res.ColorStateList;
51 import android.content.res.Configuration;
52 import android.content.res.Resources;
53 import android.content.res.TypedArray;
54 import android.graphics.Bitmap;
55 import android.graphics.Canvas;
56 import android.graphics.Color;
57 import android.graphics.PorterDuff;
58 import android.graphics.drawable.Drawable;
59 import android.graphics.drawable.Icon;
60 import android.media.AudioAttributes;
61 import android.media.AudioManager;
62 import android.media.PlayerBase;
63 import android.media.session.MediaSession;
64 import android.net.Uri;
65 import android.os.BadParcelableException;
66 import android.os.Build;
67 import android.os.Bundle;
68 import android.os.IBinder;
69 import android.os.Parcel;
70 import android.os.Parcelable;
71 import android.os.SystemClock;
72 import android.os.SystemProperties;
73 import android.os.UserHandle;
74 import android.provider.Settings;
75 import android.text.BidiFormatter;
76 import android.text.SpannableStringBuilder;
77 import android.text.Spanned;
78 import android.text.TextUtils;
79 import android.text.style.AbsoluteSizeSpan;
80 import android.text.style.CharacterStyle;
81 import android.text.style.ForegroundColorSpan;
82 import android.text.style.RelativeSizeSpan;
83 import android.text.style.TextAppearanceSpan;
84 import android.util.ArraySet;
85 import android.util.Log;
86 import android.util.Pair;
87 import android.util.SparseArray;
88 import android.util.TypedValue;
89 import android.util.proto.ProtoOutputStream;
90 import android.view.ContextThemeWrapper;
91 import android.view.Gravity;
92 import android.view.View;
93 import android.view.contentcapture.ContentCaptureContext;
94 import android.widget.ProgressBar;
95 import android.widget.RemoteViews;
96 
97 import com.android.internal.R;
98 import com.android.internal.annotations.VisibleForTesting;
99 import com.android.internal.graphics.ColorUtils;
100 import com.android.internal.util.ArrayUtils;
101 import com.android.internal.util.ContrastColorUtil;
102 
103 import java.lang.annotation.Retention;
104 import java.lang.annotation.RetentionPolicy;
105 import java.lang.reflect.Array;
106 import java.lang.reflect.Constructor;
107 import java.util.ArrayList;
108 import java.util.Arrays;
109 import java.util.Collections;
110 import java.util.List;
111 import java.util.Objects;
112 import java.util.Set;
113 import java.util.function.Consumer;
114 
115 /**
116  * A class that represents how a persistent notification is to be presented to
117  * the user using the {@link android.app.NotificationManager}.
118  *
119  * <p>The {@link Notification.Builder Notification.Builder} has been added to make it
120  * easier to construct Notifications.</p>
121  *
122  * <div class="special reference">
123  * <h3>Developer Guides</h3>
124  * <p>For a guide to creating notifications, read the
125  * <a href="{@docRoot}guide/topics/ui/notifiers/notifications.html">Status Bar Notifications</a>
126  * developer guide.</p>
127  * </div>
128  */
129 public class Notification implements Parcelable
130 {
131     private static final String TAG = "Notification";
132 
133     /**
134      * @hide
135      */
136     @Retention(RetentionPolicy.SOURCE)
137     @IntDef({
138             FOREGROUND_SERVICE_DEFAULT,
139             FOREGROUND_SERVICE_IMMEDIATE,
140             FOREGROUND_SERVICE_DEFERRED
141     })
142     public @interface ServiceNotificationPolicy {};
143 
144     /**
145      * If the Notification associated with starting a foreground service has been
146      * built using setForegroundServiceBehavior() with this behavior, display of
147      * the notification will usually be suppressed for a short time to avoid visual
148      * disturbances to the user.
149      * @see Notification.Builder#setForegroundServiceBehavior(int)
150      * @see #FOREGROUND_SERVICE_IMMEDIATE
151      * @see #FOREGROUND_SERVICE_DEFERRED
152      */
153     public static final @ServiceNotificationPolicy int FOREGROUND_SERVICE_DEFAULT = 0;
154 
155     /**
156      * If the Notification associated with starting a foreground service has been
157      * built using setForegroundServiceBehavior() with this behavior, display of
158      * the notification will be immediate even if the default behavior would be
159      * to defer visibility for a short time.
160      * @see Notification.Builder#setForegroundServiceBehavior(int)
161      * @see #FOREGROUND_SERVICE_DEFAULT
162      * @see #FOREGROUND_SERVICE_DEFERRED
163      */
164     public static final @ServiceNotificationPolicy int FOREGROUND_SERVICE_IMMEDIATE = 1;
165 
166     /**
167      * If the Notification associated with starting a foreground service has been
168      * built using setForegroundServiceBehavior() with this behavior, display of
169      * the notification will usually be suppressed for a short time to avoid visual
170      * disturbances to the user.
171      * @see Notification.Builder#setForegroundServiceBehavior(int)
172      * @see #FOREGROUND_SERVICE_DEFAULT
173      * @see #FOREGROUND_SERVICE_IMMEDIATE
174      */
175     public static final @ServiceNotificationPolicy int FOREGROUND_SERVICE_DEFERRED = 2;
176 
177     @ServiceNotificationPolicy
178     private int mFgsDeferBehavior;
179 
180     /**
181      * An activity that provides a user interface for adjusting notification preferences for its
182      * containing application.
183      */
184     @SdkConstant(SdkConstantType.INTENT_CATEGORY)
185     public static final String INTENT_CATEGORY_NOTIFICATION_PREFERENCES
186             = "android.intent.category.NOTIFICATION_PREFERENCES";
187 
188     /**
189      * Optional extra for {@link #INTENT_CATEGORY_NOTIFICATION_PREFERENCES}. If provided, will
190      * contain a {@link NotificationChannel#getId() channel id} that can be used to narrow down
191      * what settings should be shown in the target app.
192      */
193     public static final String EXTRA_CHANNEL_ID = "android.intent.extra.CHANNEL_ID";
194 
195     /**
196      * Optional extra for {@link #INTENT_CATEGORY_NOTIFICATION_PREFERENCES}. If provided, will
197      * contain a {@link NotificationChannelGroup#getId() group id} that can be used to narrow down
198      * what settings should be shown in the target app.
199      */
200     public static final String EXTRA_CHANNEL_GROUP_ID = "android.intent.extra.CHANNEL_GROUP_ID";
201 
202     /**
203      * Optional extra for {@link #INTENT_CATEGORY_NOTIFICATION_PREFERENCES}. If provided, will
204      * contain the tag provided to {@link NotificationManager#notify(String, int, Notification)}
205      * that can be used to narrow down what settings should be shown in the target app.
206      */
207     public static final String EXTRA_NOTIFICATION_TAG = "android.intent.extra.NOTIFICATION_TAG";
208 
209     /**
210      * Optional extra for {@link #INTENT_CATEGORY_NOTIFICATION_PREFERENCES}. If provided, will
211      * contain the id provided to {@link NotificationManager#notify(String, int, Notification)}
212      * that can be used to narrow down what settings should be shown in the target app.
213      */
214     public static final String EXTRA_NOTIFICATION_ID = "android.intent.extra.NOTIFICATION_ID";
215 
216     /**
217      * Use all default values (where applicable).
218      */
219     public static final int DEFAULT_ALL = ~0;
220 
221     /**
222      * Use the default notification sound. This will ignore any given
223      * {@link #sound}.
224      *
225      * <p>
226      * A notification that is noisy is more likely to be presented as a heads-up notification.
227      * </p>
228      *
229      * @see #defaults
230      */
231 
232     public static final int DEFAULT_SOUND = 1;
233 
234     /**
235      * Use the default notification vibrate. This will ignore any given
236      * {@link #vibrate}. Using phone vibration requires the
237      * {@link android.Manifest.permission#VIBRATE VIBRATE} permission.
238      *
239      * <p>
240      * A notification that vibrates is more likely to be presented as a heads-up notification.
241      * </p>
242      *
243      * @see #defaults
244      */
245 
246     public static final int DEFAULT_VIBRATE = 2;
247 
248     /**
249      * Use the default notification lights. This will ignore the
250      * {@link #FLAG_SHOW_LIGHTS} bit, and {@link #ledARGB}, {@link #ledOffMS}, or
251      * {@link #ledOnMS}.
252      *
253      * @see #defaults
254      */
255 
256     public static final int DEFAULT_LIGHTS = 4;
257 
258     /**
259      * Maximum length of CharSequences accepted by Builder and friends.
260      *
261      * <p>
262      * Avoids spamming the system with overly large strings such as full e-mails.
263      */
264     private static final int MAX_CHARSEQUENCE_LENGTH = 1024;
265 
266     /**
267      * Maximum entries of reply text that are accepted by Builder and friends.
268      */
269     private static final int MAX_REPLY_HISTORY = 5;
270 
271     /**
272      * Maximum aspect ratio of the large icon. 16:9
273      */
274     private static final float MAX_LARGE_ICON_ASPECT_RATIO = 16f / 9f;
275 
276     /**
277      * Maximum number of (generic) action buttons in a notification (contextual action buttons are
278      * handled separately).
279      * @hide
280      */
281     public static final int MAX_ACTION_BUTTONS = 3;
282 
283     /**
284      * If the notification contained an unsent draft for a RemoteInput when the user clicked on it,
285      * we're adding the draft as a String extra to the {@link #contentIntent} using this key.
286      *
287      * <p>Apps may use this extra to prepopulate text fields in the app, where the user usually
288      * sends messages.</p>
289      */
290     public static final String EXTRA_REMOTE_INPUT_DRAFT = "android.remoteInputDraft";
291 
292     /**
293      * A timestamp related to this notification, in milliseconds since the epoch.
294      *
295      * Default value: {@link System#currentTimeMillis() Now}.
296      *
297      * Choose a timestamp that will be most relevant to the user. For most finite events, this
298      * corresponds to the time the event happened (or will happen, in the case of events that have
299      * yet to occur but about which the user is being informed). Indefinite events should be
300      * timestamped according to when the activity began.
301      *
302      * Some examples:
303      *
304      * <ul>
305      *   <li>Notification of a new chat message should be stamped when the message was received.</li>
306      *   <li>Notification of an ongoing file download (with a progress bar, for example) should be stamped when the download started.</li>
307      *   <li>Notification of a completed file download should be stamped when the download finished.</li>
308      *   <li>Notification of an upcoming meeting should be stamped with the time the meeting will begin (that is, in the future).</li>
309      *   <li>Notification of an ongoing stopwatch (increasing timer) should be stamped with the watch's start time.
310      *   <li>Notification of an ongoing countdown timer should be stamped with the timer's end time.
311      * </ul>
312      *
313      * For apps targeting {@link android.os.Build.VERSION_CODES#N} and above, this time is not shown
314      * anymore by default and must be opted into by using
315      * {@link android.app.Notification.Builder#setShowWhen(boolean)}
316      */
317     public long when;
318 
319     /**
320      * The creation time of the notification
321      */
322     private long creationTime;
323 
324     /**
325      * The resource id of a drawable to use as the icon in the status bar.
326      *
327      * @deprecated Use {@link Builder#setSmallIcon(Icon)} instead.
328      */
329     @Deprecated
330     @DrawableRes
331     public int icon;
332 
333     /**
334      * If the icon in the status bar is to have more than one level, you can set this.  Otherwise,
335      * leave it at its default value of 0.
336      *
337      * @see android.widget.ImageView#setImageLevel
338      * @see android.graphics.drawable.Drawable#setLevel
339      */
340     public int iconLevel;
341 
342     /**
343      * The number of events that this notification represents. For example, in a new mail
344      * notification, this could be the number of unread messages.
345      *
346      * The system may or may not use this field to modify the appearance of the notification.
347      * Starting with {@link android.os.Build.VERSION_CODES#O}, the number may be displayed as a
348      * badge icon in Launchers that support badging.
349      */
350     public int number = 0;
351 
352     /**
353      * The intent to execute when the expanded status entry is clicked.  If
354      * this is an activity, it must include the
355      * {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK} flag, which requires
356      * that you take care of task management as described in the
357      * <a href="{@docRoot}guide/topics/fundamentals/tasks-and-back-stack.html">Tasks and Back
358      * Stack</a> document.  In particular, make sure to read the notification section
359      * <a href="{@docRoot}guide/topics/ui/notifiers/notifications.html#HandlingNotifications">Handling
360      * Notifications</a> for the correct ways to launch an application from a
361      * notification.
362      */
363     public PendingIntent contentIntent;
364 
365     /**
366      * The intent to execute when the notification is explicitly dismissed by the user, either with
367      * the "Clear All" button or by swiping it away individually.
368      *
369      * This probably shouldn't be launching an activity since several of those will be sent
370      * at the same time.
371      */
372     public PendingIntent deleteIntent;
373 
374     /**
375      * An intent to launch instead of posting the notification to the status bar.
376      *
377      * <p>
378      * The system UI may choose to display a heads-up notification, instead of
379      * launching this intent, while the user is using the device.
380      * </p>
381      *
382      * @see Notification.Builder#setFullScreenIntent
383      */
384     public PendingIntent fullScreenIntent;
385 
386     /**
387      * Text that summarizes this notification for accessibility services.
388      *
389      * As of the L release, this text is no longer shown on screen, but it is still useful to
390      * accessibility services (where it serves as an audible announcement of the notification's
391      * appearance).
392      *
393      * @see #tickerView
394      */
395     public CharSequence tickerText;
396 
397     /**
398      * Formerly, a view showing the {@link #tickerText}.
399      *
400      * No longer displayed in the status bar as of API 21.
401      */
402     @Deprecated
403     public RemoteViews tickerView;
404 
405     /**
406      * The view that will represent this notification in the notification list (which is pulled
407      * down from the status bar).
408      *
409      * As of N, this field may be null. The notification view is determined by the inputs
410      * to {@link Notification.Builder}; a custom RemoteViews can optionally be
411      * supplied with {@link Notification.Builder#setCustomContentView(RemoteViews)}.
412      */
413     @Deprecated
414     public RemoteViews contentView;
415 
416     /**
417      * A large-format version of {@link #contentView}, giving the Notification an
418      * opportunity to show more detail. The system UI may choose to show this
419      * instead of the normal content view at its discretion.
420      *
421      * As of N, this field may be null. The expanded notification view is determined by the
422      * inputs to {@link Notification.Builder}; a custom RemoteViews can optionally be
423      * supplied with {@link Notification.Builder#setCustomBigContentView(RemoteViews)}.
424      */
425     @Deprecated
426     public RemoteViews bigContentView;
427 
428 
429     /**
430      * A medium-format version of {@link #contentView}, providing the Notification an
431      * opportunity to add action buttons to contentView. At its discretion, the system UI may
432      * choose to show this as a heads-up notification, which will pop up so the user can see
433      * it without leaving their current activity.
434      *
435      * As of N, this field may be null. The heads-up notification view is determined by the
436      * inputs to {@link Notification.Builder}; a custom RemoteViews can optionally be
437      * supplied with {@link Notification.Builder#setCustomHeadsUpContentView(RemoteViews)}.
438      */
439     @Deprecated
440     public RemoteViews headsUpContentView;
441 
442     private boolean mUsesStandardHeader;
443 
444     private static final ArraySet<Integer> STANDARD_LAYOUTS = new ArraySet<>();
445     static {
446         STANDARD_LAYOUTS.add(R.layout.notification_template_material_base);
447         STANDARD_LAYOUTS.add(R.layout.notification_template_material_heads_up_base);
448         STANDARD_LAYOUTS.add(R.layout.notification_template_material_big_base);
449         STANDARD_LAYOUTS.add(R.layout.notification_template_material_big_picture);
450         STANDARD_LAYOUTS.add(R.layout.notification_template_material_big_text);
451         STANDARD_LAYOUTS.add(R.layout.notification_template_material_inbox);
452         STANDARD_LAYOUTS.add(R.layout.notification_template_material_messaging);
453         STANDARD_LAYOUTS.add(R.layout.notification_template_material_big_messaging);
454         STANDARD_LAYOUTS.add(R.layout.notification_template_material_conversation);
455         STANDARD_LAYOUTS.add(R.layout.notification_template_material_media);
456         STANDARD_LAYOUTS.add(R.layout.notification_template_material_big_media);
457         STANDARD_LAYOUTS.add(R.layout.notification_template_material_call);
458         STANDARD_LAYOUTS.add(R.layout.notification_template_material_big_call);
459         STANDARD_LAYOUTS.add(R.layout.notification_template_header);
460     }
461 
462     /**
463      * A large bitmap to be shown in the notification content area.
464      *
465      * @deprecated Use {@link Builder#setLargeIcon(Icon)} instead.
466      */
467     @Deprecated
468     public Bitmap largeIcon;
469 
470     /**
471      * The sound to play.
472      *
473      * <p>
474      * A notification that is noisy is more likely to be presented as a heads-up notification.
475      * </p>
476      *
477      * <p>
478      * To play the default notification sound, see {@link #defaults}.
479      * </p>
480      * @deprecated use {@link NotificationChannel#getSound()}.
481      */
482     @Deprecated
483     public Uri sound;
484 
485     /**
486      * Use this constant as the value for audioStreamType to request that
487      * the default stream type for notifications be used.  Currently the
488      * default stream type is {@link AudioManager#STREAM_NOTIFICATION}.
489      *
490      * @deprecated Use {@link NotificationChannel#getAudioAttributes()} instead.
491      */
492     @Deprecated
493     public static final int STREAM_DEFAULT = -1;
494 
495     /**
496      * The audio stream type to use when playing the sound.
497      * Should be one of the STREAM_ constants from
498      * {@link android.media.AudioManager}.
499      *
500      * @deprecated Use {@link #audioAttributes} instead.
501      */
502     @Deprecated
503     public int audioStreamType = STREAM_DEFAULT;
504 
505     /**
506      * The default value of {@link #audioAttributes}.
507      */
508     public static final AudioAttributes AUDIO_ATTRIBUTES_DEFAULT = new AudioAttributes.Builder()
509             .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
510             .setUsage(AudioAttributes.USAGE_NOTIFICATION)
511             .build();
512 
513     /**
514      * The {@link AudioAttributes audio attributes} to use when playing the sound.
515      *
516      * @deprecated use {@link NotificationChannel#getAudioAttributes()} instead.
517      */
518     @Deprecated
519     public AudioAttributes audioAttributes = AUDIO_ATTRIBUTES_DEFAULT;
520 
521     /**
522      * The pattern with which to vibrate.
523      *
524      * <p>
525      * To vibrate the default pattern, see {@link #defaults}.
526      * </p>
527      *
528      * @see android.os.Vibrator#vibrate(long[],int)
529      * @deprecated use {@link NotificationChannel#getVibrationPattern()}.
530      */
531     @Deprecated
532     public long[] vibrate;
533 
534     /**
535      * The color of the led.  The hardware will do its best approximation.
536      *
537      * @see #FLAG_SHOW_LIGHTS
538      * @see #flags
539      * @deprecated use {@link NotificationChannel#shouldShowLights()}.
540      */
541     @ColorInt
542     @Deprecated
543     public int ledARGB;
544 
545     /**
546      * The number of milliseconds for the LED to be on while it's flashing.
547      * The hardware will do its best approximation.
548      *
549      * @see #FLAG_SHOW_LIGHTS
550      * @see #flags
551      * @deprecated use {@link NotificationChannel#shouldShowLights()}.
552      */
553     @Deprecated
554     public int ledOnMS;
555 
556     /**
557      * The number of milliseconds for the LED to be off while it's flashing.
558      * The hardware will do its best approximation.
559      *
560      * @see #FLAG_SHOW_LIGHTS
561      * @see #flags
562      *
563      * @deprecated use {@link NotificationChannel#shouldShowLights()}.
564      */
565     @Deprecated
566     public int ledOffMS;
567 
568     /**
569      * Specifies which values should be taken from the defaults.
570      * <p>
571      * To set, OR the desired from {@link #DEFAULT_SOUND},
572      * {@link #DEFAULT_VIBRATE}, {@link #DEFAULT_LIGHTS}. For all default
573      * values, use {@link #DEFAULT_ALL}.
574      * </p>
575      *
576      * @deprecated use {@link NotificationChannel#getSound()} and
577      * {@link NotificationChannel#shouldShowLights()} and
578      * {@link NotificationChannel#shouldVibrate()}.
579      */
580     @Deprecated
581     public int defaults;
582 
583     /**
584      * Bit to be bitwise-ored into the {@link #flags} field that should be
585      * set if you want the LED on for this notification.
586      * <ul>
587      * <li>To turn the LED off, pass 0 in the alpha channel for colorARGB
588      *      or 0 for both ledOnMS and ledOffMS.</li>
589      * <li>To turn the LED on, pass 1 for ledOnMS and 0 for ledOffMS.</li>
590      * <li>To flash the LED, pass the number of milliseconds that it should
591      *      be on and off to ledOnMS and ledOffMS.</li>
592      * </ul>
593      * <p>
594      * Since hardware varies, you are not guaranteed that any of the values
595      * you pass are honored exactly.  Use the system defaults if possible
596      * because they will be set to values that work on any given hardware.
597      * <p>
598      * The alpha channel must be set for forward compatibility.
599      *
600      * @deprecated use {@link NotificationChannel#shouldShowLights()}.
601      */
602     @Deprecated
603     public static final int FLAG_SHOW_LIGHTS        = 0x00000001;
604 
605     /**
606      * Bit to be bitwise-ored into the {@link #flags} field that should be
607      * set if this notification is in reference to something that is ongoing,
608      * like a phone call.  It should not be set if this notification is in
609      * reference to something that happened at a particular point in time,
610      * like a missed phone call.
611      */
612     public static final int FLAG_ONGOING_EVENT      = 0x00000002;
613 
614     /**
615      * Bit to be bitwise-ored into the {@link #flags} field that if set,
616      * the audio will be repeated until the notification is
617      * cancelled or the notification window is opened.
618      */
619     public static final int FLAG_INSISTENT          = 0x00000004;
620 
621     /**
622      * Bit to be bitwise-ored into the {@link #flags} field that should be
623      * set if you would only like the sound, vibrate and ticker to be played
624      * if the notification was not already showing.
625      */
626     public static final int FLAG_ONLY_ALERT_ONCE    = 0x00000008;
627 
628     /**
629      * Bit to be bitwise-ored into the {@link #flags} field that should be
630      * set if the notification should be canceled when it is clicked by the
631      * user.
632      */
633     public static final int FLAG_AUTO_CANCEL        = 0x00000010;
634 
635     /**
636      * Bit to be bitwise-ored into the {@link #flags} field that should be
637      * set if the notification should not be canceled when the user clicks
638      * the Clear all button.
639      */
640     public static final int FLAG_NO_CLEAR           = 0x00000020;
641 
642     /**
643      * Bit to be bitwise-ored into the {@link #flags} field that should be
644      * set if this notification represents a currently running service.  This
645      * will normally be set for you by {@link Service#startForeground}.
646      */
647     public static final int FLAG_FOREGROUND_SERVICE = 0x00000040;
648 
649     /**
650      * Obsolete flag indicating high-priority notifications; use the priority field instead.
651      *
652      * @deprecated Use {@link #priority} with a positive value.
653      */
654     @Deprecated
655     public static final int FLAG_HIGH_PRIORITY      = 0x00000080;
656 
657     /**
658      * Bit to be bitswise-ored into the {@link #flags} field that should be
659      * set if this notification is relevant to the current device only
660      * and it is not recommended that it bridge to other devices.
661      */
662     public static final int FLAG_LOCAL_ONLY         = 0x00000100;
663 
664     /**
665      * Bit to be bitswise-ored into the {@link #flags} field that should be
666      * set if this notification is the group summary for a group of notifications.
667      * Grouped notifications may display in a cluster or stack on devices which
668      * support such rendering. Requires a group key also be set using {@link Builder#setGroup}.
669      */
670     public static final int FLAG_GROUP_SUMMARY      = 0x00000200;
671 
672     /**
673      * Bit to be bitswise-ored into the {@link #flags} field that should be
674      * set if this notification is the group summary for an auto-group of notifications.
675      *
676      * @hide
677      */
678     @SystemApi
679     public static final int FLAG_AUTOGROUP_SUMMARY  = 0x00000400;
680 
681     /**
682      * @hide
683      */
684     public static final int FLAG_CAN_COLORIZE = 0x00000800;
685 
686     /**
687      * Bit to be bitswised-ored into the {@link #flags} field that should be
688      * set by the system if this notification is showing as a bubble.
689      *
690      * Applications cannot set this flag directly; they should instead call
691      * {@link Notification.Builder#setBubbleMetadata(BubbleMetadata)} to
692      * request that a notification be displayed as a bubble, and then check
693      * this flag to see whether that request was honored by the system.
694      */
695     public static final int FLAG_BUBBLE = 0x00001000;
696 
697     private static final List<Class<? extends Style>> PLATFORM_STYLE_CLASSES = Arrays.asList(
698             BigTextStyle.class, BigPictureStyle.class, InboxStyle.class, MediaStyle.class,
699             DecoratedCustomViewStyle.class, DecoratedMediaCustomViewStyle.class,
700             MessagingStyle.class, CallStyle.class);
701 
702     /** @hide */
703     @IntDef(flag = true, prefix = { "FLAG_" }, value = {FLAG_SHOW_LIGHTS, FLAG_ONGOING_EVENT,
704             FLAG_INSISTENT, FLAG_ONLY_ALERT_ONCE,
705             FLAG_AUTO_CANCEL, FLAG_NO_CLEAR, FLAG_FOREGROUND_SERVICE, FLAG_HIGH_PRIORITY,
706             FLAG_LOCAL_ONLY, FLAG_GROUP_SUMMARY, FLAG_AUTOGROUP_SUMMARY, FLAG_BUBBLE})
707     @Retention(RetentionPolicy.SOURCE)
708     public @interface NotificationFlags{};
709 
710     public int flags;
711 
712     /** @hide */
713     @IntDef(prefix = { "PRIORITY_" }, value = {
714             PRIORITY_DEFAULT,
715             PRIORITY_LOW,
716             PRIORITY_MIN,
717             PRIORITY_HIGH,
718             PRIORITY_MAX
719     })
720     @Retention(RetentionPolicy.SOURCE)
721     public @interface Priority {}
722 
723     /**
724      * Default notification {@link #priority}. If your application does not prioritize its own
725      * notifications, use this value for all notifications.
726      *
727      * @deprecated use {@link NotificationManager#IMPORTANCE_DEFAULT} instead.
728      */
729     @Deprecated
730     public static final int PRIORITY_DEFAULT = 0;
731 
732     /**
733      * Lower {@link #priority}, for items that are less important. The UI may choose to show these
734      * items smaller, or at a different position in the list, compared with your app's
735      * {@link #PRIORITY_DEFAULT} items.
736      *
737      * @deprecated use {@link NotificationManager#IMPORTANCE_LOW} instead.
738      */
739     @Deprecated
740     public static final int PRIORITY_LOW = -1;
741 
742     /**
743      * Lowest {@link #priority}; these items might not be shown to the user except under special
744      * circumstances, such as detailed notification logs.
745      *
746      * @deprecated use {@link NotificationManager#IMPORTANCE_MIN} instead.
747      */
748     @Deprecated
749     public static final int PRIORITY_MIN = -2;
750 
751     /**
752      * Higher {@link #priority}, for more important notifications or alerts. The UI may choose to
753      * show these items larger, or at a different position in notification lists, compared with
754      * your app's {@link #PRIORITY_DEFAULT} items.
755      *
756      * @deprecated use {@link NotificationManager#IMPORTANCE_HIGH} instead.
757      */
758     @Deprecated
759     public static final int PRIORITY_HIGH = 1;
760 
761     /**
762      * Highest {@link #priority}, for your application's most important items that require the
763      * user's prompt attention or input.
764      *
765      * @deprecated use {@link NotificationManager#IMPORTANCE_HIGH} instead.
766      */
767     @Deprecated
768     public static final int PRIORITY_MAX = 2;
769 
770     /**
771      * Relative priority for this notification.
772      *
773      * Priority is an indication of how much of the user's valuable attention should be consumed by
774      * this notification. Low-priority notifications may be hidden from the user in certain
775      * situations, while the user might be interrupted for a higher-priority notification. The
776      * system will make a determination about how to interpret this priority when presenting
777      * the notification.
778      *
779      * <p>
780      * A notification that is at least {@link #PRIORITY_HIGH} is more likely to be presented
781      * as a heads-up notification.
782      * </p>
783      *
784      * @deprecated use {@link NotificationChannel#getImportance()} instead.
785      */
786     @Priority
787     @Deprecated
788     public int priority;
789 
790     /**
791      * Accent color (an ARGB integer like the constants in {@link android.graphics.Color})
792      * to be applied by the standard Style templates when presenting this notification.
793      *
794      * The current template design constructs a colorful header image by overlaying the
795      * {@link #icon} image (stenciled in white) atop a field of this color. Alpha components are
796      * ignored.
797      */
798     @ColorInt
799     public int color = COLOR_DEFAULT;
800 
801     /**
802      * Special value of {@link #color} telling the system not to decorate this notification with
803      * any special color but instead use default colors when presenting this notification.
804      */
805     @ColorInt
806     public static final int COLOR_DEFAULT = 0; // AKA Color.TRANSPARENT
807 
808     /**
809      * Special value of {@link #color} used as a place holder for an invalid color.
810      * @hide
811      */
812     @ColorInt
813     public static final int COLOR_INVALID = 1;
814 
815     /**
816      * Sphere of visibility of this notification, which affects how and when the SystemUI reveals
817      * the notification's presence and contents in untrusted situations (namely, on the secure
818      * lockscreen).
819      *
820      * The default level, {@link #VISIBILITY_PRIVATE}, behaves exactly as notifications have always
821      * done on Android: The notification's {@link #icon} and {@link #tickerText} (if available) are
822      * shown in all situations, but the contents are only available if the device is unlocked for
823      * the appropriate user.
824      *
825      * A more permissive policy can be expressed by {@link #VISIBILITY_PUBLIC}; such a notification
826      * can be read even in an "insecure" context (that is, above a secure lockscreen).
827      * To modify the public version of this notification—for example, to redact some portions—see
828      * {@link Builder#setPublicVersion(Notification)}.
829      *
830      * Finally, a notification can be made {@link #VISIBILITY_SECRET}, which will suppress its icon
831      * and ticker until the user has bypassed the lockscreen.
832      */
833     public @Visibility int visibility;
834 
835     /** @hide */
836     @IntDef(prefix = { "VISIBILITY_" }, value = {
837             VISIBILITY_PUBLIC,
838             VISIBILITY_PRIVATE,
839             VISIBILITY_SECRET,
840     })
841     @Retention(RetentionPolicy.SOURCE)
842     public @interface Visibility {}
843 
844     /**
845      * Notification visibility: Show this notification in its entirety on all lockscreens.
846      *
847      * {@see #visibility}
848      */
849     public static final int VISIBILITY_PUBLIC = 1;
850 
851     /**
852      * Notification visibility: Show this notification on all lockscreens, but conceal sensitive or
853      * private information on secure lockscreens.
854      *
855      * {@see #visibility}
856      */
857     public static final int VISIBILITY_PRIVATE = 0;
858 
859     /**
860      * Notification visibility: Do not reveal any part of this notification on a secure lockscreen.
861      *
862      * {@see #visibility}
863      */
864     public static final int VISIBILITY_SECRET = -1;
865 
866     /**
867      * @hide
868      */
869     @IntDef(prefix = "VISIBILITY_", value = {
870             VISIBILITY_PUBLIC,
871             VISIBILITY_PRIVATE,
872             VISIBILITY_SECRET,
873             NotificationManager.VISIBILITY_NO_OVERRIDE
874     })
875     public @interface NotificationVisibilityOverride{};
876 
877     /**
878      * Notification category: incoming call (voice or video) or similar synchronous communication request.
879      */
880     public static final String CATEGORY_CALL = "call";
881 
882     /**
883      * Notification category: map turn-by-turn navigation.
884      */
885     public static final String CATEGORY_NAVIGATION = "navigation";
886 
887     /**
888      * Notification category: incoming direct message (SMS, instant message, etc.).
889      */
890     public static final String CATEGORY_MESSAGE = "msg";
891 
892     /**
893      * Notification category: asynchronous bulk message (email).
894      */
895     public static final String CATEGORY_EMAIL = "email";
896 
897     /**
898      * Notification category: calendar event.
899      */
900     public static final String CATEGORY_EVENT = "event";
901 
902     /**
903      * Notification category: promotion or advertisement.
904      */
905     public static final String CATEGORY_PROMO = "promo";
906 
907     /**
908      * Notification category: alarm or timer.
909      */
910     public static final String CATEGORY_ALARM = "alarm";
911 
912     /**
913      * Notification category: progress of a long-running background operation.
914      */
915     public static final String CATEGORY_PROGRESS = "progress";
916 
917     /**
918      * Notification category: social network or sharing update.
919      */
920     public static final String CATEGORY_SOCIAL = "social";
921 
922     /**
923      * Notification category: error in background operation or authentication status.
924      */
925     public static final String CATEGORY_ERROR = "err";
926 
927     /**
928      * Notification category: media transport control for playback.
929      */
930     public static final String CATEGORY_TRANSPORT = "transport";
931 
932     /**
933      * Notification category: system or device status update.  Reserved for system use.
934      */
935     public static final String CATEGORY_SYSTEM = "sys";
936 
937     /**
938      * Notification category: indication of running background service.
939      */
940     public static final String CATEGORY_SERVICE = "service";
941 
942     /**
943      * Notification category: a specific, timely recommendation for a single thing.
944      * For example, a news app might want to recommend a news story it believes the user will
945      * want to read next.
946      */
947     public static final String CATEGORY_RECOMMENDATION = "recommendation";
948 
949     /**
950      * Notification category: ongoing information about device or contextual status.
951      */
952     public static final String CATEGORY_STATUS = "status";
953 
954     /**
955      * Notification category: user-scheduled reminder.
956      */
957     public static final String CATEGORY_REMINDER = "reminder";
958 
959     /**
960      * Notification category: extreme car emergencies.
961      * @hide
962      */
963     @SystemApi
964     public static final String CATEGORY_CAR_EMERGENCY = "car_emergency";
965 
966     /**
967      * Notification category: car warnings.
968      * @hide
969      */
970     @SystemApi
971     public static final String CATEGORY_CAR_WARNING = "car_warning";
972 
973     /**
974      * Notification category: general car system information.
975      * @hide
976      */
977     @SystemApi
978     public static final String CATEGORY_CAR_INFORMATION = "car_information";
979 
980     /**
981      * Notification category: tracking a user's workout.
982      */
983     public static final String CATEGORY_WORKOUT = "workout";
984 
985     /**
986      * Notification category: temporarily sharing location.
987      */
988     public static final String CATEGORY_LOCATION_SHARING = "location_sharing";
989 
990     /**
991      * Notification category: running stopwatch.
992      */
993     public static final String CATEGORY_STOPWATCH = "stopwatch";
994 
995     /**
996      * Notification category: missed call.
997      */
998     public static final String CATEGORY_MISSED_CALL = "missed_call";
999 
1000     /**
1001      * One of the predefined notification categories (see the <code>CATEGORY_*</code> constants)
1002      * that best describes this Notification.  May be used by the system for ranking and filtering.
1003      */
1004     public String category;
1005 
1006     @UnsupportedAppUsage
1007     private String mGroupKey;
1008 
1009     /**
1010      * Get the key used to group this notification into a cluster or stack
1011      * with other notifications on devices which support such rendering.
1012      */
getGroup()1013     public String getGroup() {
1014         return mGroupKey;
1015     }
1016 
1017     private String mSortKey;
1018 
1019     /**
1020      * Get a sort key that orders this notification among other notifications from the
1021      * same package. This can be useful if an external sort was already applied and an app
1022      * would like to preserve this. Notifications will be sorted lexicographically using this
1023      * value, although providing different priorities in addition to providing sort key may
1024      * cause this value to be ignored.
1025      *
1026      * <p>This sort key can also be used to order members of a notification group. See
1027      * {@link Builder#setGroup}.
1028      *
1029      * @see String#compareTo(String)
1030      */
getSortKey()1031     public String getSortKey() {
1032         return mSortKey;
1033     }
1034 
1035     /**
1036      * Additional semantic data to be carried around with this Notification.
1037      * <p>
1038      * The extras keys defined here are intended to capture the original inputs to {@link Builder}
1039      * APIs, and are intended to be used by
1040      * {@link android.service.notification.NotificationListenerService} implementations to extract
1041      * detailed information from notification objects.
1042      */
1043     public Bundle extras = new Bundle();
1044 
1045     /**
1046      * All pending intents in the notification as the system needs to be able to access them but
1047      * touching the extras bundle in the system process is not safe because the bundle may contain
1048      * custom parcelable objects.
1049      *
1050      * @hide
1051      */
1052     @UnsupportedAppUsage
1053     public ArraySet<PendingIntent> allPendingIntents;
1054 
1055     /**
1056      * Token identifying the notification that is applying doze/bgcheck allowlisting to the
1057      * pending intents inside of it, so only those will get the behavior.
1058      *
1059      * @hide
1060      */
1061     private IBinder mAllowlistToken;
1062 
1063     /**
1064      * Must be set by a process to start associating tokens with Notification objects
1065      * coming in to it.  This is set by NotificationManagerService.
1066      *
1067      * @hide
1068      */
1069     static public IBinder processAllowlistToken;
1070 
1071     /**
1072      * {@link #extras} key: this is the title of the notification,
1073      * as supplied to {@link Builder#setContentTitle(CharSequence)}.
1074      */
1075     public static final String EXTRA_TITLE = "android.title";
1076 
1077     /**
1078      * {@link #extras} key: this is the title of the notification when shown in expanded form,
1079      * e.g. as supplied to {@link BigTextStyle#setBigContentTitle(CharSequence)}.
1080      */
1081     public static final String EXTRA_TITLE_BIG = EXTRA_TITLE + ".big";
1082 
1083     /**
1084      * {@link #extras} key: this is the main text payload, as supplied to
1085      * {@link Builder#setContentText(CharSequence)}.
1086      */
1087     public static final String EXTRA_TEXT = "android.text";
1088 
1089     /**
1090      * {@link #extras} key: this is a third line of text, as supplied to
1091      * {@link Builder#setSubText(CharSequence)}.
1092      */
1093     public static final String EXTRA_SUB_TEXT = "android.subText";
1094 
1095     /**
1096      * {@link #extras} key: this is the remote input history, as supplied to
1097      * {@link Builder#setRemoteInputHistory(CharSequence[])}.
1098      *
1099      * Apps can fill this through {@link Builder#setRemoteInputHistory(CharSequence[])}
1100      * with the most recent inputs that have been sent through a {@link RemoteInput} of this
1101      * Notification and are expected to clear it once the it is no longer relevant (e.g. for chat
1102      * notifications once the other party has responded).
1103      *
1104      * The extra with this key is of type CharSequence[] and contains the most recent entry at
1105      * the 0 index, the second most recent at the 1 index, etc.
1106      *
1107      * @see Builder#setRemoteInputHistory(CharSequence[])
1108      */
1109     public static final String EXTRA_REMOTE_INPUT_HISTORY = "android.remoteInputHistory";
1110 
1111 
1112     /**
1113      * {@link #extras} key: this is a remote input history which can include media messages
1114      * in addition to text, as supplied to
1115      * {@link Builder#setRemoteInputHistory(RemoteInputHistoryItem[])} or
1116      * {@link Builder#setRemoteInputHistory(CharSequence[])}.
1117      *
1118      * SystemUI can populate this through
1119      * {@link Builder#setRemoteInputHistory(RemoteInputHistoryItem[])} with the most recent inputs
1120      * that have been sent through a {@link RemoteInput} of this Notification. These items can
1121      * represent either media content (specified by a URI and a MIME type) or a text message
1122      * (described by a CharSequence).
1123      *
1124      * To maintain compatibility, this can also be set by apps with
1125      * {@link Builder#setRemoteInputHistory(CharSequence[])}, which will create a
1126      * {@link RemoteInputHistoryItem} for each of the provided text-only messages.
1127      *
1128      * The extra with this key is of type {@link RemoteInputHistoryItem[]} and contains the most
1129      * recent entry at the 0 index, the second most recent at the 1 index, etc.
1130      *
1131      * @see Builder#setRemoteInputHistory(RemoteInputHistoryItem[])
1132      * @hide
1133      */
1134     public static final String EXTRA_REMOTE_INPUT_HISTORY_ITEMS = "android.remoteInputHistoryItems";
1135 
1136     /**
1137      * {@link #extras} key: boolean as supplied to
1138      * {@link Builder#setShowRemoteInputSpinner(boolean)}.
1139      *
1140      * If set to true, then the view displaying the remote input history from
1141      * {@link Builder#setRemoteInputHistory(CharSequence[])} will have a progress spinner.
1142      *
1143      * @see Builder#setShowRemoteInputSpinner(boolean)
1144      * @hide
1145      */
1146     public static final String EXTRA_SHOW_REMOTE_INPUT_SPINNER = "android.remoteInputSpinner";
1147 
1148     /**
1149      * {@link #extras} key: boolean as supplied to
1150      * {@link Builder#setHideSmartReplies(boolean)}.
1151      *
1152      * If set to true, then any smart reply buttons will be hidden.
1153      *
1154      * @see Builder#setHideSmartReplies(boolean)
1155      * @hide
1156      */
1157     public static final String EXTRA_HIDE_SMART_REPLIES = "android.hideSmartReplies";
1158 
1159     /**
1160      * {@link #extras} key: this is a small piece of additional text as supplied to
1161      * {@link Builder#setContentInfo(CharSequence)}.
1162      */
1163     public static final String EXTRA_INFO_TEXT = "android.infoText";
1164 
1165     /**
1166      * {@link #extras} key: this is a line of summary information intended to be shown
1167      * alongside expanded notifications, as supplied to (e.g.)
1168      * {@link BigTextStyle#setSummaryText(CharSequence)}.
1169      */
1170     public static final String EXTRA_SUMMARY_TEXT = "android.summaryText";
1171 
1172     /**
1173      * {@link #extras} key: this is the longer text shown in the big form of a
1174      * {@link BigTextStyle} notification, as supplied to
1175      * {@link BigTextStyle#bigText(CharSequence)}.
1176      */
1177     public static final String EXTRA_BIG_TEXT = "android.bigText";
1178 
1179     /**
1180      * {@link #extras} key: this is the resource ID of the notification's main small icon, as
1181      * supplied to {@link Builder#setSmallIcon(int)}.
1182      *
1183      * @deprecated Use {@link #getSmallIcon()}, which supports a wider variety of icon sources.
1184      */
1185     @Deprecated
1186     public static final String EXTRA_SMALL_ICON = "android.icon";
1187 
1188     /**
1189      * {@link #extras} key: this is a bitmap to be used instead of the small icon when showing the
1190      * notification payload, as
1191      * supplied to {@link Builder#setLargeIcon(android.graphics.Bitmap)}.
1192      *
1193      * @deprecated Use {@link #getLargeIcon()}, which supports a wider variety of icon sources.
1194      */
1195     @Deprecated
1196     public static final String EXTRA_LARGE_ICON = "android.largeIcon";
1197 
1198     /**
1199      * {@link #extras} key: this is a bitmap to be used instead of the one from
1200      * {@link Builder#setLargeIcon(android.graphics.Bitmap)} when the notification is
1201      * shown in its expanded form, as supplied to
1202      * {@link BigPictureStyle#bigLargeIcon(android.graphics.Bitmap)}.
1203      */
1204     public static final String EXTRA_LARGE_ICON_BIG = EXTRA_LARGE_ICON + ".big";
1205 
1206     /**
1207      * {@link #extras} key: this is the progress value supplied to
1208      * {@link Builder#setProgress(int, int, boolean)}.
1209      */
1210     public static final String EXTRA_PROGRESS = "android.progress";
1211 
1212     /**
1213      * {@link #extras} key: this is the maximum value supplied to
1214      * {@link Builder#setProgress(int, int, boolean)}.
1215      */
1216     public static final String EXTRA_PROGRESS_MAX = "android.progressMax";
1217 
1218     /**
1219      * {@link #extras} key: whether the progress bar is indeterminate, supplied to
1220      * {@link Builder#setProgress(int, int, boolean)}.
1221      */
1222     public static final String EXTRA_PROGRESS_INDETERMINATE = "android.progressIndeterminate";
1223 
1224     /**
1225      * {@link #extras} key: whether {@link #when} should be shown as a count-up timer (specifically
1226      * a {@link android.widget.Chronometer}) instead of a timestamp, as supplied to
1227      * {@link Builder#setUsesChronometer(boolean)}.
1228      */
1229     public static final String EXTRA_SHOW_CHRONOMETER = "android.showChronometer";
1230 
1231     /**
1232      * {@link #extras} key: whether the chronometer set on the notification should count down
1233      * instead of counting up. Is only relevant if key {@link #EXTRA_SHOW_CHRONOMETER} is present.
1234      * This extra is a boolean. The default is false.
1235      */
1236     public static final String EXTRA_CHRONOMETER_COUNT_DOWN = "android.chronometerCountDown";
1237 
1238     /**
1239      * {@link #extras} key: whether {@link #when} should be shown,
1240      * as supplied to {@link Builder#setShowWhen(boolean)}.
1241      */
1242     public static final String EXTRA_SHOW_WHEN = "android.showWhen";
1243 
1244     /**
1245      * {@link #extras} key: this is a bitmap to be shown in {@link BigPictureStyle} expanded
1246      * notifications, supplied to {@link BigPictureStyle#bigPicture(android.graphics.Bitmap)}.
1247      */
1248     public static final String EXTRA_PICTURE = "android.picture";
1249 
1250     /**
1251      * {@link #extras} key: this is an {@link Icon} of an image to be
1252      * shown in {@link BigPictureStyle} expanded notifications, supplied to
1253      * {@link BigPictureStyle#bigPicture(Icon)}.
1254      */
1255     public static final String EXTRA_PICTURE_ICON = "android.pictureIcon";
1256 
1257     /**
1258      * {@link #extras} key: this is a content description of the big picture supplied from
1259      * {@link BigPictureStyle#bigPicture(Bitmap)}, supplied to
1260      * {@link BigPictureStyle#setContentDescription(CharSequence)}.
1261      */
1262     public static final String EXTRA_PICTURE_CONTENT_DESCRIPTION =
1263             "android.pictureContentDescription";
1264 
1265     /**
1266      * {@link #extras} key: this is a boolean to indicate that the
1267      * {@link BigPictureStyle#bigPicture(Bitmap) big picture} is to be shown in the collapsed state
1268      * of a {@link BigPictureStyle} notification.  This will replace a
1269      * {@link Builder#setLargeIcon(Icon) large icon} in that state if one was provided.
1270      */
1271     public static final String EXTRA_SHOW_BIG_PICTURE_WHEN_COLLAPSED =
1272             "android.showBigPictureWhenCollapsed";
1273 
1274     /**
1275      * {@link #extras} key: An array of CharSequences to show in {@link InboxStyle} expanded
1276      * notifications, each of which was supplied to {@link InboxStyle#addLine(CharSequence)}.
1277      */
1278     public static final String EXTRA_TEXT_LINES = "android.textLines";
1279 
1280     /**
1281      * {@link #extras} key: A string representing the name of the specific
1282      * {@link android.app.Notification.Style} used to create this notification.
1283      */
1284     public static final String EXTRA_TEMPLATE = "android.template";
1285 
1286     /**
1287      * {@link #extras} key: A String array containing the people that this notification relates to,
1288      * each of which was supplied to {@link Builder#addPerson(String)}.
1289      *
1290      * @deprecated the actual objects are now in {@link #EXTRA_PEOPLE_LIST}
1291      */
1292     public static final String EXTRA_PEOPLE = "android.people";
1293 
1294     /**
1295      * {@link #extras} key: An arrayList of {@link Person} objects containing the people that
1296      * this notification relates to.
1297      */
1298     public static final String EXTRA_PEOPLE_LIST = "android.people.list";
1299 
1300     /**
1301      * Allow certain system-generated notifications to appear before the device is provisioned.
1302      * Only available to notifications coming from the android package.
1303      * @hide
1304      */
1305     @SystemApi
1306     @RequiresPermission(android.Manifest.permission.NOTIFICATION_DURING_SETUP)
1307     public static final String EXTRA_ALLOW_DURING_SETUP = "android.allowDuringSetup";
1308 
1309     /**
1310      * {@link #extras} key:
1311      * flat {@link String} representation of a {@link android.content.ContentUris content URI}
1312      * pointing to an image that can be displayed in the background when the notification is
1313      * selected. Used on television platforms. The URI must point to an image stream suitable for
1314      * passing into {@link android.graphics.BitmapFactory#decodeStream(java.io.InputStream)
1315      * BitmapFactory.decodeStream}; all other content types will be ignored.
1316      */
1317     public static final String EXTRA_BACKGROUND_IMAGE_URI = "android.backgroundImageUri";
1318 
1319     /**
1320      * {@link #extras} key: A
1321      * {@link android.media.session.MediaSession.Token} associated with a
1322      * {@link android.app.Notification.MediaStyle} notification.
1323      */
1324     public static final String EXTRA_MEDIA_SESSION = "android.mediaSession";
1325 
1326     /**
1327      * {@link #extras} key: the indices of actions to be shown in the compact view,
1328      * as supplied to (e.g.) {@link MediaStyle#setShowActionsInCompactView(int...)}.
1329      */
1330     public static final String EXTRA_COMPACT_ACTIONS = "android.compactActions";
1331 
1332     /**
1333      * {@link #extras} key: the username to be displayed for all messages sent by the user including
1334      * direct replies
1335      * {@link android.app.Notification.MessagingStyle} notification. This extra is a
1336      * {@link CharSequence}
1337      *
1338      * @deprecated use {@link #EXTRA_MESSAGING_PERSON}
1339      */
1340     public static final String EXTRA_SELF_DISPLAY_NAME = "android.selfDisplayName";
1341 
1342     /**
1343      * {@link #extras} key: the person to be displayed for all messages sent by the user including
1344      * direct replies
1345      * {@link android.app.Notification.MessagingStyle} notification. This extra is a
1346      * {@link Person}
1347      */
1348     public static final String EXTRA_MESSAGING_PERSON = "android.messagingUser";
1349 
1350     /**
1351      * {@link #extras} key: a {@link CharSequence} to be displayed as the title to a conversation
1352      * represented by a {@link android.app.Notification.MessagingStyle}
1353      */
1354     public static final String EXTRA_CONVERSATION_TITLE = "android.conversationTitle";
1355 
1356     /** @hide */
1357     public static final String EXTRA_CONVERSATION_ICON = "android.conversationIcon";
1358 
1359     /** @hide */
1360     public static final String EXTRA_CONVERSATION_UNREAD_MESSAGE_COUNT =
1361             "android.conversationUnreadMessageCount";
1362 
1363     /**
1364      * {@link #extras} key: an array of {@link android.app.Notification.MessagingStyle.Message}
1365      * bundles provided by a
1366      * {@link android.app.Notification.MessagingStyle} notification. This extra is a parcelable
1367      * array of bundles.
1368      */
1369     public static final String EXTRA_MESSAGES = "android.messages";
1370 
1371     /**
1372      * {@link #extras} key: an array of
1373      * {@link android.app.Notification.MessagingStyle#addHistoricMessage historic}
1374      * {@link android.app.Notification.MessagingStyle.Message} bundles provided by a
1375      * {@link android.app.Notification.MessagingStyle} notification. This extra is a parcelable
1376      * array of bundles.
1377      */
1378     public static final String EXTRA_HISTORIC_MESSAGES = "android.messages.historic";
1379 
1380     /**
1381      * {@link #extras} key: whether the {@link android.app.Notification.MessagingStyle} notification
1382      * represents a group conversation.
1383      */
1384     public static final String EXTRA_IS_GROUP_CONVERSATION = "android.isGroupConversation";
1385 
1386     /**
1387      * {@link #extras} key: the type of call represented by the
1388      * {@link android.app.Notification.CallStyle} notification. This extra is an int.
1389      * @hide
1390      */
1391     public static final String EXTRA_CALL_TYPE = "android.callType";
1392 
1393     /**
1394      * {@link #extras} key: whether the  {@link android.app.Notification.CallStyle} notification
1395      * is for a call that will activate video when answered. This extra is a boolean.
1396      */
1397     public static final String EXTRA_CALL_IS_VIDEO = "android.callIsVideo";
1398 
1399     /**
1400      * {@link #extras} key: the person to be displayed as calling for the
1401      * {@link android.app.Notification.CallStyle} notification. This extra is a {@link Person}.
1402      */
1403     public static final String EXTRA_CALL_PERSON = "android.callPerson";
1404 
1405     /**
1406      * {@link #extras} key: the icon to be displayed as a verification status of the caller on a
1407      * {@link android.app.Notification.CallStyle} notification. This extra is an {@link Icon}.
1408      */
1409     public static final String EXTRA_VERIFICATION_ICON = "android.verificationIcon";
1410 
1411     /**
1412      * {@link #extras} key: the text to be displayed as a verification status of the caller on a
1413      * {@link android.app.Notification.CallStyle} notification. This extra is a
1414      * {@link CharSequence}.
1415      */
1416     public static final String EXTRA_VERIFICATION_TEXT = "android.verificationText";
1417 
1418     /**
1419      * {@link #extras} key: the intent to be sent when the users answers a
1420      * {@link android.app.Notification.CallStyle} notification. This extra is a
1421      * {@link PendingIntent}.
1422      */
1423     public static final String EXTRA_ANSWER_INTENT = "android.answerIntent";
1424 
1425     /**
1426      * {@link #extras} key: the intent to be sent when the users declines a
1427      * {@link android.app.Notification.CallStyle} notification. This extra is a
1428      * {@link PendingIntent}.
1429      */
1430     public static final String EXTRA_DECLINE_INTENT = "android.declineIntent";
1431 
1432     /**
1433      * {@link #extras} key: the intent to be sent when the users hangs up a
1434      * {@link android.app.Notification.CallStyle} notification. This extra is a
1435      * {@link PendingIntent}.
1436      */
1437     public static final String EXTRA_HANG_UP_INTENT = "android.hangUpIntent";
1438 
1439     /**
1440      * {@link #extras} key: the color used as a hint for the Answer action button of a
1441      * {@link android.app.Notification.CallStyle} notification. This extra is a {@link ColorInt}.
1442      */
1443     public static final String EXTRA_ANSWER_COLOR = "android.answerColor";
1444 
1445     /**
1446      * {@link #extras} key: the color used as a hint for the Decline or Hang Up action button of a
1447      * {@link android.app.Notification.CallStyle} notification. This extra is a {@link ColorInt}.
1448      */
1449     public static final String EXTRA_DECLINE_COLOR = "android.declineColor";
1450 
1451     /**
1452      * {@link #extras} key: whether the notification should be colorized as
1453      * supplied to {@link Builder#setColorized(boolean)}.
1454      */
1455     public static final String EXTRA_COLORIZED = "android.colorized";
1456 
1457     /**
1458      * @hide
1459      */
1460     public static final String EXTRA_BUILDER_APPLICATION_INFO = "android.appInfo";
1461 
1462     /**
1463      * @hide
1464      */
1465     public static final String EXTRA_CONTAINS_CUSTOM_VIEW = "android.contains.customView";
1466 
1467     /**
1468      * @hide
1469      */
1470     public static final String EXTRA_REDUCED_IMAGES = "android.reduced.images";
1471 
1472     /**
1473      * {@link #extras} key: the audio contents of this notification.
1474      *
1475      * This is for use when rendering the notification on an audio-focused interface;
1476      * the audio contents are a complete sound sample that contains the contents/body of the
1477      * notification. This may be used in substitute of a Text-to-Speech reading of the
1478      * notification. For example if the notification represents a voice message this should point
1479      * to the audio of that message.
1480      *
1481      * The data stored under this key should be a String representation of a Uri that contains the
1482      * audio contents in one of the following formats: WAV, PCM 16-bit, AMR-WB.
1483      *
1484      * This extra is unnecessary if you are using {@code MessagingStyle} since each {@code Message}
1485      * has a field for holding data URI. That field can be used for audio.
1486      * See {@code Message#setData}.
1487      *
1488      * Example usage:
1489      * <pre>
1490      * {@code
1491      * Notification.Builder myBuilder = (build your Notification as normal);
1492      * myBuilder.getExtras().putString(EXTRA_AUDIO_CONTENTS_URI, myAudioUri.toString());
1493      * }
1494      * </pre>
1495      */
1496     public static final String EXTRA_AUDIO_CONTENTS_URI = "android.audioContents";
1497 
1498     /** @hide */
1499     @SystemApi
1500     @RequiresPermission(android.Manifest.permission.SUBSTITUTE_NOTIFICATION_APP_NAME)
1501     public static final String EXTRA_SUBSTITUTE_APP_NAME = "android.substName";
1502 
1503     /**
1504      * This is set on the notifications shown by system_server about apps running foreground
1505      * services. It indicates that the notification should be shown
1506      * only if any of the given apps do not already have a properly tagged
1507      * {@link #FLAG_FOREGROUND_SERVICE} notification currently visible to the user.
1508      * This is a string array of all package names of the apps.
1509      * @hide
1510      */
1511     public static final String EXTRA_FOREGROUND_APPS = "android.foregroundApps";
1512 
1513     @UnsupportedAppUsage
1514     private Icon mSmallIcon;
1515     @UnsupportedAppUsage
1516     private Icon mLargeIcon;
1517 
1518     @UnsupportedAppUsage
1519     private String mChannelId;
1520     private long mTimeout;
1521 
1522     private String mShortcutId;
1523     private LocusId mLocusId;
1524     private CharSequence mSettingsText;
1525 
1526     private BubbleMetadata mBubbleMetadata;
1527 
1528     /** @hide */
1529     @IntDef(prefix = { "GROUP_ALERT_" }, value = {
1530             GROUP_ALERT_ALL, GROUP_ALERT_CHILDREN, GROUP_ALERT_SUMMARY
1531     })
1532     @Retention(RetentionPolicy.SOURCE)
1533     public @interface GroupAlertBehavior {}
1534 
1535     /**
1536      * Constant for {@link Builder#setGroupAlertBehavior(int)}, meaning that all notifications in a
1537      * group with sound or vibration ought to make sound or vibrate (respectively), so this
1538      * notification will not be muted when it is in a group.
1539      */
1540     public static final int GROUP_ALERT_ALL = 0;
1541 
1542     /**
1543      * Constant for {@link Builder#setGroupAlertBehavior(int)}, meaning that all children
1544      * notification in a group should be silenced (no sound or vibration) even if they are posted
1545      * to a {@link NotificationChannel} that has sound and/or vibration. Use this constant to
1546      * mute this notification if this notification is a group child. This must be applied to all
1547      * children notifications you want to mute.
1548      *
1549      * <p> For example, you might want to use this constant if you post a number of children
1550      * notifications at once (say, after a periodic sync), and only need to notify the user
1551      * audibly once.
1552      */
1553     public static final int GROUP_ALERT_SUMMARY = 1;
1554 
1555     /**
1556      * Constant for {@link Builder#setGroupAlertBehavior(int)}, meaning that the summary
1557      * notification in a group should be silenced (no sound or vibration) even if they are
1558      * posted to a {@link NotificationChannel} that has sound and/or vibration. Use this constant
1559      * to mute this notification if this notification is a group summary.
1560      *
1561      * <p>For example, you might want to use this constant if only the children notifications
1562      * in your group have content and the summary is only used to visually group notifications
1563      * rather than to alert the user that new information is available.
1564      */
1565     public static final int GROUP_ALERT_CHILDREN = 2;
1566 
1567     private int mGroupAlertBehavior = GROUP_ALERT_ALL;
1568 
1569     /**
1570      * If this notification is being shown as a badge, always show as a number.
1571      */
1572     public static final int BADGE_ICON_NONE = 0;
1573 
1574     /**
1575      * If this notification is being shown as a badge, use the {@link #getSmallIcon()} to
1576      * represent this notification.
1577      */
1578     public static final int BADGE_ICON_SMALL = 1;
1579 
1580     /**
1581      * If this notification is being shown as a badge, use the {@link #getLargeIcon()} to
1582      * represent this notification.
1583      */
1584     public static final int BADGE_ICON_LARGE = 2;
1585     private int mBadgeIcon = BADGE_ICON_NONE;
1586 
1587     /**
1588      * Determines whether the platform can generate contextual actions for a notification.
1589      */
1590     private boolean mAllowSystemGeneratedContextualActions = true;
1591 
1592     /**
1593      * Structure to encapsulate a named action that can be shown as part of this notification.
1594      * It must include an icon, a label, and a {@link PendingIntent} to be fired when the action is
1595      * selected by the user.
1596      * <p>
1597      * Apps should use {@link Notification.Builder#addAction(int, CharSequence, PendingIntent)}
1598      * or {@link Notification.Builder#addAction(Notification.Action)}
1599      * to attach actions.
1600      * <p>
1601      * As of Android {@link android.os.Build.VERSION_CODES#S}, apps targeting API level {@link
1602      * android.os.Build.VERSION_CODES#S} or higher won't be able to start activities while
1603      * processing broadcast receivers or services in response to notification action clicks. To
1604      * launch an activity in those cases, provide a {@link PendingIntent} for the activity itself.
1605      */
1606     public static class Action implements Parcelable {
1607         /**
1608          * {@link #extras} key: Keys to a {@link Parcelable} {@link ArrayList} of
1609          * {@link RemoteInput}s.
1610          *
1611          * This is intended for {@link RemoteInput}s that only accept data, meaning
1612          * {@link RemoteInput#getAllowFreeFormInput} is false, {@link RemoteInput#getChoices}
1613          * is null or empty, and {@link RemoteInput#getAllowedDataTypes} is non-null and not
1614          * empty. These {@link RemoteInput}s will be ignored by devices that do not
1615          * support non-text-based {@link RemoteInput}s. See {@link Builder#build}.
1616          *
1617          * You can test if a RemoteInput matches these constraints using
1618          * {@link RemoteInput#isDataOnly}.
1619          */
1620         private static final String EXTRA_DATA_ONLY_INPUTS = "android.extra.DATA_ONLY_INPUTS";
1621 
1622         /**
1623          * {@link }: No semantic action defined.
1624          */
1625         public static final int SEMANTIC_ACTION_NONE = 0;
1626 
1627         /**
1628          * {@code SemanticAction}: Reply to a conversation, chat, group, or wherever replies
1629          * may be appropriate.
1630          */
1631         public static final int SEMANTIC_ACTION_REPLY = 1;
1632 
1633         /**
1634          * {@code SemanticAction}: Mark content as read.
1635          */
1636         public static final int SEMANTIC_ACTION_MARK_AS_READ = 2;
1637 
1638         /**
1639          * {@code SemanticAction}: Mark content as unread.
1640          */
1641         public static final int SEMANTIC_ACTION_MARK_AS_UNREAD = 3;
1642 
1643         /**
1644          * {@code SemanticAction}: Delete the content associated with the notification. This
1645          * could mean deleting an email, message, etc.
1646          */
1647         public static final int SEMANTIC_ACTION_DELETE = 4;
1648 
1649         /**
1650          * {@code SemanticAction}: Archive the content associated with the notification. This
1651          * could mean archiving an email, message, etc.
1652          */
1653         public static final int SEMANTIC_ACTION_ARCHIVE = 5;
1654 
1655         /**
1656          * {@code SemanticAction}: Mute the content associated with the notification. This could
1657          * mean silencing a conversation or currently playing media.
1658          */
1659         public static final int SEMANTIC_ACTION_MUTE = 6;
1660 
1661         /**
1662          * {@code SemanticAction}: Unmute the content associated with the notification. This could
1663          * mean un-silencing a conversation or currently playing media.
1664          */
1665         public static final int SEMANTIC_ACTION_UNMUTE = 7;
1666 
1667         /**
1668          * {@code SemanticAction}: Mark content with a thumbs up.
1669          */
1670         public static final int SEMANTIC_ACTION_THUMBS_UP = 8;
1671 
1672         /**
1673          * {@code SemanticAction}: Mark content with a thumbs down.
1674          */
1675         public static final int SEMANTIC_ACTION_THUMBS_DOWN = 9;
1676 
1677         /**
1678          * {@code SemanticAction}: Call a contact, group, etc.
1679          */
1680         public static final int SEMANTIC_ACTION_CALL = 10;
1681 
1682         /**
1683          * {@code SemanticAction}: Mark the conversation associated with the notification as a
1684          * priority. Note that this is only for use by the notification assistant services. The
1685          * type will be ignored for actions an app adds to its own notifications.
1686          * @hide
1687          */
1688         @SystemApi
1689         public static final int SEMANTIC_ACTION_MARK_CONVERSATION_AS_PRIORITY = 11;
1690 
1691         /**
1692          * {@code SemanticAction}: Mark content as a potential phishing attempt.
1693          * Note that this is only for use by the notification assistant services. The type will
1694          * be ignored for actions an app adds to its own notifications.
1695          * @hide
1696          */
1697         @SystemApi
1698         public static final int SEMANTIC_ACTION_CONVERSATION_IS_PHISHING = 12;
1699 
1700         private final Bundle mExtras;
1701         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
1702         private Icon mIcon;
1703         private final RemoteInput[] mRemoteInputs;
1704         private boolean mAllowGeneratedReplies = true;
1705         private final @SemanticAction int mSemanticAction;
1706         private final boolean mIsContextual;
1707         private boolean mAuthenticationRequired;
1708 
1709         /**
1710          * Small icon representing the action.
1711          *
1712          * @deprecated Use {@link Action#getIcon()} instead.
1713          */
1714         @Deprecated
1715         public int icon;
1716 
1717         /**
1718          * Title of the action.
1719          */
1720         public CharSequence title;
1721 
1722         /**
1723          * Intent to send when the user invokes this action. May be null, in which case the action
1724          * may be rendered in a disabled presentation by the system UI.
1725          */
1726         public PendingIntent actionIntent;
1727 
Action(Parcel in)1728         private Action(Parcel in) {
1729             if (in.readInt() != 0) {
1730                 mIcon = Icon.CREATOR.createFromParcel(in);
1731                 if (mIcon.getType() == Icon.TYPE_RESOURCE) {
1732                     icon = mIcon.getResId();
1733                 }
1734             }
1735             title = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
1736             if (in.readInt() == 1) {
1737                 actionIntent = PendingIntent.CREATOR.createFromParcel(in);
1738             }
1739             mExtras = Bundle.setDefusable(in.readBundle(), true);
1740             mRemoteInputs = in.createTypedArray(RemoteInput.CREATOR);
1741             mAllowGeneratedReplies = in.readInt() == 1;
1742             mSemanticAction = in.readInt();
1743             mIsContextual = in.readInt() == 1;
1744             mAuthenticationRequired = in.readInt() == 1;
1745         }
1746 
1747         /**
1748          * @deprecated Use {@link android.app.Notification.Action.Builder}.
1749          */
1750         @Deprecated
Action(int icon, CharSequence title, PendingIntent intent)1751         public Action(int icon, CharSequence title, PendingIntent intent) {
1752             this(Icon.createWithResource("", icon), title, intent, new Bundle(), null, true,
1753                     SEMANTIC_ACTION_NONE, false /* isContextual */, false /* requireAuth */);
1754         }
1755 
1756         /** 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, boolean requireAuth)1757         private Action(Icon icon, CharSequence title, PendingIntent intent, Bundle extras,
1758                 RemoteInput[] remoteInputs, boolean allowGeneratedReplies,
1759                 @SemanticAction int semanticAction, boolean isContextual,
1760                 boolean requireAuth) {
1761             this.mIcon = icon;
1762             if (icon != null && icon.getType() == Icon.TYPE_RESOURCE) {
1763                 this.icon = icon.getResId();
1764             }
1765             this.title = title;
1766             this.actionIntent = intent;
1767             this.mExtras = extras != null ? extras : new Bundle();
1768             this.mRemoteInputs = remoteInputs;
1769             this.mAllowGeneratedReplies = allowGeneratedReplies;
1770             this.mSemanticAction = semanticAction;
1771             this.mIsContextual = isContextual;
1772             this.mAuthenticationRequired = requireAuth;
1773         }
1774 
1775         /**
1776          * Return an icon representing the action.
1777          */
getIcon()1778         public Icon getIcon() {
1779             if (mIcon == null && icon != 0) {
1780                 // you snuck an icon in here without using the builder; let's try to keep it
1781                 mIcon = Icon.createWithResource("", icon);
1782             }
1783             return mIcon;
1784         }
1785 
1786         /**
1787          * Get additional metadata carried around with this Action.
1788          */
getExtras()1789         public Bundle getExtras() {
1790             return mExtras;
1791         }
1792 
1793         /**
1794          * Return whether the platform should automatically generate possible replies for this
1795          * {@link Action}
1796          */
getAllowGeneratedReplies()1797         public boolean getAllowGeneratedReplies() {
1798             return mAllowGeneratedReplies;
1799         }
1800 
1801         /**
1802          * Get the list of inputs to be collected from the user when this action is sent.
1803          * May return null if no remote inputs were added. Only returns inputs which accept
1804          * a text input. For inputs which only accept data use {@link #getDataOnlyRemoteInputs}.
1805          */
getRemoteInputs()1806         public RemoteInput[] getRemoteInputs() {
1807             return mRemoteInputs;
1808         }
1809 
1810         /**
1811          * Returns the {@code SemanticAction} associated with this {@link Action}. A
1812          * {@code SemanticAction} denotes what an {@link Action}'s {@link PendingIntent} will do
1813          * (eg. reply, mark as read, delete, etc).
1814          */
getSemanticAction()1815         public @SemanticAction int getSemanticAction() {
1816             return mSemanticAction;
1817         }
1818 
1819         /**
1820          * Returns whether this is a contextual Action, i.e. whether the action is dependent on the
1821          * notification message body. An example of a contextual action could be an action opening a
1822          * map application with an address shown in the notification.
1823          */
isContextual()1824         public boolean isContextual() {
1825             return mIsContextual;
1826         }
1827 
1828         /**
1829          * Get the list of inputs to be collected from the user that ONLY accept data when this
1830          * action is sent. These remote inputs are guaranteed to return true on a call to
1831          * {@link RemoteInput#isDataOnly}.
1832          *
1833          * Returns null if there are no data-only remote inputs.
1834          *
1835          * This method exists so that legacy RemoteInput collectors that pre-date the addition
1836          * of non-textual RemoteInputs do not access these remote inputs.
1837          */
getDataOnlyRemoteInputs()1838         public RemoteInput[] getDataOnlyRemoteInputs() {
1839             return getParcelableArrayFromBundle(mExtras, EXTRA_DATA_ONLY_INPUTS, RemoteInput.class);
1840         }
1841 
1842         /**
1843          * Returns whether the OS should only send this action's {@link PendingIntent} on an
1844          * unlocked device.
1845          *
1846          * If the device is locked when the action is invoked, the OS should show the keyguard and
1847          * require successful authentication before invoking the intent.
1848          */
isAuthenticationRequired()1849         public boolean isAuthenticationRequired() {
1850             return mAuthenticationRequired;
1851         }
1852 
1853         /**
1854          * Builder class for {@link Action} objects.
1855          */
1856         public static final class Builder {
1857             @Nullable private final Icon mIcon;
1858             @Nullable private final CharSequence mTitle;
1859             @Nullable private final PendingIntent mIntent;
1860             private boolean mAllowGeneratedReplies = true;
1861             @NonNull private final Bundle mExtras;
1862             @Nullable private ArrayList<RemoteInput> mRemoteInputs;
1863             private @SemanticAction int mSemanticAction;
1864             private boolean mIsContextual;
1865             private boolean mAuthenticationRequired;
1866 
1867             /**
1868              * Construct a new builder for {@link Action} object.
1869              * @param icon icon to show for this action
1870              * @param title the title of the action
1871              * @param intent the {@link PendingIntent} to fire when users trigger this action
1872              */
1873             @Deprecated
Builder(int icon, CharSequence title, PendingIntent intent)1874             public Builder(int icon, CharSequence title, PendingIntent intent) {
1875                 this(Icon.createWithResource("", icon), title, intent);
1876             }
1877 
1878             /**
1879              * Construct a new builder for {@link Action} object.
1880              *
1881              * <p>As of Android {@link android.os.Build.VERSION_CODES#S}, apps targeting API level
1882              * {@link android.os.Build.VERSION_CODES#S} or higher won't be able to start activities
1883              * while processing broadcast receivers or services in response to notification action
1884              * clicks. To launch an activity in those cases, provide a {@link PendingIntent} for the
1885              * activity itself.
1886              *
1887              * <p>How an Action is displayed, including whether the {@code icon}, {@code text}, or
1888              * both are displayed or required, depends on where and how the action is used, and the
1889              * {@link Style} applied to the Notification.
1890              *
1891              * <p>When the {@code title} is a {@link android.text.Spanned}, any colors set by a
1892              * {@link ForegroundColorSpan} or {@link TextAppearanceSpan} may be removed or displayed
1893              * with an altered in luminance to ensure proper contrast within the Notification.
1894              *
1895              * @param icon icon to show for this action
1896              * @param title the title of the action
1897              * @param intent the {@link PendingIntent} to fire when users trigger this action
1898              */
Builder(Icon icon, CharSequence title, PendingIntent intent)1899             public Builder(Icon icon, CharSequence title, PendingIntent intent) {
1900                 this(icon, title, intent, new Bundle(), null, true, SEMANTIC_ACTION_NONE, false);
1901             }
1902 
1903             /**
1904              * Construct a new builder for {@link Action} object using the fields from an
1905              * {@link Action}.
1906              * @param action the action to read fields from.
1907              */
Builder(Action action)1908             public Builder(Action action) {
1909                 this(action.getIcon(), action.title, action.actionIntent,
1910                         new Bundle(action.mExtras), action.getRemoteInputs(),
1911                         action.getAllowGeneratedReplies(), action.getSemanticAction(),
1912                         action.isAuthenticationRequired());
1913             }
1914 
Builder(@ullable Icon icon, @Nullable CharSequence title, @Nullable PendingIntent intent, @NonNull Bundle extras, @Nullable RemoteInput[] remoteInputs, boolean allowGeneratedReplies, @SemanticAction int semanticAction, boolean authRequired)1915             private Builder(@Nullable Icon icon, @Nullable CharSequence title,
1916                     @Nullable PendingIntent intent, @NonNull Bundle extras,
1917                     @Nullable RemoteInput[] remoteInputs, boolean allowGeneratedReplies,
1918                     @SemanticAction int semanticAction, boolean authRequired) {
1919                 mIcon = icon;
1920                 mTitle = title;
1921                 mIntent = intent;
1922                 mExtras = extras;
1923                 if (remoteInputs != null) {
1924                     mRemoteInputs = new ArrayList<>(remoteInputs.length);
1925                     Collections.addAll(mRemoteInputs, remoteInputs);
1926                 }
1927                 mAllowGeneratedReplies = allowGeneratedReplies;
1928                 mSemanticAction = semanticAction;
1929                 mAuthenticationRequired = authRequired;
1930             }
1931 
1932             /**
1933              * Merge additional metadata into this builder.
1934              *
1935              * <p>Values within the Bundle will replace existing extras values in this Builder.
1936              *
1937              * @see Notification.Action#extras
1938              */
1939             @NonNull
addExtras(Bundle extras)1940             public Builder addExtras(Bundle extras) {
1941                 if (extras != null) {
1942                     mExtras.putAll(extras);
1943                 }
1944                 return this;
1945             }
1946 
1947             /**
1948              * Get the metadata Bundle used by this Builder.
1949              *
1950              * <p>The returned Bundle is shared with this Builder.
1951              */
1952             @NonNull
getExtras()1953             public Bundle getExtras() {
1954                 return mExtras;
1955             }
1956 
1957             /**
1958              * Add an input to be collected from the user when this action is sent.
1959              * Response values can be retrieved from the fired intent by using the
1960              * {@link RemoteInput#getResultsFromIntent} function.
1961              * @param remoteInput a {@link RemoteInput} to add to the action
1962              * @return this object for method chaining
1963              */
1964             @NonNull
addRemoteInput(RemoteInput remoteInput)1965             public Builder addRemoteInput(RemoteInput remoteInput) {
1966                 if (mRemoteInputs == null) {
1967                     mRemoteInputs = new ArrayList<RemoteInput>();
1968                 }
1969                 mRemoteInputs.add(remoteInput);
1970                 return this;
1971             }
1972 
1973             /**
1974              * Set whether the platform should automatically generate possible replies to add to
1975              * {@link RemoteInput#getChoices()}. If the {@link Action} doesn't have a
1976              * {@link RemoteInput}, this has no effect.
1977              * @param allowGeneratedReplies {@code true} to allow generated replies, {@code false}
1978              * otherwise
1979              * @return this object for method chaining
1980              * The default value is {@code true}
1981              */
1982             @NonNull
setAllowGeneratedReplies(boolean allowGeneratedReplies)1983             public Builder setAllowGeneratedReplies(boolean allowGeneratedReplies) {
1984                 mAllowGeneratedReplies = allowGeneratedReplies;
1985                 return this;
1986             }
1987 
1988             /**
1989              * Sets the {@code SemanticAction} for this {@link Action}. A
1990              * {@code SemanticAction} denotes what an {@link Action}'s
1991              * {@link PendingIntent} will do (eg. reply, mark as read, delete, etc).
1992              * @param semanticAction a SemanticAction defined within {@link Action} with
1993              * {@code SEMANTIC_ACTION_} prefixes
1994              * @return this object for method chaining
1995              */
1996             @NonNull
setSemanticAction(@emanticAction int semanticAction)1997             public Builder setSemanticAction(@SemanticAction int semanticAction) {
1998                 mSemanticAction = semanticAction;
1999                 return this;
2000             }
2001 
2002             /**
2003              * Sets whether this {@link Action} is a contextual action, i.e. whether the action is
2004              * dependent on the notification message body. An example of a contextual action could
2005              * be an action opening a map application with an address shown in the notification.
2006              */
2007             @NonNull
setContextual(boolean isContextual)2008             public Builder setContextual(boolean isContextual) {
2009                 mIsContextual = isContextual;
2010                 return this;
2011             }
2012 
2013             /**
2014              * Apply an extender to this action builder. Extenders may be used to add
2015              * metadata or change options on this builder.
2016              */
2017             @NonNull
extend(Extender extender)2018             public Builder extend(Extender extender) {
2019                 extender.extend(this);
2020                 return this;
2021             }
2022 
2023             /**
2024              * Sets whether the OS should only send this action's {@link PendingIntent} on an
2025              * unlocked device.
2026              *
2027              * If this is true and the device is locked when the action is invoked, the OS will
2028              * show the keyguard and require successful authentication before invoking the intent.
2029              * If this is false and the device is locked, the OS will decide whether authentication
2030              * should be required.
2031              */
2032             @NonNull
setAuthenticationRequired(boolean authenticationRequired)2033             public Builder setAuthenticationRequired(boolean authenticationRequired) {
2034                 mAuthenticationRequired = authenticationRequired;
2035                 return this;
2036             }
2037 
2038             /**
2039              * Throws an NPE if we are building a contextual action missing one of the fields
2040              * necessary to display the action.
2041              */
checkContextualActionNullFields()2042             private void checkContextualActionNullFields() {
2043                 if (!mIsContextual) return;
2044 
2045                 if (mIcon == null) {
2046                     throw new NullPointerException("Contextual Actions must contain a valid icon");
2047                 }
2048 
2049                 if (mIntent == null) {
2050                     throw new NullPointerException(
2051                             "Contextual Actions must contain a valid PendingIntent");
2052                 }
2053             }
2054 
2055             /**
2056              * Combine all of the options that have been set and return a new {@link Action}
2057              * object.
2058              * @return the built action
2059              */
2060             @NonNull
build()2061             public Action build() {
2062                 checkContextualActionNullFields();
2063 
2064                 ArrayList<RemoteInput> dataOnlyInputs = new ArrayList<>();
2065                 RemoteInput[] previousDataInputs = getParcelableArrayFromBundle(
2066                         mExtras, EXTRA_DATA_ONLY_INPUTS, RemoteInput.class);
2067                 if (previousDataInputs != null) {
2068                     for (RemoteInput input : previousDataInputs) {
2069                         dataOnlyInputs.add(input);
2070                     }
2071                 }
2072                 List<RemoteInput> textInputs = new ArrayList<>();
2073                 if (mRemoteInputs != null) {
2074                     for (RemoteInput input : mRemoteInputs) {
2075                         if (input.isDataOnly()) {
2076                             dataOnlyInputs.add(input);
2077                         } else {
2078                             textInputs.add(input);
2079                         }
2080                     }
2081                 }
2082                 if (!dataOnlyInputs.isEmpty()) {
2083                     RemoteInput[] dataInputsArr =
2084                             dataOnlyInputs.toArray(new RemoteInput[dataOnlyInputs.size()]);
2085                     mExtras.putParcelableArray(EXTRA_DATA_ONLY_INPUTS, dataInputsArr);
2086                 }
2087                 RemoteInput[] textInputsArr = textInputs.isEmpty()
2088                         ? null : textInputs.toArray(new RemoteInput[textInputs.size()]);
2089                 return new Action(mIcon, mTitle, mIntent, mExtras, textInputsArr,
2090                         mAllowGeneratedReplies, mSemanticAction, mIsContextual,
2091                         mAuthenticationRequired);
2092             }
2093         }
2094 
2095         @Override
clone()2096         public Action clone() {
2097             return new Action(
2098                     getIcon(),
2099                     title,
2100                     actionIntent, // safe to alias
2101                     mExtras == null ? new Bundle() : new Bundle(mExtras),
2102                     getRemoteInputs(),
2103                     getAllowGeneratedReplies(),
2104                     getSemanticAction(),
2105                     isContextual(),
2106                     isAuthenticationRequired());
2107         }
2108 
2109         @Override
describeContents()2110         public int describeContents() {
2111             return 0;
2112         }
2113 
2114         @Override
writeToParcel(Parcel out, int flags)2115         public void writeToParcel(Parcel out, int flags) {
2116             final Icon ic = getIcon();
2117             if (ic != null) {
2118                 out.writeInt(1);
2119                 ic.writeToParcel(out, 0);
2120             } else {
2121                 out.writeInt(0);
2122             }
2123             TextUtils.writeToParcel(title, out, flags);
2124             if (actionIntent != null) {
2125                 out.writeInt(1);
2126                 actionIntent.writeToParcel(out, flags);
2127             } else {
2128                 out.writeInt(0);
2129             }
2130             out.writeBundle(mExtras);
2131             out.writeTypedArray(mRemoteInputs, flags);
2132             out.writeInt(mAllowGeneratedReplies ? 1 : 0);
2133             out.writeInt(mSemanticAction);
2134             out.writeInt(mIsContextual ? 1 : 0);
2135             out.writeInt(mAuthenticationRequired ? 1 : 0);
2136         }
2137 
2138         public static final @android.annotation.NonNull Parcelable.Creator<Action> CREATOR =
2139                 new Parcelable.Creator<Action>() {
2140             public Action createFromParcel(Parcel in) {
2141                 return new Action(in);
2142             }
2143             public Action[] newArray(int size) {
2144                 return new Action[size];
2145             }
2146         };
2147 
2148         /**
2149          * Extender interface for use with {@link Builder#extend}. Extenders may be used to add
2150          * metadata or change options on an action builder.
2151          */
2152         public interface Extender {
2153             /**
2154              * Apply this extender to a notification action builder.
2155              * @param builder the builder to be modified.
2156              * @return the build object for chaining.
2157              */
extend(Builder builder)2158             public Builder extend(Builder builder);
2159         }
2160 
2161         /**
2162          * Wearable extender for notification actions. To add extensions to an action,
2163          * create a new {@link android.app.Notification.Action.WearableExtender} object using
2164          * the {@code WearableExtender()} constructor and apply it to a
2165          * {@link android.app.Notification.Action.Builder} using
2166          * {@link android.app.Notification.Action.Builder#extend}.
2167          *
2168          * <pre class="prettyprint">
2169          * Notification.Action action = new Notification.Action.Builder(
2170          *         R.drawable.archive_all, "Archive all", actionIntent)
2171          *         .extend(new Notification.Action.WearableExtender()
2172          *                 .setAvailableOffline(false))
2173          *         .build();</pre>
2174          */
2175         public static final class WearableExtender implements Extender {
2176             /** Notification action extra which contains wearable extensions */
2177             private static final String EXTRA_WEARABLE_EXTENSIONS = "android.wearable.EXTENSIONS";
2178 
2179             // Keys within EXTRA_WEARABLE_EXTENSIONS for wearable options.
2180             private static final String KEY_FLAGS = "flags";
2181             private static final String KEY_IN_PROGRESS_LABEL = "inProgressLabel";
2182             private static final String KEY_CONFIRM_LABEL = "confirmLabel";
2183             private static final String KEY_CANCEL_LABEL = "cancelLabel";
2184 
2185             // Flags bitwise-ored to mFlags
2186             private static final int FLAG_AVAILABLE_OFFLINE = 0x1;
2187             private static final int FLAG_HINT_LAUNCHES_ACTIVITY = 1 << 1;
2188             private static final int FLAG_HINT_DISPLAY_INLINE = 1 << 2;
2189 
2190             // Default value for flags integer
2191             private static final int DEFAULT_FLAGS = FLAG_AVAILABLE_OFFLINE;
2192 
2193             private int mFlags = DEFAULT_FLAGS;
2194 
2195             private CharSequence mInProgressLabel;
2196             private CharSequence mConfirmLabel;
2197             private CharSequence mCancelLabel;
2198 
2199             /**
2200              * Create a {@link android.app.Notification.Action.WearableExtender} with default
2201              * options.
2202              */
WearableExtender()2203             public WearableExtender() {
2204             }
2205 
2206             /**
2207              * Create a {@link android.app.Notification.Action.WearableExtender} by reading
2208              * wearable options present in an existing notification action.
2209              * @param action the notification action to inspect.
2210              */
WearableExtender(Action action)2211             public WearableExtender(Action action) {
2212                 Bundle wearableBundle = action.getExtras().getBundle(EXTRA_WEARABLE_EXTENSIONS);
2213                 if (wearableBundle != null) {
2214                     mFlags = wearableBundle.getInt(KEY_FLAGS, DEFAULT_FLAGS);
2215                     mInProgressLabel = wearableBundle.getCharSequence(KEY_IN_PROGRESS_LABEL);
2216                     mConfirmLabel = wearableBundle.getCharSequence(KEY_CONFIRM_LABEL);
2217                     mCancelLabel = wearableBundle.getCharSequence(KEY_CANCEL_LABEL);
2218                 }
2219             }
2220 
2221             /**
2222              * Apply wearable extensions to a notification action that is being built. This is
2223              * typically called by the {@link android.app.Notification.Action.Builder#extend}
2224              * method of {@link android.app.Notification.Action.Builder}.
2225              */
2226             @Override
extend(Action.Builder builder)2227             public Action.Builder extend(Action.Builder builder) {
2228                 Bundle wearableBundle = new Bundle();
2229 
2230                 if (mFlags != DEFAULT_FLAGS) {
2231                     wearableBundle.putInt(KEY_FLAGS, mFlags);
2232                 }
2233                 if (mInProgressLabel != null) {
2234                     wearableBundle.putCharSequence(KEY_IN_PROGRESS_LABEL, mInProgressLabel);
2235                 }
2236                 if (mConfirmLabel != null) {
2237                     wearableBundle.putCharSequence(KEY_CONFIRM_LABEL, mConfirmLabel);
2238                 }
2239                 if (mCancelLabel != null) {
2240                     wearableBundle.putCharSequence(KEY_CANCEL_LABEL, mCancelLabel);
2241                 }
2242 
2243                 builder.getExtras().putBundle(EXTRA_WEARABLE_EXTENSIONS, wearableBundle);
2244                 return builder;
2245             }
2246 
2247             @Override
clone()2248             public WearableExtender clone() {
2249                 WearableExtender that = new WearableExtender();
2250                 that.mFlags = this.mFlags;
2251                 that.mInProgressLabel = this.mInProgressLabel;
2252                 that.mConfirmLabel = this.mConfirmLabel;
2253                 that.mCancelLabel = this.mCancelLabel;
2254                 return that;
2255             }
2256 
2257             /**
2258              * Set whether this action is available when the wearable device is not connected to
2259              * a companion device. The user can still trigger this action when the wearable device is
2260              * offline, but a visual hint will indicate that the action may not be available.
2261              * Defaults to true.
2262              */
setAvailableOffline(boolean availableOffline)2263             public WearableExtender setAvailableOffline(boolean availableOffline) {
2264                 setFlag(FLAG_AVAILABLE_OFFLINE, availableOffline);
2265                 return this;
2266             }
2267 
2268             /**
2269              * Get whether this action is available when the wearable device is not connected to
2270              * a companion device. The user can still trigger this action when the wearable device is
2271              * offline, but a visual hint will indicate that the action may not be available.
2272              * Defaults to true.
2273              */
isAvailableOffline()2274             public boolean isAvailableOffline() {
2275                 return (mFlags & FLAG_AVAILABLE_OFFLINE) != 0;
2276             }
2277 
setFlag(int mask, boolean value)2278             private void setFlag(int mask, boolean value) {
2279                 if (value) {
2280                     mFlags |= mask;
2281                 } else {
2282                     mFlags &= ~mask;
2283                 }
2284             }
2285 
2286             /**
2287              * Set a label to display while the wearable is preparing to automatically execute the
2288              * action. This is usually a 'ing' verb ending in ellipsis like "Sending..."
2289              *
2290              * @param label the label to display while the action is being prepared to execute
2291              * @return this object for method chaining
2292              */
2293             @Deprecated
setInProgressLabel(CharSequence label)2294             public WearableExtender setInProgressLabel(CharSequence label) {
2295                 mInProgressLabel = label;
2296                 return this;
2297             }
2298 
2299             /**
2300              * Get the label to display while the wearable is preparing to automatically execute
2301              * the action. This is usually a 'ing' verb ending in ellipsis like "Sending..."
2302              *
2303              * @return the label to display while the action is being prepared to execute
2304              */
2305             @Deprecated
getInProgressLabel()2306             public CharSequence getInProgressLabel() {
2307                 return mInProgressLabel;
2308             }
2309 
2310             /**
2311              * Set a label to display to confirm that the action should be executed.
2312              * This is usually an imperative verb like "Send".
2313              *
2314              * @param label the label to confirm the action should be executed
2315              * @return this object for method chaining
2316              */
2317             @Deprecated
setConfirmLabel(CharSequence label)2318             public WearableExtender setConfirmLabel(CharSequence label) {
2319                 mConfirmLabel = label;
2320                 return this;
2321             }
2322 
2323             /**
2324              * Get the label to display to confirm that the action should be executed.
2325              * This is usually an imperative verb like "Send".
2326              *
2327              * @return the label to confirm the action should be executed
2328              */
2329             @Deprecated
getConfirmLabel()2330             public CharSequence getConfirmLabel() {
2331                 return mConfirmLabel;
2332             }
2333 
2334             /**
2335              * Set a label to display to cancel the action.
2336              * This is usually an imperative verb, like "Cancel".
2337              *
2338              * @param label the label to display to cancel the action
2339              * @return this object for method chaining
2340              */
2341             @Deprecated
setCancelLabel(CharSequence label)2342             public WearableExtender setCancelLabel(CharSequence label) {
2343                 mCancelLabel = label;
2344                 return this;
2345             }
2346 
2347             /**
2348              * Get the label to display to cancel the action.
2349              * This is usually an imperative verb like "Cancel".
2350              *
2351              * @return the label to display to cancel the action
2352              */
2353             @Deprecated
getCancelLabel()2354             public CharSequence getCancelLabel() {
2355                 return mCancelLabel;
2356             }
2357 
2358             /**
2359              * Set a hint that this Action will launch an {@link Activity} directly, telling the
2360              * platform that it can generate the appropriate transitions.
2361              * @param hintLaunchesActivity {@code true} if the content intent will launch
2362              * an activity and transitions should be generated, false otherwise.
2363              * @return this object for method chaining
2364              */
setHintLaunchesActivity( boolean hintLaunchesActivity)2365             public WearableExtender setHintLaunchesActivity(
2366                     boolean hintLaunchesActivity) {
2367                 setFlag(FLAG_HINT_LAUNCHES_ACTIVITY, hintLaunchesActivity);
2368                 return this;
2369             }
2370 
2371             /**
2372              * Get a hint that this Action will launch an {@link Activity} directly, telling the
2373              * platform that it can generate the appropriate transitions
2374              * @return {@code true} if the content intent will launch an activity and transitions
2375              * should be generated, false otherwise. The default value is {@code false} if this was
2376              * never set.
2377              */
getHintLaunchesActivity()2378             public boolean getHintLaunchesActivity() {
2379                 return (mFlags & FLAG_HINT_LAUNCHES_ACTIVITY) != 0;
2380             }
2381 
2382             /**
2383              * Set a hint that this Action should be displayed inline.
2384              *
2385              * @param hintDisplayInline {@code true} if action should be displayed inline, false
2386              *        otherwise
2387              * @return this object for method chaining
2388              */
setHintDisplayActionInline( boolean hintDisplayInline)2389             public WearableExtender setHintDisplayActionInline(
2390                     boolean hintDisplayInline) {
2391                 setFlag(FLAG_HINT_DISPLAY_INLINE, hintDisplayInline);
2392                 return this;
2393             }
2394 
2395             /**
2396              * Get a hint that this Action should be displayed inline.
2397              *
2398              * @return {@code true} if the Action should be displayed inline, {@code false}
2399              *         otherwise. The default value is {@code false} if this was never set.
2400              */
getHintDisplayActionInline()2401             public boolean getHintDisplayActionInline() {
2402                 return (mFlags & FLAG_HINT_DISPLAY_INLINE) != 0;
2403             }
2404         }
2405 
2406         /**
2407          * Provides meaning to an {@link Action} that hints at what the associated
2408          * {@link PendingIntent} will do. For example, an {@link Action} with a
2409          * {@link PendingIntent} that replies to a text message notification may have the
2410          * {@link #SEMANTIC_ACTION_REPLY} {@code SemanticAction} set within it.
2411          *
2412          * @hide
2413          */
2414         @IntDef(prefix = { "SEMANTIC_ACTION_" }, value = {
2415                 SEMANTIC_ACTION_NONE,
2416                 SEMANTIC_ACTION_REPLY,
2417                 SEMANTIC_ACTION_MARK_AS_READ,
2418                 SEMANTIC_ACTION_MARK_AS_UNREAD,
2419                 SEMANTIC_ACTION_DELETE,
2420                 SEMANTIC_ACTION_ARCHIVE,
2421                 SEMANTIC_ACTION_MUTE,
2422                 SEMANTIC_ACTION_UNMUTE,
2423                 SEMANTIC_ACTION_THUMBS_UP,
2424                 SEMANTIC_ACTION_THUMBS_DOWN,
2425                 SEMANTIC_ACTION_CALL,
2426                 SEMANTIC_ACTION_MARK_CONVERSATION_AS_PRIORITY,
2427                 SEMANTIC_ACTION_CONVERSATION_IS_PHISHING
2428         })
2429         @Retention(RetentionPolicy.SOURCE)
2430         public @interface SemanticAction {}
2431     }
2432 
2433     /**
2434      * Array of all {@link Action} structures attached to this notification by
2435      * {@link Builder#addAction(int, CharSequence, PendingIntent)}. Mostly useful for instances of
2436      * {@link android.service.notification.NotificationListenerService} that provide an alternative
2437      * interface for invoking actions.
2438      */
2439     public Action[] actions;
2440 
2441     /**
2442      * Replacement version of this notification whose content will be shown
2443      * in an insecure context such as atop a secure keyguard. See {@link #visibility}
2444      * and {@link #VISIBILITY_PUBLIC}.
2445      */
2446     public Notification publicVersion;
2447 
2448     /**
2449      * Constructs a Notification object with default values.
2450      * You might want to consider using {@link Builder} instead.
2451      */
Notification()2452     public Notification()
2453     {
2454         this.when = System.currentTimeMillis();
2455         this.creationTime = System.currentTimeMillis();
2456         this.priority = PRIORITY_DEFAULT;
2457     }
2458 
2459     /**
2460      * @hide
2461      */
2462     @UnsupportedAppUsage
Notification(Context context, int icon, CharSequence tickerText, long when, CharSequence contentTitle, CharSequence contentText, Intent contentIntent)2463     public Notification(Context context, int icon, CharSequence tickerText, long when,
2464             CharSequence contentTitle, CharSequence contentText, Intent contentIntent)
2465     {
2466         new Builder(context)
2467                 .setWhen(when)
2468                 .setSmallIcon(icon)
2469                 .setTicker(tickerText)
2470                 .setContentTitle(contentTitle)
2471                 .setContentText(contentText)
2472                 .setContentIntent(PendingIntent.getActivity(
2473                         context, 0, contentIntent, PendingIntent.FLAG_MUTABLE))
2474                 .buildInto(this);
2475     }
2476 
2477     /**
2478      * Constructs a Notification object with the information needed to
2479      * have a status bar icon without the standard expanded view.
2480      *
2481      * @param icon          The resource id of the icon to put in the status bar.
2482      * @param tickerText    The text that flows by in the status bar when the notification first
2483      *                      activates.
2484      * @param when          The time to show in the time field.  In the System.currentTimeMillis
2485      *                      timebase.
2486      *
2487      * @deprecated Use {@link Builder} instead.
2488      */
2489     @Deprecated
Notification(int icon, CharSequence tickerText, long when)2490     public Notification(int icon, CharSequence tickerText, long when)
2491     {
2492         this.icon = icon;
2493         this.tickerText = tickerText;
2494         this.when = when;
2495         this.creationTime = System.currentTimeMillis();
2496     }
2497 
2498     /**
2499      * Unflatten the notification from a parcel.
2500      */
2501     @SuppressWarnings("unchecked")
Notification(Parcel parcel)2502     public Notification(Parcel parcel) {
2503         // IMPORTANT: Add unmarshaling code in readFromParcel as the pending
2504         // intents in extras are always written as the last entry.
2505         readFromParcelImpl(parcel);
2506         // Must be read last!
2507         allPendingIntents = (ArraySet<PendingIntent>) parcel.readArraySet(null);
2508     }
2509 
readFromParcelImpl(Parcel parcel)2510     private void readFromParcelImpl(Parcel parcel)
2511     {
2512         int version = parcel.readInt();
2513 
2514         mAllowlistToken = parcel.readStrongBinder();
2515         if (mAllowlistToken == null) {
2516             mAllowlistToken = processAllowlistToken;
2517         }
2518         // Propagate this token to all pending intents that are unmarshalled from the parcel.
2519         parcel.setClassCookie(PendingIntent.class, mAllowlistToken);
2520 
2521         when = parcel.readLong();
2522         creationTime = parcel.readLong();
2523         if (parcel.readInt() != 0) {
2524             mSmallIcon = Icon.CREATOR.createFromParcel(parcel);
2525             if (mSmallIcon.getType() == Icon.TYPE_RESOURCE) {
2526                 icon = mSmallIcon.getResId();
2527             }
2528         }
2529         number = parcel.readInt();
2530         if (parcel.readInt() != 0) {
2531             contentIntent = PendingIntent.CREATOR.createFromParcel(parcel);
2532         }
2533         if (parcel.readInt() != 0) {
2534             deleteIntent = PendingIntent.CREATOR.createFromParcel(parcel);
2535         }
2536         if (parcel.readInt() != 0) {
2537             tickerText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
2538         }
2539         if (parcel.readInt() != 0) {
2540             tickerView = RemoteViews.CREATOR.createFromParcel(parcel);
2541         }
2542         if (parcel.readInt() != 0) {
2543             contentView = RemoteViews.CREATOR.createFromParcel(parcel);
2544         }
2545         if (parcel.readInt() != 0) {
2546             mLargeIcon = Icon.CREATOR.createFromParcel(parcel);
2547         }
2548         defaults = parcel.readInt();
2549         flags = parcel.readInt();
2550         if (parcel.readInt() != 0) {
2551             sound = Uri.CREATOR.createFromParcel(parcel);
2552         }
2553 
2554         audioStreamType = parcel.readInt();
2555         if (parcel.readInt() != 0) {
2556             audioAttributes = AudioAttributes.CREATOR.createFromParcel(parcel);
2557         }
2558         vibrate = parcel.createLongArray();
2559         ledARGB = parcel.readInt();
2560         ledOnMS = parcel.readInt();
2561         ledOffMS = parcel.readInt();
2562         iconLevel = parcel.readInt();
2563 
2564         if (parcel.readInt() != 0) {
2565             fullScreenIntent = PendingIntent.CREATOR.createFromParcel(parcel);
2566         }
2567 
2568         priority = parcel.readInt();
2569 
2570         category = parcel.readString8();
2571 
2572         mGroupKey = parcel.readString8();
2573 
2574         mSortKey = parcel.readString8();
2575 
2576         extras = Bundle.setDefusable(parcel.readBundle(), true); // may be null
2577         fixDuplicateExtras();
2578 
2579         actions = parcel.createTypedArray(Action.CREATOR); // may be null
2580 
2581         if (parcel.readInt() != 0) {
2582             bigContentView = RemoteViews.CREATOR.createFromParcel(parcel);
2583         }
2584 
2585         if (parcel.readInt() != 0) {
2586             headsUpContentView = RemoteViews.CREATOR.createFromParcel(parcel);
2587         }
2588 
2589         visibility = parcel.readInt();
2590 
2591         if (parcel.readInt() != 0) {
2592             publicVersion = Notification.CREATOR.createFromParcel(parcel);
2593         }
2594 
2595         color = parcel.readInt();
2596 
2597         if (parcel.readInt() != 0) {
2598             mChannelId = parcel.readString8();
2599         }
2600         mTimeout = parcel.readLong();
2601 
2602         if (parcel.readInt() != 0) {
2603             mShortcutId = parcel.readString8();
2604         }
2605 
2606         if (parcel.readInt() != 0) {
2607             mLocusId = LocusId.CREATOR.createFromParcel(parcel);
2608         }
2609 
2610         mBadgeIcon = parcel.readInt();
2611 
2612         if (parcel.readInt() != 0) {
2613             mSettingsText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
2614         }
2615 
2616         mGroupAlertBehavior = parcel.readInt();
2617         if (parcel.readInt() != 0) {
2618             mBubbleMetadata = BubbleMetadata.CREATOR.createFromParcel(parcel);
2619         }
2620 
2621         mAllowSystemGeneratedContextualActions = parcel.readBoolean();
2622 
2623         mFgsDeferBehavior = parcel.readInt();
2624     }
2625 
2626     @Override
clone()2627     public Notification clone() {
2628         Notification that = new Notification();
2629         cloneInto(that, true);
2630         return that;
2631     }
2632 
2633     /**
2634      * Copy all (or if heavy is false, all except Bitmaps and RemoteViews) members
2635      * of this into that.
2636      * @hide
2637      */
cloneInto(Notification that, boolean heavy)2638     public void cloneInto(Notification that, boolean heavy) {
2639         that.mAllowlistToken = this.mAllowlistToken;
2640         that.when = this.when;
2641         that.creationTime = this.creationTime;
2642         that.mSmallIcon = this.mSmallIcon;
2643         that.number = this.number;
2644 
2645         // PendingIntents are global, so there's no reason (or way) to clone them.
2646         that.contentIntent = this.contentIntent;
2647         that.deleteIntent = this.deleteIntent;
2648         that.fullScreenIntent = this.fullScreenIntent;
2649 
2650         if (this.tickerText != null) {
2651             that.tickerText = this.tickerText.toString();
2652         }
2653         if (heavy && this.tickerView != null) {
2654             that.tickerView = this.tickerView.clone();
2655         }
2656         if (heavy && this.contentView != null) {
2657             that.contentView = this.contentView.clone();
2658         }
2659         if (heavy && this.mLargeIcon != null) {
2660             that.mLargeIcon = this.mLargeIcon;
2661         }
2662         that.iconLevel = this.iconLevel;
2663         that.sound = this.sound; // android.net.Uri is immutable
2664         that.audioStreamType = this.audioStreamType;
2665         if (this.audioAttributes != null) {
2666             that.audioAttributes = new AudioAttributes.Builder(this.audioAttributes).build();
2667         }
2668 
2669         final long[] vibrate = this.vibrate;
2670         if (vibrate != null) {
2671             final int N = vibrate.length;
2672             final long[] vib = that.vibrate = new long[N];
2673             System.arraycopy(vibrate, 0, vib, 0, N);
2674         }
2675 
2676         that.ledARGB = this.ledARGB;
2677         that.ledOnMS = this.ledOnMS;
2678         that.ledOffMS = this.ledOffMS;
2679         that.defaults = this.defaults;
2680 
2681         that.flags = this.flags;
2682 
2683         that.priority = this.priority;
2684 
2685         that.category = this.category;
2686 
2687         that.mGroupKey = this.mGroupKey;
2688 
2689         that.mSortKey = this.mSortKey;
2690 
2691         if (this.extras != null) {
2692             try {
2693                 that.extras = new Bundle(this.extras);
2694                 // will unparcel
2695                 that.extras.size();
2696             } catch (BadParcelableException e) {
2697                 Log.e(TAG, "could not unparcel extras from notification: " + this, e);
2698                 that.extras = null;
2699             }
2700         }
2701 
2702         if (!ArrayUtils.isEmpty(allPendingIntents)) {
2703             that.allPendingIntents = new ArraySet<>(allPendingIntents);
2704         }
2705 
2706         if (this.actions != null) {
2707             that.actions = new Action[this.actions.length];
2708             for(int i=0; i<this.actions.length; i++) {
2709                 if ( this.actions[i] != null) {
2710                     that.actions[i] = this.actions[i].clone();
2711                 }
2712             }
2713         }
2714 
2715         if (heavy && this.bigContentView != null) {
2716             that.bigContentView = this.bigContentView.clone();
2717         }
2718 
2719         if (heavy && this.headsUpContentView != null) {
2720             that.headsUpContentView = this.headsUpContentView.clone();
2721         }
2722 
2723         that.visibility = this.visibility;
2724 
2725         if (this.publicVersion != null) {
2726             that.publicVersion = new Notification();
2727             this.publicVersion.cloneInto(that.publicVersion, heavy);
2728         }
2729 
2730         that.color = this.color;
2731 
2732         that.mChannelId = this.mChannelId;
2733         that.mTimeout = this.mTimeout;
2734         that.mShortcutId = this.mShortcutId;
2735         that.mLocusId = this.mLocusId;
2736         that.mBadgeIcon = this.mBadgeIcon;
2737         that.mSettingsText = this.mSettingsText;
2738         that.mGroupAlertBehavior = this.mGroupAlertBehavior;
2739         that.mFgsDeferBehavior = this.mFgsDeferBehavior;
2740         that.mBubbleMetadata = this.mBubbleMetadata;
2741         that.mAllowSystemGeneratedContextualActions = this.mAllowSystemGeneratedContextualActions;
2742 
2743         if (!heavy) {
2744             that.lightenPayload(); // will clean out extras
2745         }
2746     }
2747 
visitIconUri(@onNull Consumer<Uri> visitor, @Nullable Icon icon)2748     private static void visitIconUri(@NonNull Consumer<Uri> visitor, @Nullable Icon icon) {
2749         if (icon == null) return;
2750         final int iconType = icon.getType();
2751         if (iconType == TYPE_URI || iconType == TYPE_URI_ADAPTIVE_BITMAP) {
2752             visitor.accept(icon.getUri());
2753         }
2754     }
2755 
2756     /**
2757      * Note all {@link Uri} that are referenced internally, with the expectation
2758      * that Uri permission grants will need to be issued to ensure the recipient
2759      * of this object is able to render its contents.
2760      *
2761      * @hide
2762      */
visitUris(@onNull Consumer<Uri> visitor)2763     public void visitUris(@NonNull Consumer<Uri> visitor) {
2764         visitor.accept(sound);
2765 
2766         if (tickerView != null) tickerView.visitUris(visitor);
2767         if (contentView != null) contentView.visitUris(visitor);
2768         if (bigContentView != null) bigContentView.visitUris(visitor);
2769         if (headsUpContentView != null) headsUpContentView.visitUris(visitor);
2770 
2771         visitIconUri(visitor, mSmallIcon);
2772         visitIconUri(visitor, mLargeIcon);
2773 
2774         if (actions != null) {
2775             for (Action action : actions) {
2776                 visitIconUri(visitor, action.getIcon());
2777             }
2778         }
2779 
2780         if (extras != null) {
2781             visitIconUri(visitor, extras.getParcelable(EXTRA_LARGE_ICON_BIG));
2782             visitIconUri(visitor, extras.getParcelable(EXTRA_PICTURE_ICON));
2783 
2784             // NOTE: The documentation of EXTRA_AUDIO_CONTENTS_URI explicitly says that it is a
2785             // String representation of a Uri, but the previous implementation (and unit test) of
2786             // this method has always treated it as a Uri object. Given the inconsistency,
2787             // supporting both going forward is the safest choice.
2788             Object audioContentsUri = extras.get(EXTRA_AUDIO_CONTENTS_URI);
2789             if (audioContentsUri instanceof Uri) {
2790                 visitor.accept((Uri) audioContentsUri);
2791             } else if (audioContentsUri instanceof String) {
2792                 visitor.accept(Uri.parse((String) audioContentsUri));
2793             }
2794 
2795             if (extras.containsKey(EXTRA_BACKGROUND_IMAGE_URI)) {
2796                 visitor.accept(Uri.parse(extras.getString(EXTRA_BACKGROUND_IMAGE_URI)));
2797             }
2798 
2799             ArrayList<Person> people = extras.getParcelableArrayList(EXTRA_PEOPLE_LIST);
2800             if (people != null && !people.isEmpty()) {
2801                 for (Person p : people) {
2802                     visitor.accept(p.getIconUri());
2803                 }
2804             }
2805 
2806             final Person person = extras.getParcelable(EXTRA_MESSAGING_PERSON);
2807             if (person != null) {
2808                 visitor.accept(person.getIconUri());
2809             }
2810         }
2811 
2812         if (isStyle(MessagingStyle.class) && extras != null) {
2813             final Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES);
2814             if (!ArrayUtils.isEmpty(messages)) {
2815                 for (MessagingStyle.Message message : MessagingStyle.Message
2816                         .getMessagesFromBundleArray(messages)) {
2817                     visitor.accept(message.getDataUri());
2818 
2819                     Person senderPerson = message.getSenderPerson();
2820                     if (senderPerson != null) {
2821                         visitor.accept(senderPerson.getIconUri());
2822                     }
2823                 }
2824             }
2825 
2826             final Parcelable[] historic = extras.getParcelableArray(EXTRA_HISTORIC_MESSAGES);
2827             if (!ArrayUtils.isEmpty(historic)) {
2828                 for (MessagingStyle.Message message : MessagingStyle.Message
2829                         .getMessagesFromBundleArray(historic)) {
2830                     visitor.accept(message.getDataUri());
2831 
2832                     Person senderPerson = message.getSenderPerson();
2833                     if (senderPerson != null) {
2834                         visitor.accept(senderPerson.getIconUri());
2835                     }
2836                 }
2837             }
2838         }
2839 
2840         if (mBubbleMetadata != null) {
2841             visitIconUri(visitor, mBubbleMetadata.getIcon());
2842         }
2843     }
2844 
2845     /**
2846      * Removes heavyweight parts of the Notification object for archival or for sending to
2847      * listeners when the full contents are not necessary.
2848      * @hide
2849      */
lightenPayload()2850     public final void lightenPayload() {
2851         tickerView = null;
2852         contentView = null;
2853         bigContentView = null;
2854         headsUpContentView = null;
2855         mLargeIcon = null;
2856         if (extras != null && !extras.isEmpty()) {
2857             final Set<String> keyset = extras.keySet();
2858             final int N = keyset.size();
2859             final String[] keys = keyset.toArray(new String[N]);
2860             for (int i=0; i<N; i++) {
2861                 final String key = keys[i];
2862                 if (TvExtender.EXTRA_TV_EXTENDER.equals(key)) {
2863                     continue;
2864                 }
2865                 final Object obj = extras.get(key);
2866                 if (obj != null &&
2867                     (  obj instanceof Parcelable
2868                     || obj instanceof Parcelable[]
2869                     || obj instanceof SparseArray
2870                     || obj instanceof ArrayList)) {
2871                     extras.remove(key);
2872                 }
2873             }
2874         }
2875     }
2876 
2877     /**
2878      * Make sure this CharSequence is safe to put into a bundle, which basically
2879      * means it had better not be some custom Parcelable implementation.
2880      * @hide
2881      */
safeCharSequence(CharSequence cs)2882     public static CharSequence safeCharSequence(CharSequence cs) {
2883         if (cs == null) return cs;
2884         if (cs.length() > MAX_CHARSEQUENCE_LENGTH) {
2885             cs = cs.subSequence(0, MAX_CHARSEQUENCE_LENGTH);
2886         }
2887         if (cs instanceof Parcelable) {
2888             Log.e(TAG, "warning: " + cs.getClass().getCanonicalName()
2889                     + " instance is a custom Parcelable and not allowed in Notification");
2890             return cs.toString();
2891         }
2892         return removeTextSizeSpans(cs);
2893     }
2894 
removeTextSizeSpans(CharSequence charSequence)2895     private static CharSequence removeTextSizeSpans(CharSequence charSequence) {
2896         if (charSequence instanceof Spanned) {
2897             Spanned ss = (Spanned) charSequence;
2898             Object[] spans = ss.getSpans(0, ss.length(), Object.class);
2899             SpannableStringBuilder builder = new SpannableStringBuilder(ss.toString());
2900             for (Object span : spans) {
2901                 Object resultSpan = span;
2902                 if (resultSpan instanceof CharacterStyle) {
2903                     resultSpan = ((CharacterStyle) span).getUnderlying();
2904                 }
2905                 if (resultSpan instanceof TextAppearanceSpan) {
2906                     TextAppearanceSpan originalSpan = (TextAppearanceSpan) resultSpan;
2907                     resultSpan = new TextAppearanceSpan(
2908                             originalSpan.getFamily(),
2909                             originalSpan.getTextStyle(),
2910                             -1,
2911                             originalSpan.getTextColor(),
2912                             originalSpan.getLinkTextColor());
2913                 } else if (resultSpan instanceof RelativeSizeSpan
2914                         || resultSpan instanceof AbsoluteSizeSpan) {
2915                     continue;
2916                 } else {
2917                     resultSpan = span;
2918                 }
2919                 builder.setSpan(resultSpan, ss.getSpanStart(span), ss.getSpanEnd(span),
2920                         ss.getSpanFlags(span));
2921             }
2922             return builder;
2923         }
2924         return charSequence;
2925     }
2926 
describeContents()2927     public int describeContents() {
2928         return 0;
2929     }
2930 
2931     /**
2932      * Flatten this notification into a parcel.
2933      */
writeToParcel(Parcel parcel, int flags)2934     public void writeToParcel(Parcel parcel, int flags) {
2935         // We need to mark all pending intents getting into the notification
2936         // system as being put there to later allow the notification ranker
2937         // to launch them and by doing so add the app to the battery saver white
2938         // list for a short period of time. The problem is that the system
2939         // cannot look into the extras as there may be parcelables there that
2940         // the platform does not know how to handle. To go around that we have
2941         // an explicit list of the pending intents in the extras bundle.
2942         final boolean collectPendingIntents = (allPendingIntents == null);
2943         if (collectPendingIntents) {
2944             PendingIntent.setOnMarshaledListener(
2945                     (PendingIntent intent, Parcel out, int outFlags) -> {
2946                 if (parcel == out) {
2947                     synchronized (this) {
2948                         if (allPendingIntents == null) {
2949                             allPendingIntents = new ArraySet<>();
2950                         }
2951                         allPendingIntents.add(intent);
2952                     }
2953                 }
2954             });
2955         }
2956         try {
2957             // IMPORTANT: Add marshaling code in writeToParcelImpl as we
2958             // want to intercept all pending events written to the parcel.
2959             writeToParcelImpl(parcel, flags);
2960             synchronized (this) {
2961                 // Must be written last!
2962                 parcel.writeArraySet(allPendingIntents);
2963             }
2964         } finally {
2965             if (collectPendingIntents) {
2966                 PendingIntent.setOnMarshaledListener(null);
2967             }
2968         }
2969     }
2970 
writeToParcelImpl(Parcel parcel, int flags)2971     private void writeToParcelImpl(Parcel parcel, int flags) {
2972         parcel.writeInt(1);
2973 
2974         parcel.writeStrongBinder(mAllowlistToken);
2975         parcel.writeLong(when);
2976         parcel.writeLong(creationTime);
2977         if (mSmallIcon == null && icon != 0) {
2978             // you snuck an icon in here without using the builder; let's try to keep it
2979             mSmallIcon = Icon.createWithResource("", icon);
2980         }
2981         if (mSmallIcon != null) {
2982             parcel.writeInt(1);
2983             mSmallIcon.writeToParcel(parcel, 0);
2984         } else {
2985             parcel.writeInt(0);
2986         }
2987         parcel.writeInt(number);
2988         if (contentIntent != null) {
2989             parcel.writeInt(1);
2990             contentIntent.writeToParcel(parcel, 0);
2991         } else {
2992             parcel.writeInt(0);
2993         }
2994         if (deleteIntent != null) {
2995             parcel.writeInt(1);
2996             deleteIntent.writeToParcel(parcel, 0);
2997         } else {
2998             parcel.writeInt(0);
2999         }
3000         if (tickerText != null) {
3001             parcel.writeInt(1);
3002             TextUtils.writeToParcel(tickerText, parcel, flags);
3003         } else {
3004             parcel.writeInt(0);
3005         }
3006         if (tickerView != null) {
3007             parcel.writeInt(1);
3008             tickerView.writeToParcel(parcel, 0);
3009         } else {
3010             parcel.writeInt(0);
3011         }
3012         if (contentView != null) {
3013             parcel.writeInt(1);
3014             contentView.writeToParcel(parcel, 0);
3015         } else {
3016             parcel.writeInt(0);
3017         }
3018         if (mLargeIcon == null && largeIcon != null) {
3019             // you snuck an icon in here without using the builder; let's try to keep it
3020             mLargeIcon = Icon.createWithBitmap(largeIcon);
3021         }
3022         if (mLargeIcon != null) {
3023             parcel.writeInt(1);
3024             mLargeIcon.writeToParcel(parcel, 0);
3025         } else {
3026             parcel.writeInt(0);
3027         }
3028 
3029         parcel.writeInt(defaults);
3030         parcel.writeInt(this.flags);
3031 
3032         if (sound != null) {
3033             parcel.writeInt(1);
3034             sound.writeToParcel(parcel, 0);
3035         } else {
3036             parcel.writeInt(0);
3037         }
3038         parcel.writeInt(audioStreamType);
3039 
3040         if (audioAttributes != null) {
3041             parcel.writeInt(1);
3042             audioAttributes.writeToParcel(parcel, 0);
3043         } else {
3044             parcel.writeInt(0);
3045         }
3046 
3047         parcel.writeLongArray(vibrate);
3048         parcel.writeInt(ledARGB);
3049         parcel.writeInt(ledOnMS);
3050         parcel.writeInt(ledOffMS);
3051         parcel.writeInt(iconLevel);
3052 
3053         if (fullScreenIntent != null) {
3054             parcel.writeInt(1);
3055             fullScreenIntent.writeToParcel(parcel, 0);
3056         } else {
3057             parcel.writeInt(0);
3058         }
3059 
3060         parcel.writeInt(priority);
3061 
3062         parcel.writeString8(category);
3063 
3064         parcel.writeString8(mGroupKey);
3065 
3066         parcel.writeString8(mSortKey);
3067 
3068         parcel.writeBundle(extras); // null ok
3069 
3070         parcel.writeTypedArray(actions, 0); // null ok
3071 
3072         if (bigContentView != null) {
3073             parcel.writeInt(1);
3074             bigContentView.writeToParcel(parcel, 0);
3075         } else {
3076             parcel.writeInt(0);
3077         }
3078 
3079         if (headsUpContentView != null) {
3080             parcel.writeInt(1);
3081             headsUpContentView.writeToParcel(parcel, 0);
3082         } else {
3083             parcel.writeInt(0);
3084         }
3085 
3086         parcel.writeInt(visibility);
3087 
3088         if (publicVersion != null) {
3089             parcel.writeInt(1);
3090             publicVersion.writeToParcel(parcel, 0);
3091         } else {
3092             parcel.writeInt(0);
3093         }
3094 
3095         parcel.writeInt(color);
3096 
3097         if (mChannelId != null) {
3098             parcel.writeInt(1);
3099             parcel.writeString8(mChannelId);
3100         } else {
3101             parcel.writeInt(0);
3102         }
3103         parcel.writeLong(mTimeout);
3104 
3105         if (mShortcutId != null) {
3106             parcel.writeInt(1);
3107             parcel.writeString8(mShortcutId);
3108         } else {
3109             parcel.writeInt(0);
3110         }
3111 
3112         if (mLocusId != null) {
3113             parcel.writeInt(1);
3114             mLocusId.writeToParcel(parcel, 0);
3115         } else {
3116             parcel.writeInt(0);
3117         }
3118 
3119         parcel.writeInt(mBadgeIcon);
3120 
3121         if (mSettingsText != null) {
3122             parcel.writeInt(1);
3123             TextUtils.writeToParcel(mSettingsText, parcel, flags);
3124         } else {
3125             parcel.writeInt(0);
3126         }
3127 
3128         parcel.writeInt(mGroupAlertBehavior);
3129 
3130         if (mBubbleMetadata != null) {
3131             parcel.writeInt(1);
3132             mBubbleMetadata.writeToParcel(parcel, 0);
3133         } else {
3134             parcel.writeInt(0);
3135         }
3136 
3137         parcel.writeBoolean(mAllowSystemGeneratedContextualActions);
3138 
3139         parcel.writeInt(mFgsDeferBehavior);
3140 
3141         // mUsesStandardHeader is not written because it should be recomputed in listeners
3142     }
3143 
3144     /**
3145      * Parcelable.Creator that instantiates Notification objects
3146      */
3147     public static final @android.annotation.NonNull Parcelable.Creator<Notification> CREATOR
3148             = new Parcelable.Creator<Notification>()
3149     {
3150         public Notification createFromParcel(Parcel parcel)
3151         {
3152             return new Notification(parcel);
3153         }
3154 
3155         public Notification[] newArray(int size)
3156         {
3157             return new Notification[size];
3158         }
3159     };
3160 
3161     /**
3162      * @hide
3163      */
areActionsVisiblyDifferent(Notification first, Notification second)3164     public static boolean areActionsVisiblyDifferent(Notification first, Notification second) {
3165         Notification.Action[] firstAs = first.actions;
3166         Notification.Action[] secondAs = second.actions;
3167         if (firstAs == null && secondAs != null || firstAs != null && secondAs == null) {
3168             return true;
3169         }
3170         if (firstAs != null && secondAs != null) {
3171             if (firstAs.length != secondAs.length) {
3172                 return true;
3173             }
3174             for (int i = 0; i < firstAs.length; i++) {
3175                 if (!Objects.equals(String.valueOf(firstAs[i].title),
3176                         String.valueOf(secondAs[i].title))) {
3177                     return true;
3178                 }
3179                 RemoteInput[] firstRs = firstAs[i].getRemoteInputs();
3180                 RemoteInput[] secondRs = secondAs[i].getRemoteInputs();
3181                 if (firstRs == null) {
3182                     firstRs = new RemoteInput[0];
3183                 }
3184                 if (secondRs == null) {
3185                     secondRs = new RemoteInput[0];
3186                 }
3187                 if (firstRs.length != secondRs.length) {
3188                     return true;
3189                 }
3190                 for (int j = 0; j < firstRs.length; j++) {
3191                     if (!Objects.equals(String.valueOf(firstRs[j].getLabel()),
3192                             String.valueOf(secondRs[j].getLabel()))) {
3193                         return true;
3194                     }
3195                 }
3196             }
3197         }
3198         return false;
3199     }
3200 
3201     /**
3202      * @hide
3203      */
areStyledNotificationsVisiblyDifferent(Builder first, Builder second)3204     public static boolean areStyledNotificationsVisiblyDifferent(Builder first, Builder second) {
3205         if (first.getStyle() == null) {
3206             return second.getStyle() != null;
3207         }
3208         if (second.getStyle() == null) {
3209             return true;
3210         }
3211         return first.getStyle().areNotificationsVisiblyDifferent(second.getStyle());
3212     }
3213 
3214     /**
3215      * @hide
3216      */
areRemoteViewsChanged(Builder first, Builder second)3217     public static boolean areRemoteViewsChanged(Builder first, Builder second) {
3218         if (!Objects.equals(first.usesStandardHeader(), second.usesStandardHeader())) {
3219             return true;
3220         }
3221 
3222         if (areRemoteViewsChanged(first.mN.contentView, second.mN.contentView)) {
3223             return true;
3224         }
3225         if (areRemoteViewsChanged(first.mN.bigContentView, second.mN.bigContentView)) {
3226             return true;
3227         }
3228         if (areRemoteViewsChanged(first.mN.headsUpContentView, second.mN.headsUpContentView)) {
3229             return true;
3230         }
3231 
3232         return false;
3233     }
3234 
areRemoteViewsChanged(RemoteViews first, RemoteViews second)3235     private static boolean areRemoteViewsChanged(RemoteViews first, RemoteViews second) {
3236         if (first == null && second == null) {
3237             return false;
3238         }
3239         if (first == null && second != null || first != null && second == null) {
3240             return true;
3241         }
3242 
3243         if (!Objects.equals(first.getLayoutId(), second.getLayoutId())) {
3244             return true;
3245         }
3246 
3247         if (!Objects.equals(first.getSequenceNumber(), second.getSequenceNumber())) {
3248             return true;
3249         }
3250 
3251         return false;
3252     }
3253 
3254     /**
3255      * Parcelling creates multiple copies of objects in {@code extras}. Fix them.
3256      * <p>
3257      * For backwards compatibility {@code extras} holds some references to "real" member data such
3258      * as {@link getLargeIcon()} which is mirrored by {@link #EXTRA_LARGE_ICON}. This is mostly
3259      * fine as long as the object stays in one process.
3260      * <p>
3261      * However, once the notification goes into a parcel each reference gets marshalled separately,
3262      * wasting memory. Especially with large images on Auto and TV, this is worth fixing.
3263      */
fixDuplicateExtras()3264     private void fixDuplicateExtras() {
3265         if (extras != null) {
3266             fixDuplicateExtra(mLargeIcon, EXTRA_LARGE_ICON);
3267         }
3268     }
3269 
3270     /**
3271      * If we find an extra that's exactly the same as one of the "real" fields but refers to a
3272      * separate object, replace it with the field's version to avoid holding duplicate copies.
3273      */
fixDuplicateExtra(@ullable Parcelable original, @NonNull String extraName)3274     private void fixDuplicateExtra(@Nullable Parcelable original, @NonNull String extraName) {
3275         if (original != null && extras.getParcelable(extraName) != null) {
3276             extras.putParcelable(extraName, original);
3277         }
3278     }
3279 
3280     /**
3281      * Sets the {@link #contentView} field to be a view with the standard "Latest Event"
3282      * layout.
3283      *
3284      * <p>Uses the {@link #icon} and {@link #when} fields to set the icon and time fields
3285      * in the view.</p>
3286      * @param context       The context for your application / activity.
3287      * @param contentTitle The title that goes in the expanded entry.
3288      * @param contentText  The text that goes in the expanded entry.
3289      * @param contentIntent The intent to launch when the user clicks the expanded notification.
3290      * If this is an activity, it must include the
3291      * {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK} flag, which requires
3292      * that you take care of task management as described in the
3293      * <a href="{@docRoot}guide/topics/fundamentals/tasks-and-back-stack.html">Tasks and Back
3294      * Stack</a> document.
3295      *
3296      * @deprecated Use {@link Builder} instead.
3297      * @removed
3298      */
3299     @Deprecated
setLatestEventInfo(Context context, CharSequence contentTitle, CharSequence contentText, PendingIntent contentIntent)3300     public void setLatestEventInfo(Context context,
3301             CharSequence contentTitle, CharSequence contentText, PendingIntent contentIntent) {
3302         if (context.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1){
3303             Log.e(TAG, "setLatestEventInfo() is deprecated and you should feel deprecated.",
3304                     new Throwable());
3305         }
3306 
3307         if (context.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N) {
3308             extras.putBoolean(EXTRA_SHOW_WHEN, true);
3309         }
3310 
3311         // ensure that any information already set directly is preserved
3312         final Notification.Builder builder = new Notification.Builder(context, this);
3313 
3314         // now apply the latestEventInfo fields
3315         if (contentTitle != null) {
3316             builder.setContentTitle(contentTitle);
3317         }
3318         if (contentText != null) {
3319             builder.setContentText(contentText);
3320         }
3321         builder.setContentIntent(contentIntent);
3322 
3323         builder.build(); // callers expect this notification to be ready to use
3324     }
3325 
3326     /**
3327      * Sets the token used for background operations for the pending intents associated with this
3328      * notification.
3329      *
3330      * This token is automatically set during deserialization for you, you usually won't need to
3331      * call this unless you want to change the existing token, if any.
3332      *
3333      * @hide
3334      */
setAllowlistToken(@ullable IBinder token)3335     public void setAllowlistToken(@Nullable IBinder token) {
3336         mAllowlistToken = token;
3337     }
3338 
3339     /**
3340      * @hide
3341      */
addFieldsFromContext(Context context, Notification notification)3342     public static void addFieldsFromContext(Context context, Notification notification) {
3343         addFieldsFromContext(context.getApplicationInfo(), notification);
3344     }
3345 
3346     /**
3347      * @hide
3348      */
addFieldsFromContext(ApplicationInfo ai, Notification notification)3349     public static void addFieldsFromContext(ApplicationInfo ai, Notification notification) {
3350         notification.extras.putParcelable(EXTRA_BUILDER_APPLICATION_INFO, ai);
3351     }
3352 
3353     /**
3354      * @hide
3355      */
dumpDebug(ProtoOutputStream proto, long fieldId)3356     public void dumpDebug(ProtoOutputStream proto, long fieldId) {
3357         long token = proto.start(fieldId);
3358         proto.write(NotificationProto.CHANNEL_ID, getChannelId());
3359         proto.write(NotificationProto.HAS_TICKER_TEXT, this.tickerText != null);
3360         proto.write(NotificationProto.FLAGS, this.flags);
3361         proto.write(NotificationProto.COLOR, this.color);
3362         proto.write(NotificationProto.CATEGORY, this.category);
3363         proto.write(NotificationProto.GROUP_KEY, this.mGroupKey);
3364         proto.write(NotificationProto.SORT_KEY, this.mSortKey);
3365         if (this.actions != null) {
3366             proto.write(NotificationProto.ACTION_LENGTH, this.actions.length);
3367         }
3368         if (this.visibility >= VISIBILITY_SECRET && this.visibility <= VISIBILITY_PUBLIC) {
3369             proto.write(NotificationProto.VISIBILITY, this.visibility);
3370         }
3371         if (publicVersion != null) {
3372             publicVersion.dumpDebug(proto, NotificationProto.PUBLIC_VERSION);
3373         }
3374         proto.end(token);
3375     }
3376 
3377     @Override
toString()3378     public String toString() {
3379         StringBuilder sb = new StringBuilder();
3380         sb.append("Notification(channel=");
3381         sb.append(getChannelId());
3382         sb.append(" shortcut=");
3383         sb.append(getShortcutId());
3384         sb.append(" contentView=");
3385         if (contentView != null) {
3386             sb.append(contentView.getPackage());
3387             sb.append("/0x");
3388             sb.append(Integer.toHexString(contentView.getLayoutId()));
3389         } else {
3390             sb.append("null");
3391         }
3392         sb.append(" vibrate=");
3393         if ((this.defaults & DEFAULT_VIBRATE) != 0) {
3394             sb.append("default");
3395         } else if (this.vibrate != null) {
3396             int N = this.vibrate.length-1;
3397             sb.append("[");
3398             for (int i=0; i<N; i++) {
3399                 sb.append(this.vibrate[i]);
3400                 sb.append(',');
3401             }
3402             if (N != -1) {
3403                 sb.append(this.vibrate[N]);
3404             }
3405             sb.append("]");
3406         } else {
3407             sb.append("null");
3408         }
3409         sb.append(" sound=");
3410         if ((this.defaults & DEFAULT_SOUND) != 0) {
3411             sb.append("default");
3412         } else if (this.sound != null) {
3413             sb.append(this.sound.toString());
3414         } else {
3415             sb.append("null");
3416         }
3417         if (this.tickerText != null) {
3418             sb.append(" tick");
3419         }
3420         sb.append(" defaults=0x");
3421         sb.append(Integer.toHexString(this.defaults));
3422         sb.append(" flags=0x");
3423         sb.append(Integer.toHexString(this.flags));
3424         sb.append(String.format(" color=0x%08x", this.color));
3425         if (this.category != null) {
3426             sb.append(" category=");
3427             sb.append(this.category);
3428         }
3429         if (this.mGroupKey != null) {
3430             sb.append(" groupKey=");
3431             sb.append(this.mGroupKey);
3432         }
3433         if (this.mSortKey != null) {
3434             sb.append(" sortKey=");
3435             sb.append(this.mSortKey);
3436         }
3437         if (actions != null) {
3438             sb.append(" actions=");
3439             sb.append(actions.length);
3440         }
3441         sb.append(" vis=");
3442         sb.append(visibilityToString(this.visibility));
3443         if (this.publicVersion != null) {
3444             sb.append(" publicVersion=");
3445             sb.append(publicVersion.toString());
3446         }
3447         if (this.mLocusId != null) {
3448             sb.append(" locusId=");
3449             sb.append(this.mLocusId); // LocusId.toString() is PII safe.
3450         }
3451         sb.append(")");
3452         return sb.toString();
3453     }
3454 
3455     /**
3456      * {@hide}
3457      */
visibilityToString(int vis)3458     public static String visibilityToString(int vis) {
3459         switch (vis) {
3460             case VISIBILITY_PRIVATE:
3461                 return "PRIVATE";
3462             case VISIBILITY_PUBLIC:
3463                 return "PUBLIC";
3464             case VISIBILITY_SECRET:
3465                 return "SECRET";
3466             default:
3467                 return "UNKNOWN(" + String.valueOf(vis) + ")";
3468         }
3469     }
3470 
3471     /**
3472      * {@hide}
3473      */
priorityToString(@riority int pri)3474     public static String priorityToString(@Priority int pri) {
3475         switch (pri) {
3476             case PRIORITY_MIN:
3477                 return "MIN";
3478             case PRIORITY_LOW:
3479                 return "LOW";
3480             case PRIORITY_DEFAULT:
3481                 return "DEFAULT";
3482             case PRIORITY_HIGH:
3483                 return "HIGH";
3484             case PRIORITY_MAX:
3485                 return "MAX";
3486             default:
3487                 return "UNKNOWN(" + String.valueOf(pri) + ")";
3488         }
3489     }
3490 
3491     /**
3492      * @hide
3493      */
hasCompletedProgress()3494     public boolean hasCompletedProgress() {
3495         // not a progress notification; can't be complete
3496         if (!extras.containsKey(EXTRA_PROGRESS)
3497                 || !extras.containsKey(EXTRA_PROGRESS_MAX)) {
3498             return false;
3499         }
3500         // many apps use max 0 for 'indeterminate'; not complete
3501         if (extras.getInt(EXTRA_PROGRESS_MAX) == 0) {
3502             return false;
3503         }
3504         return extras.getInt(EXTRA_PROGRESS) == extras.getInt(EXTRA_PROGRESS_MAX);
3505     }
3506 
3507     /** @removed */
3508     @Deprecated
getChannel()3509     public String getChannel() {
3510         return mChannelId;
3511     }
3512 
3513     /**
3514      * Returns the id of the channel this notification posts to.
3515      */
getChannelId()3516     public String getChannelId() {
3517         return mChannelId;
3518     }
3519 
3520     /** @removed */
3521     @Deprecated
getTimeout()3522     public long getTimeout() {
3523         return mTimeout;
3524     }
3525 
3526     /**
3527      * Returns the duration from posting after which this notification should be canceled by the
3528      * system, if it's not canceled already.
3529      */
getTimeoutAfter()3530     public long getTimeoutAfter() {
3531         return mTimeout;
3532     }
3533 
3534     /**
3535      * Returns what icon should be shown for this notification if it is being displayed in a
3536      * Launcher that supports badging. Will be one of {@link #BADGE_ICON_NONE},
3537      * {@link #BADGE_ICON_SMALL}, or {@link #BADGE_ICON_LARGE}.
3538      */
getBadgeIconType()3539     public int getBadgeIconType() {
3540         return mBadgeIcon;
3541     }
3542 
3543     /**
3544      * Returns the {@link ShortcutInfo#getId() id} that this notification supersedes, if any.
3545      *
3546      * <p>Used by some Launchers that display notification content to hide shortcuts that duplicate
3547      * notifications.
3548      */
getShortcutId()3549     public String getShortcutId() {
3550         return mShortcutId;
3551     }
3552 
3553     /**
3554      * Gets the {@link LocusId} associated with this notification.
3555      *
3556      * <p>Used by the device's intelligence services to correlate objects (such as
3557      * {@link ShortcutInfo} and {@link ContentCaptureContext}) that are correlated.
3558      */
3559     @Nullable
getLocusId()3560     public LocusId getLocusId() {
3561         return mLocusId;
3562     }
3563 
3564     /**
3565      * Returns the settings text provided to {@link Builder#setSettingsText(CharSequence)}.
3566      */
getSettingsText()3567     public CharSequence getSettingsText() {
3568         return mSettingsText;
3569     }
3570 
3571     /**
3572      * Returns which type of notifications in a group are responsible for audibly alerting the
3573      * user. See {@link #GROUP_ALERT_ALL}, {@link #GROUP_ALERT_CHILDREN},
3574      * {@link #GROUP_ALERT_SUMMARY}.
3575      */
getGroupAlertBehavior()3576     public @GroupAlertBehavior int getGroupAlertBehavior() {
3577         return mGroupAlertBehavior;
3578     }
3579 
3580     /**
3581      * Returns the bubble metadata that will be used to display app content in a floating window
3582      * over the existing foreground activity.
3583      */
3584     @Nullable
getBubbleMetadata()3585     public BubbleMetadata getBubbleMetadata() {
3586         return mBubbleMetadata;
3587     }
3588 
3589     /**
3590      * Sets the {@link BubbleMetadata} for this notification.
3591      * @hide
3592      */
setBubbleMetadata(BubbleMetadata data)3593     public void setBubbleMetadata(BubbleMetadata data) {
3594         mBubbleMetadata = data;
3595     }
3596 
3597     /**
3598      * Returns whether the platform is allowed (by the app developer) to generate contextual actions
3599      * for this notification.
3600      */
getAllowSystemGeneratedContextualActions()3601     public boolean getAllowSystemGeneratedContextualActions() {
3602         return mAllowSystemGeneratedContextualActions;
3603     }
3604 
3605     /**
3606      * The small icon representing this notification in the status bar and content view.
3607      *
3608      * @return the small icon representing this notification.
3609      *
3610      * @see Builder#getSmallIcon()
3611      * @see Builder#setSmallIcon(Icon)
3612      */
getSmallIcon()3613     public Icon getSmallIcon() {
3614         return mSmallIcon;
3615     }
3616 
3617     /**
3618      * Used when notifying to clean up legacy small icons.
3619      * @hide
3620      */
3621     @UnsupportedAppUsage
setSmallIcon(Icon icon)3622     public void setSmallIcon(Icon icon) {
3623         mSmallIcon = icon;
3624     }
3625 
3626     /**
3627      * The large icon shown in this notification's content view.
3628      * @see Builder#getLargeIcon()
3629      * @see Builder#setLargeIcon(Icon)
3630      */
getLargeIcon()3631     public Icon getLargeIcon() {
3632         return mLargeIcon;
3633     }
3634 
3635     /**
3636      * @hide
3637      */
3638     @UnsupportedAppUsage
isGroupSummary()3639     public boolean isGroupSummary() {
3640         return mGroupKey != null && (flags & FLAG_GROUP_SUMMARY) != 0;
3641     }
3642 
3643     /**
3644      * @hide
3645      */
3646     @UnsupportedAppUsage
isGroupChild()3647     public boolean isGroupChild() {
3648         return mGroupKey != null && (flags & FLAG_GROUP_SUMMARY) == 0;
3649     }
3650 
3651     /**
3652      * @hide
3653      */
suppressAlertingDueToGrouping()3654     public boolean suppressAlertingDueToGrouping() {
3655         if (isGroupSummary()
3656                 && getGroupAlertBehavior() == Notification.GROUP_ALERT_CHILDREN) {
3657             return true;
3658         } else if (isGroupChild()
3659                 && getGroupAlertBehavior() == Notification.GROUP_ALERT_SUMMARY) {
3660             return true;
3661         }
3662         return false;
3663     }
3664 
3665 
3666     /**
3667      * Finds and returns a remote input and its corresponding action.
3668      *
3669      * @param requiresFreeform requires the remoteinput to allow freeform or not.
3670      * @return the result pair, {@code null} if no result is found.
3671      */
3672     @Nullable
findRemoteInputActionPair(boolean requiresFreeform)3673     public Pair<RemoteInput, Action> findRemoteInputActionPair(boolean requiresFreeform) {
3674         if (actions == null) {
3675             return null;
3676         }
3677         for (Notification.Action action : actions) {
3678             if (action.getRemoteInputs() == null) {
3679                 continue;
3680             }
3681             RemoteInput resultRemoteInput = null;
3682             for (RemoteInput remoteInput : action.getRemoteInputs()) {
3683                 if (remoteInput.getAllowFreeFormInput() || !requiresFreeform) {
3684                     resultRemoteInput = remoteInput;
3685                 }
3686             }
3687             if (resultRemoteInput != null) {
3688                 return Pair.create(resultRemoteInput, action);
3689             }
3690         }
3691         return null;
3692     }
3693 
3694     /**
3695      * Returns the actions that are contextual (that is, suggested because of the content of the
3696      * notification) out of the actions in this notification.
3697      */
getContextualActions()3698     public @NonNull List<Notification.Action> getContextualActions() {
3699         if (actions == null) return Collections.emptyList();
3700 
3701         List<Notification.Action> contextualActions = new ArrayList<>();
3702         for (Notification.Action action : actions) {
3703             if (action.isContextual()) {
3704                 contextualActions.add(action);
3705             }
3706         }
3707         return contextualActions;
3708     }
3709 
3710     /**
3711      * Builder class for {@link Notification} objects.
3712      *
3713      * Provides a convenient way to set the various fields of a {@link Notification} and generate
3714      * content views using the platform's notification layout template. If your app supports
3715      * versions of Android as old as API level 4, you can instead use
3716      * {@link android.support.v4.app.NotificationCompat.Builder NotificationCompat.Builder},
3717      * available in the <a href="{@docRoot}tools/extras/support-library.html">Android Support
3718      * library</a>.
3719      *
3720      * <p>Example:
3721      *
3722      * <pre class="prettyprint">
3723      * Notification noti = new Notification.Builder(mContext)
3724      *         .setContentTitle(&quot;New mail from &quot; + sender.toString())
3725      *         .setContentText(subject)
3726      *         .setSmallIcon(R.drawable.new_mail)
3727      *         .setLargeIcon(aBitmap)
3728      *         .build();
3729      * </pre>
3730      */
3731     public static class Builder {
3732         /**
3733          * @hide
3734          */
3735         public static final String EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT =
3736                 "android.rebuild.contentViewActionCount";
3737         /**
3738          * @hide
3739          */
3740         public static final String EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT
3741                 = "android.rebuild.bigViewActionCount";
3742         /**
3743          * @hide
3744          */
3745         public static final String EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT
3746                 = "android.rebuild.hudViewActionCount";
3747 
3748         private static final boolean USE_ONLY_TITLE_IN_LOW_PRIORITY_SUMMARY =
3749                 SystemProperties.getBoolean("notifications.only_title", true);
3750 
3751         /**
3752          * The lightness difference that has to be added to the primary text color to obtain the
3753          * secondary text color when the background is light.
3754          */
3755         private static final int LIGHTNESS_TEXT_DIFFERENCE_LIGHT = 20;
3756 
3757         /**
3758          * The lightness difference that has to be added to the primary text color to obtain the
3759          * secondary text color when the background is dark.
3760          * A bit less then the above value, since it looks better on dark backgrounds.
3761          */
3762         private static final int LIGHTNESS_TEXT_DIFFERENCE_DARK = -10;
3763 
3764         private Context mContext;
3765         private Notification mN;
3766         private Bundle mUserExtras = new Bundle();
3767         private Style mStyle;
3768         @UnsupportedAppUsage
3769         private ArrayList<Action> mActions = new ArrayList<>(MAX_ACTION_BUTTONS);
3770         private ArrayList<Person> mPersonList = new ArrayList<>();
3771         private ContrastColorUtil mColorUtil;
3772         private boolean mIsLegacy;
3773         private boolean mIsLegacyInitialized;
3774 
3775         /**
3776          * Caches an instance of StandardTemplateParams. Note that this may have been used before,
3777          * so make sure to call {@link StandardTemplateParams#reset()} before using it.
3778          */
3779         StandardTemplateParams mParams = new StandardTemplateParams();
3780         Colors mColors = new Colors();
3781 
3782         private boolean mTintActionButtons;
3783         private boolean mInNightMode;
3784 
3785         /**
3786          * Constructs a new Builder with the defaults:
3787          *
3788          * @param context
3789          *            A {@link Context} that will be used by the Builder to construct the
3790          *            RemoteViews. The Context will not be held past the lifetime of this Builder
3791          *            object.
3792          * @param channelId
3793          *            The constructed Notification will be posted on this
3794          *            {@link NotificationChannel}. To use a NotificationChannel, it must first be
3795          *            created using {@link NotificationManager#createNotificationChannel}.
3796          */
Builder(Context context, String channelId)3797         public Builder(Context context, String channelId) {
3798             this(context, (Notification) null);
3799             mN.mChannelId = channelId;
3800         }
3801 
3802         /**
3803          * @deprecated use {@link #Builder(Context, String)}
3804          * instead. All posted Notifications must specify a NotificationChannel Id.
3805          */
3806         @Deprecated
Builder(Context context)3807         public Builder(Context context) {
3808             this(context, (Notification) null);
3809         }
3810 
3811         /**
3812          * @hide
3813          */
Builder(Context context, Notification toAdopt)3814         public Builder(Context context, Notification toAdopt) {
3815             mContext = context;
3816             Resources res = mContext.getResources();
3817             mTintActionButtons = res.getBoolean(R.bool.config_tintNotificationActionButtons);
3818 
3819             if (res.getBoolean(R.bool.config_enableNightMode)) {
3820                 Configuration currentConfig = res.getConfiguration();
3821                 mInNightMode = (currentConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK)
3822                         == Configuration.UI_MODE_NIGHT_YES;
3823             }
3824 
3825             if (toAdopt == null) {
3826                 mN = new Notification();
3827                 if (context.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N) {
3828                     mN.extras.putBoolean(EXTRA_SHOW_WHEN, true);
3829                 }
3830                 mN.priority = PRIORITY_DEFAULT;
3831                 mN.visibility = VISIBILITY_PRIVATE;
3832             } else {
3833                 mN = toAdopt;
3834                 if (mN.actions != null) {
3835                     Collections.addAll(mActions, mN.actions);
3836                 }
3837 
3838                 if (mN.extras.containsKey(EXTRA_PEOPLE_LIST)) {
3839                     ArrayList<Person> people = mN.extras.getParcelableArrayList(EXTRA_PEOPLE_LIST);
3840                     mPersonList.addAll(people);
3841                 }
3842 
3843                 if (mN.getSmallIcon() == null && mN.icon != 0) {
3844                     setSmallIcon(mN.icon);
3845                 }
3846 
3847                 if (mN.getLargeIcon() == null && mN.largeIcon != null) {
3848                     setLargeIcon(mN.largeIcon);
3849                 }
3850 
3851                 String templateClass = mN.extras.getString(EXTRA_TEMPLATE);
3852                 if (!TextUtils.isEmpty(templateClass)) {
3853                     final Class<? extends Style> styleClass
3854                             = getNotificationStyleClass(templateClass);
3855                     if (styleClass == null) {
3856                         Log.d(TAG, "Unknown style class: " + templateClass);
3857                     } else {
3858                         try {
3859                             final Constructor<? extends Style> ctor =
3860                                     styleClass.getDeclaredConstructor();
3861                             ctor.setAccessible(true);
3862                             final Style style = ctor.newInstance();
3863                             style.restoreFromExtras(mN.extras);
3864 
3865                             if (style != null) {
3866                                 setStyle(style);
3867                             }
3868                         } catch (Throwable t) {
3869                             Log.e(TAG, "Could not create Style", t);
3870                         }
3871                     }
3872                 }
3873             }
3874         }
3875 
getColorUtil()3876         private ContrastColorUtil getColorUtil() {
3877             if (mColorUtil == null) {
3878                 mColorUtil = ContrastColorUtil.getInstance(mContext);
3879             }
3880             return mColorUtil;
3881         }
3882 
3883         /**
3884          * From Android 11, messaging notifications (those that use {@link MessagingStyle}) that
3885          * use this method to link to a published long-lived sharing shortcut may appear in a
3886          * dedicated Conversation section of the shade and may show configuration options that
3887          * are unique to conversations. This behavior should be reserved for person to person(s)
3888          * conversations where there is a likely social obligation for an individual to respond.
3889          * <p>
3890          * For example, the following are some examples of notifications that belong in the
3891          * conversation space:
3892          * <ul>
3893          * <li>1:1 conversations between two individuals</li>
3894          * <li>Group conversations between individuals where everyone can contribute</li>
3895          * </ul>
3896          * And the following are some examples of notifications that do not belong in the
3897          * conversation space:
3898          * <ul>
3899          * <li>Advertisements from a bot (even if personal and contextualized)</li>
3900          * <li>Engagement notifications from a bot</li>
3901          * <li>Directional conversations where there is an active speaker and many passive
3902          * individuals</li>
3903          * <li>Stream / posting updates from other individuals</li>
3904          * <li>Email, document comments, or other conversation types that are not real-time</li>
3905          * </ul>
3906          * </p>
3907          *
3908          * <p>
3909          * Additionally, this method can be used for all types of notifications to mark this
3910          * notification as duplicative of a Launcher shortcut. Launchers that show badges or
3911          * notification content may then suppress the shortcut in favor of the content of this
3912          * notification.
3913          * <p>
3914          * If this notification has {@link BubbleMetadata} attached that was created with
3915          * a shortcutId a check will be performed to ensure the shortcutId supplied to bubble
3916          * metadata matches the shortcutId set here, if one was set. If the shortcutId's were
3917          * specified but do not match, an exception is thrown.
3918          *
3919          * @param shortcutId the {@link ShortcutInfo#getId() id} of the shortcut this notification
3920          *                   is linked to
3921          *
3922          * @see BubbleMetadata.Builder#Builder(String)
3923          */
3924         @NonNull
setShortcutId(String shortcutId)3925         public Builder setShortcutId(String shortcutId) {
3926             mN.mShortcutId = shortcutId;
3927             return this;
3928         }
3929 
3930         /**
3931          * Sets the {@link LocusId} associated with this notification.
3932          *
3933          * <p>This method should be called when the {@link LocusId} is used in other places (such
3934          * as {@link ShortcutInfo} and {@link ContentCaptureContext}) so the device's intelligence
3935          * services can correlate them.
3936          */
3937         @NonNull
setLocusId(@ullable LocusId locusId)3938         public Builder setLocusId(@Nullable LocusId locusId) {
3939             mN.mLocusId = locusId;
3940             return this;
3941         }
3942 
3943         /**
3944          * Sets which icon to display as a badge for this notification.
3945          *
3946          * Must be one of {@link #BADGE_ICON_NONE}, {@link #BADGE_ICON_SMALL},
3947          * {@link #BADGE_ICON_LARGE}.
3948          *
3949          * Note: This value might be ignored, for launchers that don't support badge icons.
3950          */
3951         @NonNull
setBadgeIconType(int icon)3952         public Builder setBadgeIconType(int icon) {
3953             mN.mBadgeIcon = icon;
3954             return this;
3955         }
3956 
3957         /**
3958          * Sets the group alert behavior for this notification. Use this method to mute this
3959          * notification if alerts for this notification's group should be handled by a different
3960          * notification. This is only applicable for notifications that belong to a
3961          * {@link #setGroup(String) group}. This must be called on all notifications you want to
3962          * mute. For example, if you want only the summary of your group to make noise, all
3963          * children in the group should have the group alert behavior {@link #GROUP_ALERT_SUMMARY}.
3964          *
3965          * <p> The default value is {@link #GROUP_ALERT_ALL}.</p>
3966          */
3967         @NonNull
setGroupAlertBehavior(@roupAlertBehavior int groupAlertBehavior)3968         public Builder setGroupAlertBehavior(@GroupAlertBehavior int groupAlertBehavior) {
3969             mN.mGroupAlertBehavior = groupAlertBehavior;
3970             return this;
3971         }
3972 
3973         /**
3974          * Sets the {@link BubbleMetadata} that will be used to display app content in a floating
3975          * window over the existing foreground activity.
3976          *
3977          * <p>This data will be ignored unless the notification is posted to a channel that
3978          * allows {@link NotificationChannel#canBubble() bubbles}.</p>
3979          *
3980          * <p>Notifications allowed to bubble that have valid bubble metadata will display in
3981          * collapsed state outside of the notification shade on unlocked devices. When a user
3982          * interacts with the collapsed state, the bubble intent will be invoked and displayed.</p>
3983          */
3984         @NonNull
setBubbleMetadata(@ullable BubbleMetadata data)3985         public Builder setBubbleMetadata(@Nullable BubbleMetadata data) {
3986             mN.mBubbleMetadata = data;
3987             return this;
3988         }
3989 
3990         /** @removed */
3991         @Deprecated
setChannel(String channelId)3992         public Builder setChannel(String channelId) {
3993             mN.mChannelId = channelId;
3994             return this;
3995         }
3996 
3997         /**
3998          * Specifies the channel the notification should be delivered on.
3999          */
4000         @NonNull
setChannelId(String channelId)4001         public Builder setChannelId(String channelId) {
4002             mN.mChannelId = channelId;
4003             return this;
4004         }
4005 
4006         /** @removed */
4007         @Deprecated
setTimeout(long durationMs)4008         public Builder setTimeout(long durationMs) {
4009             mN.mTimeout = durationMs;
4010             return this;
4011         }
4012 
4013         /**
4014          * Specifies a duration in milliseconds after which this notification should be canceled,
4015          * if it is not already canceled.
4016          */
4017         @NonNull
setTimeoutAfter(long durationMs)4018         public Builder setTimeoutAfter(long durationMs) {
4019             mN.mTimeout = durationMs;
4020             return this;
4021         }
4022 
4023         /**
4024          * Add a timestamp pertaining to the notification (usually the time the event occurred).
4025          *
4026          * For apps targeting {@link android.os.Build.VERSION_CODES#N} and above, this time is not
4027          * shown anymore by default and must be opted into by using
4028          * {@link android.app.Notification.Builder#setShowWhen(boolean)}
4029          *
4030          * @see Notification#when
4031          */
4032         @NonNull
setWhen(long when)4033         public Builder setWhen(long when) {
4034             mN.when = when;
4035             return this;
4036         }
4037 
4038         /**
4039          * Control whether the timestamp set with {@link #setWhen(long) setWhen} is shown
4040          * in the content view.
4041          * For apps targeting {@link android.os.Build.VERSION_CODES#N} and above, this defaults to
4042          * {@code false}. For earlier apps, the default is {@code true}.
4043          */
4044         @NonNull
setShowWhen(boolean show)4045         public Builder setShowWhen(boolean show) {
4046             mN.extras.putBoolean(EXTRA_SHOW_WHEN, show);
4047             return this;
4048         }
4049 
4050         /**
4051          * Show the {@link Notification#when} field as a stopwatch.
4052          *
4053          * Instead of presenting <code>when</code> as a timestamp, the notification will show an
4054          * automatically updating display of the minutes and seconds since <code>when</code>.
4055          *
4056          * Useful when showing an elapsed time (like an ongoing phone call).
4057          *
4058          * The counter can also be set to count down to <code>when</code> when using
4059          * {@link #setChronometerCountDown(boolean)}.
4060          *
4061          * @see android.widget.Chronometer
4062          * @see Notification#when
4063          * @see #setChronometerCountDown(boolean)
4064          */
4065         @NonNull
setUsesChronometer(boolean b)4066         public Builder setUsesChronometer(boolean b) {
4067             mN.extras.putBoolean(EXTRA_SHOW_CHRONOMETER, b);
4068             return this;
4069         }
4070 
4071         /**
4072          * Sets the Chronometer to count down instead of counting up.
4073          *
4074          * <p>This is only relevant if {@link #setUsesChronometer(boolean)} has been set to true.
4075          * If it isn't set the chronometer will count up.
4076          *
4077          * @see #setUsesChronometer(boolean)
4078          */
4079         @NonNull
setChronometerCountDown(boolean countDown)4080         public Builder setChronometerCountDown(boolean countDown) {
4081             mN.extras.putBoolean(EXTRA_CHRONOMETER_COUNT_DOWN, countDown);
4082             return this;
4083         }
4084 
4085         /**
4086          * Set the small icon resource, which will be used to represent the notification in the
4087          * status bar.
4088          *
4089 
4090          * The platform template for the expanded view will draw this icon in the left, unless a
4091          * {@link #setLargeIcon(Bitmap) large icon} has also been specified, in which case the small
4092          * icon will be moved to the right-hand side.
4093          *
4094 
4095          * @param icon
4096          *            A resource ID in the application's package of the drawable to use.
4097          * @see Notification#icon
4098          */
4099         @NonNull
setSmallIcon(@rawableRes int icon)4100         public Builder setSmallIcon(@DrawableRes int icon) {
4101             return setSmallIcon(icon != 0
4102                     ? Icon.createWithResource(mContext, icon)
4103                     : null);
4104         }
4105 
4106         /**
4107          * A variant of {@link #setSmallIcon(int) setSmallIcon(int)} that takes an additional
4108          * level parameter for when the icon is a {@link android.graphics.drawable.LevelListDrawable
4109          * LevelListDrawable}.
4110          *
4111          * @param icon A resource ID in the application's package of the drawable to use.
4112          * @param level The level to use for the icon.
4113          *
4114          * @see Notification#icon
4115          * @see Notification#iconLevel
4116          */
4117         @NonNull
setSmallIcon(@rawableRes int icon, int level)4118         public Builder setSmallIcon(@DrawableRes int icon, int level) {
4119             mN.iconLevel = level;
4120             return setSmallIcon(icon);
4121         }
4122 
4123         /**
4124          * Set the small icon, which will be used to represent the notification in the
4125          * status bar and content view (unless overridden there by a
4126          * {@link #setLargeIcon(Bitmap) large icon}).
4127          *
4128          * @param icon An Icon object to use.
4129          * @see Notification#icon
4130          */
4131         @NonNull
setSmallIcon(Icon icon)4132         public Builder setSmallIcon(Icon icon) {
4133             mN.setSmallIcon(icon);
4134             if (icon != null && icon.getType() == Icon.TYPE_RESOURCE) {
4135                 mN.icon = icon.getResId();
4136             }
4137             return this;
4138         }
4139 
4140         /**
4141          * Set the first line of text in the platform notification template.
4142          */
4143         @NonNull
setContentTitle(CharSequence title)4144         public Builder setContentTitle(CharSequence title) {
4145             mN.extras.putCharSequence(EXTRA_TITLE, safeCharSequence(title));
4146             return this;
4147         }
4148 
4149         /**
4150          * Set the second line of text in the platform notification template.
4151          */
4152         @NonNull
setContentText(CharSequence text)4153         public Builder setContentText(CharSequence text) {
4154             mN.extras.putCharSequence(EXTRA_TEXT, safeCharSequence(text));
4155             return this;
4156         }
4157 
4158         /**
4159          * This provides some additional information that is displayed in the notification. No
4160          * guarantees are given where exactly it is displayed.
4161          *
4162          * <p>This information should only be provided if it provides an essential
4163          * benefit to the understanding of the notification. The more text you provide the
4164          * less readable it becomes. For example, an email client should only provide the account
4165          * name here if more than one email account has been added.</p>
4166          *
4167          * <p>As of {@link android.os.Build.VERSION_CODES#N} this information is displayed in the
4168          * notification header area.
4169          *
4170          * On Android versions before {@link android.os.Build.VERSION_CODES#N}
4171          * this will be shown in the third line of text in the platform notification template.
4172          * You should not be using {@link #setProgress(int, int, boolean)} at the
4173          * same time on those versions; they occupy the same place.
4174          * </p>
4175          */
4176         @NonNull
setSubText(CharSequence text)4177         public Builder setSubText(CharSequence text) {
4178             mN.extras.putCharSequence(EXTRA_SUB_TEXT, safeCharSequence(text));
4179             return this;
4180         }
4181 
4182         /**
4183          * Provides text that will appear as a link to your application's settings.
4184          *
4185          * <p>This text does not appear within notification {@link Style templates} but may
4186          * appear when the user uses an affordance to learn more about the notification.
4187          * Additionally, this text will not appear unless you provide a valid link target by
4188          * handling {@link #INTENT_CATEGORY_NOTIFICATION_PREFERENCES}.
4189          *
4190          * <p>This text is meant to be concise description about what the user can customize
4191          * when they click on this link. The recommended maximum length is 40 characters.
4192          * @param text
4193          * @return
4194          */
4195         @NonNull
setSettingsText(CharSequence text)4196         public Builder setSettingsText(CharSequence text) {
4197             mN.mSettingsText = safeCharSequence(text);
4198             return this;
4199         }
4200 
4201         /**
4202          * Set the remote input history.
4203          *
4204          * This should be set to the most recent inputs that have been sent
4205          * through a {@link RemoteInput} of this Notification and cleared once the it is no
4206          * longer relevant (e.g. for chat notifications once the other party has responded).
4207          *
4208          * The most recent input must be stored at the 0 index, the second most recent at the
4209          * 1 index, etc. Note that the system will limit both how far back the inputs will be shown
4210          * and how much of each individual input is shown.
4211          *
4212          * <p>Note: The reply text will only be shown on notifications that have least one action
4213          * with a {@code RemoteInput}.</p>
4214          */
4215         @NonNull
setRemoteInputHistory(CharSequence[] text)4216         public Builder setRemoteInputHistory(CharSequence[] text) {
4217             if (text == null) {
4218                 mN.extras.putCharSequenceArray(EXTRA_REMOTE_INPUT_HISTORY, null);
4219             } else {
4220                 final int itemCount = Math.min(MAX_REPLY_HISTORY, text.length);
4221                 CharSequence[] safe = new CharSequence[itemCount];
4222                 RemoteInputHistoryItem[] items = new RemoteInputHistoryItem[itemCount];
4223                 for (int i = 0; i < itemCount; i++) {
4224                     safe[i] = safeCharSequence(text[i]);
4225                     items[i] = new RemoteInputHistoryItem(text[i]);
4226                 }
4227                 mN.extras.putCharSequenceArray(EXTRA_REMOTE_INPUT_HISTORY, safe);
4228 
4229                 // Also add these messages as structured history items.
4230                 mN.extras.putParcelableArray(EXTRA_REMOTE_INPUT_HISTORY_ITEMS, items);
4231             }
4232             return this;
4233         }
4234 
4235         /**
4236          * Set the remote input history, with support for embedding URIs and mime types for
4237          * images and other media.
4238          * @hide
4239          */
4240         @NonNull
setRemoteInputHistory(RemoteInputHistoryItem[] items)4241         public Builder setRemoteInputHistory(RemoteInputHistoryItem[] items) {
4242             if (items == null) {
4243                 mN.extras.putParcelableArray(EXTRA_REMOTE_INPUT_HISTORY_ITEMS, null);
4244             } else {
4245                 final int itemCount = Math.min(MAX_REPLY_HISTORY, items.length);
4246                 RemoteInputHistoryItem[] history = new RemoteInputHistoryItem[itemCount];
4247                 for (int i = 0; i < itemCount; i++) {
4248                     history[i] = items[i];
4249                 }
4250                 mN.extras.putParcelableArray(EXTRA_REMOTE_INPUT_HISTORY_ITEMS, history);
4251             }
4252             return this;
4253         }
4254 
4255         /**
4256          * Sets whether remote history entries view should have a spinner.
4257          * @hide
4258          */
4259         @NonNull
setShowRemoteInputSpinner(boolean showSpinner)4260         public Builder setShowRemoteInputSpinner(boolean showSpinner) {
4261             mN.extras.putBoolean(EXTRA_SHOW_REMOTE_INPUT_SPINNER, showSpinner);
4262             return this;
4263         }
4264 
4265         /**
4266          * Sets whether smart reply buttons should be hidden.
4267          * @hide
4268          */
4269         @NonNull
setHideSmartReplies(boolean hideSmartReplies)4270         public Builder setHideSmartReplies(boolean hideSmartReplies) {
4271             mN.extras.putBoolean(EXTRA_HIDE_SMART_REPLIES, hideSmartReplies);
4272             return this;
4273         }
4274 
4275         /**
4276          * Sets the number of items this notification represents. May be displayed as a badge count
4277          * for Launchers that support badging.
4278          */
4279         @NonNull
setNumber(int number)4280         public Builder setNumber(int number) {
4281             mN.number = number;
4282             return this;
4283         }
4284 
4285         /**
4286          * A small piece of additional information pertaining to this notification.
4287          *
4288          * The platform template will draw this on the last line of the notification, at the far
4289          * right (to the right of a smallIcon if it has been placed there).
4290          *
4291          * @deprecated use {@link #setSubText(CharSequence)} instead to set a text in the header.
4292          * For legacy apps targeting a version below {@link android.os.Build.VERSION_CODES#N} this
4293          * field will still show up, but the subtext will take precedence.
4294          */
4295         @Deprecated
setContentInfo(CharSequence info)4296         public Builder setContentInfo(CharSequence info) {
4297             mN.extras.putCharSequence(EXTRA_INFO_TEXT, safeCharSequence(info));
4298             return this;
4299         }
4300 
4301         /**
4302          * Set the progress this notification represents.
4303          *
4304          * The platform template will represent this using a {@link ProgressBar}.
4305          */
4306         @NonNull
setProgress(int max, int progress, boolean indeterminate)4307         public Builder setProgress(int max, int progress, boolean indeterminate) {
4308             mN.extras.putInt(EXTRA_PROGRESS, progress);
4309             mN.extras.putInt(EXTRA_PROGRESS_MAX, max);
4310             mN.extras.putBoolean(EXTRA_PROGRESS_INDETERMINATE, indeterminate);
4311             return this;
4312         }
4313 
4314         /**
4315          * Supply a custom RemoteViews to use instead of the platform template.
4316          *
4317          * Use {@link #setCustomContentView(RemoteViews)} instead.
4318          */
4319         @Deprecated
setContent(RemoteViews views)4320         public Builder setContent(RemoteViews views) {
4321             return setCustomContentView(views);
4322         }
4323 
4324         /**
4325          * Supply custom RemoteViews to use instead of the platform template.
4326          *
4327          * This will override the layout that would otherwise be constructed by this Builder
4328          * object.
4329          */
4330         @NonNull
setCustomContentView(RemoteViews contentView)4331         public Builder setCustomContentView(RemoteViews contentView) {
4332             mN.contentView = contentView;
4333             return this;
4334         }
4335 
4336         /**
4337          * Supply custom RemoteViews to use instead of the platform template in the expanded form.
4338          *
4339          * This will override the expanded layout that would otherwise be constructed by this
4340          * Builder object.
4341          */
4342         @NonNull
setCustomBigContentView(RemoteViews contentView)4343         public Builder setCustomBigContentView(RemoteViews contentView) {
4344             mN.bigContentView = contentView;
4345             return this;
4346         }
4347 
4348         /**
4349          * Supply custom RemoteViews to use instead of the platform template in the heads up dialog.
4350          *
4351          * This will override the heads-up layout that would otherwise be constructed by this
4352          * Builder object.
4353          */
4354         @NonNull
setCustomHeadsUpContentView(RemoteViews contentView)4355         public Builder setCustomHeadsUpContentView(RemoteViews contentView) {
4356             mN.headsUpContentView = contentView;
4357             return this;
4358         }
4359 
4360         /**
4361          * Supply a {@link PendingIntent} to be sent when the notification is clicked.
4362          *
4363          * <p>As of Android {@link android.os.Build.VERSION_CODES#S}, apps targeting API level
4364          * {@link android.os.Build.VERSION_CODES#S} or higher won't be able to start activities
4365          * while processing broadcast receivers or services in response to notification clicks. To
4366          * launch an activity in those cases, provide a {@link PendingIntent} for the activity
4367          * itself.
4368          *
4369          * <p>As of {@link android.os.Build.VERSION_CODES#HONEYCOMB}, if this field is unset and you
4370          * have specified a custom RemoteViews with {@link #setContent(RemoteViews)}, you can use
4371          * {@link RemoteViews#setOnClickPendingIntent RemoteViews.setOnClickPendingIntent(int,PendingIntent)}
4372          * to assign PendingIntents to individual views in that custom layout (i.e., to create
4373          * clickable buttons inside the notification view).
4374          *
4375          * @see Notification#contentIntent Notification.contentIntent
4376          */
4377         @NonNull
setContentIntent(PendingIntent intent)4378         public Builder setContentIntent(PendingIntent intent) {
4379             mN.contentIntent = intent;
4380             return this;
4381         }
4382 
4383         /**
4384          * Supply a {@link PendingIntent} to send when the notification is cleared explicitly by the user.
4385          *
4386          * @see Notification#deleteIntent
4387          */
4388         @NonNull
setDeleteIntent(PendingIntent intent)4389         public Builder setDeleteIntent(PendingIntent intent) {
4390             mN.deleteIntent = intent;
4391             return this;
4392         }
4393 
4394         /**
4395          * An intent to launch instead of posting the notification to the status bar.
4396          * Only for use with extremely high-priority notifications demanding the user's
4397          * <strong>immediate</strong> attention, such as an incoming phone call or
4398          * alarm clock that the user has explicitly set to a particular time.
4399          * If this facility is used for something else, please give the user an option
4400          * to turn it off and use a normal notification, as this can be extremely
4401          * disruptive.
4402          *
4403          * <p>
4404          * The system UI may choose to display a heads-up notification, instead of
4405          * launching this intent, while the user is using the device.
4406          * </p>
4407          * <p>Apps targeting {@link Build.VERSION_CODES#Q} and above will have to request
4408          * a permission ({@link android.Manifest.permission#USE_FULL_SCREEN_INTENT}) in order to
4409          * use full screen intents.</p>
4410          *
4411          * @param intent The pending intent to launch.
4412          * @param highPriority Passing true will cause this notification to be sent
4413          *          even if other notifications are suppressed.
4414          *
4415          * @see Notification#fullScreenIntent
4416          */
4417         @NonNull
setFullScreenIntent(PendingIntent intent, boolean highPriority)4418         public Builder setFullScreenIntent(PendingIntent intent, boolean highPriority) {
4419             mN.fullScreenIntent = intent;
4420             setFlag(FLAG_HIGH_PRIORITY, highPriority);
4421             return this;
4422         }
4423 
4424         /**
4425          * Set the "ticker" text which is sent to accessibility services.
4426          *
4427          * @see Notification#tickerText
4428          */
4429         @NonNull
setTicker(CharSequence tickerText)4430         public Builder setTicker(CharSequence tickerText) {
4431             mN.tickerText = safeCharSequence(tickerText);
4432             return this;
4433         }
4434 
4435         /**
4436          * Obsolete version of {@link #setTicker(CharSequence)}.
4437          *
4438          */
4439         @Deprecated
setTicker(CharSequence tickerText, RemoteViews views)4440         public Builder setTicker(CharSequence tickerText, RemoteViews views) {
4441             setTicker(tickerText);
4442             // views is ignored
4443             return this;
4444         }
4445 
4446         /**
4447          * Add a large icon to the notification content view.
4448          *
4449          * In the platform template, this image will be shown either on the right of the
4450          * notification, with an aspect ratio of up to 16:9, or (when the notification is grouped)
4451          * on the left in place of the {@link #setSmallIcon(Icon) small icon}.
4452          */
4453         @NonNull
setLargeIcon(Bitmap b)4454         public Builder setLargeIcon(Bitmap b) {
4455             return setLargeIcon(b != null ? Icon.createWithBitmap(b) : null);
4456         }
4457 
4458         /**
4459          * Add a large icon to the notification content view.
4460          *
4461          * In the platform template, this image will be shown either on the right of the
4462          * notification, with an aspect ratio of up to 16:9, or (when the notification is grouped)
4463          * on the left in place of the {@link #setSmallIcon(Icon) small icon}.
4464          */
4465         @NonNull
setLargeIcon(Icon icon)4466         public Builder setLargeIcon(Icon icon) {
4467             mN.mLargeIcon = icon;
4468             mN.extras.putParcelable(EXTRA_LARGE_ICON, icon);
4469             return this;
4470         }
4471 
4472         /**
4473          * Set the sound to play.
4474          *
4475          * It will be played using the {@link #AUDIO_ATTRIBUTES_DEFAULT default audio attributes}
4476          * for notifications.
4477          *
4478          * @deprecated use {@link NotificationChannel#setSound(Uri, AudioAttributes)} instead.
4479          */
4480         @Deprecated
setSound(Uri sound)4481         public Builder setSound(Uri sound) {
4482             mN.sound = sound;
4483             mN.audioAttributes = AUDIO_ATTRIBUTES_DEFAULT;
4484             return this;
4485         }
4486 
4487         /**
4488          * Set the sound to play, along with a specific stream on which to play it.
4489          *
4490          * See {@link android.media.AudioManager} for the <code>STREAM_</code> constants.
4491          *
4492          * @deprecated use {@link NotificationChannel#setSound(Uri, AudioAttributes)}.
4493          */
4494         @Deprecated
setSound(Uri sound, int streamType)4495         public Builder setSound(Uri sound, int streamType) {
4496             PlayerBase.deprecateStreamTypeForPlayback(streamType, "Notification", "setSound()");
4497             mN.sound = sound;
4498             mN.audioStreamType = streamType;
4499             return this;
4500         }
4501 
4502         /**
4503          * Set the sound to play, along with specific {@link AudioAttributes audio attributes} to
4504          * use during playback.
4505          *
4506          * @deprecated use {@link NotificationChannel#setSound(Uri, AudioAttributes)} instead.
4507          * @see Notification#sound
4508          */
4509         @Deprecated
setSound(Uri sound, AudioAttributes audioAttributes)4510         public Builder setSound(Uri sound, AudioAttributes audioAttributes) {
4511             mN.sound = sound;
4512             mN.audioAttributes = audioAttributes;
4513             return this;
4514         }
4515 
4516         /**
4517          * Set the vibration pattern to use.
4518          *
4519          * See {@link android.os.Vibrator#vibrate(long[], int)} for a discussion of the
4520          * <code>pattern</code> parameter.
4521          *
4522          * <p>
4523          * A notification that vibrates is more likely to be presented as a heads-up notification.
4524          * </p>
4525          *
4526          * @deprecated use {@link NotificationChannel#setVibrationPattern(long[])} instead.
4527          * @see Notification#vibrate
4528          */
4529         @Deprecated
setVibrate(long[] pattern)4530         public Builder setVibrate(long[] pattern) {
4531             mN.vibrate = pattern;
4532             return this;
4533         }
4534 
4535         /**
4536          * Set the desired color for the indicator LED on the device, as well as the
4537          * blink duty cycle (specified in milliseconds).
4538          *
4539 
4540          * Not all devices will honor all (or even any) of these values.
4541          *
4542          * @deprecated use {@link NotificationChannel#enableLights(boolean)} instead.
4543          * @see Notification#ledARGB
4544          * @see Notification#ledOnMS
4545          * @see Notification#ledOffMS
4546          */
4547         @Deprecated
setLights(@olorInt int argb, int onMs, int offMs)4548         public Builder setLights(@ColorInt int argb, int onMs, int offMs) {
4549             mN.ledARGB = argb;
4550             mN.ledOnMS = onMs;
4551             mN.ledOffMS = offMs;
4552             if (onMs != 0 || offMs != 0) {
4553                 mN.flags |= FLAG_SHOW_LIGHTS;
4554             }
4555             return this;
4556         }
4557 
4558         /**
4559          * Set whether this is an "ongoing" notification.
4560          *
4561 
4562          * Ongoing notifications cannot be dismissed by the user, so your application or service
4563          * must take care of canceling them.
4564          *
4565 
4566          * They are typically used to indicate a background task that the user is actively engaged
4567          * with (e.g., playing music) or is pending in some way and therefore occupying the device
4568          * (e.g., a file download, sync operation, active network connection).
4569          *
4570 
4571          * @see Notification#FLAG_ONGOING_EVENT
4572          */
4573         @NonNull
setOngoing(boolean ongoing)4574         public Builder setOngoing(boolean ongoing) {
4575             setFlag(FLAG_ONGOING_EVENT, ongoing);
4576             return this;
4577         }
4578 
4579         /**
4580          * Set whether this notification should be colorized. When set, the color set with
4581          * {@link #setColor(int)} will be used as the background color of this notification.
4582          * <p>
4583          * This should only be used for high priority ongoing tasks like navigation, an ongoing
4584          * call, or other similarly high-priority events for the user.
4585          * <p>
4586          * For most styles, the coloring will only be applied if the notification is for a
4587          * foreground service notification.
4588          * However, for {@link MediaStyle} and {@link DecoratedMediaCustomViewStyle} notifications
4589          * that have a media session attached there is no such requirement.
4590          *
4591          * @see #setColor(int)
4592          * @see MediaStyle#setMediaSession(MediaSession.Token)
4593          */
4594         @NonNull
setColorized(boolean colorize)4595         public Builder setColorized(boolean colorize) {
4596             mN.extras.putBoolean(EXTRA_COLORIZED, colorize);
4597             return this;
4598         }
4599 
4600         /**
4601          * Set this flag if you would only like the sound, vibrate
4602          * and ticker to be played if the notification is not already showing.
4603          *
4604          * @see Notification#FLAG_ONLY_ALERT_ONCE
4605          */
4606         @NonNull
setOnlyAlertOnce(boolean onlyAlertOnce)4607         public Builder setOnlyAlertOnce(boolean onlyAlertOnce) {
4608             setFlag(FLAG_ONLY_ALERT_ONCE, onlyAlertOnce);
4609             return this;
4610         }
4611 
4612         /**
4613          * Specify a desired visibility policy for a Notification associated with a
4614          * foreground service.  By default, the system can choose to defer
4615          * visibility of the notification for a short time after the service is
4616          * started.  Pass
4617          * {@link Notification#FOREGROUND_SERVICE_IMMEDIATE FOREGROUND_SERVICE_IMMEDIATE}
4618          * to this method in order to guarantee that visibility is never deferred.  Pass
4619          * {@link Notification#FOREGROUND_SERVICE_DEFERRED FOREGROUND_SERVICE_DEFERRED}
4620          * to request that visibility is deferred whenever possible.
4621          *
4622          * <p class="note">Note that deferred visibility is not guaranteed.  There
4623          * may be some circumstances under which the system will show the foreground
4624          * service's associated Notification immediately even when the app has used
4625          * this method to explicitly request deferred display.</p>
4626          * @param behavior One of
4627          * {@link Notification#FOREGROUND_SERVICE_DEFAULT FOREGROUND_SERVICE_DEFAULT},
4628          * {@link Notification#FOREGROUND_SERVICE_IMMEDIATE FOREGROUND_SERVICE_IMMEDIATE},
4629          * or {@link Notification#FOREGROUND_SERVICE_DEFERRED FOREGROUND_SERVICE_DEFERRED}
4630          * @return
4631          */
4632         @NonNull
setForegroundServiceBehavior(@erviceNotificationPolicy int behavior)4633         public Builder setForegroundServiceBehavior(@ServiceNotificationPolicy int behavior) {
4634             mN.mFgsDeferBehavior = behavior;
4635             return this;
4636         }
4637 
4638         /**
4639          * Make this notification automatically dismissed when the user touches it.
4640          *
4641          * @see Notification#FLAG_AUTO_CANCEL
4642          */
4643         @NonNull
setAutoCancel(boolean autoCancel)4644         public Builder setAutoCancel(boolean autoCancel) {
4645             setFlag(FLAG_AUTO_CANCEL, autoCancel);
4646             return this;
4647         }
4648 
4649         /**
4650          * Set whether or not this notification should not bridge to other devices.
4651          *
4652          * <p>Some notifications can be bridged to other devices for remote display.
4653          * This hint can be set to recommend this notification not be bridged.
4654          */
4655         @NonNull
setLocalOnly(boolean localOnly)4656         public Builder setLocalOnly(boolean localOnly) {
4657             setFlag(FLAG_LOCAL_ONLY, localOnly);
4658             return this;
4659         }
4660 
4661         /**
4662          * Set which notification properties will be inherited from system defaults.
4663          * <p>
4664          * The value should be one or more of the following fields combined with
4665          * bitwise-or:
4666          * {@link #DEFAULT_SOUND}, {@link #DEFAULT_VIBRATE}, {@link #DEFAULT_LIGHTS}.
4667          * <p>
4668          * For all default values, use {@link #DEFAULT_ALL}.
4669          *
4670          * @deprecated use {@link NotificationChannel#enableVibration(boolean)} and
4671          * {@link NotificationChannel#enableLights(boolean)} and
4672          * {@link NotificationChannel#setSound(Uri, AudioAttributes)} instead.
4673          */
4674         @Deprecated
setDefaults(int defaults)4675         public Builder setDefaults(int defaults) {
4676             mN.defaults = defaults;
4677             return this;
4678         }
4679 
4680         /**
4681          * Set the priority of this notification.
4682          *
4683          * @see Notification#priority
4684          * @deprecated use {@link NotificationChannel#setImportance(int)} instead.
4685          */
4686         @Deprecated
setPriority(@riority int pri)4687         public Builder setPriority(@Priority int pri) {
4688             mN.priority = pri;
4689             return this;
4690         }
4691 
4692         /**
4693          * Set the notification category.
4694          *
4695          * @see Notification#category
4696          */
4697         @NonNull
setCategory(String category)4698         public Builder setCategory(String category) {
4699             mN.category = category;
4700             return this;
4701         }
4702 
4703         /**
4704          * Add a person that is relevant to this notification.
4705          *
4706          * <P>
4707          * Depending on user preferences, this annotation may allow the notification to pass
4708          * through interruption filters, if this notification is of category {@link #CATEGORY_CALL}
4709          * or {@link #CATEGORY_MESSAGE}. The addition of people may also cause this notification to
4710          * appear more prominently in the user interface.
4711          * </P>
4712          *
4713          * <P>
4714          * The person should be specified by the {@code String} representation of a
4715          * {@link android.provider.ContactsContract.Contacts#CONTENT_LOOKUP_URI}.
4716          * </P>
4717          *
4718          * <P>The system will also attempt to resolve {@code mailto:} and {@code tel:} schema
4719          * URIs.  The path part of these URIs must exist in the contacts database, in the
4720          * appropriate column, or the reference will be discarded as invalid. Telephone schema
4721          * URIs will be resolved by {@link android.provider.ContactsContract.PhoneLookup}.
4722          * It is also possible to provide a URI with the schema {@code name:} in order to uniquely
4723          * identify a person without an entry in the contacts database.
4724          * </P>
4725          *
4726          * @param uri A URI for the person.
4727          * @see Notification#EXTRA_PEOPLE
4728          * @deprecated use {@link #addPerson(Person)}
4729          */
addPerson(String uri)4730         public Builder addPerson(String uri) {
4731             addPerson(new Person.Builder().setUri(uri).build());
4732             return this;
4733         }
4734 
4735         /**
4736          * Add a person that is relevant to this notification.
4737          *
4738          * <P>
4739          * Depending on user preferences, this annotation may allow the notification to pass
4740          * through interruption filters, if this notification is of category {@link #CATEGORY_CALL}
4741          * or {@link #CATEGORY_MESSAGE}. The addition of people may also cause this notification to
4742          * appear more prominently in the user interface.
4743          * </P>
4744          *
4745          * <P>
4746          * A person should usually contain a uri in order to benefit from the ranking boost.
4747          * However, even if no uri is provided, it's beneficial to provide other people in the
4748          * notification, such that listeners and voice only devices can announce and handle them
4749          * properly.
4750          * </P>
4751          *
4752          * @param person the person to add.
4753          * @see Notification#EXTRA_PEOPLE_LIST
4754          */
4755         @NonNull
addPerson(Person person)4756         public Builder addPerson(Person person) {
4757             mPersonList.add(person);
4758             return this;
4759         }
4760 
4761         /**
4762          * Set this notification to be part of a group of notifications sharing the same key.
4763          * Grouped notifications may display in a cluster or stack on devices which
4764          * support such rendering.
4765          *
4766          * <p>To make this notification the summary for its group, also call
4767          * {@link #setGroupSummary}. A sort order can be specified for group members by using
4768          * {@link #setSortKey}.
4769          * @param groupKey The group key of the group.
4770          * @return this object for method chaining
4771          */
4772         @NonNull
setGroup(String groupKey)4773         public Builder setGroup(String groupKey) {
4774             mN.mGroupKey = groupKey;
4775             return this;
4776         }
4777 
4778         /**
4779          * Set this notification to be the group summary for a group of notifications.
4780          * Grouped notifications may display in a cluster or stack on devices which
4781          * support such rendering. If thereRequires a group key also be set using {@link #setGroup}.
4782          * The group summary may be suppressed if too few notifications are included in the group.
4783          * @param isGroupSummary Whether this notification should be a group summary.
4784          * @return this object for method chaining
4785          */
4786         @NonNull
setGroupSummary(boolean isGroupSummary)4787         public Builder setGroupSummary(boolean isGroupSummary) {
4788             setFlag(FLAG_GROUP_SUMMARY, isGroupSummary);
4789             return this;
4790         }
4791 
4792         /**
4793          * Set a sort key that orders this notification among other notifications from the
4794          * same package. This can be useful if an external sort was already applied and an app
4795          * would like to preserve this. Notifications will be sorted lexicographically using this
4796          * value, although providing different priorities in addition to providing sort key may
4797          * cause this value to be ignored.
4798          *
4799          * <p>This sort key can also be used to order members of a notification group. See
4800          * {@link #setGroup}.
4801          *
4802          * @see String#compareTo(String)
4803          */
4804         @NonNull
setSortKey(String sortKey)4805         public Builder setSortKey(String sortKey) {
4806             mN.mSortKey = sortKey;
4807             return this;
4808         }
4809 
4810         /**
4811          * Merge additional metadata into this notification.
4812          *
4813          * <p>Values within the Bundle will replace existing extras values in this Builder.
4814          *
4815          * @see Notification#extras
4816          */
4817         @NonNull
addExtras(Bundle extras)4818         public Builder addExtras(Bundle extras) {
4819             if (extras != null) {
4820                 mUserExtras.putAll(extras);
4821             }
4822             return this;
4823         }
4824 
4825         /**
4826          * Set metadata for this notification.
4827          *
4828          * <p>A reference to the Bundle is held for the lifetime of this Builder, and the Bundle's
4829          * current contents are copied into the Notification each time {@link #build()} is
4830          * called.
4831          *
4832          * <p>Replaces any existing extras values with those from the provided Bundle.
4833          * Use {@link #addExtras} to merge in metadata instead.
4834          *
4835          * @see Notification#extras
4836          */
4837         @NonNull
setExtras(Bundle extras)4838         public Builder setExtras(Bundle extras) {
4839             if (extras != null) {
4840                 mUserExtras = extras;
4841             }
4842             return this;
4843         }
4844 
4845         /**
4846          * Get the current metadata Bundle used by this notification Builder.
4847          *
4848          * <p>The returned Bundle is shared with this Builder.
4849          *
4850          * <p>The current contents of this Bundle are copied into the Notification each time
4851          * {@link #build()} is called.
4852          *
4853          * @see Notification#extras
4854          */
getExtras()4855         public Bundle getExtras() {
4856             return mUserExtras;
4857         }
4858 
getAllExtras()4859         private Bundle getAllExtras() {
4860             final Bundle saveExtras = (Bundle) mUserExtras.clone();
4861             saveExtras.putAll(mN.extras);
4862             return saveExtras;
4863         }
4864 
4865         /**
4866          * Add an action to this notification. Actions are typically displayed by
4867          * the system as a button adjacent to the notification content.
4868          * <p>
4869          * Every action must have an icon (32dp square and matching the
4870          * <a href="{@docRoot}design/style/iconography.html#action-bar">Holo
4871          * Dark action bar</a> visual style), a textual label, and a {@link PendingIntent}.
4872          * <p>
4873          * A notification in its expanded form can display up to 3 actions, from left to right in
4874          * the order they were added. Actions will not be displayed when the notification is
4875          * collapsed, however, so be sure that any essential functions may be accessed by the user
4876          * in some other way (for example, in the Activity pointed to by {@link #contentIntent}).
4877          * <p>
4878          * As of Android {@link android.os.Build.VERSION_CODES#S}, apps targeting API level
4879          * {@link android.os.Build.VERSION_CODES#S} or higher won't be able to start activities
4880          * while processing broadcast receivers or services in response to notification action
4881          * clicks. To launch an activity in those cases, provide a {@link PendingIntent} to the
4882          * activity itself.
4883          *
4884          * @param icon Resource ID of a drawable that represents the action.
4885          * @param title Text describing the action.
4886          * @param intent PendingIntent to be fired when the action is invoked.
4887          *
4888          * @deprecated Use {@link #addAction(Action)} instead.
4889          */
4890         @Deprecated
addAction(int icon, CharSequence title, PendingIntent intent)4891         public Builder addAction(int icon, CharSequence title, PendingIntent intent) {
4892             mActions.add(new Action(icon, safeCharSequence(title), intent));
4893             return this;
4894         }
4895 
4896         /**
4897          * Add an action to this notification. Actions are typically displayed by
4898          * the system as a button adjacent to the notification content.
4899          * <p>
4900          * Every action must have an icon (32dp square and matching the
4901          * <a href="{@docRoot}design/style/iconography.html#action-bar">Holo
4902          * Dark action bar</a> visual style), a textual label, and a {@link PendingIntent}.
4903          * <p>
4904          * A notification in its expanded form can display up to 3 actions, from left to right in
4905          * the order they were added. Actions will not be displayed when the notification is
4906          * collapsed, however, so be sure that any essential functions may be accessed by the user
4907          * in some other way (for example, in the Activity pointed to by {@link #contentIntent}).
4908          *
4909          * @param action The action to add.
4910          */
4911         @NonNull
addAction(Action action)4912         public Builder addAction(Action action) {
4913             if (action != null) {
4914                 mActions.add(action);
4915             }
4916             return this;
4917         }
4918 
4919         /**
4920          * Alter the complete list of actions attached to this notification.
4921          * @see #addAction(Action).
4922          *
4923          * @param actions
4924          * @return
4925          */
4926         @NonNull
setActions(Action... actions)4927         public Builder setActions(Action... actions) {
4928             mActions.clear();
4929             for (int i = 0; i < actions.length; i++) {
4930                 if (actions[i] != null) {
4931                     mActions.add(actions[i]);
4932                 }
4933             }
4934             return this;
4935         }
4936 
4937         /**
4938          * Add a rich notification style to be applied at build time.
4939          *
4940          * @param style Object responsible for modifying the notification style.
4941          */
4942         @NonNull
setStyle(Style style)4943         public Builder setStyle(Style style) {
4944             if (mStyle != style) {
4945                 mStyle = style;
4946                 if (mStyle != null) {
4947                     mStyle.setBuilder(this);
4948                     mN.extras.putString(EXTRA_TEMPLATE, style.getClass().getName());
4949                 }  else {
4950                     mN.extras.remove(EXTRA_TEMPLATE);
4951                 }
4952             }
4953             return this;
4954         }
4955 
4956         /**
4957          * Returns the style set by {@link #setStyle(Style)}.
4958          */
getStyle()4959         public Style getStyle() {
4960             return mStyle;
4961         }
4962 
4963         /**
4964          * Specify the value of {@link #visibility}.
4965          *
4966          * @return The same Builder.
4967          */
4968         @NonNull
setVisibility(@isibility int visibility)4969         public Builder setVisibility(@Visibility int visibility) {
4970             mN.visibility = visibility;
4971             return this;
4972         }
4973 
4974         /**
4975          * Supply a replacement Notification whose contents should be shown in insecure contexts
4976          * (i.e. atop the secure lockscreen). See {@link #visibility} and {@link #VISIBILITY_PUBLIC}.
4977          * @param n A replacement notification, presumably with some or all info redacted.
4978          * @return The same Builder.
4979          */
4980         @NonNull
setPublicVersion(Notification n)4981         public Builder setPublicVersion(Notification n) {
4982             if (n != null) {
4983                 mN.publicVersion = new Notification();
4984                 n.cloneInto(mN.publicVersion, /*heavy=*/ true);
4985             } else {
4986                 mN.publicVersion = null;
4987             }
4988             return this;
4989         }
4990 
4991         /**
4992          * Apply an extender to this notification builder. Extenders may be used to add
4993          * metadata or change options on this builder.
4994          */
4995         @NonNull
extend(Extender extender)4996         public Builder extend(Extender extender) {
4997             extender.extend(this);
4998             return this;
4999         }
5000 
5001         /**
5002          * Set the value for a notification flag
5003          *
5004          * @param mask Bit mask of the flag
5005          * @param value Status (on/off) of the flag
5006          *
5007          * @return The same Builder.
5008          */
5009         @NonNull
setFlag(@otificationFlags int mask, boolean value)5010         public Builder setFlag(@NotificationFlags int mask, boolean value) {
5011             if (value) {
5012                 mN.flags |= mask;
5013             } else {
5014                 mN.flags &= ~mask;
5015             }
5016             return this;
5017         }
5018 
5019         /**
5020          * Sets {@link Notification#color}.
5021          *
5022          * @param argb The accent color to use
5023          *
5024          * @return The same Builder.
5025          */
5026         @NonNull
setColor(@olorInt int argb)5027         public Builder setColor(@ColorInt int argb) {
5028             mN.color = argb;
5029             sanitizeColor();
5030             return this;
5031         }
5032 
bindPhishingAlertIcon(RemoteViews contentView, StandardTemplateParams p)5033         private void bindPhishingAlertIcon(RemoteViews contentView, StandardTemplateParams p) {
5034             contentView.setDrawableTint(
5035                     R.id.phishing_alert,
5036                     false /* targetBackground */,
5037                     getColors(p).getErrorColor(),
5038                     PorterDuff.Mode.SRC_ATOP);
5039         }
5040 
getProfileBadgeDrawable()5041         private Drawable getProfileBadgeDrawable() {
5042             if (mContext.getUserId() == UserHandle.USER_SYSTEM) {
5043                 // This user can never be a badged profile,
5044                 // and also includes USER_ALL system notifications.
5045                 return null;
5046             }
5047             // Note: This assumes that the current user can read the profile badge of the
5048             // originating user.
5049             return mContext.getPackageManager().getUserBadgeForDensityNoBackground(
5050                     new UserHandle(mContext.getUserId()), 0);
5051         }
5052 
getProfileBadge()5053         private Bitmap getProfileBadge() {
5054             Drawable badge = getProfileBadgeDrawable();
5055             if (badge == null) {
5056                 return null;
5057             }
5058             final int size = mContext.getResources().getDimensionPixelSize(
5059                     R.dimen.notification_badge_size);
5060             Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
5061             Canvas canvas = new Canvas(bitmap);
5062             badge.setBounds(0, 0, size, size);
5063             badge.draw(canvas);
5064             return bitmap;
5065         }
5066 
bindProfileBadge(RemoteViews contentView, StandardTemplateParams p)5067         private void bindProfileBadge(RemoteViews contentView, StandardTemplateParams p) {
5068             Bitmap profileBadge = getProfileBadge();
5069 
5070             if (profileBadge != null) {
5071                 contentView.setImageViewBitmap(R.id.profile_badge, profileBadge);
5072                 contentView.setViewVisibility(R.id.profile_badge, View.VISIBLE);
5073                 if (isBackgroundColorized(p)) {
5074                     contentView.setDrawableTint(R.id.profile_badge, false,
5075                             getPrimaryTextColor(p), PorterDuff.Mode.SRC_ATOP);
5076                 }
5077             }
5078         }
5079 
bindAlertedIcon(RemoteViews contentView, StandardTemplateParams p)5080         private void bindAlertedIcon(RemoteViews contentView, StandardTemplateParams p) {
5081             contentView.setDrawableTint(
5082                     R.id.alerted_icon,
5083                     false /* targetBackground */,
5084                     getColors(p).getSecondaryTextColor(),
5085                     PorterDuff.Mode.SRC_IN);
5086         }
5087 
5088         /**
5089          * @hide
5090          */
usesStandardHeader()5091         public boolean usesStandardHeader() {
5092             if (mN.mUsesStandardHeader) {
5093                 return true;
5094             }
5095             if (mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.N) {
5096                 if (mN.contentView == null && mN.bigContentView == null) {
5097                     return true;
5098                 }
5099             }
5100             boolean contentViewUsesHeader = mN.contentView == null
5101                     || STANDARD_LAYOUTS.contains(mN.contentView.getLayoutId());
5102             boolean bigContentViewUsesHeader = mN.bigContentView == null
5103                     || STANDARD_LAYOUTS.contains(mN.bigContentView.getLayoutId());
5104             return contentViewUsesHeader && bigContentViewUsesHeader;
5105         }
5106 
resetStandardTemplate(RemoteViews contentView)5107         private void resetStandardTemplate(RemoteViews contentView) {
5108             resetNotificationHeader(contentView);
5109             contentView.setViewVisibility(R.id.right_icon, View.GONE);
5110             contentView.setViewVisibility(R.id.title, View.GONE);
5111             contentView.setTextViewText(R.id.title, null);
5112             contentView.setViewVisibility(R.id.text, View.GONE);
5113             contentView.setTextViewText(R.id.text, null);
5114         }
5115 
5116         /**
5117          * Resets the notification header to its original state
5118          */
resetNotificationHeader(RemoteViews contentView)5119         private void resetNotificationHeader(RemoteViews contentView) {
5120             // Small icon doesn't need to be reset, as it's always set. Resetting would prevent
5121             // re-using the drawable when the notification is updated.
5122             contentView.setBoolean(R.id.expand_button, "setExpanded", false);
5123             contentView.setViewVisibility(R.id.app_name_text, View.GONE);
5124             contentView.setTextViewText(R.id.app_name_text, null);
5125             contentView.setViewVisibility(R.id.chronometer, View.GONE);
5126             contentView.setViewVisibility(R.id.header_text, View.GONE);
5127             contentView.setTextViewText(R.id.header_text, null);
5128             contentView.setViewVisibility(R.id.header_text_secondary, View.GONE);
5129             contentView.setTextViewText(R.id.header_text_secondary, null);
5130             contentView.setViewVisibility(R.id.header_text_divider, View.GONE);
5131             contentView.setViewVisibility(R.id.header_text_secondary_divider, View.GONE);
5132             contentView.setViewVisibility(R.id.time_divider, View.GONE);
5133             contentView.setViewVisibility(R.id.time, View.GONE);
5134             contentView.setImageViewIcon(R.id.profile_badge, null);
5135             contentView.setViewVisibility(R.id.profile_badge, View.GONE);
5136             mN.mUsesStandardHeader = false;
5137         }
5138 
applyStandardTemplate(int resId, StandardTemplateParams p, TemplateBindResult result)5139         private RemoteViews applyStandardTemplate(int resId, StandardTemplateParams p,
5140                 TemplateBindResult result) {
5141             p.headerless(resId == getBaseLayoutResource()
5142                     || resId == getHeadsUpBaseLayoutResource()
5143                     || resId == getMessagingLayoutResource()
5144                     || resId == R.layout.notification_template_material_media);
5145             RemoteViews contentView = new BuilderRemoteViews(mContext.getApplicationInfo(), resId);
5146 
5147             resetStandardTemplate(contentView);
5148 
5149             final Bundle ex = mN.extras;
5150             updateBackgroundColor(contentView, p);
5151             bindNotificationHeader(contentView, p);
5152             bindLargeIconAndApplyMargin(contentView, p, result);
5153             boolean showProgress = handleProgressBar(contentView, ex, p);
5154             boolean hasSecondLine = showProgress;
5155             if (p.hasTitle()) {
5156                 contentView.setViewVisibility(p.mTitleViewId, View.VISIBLE);
5157                 contentView.setTextViewText(p.mTitleViewId, processTextSpans(p.title));
5158                 setTextViewColorPrimary(contentView, p.mTitleViewId, p);
5159             } else if (p.mTitleViewId != R.id.title) {
5160                 // This alternate title view ID is not cleared by resetStandardTemplate
5161                 contentView.setViewVisibility(p.mTitleViewId, View.GONE);
5162                 contentView.setTextViewText(p.mTitleViewId, null);
5163             }
5164             if (p.text != null && p.text.length() != 0
5165                     && (!showProgress || p.mAllowTextWithProgress)) {
5166                 contentView.setViewVisibility(p.mTextViewId, View.VISIBLE);
5167                 contentView.setTextViewText(p.mTextViewId, processTextSpans(p.text));
5168                 setTextViewColorSecondary(contentView, p.mTextViewId, p);
5169                 hasSecondLine = true;
5170             } else if (p.mTextViewId != R.id.text) {
5171                 // This alternate text view ID is not cleared by resetStandardTemplate
5172                 contentView.setViewVisibility(p.mTextViewId, View.GONE);
5173                 contentView.setTextViewText(p.mTextViewId, null);
5174             }
5175             setHeaderlessVerticalMargins(contentView, p, hasSecondLine);
5176 
5177             return contentView;
5178         }
5179 
setHeaderlessVerticalMargins(RemoteViews contentView, StandardTemplateParams p, boolean hasSecondLine)5180         private static void setHeaderlessVerticalMargins(RemoteViews contentView,
5181                 StandardTemplateParams p, boolean hasSecondLine) {
5182             if (!p.mHeaderless) {
5183                 return;
5184             }
5185             int marginDimen = hasSecondLine
5186                     ? R.dimen.notification_headerless_margin_twoline
5187                     : R.dimen.notification_headerless_margin_oneline;
5188             contentView.setViewLayoutMarginDimen(R.id.notification_headerless_view_column,
5189                     RemoteViews.MARGIN_TOP, marginDimen);
5190             contentView.setViewLayoutMarginDimen(R.id.notification_headerless_view_column,
5191                     RemoteViews.MARGIN_BOTTOM, marginDimen);
5192         }
5193 
processTextSpans(CharSequence text)5194         private CharSequence processTextSpans(CharSequence text) {
5195             if (mInNightMode) {
5196                 return ContrastColorUtil.clearColorSpans(text);
5197             }
5198             return text;
5199         }
5200 
setTextViewColorPrimary(RemoteViews contentView, @IdRes int id, StandardTemplateParams p)5201         private void setTextViewColorPrimary(RemoteViews contentView, @IdRes int id,
5202                 StandardTemplateParams p) {
5203             contentView.setTextColor(id, getPrimaryTextColor(p));
5204         }
5205 
5206         /**
5207          * @param p the template params to inflate this with
5208          * @return the primary text color
5209          * @hide
5210          */
5211         @VisibleForTesting
getPrimaryTextColor(StandardTemplateParams p)5212         public @ColorInt int getPrimaryTextColor(StandardTemplateParams p) {
5213             return getColors(p).getPrimaryTextColor();
5214         }
5215 
5216         /**
5217          * @param p the template params to inflate this with
5218          * @return the secondary text color
5219          * @hide
5220          */
5221         @VisibleForTesting
getSecondaryTextColor(StandardTemplateParams p)5222         public @ColorInt int getSecondaryTextColor(StandardTemplateParams p) {
5223             return getColors(p).getSecondaryTextColor();
5224         }
5225 
setTextViewColorSecondary(RemoteViews contentView, @IdRes int id, StandardTemplateParams p)5226         private void setTextViewColorSecondary(RemoteViews contentView, @IdRes int id,
5227                 StandardTemplateParams p) {
5228             contentView.setTextColor(id, getSecondaryTextColor(p));
5229         }
5230 
getColors(StandardTemplateParams p)5231         private Colors getColors(StandardTemplateParams p) {
5232             mColors.resolvePalette(mContext, mN.color, isBackgroundColorized(p), mInNightMode);
5233             return mColors;
5234         }
5235 
updateBackgroundColor(RemoteViews contentView, StandardTemplateParams p)5236         private void updateBackgroundColor(RemoteViews contentView,
5237                 StandardTemplateParams p) {
5238             if (isBackgroundColorized(p)) {
5239                 contentView.setInt(R.id.status_bar_latest_event_content, "setBackgroundColor",
5240                         getBackgroundColor(p));
5241             } else {
5242                 // Clear it!
5243                 contentView.setInt(R.id.status_bar_latest_event_content, "setBackgroundResource",
5244                         0);
5245             }
5246         }
5247 
handleProgressBar(RemoteViews contentView, Bundle ex, StandardTemplateParams p)5248         private boolean handleProgressBar(RemoteViews contentView, Bundle ex,
5249                 StandardTemplateParams p) {
5250             final int max = ex.getInt(EXTRA_PROGRESS_MAX, 0);
5251             final int progress = ex.getInt(EXTRA_PROGRESS, 0);
5252             final boolean ind = ex.getBoolean(EXTRA_PROGRESS_INDETERMINATE);
5253             if (!p.mHideProgress && (max != 0 || ind)) {
5254                 contentView.setViewVisibility(com.android.internal.R.id.progress, View.VISIBLE);
5255                 contentView.setProgressBar(R.id.progress, max, progress, ind);
5256                 contentView.setProgressBackgroundTintList(R.id.progress,
5257                         mContext.getColorStateList(R.color.notification_progress_background_color));
5258                 ColorStateList progressTint = ColorStateList.valueOf(getPrimaryAccentColor(p));
5259                 contentView.setProgressTintList(R.id.progress, progressTint);
5260                 contentView.setProgressIndeterminateTintList(R.id.progress, progressTint);
5261                 return true;
5262             } else {
5263                 contentView.setViewVisibility(R.id.progress, View.GONE);
5264                 return false;
5265             }
5266         }
5267 
bindLargeIconAndApplyMargin(RemoteViews contentView, @NonNull StandardTemplateParams p, @Nullable TemplateBindResult result)5268         private void bindLargeIconAndApplyMargin(RemoteViews contentView,
5269                 @NonNull StandardTemplateParams p,
5270                 @Nullable TemplateBindResult result) {
5271             if (result == null) {
5272                 result = new TemplateBindResult();
5273             }
5274             bindLargeIcon(contentView, p, result);
5275             if (!p.mHeaderless) {
5276                 // views in states with a header (big states)
5277                 result.mHeadingExtraMarginSet.applyToView(contentView, R.id.notification_header);
5278                 result.mTitleMarginSet.applyToView(contentView, R.id.title);
5279                 // If there is no title, the text (or big_text) needs to wrap around the image
5280                 result.mTitleMarginSet.applyToView(contentView, p.mTextViewId);
5281                 contentView.setInt(p.mTextViewId, "setNumIndentLines", p.hasTitle() ? 0 : 1);
5282             }
5283         }
5284 
5285         // This code is executed on behalf of other apps' notifications, sometimes even by 3p apps,
5286         // a use case that is not supported by the Compat Framework library.  Workarounds to resolve
5287         // the change's state in NotificationManagerService were very complex. These behavior
5288         // changes are entirely visual, and should otherwise be undetectable by apps.
5289         @SuppressWarnings("AndroidFrameworkCompatChange")
calculateRightIconDimens(Icon rightIcon, boolean isPromotedPicture, @NonNull TemplateBindResult result)5290         private void calculateRightIconDimens(Icon rightIcon, boolean isPromotedPicture,
5291                 @NonNull TemplateBindResult result) {
5292             final Resources resources = mContext.getResources();
5293             final float density = resources.getDisplayMetrics().density;
5294             final float iconMarginDp = resources.getDimension(
5295                     R.dimen.notification_right_icon_content_margin) / density;
5296             final float contentMarginDp = resources.getDimension(
5297                     R.dimen.notification_content_margin_end) / density;
5298             final float expanderSizeDp = resources.getDimension(
5299                     R.dimen.notification_header_expand_icon_size) / density - contentMarginDp;
5300             final float viewHeightDp = resources.getDimension(
5301                     R.dimen.notification_right_icon_size) / density;
5302             float viewWidthDp = viewHeightDp;  // icons are 1:1 by default
5303             if (rightIcon != null && (isPromotedPicture
5304                     || mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.S)) {
5305                 Drawable drawable = rightIcon.loadDrawable(mContext);
5306                 if (drawable != null) {
5307                     int iconWidth = drawable.getIntrinsicWidth();
5308                     int iconHeight = drawable.getIntrinsicHeight();
5309                     if (iconWidth > iconHeight && iconHeight > 0) {
5310                         final float maxViewWidthDp = viewHeightDp * MAX_LARGE_ICON_ASPECT_RATIO;
5311                         viewWidthDp = Math.min(viewHeightDp * iconWidth / iconHeight,
5312                                 maxViewWidthDp);
5313                     }
5314                 }
5315             }
5316             final float extraMarginEndDpIfVisible = viewWidthDp + iconMarginDp;
5317             result.setRightIconState(rightIcon != null /* visible */, viewWidthDp,
5318                     viewHeightDp, extraMarginEndDpIfVisible, expanderSizeDp);
5319         }
5320 
5321         /**
5322          * Bind the large icon.
5323          */
bindLargeIcon(RemoteViews contentView, @NonNull StandardTemplateParams p, @NonNull TemplateBindResult result)5324         private void bindLargeIcon(RemoteViews contentView, @NonNull StandardTemplateParams p,
5325                 @NonNull TemplateBindResult result) {
5326             if (mN.mLargeIcon == null && mN.largeIcon != null) {
5327                 mN.mLargeIcon = Icon.createWithBitmap(mN.largeIcon);
5328             }
5329 
5330             // Determine the left and right icons
5331             Icon leftIcon = p.mHideLeftIcon ? null : mN.mLargeIcon;
5332             Icon rightIcon = p.mHideRightIcon ? null
5333                     : (p.mPromotedPicture != null ? p.mPromotedPicture : mN.mLargeIcon);
5334 
5335             // Apply the left icon (without duplicating the bitmap)
5336             if (leftIcon != rightIcon || leftIcon == null) {
5337                 // If the leftIcon is explicitly hidden or different from the rightIcon, then set it
5338                 // explicitly and make sure it won't take the right_icon drawable.
5339                 contentView.setImageViewIcon(R.id.left_icon, leftIcon);
5340                 contentView.setIntTag(R.id.left_icon, R.id.tag_uses_right_icon_drawable, 0);
5341             } else {
5342                 // If the leftIcon equals the rightIcon, just set the flag to use the right_icon
5343                 // drawable.  This avoids the view having two copies of the same bitmap.
5344                 contentView.setIntTag(R.id.left_icon, R.id.tag_uses_right_icon_drawable, 1);
5345             }
5346 
5347             // Always calculate dimens to populate `result` for the GONE case
5348             boolean isPromotedPicture = p.mPromotedPicture != null;
5349             calculateRightIconDimens(rightIcon, isPromotedPicture, result);
5350 
5351             // Bind the right icon
5352             if (rightIcon != null) {
5353                 contentView.setViewLayoutWidth(R.id.right_icon,
5354                         result.mRightIconWidthDp, TypedValue.COMPLEX_UNIT_DIP);
5355                 contentView.setViewLayoutHeight(R.id.right_icon,
5356                         result.mRightIconHeightDp, TypedValue.COMPLEX_UNIT_DIP);
5357                 contentView.setViewVisibility(R.id.right_icon, View.VISIBLE);
5358                 contentView.setImageViewIcon(R.id.right_icon, rightIcon);
5359                 contentView.setIntTag(R.id.right_icon, R.id.tag_keep_when_showing_left_icon,
5360                         isPromotedPicture ? 1 : 0);
5361                 processLargeLegacyIcon(rightIcon, contentView, p);
5362             } else {
5363                 // The "reset" doesn't clear the drawable, so we do it here.  This clear is
5364                 // important because the presence of a drawable in this view (regardless of the
5365                 // visibility) is used by NotificationGroupingUtil to set the visibility.
5366                 contentView.setImageViewIcon(R.id.right_icon, null);
5367                 contentView.setIntTag(R.id.right_icon, R.id.tag_keep_when_showing_left_icon, 0);
5368             }
5369         }
5370 
bindNotificationHeader(RemoteViews contentView, StandardTemplateParams p)5371         private void bindNotificationHeader(RemoteViews contentView, StandardTemplateParams p) {
5372             bindSmallIcon(contentView, p);
5373             // Populate text left-to-right so that separators are only shown between strings
5374             boolean hasTextToLeft = bindHeaderAppName(contentView, p, false /* force */);
5375             hasTextToLeft |= bindHeaderTextSecondary(contentView, p, hasTextToLeft);
5376             hasTextToLeft |= bindHeaderText(contentView, p, hasTextToLeft);
5377             if (!hasTextToLeft) {
5378                 // If there's still no text, force add the app name so there is some text.
5379                 hasTextToLeft |= bindHeaderAppName(contentView, p, true /* force */);
5380             }
5381             bindHeaderChronometerAndTime(contentView, p, hasTextToLeft);
5382             bindPhishingAlertIcon(contentView, p);
5383             bindProfileBadge(contentView, p);
5384             bindAlertedIcon(contentView, p);
5385             bindExpandButton(contentView, p);
5386             mN.mUsesStandardHeader = true;
5387         }
5388 
bindExpandButton(RemoteViews contentView, StandardTemplateParams p)5389         private void bindExpandButton(RemoteViews contentView, StandardTemplateParams p) {
5390             // set default colors
5391             int bgColor = getBackgroundColor(p);
5392             int pillColor = Colors.flattenAlpha(getColors(p).getProtectionColor(), bgColor);
5393             int textColor = Colors.flattenAlpha(getPrimaryTextColor(p), pillColor);
5394             contentView.setInt(R.id.expand_button, "setDefaultTextColor", textColor);
5395             contentView.setInt(R.id.expand_button, "setDefaultPillColor", pillColor);
5396             // Use different highlighted colors for conversations' unread count
5397             if (p.mHighlightExpander) {
5398                 pillColor = Colors.flattenAlpha(getColors(p).getTertiaryAccentColor(), bgColor);
5399                 textColor = Colors.flattenAlpha(getColors(p).getOnAccentTextColor(), pillColor);
5400             }
5401             contentView.setInt(R.id.expand_button, "setHighlightTextColor", textColor);
5402             contentView.setInt(R.id.expand_button, "setHighlightPillColor", pillColor);
5403         }
5404 
bindHeaderChronometerAndTime(RemoteViews contentView, StandardTemplateParams p, boolean hasTextToLeft)5405         private void bindHeaderChronometerAndTime(RemoteViews contentView,
5406                 StandardTemplateParams p, boolean hasTextToLeft) {
5407             if (!p.mHideTime && showsTimeOrChronometer()) {
5408                 if (hasTextToLeft) {
5409                     contentView.setViewVisibility(R.id.time_divider, View.VISIBLE);
5410                     setTextViewColorSecondary(contentView, R.id.time_divider, p);
5411                 }
5412                 if (mN.extras.getBoolean(EXTRA_SHOW_CHRONOMETER)) {
5413                     contentView.setViewVisibility(R.id.chronometer, View.VISIBLE);
5414                     contentView.setLong(R.id.chronometer, "setBase",
5415                             mN.when + (SystemClock.elapsedRealtime() - System.currentTimeMillis()));
5416                     contentView.setBoolean(R.id.chronometer, "setStarted", true);
5417                     boolean countsDown = mN.extras.getBoolean(EXTRA_CHRONOMETER_COUNT_DOWN);
5418                     contentView.setChronometerCountDown(R.id.chronometer, countsDown);
5419                     setTextViewColorSecondary(contentView, R.id.chronometer, p);
5420                 } else {
5421                     contentView.setViewVisibility(R.id.time, View.VISIBLE);
5422                     contentView.setLong(R.id.time, "setTime", mN.when);
5423                     setTextViewColorSecondary(contentView, R.id.time, p);
5424                 }
5425             } else {
5426                 // We still want a time to be set but gone, such that we can show and hide it
5427                 // on demand in case it's a child notification without anything in the header
5428                 contentView.setLong(R.id.time, "setTime", mN.when != 0 ? mN.when : mN.creationTime);
5429                 setTextViewColorSecondary(contentView, R.id.time, p);
5430             }
5431         }
5432 
5433         /**
5434          * @return true if the header text will be visible
5435          */
bindHeaderText(RemoteViews contentView, StandardTemplateParams p, boolean hasTextToLeft)5436         private boolean bindHeaderText(RemoteViews contentView, StandardTemplateParams p,
5437                 boolean hasTextToLeft) {
5438             if (p.mHideSubText) {
5439                 return false;
5440             }
5441             CharSequence summaryText = p.summaryText;
5442             if (summaryText == null && mStyle != null && mStyle.mSummaryTextSet
5443                     && mStyle.hasSummaryInHeader()) {
5444                 summaryText = mStyle.mSummaryText;
5445             }
5446             if (summaryText == null
5447                     && mContext.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N
5448                     && mN.extras.getCharSequence(EXTRA_INFO_TEXT) != null) {
5449                 summaryText = mN.extras.getCharSequence(EXTRA_INFO_TEXT);
5450             }
5451             if (!TextUtils.isEmpty(summaryText)) {
5452                 // TODO: Remove the span entirely to only have the string with propper formating.
5453                 contentView.setTextViewText(R.id.header_text, processTextSpans(
5454                         processLegacyText(summaryText)));
5455                 setTextViewColorSecondary(contentView, R.id.header_text, p);
5456                 contentView.setViewVisibility(R.id.header_text, View.VISIBLE);
5457                 if (hasTextToLeft) {
5458                     contentView.setViewVisibility(R.id.header_text_divider, View.VISIBLE);
5459                     setTextViewColorSecondary(contentView, R.id.header_text_divider, p);
5460                 }
5461                 return true;
5462             }
5463             return false;
5464         }
5465 
5466         /**
5467          * @return true if the secondary header text will be visible
5468          */
bindHeaderTextSecondary(RemoteViews contentView, StandardTemplateParams p, boolean hasTextToLeft)5469         private boolean bindHeaderTextSecondary(RemoteViews contentView, StandardTemplateParams p,
5470                 boolean hasTextToLeft) {
5471             if (p.mHideSubText) {
5472                 return false;
5473             }
5474             if (!TextUtils.isEmpty(p.headerTextSecondary)) {
5475                 contentView.setTextViewText(R.id.header_text_secondary, processTextSpans(
5476                         processLegacyText(p.headerTextSecondary)));
5477                 setTextViewColorSecondary(contentView, R.id.header_text_secondary, p);
5478                 contentView.setViewVisibility(R.id.header_text_secondary, View.VISIBLE);
5479                 if (hasTextToLeft) {
5480                     contentView.setViewVisibility(R.id.header_text_secondary_divider, View.VISIBLE);
5481                     setTextViewColorSecondary(contentView, R.id.header_text_secondary_divider, p);
5482                 }
5483                 return true;
5484             }
5485             return false;
5486         }
5487 
5488         /**
5489          * @hide
5490          */
5491         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
loadHeaderAppName()5492         public String loadHeaderAppName() {
5493             CharSequence name = null;
5494             final PackageManager pm = mContext.getPackageManager();
5495             if (mN.extras.containsKey(EXTRA_SUBSTITUTE_APP_NAME)) {
5496                 // only system packages which lump together a bunch of unrelated stuff
5497                 // may substitute a different name to make the purpose of the
5498                 // notification more clear. the correct package label should always
5499                 // be accessible via SystemUI.
5500                 final String pkg = mContext.getPackageName();
5501                 final String subName = mN.extras.getString(EXTRA_SUBSTITUTE_APP_NAME);
5502                 if (PackageManager.PERMISSION_GRANTED == pm.checkPermission(
5503                         android.Manifest.permission.SUBSTITUTE_NOTIFICATION_APP_NAME, pkg)) {
5504                     name = subName;
5505                 } else {
5506                     Log.w(TAG, "warning: pkg "
5507                             + pkg + " attempting to substitute app name '" + subName
5508                             + "' without holding perm "
5509                             + android.Manifest.permission.SUBSTITUTE_NOTIFICATION_APP_NAME);
5510                 }
5511             }
5512             if (TextUtils.isEmpty(name)) {
5513                 name = pm.getApplicationLabel(mContext.getApplicationInfo());
5514             }
5515             if (TextUtils.isEmpty(name)) {
5516                 // still nothing?
5517                 return null;
5518             }
5519 
5520             return String.valueOf(name);
5521         }
5522 
5523         /**
5524          * @return true if the app name will be visible
5525          */
bindHeaderAppName(RemoteViews contentView, StandardTemplateParams p, boolean force)5526         private boolean bindHeaderAppName(RemoteViews contentView, StandardTemplateParams p,
5527                 boolean force) {
5528             if (p.mViewType == StandardTemplateParams.VIEW_TYPE_MINIMIZED && !force) {
5529                 // unless the force flag is set, don't show the app name in the minimized state.
5530                 return false;
5531             }
5532             if (p.mHeaderless && p.hasTitle()) {
5533                 // the headerless template will have the TITLE in this position; return true to
5534                 // keep the divider visible between that title and the next text element.
5535                 return true;
5536             }
5537             if (p.mHideAppName) {
5538                 // The app name is being hidden, so we definitely want to return here.
5539                 // Assume that there is a title which will replace it in the header.
5540                 return p.hasTitle();
5541             }
5542             contentView.setViewVisibility(R.id.app_name_text, View.VISIBLE);
5543             contentView.setTextViewText(R.id.app_name_text, loadHeaderAppName());
5544             contentView.setTextColor(R.id.app_name_text, getSecondaryTextColor(p));
5545             return true;
5546         }
5547 
5548         /**
5549          * Determines if the notification should be colorized *for the purposes of applying colors*.
5550          * If this is the minimized view of a colorized notification, or if the app did not provide
5551          * a color to colorize with, this will return false so that internal coloring logic can
5552          * still render the notification normally.
5553          */
isBackgroundColorized(StandardTemplateParams p)5554         private boolean isBackgroundColorized(StandardTemplateParams p) {
5555             return p.allowColorization && mN.color != COLOR_DEFAULT && mN.isColorized();
5556         }
5557 
isCallActionColorCustomizable()5558         private boolean isCallActionColorCustomizable() {
5559             // NOTE: this doesn't need to check StandardTemplateParams.allowColorization because
5560             //  that is only used for disallowing colorization of headers for the minimized state,
5561             //  and neither of those conditions applies when showing actions.
5562             //  Not requiring StandardTemplateParams as an argument simplifies the creation process.
5563             return mN.color != COLOR_DEFAULT && mN.isColorized()
5564                     && mContext.getResources().getBoolean(
5565                     R.bool.config_callNotificationActionColorsRequireColorized);
5566         }
5567 
bindSmallIcon(RemoteViews contentView, StandardTemplateParams p)5568         private void bindSmallIcon(RemoteViews contentView, StandardTemplateParams p) {
5569             if (mN.mSmallIcon == null && mN.icon != 0) {
5570                 mN.mSmallIcon = Icon.createWithResource(mContext, mN.icon);
5571             }
5572             contentView.setImageViewIcon(R.id.icon, mN.mSmallIcon);
5573             contentView.setInt(R.id.icon, "setImageLevel", mN.iconLevel);
5574             processSmallIconColor(mN.mSmallIcon, contentView, p);
5575         }
5576 
5577         /**
5578          * @return true if the built notification will show the time or the chronometer; false
5579          *         otherwise
5580          */
showsTimeOrChronometer()5581         private boolean showsTimeOrChronometer() {
5582             return mN.showsTime() || mN.showsChronometer();
5583         }
5584 
resetStandardTemplateWithActions(RemoteViews big)5585         private void resetStandardTemplateWithActions(RemoteViews big) {
5586             // actions_container is only reset when there are no actions to avoid focus issues with
5587             // remote inputs.
5588             big.setViewVisibility(R.id.actions, View.GONE);
5589             big.removeAllViews(R.id.actions);
5590 
5591             big.setViewVisibility(R.id.notification_material_reply_container, View.GONE);
5592             big.setTextViewText(R.id.notification_material_reply_text_1, null);
5593             big.setViewVisibility(R.id.notification_material_reply_text_1_container, View.GONE);
5594             big.setViewVisibility(R.id.notification_material_reply_progress, View.GONE);
5595 
5596             big.setViewVisibility(R.id.notification_material_reply_text_2, View.GONE);
5597             big.setTextViewText(R.id.notification_material_reply_text_2, null);
5598             big.setViewVisibility(R.id.notification_material_reply_text_3, View.GONE);
5599             big.setTextViewText(R.id.notification_material_reply_text_3, null);
5600 
5601             // This may get erased by bindSnoozeAction
5602             big.setViewLayoutMarginDimen(R.id.notification_action_list_margin_target,
5603                     RemoteViews.MARGIN_BOTTOM, R.dimen.notification_content_margin);
5604         }
5605 
bindSnoozeAction(RemoteViews big, StandardTemplateParams p)5606         private void bindSnoozeAction(RemoteViews big, StandardTemplateParams p) {
5607             boolean hideSnoozeButton = mN.isForegroundService() || mN.fullScreenIntent != null
5608                     || isBackgroundColorized(p)
5609                     || p.mViewType != StandardTemplateParams.VIEW_TYPE_BIG;
5610             big.setBoolean(R.id.snooze_button, "setEnabled", !hideSnoozeButton);
5611             if (hideSnoozeButton) {
5612                 // Only hide; NotificationContentView will show it when it adds the click listener
5613                 big.setViewVisibility(R.id.snooze_button, View.GONE);
5614             }
5615 
5616             final boolean snoozeEnabled = !hideSnoozeButton
5617                     && mContext.getContentResolver() != null
5618                     && isSnoozeSettingEnabled();
5619             if (snoozeEnabled) {
5620                 big.setViewLayoutMarginDimen(R.id.notification_action_list_margin_target,
5621                         RemoteViews.MARGIN_BOTTOM, 0);
5622             }
5623         }
5624 
isSnoozeSettingEnabled()5625         private boolean isSnoozeSettingEnabled() {
5626             try {
5627                 return Settings.Secure.getInt(mContext.getContentResolver(),
5628                     Settings.Secure.SHOW_NOTIFICATION_SNOOZE, 0) == 1;
5629             } catch (SecurityException ex) {
5630                 // Most 3p apps can't access this snooze setting, so their NotificationListeners
5631                 // would be unable to create notification views if we propagated this exception.
5632                 return false;
5633             }
5634         }
5635 
5636         /**
5637          * Returns the actions that are not contextual.
5638          */
getNonContextualActions()5639         private @NonNull List<Notification.Action> getNonContextualActions() {
5640             if (mActions == null) return Collections.emptyList();
5641             List<Notification.Action> standardActions = new ArrayList<>();
5642             for (Notification.Action action : mActions) {
5643                 if (!action.isContextual()) {
5644                     standardActions.add(action);
5645                 }
5646             }
5647             return standardActions;
5648         }
5649 
applyStandardTemplateWithActions(int layoutId, StandardTemplateParams p, TemplateBindResult result)5650         private RemoteViews applyStandardTemplateWithActions(int layoutId,
5651                 StandardTemplateParams p, TemplateBindResult result) {
5652             RemoteViews big = applyStandardTemplate(layoutId, p, result);
5653 
5654             resetStandardTemplateWithActions(big);
5655             bindSnoozeAction(big, p);
5656             // color the snooze and bubble actions with the theme color
5657             ColorStateList actionColor = ColorStateList.valueOf(getStandardActionColor(p));
5658             big.setColorStateList(R.id.snooze_button, "setImageTintList", actionColor);
5659             big.setColorStateList(R.id.bubble_button, "setImageTintList", actionColor);
5660 
5661             boolean validRemoteInput = false;
5662 
5663             // In the UI, contextual actions appear separately from the standard actions, so we
5664             // filter them out here.
5665             List<Notification.Action> nonContextualActions = getNonContextualActions();
5666 
5667             int numActions = Math.min(nonContextualActions.size(), MAX_ACTION_BUTTONS);
5668             boolean emphazisedMode = mN.fullScreenIntent != null || p.mCallStyleActions;
5669             if (p.mCallStyleActions) {
5670                 // Clear view padding to allow buttons to start on the left edge.
5671                 // This must be done before 'setEmphasizedMode' which sets top/bottom margins.
5672                 big.setViewPadding(R.id.actions, 0, 0, 0, 0);
5673                 // Add an optional indent that will make buttons start at the correct column when
5674                 // there is enough space to do so (and fall back to the left edge if not).
5675                 big.setInt(R.id.actions, "setCollapsibleIndentDimen",
5676                         R.dimen.call_notification_collapsible_indent);
5677             }
5678             big.setBoolean(R.id.actions, "setEmphasizedMode", emphazisedMode);
5679             if (numActions > 0 && !p.mHideActions) {
5680                 big.setViewVisibility(R.id.actions_container, View.VISIBLE);
5681                 big.setViewVisibility(R.id.actions, View.VISIBLE);
5682                 big.setViewLayoutMarginDimen(R.id.notification_action_list_margin_target,
5683                         RemoteViews.MARGIN_BOTTOM, 0);
5684                 for (int i = 0; i < numActions; i++) {
5685                     Action action = nonContextualActions.get(i);
5686 
5687                     boolean actionHasValidInput = hasValidRemoteInput(action);
5688                     validRemoteInput |= actionHasValidInput;
5689 
5690                     final RemoteViews button = generateActionButton(action, emphazisedMode, p);
5691                     if (actionHasValidInput && !emphazisedMode) {
5692                         // Clear the drawable
5693                         button.setInt(R.id.action0, "setBackgroundResource", 0);
5694                     }
5695                     if (emphazisedMode && i > 0) {
5696                         // Clear start margin from non-first buttons to reduce the gap between them.
5697                         //  (8dp remaining gap is from all buttons' standard 4dp inset).
5698                         button.setViewLayoutMarginDimen(R.id.action0, RemoteViews.MARGIN_START, 0);
5699                     }
5700                     big.addView(R.id.actions, button);
5701                 }
5702             } else {
5703                 big.setViewVisibility(R.id.actions_container, View.GONE);
5704             }
5705 
5706             RemoteInputHistoryItem[] replyText = getParcelableArrayFromBundle(
5707                     mN.extras, EXTRA_REMOTE_INPUT_HISTORY_ITEMS, RemoteInputHistoryItem.class);
5708             if (validRemoteInput && replyText != null && replyText.length > 0
5709                     && !TextUtils.isEmpty(replyText[0].getText())
5710                     && p.maxRemoteInputHistory > 0) {
5711                 boolean showSpinner = mN.extras.getBoolean(EXTRA_SHOW_REMOTE_INPUT_SPINNER);
5712                 big.setViewVisibility(R.id.notification_material_reply_container, View.VISIBLE);
5713                 big.setViewVisibility(R.id.notification_material_reply_text_1_container,
5714                         View.VISIBLE);
5715                 big.setTextViewText(R.id.notification_material_reply_text_1,
5716                         processTextSpans(replyText[0].getText()));
5717                 setTextViewColorSecondary(big, R.id.notification_material_reply_text_1, p);
5718                 big.setViewVisibility(R.id.notification_material_reply_progress,
5719                         showSpinner ? View.VISIBLE : View.GONE);
5720                 big.setProgressIndeterminateTintList(
5721                         R.id.notification_material_reply_progress,
5722                         ColorStateList.valueOf(getPrimaryAccentColor(p)));
5723 
5724                 if (replyText.length > 1 && !TextUtils.isEmpty(replyText[1].getText())
5725                         && p.maxRemoteInputHistory > 1) {
5726                     big.setViewVisibility(R.id.notification_material_reply_text_2, View.VISIBLE);
5727                     big.setTextViewText(R.id.notification_material_reply_text_2,
5728                             processTextSpans(replyText[1].getText()));
5729                     setTextViewColorSecondary(big, R.id.notification_material_reply_text_2, p);
5730 
5731                     if (replyText.length > 2 && !TextUtils.isEmpty(replyText[2].getText())
5732                             && p.maxRemoteInputHistory > 2) {
5733                         big.setViewVisibility(
5734                                 R.id.notification_material_reply_text_3, View.VISIBLE);
5735                         big.setTextViewText(R.id.notification_material_reply_text_3,
5736                                 processTextSpans(replyText[2].getText()));
5737                         setTextViewColorSecondary(big, R.id.notification_material_reply_text_3, p);
5738                     }
5739                 }
5740             }
5741 
5742             return big;
5743         }
5744 
hasValidRemoteInput(Action action)5745         private boolean hasValidRemoteInput(Action action) {
5746             if (TextUtils.isEmpty(action.title) || action.actionIntent == null) {
5747                 // Weird actions
5748                 return false;
5749             }
5750 
5751             RemoteInput[] remoteInputs = action.getRemoteInputs();
5752             if (remoteInputs == null) {
5753                 return false;
5754             }
5755 
5756             for (RemoteInput r : remoteInputs) {
5757                 CharSequence[] choices = r.getChoices();
5758                 if (r.getAllowFreeFormInput() || (choices != null && choices.length != 0)) {
5759                     return true;
5760                 }
5761             }
5762             return false;
5763         }
5764 
5765         /**
5766          * Construct a RemoteViews for the final 1U notification layout. In order:
5767          *   1. Custom contentView from the caller
5768          *   2. Style's proposed content view
5769          *   3. Standard template view
5770          */
createContentView()5771         public RemoteViews createContentView() {
5772             return createContentView(false /* increasedheight */ );
5773         }
5774 
5775         // This code is executed on behalf of other apps' notifications, sometimes even by 3p apps,
5776         // a use case that is not supported by the Compat Framework library.  Workarounds to resolve
5777         // the change's state in NotificationManagerService were very complex. While it's possible
5778         // apps can detect the change, it's most likely that the changes will simply result in
5779         // visual regressions.
5780         @SuppressWarnings("AndroidFrameworkCompatChange")
fullyCustomViewRequiresDecoration(boolean fromStyle)5781         private boolean fullyCustomViewRequiresDecoration(boolean fromStyle) {
5782             // Custom views which come from a platform style class are safe, and thus do not need to
5783             // be wrapped.  Any subclass of those styles has the opportunity to make arbitrary
5784             // changes to the RemoteViews, and thus can't be trusted as a fully vetted view.
5785             if (fromStyle && PLATFORM_STYLE_CLASSES.contains(mStyle.getClass())) {
5786                 return false;
5787             }
5788             return mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.S;
5789         }
5790 
minimallyDecoratedContentView(@onNull RemoteViews customContent)5791         private RemoteViews minimallyDecoratedContentView(@NonNull RemoteViews customContent) {
5792             StandardTemplateParams p = mParams.reset()
5793                     .viewType(StandardTemplateParams.VIEW_TYPE_NORMAL)
5794                     .decorationType(StandardTemplateParams.DECORATION_MINIMAL)
5795                     .fillTextsFrom(this);
5796             TemplateBindResult result = new TemplateBindResult();
5797             RemoteViews standard = applyStandardTemplate(getBaseLayoutResource(), p, result);
5798             buildCustomContentIntoTemplate(mContext, standard, customContent,
5799                     p, result);
5800             return standard;
5801         }
5802 
minimallyDecoratedBigContentView(@onNull RemoteViews customContent)5803         private RemoteViews minimallyDecoratedBigContentView(@NonNull RemoteViews customContent) {
5804             StandardTemplateParams p = mParams.reset()
5805                     .viewType(StandardTemplateParams.VIEW_TYPE_BIG)
5806                     .decorationType(StandardTemplateParams.DECORATION_MINIMAL)
5807                     .fillTextsFrom(this);
5808             TemplateBindResult result = new TemplateBindResult();
5809             RemoteViews standard = applyStandardTemplateWithActions(getBigBaseLayoutResource(),
5810                     p, result);
5811             buildCustomContentIntoTemplate(mContext, standard, customContent,
5812                     p, result);
5813             makeHeaderExpanded(standard);
5814             return standard;
5815         }
5816 
minimallyDecoratedHeadsUpContentView( @onNull RemoteViews customContent)5817         private RemoteViews minimallyDecoratedHeadsUpContentView(
5818                 @NonNull RemoteViews customContent) {
5819             StandardTemplateParams p = mParams.reset()
5820                     .viewType(StandardTemplateParams.VIEW_TYPE_HEADS_UP)
5821                     .decorationType(StandardTemplateParams.DECORATION_MINIMAL)
5822                     .fillTextsFrom(this);
5823             TemplateBindResult result = new TemplateBindResult();
5824             RemoteViews standard = applyStandardTemplateWithActions(getHeadsUpBaseLayoutResource(),
5825                     p, result);
5826             buildCustomContentIntoTemplate(mContext, standard, customContent,
5827                     p, result);
5828             return standard;
5829         }
5830 
5831         /**
5832          * Construct a RemoteViews for the smaller content view.
5833          *
5834          *   @param increasedHeight true if this layout be created with an increased height. Some
5835          *   styles may support showing more then just that basic 1U size
5836          *   and the system may decide to render important notifications
5837          *   slightly bigger even when collapsed.
5838          *
5839          *   @hide
5840          */
createContentView(boolean increasedHeight)5841         public RemoteViews createContentView(boolean increasedHeight) {
5842             if (useExistingRemoteView(mN.contentView)) {
5843                 return fullyCustomViewRequiresDecoration(false /* fromStyle */)
5844                         ? minimallyDecoratedContentView(mN.contentView) : mN.contentView;
5845             } else if (mStyle != null) {
5846                 final RemoteViews styleView = mStyle.makeContentView(increasedHeight);
5847                 if (styleView != null) {
5848                     return fullyCustomViewRequiresDecoration(true /* fromStyle */)
5849                             ? minimallyDecoratedContentView(styleView) : styleView;
5850                 }
5851             }
5852             StandardTemplateParams p = mParams.reset()
5853                     .viewType(StandardTemplateParams.VIEW_TYPE_NORMAL)
5854                     .fillTextsFrom(this);
5855             return applyStandardTemplate(getBaseLayoutResource(), p, null /* result */);
5856         }
5857 
useExistingRemoteView(RemoteViews customContent)5858         private boolean useExistingRemoteView(RemoteViews customContent) {
5859             if (customContent == null) {
5860                 return false;
5861             }
5862             if (styleDisplaysCustomViewInline()) {
5863                 // the provided custom view is intended to be wrapped by the style.
5864                 return false;
5865             }
5866             if (fullyCustomViewRequiresDecoration(false)
5867                     && STANDARD_LAYOUTS.contains(customContent.getLayoutId())) {
5868                 // If the app's custom views are objects returned from Builder.create*ContentView()
5869                 // then the app is most likely attempting to spoof the user.  Even if they are not,
5870                 // the result would be broken (b/189189308) so we will ignore it.
5871                 Log.w(TAG, "For apps targeting S, a custom content view that is a modified "
5872                         + "version of any standard layout is disallowed.");
5873                 return false;
5874             }
5875             return true;
5876         }
5877 
5878         /**
5879          * Construct a RemoteViews for the final big notification layout.
5880          */
createBigContentView()5881         public RemoteViews createBigContentView() {
5882             RemoteViews result = null;
5883             if (useExistingRemoteView(mN.bigContentView)) {
5884                 return fullyCustomViewRequiresDecoration(false /* fromStyle */)
5885                         ? minimallyDecoratedBigContentView(mN.bigContentView) : mN.bigContentView;
5886             }
5887             if (mStyle != null) {
5888                 result = mStyle.makeBigContentView();
5889                 if (fullyCustomViewRequiresDecoration(true /* fromStyle */)) {
5890                     result = minimallyDecoratedBigContentView(result);
5891                 }
5892             }
5893             if (result == null) {
5894                 if (bigContentViewRequired()) {
5895                     StandardTemplateParams p = mParams.reset()
5896                             .viewType(StandardTemplateParams.VIEW_TYPE_BIG)
5897                             .allowTextWithProgress(true)
5898                             .fillTextsFrom(this);
5899                     result = applyStandardTemplateWithActions(getBigBaseLayoutResource(), p,
5900                             null /* result */);
5901                 }
5902             }
5903             makeHeaderExpanded(result);
5904             return result;
5905         }
5906 
5907         // This code is executed on behalf of other apps' notifications, sometimes even by 3p apps,
5908         // a use case that is not supported by the Compat Framework library.  Workarounds to resolve
5909         // the change's state in NotificationManagerService were very complex. While it's possible
5910         // apps can detect the change, it's most likely that the changes will simply result in
5911         // visual regressions.
5912         @SuppressWarnings("AndroidFrameworkCompatChange")
bigContentViewRequired()5913         private boolean bigContentViewRequired() {
5914             if (mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.S) {
5915                 return true;
5916             }
5917             // Notifications with contentView and without a bigContentView, style, or actions would
5918             // not have an expanded state before S, so showing the standard template expanded state
5919             // usually looks wrong, so we keep it simple and don't show the expanded state.
5920             boolean exempt = mN.contentView != null && mN.bigContentView == null
5921                     && mStyle == null && mActions.size() == 0;
5922             return !exempt;
5923         }
5924 
5925         /**
5926          * Construct a RemoteViews for the final notification header only. This will not be
5927          * colorized.
5928          *
5929          * @hide
5930          */
makeNotificationGroupHeader()5931         public RemoteViews makeNotificationGroupHeader() {
5932             return makeNotificationHeader(mParams.reset()
5933                     .viewType(StandardTemplateParams.VIEW_TYPE_GROUP_HEADER)
5934                     .fillTextsFrom(this));
5935         }
5936 
5937         /**
5938          * Construct a RemoteViews for the final notification header only. This will not be
5939          * colorized.
5940          *
5941          * @param p the template params to inflate this with
5942          */
makeNotificationHeader(StandardTemplateParams p)5943         private RemoteViews makeNotificationHeader(StandardTemplateParams p) {
5944             // Headers on their own are never colorized
5945             p.disallowColorization();
5946             RemoteViews header = new BuilderRemoteViews(mContext.getApplicationInfo(),
5947                     R.layout.notification_template_header);
5948             resetNotificationHeader(header);
5949             bindNotificationHeader(header, p);
5950             return header;
5951         }
5952 
5953         /**
5954          * Construct a RemoteViews for the ambient version of the notification.
5955          *
5956          * @hide
5957          */
makeAmbientNotification()5958         public RemoteViews makeAmbientNotification() {
5959             RemoteViews headsUpContentView = createHeadsUpContentView(false /* increasedHeight */);
5960             if (headsUpContentView != null) {
5961                 return headsUpContentView;
5962             }
5963             return createContentView();
5964         }
5965 
5966         /**
5967          * Adapt the Notification header if this view is used as an expanded view.
5968          *
5969          * @hide
5970          */
makeHeaderExpanded(RemoteViews result)5971         public static void makeHeaderExpanded(RemoteViews result) {
5972             if (result != null) {
5973                 result.setBoolean(R.id.expand_button, "setExpanded", true);
5974             }
5975         }
5976 
5977         /**
5978          * Construct a RemoteViews for the final heads-up notification layout.
5979          *
5980          * @param increasedHeight true if this layout be created with an increased height. Some
5981          * styles may support showing more then just that basic 1U size
5982          * and the system may decide to render important notifications
5983          * slightly bigger even when collapsed.
5984          *
5985          * @hide
5986          */
createHeadsUpContentView(boolean increasedHeight)5987         public RemoteViews createHeadsUpContentView(boolean increasedHeight) {
5988             if (useExistingRemoteView(mN.headsUpContentView)) {
5989                 return fullyCustomViewRequiresDecoration(false /* fromStyle */)
5990                         ? minimallyDecoratedHeadsUpContentView(mN.headsUpContentView)
5991                         : mN.headsUpContentView;
5992             } else if (mStyle != null) {
5993                 final RemoteViews styleView = mStyle.makeHeadsUpContentView(increasedHeight);
5994                 if (styleView != null) {
5995                     return fullyCustomViewRequiresDecoration(true /* fromStyle */)
5996                             ? minimallyDecoratedHeadsUpContentView(styleView) : styleView;
5997                 }
5998             } else if (mActions.size() == 0) {
5999                 return null;
6000             }
6001 
6002             // We only want at most a single remote input history to be shown here, otherwise
6003             // the content would become squished.
6004             StandardTemplateParams p = mParams.reset()
6005                     .viewType(StandardTemplateParams.VIEW_TYPE_HEADS_UP)
6006                     .fillTextsFrom(this)
6007                     .setMaxRemoteInputHistory(1);
6008             return applyStandardTemplateWithActions(getHeadsUpBaseLayoutResource(), p,
6009                     null /* result */);
6010         }
6011 
6012         /**
6013          * Construct a RemoteViews for the final heads-up notification layout.
6014          */
createHeadsUpContentView()6015         public RemoteViews createHeadsUpContentView() {
6016             return createHeadsUpContentView(false /* useIncreasedHeight */);
6017         }
6018 
6019         /**
6020          * Construct a RemoteViews for the display in public contexts like on the lockscreen.
6021          *
6022          * @param isLowPriority is this notification low priority
6023          * @hide
6024          */
6025         @UnsupportedAppUsage
makePublicContentView(boolean isLowPriority)6026         public RemoteViews makePublicContentView(boolean isLowPriority) {
6027             if (mN.publicVersion != null) {
6028                 final Builder builder = recoverBuilder(mContext, mN.publicVersion);
6029                 return builder.createContentView();
6030             }
6031             Bundle savedBundle = mN.extras;
6032             Style style = mStyle;
6033             mStyle = null;
6034             Icon largeIcon = mN.mLargeIcon;
6035             mN.mLargeIcon = null;
6036             Bitmap largeIconLegacy = mN.largeIcon;
6037             mN.largeIcon = null;
6038             ArrayList<Action> actions = mActions;
6039             mActions = new ArrayList<>();
6040             Bundle publicExtras = new Bundle();
6041             publicExtras.putBoolean(EXTRA_SHOW_WHEN,
6042                     savedBundle.getBoolean(EXTRA_SHOW_WHEN));
6043             publicExtras.putBoolean(EXTRA_SHOW_CHRONOMETER,
6044                     savedBundle.getBoolean(EXTRA_SHOW_CHRONOMETER));
6045             publicExtras.putBoolean(EXTRA_CHRONOMETER_COUNT_DOWN,
6046                     savedBundle.getBoolean(EXTRA_CHRONOMETER_COUNT_DOWN));
6047             String appName = savedBundle.getString(EXTRA_SUBSTITUTE_APP_NAME);
6048             if (appName != null) {
6049                 publicExtras.putString(EXTRA_SUBSTITUTE_APP_NAME, appName);
6050             }
6051             mN.extras = publicExtras;
6052             RemoteViews view;
6053             StandardTemplateParams params = mParams.reset()
6054                     .viewType(StandardTemplateParams.VIEW_TYPE_PUBLIC)
6055                     .fillTextsFrom(this);
6056             if (isLowPriority) {
6057                 params.highlightExpander(false);
6058             }
6059             view = makeNotificationHeader(params);
6060             view.setBoolean(R.id.notification_header, "setExpandOnlyOnButton", true);
6061             mN.extras = savedBundle;
6062             mN.mLargeIcon = largeIcon;
6063             mN.largeIcon = largeIconLegacy;
6064             mActions = actions;
6065             mStyle = style;
6066             return view;
6067         }
6068 
6069         /**
6070          * Construct a content view for the display when low - priority
6071          *
6072          * @param useRegularSubtext uses the normal subtext set if there is one available. Otherwise
6073          *                          a new subtext is created consisting of the content of the
6074          *                          notification.
6075          * @hide
6076          */
makeLowPriorityContentView(boolean useRegularSubtext)6077         public RemoteViews makeLowPriorityContentView(boolean useRegularSubtext) {
6078             StandardTemplateParams p = mParams.reset()
6079                     .viewType(StandardTemplateParams.VIEW_TYPE_MINIMIZED)
6080                     .highlightExpander(false)
6081                     .fillTextsFrom(this);
6082             if (!useRegularSubtext || TextUtils.isEmpty(p.summaryText)) {
6083                 p.summaryText(createSummaryText());
6084             }
6085             RemoteViews header = makeNotificationHeader(p);
6086             header.setBoolean(R.id.notification_header, "setAcceptAllTouches", true);
6087             // The low priority header has no app name and shows the text
6088             header.setBoolean(R.id.notification_header, "styleTextAsTitle", true);
6089             return header;
6090         }
6091 
createSummaryText()6092         private CharSequence createSummaryText() {
6093             CharSequence titleText = mN.extras.getCharSequence(Notification.EXTRA_TITLE);
6094             if (USE_ONLY_TITLE_IN_LOW_PRIORITY_SUMMARY) {
6095                 return titleText;
6096             }
6097             SpannableStringBuilder summary = new SpannableStringBuilder();
6098             if (titleText == null) {
6099                 titleText = mN.extras.getCharSequence(Notification.EXTRA_TITLE_BIG);
6100             }
6101             BidiFormatter bidi = BidiFormatter.getInstance();
6102             if (titleText != null) {
6103                 summary.append(bidi.unicodeWrap(titleText));
6104             }
6105             CharSequence contentText = mN.extras.getCharSequence(Notification.EXTRA_TEXT);
6106             if (titleText != null && contentText != null) {
6107                 summary.append(bidi.unicodeWrap(mContext.getText(
6108                         R.string.notification_header_divider_symbol_with_spaces)));
6109             }
6110             if (contentText != null) {
6111                 summary.append(bidi.unicodeWrap(contentText));
6112             }
6113             return summary;
6114         }
6115 
generateActionButton(Action action, boolean emphasizedMode, StandardTemplateParams p)6116         private RemoteViews generateActionButton(Action action, boolean emphasizedMode,
6117                 StandardTemplateParams p) {
6118             final boolean tombstone = (action.actionIntent == null);
6119             RemoteViews button = new BuilderRemoteViews(mContext.getApplicationInfo(),
6120                     emphasizedMode ? getEmphasizedActionLayoutResource()
6121                             : tombstone ? getActionTombstoneLayoutResource()
6122                                     : getActionLayoutResource());
6123             if (!tombstone) {
6124                 button.setOnClickPendingIntent(R.id.action0, action.actionIntent);
6125             }
6126             button.setContentDescription(R.id.action0, action.title);
6127             if (action.mRemoteInputs != null) {
6128                 button.setRemoteInputs(R.id.action0, action.mRemoteInputs);
6129             }
6130             if (emphasizedMode) {
6131                 // change the background bgColor
6132                 CharSequence title = action.title;
6133                 int buttonFillColor = getColors(p).getSecondaryAccentColor();
6134                 if (isLegacy()) {
6135                     title = ContrastColorUtil.clearColorSpans(title);
6136                 } else {
6137                     // Check for a full-length span color to use as the button fill color.
6138                     Integer fullLengthColor = getFullLengthSpanColor(title);
6139                     if (fullLengthColor != null) {
6140                         // Ensure the custom button fill has 1.3:1 contrast w/ notification bg.
6141                         int notifBackgroundColor = getColors(p).getBackgroundColor();
6142                         buttonFillColor = ensureButtonFillContrast(
6143                                 fullLengthColor, notifBackgroundColor);
6144                     }
6145                     // Remove full-length color spans and ensure text contrast with the button fill.
6146                     title = ensureColorSpanContrast(title, buttonFillColor);
6147                 }
6148                 button.setTextViewText(R.id.action0, processTextSpans(title));
6149                 final int textColor = ContrastColorUtil.resolvePrimaryColor(mContext,
6150                         buttonFillColor, mInNightMode);
6151                 button.setTextColor(R.id.action0, textColor);
6152                 // We only want about 20% alpha for the ripple
6153                 final int rippleColor = (textColor & 0x00ffffff) | 0x33000000;
6154                 button.setColorStateList(R.id.action0, "setRippleColor",
6155                         ColorStateList.valueOf(rippleColor));
6156                 button.setColorStateList(R.id.action0, "setButtonBackground",
6157                         ColorStateList.valueOf(buttonFillColor));
6158                 if (p.mCallStyleActions) {
6159                     button.setImageViewIcon(R.id.action0, action.getIcon());
6160                     boolean priority = action.getExtras().getBoolean(CallStyle.KEY_ACTION_PRIORITY);
6161                     button.setBoolean(R.id.action0, "setIsPriority", priority);
6162                     int minWidthDimen =
6163                             priority ? R.dimen.call_notification_system_action_min_width : 0;
6164                     button.setIntDimen(R.id.action0, "setMinimumWidth", minWidthDimen);
6165                 }
6166             } else {
6167                 button.setTextViewText(R.id.action0, processTextSpans(
6168                         processLegacyText(action.title)));
6169                 button.setTextColor(R.id.action0, getStandardActionColor(p));
6170             }
6171             // CallStyle notifications add action buttons which don't actually exist in mActions,
6172             //  so we have to omit the index in that case.
6173             int actionIndex = mActions.indexOf(action);
6174             if (actionIndex != -1) {
6175                 button.setIntTag(R.id.action0, R.id.notification_action_index_tag, actionIndex);
6176             }
6177             return button;
6178         }
6179 
6180         /**
6181          * Extract the color from a full-length span from the text.
6182          *
6183          * @param charSequence the charSequence containing spans
6184          * @return the raw color of the text's last full-length span containing a color, or null if
6185          * no full-length span sets the text color.
6186          * @hide
6187          */
6188         @VisibleForTesting
6189         @Nullable
getFullLengthSpanColor(CharSequence charSequence)6190         public static Integer getFullLengthSpanColor(CharSequence charSequence) {
6191             // NOTE: this method preserves the functionality that for a CharSequence with multiple
6192             // full-length spans, the color of the last one is used.
6193             Integer result = null;
6194             if (charSequence instanceof Spanned) {
6195                 Spanned ss = (Spanned) charSequence;
6196                 Object[] spans = ss.getSpans(0, ss.length(), Object.class);
6197                 // First read through all full-length spans to get the button fill color, which will
6198                 //  be used as the background color for ensuring contrast of non-full-length spans.
6199                 for (Object span : spans) {
6200                     int spanStart = ss.getSpanStart(span);
6201                     int spanEnd = ss.getSpanEnd(span);
6202                     boolean fullLength = (spanEnd - spanStart) == charSequence.length();
6203                     if (!fullLength) {
6204                         continue;
6205                     }
6206                     if (span instanceof TextAppearanceSpan) {
6207                         TextAppearanceSpan originalSpan = (TextAppearanceSpan) span;
6208                         ColorStateList textColor = originalSpan.getTextColor();
6209                         if (textColor != null) {
6210                             result = textColor.getDefaultColor();
6211                         }
6212                     } else if (span instanceof ForegroundColorSpan) {
6213                         ForegroundColorSpan originalSpan = (ForegroundColorSpan) span;
6214                         result = originalSpan.getForegroundColor();
6215                     }
6216                 }
6217             }
6218             return result;
6219         }
6220 
6221         /**
6222          * Ensures contrast on color spans against a background color.
6223          * Note that any full-length color spans will be removed instead of being contrasted.
6224          *
6225          * @param charSequence the charSequence on which the spans are
6226          * @param background the background color to ensure the contrast against
6227          * @return the contrasted charSequence
6228          * @hide
6229          */
6230         @VisibleForTesting
ensureColorSpanContrast(CharSequence charSequence, int background)6231         public static CharSequence ensureColorSpanContrast(CharSequence charSequence,
6232                 int background) {
6233             if (charSequence instanceof Spanned) {
6234                 Spanned ss = (Spanned) charSequence;
6235                 Object[] spans = ss.getSpans(0, ss.length(), Object.class);
6236                 SpannableStringBuilder builder = new SpannableStringBuilder(ss.toString());
6237                 for (Object span : spans) {
6238                     Object resultSpan = span;
6239                     int spanStart = ss.getSpanStart(span);
6240                     int spanEnd = ss.getSpanEnd(span);
6241                     boolean fullLength = (spanEnd - spanStart) == charSequence.length();
6242                     if (resultSpan instanceof CharacterStyle) {
6243                         resultSpan = ((CharacterStyle) span).getUnderlying();
6244                     }
6245                     if (resultSpan instanceof TextAppearanceSpan) {
6246                         TextAppearanceSpan originalSpan = (TextAppearanceSpan) resultSpan;
6247                         ColorStateList textColor = originalSpan.getTextColor();
6248                         if (textColor != null) {
6249                             if (fullLength) {
6250                                 // Let's drop the color from the span
6251                                 textColor = null;
6252                             } else {
6253                                 int[] colors = textColor.getColors();
6254                                 int[] newColors = new int[colors.length];
6255                                 for (int i = 0; i < newColors.length; i++) {
6256                                     boolean isBgDark = isColorDark(background);
6257                                     newColors[i] = ContrastColorUtil.ensureLargeTextContrast(
6258                                             colors[i], background, isBgDark);
6259                                 }
6260                                 textColor = new ColorStateList(textColor.getStates().clone(),
6261                                         newColors);
6262                             }
6263                             resultSpan = new TextAppearanceSpan(
6264                                     originalSpan.getFamily(),
6265                                     originalSpan.getTextStyle(),
6266                                     originalSpan.getTextSize(),
6267                                     textColor,
6268                                     originalSpan.getLinkTextColor());
6269                         }
6270                     } else if (resultSpan instanceof ForegroundColorSpan) {
6271                         if (fullLength) {
6272                             resultSpan = null;
6273                         } else {
6274                             ForegroundColorSpan originalSpan = (ForegroundColorSpan) resultSpan;
6275                             int foregroundColor = originalSpan.getForegroundColor();
6276                             boolean isBgDark = isColorDark(background);
6277                             foregroundColor = ContrastColorUtil.ensureLargeTextContrast(
6278                                     foregroundColor, background, isBgDark);
6279                             resultSpan = new ForegroundColorSpan(foregroundColor);
6280                         }
6281                     } else {
6282                         resultSpan = span;
6283                     }
6284                     if (resultSpan != null) {
6285                         builder.setSpan(resultSpan, spanStart, spanEnd, ss.getSpanFlags(span));
6286                     }
6287                 }
6288                 return builder;
6289             }
6290             return charSequence;
6291         }
6292 
6293         /**
6294          * Determines if the color is light or dark.  Specifically, this is using the same metric as
6295          * {@link ContrastColorUtil#resolvePrimaryColor(Context, int, boolean)} and peers so that
6296          * the direction of color shift is consistent.
6297          *
6298          * @param color the color to check
6299          * @return true if the color has higher contrast with white than black
6300          * @hide
6301          */
isColorDark(int color)6302         public static boolean isColorDark(int color) {
6303             // as per ContrastColorUtil.shouldUseDark, this uses the color contrast midpoint.
6304             return ContrastColorUtil.calculateLuminance(color) <= 0.17912878474;
6305         }
6306 
6307         /**
6308          * Finds a button fill color with sufficient contrast over bg (1.3:1) that has the same hue
6309          * as the original color, but is lightened or darkened depending on whether the background
6310          * is dark or light.
6311          *
6312          * @hide
6313          */
6314         @VisibleForTesting
ensureButtonFillContrast(int color, int bg)6315         public static int ensureButtonFillContrast(int color, int bg) {
6316             return isColorDark(bg)
6317                     ? ContrastColorUtil.findContrastColorAgainstDark(color, bg, true, 1.3)
6318                     : ContrastColorUtil.findContrastColor(color, bg, true, 1.3);
6319         }
6320 
6321 
6322         /**
6323          * @return Whether we are currently building a notification from a legacy (an app that
6324          *         doesn't create material notifications by itself) app.
6325          */
isLegacy()6326         private boolean isLegacy() {
6327             if (!mIsLegacyInitialized) {
6328                 mIsLegacy = mContext.getApplicationInfo().targetSdkVersion
6329                         < Build.VERSION_CODES.LOLLIPOP;
6330                 mIsLegacyInitialized = true;
6331             }
6332             return mIsLegacy;
6333         }
6334 
6335         private CharSequence processLegacyText(CharSequence charSequence) {
6336             boolean isAlreadyLightText = isLegacy() || textColorsNeedInversion();
6337             if (isAlreadyLightText) {
6338                 return getColorUtil().invertCharSequenceColors(charSequence);
6339             } else {
6340                 return charSequence;
6341             }
6342         }
6343 
6344         /**
6345          * Apply any necessary colors to the small icon
6346          */
6347         private void processSmallIconColor(Icon smallIcon, RemoteViews contentView,
6348                 StandardTemplateParams p) {
6349             boolean colorable = !isLegacy() || getColorUtil().isGrayscaleIcon(mContext, smallIcon);
6350             int color = getSmallIconColor(p);
6351             contentView.setInt(R.id.icon, "setBackgroundColor",
6352                     getBackgroundColor(p));
6353             contentView.setInt(R.id.icon, "setOriginalIconColor",
6354                     colorable ? color : COLOR_INVALID);
6355         }
6356 
6357         /**
6358          * Make the largeIcon dark if it's a fake smallIcon (that is,
6359          * if it's grayscale).
6360          */
6361         // TODO: also check bounds, transparency, that sort of thing.
6362         private void processLargeLegacyIcon(Icon largeIcon, RemoteViews contentView,
6363                 StandardTemplateParams p) {
6364             if (largeIcon != null && isLegacy()
6365                     && getColorUtil().isGrayscaleIcon(mContext, largeIcon)) {
6366                 // resolve color will fall back to the default when legacy
6367                 int color = getSmallIconColor(p);
6368                 contentView.setInt(R.id.icon, "setOriginalIconColor", color);
6369             }
6370         }
6371 
6372         private void sanitizeColor() {
6373             if (mN.color != COLOR_DEFAULT) {
6374                 mN.color |= 0xFF000000; // no alpha for custom colors
6375             }
6376         }
6377 
6378         /**
6379          * Gets the standard action button color
6380          */
6381         private @ColorInt int getStandardActionColor(Notification.StandardTemplateParams p) {
6382             return mTintActionButtons || isBackgroundColorized(p)
6383                     ? getPrimaryAccentColor(p) : getSecondaryTextColor(p);
6384         }
6385 
6386         /**
6387          * Gets the foreground color of the small icon.  If the notification is colorized, this
6388          * is the primary text color, otherwise it's the contrast-adjusted app-provided color.
6389          */
6390         private @ColorInt int getSmallIconColor(StandardTemplateParams p) {
6391             return getColors(p).getContrastColor();
6392         }
6393 
6394         /** @return the theme's accent color for colored UI elements. */
6395         private @ColorInt int getPrimaryAccentColor(StandardTemplateParams p) {
6396             return getColors(p).getPrimaryAccentColor();
6397         }
6398 
6399         /**
6400          * Apply the unstyled operations and return a new {@link Notification} object.
6401          * @hide
6402          */
6403         @NonNull
6404         public Notification buildUnstyled() {
6405             if (mActions.size() > 0) {
6406                 mN.actions = new Action[mActions.size()];
6407                 mActions.toArray(mN.actions);
6408             }
6409             if (!mPersonList.isEmpty()) {
6410                 mN.extras.putParcelableArrayList(EXTRA_PEOPLE_LIST, mPersonList);
6411             }
6412             if (mN.bigContentView != null || mN.contentView != null
6413                     || mN.headsUpContentView != null) {
6414                 mN.extras.putBoolean(EXTRA_CONTAINS_CUSTOM_VIEW, true);
6415             }
6416             return mN;
6417         }
6418 
6419         /**
6420          * Creates a Builder from an existing notification so further changes can be made.
6421          * @param context The context for your application / activity.
6422          * @param n The notification to create a Builder from.
6423          */
6424         @NonNull
recoverBuilder(Context context, Notification n)6425         public static Notification.Builder recoverBuilder(Context context, Notification n) {
6426             // Re-create notification context so we can access app resources.
6427             ApplicationInfo applicationInfo = n.extras.getParcelable(
6428                     EXTRA_BUILDER_APPLICATION_INFO);
6429             Context builderContext;
6430             if (applicationInfo != null) {
6431                 try {
6432                     builderContext = context.createApplicationContext(applicationInfo,
6433                             Context.CONTEXT_RESTRICTED);
6434                 } catch (NameNotFoundException e) {
6435                     Log.e(TAG, "ApplicationInfo " + applicationInfo + " not found");
6436                     builderContext = context;  // try with our context
6437                 }
6438             } else {
6439                 builderContext = context; // try with given context
6440             }
6441 
6442             return new Builder(builderContext, n);
6443         }
6444 
6445         /**
6446          * Determines whether the platform can generate contextual actions for a notification.
6447          * By default this is true.
6448          */
6449         @NonNull
setAllowSystemGeneratedContextualActions(boolean allowed)6450         public Builder setAllowSystemGeneratedContextualActions(boolean allowed) {
6451             mN.mAllowSystemGeneratedContextualActions = allowed;
6452             return this;
6453         }
6454 
6455         /**
6456          * @deprecated Use {@link #build()} instead.
6457          */
6458         @Deprecated
getNotification()6459         public Notification getNotification() {
6460             return build();
6461         }
6462 
6463         /**
6464          * Combine all of the options that have been set and return a new {@link Notification}
6465          * object.
6466          *
6467          * If this notification has {@link BubbleMetadata} attached that was created with
6468          * a shortcutId a check will be performed to ensure the shortcutId supplied to bubble
6469          * metadata matches the shortcutId set on the  notification builder, if one was set.
6470          * If the shortcutId's were specified but do not match, an exception is thrown here.
6471          *
6472          * @see BubbleMetadata.Builder#Builder(String)
6473          * @see #setShortcutId(String)
6474          */
6475         @NonNull
build()6476         public Notification build() {
6477             // Check shortcut id matches
6478             if (mN.mShortcutId != null
6479                     && mN.mBubbleMetadata != null
6480                     && mN.mBubbleMetadata.getShortcutId() != null
6481                     && !mN.mShortcutId.equals(mN.mBubbleMetadata.getShortcutId())) {
6482                 throw new IllegalArgumentException(
6483                         "Notification and BubbleMetadata shortcut id's don't match,"
6484                                 + " notification: " + mN.mShortcutId
6485                                 + " vs bubble: " + mN.mBubbleMetadata.getShortcutId());
6486             }
6487 
6488             // first, add any extras from the calling code
6489             if (mUserExtras != null) {
6490                 mN.extras = getAllExtras();
6491             }
6492 
6493             mN.creationTime = System.currentTimeMillis();
6494 
6495             // lazy stuff from mContext; see comment in Builder(Context, Notification)
6496             Notification.addFieldsFromContext(mContext, mN);
6497 
6498             buildUnstyled();
6499 
6500             if (mStyle != null) {
6501                 mStyle.reduceImageSizes(mContext);
6502                 mStyle.purgeResources();
6503                 mStyle.validate(mContext);
6504                 mStyle.buildStyled(mN);
6505             }
6506             mN.reduceImageSizes(mContext);
6507 
6508             if (mContext.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N
6509                     && !styleDisplaysCustomViewInline()) {
6510                 RemoteViews newContentView = mN.contentView;
6511                 RemoteViews newBigContentView = mN.bigContentView;
6512                 RemoteViews newHeadsUpContentView = mN.headsUpContentView;
6513                 if (newContentView == null) {
6514                     newContentView = createContentView();
6515                     mN.extras.putInt(EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT,
6516                             newContentView.getSequenceNumber());
6517                 }
6518                 if (newBigContentView == null) {
6519                     newBigContentView = createBigContentView();
6520                     if (newBigContentView != null) {
6521                         mN.extras.putInt(EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT,
6522                                 newBigContentView.getSequenceNumber());
6523                     }
6524                 }
6525                 if (newHeadsUpContentView == null) {
6526                     newHeadsUpContentView = createHeadsUpContentView();
6527                     if (newHeadsUpContentView != null) {
6528                         mN.extras.putInt(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT,
6529                                 newHeadsUpContentView.getSequenceNumber());
6530                     }
6531                 }
6532                 // Don't set any of the content views until after they have all been generated,
6533                 //  to avoid the generated .contentView triggering the logic which skips generating
6534                 //  the .bigContentView.
6535                 mN.contentView = newContentView;
6536                 mN.bigContentView = newBigContentView;
6537                 mN.headsUpContentView = newHeadsUpContentView;
6538             }
6539 
6540             if ((mN.defaults & DEFAULT_LIGHTS) != 0) {
6541                 mN.flags |= FLAG_SHOW_LIGHTS;
6542             }
6543 
6544             mN.allPendingIntents = null;
6545 
6546             return mN;
6547         }
6548 
styleDisplaysCustomViewInline()6549         private boolean styleDisplaysCustomViewInline() {
6550             return mStyle != null && mStyle.displayCustomViewInline();
6551         }
6552 
6553         /**
6554          * Apply this Builder to an existing {@link Notification} object.
6555          *
6556          * @hide
6557          */
6558         @NonNull
buildInto(@onNull Notification n)6559         public Notification buildInto(@NonNull Notification n) {
6560             build().cloneInto(n, true);
6561             return n;
6562         }
6563 
6564         /**
6565          * Removes RemoteViews that were created for compatibility from {@param n}, if they did not
6566          * change.
6567          *
6568          * @return {@param n}, if no stripping is needed, otherwise a stripped clone of {@param n}.
6569          *
6570          * @hide
6571          */
maybeCloneStrippedForDelivery(Notification n)6572         public static Notification maybeCloneStrippedForDelivery(Notification n) {
6573             String templateClass = n.extras.getString(EXTRA_TEMPLATE);
6574 
6575             // Only strip views for known Styles because we won't know how to
6576             // re-create them otherwise.
6577             if (!TextUtils.isEmpty(templateClass)
6578                     && getNotificationStyleClass(templateClass) == null) {
6579                 return n;
6580             }
6581 
6582             // Only strip unmodified BuilderRemoteViews.
6583             boolean stripContentView = n.contentView instanceof BuilderRemoteViews &&
6584                     n.extras.getInt(EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT, -1) ==
6585                             n.contentView.getSequenceNumber();
6586             boolean stripBigContentView = n.bigContentView instanceof BuilderRemoteViews &&
6587                     n.extras.getInt(EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT, -1) ==
6588                             n.bigContentView.getSequenceNumber();
6589             boolean stripHeadsUpContentView = n.headsUpContentView instanceof BuilderRemoteViews &&
6590                     n.extras.getInt(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT, -1) ==
6591                             n.headsUpContentView.getSequenceNumber();
6592 
6593             // Nothing to do here, no need to clone.
6594             if (!stripContentView && !stripBigContentView && !stripHeadsUpContentView) {
6595                 return n;
6596             }
6597 
6598             Notification clone = n.clone();
6599             if (stripContentView) {
6600                 clone.contentView = null;
6601                 clone.extras.remove(EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT);
6602             }
6603             if (stripBigContentView) {
6604                 clone.bigContentView = null;
6605                 clone.extras.remove(EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT);
6606             }
6607             if (stripHeadsUpContentView) {
6608                 clone.headsUpContentView = null;
6609                 clone.extras.remove(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT);
6610             }
6611             return clone;
6612         }
6613 
6614         @UnsupportedAppUsage
getBaseLayoutResource()6615         private int getBaseLayoutResource() {
6616             return R.layout.notification_template_material_base;
6617         }
6618 
getHeadsUpBaseLayoutResource()6619         private int getHeadsUpBaseLayoutResource() {
6620             return R.layout.notification_template_material_heads_up_base;
6621         }
6622 
getBigBaseLayoutResource()6623         private int getBigBaseLayoutResource() {
6624             return R.layout.notification_template_material_big_base;
6625         }
6626 
getBigPictureLayoutResource()6627         private int getBigPictureLayoutResource() {
6628             return R.layout.notification_template_material_big_picture;
6629         }
6630 
getBigTextLayoutResource()6631         private int getBigTextLayoutResource() {
6632             return R.layout.notification_template_material_big_text;
6633         }
6634 
getInboxLayoutResource()6635         private int getInboxLayoutResource() {
6636             return R.layout.notification_template_material_inbox;
6637         }
6638 
getMessagingLayoutResource()6639         private int getMessagingLayoutResource() {
6640             return R.layout.notification_template_material_messaging;
6641         }
6642 
getBigMessagingLayoutResource()6643         private int getBigMessagingLayoutResource() {
6644             return R.layout.notification_template_material_big_messaging;
6645         }
6646 
getConversationLayoutResource()6647         private int getConversationLayoutResource() {
6648             return R.layout.notification_template_material_conversation;
6649         }
6650 
getActionLayoutResource()6651         private int getActionLayoutResource() {
6652             return R.layout.notification_material_action;
6653         }
6654 
getEmphasizedActionLayoutResource()6655         private int getEmphasizedActionLayoutResource() {
6656             return R.layout.notification_material_action_emphasized;
6657         }
6658 
getActionTombstoneLayoutResource()6659         private int getActionTombstoneLayoutResource() {
6660             return R.layout.notification_material_action_tombstone;
6661         }
6662 
getBackgroundColor(StandardTemplateParams p)6663         private @ColorInt int getBackgroundColor(StandardTemplateParams p) {
6664             return getColors(p).getBackgroundColor();
6665         }
6666 
textColorsNeedInversion()6667         private boolean textColorsNeedInversion() {
6668             if (mStyle == null || !MediaStyle.class.equals(mStyle.getClass())) {
6669                 return false;
6670             }
6671             int targetSdkVersion = mContext.getApplicationInfo().targetSdkVersion;
6672             return targetSdkVersion > Build.VERSION_CODES.M
6673                     && targetSdkVersion < Build.VERSION_CODES.O;
6674         }
6675 
6676         /**
6677          * Get the text that should be displayed in the statusBar when heads upped. This is
6678          * usually just the app name, but may be different depending on the style.
6679          *
6680          * @param publicMode If true, return a text that is safe to display in public.
6681          *
6682          * @hide
6683          */
getHeadsUpStatusBarText(boolean publicMode)6684         public CharSequence getHeadsUpStatusBarText(boolean publicMode) {
6685             if (mStyle != null && !publicMode) {
6686                 CharSequence text = mStyle.getHeadsUpStatusBarText();
6687                 if (!TextUtils.isEmpty(text)) {
6688                     return text;
6689                 }
6690             }
6691             return loadHeaderAppName();
6692         }
6693 
6694         /**
6695          * @return if this builder uses a template
6696          *
6697          * @hide
6698          */
usesTemplate()6699         public boolean usesTemplate() {
6700             return (mN.contentView == null && mN.headsUpContentView == null
6701                     && mN.bigContentView == null)
6702                     || styleDisplaysCustomViewInline();
6703         }
6704     }
6705 
6706     /**
6707      * Reduces the image sizes to conform to a maximum allowed size. This also processes all custom
6708      * remote views.
6709      *
6710      * @hide
6711      */
reduceImageSizes(Context context)6712     void reduceImageSizes(Context context) {
6713         if (extras.getBoolean(EXTRA_REDUCED_IMAGES)) {
6714             return;
6715         }
6716         boolean isLowRam = ActivityManager.isLowRamDeviceStatic();
6717         if (mLargeIcon != null || largeIcon != null) {
6718             Resources resources = context.getResources();
6719             Class<? extends Style> style = getNotificationStyle();
6720             int maxSize = resources.getDimensionPixelSize(isLowRam
6721                     ? R.dimen.notification_right_icon_size_low_ram
6722                     : R.dimen.notification_right_icon_size);
6723             if (mLargeIcon != null) {
6724                 mLargeIcon.scaleDownIfNecessary(maxSize, maxSize);
6725             }
6726             if (largeIcon != null) {
6727                 largeIcon = Icon.scaleDownIfNecessary(largeIcon, maxSize, maxSize);
6728             }
6729         }
6730         reduceImageSizesForRemoteView(contentView, context, isLowRam);
6731         reduceImageSizesForRemoteView(headsUpContentView, context, isLowRam);
6732         reduceImageSizesForRemoteView(bigContentView, context, isLowRam);
6733         extras.putBoolean(EXTRA_REDUCED_IMAGES, true);
6734     }
6735 
reduceImageSizesForRemoteView(RemoteViews remoteView, Context context, boolean isLowRam)6736     private void reduceImageSizesForRemoteView(RemoteViews remoteView, Context context,
6737             boolean isLowRam) {
6738         if (remoteView != null) {
6739             Resources resources = context.getResources();
6740             int maxWidth = resources.getDimensionPixelSize(isLowRam
6741                     ? R.dimen.notification_custom_view_max_image_width_low_ram
6742                     : R.dimen.notification_custom_view_max_image_width);
6743             int maxHeight = resources.getDimensionPixelSize(isLowRam
6744                     ? R.dimen.notification_custom_view_max_image_height_low_ram
6745                     : R.dimen.notification_custom_view_max_image_height);
6746             remoteView.reduceImageSizes(maxWidth, maxHeight);
6747         }
6748     }
6749 
6750     /**
6751      * @return whether this notification is a foreground service notification
6752      * @hide
6753      */
isForegroundService()6754     public boolean isForegroundService() {
6755         return (flags & Notification.FLAG_FOREGROUND_SERVICE) != 0;
6756     }
6757 
6758     /**
6759      * Describe whether this notification's content such that it should always display
6760      * immediately when tied to a foreground service, even if the system might generally
6761      * avoid showing the notifications for short-lived foreground service lifetimes.
6762      *
6763      * Immediate visibility of the Notification is indicated when:
6764      * <ul>
6765      *     <li>The app specifically indicated it with
6766      *         {@link Notification.Builder#setForegroundServiceBehavior(int)
6767      *         setForegroundServiceBehavior(BEHAVIOR_IMMEDIATE_DISPLAY)}</li>
6768      *     <li>It is a media notification or has an associated media session</li>
6769      *     <li>It is a call or navigation notification</li>
6770      *     <li>It provides additional action affordances</li>
6771      * </ul>
6772      *
6773      * If the app has specified
6774      * {@code NotificationBuilder.setForegroundServiceBehavior(BEHAVIOR_DEFERRED_DISPLAY)}
6775      * then this method will return {@code false} and notification visibility will be
6776      * deferred following the service's transition to the foreground state even in the
6777      * circumstances described above.
6778      *
6779      * @return whether this notification should be displayed immediately when
6780      * its associated service transitions to the foreground state
6781      * @hide
6782      */
6783     @TestApi
shouldShowForegroundImmediately()6784     public boolean shouldShowForegroundImmediately() {
6785         // Has the app demanded immediate display?
6786         if (mFgsDeferBehavior == FOREGROUND_SERVICE_IMMEDIATE) {
6787             return true;
6788         }
6789 
6790         // Has the app demanded deferred display?
6791         if (mFgsDeferBehavior == FOREGROUND_SERVICE_DEFERRED) {
6792             return false;
6793         }
6794 
6795         // We show these sorts of notifications immediately in the absence of
6796         // any explicit app declaration
6797         if (isMediaNotification()
6798                     || CATEGORY_CALL.equals(category)
6799                     || CATEGORY_NAVIGATION.equals(category)
6800                     || (actions != null && actions.length > 0)) {
6801             return true;
6802         }
6803 
6804         // No extenuating circumstances: defer visibility
6805         return false;
6806     }
6807 
6808     /**
6809      * Has forced deferral for FGS purposes been specified?
6810      * @hide
6811      */
isForegroundDisplayForceDeferred()6812     public boolean isForegroundDisplayForceDeferred() {
6813         return FOREGROUND_SERVICE_DEFERRED == mFgsDeferBehavior;
6814     }
6815 
6816     /**
6817      * @return the style class of this notification
6818      * @hide
6819      */
getNotificationStyle()6820     public Class<? extends Notification.Style> getNotificationStyle() {
6821         String templateClass = extras.getString(Notification.EXTRA_TEMPLATE);
6822 
6823         if (!TextUtils.isEmpty(templateClass)) {
6824             return Notification.getNotificationStyleClass(templateClass);
6825         }
6826         return null;
6827     }
6828 
6829     /**
6830      * @return whether the style of this notification is the one provided
6831      * @hide
6832      */
isStyle(@onNull Class<? extends Style> styleClass)6833     public boolean isStyle(@NonNull Class<? extends Style> styleClass) {
6834         String templateClass = extras.getString(Notification.EXTRA_TEMPLATE);
6835         return Objects.equals(templateClass, styleClass.getName());
6836     }
6837 
6838     /**
6839      * @return true if this notification is colorized *for the purposes of ranking*.  If the
6840      * {@link #color} is {@link #COLOR_DEFAULT} this will be true, even though the actual
6841      * appearance of the notification may not be "colorized".
6842      *
6843      * @hide
6844      */
isColorized()6845     public boolean isColorized() {
6846         return extras.getBoolean(EXTRA_COLORIZED)
6847                 && (hasColorizedPermission() || isForegroundService());
6848     }
6849 
6850     /**
6851      * Returns whether an app can colorize due to the android.permission.USE_COLORIZED_NOTIFICATIONS
6852      * permission. The permission is checked when a notification is enqueued.
6853      */
hasColorizedPermission()6854     private boolean hasColorizedPermission() {
6855         return (flags & Notification.FLAG_CAN_COLORIZE) != 0;
6856     }
6857 
6858     /**
6859      * @return true if this is a media style notification with a media session
6860      *
6861      * @hide
6862      */
isMediaNotification()6863     public boolean isMediaNotification() {
6864         Class<? extends Style> style = getNotificationStyle();
6865         boolean isMediaStyle = (MediaStyle.class.equals(style)
6866                 || DecoratedMediaCustomViewStyle.class.equals(style));
6867 
6868         boolean hasMediaSession = (extras.getParcelable(Notification.EXTRA_MEDIA_SESSION) != null
6869                 && extras.getParcelable(Notification.EXTRA_MEDIA_SESSION)
6870                 instanceof MediaSession.Token);
6871 
6872         return isMediaStyle && hasMediaSession;
6873     }
6874 
6875     /**
6876      * @return true if this notification is showing as a bubble
6877      *
6878      * @hide
6879      */
isBubbleNotification()6880     public boolean isBubbleNotification() {
6881         return (flags & Notification.FLAG_BUBBLE) != 0;
6882     }
6883 
hasLargeIcon()6884     private boolean hasLargeIcon() {
6885         return mLargeIcon != null || largeIcon != null;
6886     }
6887 
6888     /**
6889      * @return true if the notification will show the time; false otherwise
6890      * @hide
6891      */
showsTime()6892     public boolean showsTime() {
6893         return when != 0 && extras.getBoolean(EXTRA_SHOW_WHEN);
6894     }
6895 
6896     /**
6897      * @return true if the notification will show a chronometer; false otherwise
6898      * @hide
6899      */
showsChronometer()6900     public boolean showsChronometer() {
6901         return when != 0 && extras.getBoolean(EXTRA_SHOW_CHRONOMETER);
6902     }
6903 
6904     /**
6905      * @return true if the notification has image
6906      */
hasImage()6907     public boolean hasImage() {
6908         if (isStyle(MessagingStyle.class) && extras != null) {
6909             final Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES);
6910             if (!ArrayUtils.isEmpty(messages)) {
6911                 for (MessagingStyle.Message m : MessagingStyle.Message
6912                         .getMessagesFromBundleArray(messages)) {
6913                     if (m.getDataUri() != null
6914                             && m.getDataMimeType() != null
6915                             && m.getDataMimeType().startsWith("image/")) {
6916                         return true;
6917                     }
6918                 }
6919             }
6920         } else if (hasLargeIcon()) {
6921             return true;
6922         } else if (extras.containsKey(EXTRA_BACKGROUND_IMAGE_URI)) {
6923             return true;
6924         }
6925         return false;
6926     }
6927 
6928 
6929     /**
6930      * @removed
6931      */
6932     @SystemApi
getNotificationStyleClass(String templateClass)6933     public static Class<? extends Style> getNotificationStyleClass(String templateClass) {
6934         for (Class<? extends Style> innerClass : PLATFORM_STYLE_CLASSES) {
6935             if (templateClass.equals(innerClass.getName())) {
6936                 return innerClass;
6937             }
6938         }
6939         return null;
6940     }
6941 
buildCustomContentIntoTemplate(@onNull Context context, @NonNull RemoteViews template, @Nullable RemoteViews customContent, @NonNull StandardTemplateParams p, @NonNull TemplateBindResult result)6942     private static void buildCustomContentIntoTemplate(@NonNull Context context,
6943             @NonNull RemoteViews template, @Nullable RemoteViews customContent,
6944             @NonNull StandardTemplateParams p, @NonNull TemplateBindResult result) {
6945         int childIndex = -1;
6946         if (customContent != null) {
6947             // Need to clone customContent before adding, because otherwise it can no longer be
6948             // parceled independently of remoteViews.
6949             customContent = customContent.clone();
6950             if (p.mHeaderless) {
6951                 template.removeFromParent(R.id.notification_top_line);
6952                 // We do not know how many lines ar emote view has, so we presume it has 2;  this
6953                 // ensures that we don't under-pad the content, which could lead to abuse, at the
6954                 // cost of making single-line custom content over-padded.
6955                 Builder.setHeaderlessVerticalMargins(template, p, true /* hasSecondLine */);
6956             } else {
6957                 // also update the end margin to account for the large icon or expander
6958                 Resources resources = context.getResources();
6959                 result.mTitleMarginSet.applyToView(template, R.id.notification_main_column,
6960                         resources.getDimension(R.dimen.notification_content_margin_end)
6961                                 / resources.getDisplayMetrics().density);
6962             }
6963             template.removeAllViewsExceptId(R.id.notification_main_column, R.id.progress);
6964             template.addView(R.id.notification_main_column, customContent, 0 /* index */);
6965             template.addFlags(RemoteViews.FLAG_REAPPLY_DISALLOWED);
6966             childIndex = 0;
6967         }
6968         template.setIntTag(R.id.notification_main_column,
6969                 com.android.internal.R.id.notification_custom_view_index_tag,
6970                 childIndex);
6971     }
6972 
6973     /**
6974      * An object that can apply a rich notification style to a {@link Notification.Builder}
6975      * object.
6976      */
6977     public static abstract class Style {
6978 
6979         /**
6980          * The number of items allowed simulatanously in the remote input history.
6981          * @hide
6982          */
6983         static final int MAX_REMOTE_INPUT_HISTORY_LINES = 3;
6984         private CharSequence mBigContentTitle;
6985 
6986         /**
6987          * @hide
6988          */
6989         protected CharSequence mSummaryText = null;
6990 
6991         /**
6992          * @hide
6993          */
6994         protected boolean mSummaryTextSet = false;
6995 
6996         protected Builder mBuilder;
6997 
6998         /**
6999          * Overrides ContentTitle in the big form of the template.
7000          * This defaults to the value passed to setContentTitle().
7001          */
internalSetBigContentTitle(CharSequence title)7002         protected void internalSetBigContentTitle(CharSequence title) {
7003             mBigContentTitle = title;
7004         }
7005 
7006         /**
7007          * Set the first line of text after the detail section in the big form of the template.
7008          */
internalSetSummaryText(CharSequence cs)7009         protected void internalSetSummaryText(CharSequence cs) {
7010             mSummaryText = cs;
7011             mSummaryTextSet = true;
7012         }
7013 
setBuilder(Builder builder)7014         public void setBuilder(Builder builder) {
7015             if (mBuilder != builder) {
7016                 mBuilder = builder;
7017                 if (mBuilder != null) {
7018                     mBuilder.setStyle(this);
7019                 }
7020             }
7021         }
7022 
checkBuilder()7023         protected void checkBuilder() {
7024             if (mBuilder == null) {
7025                 throw new IllegalArgumentException("Style requires a valid Builder object");
7026             }
7027         }
7028 
getStandardView(int layoutId)7029         protected RemoteViews getStandardView(int layoutId) {
7030             // TODO(jeffdq): set the view type based on the layout resource?
7031             StandardTemplateParams p = mBuilder.mParams.reset()
7032                     .viewType(StandardTemplateParams.VIEW_TYPE_UNSPECIFIED)
7033                     .fillTextsFrom(mBuilder);
7034             return getStandardView(layoutId, p, null);
7035         }
7036 
7037 
7038         /**
7039          * Get the standard view for this style.
7040          *
7041          * @param layoutId The layout id to use.
7042          * @param p the params for this inflation.
7043          * @param result The result where template bind information is saved.
7044          * @return A remoteView for this style.
7045          * @hide
7046          */
getStandardView(int layoutId, StandardTemplateParams p, TemplateBindResult result)7047         protected RemoteViews getStandardView(int layoutId, StandardTemplateParams p,
7048                 TemplateBindResult result) {
7049             checkBuilder();
7050 
7051             if (mBigContentTitle != null) {
7052                 p.title = mBigContentTitle;
7053             }
7054 
7055             return mBuilder.applyStandardTemplateWithActions(layoutId, p, result);
7056         }
7057 
7058         /**
7059          * Construct a Style-specific RemoteViews for the collapsed notification layout.
7060          * The default implementation has nothing additional to add.
7061          *
7062          * @param increasedHeight true if this layout be created with an increased height.
7063          * @hide
7064          */
makeContentView(boolean increasedHeight)7065         public RemoteViews makeContentView(boolean increasedHeight) {
7066             return null;
7067         }
7068 
7069         /**
7070          * Construct a Style-specific RemoteViews for the final big notification layout.
7071          * @hide
7072          */
makeBigContentView()7073         public RemoteViews makeBigContentView() {
7074             return null;
7075         }
7076 
7077         /**
7078          * Construct a Style-specific RemoteViews for the final HUN layout.
7079          *
7080          * @param increasedHeight true if this layout be created with an increased height.
7081          * @hide
7082          */
makeHeadsUpContentView(boolean increasedHeight)7083         public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
7084             return null;
7085         }
7086 
7087         /**
7088          * Apply any style-specific extras to this notification before shipping it out.
7089          * @hide
7090          */
addExtras(Bundle extras)7091         public void addExtras(Bundle extras) {
7092             if (mSummaryTextSet) {
7093                 extras.putCharSequence(EXTRA_SUMMARY_TEXT, mSummaryText);
7094             }
7095             if (mBigContentTitle != null) {
7096                 extras.putCharSequence(EXTRA_TITLE_BIG, mBigContentTitle);
7097             }
7098             extras.putString(EXTRA_TEMPLATE, this.getClass().getName());
7099         }
7100 
7101         /**
7102          * Reconstruct the internal state of this Style object from extras.
7103          * @hide
7104          */
restoreFromExtras(Bundle extras)7105         protected void restoreFromExtras(Bundle extras) {
7106             if (extras.containsKey(EXTRA_SUMMARY_TEXT)) {
7107                 mSummaryText = extras.getCharSequence(EXTRA_SUMMARY_TEXT);
7108                 mSummaryTextSet = true;
7109             }
7110             if (extras.containsKey(EXTRA_TITLE_BIG)) {
7111                 mBigContentTitle = extras.getCharSequence(EXTRA_TITLE_BIG);
7112             }
7113         }
7114 
7115 
7116         /**
7117          * @hide
7118          */
buildStyled(Notification wip)7119         public Notification buildStyled(Notification wip) {
7120             addExtras(wip.extras);
7121             return wip;
7122         }
7123 
7124         /**
7125          * @hide
7126          */
purgeResources()7127         public void purgeResources() {}
7128 
7129         /**
7130          * Calls {@link android.app.Notification.Builder#build()} on the Builder this Style is
7131          * attached to.
7132          *
7133          * @return the fully constructed Notification.
7134          */
build()7135         public Notification build() {
7136             checkBuilder();
7137             return mBuilder.build();
7138         }
7139 
7140         /**
7141          * @hide
7142          * @return Whether we should put the summary be put into the notification header
7143          */
hasSummaryInHeader()7144         public boolean hasSummaryInHeader() {
7145             return true;
7146         }
7147 
7148         /**
7149          * @hide
7150          * @return Whether custom content views are displayed inline in the style
7151          */
displayCustomViewInline()7152         public boolean displayCustomViewInline() {
7153             return false;
7154         }
7155 
7156         /**
7157          * Reduces the image sizes contained in this style.
7158          *
7159          * @hide
7160          */
reduceImageSizes(Context context)7161         public void reduceImageSizes(Context context) {
7162         }
7163 
7164         /**
7165          * Validate that this style was properly composed. This is called at build time.
7166          * @hide
7167          */
validate(Context context)7168         public void validate(Context context) {
7169         }
7170 
7171         /**
7172          * @hide
7173          */
areNotificationsVisiblyDifferent(Style other)7174         public abstract boolean areNotificationsVisiblyDifferent(Style other);
7175 
7176         /**
7177          * @return the text that should be displayed in the statusBar when heads-upped.
7178          * If {@code null} is returned, the default implementation will be used.
7179          *
7180          * @hide
7181          */
getHeadsUpStatusBarText()7182         public CharSequence getHeadsUpStatusBarText() {
7183             return null;
7184         }
7185     }
7186 
7187     /**
7188      * Helper class for generating large-format notifications that include a large image attachment.
7189      *
7190      * Here's how you'd set the <code>BigPictureStyle</code> on a notification:
7191      * <pre class="prettyprint">
7192      * Notification notif = new Notification.Builder(mContext)
7193      *     .setContentTitle(&quot;New photo from &quot; + sender.toString())
7194      *     .setContentText(subject)
7195      *     .setSmallIcon(R.drawable.new_post)
7196      *     .setLargeIcon(aBitmap)
7197      *     .setStyle(new Notification.BigPictureStyle()
7198      *         .bigPicture(aBigBitmap))
7199      *     .build();
7200      * </pre>
7201      *
7202      * @see Notification#bigContentView
7203      */
7204     public static class BigPictureStyle extends Style {
7205         private Icon mPictureIcon;
7206         private Icon mBigLargeIcon;
7207         private boolean mBigLargeIconSet = false;
7208         private CharSequence mPictureContentDescription;
7209         private boolean mShowBigPictureWhenCollapsed;
7210 
BigPictureStyle()7211         public BigPictureStyle() {
7212         }
7213 
7214         /**
7215          * @deprecated use {@code BigPictureStyle()}.
7216          */
7217         @Deprecated
BigPictureStyle(Builder builder)7218         public BigPictureStyle(Builder builder) {
7219             setBuilder(builder);
7220         }
7221 
7222         /**
7223          * Overrides ContentTitle in the big form of the template.
7224          * This defaults to the value passed to setContentTitle().
7225          */
7226         @NonNull
setBigContentTitle(@ullable CharSequence title)7227         public BigPictureStyle setBigContentTitle(@Nullable CharSequence title) {
7228             internalSetBigContentTitle(safeCharSequence(title));
7229             return this;
7230         }
7231 
7232         /**
7233          * Set the first line of text after the detail section in the big form of the template.
7234          */
7235         @NonNull
setSummaryText(@ullable CharSequence cs)7236         public BigPictureStyle setSummaryText(@Nullable CharSequence cs) {
7237             internalSetSummaryText(safeCharSequence(cs));
7238             return this;
7239         }
7240 
7241         /**
7242          * Set the content description of the big picture.
7243          */
7244         @NonNull
setContentDescription( @ullable CharSequence contentDescription)7245         public BigPictureStyle setContentDescription(
7246                 @Nullable CharSequence contentDescription) {
7247             mPictureContentDescription = contentDescription;
7248             return this;
7249         }
7250 
7251         /**
7252          * @hide
7253          */
7254         @Nullable
getBigPicture()7255         public Icon getBigPicture() {
7256             if (mPictureIcon != null) {
7257                 return mPictureIcon;
7258             }
7259             return null;
7260         }
7261 
7262         /**
7263          * Provide the bitmap to be used as the payload for the BigPicture notification.
7264          */
7265         @NonNull
bigPicture(@ullable Bitmap b)7266         public BigPictureStyle bigPicture(@Nullable Bitmap b) {
7267             mPictureIcon = b == null ? null : Icon.createWithBitmap(b);
7268             return this;
7269         }
7270 
7271         /**
7272          * Provide the content Uri to be used as the payload for the BigPicture notification.
7273          */
7274         @NonNull
bigPicture(@ullable Icon icon)7275         public BigPictureStyle bigPicture(@Nullable Icon icon) {
7276             mPictureIcon = icon;
7277             return this;
7278         }
7279 
7280         /**
7281          * When set, the {@link #bigPicture(Bitmap) big picture} of this style will be promoted and
7282          * shown in place of the {@link Builder#setLargeIcon(Icon) large icon} in the collapsed
7283          * state of this notification.
7284          */
7285         @NonNull
showBigPictureWhenCollapsed(boolean show)7286         public BigPictureStyle showBigPictureWhenCollapsed(boolean show) {
7287             mShowBigPictureWhenCollapsed = show;
7288             return this;
7289         }
7290 
7291         /**
7292          * Override the large icon when the big notification is shown.
7293          */
7294         @NonNull
bigLargeIcon(@ullable Bitmap b)7295         public BigPictureStyle bigLargeIcon(@Nullable Bitmap b) {
7296             return bigLargeIcon(b != null ? Icon.createWithBitmap(b) : null);
7297         }
7298 
7299         /**
7300          * Override the large icon when the big notification is shown.
7301          */
7302         @NonNull
bigLargeIcon(@ullable Icon icon)7303         public BigPictureStyle bigLargeIcon(@Nullable Icon icon) {
7304             mBigLargeIconSet = true;
7305             mBigLargeIcon = icon;
7306             return this;
7307         }
7308 
7309         /** @hide */
7310         public static final int MIN_ASHMEM_BITMAP_SIZE = 128 * (1 << 10);
7311 
7312         /**
7313          * @hide
7314          */
7315         @Override
purgeResources()7316         public void purgeResources() {
7317             super.purgeResources();
7318             if (mPictureIcon != null) {
7319                 mPictureIcon.convertToAshmem();
7320             }
7321             if (mBigLargeIcon != null) {
7322                 mBigLargeIcon.convertToAshmem();
7323             }
7324         }
7325 
7326         /**
7327          * @hide
7328          */
7329         @Override
reduceImageSizes(Context context)7330         public void reduceImageSizes(Context context) {
7331             super.reduceImageSizes(context);
7332             Resources resources = context.getResources();
7333             boolean isLowRam = ActivityManager.isLowRamDeviceStatic();
7334             if (mPictureIcon != null) {
7335                 int maxPictureWidth = resources.getDimensionPixelSize(isLowRam
7336                         ? R.dimen.notification_big_picture_max_height_low_ram
7337                         : R.dimen.notification_big_picture_max_height);
7338                 int maxPictureHeight = resources.getDimensionPixelSize(isLowRam
7339                         ? R.dimen.notification_big_picture_max_width_low_ram
7340                         : R.dimen.notification_big_picture_max_width);
7341                 mPictureIcon.scaleDownIfNecessary(maxPictureWidth, maxPictureHeight);
7342             }
7343             if (mBigLargeIcon != null) {
7344                 int rightIconSize = resources.getDimensionPixelSize(isLowRam
7345                         ? R.dimen.notification_right_icon_size_low_ram
7346                         : R.dimen.notification_right_icon_size);
7347                 mBigLargeIcon.scaleDownIfNecessary(rightIconSize, rightIconSize);
7348             }
7349         }
7350 
7351         /**
7352          * @hide
7353          */
7354         @Override
makeContentView(boolean increasedHeight)7355         public RemoteViews makeContentView(boolean increasedHeight) {
7356             if (mPictureIcon == null || !mShowBigPictureWhenCollapsed) {
7357                 return super.makeContentView(increasedHeight);
7358             }
7359 
7360             StandardTemplateParams p = mBuilder.mParams.reset()
7361                     .viewType(StandardTemplateParams.VIEW_TYPE_NORMAL)
7362                     .fillTextsFrom(mBuilder)
7363                     .promotedPicture(mPictureIcon);
7364             return getStandardView(mBuilder.getBaseLayoutResource(), p, null /* result */);
7365         }
7366 
7367         /**
7368          * @hide
7369          */
7370         @Override
makeHeadsUpContentView(boolean increasedHeight)7371         public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
7372             if (mPictureIcon == null || !mShowBigPictureWhenCollapsed) {
7373                 return super.makeHeadsUpContentView(increasedHeight);
7374             }
7375 
7376             StandardTemplateParams p = mBuilder.mParams.reset()
7377                     .viewType(StandardTemplateParams.VIEW_TYPE_HEADS_UP)
7378                     .fillTextsFrom(mBuilder)
7379                     .promotedPicture(mPictureIcon);
7380             return getStandardView(mBuilder.getHeadsUpBaseLayoutResource(), p, null /* result */);
7381         }
7382 
7383         /**
7384          * @hide
7385          */
makeBigContentView()7386         public RemoteViews makeBigContentView() {
7387             // Replace mN.mLargeIcon with mBigLargeIcon if mBigLargeIconSet
7388             // This covers the following cases:
7389             //   1. mBigLargeIconSet -> mBigLargeIcon (null or non-null) applies, overrides
7390             //          mN.mLargeIcon
7391             //   2. !mBigLargeIconSet -> mN.mLargeIcon applies
7392             Icon oldLargeIcon = null;
7393             Bitmap largeIconLegacy = null;
7394             if (mBigLargeIconSet) {
7395                 oldLargeIcon = mBuilder.mN.mLargeIcon;
7396                 mBuilder.mN.mLargeIcon = mBigLargeIcon;
7397                 // The legacy largeIcon might not allow us to clear the image, as it's taken in
7398                 // replacement if the other one is null. Because we're restoring these legacy icons
7399                 // for old listeners, this is in general non-null.
7400                 largeIconLegacy = mBuilder.mN.largeIcon;
7401                 mBuilder.mN.largeIcon = null;
7402             }
7403 
7404             StandardTemplateParams p = mBuilder.mParams.reset()
7405                     .viewType(StandardTemplateParams.VIEW_TYPE_BIG).fillTextsFrom(mBuilder);
7406             RemoteViews contentView = getStandardView(mBuilder.getBigPictureLayoutResource(),
7407                     p, null /* result */);
7408             if (mSummaryTextSet) {
7409                 contentView.setTextViewText(R.id.text, mBuilder.processTextSpans(
7410                         mBuilder.processLegacyText(mSummaryText)));
7411                 mBuilder.setTextViewColorSecondary(contentView, R.id.text, p);
7412                 contentView.setViewVisibility(R.id.text, View.VISIBLE);
7413             }
7414 
7415             if (mBigLargeIconSet) {
7416                 mBuilder.mN.mLargeIcon = oldLargeIcon;
7417                 mBuilder.mN.largeIcon = largeIconLegacy;
7418             }
7419 
7420             contentView.setImageViewIcon(R.id.big_picture, mPictureIcon);
7421 
7422             if (mPictureContentDescription != null) {
7423                 contentView.setContentDescription(R.id.big_picture, mPictureContentDescription);
7424             }
7425 
7426             return contentView;
7427         }
7428 
7429         /**
7430          * @hide
7431          */
addExtras(Bundle extras)7432         public void addExtras(Bundle extras) {
7433             super.addExtras(extras);
7434 
7435             if (mBigLargeIconSet) {
7436                 extras.putParcelable(EXTRA_LARGE_ICON_BIG, mBigLargeIcon);
7437             }
7438             if (mPictureContentDescription != null) {
7439                 extras.putCharSequence(EXTRA_PICTURE_CONTENT_DESCRIPTION,
7440                         mPictureContentDescription);
7441             }
7442             extras.putBoolean(EXTRA_SHOW_BIG_PICTURE_WHEN_COLLAPSED, mShowBigPictureWhenCollapsed);
7443 
7444             // If the icon contains a bitmap, use the old extra so that listeners which look for
7445             // that extra can still find the picture.  Don't include the new extra in that case,
7446             // to avoid duplicating data.
7447             if (mPictureIcon != null && mPictureIcon.getType() == Icon.TYPE_BITMAP) {
7448                 extras.putParcelable(EXTRA_PICTURE, mPictureIcon.getBitmap());
7449                 extras.putParcelable(EXTRA_PICTURE_ICON, null);
7450             } else {
7451                 extras.putParcelable(EXTRA_PICTURE, null);
7452                 extras.putParcelable(EXTRA_PICTURE_ICON, mPictureIcon);
7453             }
7454         }
7455 
7456         /**
7457          * @hide
7458          */
7459         @Override
restoreFromExtras(Bundle extras)7460         protected void restoreFromExtras(Bundle extras) {
7461             super.restoreFromExtras(extras);
7462 
7463             if (extras.containsKey(EXTRA_LARGE_ICON_BIG)) {
7464                 mBigLargeIconSet = true;
7465                 mBigLargeIcon = extras.getParcelable(EXTRA_LARGE_ICON_BIG);
7466             }
7467 
7468             if (extras.containsKey(EXTRA_PICTURE_CONTENT_DESCRIPTION)) {
7469                 mPictureContentDescription =
7470                         extras.getCharSequence(EXTRA_PICTURE_CONTENT_DESCRIPTION);
7471             }
7472 
7473             mShowBigPictureWhenCollapsed = extras.getBoolean(EXTRA_SHOW_BIG_PICTURE_WHEN_COLLAPSED);
7474 
7475             mPictureIcon = getPictureIcon(extras);
7476         }
7477 
7478         /** @hide */
7479         @Nullable
getPictureIcon(@ullable Bundle extras)7480         public static Icon getPictureIcon(@Nullable Bundle extras) {
7481             if (extras == null) return null;
7482             // When this style adds a picture, we only add one of the keys.  If both were added,
7483             // it would most likely be a legacy app trying to override the picture in some way.
7484             // Because of that case it's better to give precedence to the legacy field.
7485             Bitmap bitmapPicture = extras.getParcelable(EXTRA_PICTURE);
7486             if (bitmapPicture != null) {
7487                 return Icon.createWithBitmap(bitmapPicture);
7488             } else {
7489                 return extras.getParcelable(EXTRA_PICTURE_ICON);
7490             }
7491         }
7492 
7493         /**
7494          * @hide
7495          */
7496         @Override
hasSummaryInHeader()7497         public boolean hasSummaryInHeader() {
7498             return false;
7499         }
7500 
7501         /**
7502          * @hide
7503          * Note that we aren't actually comparing the contents of the bitmaps here, so this
7504          * is only doing a cursory inspection. Bitmaps of equal size will appear the same.
7505          */
7506         @Override
areNotificationsVisiblyDifferent(Style other)7507         public boolean areNotificationsVisiblyDifferent(Style other) {
7508             if (other == null || getClass() != other.getClass()) {
7509                 return true;
7510             }
7511             BigPictureStyle otherS = (BigPictureStyle) other;
7512             return areIconsObviouslyDifferent(getBigPicture(), otherS.getBigPicture());
7513         }
7514 
areIconsObviouslyDifferent(Icon a, Icon b)7515         private static boolean areIconsObviouslyDifferent(Icon a, Icon b) {
7516             if (a == b) {
7517                 return false;
7518             }
7519             if (a == null || b == null) {
7520                 return true;
7521             }
7522             if (a.sameAs(b)) {
7523                 return false;
7524             }
7525             final int aType = a.getType();
7526             if (aType != b.getType()) {
7527                 return true;
7528             }
7529             if (aType == Icon.TYPE_BITMAP || aType == Icon.TYPE_ADAPTIVE_BITMAP) {
7530                 final Bitmap aBitmap = a.getBitmap();
7531                 final Bitmap bBitmap = b.getBitmap();
7532                 return aBitmap.getWidth() != bBitmap.getWidth()
7533                         || aBitmap.getHeight() != bBitmap.getHeight()
7534                         || aBitmap.getConfig() != bBitmap.getConfig()
7535                         || aBitmap.getGenerationId() != bBitmap.getGenerationId();
7536             }
7537             return true;
7538         }
7539     }
7540 
7541     /**
7542      * Helper class for generating large-format notifications that include a lot of text.
7543      *
7544      * Here's how you'd set the <code>BigTextStyle</code> on a notification:
7545      * <pre class="prettyprint">
7546      * Notification notif = new Notification.Builder(mContext)
7547      *     .setContentTitle(&quot;New mail from &quot; + sender.toString())
7548      *     .setContentText(subject)
7549      *     .setSmallIcon(R.drawable.new_mail)
7550      *     .setLargeIcon(aBitmap)
7551      *     .setStyle(new Notification.BigTextStyle()
7552      *         .bigText(aVeryLongString))
7553      *     .build();
7554      * </pre>
7555      *
7556      * @see Notification#bigContentView
7557      */
7558     public static class BigTextStyle extends Style {
7559 
7560         private CharSequence mBigText;
7561 
BigTextStyle()7562         public BigTextStyle() {
7563         }
7564 
7565         /**
7566          * @deprecated use {@code BigTextStyle()}.
7567          */
7568         @Deprecated
BigTextStyle(Builder builder)7569         public BigTextStyle(Builder builder) {
7570             setBuilder(builder);
7571         }
7572 
7573         /**
7574          * Overrides ContentTitle in the big form of the template.
7575          * This defaults to the value passed to setContentTitle().
7576          */
setBigContentTitle(CharSequence title)7577         public BigTextStyle setBigContentTitle(CharSequence title) {
7578             internalSetBigContentTitle(safeCharSequence(title));
7579             return this;
7580         }
7581 
7582         /**
7583          * Set the first line of text after the detail section in the big form of the template.
7584          */
setSummaryText(CharSequence cs)7585         public BigTextStyle setSummaryText(CharSequence cs) {
7586             internalSetSummaryText(safeCharSequence(cs));
7587             return this;
7588         }
7589 
7590         /**
7591          * Provide the longer text to be displayed in the big form of the
7592          * template in place of the content text.
7593          */
bigText(CharSequence cs)7594         public BigTextStyle bigText(CharSequence cs) {
7595             mBigText = safeCharSequence(cs);
7596             return this;
7597         }
7598 
7599         /**
7600          * @hide
7601          */
getBigText()7602         public CharSequence getBigText() {
7603             return mBigText;
7604         }
7605 
7606         /**
7607          * @hide
7608          */
addExtras(Bundle extras)7609         public void addExtras(Bundle extras) {
7610             super.addExtras(extras);
7611 
7612             extras.putCharSequence(EXTRA_BIG_TEXT, mBigText);
7613         }
7614 
7615         /**
7616          * @hide
7617          */
7618         @Override
restoreFromExtras(Bundle extras)7619         protected void restoreFromExtras(Bundle extras) {
7620             super.restoreFromExtras(extras);
7621 
7622             mBigText = extras.getCharSequence(EXTRA_BIG_TEXT);
7623         }
7624 
7625         /**
7626          * @param increasedHeight true if this layout be created with an increased height.
7627          *
7628          * @hide
7629          */
7630         @Override
makeContentView(boolean increasedHeight)7631         public RemoteViews makeContentView(boolean increasedHeight) {
7632             if (increasedHeight) {
7633                 ArrayList<Action> originalActions = mBuilder.mActions;
7634                 mBuilder.mActions = new ArrayList<>();
7635                 RemoteViews remoteViews = makeBigContentView();
7636                 mBuilder.mActions = originalActions;
7637                 return remoteViews;
7638             }
7639             return super.makeContentView(increasedHeight);
7640         }
7641 
7642         /**
7643          * @hide
7644          */
7645         @Override
makeHeadsUpContentView(boolean increasedHeight)7646         public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
7647             if (increasedHeight && mBuilder.mActions.size() > 0) {
7648                 // TODO(b/163626038): pass VIEW_TYPE_HEADS_UP?
7649                 return makeBigContentView();
7650             }
7651             return super.makeHeadsUpContentView(increasedHeight);
7652         }
7653 
7654         /**
7655          * @hide
7656          */
makeBigContentView()7657         public RemoteViews makeBigContentView() {
7658             StandardTemplateParams p = mBuilder.mParams.reset()
7659                     .viewType(StandardTemplateParams.VIEW_TYPE_BIG)
7660                     .allowTextWithProgress(true)
7661                     .textViewId(R.id.big_text)
7662                     .fillTextsFrom(mBuilder);
7663 
7664             // Replace the text with the big text, but only if the big text is not empty.
7665             CharSequence bigTextText = mBuilder.processLegacyText(mBigText);
7666             if (!TextUtils.isEmpty(bigTextText)) {
7667                 p.text(bigTextText);
7668             }
7669 
7670             return getStandardView(mBuilder.getBigTextLayoutResource(), p, null /* result */);
7671         }
7672 
7673         /**
7674          * @hide
7675          * Spans are ignored when comparing text for visual difference.
7676          */
7677         @Override
areNotificationsVisiblyDifferent(Style other)7678         public boolean areNotificationsVisiblyDifferent(Style other) {
7679             if (other == null || getClass() != other.getClass()) {
7680                 return true;
7681             }
7682             BigTextStyle newS = (BigTextStyle) other;
7683             return !Objects.equals(String.valueOf(getBigText()), String.valueOf(newS.getBigText()));
7684         }
7685 
7686     }
7687 
7688     /**
7689      * Helper class for generating large-format notifications that include multiple back-and-forth
7690      * messages of varying types between any number of people.
7691      *
7692      * <p>
7693      * If the platform does not provide large-format notifications, this method has no effect. The
7694      * user will always see the normal notification view.
7695      *
7696      * <p>
7697      * If the app is targeting Android P and above, it is required to use the {@link Person}
7698      * class in order to get an optimal rendering of the notification and its avatars. For
7699      * conversations involving multiple people, the app should also make sure that it marks the
7700      * conversation as a group with {@link #setGroupConversation(boolean)}.
7701      *
7702      * <p>
7703      * This class is a "rebuilder": It attaches to a Builder object and modifies its behavior.
7704      * Here's an example of how this may be used:
7705      * <pre class="prettyprint">
7706      *
7707      * Person user = new Person.Builder().setIcon(userIcon).setName(userName).build();
7708      * MessagingStyle style = new MessagingStyle(user)
7709      *      .addMessage(messages[1].getText(), messages[1].getTime(), messages[1].getPerson())
7710      *      .addMessage(messages[2].getText(), messages[2].getTime(), messages[2].getPerson())
7711      *      .setGroupConversation(hasMultiplePeople());
7712      *
7713      * Notification noti = new Notification.Builder()
7714      *     .setContentTitle(&quot;2 new messages with &quot; + sender.toString())
7715      *     .setContentText(subject)
7716      *     .setSmallIcon(R.drawable.new_message)
7717      *     .setLargeIcon(aBitmap)
7718      *     .setStyle(style)
7719      *     .build();
7720      * </pre>
7721      */
7722     public static class MessagingStyle extends Style {
7723 
7724         /**
7725          * The maximum number of messages that will be retained in the Notification itself (the
7726          * number displayed is up to the platform).
7727          */
7728         public static final int MAXIMUM_RETAINED_MESSAGES = 25;
7729 
7730 
7731         /** @hide */
7732         public static final int CONVERSATION_TYPE_LEGACY = 0;
7733         /** @hide */
7734         public static final int CONVERSATION_TYPE_NORMAL = 1;
7735         /** @hide */
7736         public static final int CONVERSATION_TYPE_IMPORTANT = 2;
7737 
7738         /** @hide */
7739         @IntDef(prefix = {"CONVERSATION_TYPE_"}, value = {
7740                 CONVERSATION_TYPE_LEGACY, CONVERSATION_TYPE_NORMAL, CONVERSATION_TYPE_IMPORTANT
7741         })
7742         @Retention(RetentionPolicy.SOURCE)
7743         public @interface ConversationType {}
7744 
7745         @NonNull Person mUser;
7746         @Nullable CharSequence mConversationTitle;
7747         @Nullable Icon mShortcutIcon;
7748         List<Message> mMessages = new ArrayList<>();
7749         List<Message> mHistoricMessages = new ArrayList<>();
7750         boolean mIsGroupConversation;
7751         @ConversationType int mConversationType = CONVERSATION_TYPE_LEGACY;
7752         int mUnreadMessageCount;
7753 
MessagingStyle()7754         MessagingStyle() {
7755         }
7756 
7757         /**
7758          * @param userDisplayName Required - the name to be displayed for any replies sent by the
7759          * user before the posting app reposts the notification with those messages after they've
7760          * been actually sent and in previous messages sent by the user added in
7761          * {@link #addMessage(Notification.MessagingStyle.Message)}
7762          *
7763          * @deprecated use {@code MessagingStyle(Person)}
7764          */
MessagingStyle(@onNull CharSequence userDisplayName)7765         public MessagingStyle(@NonNull CharSequence userDisplayName) {
7766             this(new Person.Builder().setName(userDisplayName).build());
7767         }
7768 
7769         /**
7770          * @param user Required - The person displayed for any messages that are sent by the
7771          * user. Any messages added with {@link #addMessage(Notification.MessagingStyle.Message)}
7772          * who don't have a Person associated with it will be displayed as if they were sent
7773          * by this user. The user also needs to have a valid name associated with it, which will
7774          * be enforced starting in Android P.
7775          */
MessagingStyle(@onNull Person user)7776         public MessagingStyle(@NonNull Person user) {
7777             mUser = user;
7778         }
7779 
7780         /**
7781          * Validate that this style was properly composed. This is called at build time.
7782          * @hide
7783          */
7784         @Override
validate(Context context)7785         public void validate(Context context) {
7786             super.validate(context);
7787             if (context.getApplicationInfo().targetSdkVersion
7788                     >= Build.VERSION_CODES.P && (mUser == null || mUser.getName() == null)) {
7789                 throw new RuntimeException("User must be valid and have a name.");
7790             }
7791         }
7792 
7793         /**
7794          * @return the text that should be displayed in the statusBar when heads upped.
7795          * If {@code null} is returned, the default implementation will be used.
7796          *
7797          * @hide
7798          */
7799         @Override
getHeadsUpStatusBarText()7800         public CharSequence getHeadsUpStatusBarText() {
7801             CharSequence conversationTitle = !TextUtils.isEmpty(super.mBigContentTitle)
7802                     ? super.mBigContentTitle
7803                     : mConversationTitle;
7804             if (mConversationType == CONVERSATION_TYPE_LEGACY
7805                     && !TextUtils.isEmpty(conversationTitle) && !hasOnlyWhiteSpaceSenders()) {
7806                 return conversationTitle;
7807             }
7808             return null;
7809         }
7810 
7811         /**
7812          * @return the user to be displayed for any replies sent by the user
7813          */
7814         @NonNull
getUser()7815         public Person getUser() {
7816             return mUser;
7817         }
7818 
7819         /**
7820          * Returns the name to be displayed for any replies sent by the user
7821          *
7822          * @deprecated use {@link #getUser()} instead
7823          */
getUserDisplayName()7824         public CharSequence getUserDisplayName() {
7825             return mUser.getName();
7826         }
7827 
7828         /**
7829          * Sets the title to be displayed on this conversation. May be set to {@code null}.
7830          *
7831          * <p>Starting in {@link Build.VERSION_CODES#R, this conversation title will be ignored if a
7832          * valid shortcutId is added via {@link Notification.Builder#setShortcutId(String)}. In this
7833          * case, {@link ShortcutInfo#getLongLabel()} (or, if missing,
7834          * {@link ShortcutInfo#getShortLabel()}) will be shown as the conversation title
7835          * instead.
7836          *
7837          * <p>This API's behavior was changed in SDK version {@link Build.VERSION_CODES#P}. If your
7838          * application's target version is less than {@link Build.VERSION_CODES#P}, setting a
7839          * conversation title to a non-null value will make {@link #isGroupConversation()} return
7840          * {@code true} and passing {@code null} will make it return {@code false}. In
7841          * {@link Build.VERSION_CODES#P} and beyond, use {@link #setGroupConversation(boolean)}
7842          * to set group conversation status.
7843          *
7844          * @param conversationTitle Title displayed for this conversation
7845          * @return this object for method chaining
7846          */
setConversationTitle(@ullable CharSequence conversationTitle)7847         public MessagingStyle setConversationTitle(@Nullable CharSequence conversationTitle) {
7848             mConversationTitle = conversationTitle;
7849             return this;
7850         }
7851 
7852         /**
7853          * Return the title to be displayed on this conversation. May return {@code null}.
7854          */
7855         @Nullable
getConversationTitle()7856         public CharSequence getConversationTitle() {
7857             return mConversationTitle;
7858         }
7859 
7860         /**
7861          * Sets the icon to be displayed on the conversation, derived from the shortcutId.
7862          *
7863          * @hide
7864          */
setShortcutIcon(@ullable Icon conversationIcon)7865         public MessagingStyle setShortcutIcon(@Nullable Icon conversationIcon) {
7866             mShortcutIcon = conversationIcon;
7867             return this;
7868         }
7869 
7870         /**
7871          * Return the icon to be displayed on this conversation, derived from the shortcutId. May
7872          * return {@code null}.
7873          *
7874          * @hide
7875          */
7876         @Nullable
getShortcutIcon()7877         public Icon getShortcutIcon() {
7878             return mShortcutIcon;
7879         }
7880 
7881         /**
7882          * Sets the conversation type of this MessageStyle notification.
7883          * {@link #CONVERSATION_TYPE_LEGACY} will use the "older" layout from pre-R,
7884          * {@link #CONVERSATION_TYPE_NORMAL} will use the new "conversation" layout, and
7885          * {@link #CONVERSATION_TYPE_IMPORTANT} will add additional "important" treatments.
7886          *
7887          * @hide
7888          */
setConversationType(@onversationType int conversationType)7889         public MessagingStyle setConversationType(@ConversationType int conversationType) {
7890             mConversationType = conversationType;
7891             return this;
7892         }
7893 
7894         /** @hide */
7895         @ConversationType
getConversationType()7896         public int getConversationType() {
7897             return mConversationType;
7898         }
7899 
7900         /** @hide */
getUnreadMessageCount()7901         public int getUnreadMessageCount() {
7902             return mUnreadMessageCount;
7903         }
7904 
7905         /** @hide */
setUnreadMessageCount(int unreadMessageCount)7906         public MessagingStyle setUnreadMessageCount(int unreadMessageCount) {
7907             mUnreadMessageCount = unreadMessageCount;
7908             return this;
7909         }
7910 
7911         /**
7912          * Adds a message for display by this notification. Convenience call for a simple
7913          * {@link Message} in {@link #addMessage(Notification.MessagingStyle.Message)}.
7914          * @param text A {@link CharSequence} to be displayed as the message content
7915          * @param timestamp Time at which the message arrived
7916          * @param sender A {@link CharSequence} to be used for displaying the name of the
7917          * sender. Should be <code>null</code> for messages by the current user, in which case
7918          * the platform will insert {@link #getUserDisplayName()}.
7919          * Should be unique amongst all individuals in the conversation, and should be
7920          * consistent during re-posts of the notification.
7921          *
7922          * @see Message#Message(CharSequence, long, CharSequence)
7923          *
7924          * @return this object for method chaining
7925          *
7926          * @deprecated use {@link #addMessage(CharSequence, long, Person)}
7927          */
addMessage(CharSequence text, long timestamp, CharSequence sender)7928         public MessagingStyle addMessage(CharSequence text, long timestamp, CharSequence sender) {
7929             return addMessage(text, timestamp,
7930                     sender == null ? null : new Person.Builder().setName(sender).build());
7931         }
7932 
7933         /**
7934          * Adds a message for display by this notification. Convenience call for a simple
7935          * {@link Message} in {@link #addMessage(Notification.MessagingStyle.Message)}.
7936          * @param text A {@link CharSequence} to be displayed as the message content
7937          * @param timestamp Time at which the message arrived
7938          * @param sender The {@link Person} who sent the message.
7939          * Should be <code>null</code> for messages by the current user, in which case
7940          * the platform will insert the user set in {@code MessagingStyle(Person)}.
7941          *
7942          * @see Message#Message(CharSequence, long, CharSequence)
7943          *
7944          * @return this object for method chaining
7945          */
addMessage(@onNull CharSequence text, long timestamp, @Nullable Person sender)7946         public MessagingStyle addMessage(@NonNull CharSequence text, long timestamp,
7947                 @Nullable Person sender) {
7948             return addMessage(new Message(text, timestamp, sender));
7949         }
7950 
7951         /**
7952          * Adds a {@link Message} for display in this notification.
7953          *
7954          * <p>The messages should be added in chronologic order, i.e. the oldest first,
7955          * the newest last.
7956          *
7957          * @param message The {@link Message} to be displayed
7958          * @return this object for method chaining
7959          */
addMessage(Message message)7960         public MessagingStyle addMessage(Message message) {
7961             mMessages.add(message);
7962             if (mMessages.size() > MAXIMUM_RETAINED_MESSAGES) {
7963                 mMessages.remove(0);
7964             }
7965             return this;
7966         }
7967 
7968         /**
7969          * Adds a {@link Message} for historic context in this notification.
7970          *
7971          * <p>Messages should be added as historic if they are not the main subject of the
7972          * notification but may give context to a conversation. The system may choose to present
7973          * them only when relevant, e.g. when replying to a message through a {@link RemoteInput}.
7974          *
7975          * <p>The messages should be added in chronologic order, i.e. the oldest first,
7976          * the newest last.
7977          *
7978          * @param message The historic {@link Message} to be added
7979          * @return this object for method chaining
7980          */
addHistoricMessage(Message message)7981         public MessagingStyle addHistoricMessage(Message message) {
7982             mHistoricMessages.add(message);
7983             if (mHistoricMessages.size() > MAXIMUM_RETAINED_MESSAGES) {
7984                 mHistoricMessages.remove(0);
7985             }
7986             return this;
7987         }
7988 
7989         /**
7990          * Gets the list of {@code Message} objects that represent the notification
7991          */
getMessages()7992         public List<Message> getMessages() {
7993             return mMessages;
7994         }
7995 
7996         /**
7997          * Gets the list of historic {@code Message}s in the notification.
7998          */
getHistoricMessages()7999         public List<Message> getHistoricMessages() {
8000             return mHistoricMessages;
8001         }
8002 
8003         /**
8004          * Sets whether this conversation notification represents a group. If the app is targeting
8005          * Android P, this is required if the app wants to display the largeIcon set with
8006          * {@link Notification.Builder#setLargeIcon(Bitmap)}, otherwise it will be hidden.
8007          *
8008          * @param isGroupConversation {@code true} if the conversation represents a group,
8009          * {@code false} otherwise.
8010          * @return this object for method chaining
8011          */
setGroupConversation(boolean isGroupConversation)8012         public MessagingStyle setGroupConversation(boolean isGroupConversation) {
8013             mIsGroupConversation = isGroupConversation;
8014             return this;
8015         }
8016 
8017         /**
8018          * Returns {@code true} if this notification represents a group conversation, otherwise
8019          * {@code false}.
8020          *
8021          * <p> If the application that generated this {@link MessagingStyle} targets an SDK version
8022          * less than {@link Build.VERSION_CODES#P}, this method becomes dependent on whether or
8023          * not the conversation title is set; returning {@code true} if the conversation title is
8024          * a non-null value, or {@code false} otherwise. From {@link Build.VERSION_CODES#P} forward,
8025          * this method returns what's set by {@link #setGroupConversation(boolean)} allowing for
8026          * named, non-group conversations.
8027          *
8028          * @see #setConversationTitle(CharSequence)
8029          */
isGroupConversation()8030         public boolean isGroupConversation() {
8031             // When target SDK version is < P, a non-null conversation title dictates if this is
8032             // as group conversation.
8033             if (mBuilder != null
8034                     && mBuilder.mContext.getApplicationInfo().targetSdkVersion
8035                             < Build.VERSION_CODES.P) {
8036                 return mConversationTitle != null;
8037             }
8038 
8039             return mIsGroupConversation;
8040         }
8041 
8042         /**
8043          * @hide
8044          */
8045         @Override
addExtras(Bundle extras)8046         public void addExtras(Bundle extras) {
8047             super.addExtras(extras);
8048             if (mUser != null) {
8049                 // For legacy usages
8050                 extras.putCharSequence(EXTRA_SELF_DISPLAY_NAME, mUser.getName());
8051                 extras.putParcelable(EXTRA_MESSAGING_PERSON, mUser);
8052             }
8053             if (mConversationTitle != null) {
8054                 extras.putCharSequence(EXTRA_CONVERSATION_TITLE, mConversationTitle);
8055             }
8056             if (!mMessages.isEmpty()) { extras.putParcelableArray(EXTRA_MESSAGES,
8057                     Message.getBundleArrayForMessages(mMessages));
8058             }
8059             if (!mHistoricMessages.isEmpty()) { extras.putParcelableArray(EXTRA_HISTORIC_MESSAGES,
8060                     Message.getBundleArrayForMessages(mHistoricMessages));
8061             }
8062             if (mShortcutIcon != null) {
8063                 extras.putParcelable(EXTRA_CONVERSATION_ICON, mShortcutIcon);
8064             }
8065             extras.putInt(EXTRA_CONVERSATION_UNREAD_MESSAGE_COUNT, mUnreadMessageCount);
8066 
8067             fixTitleAndTextExtras(extras);
8068             extras.putBoolean(EXTRA_IS_GROUP_CONVERSATION, mIsGroupConversation);
8069         }
8070 
fixTitleAndTextExtras(Bundle extras)8071         private void fixTitleAndTextExtras(Bundle extras) {
8072             Message m = findLatestIncomingMessage();
8073             CharSequence text = (m == null) ? null : m.mText;
8074             CharSequence sender = m == null ? null
8075                     : m.mSender == null || TextUtils.isEmpty(m.mSender.getName())
8076                             ? mUser.getName() : m.mSender.getName();
8077             CharSequence title;
8078             if (!TextUtils.isEmpty(mConversationTitle)) {
8079                 if (!TextUtils.isEmpty(sender)) {
8080                     BidiFormatter bidi = BidiFormatter.getInstance();
8081                     title = mBuilder.mContext.getString(
8082                             com.android.internal.R.string.notification_messaging_title_template,
8083                             bidi.unicodeWrap(mConversationTitle), bidi.unicodeWrap(sender));
8084                 } else {
8085                     title = mConversationTitle;
8086                 }
8087             } else {
8088                 title = sender;
8089             }
8090 
8091             if (title != null) {
8092                 extras.putCharSequence(EXTRA_TITLE, title);
8093             }
8094             if (text != null) {
8095                 extras.putCharSequence(EXTRA_TEXT, text);
8096             }
8097         }
8098 
8099         /**
8100          * @hide
8101          */
8102         @Override
restoreFromExtras(Bundle extras)8103         protected void restoreFromExtras(Bundle extras) {
8104             super.restoreFromExtras(extras);
8105 
8106             mUser = extras.getParcelable(EXTRA_MESSAGING_PERSON);
8107             if (mUser == null) {
8108                 CharSequence displayName = extras.getCharSequence(EXTRA_SELF_DISPLAY_NAME);
8109                 mUser = new Person.Builder().setName(displayName).build();
8110             }
8111             mConversationTitle = extras.getCharSequence(EXTRA_CONVERSATION_TITLE);
8112             Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES);
8113             mMessages = Message.getMessagesFromBundleArray(messages);
8114             Parcelable[] histMessages = extras.getParcelableArray(EXTRA_HISTORIC_MESSAGES);
8115             mHistoricMessages = Message.getMessagesFromBundleArray(histMessages);
8116             mIsGroupConversation = extras.getBoolean(EXTRA_IS_GROUP_CONVERSATION);
8117             mUnreadMessageCount = extras.getInt(EXTRA_CONVERSATION_UNREAD_MESSAGE_COUNT);
8118             mShortcutIcon = extras.getParcelable(EXTRA_CONVERSATION_ICON);
8119         }
8120 
8121         /**
8122          * @hide
8123          */
8124         @Override
makeContentView(boolean increasedHeight)8125         public RemoteViews makeContentView(boolean increasedHeight) {
8126             // All messaging templates contain the actions
8127             ArrayList<Action> originalActions = mBuilder.mActions;
8128             try {
8129                 mBuilder.mActions = new ArrayList<>();
8130                 return makeMessagingView(StandardTemplateParams.VIEW_TYPE_NORMAL);
8131             } finally {
8132                 mBuilder.mActions = originalActions;
8133             }
8134         }
8135 
8136         /**
8137          * @hide
8138          * Spans are ignored when comparing text for visual difference.
8139          */
8140         @Override
areNotificationsVisiblyDifferent(Style other)8141         public boolean areNotificationsVisiblyDifferent(Style other) {
8142             if (other == null || getClass() != other.getClass()) {
8143                 return true;
8144             }
8145             MessagingStyle newS = (MessagingStyle) other;
8146             List<MessagingStyle.Message> oldMs = getMessages();
8147             List<MessagingStyle.Message> newMs = newS.getMessages();
8148 
8149             if (oldMs == null || newMs == null) {
8150                 newMs = new ArrayList<>();
8151             }
8152 
8153             int n = oldMs.size();
8154             if (n != newMs.size()) {
8155                 return true;
8156             }
8157             for (int i = 0; i < n; i++) {
8158                 MessagingStyle.Message oldM = oldMs.get(i);
8159                 MessagingStyle.Message newM = newMs.get(i);
8160                 if (!Objects.equals(
8161                         String.valueOf(oldM.getText()),
8162                         String.valueOf(newM.getText()))) {
8163                     return true;
8164                 }
8165                 if (!Objects.equals(oldM.getDataUri(), newM.getDataUri())) {
8166                     return true;
8167                 }
8168                 String oldSender = String.valueOf(oldM.getSenderPerson() == null
8169                         ? oldM.getSender()
8170                         : oldM.getSenderPerson().getName());
8171                 String newSender = String.valueOf(newM.getSenderPerson() == null
8172                         ? newM.getSender()
8173                         : newM.getSenderPerson().getName());
8174                 if (!Objects.equals(oldSender, newSender)) {
8175                     return true;
8176                 }
8177 
8178                 String oldKey = oldM.getSenderPerson() == null
8179                         ? null : oldM.getSenderPerson().getKey();
8180                 String newKey = newM.getSenderPerson() == null
8181                         ? null : newM.getSenderPerson().getKey();
8182                 if (!Objects.equals(oldKey, newKey)) {
8183                     return true;
8184                 }
8185                 // Other fields (like timestamp) intentionally excluded
8186             }
8187             return false;
8188         }
8189 
findLatestIncomingMessage()8190         private Message findLatestIncomingMessage() {
8191             return findLatestIncomingMessage(mMessages);
8192         }
8193 
8194         /**
8195          * @hide
8196          */
8197         @Nullable
findLatestIncomingMessage( List<Message> messages)8198         public static Message findLatestIncomingMessage(
8199                 List<Message> messages) {
8200             for (int i = messages.size() - 1; i >= 0; i--) {
8201                 Message m = messages.get(i);
8202                 // Incoming messages have a non-empty sender.
8203                 if (m.mSender != null && !TextUtils.isEmpty(m.mSender.getName())) {
8204                     return m;
8205                 }
8206             }
8207             if (!messages.isEmpty()) {
8208                 // No incoming messages, fall back to outgoing message
8209                 return messages.get(messages.size() - 1);
8210             }
8211             return null;
8212         }
8213 
8214         /**
8215          * @hide
8216          */
8217         @Override
makeBigContentView()8218         public RemoteViews makeBigContentView() {
8219             return makeMessagingView(StandardTemplateParams.VIEW_TYPE_BIG);
8220         }
8221 
8222         /**
8223          * Create a messaging layout.
8224          *
8225          * @param viewType one of StandardTemplateParams.VIEW_TYPE_NORMAL, VIEW_TYPE_BIG,
8226          *                VIEW_TYPE_HEADS_UP
8227          * @return the created remoteView.
8228          */
8229         @NonNull
makeMessagingView(int viewType)8230         private RemoteViews makeMessagingView(int viewType) {
8231             boolean isCollapsed = viewType != StandardTemplateParams.VIEW_TYPE_BIG;
8232             boolean hideRightIcons = viewType != StandardTemplateParams.VIEW_TYPE_NORMAL;
8233             boolean isConversationLayout = mConversationType != CONVERSATION_TYPE_LEGACY;
8234             boolean isImportantConversation = mConversationType == CONVERSATION_TYPE_IMPORTANT;
8235             boolean isHeaderless = !isConversationLayout && isCollapsed;
8236 
8237             CharSequence conversationTitle = !TextUtils.isEmpty(super.mBigContentTitle)
8238                     ? super.mBigContentTitle
8239                     : mConversationTitle;
8240             boolean atLeastP = mBuilder.mContext.getApplicationInfo().targetSdkVersion
8241                     >= Build.VERSION_CODES.P;
8242             boolean isOneToOne;
8243             CharSequence nameReplacement = null;
8244             if (!atLeastP) {
8245                 isOneToOne = TextUtils.isEmpty(conversationTitle);
8246                 if (hasOnlyWhiteSpaceSenders()) {
8247                     isOneToOne = true;
8248                     nameReplacement = conversationTitle;
8249                     conversationTitle = null;
8250                 }
8251             } else {
8252                 isOneToOne = !isGroupConversation();
8253             }
8254             if (isHeaderless && isOneToOne && TextUtils.isEmpty(conversationTitle)) {
8255                 conversationTitle = getOtherPersonName();
8256             }
8257 
8258             Icon largeIcon = mBuilder.mN.mLargeIcon;
8259             TemplateBindResult bindResult = new TemplateBindResult();
8260             StandardTemplateParams p = mBuilder.mParams.reset()
8261                     .viewType(viewType)
8262                     .highlightExpander(isConversationLayout)
8263                     .hideProgress(true)
8264                     .title(isHeaderless ? conversationTitle : null)
8265                     .text(null)
8266                     .hideLeftIcon(isOneToOne)
8267                     .hideRightIcon(hideRightIcons || isOneToOne)
8268                     .headerTextSecondary(isHeaderless ? null : conversationTitle);
8269             RemoteViews contentView = mBuilder.applyStandardTemplateWithActions(
8270                     isConversationLayout
8271                             ? mBuilder.getConversationLayoutResource()
8272                             : isCollapsed
8273                                     ? mBuilder.getMessagingLayoutResource()
8274                                     : mBuilder.getBigMessagingLayoutResource(),
8275                     p,
8276                     bindResult);
8277             if (isConversationLayout) {
8278                 mBuilder.setTextViewColorPrimary(contentView, R.id.conversation_text, p);
8279                 mBuilder.setTextViewColorSecondary(contentView, R.id.app_name_divider, p);
8280             }
8281 
8282             addExtras(mBuilder.mN.extras);
8283             contentView.setInt(R.id.status_bar_latest_event_content, "setLayoutColor",
8284                     mBuilder.getSmallIconColor(p));
8285             contentView.setInt(R.id.status_bar_latest_event_content, "setSenderTextColor",
8286                     mBuilder.getPrimaryTextColor(p));
8287             contentView.setInt(R.id.status_bar_latest_event_content, "setMessageTextColor",
8288                     mBuilder.getSecondaryTextColor(p));
8289             contentView.setInt(R.id.status_bar_latest_event_content,
8290                     "setNotificationBackgroundColor",
8291                     mBuilder.getBackgroundColor(p));
8292             contentView.setBoolean(R.id.status_bar_latest_event_content, "setIsCollapsed",
8293                     isCollapsed);
8294             contentView.setIcon(R.id.status_bar_latest_event_content, "setAvatarReplacement",
8295                     mBuilder.mN.mLargeIcon);
8296             contentView.setCharSequence(R.id.status_bar_latest_event_content, "setNameReplacement",
8297                     nameReplacement);
8298             contentView.setBoolean(R.id.status_bar_latest_event_content, "setIsOneToOne",
8299                     isOneToOne);
8300             contentView.setCharSequence(R.id.status_bar_latest_event_content,
8301                     "setConversationTitle", conversationTitle);
8302             if (isConversationLayout) {
8303                 contentView.setIcon(R.id.status_bar_latest_event_content,
8304                         "setShortcutIcon", mShortcutIcon);
8305                 contentView.setBoolean(R.id.status_bar_latest_event_content,
8306                         "setIsImportantConversation", isImportantConversation);
8307             }
8308             if (isHeaderless) {
8309                 // Collapsed legacy messaging style has a 1-line limit.
8310                 contentView.setInt(R.id.notification_messaging, "setMaxDisplayedLines", 1);
8311             }
8312             contentView.setIcon(R.id.status_bar_latest_event_content, "setLargeIcon",
8313                     largeIcon);
8314             contentView.setBundle(R.id.status_bar_latest_event_content, "setData",
8315                     mBuilder.mN.extras);
8316             return contentView;
8317         }
8318 
getKey(Person person)8319         private CharSequence getKey(Person person) {
8320             return person == null ? null
8321                     : person.getKey() == null ? person.getName() : person.getKey();
8322         }
8323 
getOtherPersonName()8324         private CharSequence getOtherPersonName() {
8325             CharSequence userKey = getKey(mUser);
8326             for (int i = mMessages.size() - 1; i >= 0; i--) {
8327                 Person sender = mMessages.get(i).getSenderPerson();
8328                 if (sender != null && !TextUtils.equals(userKey, getKey(sender))) {
8329                     return sender.getName();
8330                 }
8331             }
8332             return null;
8333         }
8334 
hasOnlyWhiteSpaceSenders()8335         private boolean hasOnlyWhiteSpaceSenders() {
8336             for (int i = 0; i < mMessages.size(); i++) {
8337                 Message m = mMessages.get(i);
8338                 Person sender = m.getSenderPerson();
8339                 if (sender != null && !isWhiteSpace(sender.getName())) {
8340                     return false;
8341                 }
8342             }
8343             return true;
8344         }
8345 
isWhiteSpace(CharSequence sender)8346         private boolean isWhiteSpace(CharSequence sender) {
8347             if (TextUtils.isEmpty(sender)) {
8348                 return true;
8349             }
8350             if (sender.toString().matches("^\\s*$")) {
8351                 return true;
8352             }
8353             // Let's check if we only have 0 whitespace chars. Some apps did this as a workaround
8354             // For the presentation that we had.
8355             for (int i = 0; i < sender.length(); i++) {
8356                 char c = sender.charAt(i);
8357                 if (c != '\u200B') {
8358                     return false;
8359                 }
8360             }
8361             return true;
8362         }
8363 
8364         /**
8365          * @hide
8366          */
8367         @Override
makeHeadsUpContentView(boolean increasedHeight)8368         public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
8369             return makeMessagingView(StandardTemplateParams.VIEW_TYPE_HEADS_UP);
8370         }
8371 
8372         public static final class Message {
8373             /** @hide */
8374             public static final String KEY_TEXT = "text";
8375             static final String KEY_TIMESTAMP = "time";
8376             static final String KEY_SENDER = "sender";
8377             static final String KEY_SENDER_PERSON = "sender_person";
8378             static final String KEY_DATA_MIME_TYPE = "type";
8379             static final String KEY_DATA_URI= "uri";
8380             static final String KEY_EXTRAS_BUNDLE = "extras";
8381             static final String KEY_REMOTE_INPUT_HISTORY = "remote_input_history";
8382 
8383             private final CharSequence mText;
8384             private final long mTimestamp;
8385             @Nullable
8386             private final Person mSender;
8387             /** True if this message was generated from the extra
8388              *  {@link Notification#EXTRA_REMOTE_INPUT_HISTORY_ITEMS}
8389              */
8390             private final boolean mRemoteInputHistory;
8391 
8392             private Bundle mExtras = new Bundle();
8393             private String mDataMimeType;
8394             private Uri mDataUri;
8395 
8396             /**
8397              * Constructor
8398              * @param text A {@link CharSequence} to be displayed as the message content
8399              * @param timestamp Time at which the message arrived
8400              * @param sender A {@link CharSequence} to be used for displaying the name of the
8401              * sender. Should be <code>null</code> for messages by the current user, in which case
8402              * the platform will insert {@link MessagingStyle#getUserDisplayName()}.
8403              * Should be unique amongst all individuals in the conversation, and should be
8404              * consistent during re-posts of the notification.
8405              *
8406              *  @deprecated use {@code Message(CharSequence, long, Person)}
8407              */
Message(CharSequence text, long timestamp, CharSequence sender)8408             public Message(CharSequence text, long timestamp, CharSequence sender){
8409                 this(text, timestamp, sender == null ? null
8410                         : new Person.Builder().setName(sender).build());
8411             }
8412 
8413             /**
8414              * Constructor
8415              * @param text A {@link CharSequence} to be displayed as the message content
8416              * @param timestamp Time at which the message arrived
8417              * @param sender The {@link Person} who sent the message.
8418              * Should be <code>null</code> for messages by the current user, in which case
8419              * the platform will insert the user set in {@code MessagingStyle(Person)}.
8420              * <p>
8421              * The person provided should contain an Icon, set with
8422              * {@link Person.Builder#setIcon(Icon)} and also have a name provided
8423              * with {@link Person.Builder#setName(CharSequence)}. If multiple users have the same
8424              * name, consider providing a key with {@link Person.Builder#setKey(String)} in order
8425              * to differentiate between the different users.
8426              * </p>
8427              */
Message(@onNull CharSequence text, long timestamp, @Nullable Person sender)8428             public Message(@NonNull CharSequence text, long timestamp, @Nullable Person sender) {
8429                 this(text, timestamp, sender, false /* remoteHistory */);
8430             }
8431 
8432             /**
8433              * Constructor
8434              * @param text A {@link CharSequence} to be displayed as the message content
8435              * @param timestamp Time at which the message arrived
8436              * @param sender The {@link Person} who sent the message.
8437              * Should be <code>null</code> for messages by the current user, in which case
8438              * the platform will insert the user set in {@code MessagingStyle(Person)}.
8439              * @param remoteInputHistory True if the messages was generated from the extra
8440              * {@link Notification#EXTRA_REMOTE_INPUT_HISTORY_ITEMS}.
8441              * <p>
8442              * The person provided should contain an Icon, set with
8443              * {@link Person.Builder#setIcon(Icon)} and also have a name provided
8444              * with {@link Person.Builder#setName(CharSequence)}. If multiple users have the same
8445              * name, consider providing a key with {@link Person.Builder#setKey(String)} in order
8446              * to differentiate between the different users.
8447              * </p>
8448              * @hide
8449              */
Message(@onNull CharSequence text, long timestamp, @Nullable Person sender, boolean remoteInputHistory)8450             public Message(@NonNull CharSequence text, long timestamp, @Nullable Person sender,
8451                     boolean remoteInputHistory) {
8452                 mText = safeCharSequence(text);
8453                 mTimestamp = timestamp;
8454                 mSender = sender;
8455                 mRemoteInputHistory = remoteInputHistory;
8456             }
8457 
8458             /**
8459              * Sets a binary blob of data and an associated MIME type for a message. In the case
8460              * where the platform doesn't support the MIME type, the original text provided in the
8461              * constructor will be used.
8462              * @param dataMimeType The MIME type of the content. See
8463              * <a href="{@docRoot}notifications/messaging.html"> for the list of supported MIME
8464              * types on Android and Android Wear.
8465              * @param dataUri The uri containing the content whose type is given by the MIME type.
8466              * <p class="note">
8467              * <ol>
8468              *   <li>Notification Listeners including the System UI need permission to access the
8469              *       data the Uri points to. The recommended ways to do this are:</li>
8470              *   <li>Store the data in your own ContentProvider, making sure that other apps have
8471              *       the correct permission to access your provider. The preferred mechanism for
8472              *       providing access is to use per-URI permissions which are temporary and only
8473              *       grant access to the receiving application. An easy way to create a
8474              *       ContentProvider like this is to use the FileProvider helper class.</li>
8475              *   <li>Use the system MediaStore. The MediaStore is primarily aimed at video, audio
8476              *       and image MIME types, however beginning with Android 3.0 (API level 11) it can
8477              *       also store non-media types (see MediaStore.Files for more info). Files can be
8478              *       inserted into the MediaStore using scanFile() after which a content:// style
8479              *       Uri suitable for sharing is passed to the provided onScanCompleted() callback.
8480              *       Note that once added to the system MediaStore the content is accessible to any
8481              *       app on the device.</li>
8482              * </ol>
8483              * @return this object for method chaining
8484              */
setData(String dataMimeType, Uri dataUri)8485             public Message setData(String dataMimeType, Uri dataUri) {
8486                 mDataMimeType = dataMimeType;
8487                 mDataUri = dataUri;
8488                 return this;
8489             }
8490 
8491             /**
8492              * Get the text to be used for this message, or the fallback text if a type and content
8493              * Uri have been set
8494              */
getText()8495             public CharSequence getText() {
8496                 return mText;
8497             }
8498 
8499             /**
8500              * Get the time at which this message arrived
8501              */
getTimestamp()8502             public long getTimestamp() {
8503                 return mTimestamp;
8504             }
8505 
8506             /**
8507              * Get the extras Bundle for this message.
8508              */
getExtras()8509             public Bundle getExtras() {
8510                 return mExtras;
8511             }
8512 
8513             /**
8514              * Get the text used to display the contact's name in the messaging experience
8515              *
8516              * @deprecated use {@link #getSenderPerson()}
8517              */
getSender()8518             public CharSequence getSender() {
8519                 return mSender == null ? null : mSender.getName();
8520             }
8521 
8522             /**
8523              * Get the sender associated with this message.
8524              */
8525             @Nullable
getSenderPerson()8526             public Person getSenderPerson() {
8527                 return mSender;
8528             }
8529 
8530             /**
8531              * Get the MIME type of the data pointed to by the Uri
8532              */
getDataMimeType()8533             public String getDataMimeType() {
8534                 return mDataMimeType;
8535             }
8536 
8537             /**
8538              * Get the Uri pointing to the content of the message. Can be null, in which case
8539              * {@see #getText()} is used.
8540              */
getDataUri()8541             public Uri getDataUri() {
8542                 return mDataUri;
8543             }
8544 
8545             /**
8546              * @return True if the message was generated from
8547              * {@link Notification#EXTRA_REMOTE_INPUT_HISTORY_ITEMS}.
8548              * @hide
8549              */
isRemoteInputHistory()8550             public boolean isRemoteInputHistory() {
8551                 return mRemoteInputHistory;
8552             }
8553 
8554             /**
8555              * @hide
8556              */
8557             @VisibleForTesting
toBundle()8558             public Bundle toBundle() {
8559                 Bundle bundle = new Bundle();
8560                 if (mText != null) {
8561                     bundle.putCharSequence(KEY_TEXT, mText);
8562                 }
8563                 bundle.putLong(KEY_TIMESTAMP, mTimestamp);
8564                 if (mSender != null) {
8565                     // Legacy listeners need this
8566                     bundle.putCharSequence(KEY_SENDER, safeCharSequence(mSender.getName()));
8567                     bundle.putParcelable(KEY_SENDER_PERSON, mSender);
8568                 }
8569                 if (mDataMimeType != null) {
8570                     bundle.putString(KEY_DATA_MIME_TYPE, mDataMimeType);
8571                 }
8572                 if (mDataUri != null) {
8573                     bundle.putParcelable(KEY_DATA_URI, mDataUri);
8574                 }
8575                 if (mExtras != null) {
8576                     bundle.putBundle(KEY_EXTRAS_BUNDLE, mExtras);
8577                 }
8578                 if (mRemoteInputHistory) {
8579                     bundle.putBoolean(KEY_REMOTE_INPUT_HISTORY, mRemoteInputHistory);
8580                 }
8581                 return bundle;
8582             }
8583 
getBundleArrayForMessages(List<Message> messages)8584             static Bundle[] getBundleArrayForMessages(List<Message> messages) {
8585                 Bundle[] bundles = new Bundle[messages.size()];
8586                 final int N = messages.size();
8587                 for (int i = 0; i < N; i++) {
8588                     bundles[i] = messages.get(i).toBundle();
8589                 }
8590                 return bundles;
8591             }
8592 
8593             /**
8594              * Returns a list of messages read from the given bundle list, e.g.
8595              * {@link #EXTRA_MESSAGES} or {@link #EXTRA_HISTORIC_MESSAGES}.
8596              */
8597             @NonNull
getMessagesFromBundleArray(@ullable Parcelable[] bundles)8598             public static List<Message> getMessagesFromBundleArray(@Nullable Parcelable[] bundles) {
8599                 if (bundles == null) {
8600                     return new ArrayList<>();
8601                 }
8602                 List<Message> messages = new ArrayList<>(bundles.length);
8603                 for (int i = 0; i < bundles.length; i++) {
8604                     if (bundles[i] instanceof Bundle) {
8605                         Message message = getMessageFromBundle((Bundle)bundles[i]);
8606                         if (message != null) {
8607                             messages.add(message);
8608                         }
8609                     }
8610                 }
8611                 return messages;
8612             }
8613 
8614             /**
8615              * Returns the message that is stored in the bundle (e.g. one of the values in the lists
8616              * in {@link #EXTRA_MESSAGES} or {@link #EXTRA_HISTORIC_MESSAGES}) or null if the
8617              * message couldn't be resolved.
8618              * @hide
8619              */
8620             @Nullable
getMessageFromBundle(@onNull Bundle bundle)8621             public static Message getMessageFromBundle(@NonNull Bundle bundle) {
8622                 try {
8623                     if (!bundle.containsKey(KEY_TEXT) || !bundle.containsKey(KEY_TIMESTAMP)) {
8624                         return null;
8625                     } else {
8626 
8627                         Person senderPerson = bundle.getParcelable(KEY_SENDER_PERSON);
8628                         if (senderPerson == null) {
8629                             // Legacy apps that use compat don't actually provide the sender objects
8630                             // We need to fix the compat version to provide people / use
8631                             // the native api instead
8632                             CharSequence senderName = bundle.getCharSequence(KEY_SENDER);
8633                             if (senderName != null) {
8634                                 senderPerson = new Person.Builder().setName(senderName).build();
8635                             }
8636                         }
8637                         Message message = new Message(bundle.getCharSequence(KEY_TEXT),
8638                                 bundle.getLong(KEY_TIMESTAMP),
8639                                 senderPerson,
8640                                 bundle.getBoolean(KEY_REMOTE_INPUT_HISTORY, false));
8641                         if (bundle.containsKey(KEY_DATA_MIME_TYPE) &&
8642                                 bundle.containsKey(KEY_DATA_URI)) {
8643                             message.setData(bundle.getString(KEY_DATA_MIME_TYPE),
8644                                     (Uri) bundle.getParcelable(KEY_DATA_URI));
8645                         }
8646                         if (bundle.containsKey(KEY_EXTRAS_BUNDLE)) {
8647                             message.getExtras().putAll(bundle.getBundle(KEY_EXTRAS_BUNDLE));
8648                         }
8649                         return message;
8650                     }
8651                 } catch (ClassCastException e) {
8652                     return null;
8653                 }
8654             }
8655         }
8656     }
8657 
8658     /**
8659      * Helper class for generating large-format notifications that include a list of (up to 5) strings.
8660      *
8661      * Here's how you'd set the <code>InboxStyle</code> on a notification:
8662      * <pre class="prettyprint">
8663      * Notification notif = new Notification.Builder(mContext)
8664      *     .setContentTitle(&quot;5 New mails from &quot; + sender.toString())
8665      *     .setContentText(subject)
8666      *     .setSmallIcon(R.drawable.new_mail)
8667      *     .setLargeIcon(aBitmap)
8668      *     .setStyle(new Notification.InboxStyle()
8669      *         .addLine(str1)
8670      *         .addLine(str2)
8671      *         .setContentTitle(&quot;&quot;)
8672      *         .setSummaryText(&quot;+3 more&quot;))
8673      *     .build();
8674      * </pre>
8675      *
8676      * @see Notification#bigContentView
8677      */
8678     public static class InboxStyle extends Style {
8679 
8680         /**
8681          * The number of lines of remote input history allowed until we start reducing lines.
8682          */
8683         private static final int NUMBER_OF_HISTORY_ALLOWED_UNTIL_REDUCTION = 1;
8684         private ArrayList<CharSequence> mTexts = new ArrayList<CharSequence>(5);
8685 
InboxStyle()8686         public InboxStyle() {
8687         }
8688 
8689         /**
8690          * @deprecated use {@code InboxStyle()}.
8691          */
8692         @Deprecated
InboxStyle(Builder builder)8693         public InboxStyle(Builder builder) {
8694             setBuilder(builder);
8695         }
8696 
8697         /**
8698          * Overrides ContentTitle in the big form of the template.
8699          * This defaults to the value passed to setContentTitle().
8700          */
setBigContentTitle(CharSequence title)8701         public InboxStyle setBigContentTitle(CharSequence title) {
8702             internalSetBigContentTitle(safeCharSequence(title));
8703             return this;
8704         }
8705 
8706         /**
8707          * Set the first line of text after the detail section in the big form of the template.
8708          */
setSummaryText(CharSequence cs)8709         public InboxStyle setSummaryText(CharSequence cs) {
8710             internalSetSummaryText(safeCharSequence(cs));
8711             return this;
8712         }
8713 
8714         /**
8715          * Append a line to the digest section of the Inbox notification.
8716          */
addLine(CharSequence cs)8717         public InboxStyle addLine(CharSequence cs) {
8718             mTexts.add(safeCharSequence(cs));
8719             return this;
8720         }
8721 
8722         /**
8723          * @hide
8724          */
getLines()8725         public ArrayList<CharSequence> getLines() {
8726             return mTexts;
8727         }
8728 
8729         /**
8730          * @hide
8731          */
addExtras(Bundle extras)8732         public void addExtras(Bundle extras) {
8733             super.addExtras(extras);
8734 
8735             CharSequence[] a = new CharSequence[mTexts.size()];
8736             extras.putCharSequenceArray(EXTRA_TEXT_LINES, mTexts.toArray(a));
8737         }
8738 
8739         /**
8740          * @hide
8741          */
8742         @Override
restoreFromExtras(Bundle extras)8743         protected void restoreFromExtras(Bundle extras) {
8744             super.restoreFromExtras(extras);
8745 
8746             mTexts.clear();
8747             if (extras.containsKey(EXTRA_TEXT_LINES)) {
8748                 Collections.addAll(mTexts, extras.getCharSequenceArray(EXTRA_TEXT_LINES));
8749             }
8750         }
8751 
8752         /**
8753          * @hide
8754          */
makeBigContentView()8755         public RemoteViews makeBigContentView() {
8756             StandardTemplateParams p = mBuilder.mParams.reset()
8757                     .viewType(StandardTemplateParams.VIEW_TYPE_BIG)
8758                     .fillTextsFrom(mBuilder).text(null);
8759             TemplateBindResult result = new TemplateBindResult();
8760             RemoteViews contentView = getStandardView(mBuilder.getInboxLayoutResource(), p, result);
8761 
8762             int[] rowIds = {R.id.inbox_text0, R.id.inbox_text1, R.id.inbox_text2, R.id.inbox_text3,
8763                     R.id.inbox_text4, R.id.inbox_text5, R.id.inbox_text6};
8764 
8765             // Make sure all rows are gone in case we reuse a view.
8766             for (int rowId : rowIds) {
8767                 contentView.setViewVisibility(rowId, View.GONE);
8768             }
8769 
8770             int i=0;
8771             int topPadding = mBuilder.mContext.getResources().getDimensionPixelSize(
8772                     R.dimen.notification_inbox_item_top_padding);
8773             boolean first = true;
8774             int onlyViewId = 0;
8775             int maxRows = rowIds.length;
8776             if (mBuilder.mActions.size() > 0) {
8777                 maxRows--;
8778             }
8779             RemoteInputHistoryItem[] remoteInputHistory = getParcelableArrayFromBundle(
8780                     mBuilder.mN.extras, EXTRA_REMOTE_INPUT_HISTORY_ITEMS,
8781                     RemoteInputHistoryItem.class);
8782             if (remoteInputHistory != null
8783                     && remoteInputHistory.length > NUMBER_OF_HISTORY_ALLOWED_UNTIL_REDUCTION) {
8784                 // Let's remove some messages to make room for the remote input history.
8785                 // 1 is always able to fit, but let's remove them if they are 2 or 3
8786                 int numRemoteInputs = Math.min(remoteInputHistory.length,
8787                         MAX_REMOTE_INPUT_HISTORY_LINES);
8788                 int totalNumRows = mTexts.size() + numRemoteInputs
8789                         - NUMBER_OF_HISTORY_ALLOWED_UNTIL_REDUCTION;
8790                 if (totalNumRows > maxRows) {
8791                     int overflow = totalNumRows - maxRows;
8792                     if (mTexts.size() > maxRows) {
8793                         // Heuristic: if the Texts don't fit anyway, we'll rather drop the last
8794                         // few messages, even with the remote input
8795                         maxRows -= overflow;
8796                     } else  {
8797                         // otherwise we drop the first messages
8798                         i = overflow;
8799                     }
8800                 }
8801             }
8802             while (i < mTexts.size() && i < maxRows) {
8803                 CharSequence str = mTexts.get(i);
8804                 if (!TextUtils.isEmpty(str)) {
8805                     contentView.setViewVisibility(rowIds[i], View.VISIBLE);
8806                     contentView.setTextViewText(rowIds[i],
8807                             mBuilder.processTextSpans(mBuilder.processLegacyText(str)));
8808                     mBuilder.setTextViewColorSecondary(contentView, rowIds[i], p);
8809                     contentView.setViewPadding(rowIds[i], 0, topPadding, 0, 0);
8810                     if (first) {
8811                         onlyViewId = rowIds[i];
8812                     } else {
8813                         onlyViewId = 0;
8814                     }
8815                     first = false;
8816                 }
8817                 i++;
8818             }
8819             if (onlyViewId != 0) {
8820                 // We only have 1 entry, lets make it look like the normal Text of a Bigtext
8821                 topPadding = mBuilder.mContext.getResources().getDimensionPixelSize(
8822                         R.dimen.notification_text_margin_top);
8823                 contentView.setViewPadding(onlyViewId, 0, topPadding, 0, 0);
8824             }
8825 
8826             return contentView;
8827         }
8828 
8829         /**
8830          * @hide
8831          */
8832         @Override
areNotificationsVisiblyDifferent(Style other)8833         public boolean areNotificationsVisiblyDifferent(Style other) {
8834             if (other == null || getClass() != other.getClass()) {
8835                 return true;
8836             }
8837             InboxStyle newS = (InboxStyle) other;
8838 
8839             final ArrayList<CharSequence> myLines = getLines();
8840             final ArrayList<CharSequence> newLines = newS.getLines();
8841             final int n = myLines.size();
8842             if (n != newLines.size()) {
8843                 return true;
8844             }
8845 
8846             for (int i = 0; i < n; i++) {
8847                 if (!Objects.equals(
8848                         String.valueOf(myLines.get(i)),
8849                         String.valueOf(newLines.get(i)))) {
8850                     return true;
8851                 }
8852             }
8853             return false;
8854         }
8855     }
8856 
8857     /**
8858      * Notification style for media playback notifications.
8859      *
8860      * In the expanded form, {@link Notification#bigContentView}, up to 5
8861      * {@link Notification.Action}s specified with
8862      * {@link Notification.Builder#addAction(Action) addAction} will be
8863      * shown as icon-only pushbuttons, suitable for transport controls. The Bitmap given to
8864      * {@link Notification.Builder#setLargeIcon(android.graphics.Bitmap) setLargeIcon()} will be
8865      * treated as album artwork.
8866      * <p>
8867      * Unlike the other styles provided here, MediaStyle can also modify the standard-size
8868      * {@link Notification#contentView}; by providing action indices to
8869      * {@link #setShowActionsInCompactView(int...)} you can promote up to 3 actions to be displayed
8870      * in the standard view alongside the usual content.
8871      * <p>
8872      * Notifications created with MediaStyle will have their category set to
8873      * {@link Notification#CATEGORY_TRANSPORT CATEGORY_TRANSPORT} unless you set a different
8874      * category using {@link Notification.Builder#setCategory(String) setCategory()}.
8875      * <p>
8876      * Finally, if you attach a {@link android.media.session.MediaSession.Token} using
8877      * {@link android.app.Notification.MediaStyle#setMediaSession(MediaSession.Token)},
8878      * the System UI can identify this as a notification representing an active media session
8879      * and respond accordingly (by showing album artwork in the lockscreen, for example).
8880      *
8881      * <p>
8882      * Starting at {@link android.os.Build.VERSION_CODES#O Android O} any notification that has a
8883      * media session attached with {@link #setMediaSession(MediaSession.Token)} will be colorized.
8884      * You can opt-out of this behavior by using {@link Notification.Builder#setColorized(boolean)}.
8885      * <p>
8886      *
8887      * To use this style with your Notification, feed it to
8888      * {@link Notification.Builder#setStyle(android.app.Notification.Style)} like so:
8889      * <pre class="prettyprint">
8890      * Notification noti = new Notification.Builder()
8891      *     .setSmallIcon(R.drawable.ic_stat_player)
8892      *     .setContentTitle(&quot;Track title&quot;)
8893      *     .setContentText(&quot;Artist - Album&quot;)
8894      *     .setLargeIcon(albumArtBitmap))
8895      *     .setStyle(<b>new Notification.MediaStyle()</b>
8896      *         .setMediaSession(mySession))
8897      *     .build();
8898      * </pre>
8899      *
8900      * @see Notification#bigContentView
8901      * @see Notification.Builder#setColorized(boolean)
8902      */
8903     public static class MediaStyle extends Style {
8904         // Changing max media buttons requires also changing templates
8905         // (notification_template_material_media and notification_template_material_big_media).
8906         static final int MAX_MEDIA_BUTTONS_IN_COMPACT = 3;
8907         static final int MAX_MEDIA_BUTTONS = 5;
8908         @IdRes private static final int[] MEDIA_BUTTON_IDS = {
8909                 R.id.action0,
8910                 R.id.action1,
8911                 R.id.action2,
8912                 R.id.action3,
8913                 R.id.action4,
8914         };
8915 
8916         private int[] mActionsToShowInCompact = null;
8917         private MediaSession.Token mToken;
8918 
MediaStyle()8919         public MediaStyle() {
8920         }
8921 
8922         /**
8923          * @deprecated use {@code MediaStyle()}.
8924          */
8925         @Deprecated
MediaStyle(Builder builder)8926         public MediaStyle(Builder builder) {
8927             setBuilder(builder);
8928         }
8929 
8930         /**
8931          * Request up to 3 actions (by index in the order of addition) to be shown in the compact
8932          * notification view.
8933          *
8934          * @param actions the indices of the actions to show in the compact notification view
8935          */
setShowActionsInCompactView(int...actions)8936         public MediaStyle setShowActionsInCompactView(int...actions) {
8937             mActionsToShowInCompact = actions;
8938             return this;
8939         }
8940 
8941         /**
8942          * Attach a {@link android.media.session.MediaSession.Token} to this Notification
8943          * to provide additional playback information and control to the SystemUI.
8944          */
setMediaSession(MediaSession.Token token)8945         public MediaStyle setMediaSession(MediaSession.Token token) {
8946             mToken = token;
8947             return this;
8948         }
8949 
8950         /**
8951          * @hide
8952          */
8953         @Override
8954         @UnsupportedAppUsage
buildStyled(Notification wip)8955         public Notification buildStyled(Notification wip) {
8956             super.buildStyled(wip);
8957             if (wip.category == null) {
8958                 wip.category = Notification.CATEGORY_TRANSPORT;
8959             }
8960             return wip;
8961         }
8962 
8963         /**
8964          * @hide
8965          */
8966         @Override
makeContentView(boolean increasedHeight)8967         public RemoteViews makeContentView(boolean increasedHeight) {
8968             return makeMediaContentView(null /* customContent */);
8969         }
8970 
8971         /**
8972          * @hide
8973          */
8974         @Override
makeBigContentView()8975         public RemoteViews makeBigContentView() {
8976             return makeMediaBigContentView(null /* customContent */);
8977         }
8978 
8979         /**
8980          * @hide
8981          */
8982         @Override
makeHeadsUpContentView(boolean increasedHeight)8983         public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
8984             return makeMediaContentView(null /* customContent */);
8985         }
8986 
8987         /** @hide */
8988         @Override
addExtras(Bundle extras)8989         public void addExtras(Bundle extras) {
8990             super.addExtras(extras);
8991 
8992             if (mToken != null) {
8993                 extras.putParcelable(EXTRA_MEDIA_SESSION, mToken);
8994             }
8995             if (mActionsToShowInCompact != null) {
8996                 extras.putIntArray(EXTRA_COMPACT_ACTIONS, mActionsToShowInCompact);
8997             }
8998         }
8999 
9000         /**
9001          * @hide
9002          */
9003         @Override
restoreFromExtras(Bundle extras)9004         protected void restoreFromExtras(Bundle extras) {
9005             super.restoreFromExtras(extras);
9006 
9007             if (extras.containsKey(EXTRA_MEDIA_SESSION)) {
9008                 mToken = extras.getParcelable(EXTRA_MEDIA_SESSION);
9009             }
9010             if (extras.containsKey(EXTRA_COMPACT_ACTIONS)) {
9011                 mActionsToShowInCompact = extras.getIntArray(EXTRA_COMPACT_ACTIONS);
9012             }
9013         }
9014 
9015         /**
9016          * @hide
9017          */
9018         @Override
areNotificationsVisiblyDifferent(Style other)9019         public boolean areNotificationsVisiblyDifferent(Style other) {
9020             if (other == null || getClass() != other.getClass()) {
9021                 return true;
9022             }
9023             // All fields to compare are on the Notification object
9024             return false;
9025         }
9026 
bindMediaActionButton(RemoteViews container, @IdRes int buttonId, Action action, StandardTemplateParams p)9027         private void bindMediaActionButton(RemoteViews container, @IdRes int buttonId,
9028                 Action action, StandardTemplateParams p) {
9029             final boolean tombstone = (action.actionIntent == null);
9030             container.setViewVisibility(buttonId, View.VISIBLE);
9031             container.setImageViewIcon(buttonId, action.getIcon());
9032 
9033             // If the action buttons should not be tinted, then just use the default
9034             // notification color. Otherwise, just use the passed-in color.
9035             int tintColor = mBuilder.getStandardActionColor(p);
9036 
9037             container.setDrawableTint(buttonId, false, tintColor,
9038                     PorterDuff.Mode.SRC_ATOP);
9039 
9040             int rippleAlpha = mBuilder.getColors(p).getRippleAlpha();
9041             int rippleColor = Color.argb(rippleAlpha, Color.red(tintColor), Color.green(tintColor),
9042                     Color.blue(tintColor));
9043             container.setRippleDrawableColor(buttonId, ColorStateList.valueOf(rippleColor));
9044 
9045             if (!tombstone) {
9046                 container.setOnClickPendingIntent(buttonId, action.actionIntent);
9047             }
9048             container.setContentDescription(buttonId, action.title);
9049         }
9050 
9051         /** @hide */
makeMediaContentView(@ullable RemoteViews customContent)9052         protected RemoteViews makeMediaContentView(@Nullable RemoteViews customContent) {
9053             final int numActions = mBuilder.mActions.size();
9054             final int numActionsToShow = Math.min(mActionsToShowInCompact == null
9055                     ? 0 : mActionsToShowInCompact.length, MAX_MEDIA_BUTTONS_IN_COMPACT);
9056             if (numActionsToShow > numActions) {
9057                 throw new IllegalArgumentException(String.format(
9058                         "setShowActionsInCompactView: action %d out of bounds (max %d)",
9059                         numActions, numActions - 1));
9060             }
9061 
9062             StandardTemplateParams p = mBuilder.mParams.reset()
9063                     .viewType(StandardTemplateParams.VIEW_TYPE_NORMAL)
9064                     .hideTime(numActionsToShow > 1)       // hide if actions wider than a right icon
9065                     .hideSubText(numActionsToShow > 1)    // hide if actions wider than a right icon
9066                     .hideLeftIcon(false)                  // allow large icon on left when grouped
9067                     .hideRightIcon(numActionsToShow > 0)  // right icon or actions; not both
9068                     .hideProgress(true)
9069                     .fillTextsFrom(mBuilder);
9070             TemplateBindResult result = new TemplateBindResult();
9071             RemoteViews template = mBuilder.applyStandardTemplate(
9072                     R.layout.notification_template_material_media, p,
9073                     null /* result */);
9074 
9075             for (int i = 0; i < MAX_MEDIA_BUTTONS_IN_COMPACT; i++) {
9076                 if (i < numActionsToShow) {
9077                     final Action action = mBuilder.mActions.get(mActionsToShowInCompact[i]);
9078                     bindMediaActionButton(template, MEDIA_BUTTON_IDS[i], action, p);
9079                 } else {
9080                     template.setViewVisibility(MEDIA_BUTTON_IDS[i], View.GONE);
9081                 }
9082             }
9083             // Prevent a swooping expand animation when there are no actions
9084             boolean hasActions = numActionsToShow != 0;
9085             template.setViewVisibility(R.id.media_actions, hasActions ? View.VISIBLE : View.GONE);
9086 
9087             // Add custom view if provided by subclass.
9088             buildCustomContentIntoTemplate(mBuilder.mContext, template, customContent, p, result);
9089             return template;
9090         }
9091 
9092         /** @hide */
makeMediaBigContentView(@ullable RemoteViews customContent)9093         protected RemoteViews makeMediaBigContentView(@Nullable RemoteViews customContent) {
9094             final int actionCount = Math.min(mBuilder.mActions.size(), MAX_MEDIA_BUTTONS);
9095             StandardTemplateParams p = mBuilder.mParams.reset()
9096                     .viewType(StandardTemplateParams.VIEW_TYPE_BIG)
9097                     .hideProgress(true)
9098                     .fillTextsFrom(mBuilder);
9099             TemplateBindResult result = new TemplateBindResult();
9100             RemoteViews template = mBuilder.applyStandardTemplate(
9101                     R.layout.notification_template_material_big_media, p , result);
9102 
9103             for (int i = 0; i < MAX_MEDIA_BUTTONS; i++) {
9104                 if (i < actionCount) {
9105                     bindMediaActionButton(template,
9106                             MEDIA_BUTTON_IDS[i], mBuilder.mActions.get(i), p);
9107                 } else {
9108                     template.setViewVisibility(MEDIA_BUTTON_IDS[i], View.GONE);
9109                 }
9110             }
9111             buildCustomContentIntoTemplate(mBuilder.mContext, template, customContent, p, result);
9112             return template;
9113         }
9114     }
9115 
9116     /**
9117      * Helper class for generating large-format notifications that include a large image attachment.
9118      *
9119      * Here's how you'd set the <code>CallStyle</code> on a notification:
9120      * <pre class="prettyprint">
9121      * Notification notif = new Notification.Builder(mContext)
9122      *     .setSmallIcon(R.drawable.new_post)
9123      *     .setStyle(Notification.CallStyle.forIncomingCall(caller, declineIntent, answerIntent))
9124      *     .build();
9125      * </pre>
9126      */
9127     public static class CallStyle extends Style {
9128         /** @hide */
9129         public static final int CALL_TYPE_INCOMING = 1;
9130         /** @hide */
9131         public static final int CALL_TYPE_ONGOING = 2;
9132         /** @hide */
9133         public static final int CALL_TYPE_SCREENING = 3;
9134 
9135         /**
9136          * This is a key used privately on the action.extras to give spacing priority
9137          * to the required call actions
9138          */
9139         private static final String KEY_ACTION_PRIORITY = "key_action_priority";
9140 
9141         private int mCallType;
9142         private Person mPerson;
9143         private PendingIntent mAnswerIntent;
9144         private PendingIntent mDeclineIntent;
9145         private PendingIntent mHangUpIntent;
9146         private boolean mIsVideo;
9147         private Integer mAnswerButtonColor;
9148         private Integer mDeclineButtonColor;
9149         private Icon mVerificationIcon;
9150         private CharSequence mVerificationText;
9151 
CallStyle()9152         CallStyle() {
9153         }
9154 
9155         /**
9156          * Create a CallStyle for an incoming call.
9157          * This notification will have a decline and an answer action, will allow a single
9158          * custom {@link Builder#addAction(Action) action}, and will have a default
9159          * {@link Builder#setContentText(CharSequence) content text} for an incoming call.
9160          *
9161          * @param person        The person displayed as the caller.
9162          *                      The person also needs to have a non-empty name associated with it.
9163          * @param declineIntent The intent to be sent when the user taps the decline action
9164          * @param answerIntent  The intent to be sent when the user taps the answer action
9165          */
9166         @NonNull
forIncomingCall(@onNull Person person, @NonNull PendingIntent declineIntent, @NonNull PendingIntent answerIntent)9167         public static CallStyle forIncomingCall(@NonNull Person person,
9168                 @NonNull PendingIntent declineIntent, @NonNull PendingIntent answerIntent) {
9169             return new CallStyle(CALL_TYPE_INCOMING, person,
9170                     null /* hangUpIntent */,
9171                     requireNonNull(declineIntent, "declineIntent is required"),
9172                     requireNonNull(answerIntent, "answerIntent is required")
9173             );
9174         }
9175 
9176         /**
9177          * Create a CallStyle for an ongoing call.
9178          * This notification will have a hang up action, will allow up to two
9179          * custom {@link Builder#addAction(Action) actions}, and will have a default
9180          * {@link Builder#setContentText(CharSequence) content text} for an ongoing call.
9181          *
9182          * @param person       The person displayed as being on the other end of the call.
9183          *                     The person also needs to have a non-empty name associated with it.
9184          * @param hangUpIntent The intent to be sent when the user taps the hang up action
9185          */
9186         @NonNull
forOngoingCall(@onNull Person person, @NonNull PendingIntent hangUpIntent)9187         public static CallStyle forOngoingCall(@NonNull Person person,
9188                 @NonNull PendingIntent hangUpIntent) {
9189             return new CallStyle(CALL_TYPE_ONGOING, person,
9190                     requireNonNull(hangUpIntent, "hangUpIntent is required"),
9191                     null /* declineIntent */,
9192                     null /* answerIntent */
9193             );
9194         }
9195 
9196         /**
9197          * Create a CallStyle for a call that is being screened.
9198          * This notification will have a hang up and an answer action, will allow a single
9199          * custom {@link Builder#addAction(Action) action}, and will have a default
9200          * {@link Builder#setContentText(CharSequence) content text} for a call that is being
9201          * screened.
9202          *
9203          * @param person       The person displayed as the caller.
9204          *                     The person also needs to have a non-empty name associated with it.
9205          * @param hangUpIntent The intent to be sent when the user taps the hang up action
9206          * @param answerIntent The intent to be sent when the user taps the answer action
9207          */
9208         @NonNull
forScreeningCall(@onNull Person person, @NonNull PendingIntent hangUpIntent, @NonNull PendingIntent answerIntent)9209         public static CallStyle forScreeningCall(@NonNull Person person,
9210                 @NonNull PendingIntent hangUpIntent, @NonNull PendingIntent answerIntent) {
9211             return new CallStyle(CALL_TYPE_SCREENING, person,
9212                     requireNonNull(hangUpIntent, "hangUpIntent is required"),
9213                     null /* declineIntent */,
9214                     requireNonNull(answerIntent, "answerIntent is required")
9215             );
9216         }
9217 
9218         /**
9219          * @param person The person displayed for the incoming call.
9220          *             The user also needs to have a non-empty name associated with it.
9221          * @param hangUpIntent The intent to be sent when the user taps the hang up action
9222          * @param declineIntent The intent to be sent when the user taps the decline action
9223          * @param answerIntent The intent to be sent when the user taps the answer action
9224          */
CallStyle(int callType, @NonNull Person person, @Nullable PendingIntent hangUpIntent, @Nullable PendingIntent declineIntent, @Nullable PendingIntent answerIntent)9225         private CallStyle(int callType, @NonNull Person person,
9226                 @Nullable PendingIntent hangUpIntent, @Nullable PendingIntent declineIntent,
9227                 @Nullable PendingIntent answerIntent) {
9228             if (person == null || TextUtils.isEmpty(person.getName())) {
9229                 throw new IllegalArgumentException("person must have a non-empty a name");
9230             }
9231             mCallType = callType;
9232             mPerson = person;
9233             mAnswerIntent = answerIntent;
9234             mDeclineIntent = declineIntent;
9235             mHangUpIntent = hangUpIntent;
9236         }
9237 
9238         /**
9239          * Sets whether the call is a video call, which may affect the icons or text used on the
9240          * required action buttons.
9241          */
9242         @NonNull
setIsVideo(boolean isVideo)9243         public CallStyle setIsVideo(boolean isVideo) {
9244             mIsVideo = isVideo;
9245             return this;
9246         }
9247 
9248         /**
9249          * Optional icon to be displayed with {@link #setVerificationText(CharSequence) text}
9250          * as a verification status of the caller.
9251          */
9252         @NonNull
setVerificationIcon(@ullable Icon verificationIcon)9253         public CallStyle setVerificationIcon(@Nullable Icon verificationIcon) {
9254             mVerificationIcon = verificationIcon;
9255             return this;
9256         }
9257 
9258         /**
9259          * Optional text to be displayed with an {@link #setVerificationIcon(Icon) icon}
9260          * as a verification status of the caller.
9261          */
9262         @NonNull
setVerificationText(@ullable CharSequence verificationText)9263         public CallStyle setVerificationText(@Nullable CharSequence verificationText) {
9264             mVerificationText = safeCharSequence(verificationText);
9265             return this;
9266         }
9267 
9268         /**
9269          * Optional color to be used as a hint for the Answer action button's color.
9270          * The system may change this color to ensure sufficient contrast with the background.
9271          * The system may choose to disregard this hint if the notification is not colorized.
9272          */
9273         @NonNull
setAnswerButtonColorHint(@olorInt int color)9274         public CallStyle setAnswerButtonColorHint(@ColorInt int color) {
9275             mAnswerButtonColor = color;
9276             return this;
9277         }
9278 
9279         /**
9280          * Optional color to be used as a hint for the Decline or Hang Up action button's color.
9281          * The system may change this color to ensure sufficient contrast with the background.
9282          * The system may choose to disregard this hint if the notification is not colorized.
9283          */
9284         @NonNull
setDeclineButtonColorHint(@olorInt int color)9285         public CallStyle setDeclineButtonColorHint(@ColorInt int color) {
9286             mDeclineButtonColor = color;
9287             return this;
9288         }
9289 
9290         /** @hide */
9291         @Override
buildStyled(Notification wip)9292         public Notification buildStyled(Notification wip) {
9293             wip = super.buildStyled(wip);
9294             // ensure that the actions in the builder and notification are corrected.
9295             mBuilder.mActions = getActionsListWithSystemActions();
9296             wip.actions = new Action[mBuilder.mActions.size()];
9297             mBuilder.mActions.toArray(wip.actions);
9298             return wip;
9299         }
9300 
9301         /**
9302          * @hide
9303          */
displayCustomViewInline()9304         public boolean displayCustomViewInline() {
9305             // This is a lie; True is returned to make sure that the custom view is not used
9306             // instead of the template, but it will not actually be included.
9307             return true;
9308         }
9309 
9310         /**
9311          * @hide
9312          */
9313         @Override
purgeResources()9314         public void purgeResources() {
9315             super.purgeResources();
9316             if (mVerificationIcon != null) {
9317                 mVerificationIcon.convertToAshmem();
9318             }
9319         }
9320 
9321         /**
9322          * @hide
9323          */
9324         @Override
reduceImageSizes(Context context)9325         public void reduceImageSizes(Context context) {
9326             super.reduceImageSizes(context);
9327             if (mVerificationIcon != null) {
9328                 int rightIconSize = context.getResources().getDimensionPixelSize(
9329                         ActivityManager.isLowRamDeviceStatic()
9330                                 ? R.dimen.notification_right_icon_size_low_ram
9331                                 : R.dimen.notification_right_icon_size);
9332                 mVerificationIcon.scaleDownIfNecessary(rightIconSize, rightIconSize);
9333             }
9334         }
9335 
9336         /**
9337          * @hide
9338          */
9339         @Override
makeContentView(boolean increasedHeight)9340         public RemoteViews makeContentView(boolean increasedHeight) {
9341             return makeCallLayout(StandardTemplateParams.VIEW_TYPE_NORMAL);
9342         }
9343 
9344         /**
9345          * @hide
9346          */
9347         @Override
makeHeadsUpContentView(boolean increasedHeight)9348         public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
9349             return makeCallLayout(StandardTemplateParams.VIEW_TYPE_HEADS_UP);
9350         }
9351 
9352         /**
9353          * @hide
9354          */
makeBigContentView()9355         public RemoteViews makeBigContentView() {
9356             return makeCallLayout(StandardTemplateParams.VIEW_TYPE_BIG);
9357         }
9358 
9359         @NonNull
makeNegativeAction()9360         private Action makeNegativeAction() {
9361             if (mDeclineIntent == null) {
9362                 return makeAction(R.drawable.ic_call_decline,
9363                         R.string.call_notification_hang_up_action,
9364                         mDeclineButtonColor, R.color.call_notification_decline_color,
9365                         mHangUpIntent);
9366             } else {
9367                 return makeAction(R.drawable.ic_call_decline,
9368                         R.string.call_notification_decline_action,
9369                         mDeclineButtonColor, R.color.call_notification_decline_color,
9370                         mDeclineIntent);
9371             }
9372         }
9373 
9374         @Nullable
makeAnswerAction()9375         private Action makeAnswerAction() {
9376             return mAnswerIntent == null ? null : makeAction(
9377                     mIsVideo ? R.drawable.ic_call_answer_video : R.drawable.ic_call_answer,
9378                     mIsVideo ? R.string.call_notification_answer_video_action
9379                             : R.string.call_notification_answer_action,
9380                     mAnswerButtonColor, R.color.call_notification_answer_color,
9381                     mAnswerIntent);
9382         }
9383 
9384         @NonNull
makeAction(@rawableRes int icon, @StringRes int title, @ColorInt Integer colorInt, @ColorRes int defaultColorRes, PendingIntent intent)9385         private Action makeAction(@DrawableRes int icon, @StringRes int title,
9386                 @ColorInt Integer colorInt, @ColorRes int defaultColorRes, PendingIntent intent) {
9387             if (colorInt == null || !mBuilder.isCallActionColorCustomizable()) {
9388                 colorInt = mBuilder.mContext.getColor(defaultColorRes);
9389             }
9390             Action action = new Action.Builder(Icon.createWithResource("", icon),
9391                     new SpannableStringBuilder().append(mBuilder.mContext.getString(title),
9392                             new ForegroundColorSpan(colorInt),
9393                             SpannableStringBuilder.SPAN_INCLUSIVE_INCLUSIVE),
9394                     intent).build();
9395             action.getExtras().putBoolean(KEY_ACTION_PRIORITY, true);
9396             return action;
9397         }
9398 
isActionAddedByCallStyle(Action action)9399         private boolean isActionAddedByCallStyle(Action action) {
9400             // This is an internal extra added by the style to these actions. If an app were to add
9401             // this extra to the action themselves, the action would be dropped.  :shrug:
9402             return action != null && action.getExtras().getBoolean(KEY_ACTION_PRIORITY);
9403         }
9404 
9405         /**
9406          * Gets the actions list for the call with the answer/decline/hangUp actions inserted in
9407          * the correct place.  This returns the correct result even if the system actions have
9408          * already been added, and even if more actions were added since then.
9409          * @hide
9410          */
9411         @NonNull
getActionsListWithSystemActions()9412         public ArrayList<Action> getActionsListWithSystemActions() {
9413             // Define the system actions we expect to see
9414             final Action negativeAction = makeNegativeAction();
9415             final Action answerAction = makeAnswerAction();
9416             // Sort the expected actions into the correct order:
9417             // * If there's no answer action, put the hang up / decline action at the end
9418             // * Otherwise put the answer action at the end, and put the decline action at start.
9419             final Action firstAction = answerAction == null ? null : negativeAction;
9420             final Action lastAction = answerAction == null ? negativeAction : answerAction;
9421 
9422             // Start creating the result list.
9423             int nonContextualActionSlotsRemaining = MAX_ACTION_BUTTONS;
9424             ArrayList<Action> resultActions = new ArrayList<>(MAX_ACTION_BUTTONS);
9425             if (firstAction != null) {
9426                 resultActions.add(firstAction);
9427                 --nonContextualActionSlotsRemaining;
9428             }
9429 
9430             // Copy actions into the new list, correcting system actions.
9431             if (mBuilder.mActions != null) {
9432                 for (Notification.Action action : mBuilder.mActions) {
9433                     if (action.isContextual()) {
9434                         // Always include all contextual actions
9435                         resultActions.add(action);
9436                     } else if (isActionAddedByCallStyle(action)) {
9437                         // Drop any old versions of system actions
9438                     } else {
9439                         // Copy non-contextual actions; decrement the remaining action slots.
9440                         resultActions.add(action);
9441                         --nonContextualActionSlotsRemaining;
9442                     }
9443                     // If there's exactly one action slot left, fill it with the lastAction.
9444                     if (nonContextualActionSlotsRemaining == 1) {
9445                         resultActions.add(lastAction);
9446                         --nonContextualActionSlotsRemaining;
9447                     }
9448                 }
9449             }
9450             // If there are any action slots left, the lastAction still needs to be added.
9451             if (nonContextualActionSlotsRemaining >= 1) {
9452                 resultActions.add(lastAction);
9453             }
9454             return resultActions;
9455         }
9456 
makeCallLayout(int viewType)9457         private RemoteViews makeCallLayout(int viewType) {
9458             final boolean isCollapsed = viewType == StandardTemplateParams.VIEW_TYPE_NORMAL;
9459             Bundle extras = mBuilder.mN.extras;
9460             CharSequence title = mPerson != null ? mPerson.getName() : null;
9461             CharSequence text = mBuilder.processLegacyText(extras.getCharSequence(EXTRA_TEXT));
9462             if (text == null) {
9463                 text = getDefaultText();
9464             }
9465 
9466             // Bind standard template
9467             StandardTemplateParams p = mBuilder.mParams.reset()
9468                     .viewType(viewType)
9469                     .callStyleActions(true)
9470                     .allowTextWithProgress(true)
9471                     .hideLeftIcon(true)
9472                     .hideRightIcon(true)
9473                     .hideAppName(isCollapsed)
9474                     .titleViewId(R.id.conversation_text)
9475                     .title(title)
9476                     .text(text)
9477                     .summaryText(mBuilder.processLegacyText(mVerificationText));
9478             mBuilder.mActions = getActionsListWithSystemActions();
9479             final RemoteViews contentView;
9480             if (isCollapsed) {
9481                 contentView = mBuilder.applyStandardTemplate(
9482                         R.layout.notification_template_material_call, p, null /* result */);
9483             } else {
9484                 contentView = mBuilder.applyStandardTemplateWithActions(
9485                         R.layout.notification_template_material_big_call, p, null /* result */);
9486             }
9487 
9488             // Bind some extra conversation-specific header fields.
9489             if (!p.mHideAppName) {
9490                 mBuilder.setTextViewColorSecondary(contentView, R.id.app_name_divider, p);
9491                 contentView.setViewVisibility(R.id.app_name_divider, View.VISIBLE);
9492             }
9493             bindCallerVerification(contentView, p);
9494 
9495             // Bind some custom CallLayout properties
9496             contentView.setInt(R.id.status_bar_latest_event_content, "setLayoutColor",
9497                     mBuilder.getSmallIconColor(p));
9498             contentView.setInt(R.id.status_bar_latest_event_content,
9499                     "setNotificationBackgroundColor", mBuilder.getBackgroundColor(p));
9500             contentView.setIcon(R.id.status_bar_latest_event_content, "setLargeIcon",
9501                     mBuilder.mN.mLargeIcon);
9502             contentView.setBundle(R.id.status_bar_latest_event_content, "setData",
9503                     mBuilder.mN.extras);
9504 
9505             return contentView;
9506         }
9507 
bindCallerVerification(RemoteViews contentView, StandardTemplateParams p)9508         private void bindCallerVerification(RemoteViews contentView, StandardTemplateParams p) {
9509             String iconContentDescription = null;
9510             boolean showDivider = true;
9511             if (mVerificationIcon != null) {
9512                 contentView.setImageViewIcon(R.id.verification_icon, mVerificationIcon);
9513                 contentView.setDrawableTint(R.id.verification_icon, false /* targetBackground */,
9514                         mBuilder.getSecondaryTextColor(p), PorterDuff.Mode.SRC_ATOP);
9515                 contentView.setViewVisibility(R.id.verification_icon, View.VISIBLE);
9516                 iconContentDescription = mBuilder.mContext.getString(
9517                         R.string.notification_verified_content_description);
9518                 showDivider = false;  // the icon replaces the divider
9519             } else {
9520                 contentView.setViewVisibility(R.id.verification_icon, View.GONE);
9521             }
9522             if (!TextUtils.isEmpty(mVerificationText)) {
9523                 contentView.setTextViewText(R.id.verification_text, mVerificationText);
9524                 mBuilder.setTextViewColorSecondary(contentView, R.id.verification_text, p);
9525                 contentView.setViewVisibility(R.id.verification_text, View.VISIBLE);
9526                 iconContentDescription = null;  // let the app's text take precedence
9527             } else {
9528                 contentView.setViewVisibility(R.id.verification_text, View.GONE);
9529                 showDivider = false;  // no divider if no text
9530             }
9531             contentView.setContentDescription(R.id.verification_icon, iconContentDescription);
9532             if (showDivider) {
9533                 contentView.setViewVisibility(R.id.verification_divider, View.VISIBLE);
9534                 mBuilder.setTextViewColorSecondary(contentView, R.id.verification_divider, p);
9535             } else {
9536                 contentView.setViewVisibility(R.id.verification_divider, View.GONE);
9537             }
9538         }
9539 
9540         @Nullable
getDefaultText()9541         private String getDefaultText() {
9542             switch (mCallType) {
9543                 case CALL_TYPE_INCOMING:
9544                     return mBuilder.mContext.getString(R.string.call_notification_incoming_text);
9545                 case CALL_TYPE_ONGOING:
9546                     return mBuilder.mContext.getString(R.string.call_notification_ongoing_text);
9547                 case CALL_TYPE_SCREENING:
9548                     return mBuilder.mContext.getString(R.string.call_notification_screening_text);
9549             }
9550             return null;
9551         }
9552 
9553         /**
9554          * @hide
9555          */
addExtras(Bundle extras)9556         public void addExtras(Bundle extras) {
9557             super.addExtras(extras);
9558             extras.putInt(EXTRA_CALL_TYPE, mCallType);
9559             extras.putBoolean(EXTRA_CALL_IS_VIDEO, mIsVideo);
9560             extras.putParcelable(EXTRA_CALL_PERSON, mPerson);
9561             if (mVerificationIcon != null) {
9562                 extras.putParcelable(EXTRA_VERIFICATION_ICON, mVerificationIcon);
9563             }
9564             if (mVerificationText != null) {
9565                 extras.putCharSequence(EXTRA_VERIFICATION_TEXT, mVerificationText);
9566             }
9567             if (mAnswerIntent != null) {
9568                 extras.putParcelable(EXTRA_ANSWER_INTENT, mAnswerIntent);
9569             }
9570             if (mDeclineIntent != null) {
9571                 extras.putParcelable(EXTRA_DECLINE_INTENT, mDeclineIntent);
9572             }
9573             if (mHangUpIntent != null) {
9574                 extras.putParcelable(EXTRA_HANG_UP_INTENT, mHangUpIntent);
9575             }
9576             if (mAnswerButtonColor != null) {
9577                 extras.putInt(EXTRA_ANSWER_COLOR, mAnswerButtonColor);
9578             }
9579             if (mDeclineButtonColor != null) {
9580                 extras.putInt(EXTRA_DECLINE_COLOR, mDeclineButtonColor);
9581             }
9582             fixTitleAndTextExtras(extras);
9583         }
9584 
fixTitleAndTextExtras(Bundle extras)9585         private void fixTitleAndTextExtras(Bundle extras) {
9586             CharSequence sender = mPerson != null ? mPerson.getName() : null;
9587             if (sender != null) {
9588                 extras.putCharSequence(EXTRA_TITLE, sender);
9589             }
9590             if (extras.getCharSequence(EXTRA_TEXT) == null) {
9591                 extras.putCharSequence(EXTRA_TEXT, getDefaultText());
9592             }
9593         }
9594 
9595         /**
9596          * @hide
9597          */
9598         @Override
restoreFromExtras(Bundle extras)9599         protected void restoreFromExtras(Bundle extras) {
9600             super.restoreFromExtras(extras);
9601             mCallType = extras.getInt(EXTRA_CALL_TYPE);
9602             mIsVideo = extras.getBoolean(EXTRA_CALL_IS_VIDEO);
9603             mPerson = extras.getParcelable(EXTRA_CALL_PERSON);
9604             mVerificationIcon = extras.getParcelable(EXTRA_VERIFICATION_ICON);
9605             mVerificationText = extras.getCharSequence(EXTRA_VERIFICATION_TEXT);
9606             mAnswerIntent = extras.getParcelable(EXTRA_ANSWER_INTENT);
9607             mDeclineIntent = extras.getParcelable(EXTRA_DECLINE_INTENT);
9608             mHangUpIntent = extras.getParcelable(EXTRA_HANG_UP_INTENT);
9609             mAnswerButtonColor = extras.containsKey(EXTRA_ANSWER_COLOR)
9610                     ? extras.getInt(EXTRA_ANSWER_COLOR) : null;
9611             mDeclineButtonColor = extras.containsKey(EXTRA_DECLINE_COLOR)
9612                     ? extras.getInt(EXTRA_DECLINE_COLOR) : null;
9613         }
9614 
9615         /**
9616          * @hide
9617          */
9618         @Override
hasSummaryInHeader()9619         public boolean hasSummaryInHeader() {
9620             return false;
9621         }
9622 
9623         /**
9624          * @hide
9625          */
9626         @Override
areNotificationsVisiblyDifferent(Style other)9627         public boolean areNotificationsVisiblyDifferent(Style other) {
9628             if (other == null || getClass() != other.getClass()) {
9629                 return true;
9630             }
9631             CallStyle otherS = (CallStyle) other;
9632             return !Objects.equals(mCallType, otherS.mCallType)
9633                     || !Objects.equals(mPerson, otherS.mPerson)
9634                     || !Objects.equals(mVerificationText, otherS.mVerificationText);
9635         }
9636     }
9637 
9638     /**
9639      * Notification style for custom views that are decorated by the system
9640      *
9641      * <p>Instead of providing a notification that is completely custom, a developer can set this
9642      * style and still obtain system decorations like the notification header with the expand
9643      * affordance and actions.
9644      *
9645      * <p>Use {@link android.app.Notification.Builder#setCustomContentView(RemoteViews)},
9646      * {@link android.app.Notification.Builder#setCustomBigContentView(RemoteViews)} and
9647      * {@link android.app.Notification.Builder#setCustomHeadsUpContentView(RemoteViews)} to set the
9648      * corresponding custom views to display.
9649      *
9650      * To use this style with your Notification, feed it to
9651      * {@link Notification.Builder#setStyle(android.app.Notification.Style)} like so:
9652      * <pre class="prettyprint">
9653      * Notification noti = new Notification.Builder()
9654      *     .setSmallIcon(R.drawable.ic_stat_player)
9655      *     .setLargeIcon(albumArtBitmap))
9656      *     .setCustomContentView(contentView);
9657      *     .setStyle(<b>new Notification.DecoratedCustomViewStyle()</b>)
9658      *     .build();
9659      * </pre>
9660      */
9661     public static class DecoratedCustomViewStyle extends Style {
9662 
DecoratedCustomViewStyle()9663         public DecoratedCustomViewStyle() {
9664         }
9665 
9666         /**
9667          * @hide
9668          */
displayCustomViewInline()9669         public boolean displayCustomViewInline() {
9670             return true;
9671         }
9672 
9673         /**
9674          * @hide
9675          */
9676         @Override
makeContentView(boolean increasedHeight)9677         public RemoteViews makeContentView(boolean increasedHeight) {
9678             return makeStandardTemplateWithCustomContent(mBuilder.mN.contentView);
9679         }
9680 
9681         /**
9682          * @hide
9683          */
9684         @Override
makeBigContentView()9685         public RemoteViews makeBigContentView() {
9686             return makeDecoratedBigContentView();
9687         }
9688 
9689         /**
9690          * @hide
9691          */
9692         @Override
makeHeadsUpContentView(boolean increasedHeight)9693         public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
9694             return makeDecoratedHeadsUpContentView();
9695         }
9696 
makeDecoratedHeadsUpContentView()9697         private RemoteViews makeDecoratedHeadsUpContentView() {
9698             RemoteViews headsUpContentView = mBuilder.mN.headsUpContentView == null
9699                     ? mBuilder.mN.contentView
9700                     : mBuilder.mN.headsUpContentView;
9701             if (headsUpContentView == null) {
9702                 return null;  // no custom view; use the default behavior
9703             }
9704             if (mBuilder.mActions.size() == 0) {
9705                return makeStandardTemplateWithCustomContent(headsUpContentView);
9706             }
9707             TemplateBindResult result = new TemplateBindResult();
9708             StandardTemplateParams p = mBuilder.mParams.reset()
9709                     .viewType(StandardTemplateParams.VIEW_TYPE_HEADS_UP)
9710                     .decorationType(StandardTemplateParams.DECORATION_PARTIAL)
9711                     .fillTextsFrom(mBuilder);
9712             RemoteViews remoteViews = mBuilder.applyStandardTemplateWithActions(
9713                     mBuilder.getHeadsUpBaseLayoutResource(), p, result);
9714             buildCustomContentIntoTemplate(mBuilder.mContext, remoteViews, headsUpContentView,
9715                     p, result);
9716             return remoteViews;
9717         }
9718 
makeStandardTemplateWithCustomContent(RemoteViews customContent)9719         private RemoteViews makeStandardTemplateWithCustomContent(RemoteViews customContent) {
9720             if (customContent == null) {
9721                 return null;  // no custom view; use the default behavior
9722             }
9723             TemplateBindResult result = new TemplateBindResult();
9724             StandardTemplateParams p = mBuilder.mParams.reset()
9725                     .viewType(StandardTemplateParams.VIEW_TYPE_NORMAL)
9726                     .decorationType(StandardTemplateParams.DECORATION_PARTIAL)
9727                     .fillTextsFrom(mBuilder);
9728             RemoteViews remoteViews = mBuilder.applyStandardTemplate(
9729                     mBuilder.getBaseLayoutResource(), p, result);
9730             buildCustomContentIntoTemplate(mBuilder.mContext, remoteViews, customContent,
9731                     p, result);
9732             return remoteViews;
9733         }
9734 
makeDecoratedBigContentView()9735         private RemoteViews makeDecoratedBigContentView() {
9736             RemoteViews bigContentView = mBuilder.mN.bigContentView == null
9737                     ? mBuilder.mN.contentView
9738                     : mBuilder.mN.bigContentView;
9739             if (bigContentView == null) {
9740                 return null;  // no custom view; use the default behavior
9741             }
9742             TemplateBindResult result = new TemplateBindResult();
9743             StandardTemplateParams p = mBuilder.mParams.reset()
9744                     .viewType(StandardTemplateParams.VIEW_TYPE_BIG)
9745                     .decorationType(StandardTemplateParams.DECORATION_PARTIAL)
9746                     .fillTextsFrom(mBuilder);
9747             RemoteViews remoteViews = mBuilder.applyStandardTemplateWithActions(
9748                     mBuilder.getBigBaseLayoutResource(), p, result);
9749             buildCustomContentIntoTemplate(mBuilder.mContext, remoteViews, bigContentView,
9750                     p, result);
9751             return remoteViews;
9752         }
9753 
9754         /**
9755          * @hide
9756          */
9757         @Override
areNotificationsVisiblyDifferent(Style other)9758         public boolean areNotificationsVisiblyDifferent(Style other) {
9759             if (other == null || getClass() != other.getClass()) {
9760                 return true;
9761             }
9762             // Comparison done for all custom RemoteViews, independent of style
9763             return false;
9764         }
9765     }
9766 
9767     /**
9768      * Notification style for media custom views that are decorated by the system
9769      *
9770      * <p>Instead of providing a media notification that is completely custom, a developer can set
9771      * this style and still obtain system decorations like the notification header with the expand
9772      * affordance and actions.
9773      *
9774      * <p>Use {@link android.app.Notification.Builder#setCustomContentView(RemoteViews)},
9775      * {@link android.app.Notification.Builder#setCustomBigContentView(RemoteViews)} and
9776      * {@link android.app.Notification.Builder#setCustomHeadsUpContentView(RemoteViews)} to set the
9777      * corresponding custom views to display.
9778      * <p>
9779      * Contrary to {@link MediaStyle} a developer has to opt-in to the colorizing of the
9780      * notification by using {@link Notification.Builder#setColorized(boolean)}.
9781      * <p>
9782      * To use this style with your Notification, feed it to
9783      * {@link Notification.Builder#setStyle(android.app.Notification.Style)} like so:
9784      * <pre class="prettyprint">
9785      * Notification noti = new Notification.Builder()
9786      *     .setSmallIcon(R.drawable.ic_stat_player)
9787      *     .setLargeIcon(albumArtBitmap))
9788      *     .setCustomContentView(contentView);
9789      *     .setStyle(<b>new Notification.DecoratedMediaCustomViewStyle()</b>
9790      *          .setMediaSession(mySession))
9791      *     .build();
9792      * </pre>
9793      *
9794      * @see android.app.Notification.DecoratedCustomViewStyle
9795      * @see android.app.Notification.MediaStyle
9796      */
9797     public static class DecoratedMediaCustomViewStyle extends MediaStyle {
9798 
DecoratedMediaCustomViewStyle()9799         public DecoratedMediaCustomViewStyle() {
9800         }
9801 
9802         /**
9803          * @hide
9804          */
displayCustomViewInline()9805         public boolean displayCustomViewInline() {
9806             return true;
9807         }
9808 
9809         /**
9810          * @hide
9811          */
9812         @Override
makeContentView(boolean increasedHeight)9813         public RemoteViews makeContentView(boolean increasedHeight) {
9814             return makeMediaContentView(mBuilder.mN.contentView);
9815         }
9816 
9817         /**
9818          * @hide
9819          */
9820         @Override
makeBigContentView()9821         public RemoteViews makeBigContentView() {
9822             RemoteViews customContent = mBuilder.mN.bigContentView != null
9823                     ? mBuilder.mN.bigContentView
9824                     : mBuilder.mN.contentView;
9825             return makeMediaBigContentView(customContent);
9826         }
9827 
9828         /**
9829          * @hide
9830          */
9831         @Override
makeHeadsUpContentView(boolean increasedHeight)9832         public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
9833             RemoteViews customContent = mBuilder.mN.headsUpContentView != null
9834                     ? mBuilder.mN.headsUpContentView
9835                     : mBuilder.mN.contentView;
9836             return makeMediaBigContentView(customContent);
9837         }
9838 
9839         /**
9840          * @hide
9841          */
9842         @Override
areNotificationsVisiblyDifferent(Style other)9843         public boolean areNotificationsVisiblyDifferent(Style other) {
9844             if (other == null || getClass() != other.getClass()) {
9845                 return true;
9846             }
9847             // Comparison done for all custom RemoteViews, independent of style
9848             return false;
9849         }
9850     }
9851 
9852     /**
9853      * Encapsulates the information needed to display a notification as a bubble.
9854      *
9855      * <p>A bubble is used to display app content in a floating window over the existing
9856      * foreground activity. A bubble has a collapsed state represented by an icon and an
9857      * expanded state that displays an activity. These may be defined via
9858      * {@link Builder#Builder(PendingIntent, Icon)} or they may
9859      * be defined via an existing shortcut using {@link Builder#Builder(String)}.
9860      * </p>
9861      *
9862      * <b>Notifications with a valid and allowed bubble will display in collapsed state
9863      * outside of the notification shade on unlocked devices. When a user interacts with the
9864      * collapsed bubble, the bubble activity will be invoked and displayed.</b>
9865      *
9866      * @see Notification.Builder#setBubbleMetadata(BubbleMetadata)
9867      */
9868     public static final class BubbleMetadata implements Parcelable {
9869 
9870         private PendingIntent mPendingIntent;
9871         private PendingIntent mDeleteIntent;
9872         private Icon mIcon;
9873         private int mDesiredHeight;
9874         @DimenRes private int mDesiredHeightResId;
9875         private int mFlags;
9876         private String mShortcutId;
9877 
9878         /**
9879          * If set and the app creating the bubble is in the foreground, the bubble will be posted
9880          * in its expanded state.
9881          *
9882          * <p>This flag has no effect if the app posting the bubble is not in the foreground.
9883          * The app is considered foreground if it is visible and on the screen, note that
9884          * a foreground service does not qualify.
9885          * </p>
9886          *
9887          * <p>Generally this flag should only be set if the user has performed an action to request
9888          * or create a bubble.</p>
9889          *
9890          * @hide
9891          */
9892         public static final int FLAG_AUTO_EXPAND_BUBBLE = 0x00000001;
9893 
9894         /**
9895          * Indicates whether the notification associated with the bubble is being visually
9896          * suppressed from the notification shade. When <code>true</code> the notification is
9897          * hidden, when <code>false</code> the notification shows as normal.
9898          *
9899          * <p>Apps sending bubbles may set this flag so that the bubble is posted <b>without</b>
9900          * the associated notification in the notification shade.</p>
9901          *
9902          * <p>Generally this flag should only be set by the app if the user has performed an
9903          * action to request or create a bubble, or if the user has seen the content in the
9904          * notification and the notification is no longer relevant. </p>
9905          *
9906          * <p>The system will also update this flag with <code>true</code> to hide the notification
9907          * from the user once the bubble has been expanded. </p>
9908          *
9909          * @hide
9910          */
9911         public static final int FLAG_SUPPRESS_NOTIFICATION = 0x00000002;
9912 
9913         /**
9914          * Indicates whether the bubble should be visually suppressed from the bubble stack if the
9915          * user is viewing the same content outside of the bubble. For example, the user has a
9916          * bubble with Alice and then opens up the main app and navigates to Alice's page.
9917          *
9918          * @hide
9919          */
9920         public static final int FLAG_SUPPRESSABLE_BUBBLE = 0x00000004;
9921 
9922         /**
9923          * Indicates whether the bubble is visually suppressed from the bubble stack.
9924          *
9925          * @hide
9926          */
9927         public static final int FLAG_SUPPRESS_BUBBLE = 0x00000008;
9928 
BubbleMetadata(PendingIntent expandIntent, PendingIntent deleteIntent, Icon icon, int height, @DimenRes int heightResId, String shortcutId)9929         private BubbleMetadata(PendingIntent expandIntent, PendingIntent deleteIntent,
9930                 Icon icon, int height, @DimenRes int heightResId, String shortcutId) {
9931             mPendingIntent = expandIntent;
9932             mIcon = icon;
9933             mDesiredHeight = height;
9934             mDesiredHeightResId = heightResId;
9935             mDeleteIntent = deleteIntent;
9936             mShortcutId = shortcutId;
9937         }
9938 
BubbleMetadata(Parcel in)9939         private BubbleMetadata(Parcel in) {
9940             if (in.readInt() != 0) {
9941                 mPendingIntent = PendingIntent.CREATOR.createFromParcel(in);
9942             }
9943             if (in.readInt() != 0) {
9944                 mIcon = Icon.CREATOR.createFromParcel(in);
9945             }
9946             mDesiredHeight = in.readInt();
9947             mFlags = in.readInt();
9948             if (in.readInt() != 0) {
9949                 mDeleteIntent = PendingIntent.CREATOR.createFromParcel(in);
9950             }
9951             mDesiredHeightResId = in.readInt();
9952             if (in.readInt() != 0) {
9953                 mShortcutId = in.readString8();
9954             }
9955         }
9956 
9957         /**
9958          * @return the shortcut id used for this bubble if created via
9959          * {@link Builder#Builder(String)} or null if created
9960          * via {@link Builder#Builder(PendingIntent, Icon)}.
9961          */
9962         @Nullable
getShortcutId()9963         public String getShortcutId() {
9964             return mShortcutId;
9965         }
9966 
9967         /**
9968          * @return the pending intent used to populate the floating window for this bubble, or
9969          * null if this bubble is created via {@link Builder#Builder(String)}.
9970          */
9971         @SuppressLint("InvalidNullConversion")
9972         @Nullable
getIntent()9973         public PendingIntent getIntent() {
9974             return mPendingIntent;
9975         }
9976 
9977         /**
9978          * @deprecated use {@link #getIntent()} instead.
9979          * @removed Removed from the R SDK but was never publicly stable.
9980          */
9981         @Nullable
9982         @Deprecated
getBubbleIntent()9983         public PendingIntent getBubbleIntent() {
9984             return mPendingIntent;
9985         }
9986 
9987         /**
9988          * @return the pending intent to send when the bubble is dismissed by a user, if one exists.
9989          */
9990         @Nullable
getDeleteIntent()9991         public PendingIntent getDeleteIntent() {
9992             return mDeleteIntent;
9993         }
9994 
9995         /**
9996          * @return the icon that will be displayed for this bubble when it is collapsed, or null
9997          * if the bubble is created via {@link Builder#Builder(String)}.
9998          */
9999         @SuppressLint("InvalidNullConversion")
10000         @Nullable
getIcon()10001         public Icon getIcon() {
10002             return mIcon;
10003         }
10004 
10005         /**
10006          * @deprecated use {@link #getIcon()} instead.
10007          * @removed Removed from the R SDK but was never publicly stable.
10008          */
10009         @Nullable
10010         @Deprecated
getBubbleIcon()10011         public Icon getBubbleIcon() {
10012             return mIcon;
10013         }
10014 
10015         /**
10016          * @return the ideal height, in DPs, for the floating window that app content defined by
10017          * {@link #getIntent()} for this bubble. A value of 0 indicates a desired height has
10018          * not been set.
10019          */
10020         @Dimension(unit = DP)
getDesiredHeight()10021         public int getDesiredHeight() {
10022             return mDesiredHeight;
10023         }
10024 
10025         /**
10026          * @return the resId of ideal height for the floating window that app content defined by
10027          * {@link #getIntent()} for this bubble. A value of 0 indicates a res value has not
10028          * been provided for the desired height.
10029          */
10030         @DimenRes
getDesiredHeightResId()10031         public int getDesiredHeightResId() {
10032             return mDesiredHeightResId;
10033         }
10034 
10035         /**
10036          * @return whether this bubble should auto expand when it is posted.
10037          *
10038          * @see BubbleMetadata.Builder#setAutoExpandBubble(boolean)
10039          */
getAutoExpandBubble()10040         public boolean getAutoExpandBubble() {
10041             return (mFlags & FLAG_AUTO_EXPAND_BUBBLE) != 0;
10042         }
10043 
10044         /**
10045          * Indicates whether the notification associated with the bubble is being visually
10046          * suppressed from the notification shade. When <code>true</code> the notification is
10047          * hidden, when <code>false</code> the notification shows as normal.
10048          *
10049          * <p>Apps sending bubbles may set this flag so that the bubble is posted <b>without</b>
10050          * the associated notification in the notification shade.</p>
10051          *
10052          * <p>Generally the app should only set this flag if the user has performed an
10053          * action to request or create a bubble, or if the user has seen the content in the
10054          * notification and the notification is no longer relevant. </p>
10055          *
10056          * <p>The system will update this flag with <code>true</code> to hide the notification
10057          * from the user once the bubble has been expanded.</p>
10058          *
10059          * @return whether this bubble should suppress the notification when it is posted.
10060          *
10061          * @see BubbleMetadata.Builder#setSuppressNotification(boolean)
10062          */
isNotificationSuppressed()10063         public boolean isNotificationSuppressed() {
10064             return (mFlags & FLAG_SUPPRESS_NOTIFICATION) != 0;
10065         }
10066 
10067         /**
10068          * Indicates whether the bubble should be visually suppressed from the bubble stack if the
10069          * user is viewing the same content outside of the bubble. For example, the user has a
10070          * bubble with Alice and then opens up the main app and navigates to Alice's page.
10071          *
10072          * To match the activity and the bubble notification, the bubble notification should
10073          * have a locus id set that matches a locus id set on the activity.
10074          *
10075          * @return whether this bubble should be suppressed when the same content is visible
10076          * outside of the bubble.
10077          *
10078          * @see BubbleMetadata.Builder#setSuppressableBubble(boolean)
10079          */
isBubbleSuppressable()10080         public boolean isBubbleSuppressable() {
10081             return (mFlags & FLAG_SUPPRESSABLE_BUBBLE) != 0;
10082         }
10083 
10084         /**
10085          * Indicates whether the bubble is currently visually suppressed from the bubble stack.
10086          *
10087          * @see BubbleMetadata.Builder#setSuppressableBubble(boolean)
10088          */
isBubbleSuppressed()10089         public boolean isBubbleSuppressed() {
10090             return (mFlags & FLAG_SUPPRESS_BUBBLE) != 0;
10091         }
10092 
10093         /**
10094          * Sets whether the notification associated with the bubble is being visually
10095          * suppressed from the notification shade. When <code>true</code> the notification is
10096          * hidden, when <code>false</code> the notification shows as normal.
10097          *
10098          * @hide
10099          */
setSuppressNotification(boolean suppressed)10100         public void setSuppressNotification(boolean suppressed) {
10101             if (suppressed) {
10102                 mFlags |= Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION;
10103             } else {
10104                 mFlags &= ~Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION;
10105             }
10106         }
10107 
10108         /**
10109          * Sets whether the bubble should be visually suppressed from the bubble stack if the
10110          * user is viewing the same content outside of the bubble. For example, the user has a
10111          * bubble with Alice and then opens up the main app and navigates to Alice's page.
10112          *
10113          * @hide
10114          */
setSuppressBubble(boolean suppressed)10115         public void setSuppressBubble(boolean suppressed) {
10116             if (suppressed) {
10117                 mFlags |= Notification.BubbleMetadata.FLAG_SUPPRESS_BUBBLE;
10118             } else {
10119                 mFlags &= ~Notification.BubbleMetadata.FLAG_SUPPRESS_BUBBLE;
10120             }
10121         }
10122 
10123         /**
10124          * @hide
10125          */
setFlags(int flags)10126         public void setFlags(int flags) {
10127             mFlags = flags;
10128         }
10129 
10130         /**
10131          * @hide
10132          */
getFlags()10133         public int getFlags() {
10134             return mFlags;
10135         }
10136 
10137         public static final @android.annotation.NonNull Parcelable.Creator<BubbleMetadata> CREATOR =
10138                 new Parcelable.Creator<BubbleMetadata>() {
10139 
10140                     @Override
10141                     public BubbleMetadata createFromParcel(Parcel source) {
10142                         return new BubbleMetadata(source);
10143                     }
10144 
10145                     @Override
10146                     public BubbleMetadata[] newArray(int size) {
10147                         return new BubbleMetadata[size];
10148                     }
10149                 };
10150 
10151         @Override
describeContents()10152         public int describeContents() {
10153             return 0;
10154         }
10155 
10156         @Override
writeToParcel(Parcel out, int flags)10157         public void writeToParcel(Parcel out, int flags) {
10158             out.writeInt(mPendingIntent != null ? 1 : 0);
10159             if (mPendingIntent != null) {
10160                 mPendingIntent.writeToParcel(out, 0);
10161             }
10162             out.writeInt(mIcon != null ? 1 : 0);
10163             if (mIcon != null) {
10164                 mIcon.writeToParcel(out, 0);
10165             }
10166             out.writeInt(mDesiredHeight);
10167             out.writeInt(mFlags);
10168             out.writeInt(mDeleteIntent != null ? 1 : 0);
10169             if (mDeleteIntent != null) {
10170                 mDeleteIntent.writeToParcel(out, 0);
10171             }
10172             out.writeInt(mDesiredHeightResId);
10173             out.writeInt(TextUtils.isEmpty(mShortcutId) ? 0 : 1);
10174             if (!TextUtils.isEmpty(mShortcutId)) {
10175                 out.writeString8(mShortcutId);
10176             }
10177         }
10178 
10179         /**
10180          * Builder to construct a {@link BubbleMetadata} object.
10181          */
10182         public static final class Builder {
10183 
10184             private PendingIntent mPendingIntent;
10185             private Icon mIcon;
10186             private int mDesiredHeight;
10187             @DimenRes private int mDesiredHeightResId;
10188             private int mFlags;
10189             private PendingIntent mDeleteIntent;
10190             private String mShortcutId;
10191 
10192             /**
10193              * @deprecated use {@link Builder#Builder(String)} for a bubble created via a
10194              * {@link ShortcutInfo} or {@link Builder#Builder(PendingIntent, Icon)} for a bubble
10195              * created via a {@link PendingIntent}.
10196              */
10197             @Deprecated
Builder()10198             public Builder() {
10199             }
10200 
10201             /**
10202              * Creates a {@link BubbleMetadata.Builder} based on a {@link ShortcutInfo}. To create
10203              * a shortcut bubble, ensure that the shortcut associated with the provided
10204              * {@param shortcutId} is published as a dynamic shortcut that was built with
10205              * {@link ShortcutInfo.Builder#setLongLived(boolean)} being true, otherwise your
10206              * notification will not be able to bubble.
10207              *
10208              * <p>The shortcut icon will be used to represent the bubble when it is collapsed.</p>
10209              *
10210              * <p>The shortcut activity will be used when the bubble is expanded. This will display
10211              * the shortcut activity in a floating window over the existing foreground activity.</p>
10212              *
10213              * <p>When the activity is launched from a bubble,
10214              * {@link Activity#isLaunchedFromBubble()} will return with {@code true}.
10215              * </p>
10216              *
10217              * <p>If the shortcut has not been published when the bubble notification is sent,
10218              * no bubble will be produced. If the shortcut is deleted while the bubble is active,
10219              * the bubble will be removed.</p>
10220              *
10221              * @throws NullPointerException if shortcutId is null.
10222              *
10223              * @see ShortcutInfo
10224              * @see ShortcutInfo.Builder#setLongLived(boolean)
10225              * @see android.content.pm.ShortcutManager#addDynamicShortcuts(List)
10226              */
Builder(@onNull String shortcutId)10227             public Builder(@NonNull String shortcutId) {
10228                 if (TextUtils.isEmpty(shortcutId)) {
10229                     throw new NullPointerException("Bubble requires a non-null shortcut id");
10230                 }
10231                 mShortcutId = shortcutId;
10232             }
10233 
10234             /**
10235              * Creates a {@link BubbleMetadata.Builder} based on the provided intent and icon.
10236              *
10237              * <p>The icon will be used to represent the bubble when it is collapsed. An icon
10238              * should be representative of the content within the bubble. If your app produces
10239              * multiple bubbles, the icon should be unique for each of them.</p>
10240              *
10241              * <p>The intent that will be used when the bubble is expanded. This will display the
10242              * app content in a floating window over the existing foreground activity. The intent
10243              * should point to a resizable activity. </p>
10244              *
10245              * <p>When the activity is launched from a bubble,
10246              * {@link Activity#isLaunchedFromBubble()} will return with {@code true}.
10247              * </p>
10248              *
10249              * Note that the pending intent used here requires PendingIntent.FLAG_MUTABLE.
10250              *
10251              * @throws NullPointerException if intent is null.
10252              * @throws NullPointerException if icon is null.
10253              */
Builder(@onNull PendingIntent intent, @NonNull Icon icon)10254             public Builder(@NonNull PendingIntent intent, @NonNull Icon icon) {
10255                 if (intent == null) {
10256                     throw new NullPointerException("Bubble requires non-null pending intent");
10257                 }
10258                 if (icon == null) {
10259                     throw new NullPointerException("Bubbles require non-null icon");
10260                 }
10261                 if (icon.getType() != TYPE_URI_ADAPTIVE_BITMAP
10262                         && icon.getType() != TYPE_URI) {
10263                     Log.w(TAG, "Bubbles work best with icons of TYPE_URI or "
10264                             + "TYPE_URI_ADAPTIVE_BITMAP. "
10265                             + "In the future, using an icon of this type will be required.");
10266                 }
10267                 mPendingIntent = intent;
10268                 mIcon = icon;
10269             }
10270 
10271             /**
10272              * @deprecated use {@link Builder#Builder(String)} instead.
10273              * @removed Removed from the R SDK but was never publicly stable.
10274              */
10275             @NonNull
10276             @Deprecated
createShortcutBubble(@onNull String shortcutId)10277             public BubbleMetadata.Builder createShortcutBubble(@NonNull String shortcutId) {
10278                 if (!TextUtils.isEmpty(shortcutId)) {
10279                     // If shortcut id is set, we don't use these if they were previously set.
10280                     mPendingIntent = null;
10281                     mIcon = null;
10282                 }
10283                 mShortcutId = shortcutId;
10284                 return this;
10285             }
10286 
10287             /**
10288              * @deprecated use {@link Builder#Builder(PendingIntent, Icon)} instead.
10289              * @removed Removed from the R SDK but was never publicly stable.
10290              */
10291             @NonNull
10292             @Deprecated
createIntentBubble(@onNull PendingIntent intent, @NonNull Icon icon)10293             public BubbleMetadata.Builder createIntentBubble(@NonNull PendingIntent intent,
10294                     @NonNull Icon icon) {
10295                 if (intent == null) {
10296                     throw new IllegalArgumentException("Bubble requires non-null pending intent");
10297                 }
10298                 if (icon == null) {
10299                     throw new IllegalArgumentException("Bubbles require non-null icon");
10300                 }
10301                 if (icon.getType() != TYPE_URI_ADAPTIVE_BITMAP
10302                         && icon.getType() != TYPE_URI) {
10303                     Log.w(TAG, "Bubbles work best with icons of TYPE_URI or "
10304                             + "TYPE_URI_ADAPTIVE_BITMAP. "
10305                             + "In the future, using an icon of this type will be required.");
10306                 }
10307                 mShortcutId = null;
10308                 mPendingIntent = intent;
10309                 mIcon = icon;
10310                 return this;
10311             }
10312 
10313             /**
10314              * Sets the intent for the bubble.
10315              *
10316              * <p>The intent that will be used when the bubble is expanded. This will display the
10317              * app content in a floating window over the existing foreground activity. The intent
10318              * should point to a resizable activity. </p>
10319              *
10320              * @throws NullPointerException  if intent is null.
10321              * @throws IllegalStateException if this builder was created via
10322              *                               {@link Builder#Builder(String)}.
10323              */
10324             @NonNull
setIntent(@onNull PendingIntent intent)10325             public BubbleMetadata.Builder setIntent(@NonNull PendingIntent intent) {
10326                 if (mShortcutId != null) {
10327                     throw new IllegalStateException("Created as a shortcut bubble, cannot set a "
10328                             + "PendingIntent. Consider using "
10329                             + "BubbleMetadata.Builder(PendingIntent,Icon) instead.");
10330                 }
10331                 if (intent == null) {
10332                     throw new NullPointerException("Bubble requires non-null pending intent");
10333                 }
10334                 mPendingIntent = intent;
10335                 return this;
10336             }
10337 
10338             /**
10339              * Sets the icon for the bubble. Can only be used if the bubble was created
10340              * via {@link Builder#Builder(PendingIntent, Icon)}.
10341              *
10342              * <p>The icon will be used to represent the bubble when it is collapsed. An icon
10343              * should be representative of the content within the bubble. If your app produces
10344              * multiple bubbles, the icon should be unique for each of them.</p>
10345              *
10346              * <p>It is recommended to use an {@link Icon} of type {@link Icon#TYPE_URI}
10347              * or {@link Icon#TYPE_URI_ADAPTIVE_BITMAP}</p>
10348              *
10349              * @throws NullPointerException  if icon is null.
10350              * @throws IllegalStateException if this builder was created via
10351              *                               {@link Builder#Builder(String)}.
10352              */
10353             @NonNull
setIcon(@onNull Icon icon)10354             public BubbleMetadata.Builder setIcon(@NonNull Icon icon) {
10355                 if (mShortcutId != null) {
10356                     throw new IllegalStateException("Created as a shortcut bubble, cannot set an "
10357                             + "Icon. Consider using "
10358                             + "BubbleMetadata.Builder(PendingIntent,Icon) instead.");
10359                 }
10360                 if (icon == null) {
10361                     throw new NullPointerException("Bubbles require non-null icon");
10362                 }
10363                 if (icon.getType() != TYPE_URI_ADAPTIVE_BITMAP
10364                         && icon.getType() != TYPE_URI) {
10365                     Log.w(TAG, "Bubbles work best with icons of TYPE_URI or "
10366                             + "TYPE_URI_ADAPTIVE_BITMAP. "
10367                             + "In the future, using an icon of this type will be required.");
10368                 }
10369                 mIcon = icon;
10370                 return this;
10371             }
10372 
10373             /**
10374              * Sets the desired height in DPs for the expanded content of the bubble.
10375              *
10376              * <p>This height may not be respected if there is not enough space on the screen or if
10377              * the provided height is too small to be useful.</p>
10378              *
10379              * <p>If {@link #setDesiredHeightResId(int)} was previously called on this builder, the
10380              * previous value set will be cleared after calling this method, and this value will
10381              * be used instead.</p>
10382              *
10383              * <p>A desired height (in DPs or via resID) is optional.</p>
10384              *
10385              * @see #setDesiredHeightResId(int)
10386              */
10387             @NonNull
setDesiredHeight(@imensionunit = DP) int height)10388             public BubbleMetadata.Builder setDesiredHeight(@Dimension(unit = DP) int height) {
10389                 mDesiredHeight = Math.max(height, 0);
10390                 mDesiredHeightResId = 0;
10391                 return this;
10392             }
10393 
10394 
10395             /**
10396              * Sets the desired height via resId for the expanded content of the bubble.
10397              *
10398              * <p>This height may not be respected if there is not enough space on the screen or if
10399              * the provided height is too small to be useful.</p>
10400              *
10401              * <p>If {@link #setDesiredHeight(int)} was previously called on this builder, the
10402              * previous value set will be cleared after calling this method, and this value will
10403              * be used instead.</p>
10404              *
10405              * <p>A desired height (in DPs or via resID) is optional.</p>
10406              *
10407              * @see #setDesiredHeight(int)
10408              */
10409             @NonNull
setDesiredHeightResId(@imenRes int heightResId)10410             public BubbleMetadata.Builder setDesiredHeightResId(@DimenRes int heightResId) {
10411                 mDesiredHeightResId = heightResId;
10412                 mDesiredHeight = 0;
10413                 return this;
10414             }
10415 
10416             /**
10417              * Sets whether the bubble will be posted in its expanded state.
10418              *
10419              * <p>This flag has no effect if the app posting the bubble is not in the foreground.
10420              * The app is considered foreground if it is visible and on the screen, note that
10421              * a foreground service does not qualify.
10422              * </p>
10423              *
10424              * <p>Generally, this flag should only be set if the user has performed an action to
10425              * request or create a bubble.</p>
10426              *
10427              * <p>Setting this flag is optional; it defaults to false.</p>
10428              */
10429             @NonNull
setAutoExpandBubble(boolean shouldExpand)10430             public BubbleMetadata.Builder setAutoExpandBubble(boolean shouldExpand) {
10431                 setFlag(FLAG_AUTO_EXPAND_BUBBLE, shouldExpand);
10432                 return this;
10433             }
10434 
10435             /**
10436              * Sets whether the bubble will be posted <b>without</b> the associated notification in
10437              * the notification shade.
10438              *
10439              * <p>Generally, this flag should only be set if the user has performed an action to
10440              * request or create a bubble, or if the user has seen the content in the notification
10441              * and the notification is no longer relevant.</p>
10442              *
10443              * <p>Setting this flag is optional; it defaults to false.</p>
10444              */
10445             @NonNull
setSuppressNotification(boolean shouldSuppressNotif)10446             public BubbleMetadata.Builder setSuppressNotification(boolean shouldSuppressNotif) {
10447                 setFlag(FLAG_SUPPRESS_NOTIFICATION, shouldSuppressNotif);
10448                 return this;
10449             }
10450 
10451             /**
10452              * Indicates whether the bubble should be visually suppressed from the bubble stack if
10453              * the user is viewing the same content outside of the bubble. For example, the user has
10454              * a bubble with Alice and then opens up the main app and navigates to Alice's page.
10455              *
10456              * To match the activity and the bubble notification, the bubble notification should
10457              * have a locus id set that matches a locus id set on the activity.
10458              *
10459              * {@link Notification.Builder#setLocusId(LocusId)}
10460              * {@link Activity#setLocusContext(LocusId, Bundle)}
10461              */
10462             @NonNull
setSuppressableBubble(boolean suppressBubble)10463             public BubbleMetadata.Builder setSuppressableBubble(boolean suppressBubble) {
10464                 setFlag(FLAG_SUPPRESSABLE_BUBBLE, suppressBubble);
10465                 return this;
10466             }
10467 
10468             /**
10469              * Sets an intent to send when this bubble is explicitly removed by the user.
10470              *
10471              * <p>Setting a delete intent is optional.</p>
10472              */
10473             @NonNull
setDeleteIntent(@ullable PendingIntent deleteIntent)10474             public BubbleMetadata.Builder setDeleteIntent(@Nullable PendingIntent deleteIntent) {
10475                 mDeleteIntent = deleteIntent;
10476                 return this;
10477             }
10478 
10479             /**
10480              * Creates the {@link BubbleMetadata} defined by this builder.
10481              *
10482              * @throws NullPointerException if required elements have not been set.
10483              */
10484             @NonNull
build()10485             public BubbleMetadata build() {
10486                 if (mShortcutId == null && mPendingIntent == null) {
10487                     throw new NullPointerException(
10488                             "Must supply pending intent or shortcut to bubble");
10489                 }
10490                 if (mShortcutId == null && mIcon == null) {
10491                     throw new NullPointerException(
10492                             "Must supply an icon or shortcut for the bubble");
10493                 }
10494                 BubbleMetadata data = new BubbleMetadata(mPendingIntent, mDeleteIntent,
10495                         mIcon, mDesiredHeight, mDesiredHeightResId, mShortcutId);
10496                 data.setFlags(mFlags);
10497                 return data;
10498             }
10499 
10500             /**
10501              * @hide
10502              */
setFlag(int mask, boolean value)10503             public BubbleMetadata.Builder setFlag(int mask, boolean value) {
10504                 if (value) {
10505                     mFlags |= mask;
10506                 } else {
10507                     mFlags &= ~mask;
10508                 }
10509                 return this;
10510             }
10511         }
10512     }
10513 
10514 
10515     // When adding a new Style subclass here, don't forget to update
10516     // Builder.getNotificationStyleClass.
10517 
10518     /**
10519      * Extender interface for use with {@link Builder#extend}. Extenders may be used to add
10520      * metadata or change options on a notification builder.
10521      */
10522     public interface Extender {
10523         /**
10524          * Apply this extender to a notification builder.
10525          * @param builder the builder to be modified.
10526          * @return the build object for chaining.
10527          */
extend(Builder builder)10528         public Builder extend(Builder builder);
10529     }
10530 
10531     /**
10532      * Helper class to add wearable extensions to notifications.
10533      * <p class="note"> See
10534      * <a href="{@docRoot}wear/notifications/creating.html">Creating Notifications
10535      * for Android Wear</a> for more information on how to use this class.
10536      * <p>
10537      * To create a notification with wearable extensions:
10538      * <ol>
10539      *   <li>Create a {@link android.app.Notification.Builder}, setting any desired
10540      *   properties.
10541      *   <li>Create a {@link android.app.Notification.WearableExtender}.
10542      *   <li>Set wearable-specific properties using the
10543      *   {@code add} and {@code set} methods of {@link android.app.Notification.WearableExtender}.
10544      *   <li>Call {@link android.app.Notification.Builder#extend} to apply the extensions to a
10545      *   notification.
10546      *   <li>Post the notification to the notification system with the
10547      *   {@code NotificationManager.notify(...)} methods.
10548      * </ol>
10549      *
10550      * <pre class="prettyprint">
10551      * Notification notif = new Notification.Builder(mContext)
10552      *         .setContentTitle(&quot;New mail from &quot; + sender.toString())
10553      *         .setContentText(subject)
10554      *         .setSmallIcon(R.drawable.new_mail)
10555      *         .extend(new Notification.WearableExtender()
10556      *                 .setContentIcon(R.drawable.new_mail))
10557      *         .build();
10558      * NotificationManager notificationManger =
10559      *         (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
10560      * notificationManger.notify(0, notif);</pre>
10561      *
10562      * <p>Wearable extensions can be accessed on an existing notification by using the
10563      * {@code WearableExtender(Notification)} constructor,
10564      * and then using the {@code get} methods to access values.
10565      *
10566      * <pre class="prettyprint">
10567      * Notification.WearableExtender wearableExtender = new Notification.WearableExtender(
10568      *         notification);
10569      * List&lt;Notification&gt; pages = wearableExtender.getPages();</pre>
10570      */
10571     public static final class WearableExtender implements Extender {
10572         /**
10573          * Sentinel value for an action index that is unset.
10574          */
10575         public static final int UNSET_ACTION_INDEX = -1;
10576 
10577         /**
10578          * Size value for use with {@link #setCustomSizePreset} to show this notification with
10579          * default sizing.
10580          * <p>For custom display notifications created using {@link #setDisplayIntent},
10581          * the default is {@link #SIZE_MEDIUM}. All other notifications size automatically based
10582          * on their content.
10583          *
10584          * @deprecated Display intents are no longer supported.
10585          */
10586         @Deprecated
10587         public static final int SIZE_DEFAULT = 0;
10588 
10589         /**
10590          * Size value for use with {@link #setCustomSizePreset} to show this notification
10591          * with an extra small size.
10592          * <p>This value is only applicable for custom display notifications created using
10593          * {@link #setDisplayIntent}.
10594          *
10595          * @deprecated Display intents are no longer supported.
10596          */
10597         @Deprecated
10598         public static final int SIZE_XSMALL = 1;
10599 
10600         /**
10601          * Size value for use with {@link #setCustomSizePreset} to show this notification
10602          * with a small size.
10603          * <p>This value is only applicable for custom display notifications created using
10604          * {@link #setDisplayIntent}.
10605          *
10606          * @deprecated Display intents are no longer supported.
10607          */
10608         @Deprecated
10609         public static final int SIZE_SMALL = 2;
10610 
10611         /**
10612          * Size value for use with {@link #setCustomSizePreset} to show this notification
10613          * with a medium size.
10614          * <p>This value is only applicable for custom display notifications created using
10615          * {@link #setDisplayIntent}.
10616          *
10617          * @deprecated Display intents are no longer supported.
10618          */
10619         @Deprecated
10620         public static final int SIZE_MEDIUM = 3;
10621 
10622         /**
10623          * Size value for use with {@link #setCustomSizePreset} to show this notification
10624          * with a large size.
10625          * <p>This value is only applicable for custom display notifications created using
10626          * {@link #setDisplayIntent}.
10627          *
10628          * @deprecated Display intents are no longer supported.
10629          */
10630         @Deprecated
10631         public static final int SIZE_LARGE = 4;
10632 
10633         /**
10634          * Size value for use with {@link #setCustomSizePreset} to show this notification
10635          * full screen.
10636          * <p>This value is only applicable for custom display notifications created using
10637          * {@link #setDisplayIntent}.
10638          *
10639          * @deprecated Display intents are no longer supported.
10640          */
10641         @Deprecated
10642         public static final int SIZE_FULL_SCREEN = 5;
10643 
10644         /**
10645          * Sentinel value for use with {@link #setHintScreenTimeout} to keep the screen on for a
10646          * short amount of time when this notification is displayed on the screen. This
10647          * is the default value.
10648          *
10649          * @deprecated This feature is no longer supported.
10650          */
10651         @Deprecated
10652         public static final int SCREEN_TIMEOUT_SHORT = 0;
10653 
10654         /**
10655          * Sentinel value for use with {@link #setHintScreenTimeout} to keep the screen on
10656          * for a longer amount of time when this notification is displayed on the screen.
10657          *
10658          * @deprecated This feature is no longer supported.
10659          */
10660         @Deprecated
10661         public static final int SCREEN_TIMEOUT_LONG = -1;
10662 
10663         /** Notification extra which contains wearable extensions */
10664         private static final String EXTRA_WEARABLE_EXTENSIONS = "android.wearable.EXTENSIONS";
10665 
10666         // Keys within EXTRA_WEARABLE_EXTENSIONS for wearable options.
10667         private static final String KEY_ACTIONS = "actions";
10668         private static final String KEY_FLAGS = "flags";
10669         private static final String KEY_DISPLAY_INTENT = "displayIntent";
10670         private static final String KEY_PAGES = "pages";
10671         private static final String KEY_BACKGROUND = "background";
10672         private static final String KEY_CONTENT_ICON = "contentIcon";
10673         private static final String KEY_CONTENT_ICON_GRAVITY = "contentIconGravity";
10674         private static final String KEY_CONTENT_ACTION_INDEX = "contentActionIndex";
10675         private static final String KEY_CUSTOM_SIZE_PRESET = "customSizePreset";
10676         private static final String KEY_CUSTOM_CONTENT_HEIGHT = "customContentHeight";
10677         private static final String KEY_GRAVITY = "gravity";
10678         private static final String KEY_HINT_SCREEN_TIMEOUT = "hintScreenTimeout";
10679         private static final String KEY_DISMISSAL_ID = "dismissalId";
10680         private static final String KEY_BRIDGE_TAG = "bridgeTag";
10681 
10682         // Flags bitwise-ored to mFlags
10683         private static final int FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE = 0x1;
10684         private static final int FLAG_HINT_HIDE_ICON = 1 << 1;
10685         private static final int FLAG_HINT_SHOW_BACKGROUND_ONLY = 1 << 2;
10686         private static final int FLAG_START_SCROLL_BOTTOM = 1 << 3;
10687         private static final int FLAG_HINT_AVOID_BACKGROUND_CLIPPING = 1 << 4;
10688         private static final int FLAG_BIG_PICTURE_AMBIENT = 1 << 5;
10689         private static final int FLAG_HINT_CONTENT_INTENT_LAUNCHES_ACTIVITY = 1 << 6;
10690 
10691         // Default value for flags integer
10692         private static final int DEFAULT_FLAGS = FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE;
10693 
10694         private static final int DEFAULT_CONTENT_ICON_GRAVITY = Gravity.END;
10695         private static final int DEFAULT_GRAVITY = Gravity.BOTTOM;
10696 
10697         private ArrayList<Action> mActions = new ArrayList<Action>();
10698         private int mFlags = DEFAULT_FLAGS;
10699         private PendingIntent mDisplayIntent;
10700         private ArrayList<Notification> mPages = new ArrayList<Notification>();
10701         private Bitmap mBackground;
10702         private int mContentIcon;
10703         private int mContentIconGravity = DEFAULT_CONTENT_ICON_GRAVITY;
10704         private int mContentActionIndex = UNSET_ACTION_INDEX;
10705         private int mCustomSizePreset = SIZE_DEFAULT;
10706         private int mCustomContentHeight;
10707         private int mGravity = DEFAULT_GRAVITY;
10708         private int mHintScreenTimeout;
10709         private String mDismissalId;
10710         private String mBridgeTag;
10711 
10712         /**
10713          * Create a {@link android.app.Notification.WearableExtender} with default
10714          * options.
10715          */
WearableExtender()10716         public WearableExtender() {
10717         }
10718 
WearableExtender(Notification notif)10719         public WearableExtender(Notification notif) {
10720             Bundle wearableBundle = notif.extras.getBundle(EXTRA_WEARABLE_EXTENSIONS);
10721             if (wearableBundle != null) {
10722                 List<Action> actions = wearableBundle.getParcelableArrayList(KEY_ACTIONS);
10723                 if (actions != null) {
10724                     mActions.addAll(actions);
10725                 }
10726 
10727                 mFlags = wearableBundle.getInt(KEY_FLAGS, DEFAULT_FLAGS);
10728                 mDisplayIntent = wearableBundle.getParcelable(KEY_DISPLAY_INTENT);
10729 
10730                 Notification[] pages = getParcelableArrayFromBundle(
10731                         wearableBundle, KEY_PAGES, Notification.class);
10732                 if (pages != null) {
10733                     Collections.addAll(mPages, pages);
10734                 }
10735 
10736                 mBackground = wearableBundle.getParcelable(KEY_BACKGROUND);
10737                 mContentIcon = wearableBundle.getInt(KEY_CONTENT_ICON);
10738                 mContentIconGravity = wearableBundle.getInt(KEY_CONTENT_ICON_GRAVITY,
10739                         DEFAULT_CONTENT_ICON_GRAVITY);
10740                 mContentActionIndex = wearableBundle.getInt(KEY_CONTENT_ACTION_INDEX,
10741                         UNSET_ACTION_INDEX);
10742                 mCustomSizePreset = wearableBundle.getInt(KEY_CUSTOM_SIZE_PRESET,
10743                         SIZE_DEFAULT);
10744                 mCustomContentHeight = wearableBundle.getInt(KEY_CUSTOM_CONTENT_HEIGHT);
10745                 mGravity = wearableBundle.getInt(KEY_GRAVITY, DEFAULT_GRAVITY);
10746                 mHintScreenTimeout = wearableBundle.getInt(KEY_HINT_SCREEN_TIMEOUT);
10747                 mDismissalId = wearableBundle.getString(KEY_DISMISSAL_ID);
10748                 mBridgeTag = wearableBundle.getString(KEY_BRIDGE_TAG);
10749             }
10750         }
10751 
10752         /**
10753          * Apply wearable extensions to a notification that is being built. This is typically
10754          * called by the {@link android.app.Notification.Builder#extend} method of
10755          * {@link android.app.Notification.Builder}.
10756          */
10757         @Override
extend(Notification.Builder builder)10758         public Notification.Builder extend(Notification.Builder builder) {
10759             Bundle wearableBundle = new Bundle();
10760 
10761             if (!mActions.isEmpty()) {
10762                 wearableBundle.putParcelableArrayList(KEY_ACTIONS, mActions);
10763             }
10764             if (mFlags != DEFAULT_FLAGS) {
10765                 wearableBundle.putInt(KEY_FLAGS, mFlags);
10766             }
10767             if (mDisplayIntent != null) {
10768                 wearableBundle.putParcelable(KEY_DISPLAY_INTENT, mDisplayIntent);
10769             }
10770             if (!mPages.isEmpty()) {
10771                 wearableBundle.putParcelableArray(KEY_PAGES, mPages.toArray(
10772                         new Notification[mPages.size()]));
10773             }
10774             if (mBackground != null) {
10775                 wearableBundle.putParcelable(KEY_BACKGROUND, mBackground);
10776             }
10777             if (mContentIcon != 0) {
10778                 wearableBundle.putInt(KEY_CONTENT_ICON, mContentIcon);
10779             }
10780             if (mContentIconGravity != DEFAULT_CONTENT_ICON_GRAVITY) {
10781                 wearableBundle.putInt(KEY_CONTENT_ICON_GRAVITY, mContentIconGravity);
10782             }
10783             if (mContentActionIndex != UNSET_ACTION_INDEX) {
10784                 wearableBundle.putInt(KEY_CONTENT_ACTION_INDEX,
10785                         mContentActionIndex);
10786             }
10787             if (mCustomSizePreset != SIZE_DEFAULT) {
10788                 wearableBundle.putInt(KEY_CUSTOM_SIZE_PRESET, mCustomSizePreset);
10789             }
10790             if (mCustomContentHeight != 0) {
10791                 wearableBundle.putInt(KEY_CUSTOM_CONTENT_HEIGHT, mCustomContentHeight);
10792             }
10793             if (mGravity != DEFAULT_GRAVITY) {
10794                 wearableBundle.putInt(KEY_GRAVITY, mGravity);
10795             }
10796             if (mHintScreenTimeout != 0) {
10797                 wearableBundle.putInt(KEY_HINT_SCREEN_TIMEOUT, mHintScreenTimeout);
10798             }
10799             if (mDismissalId != null) {
10800                 wearableBundle.putString(KEY_DISMISSAL_ID, mDismissalId);
10801             }
10802             if (mBridgeTag != null) {
10803                 wearableBundle.putString(KEY_BRIDGE_TAG, mBridgeTag);
10804             }
10805 
10806             builder.getExtras().putBundle(EXTRA_WEARABLE_EXTENSIONS, wearableBundle);
10807             return builder;
10808         }
10809 
10810         @Override
clone()10811         public WearableExtender clone() {
10812             WearableExtender that = new WearableExtender();
10813             that.mActions = new ArrayList<Action>(this.mActions);
10814             that.mFlags = this.mFlags;
10815             that.mDisplayIntent = this.mDisplayIntent;
10816             that.mPages = new ArrayList<Notification>(this.mPages);
10817             that.mBackground = this.mBackground;
10818             that.mContentIcon = this.mContentIcon;
10819             that.mContentIconGravity = this.mContentIconGravity;
10820             that.mContentActionIndex = this.mContentActionIndex;
10821             that.mCustomSizePreset = this.mCustomSizePreset;
10822             that.mCustomContentHeight = this.mCustomContentHeight;
10823             that.mGravity = this.mGravity;
10824             that.mHintScreenTimeout = this.mHintScreenTimeout;
10825             that.mDismissalId = this.mDismissalId;
10826             that.mBridgeTag = this.mBridgeTag;
10827             return that;
10828         }
10829 
10830         /**
10831          * Add a wearable action to this notification.
10832          *
10833          * <p>When wearable actions are added using this method, the set of actions that
10834          * show on a wearable device splits from devices that only show actions added
10835          * using {@link android.app.Notification.Builder#addAction}. This allows for customization
10836          * of which actions display on different devices.
10837          *
10838          * @param action the action to add to this notification
10839          * @return this object for method chaining
10840          * @see android.app.Notification.Action
10841          */
addAction(Action action)10842         public WearableExtender addAction(Action action) {
10843             mActions.add(action);
10844             return this;
10845         }
10846 
10847         /**
10848          * Adds wearable actions to this notification.
10849          *
10850          * <p>When wearable actions are added using this method, the set of actions that
10851          * show on a wearable device splits from devices that only show actions added
10852          * using {@link android.app.Notification.Builder#addAction}. This allows for customization
10853          * of which actions display on different devices.
10854          *
10855          * @param actions the actions to add to this notification
10856          * @return this object for method chaining
10857          * @see android.app.Notification.Action
10858          */
addActions(List<Action> actions)10859         public WearableExtender addActions(List<Action> actions) {
10860             mActions.addAll(actions);
10861             return this;
10862         }
10863 
10864         /**
10865          * Clear all wearable actions present on this builder.
10866          * @return this object for method chaining.
10867          * @see #addAction
10868          */
clearActions()10869         public WearableExtender clearActions() {
10870             mActions.clear();
10871             return this;
10872         }
10873 
10874         /**
10875          * Get the wearable actions present on this notification.
10876          */
getActions()10877         public List<Action> getActions() {
10878             return mActions;
10879         }
10880 
10881         /**
10882          * Set an intent to launch inside of an activity view when displaying
10883          * this notification. The {@link PendingIntent} provided should be for an activity.
10884          *
10885          * <pre class="prettyprint">
10886          * Intent displayIntent = new Intent(context, MyDisplayActivity.class);
10887          * PendingIntent displayPendingIntent = PendingIntent.getActivity(context,
10888          *         0, displayIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
10889          * Notification notif = new Notification.Builder(context)
10890          *         .extend(new Notification.WearableExtender()
10891          *                 .setDisplayIntent(displayPendingIntent)
10892          *                 .setCustomSizePreset(Notification.WearableExtender.SIZE_MEDIUM))
10893          *         .build();</pre>
10894          *
10895          * <p>The activity to launch needs to allow embedding, must be exported, and
10896          * should have an empty task affinity. It is also recommended to use the device
10897          * default light theme.
10898          *
10899          * <p>Example AndroidManifest.xml entry:
10900          * <pre class="prettyprint">
10901          * &lt;activity android:name=&quot;com.example.MyDisplayActivity&quot;
10902          *     android:exported=&quot;true&quot;
10903          *     android:allowEmbedded=&quot;true&quot;
10904          *     android:taskAffinity=&quot;&quot;
10905          *     android:theme=&quot;@android:style/Theme.DeviceDefault.Light&quot; /&gt;</pre>
10906          *
10907          * @param intent the {@link PendingIntent} for an activity
10908          * @return this object for method chaining
10909          * @see android.app.Notification.WearableExtender#getDisplayIntent
10910          * @deprecated Display intents are no longer supported.
10911          */
10912         @Deprecated
setDisplayIntent(PendingIntent intent)10913         public WearableExtender setDisplayIntent(PendingIntent intent) {
10914             mDisplayIntent = intent;
10915             return this;
10916         }
10917 
10918         /**
10919          * Get the intent to launch inside of an activity view when displaying this
10920          * notification. This {@code PendingIntent} should be for an activity.
10921          *
10922          * @deprecated Display intents are no longer supported.
10923          */
10924         @Deprecated
getDisplayIntent()10925         public PendingIntent getDisplayIntent() {
10926             return mDisplayIntent;
10927         }
10928 
10929         /**
10930          * Add an additional page of content to display with this notification. The current
10931          * notification forms the first page, and pages added using this function form
10932          * subsequent pages. This field can be used to separate a notification into multiple
10933          * sections.
10934          *
10935          * @param page the notification to add as another page
10936          * @return this object for method chaining
10937          * @see android.app.Notification.WearableExtender#getPages
10938          * @deprecated Multiple content pages are no longer supported.
10939          */
10940         @Deprecated
addPage(Notification page)10941         public WearableExtender addPage(Notification page) {
10942             mPages.add(page);
10943             return this;
10944         }
10945 
10946         /**
10947          * Add additional pages of content to display with this notification. The current
10948          * notification forms the first page, and pages added using this function form
10949          * subsequent pages. This field can be used to separate a notification into multiple
10950          * sections.
10951          *
10952          * @param pages a list of notifications
10953          * @return this object for method chaining
10954          * @see android.app.Notification.WearableExtender#getPages
10955          * @deprecated Multiple content pages are no longer supported.
10956          */
10957         @Deprecated
addPages(List<Notification> pages)10958         public WearableExtender addPages(List<Notification> pages) {
10959             mPages.addAll(pages);
10960             return this;
10961         }
10962 
10963         /**
10964          * Clear all additional pages present on this builder.
10965          * @return this object for method chaining.
10966          * @see #addPage
10967          * @deprecated Multiple content pages are no longer supported.
10968          */
10969         @Deprecated
clearPages()10970         public WearableExtender clearPages() {
10971             mPages.clear();
10972             return this;
10973         }
10974 
10975         /**
10976          * Get the array of additional pages of content for displaying this notification. The
10977          * current notification forms the first page, and elements within this array form
10978          * subsequent pages. This field can be used to separate a notification into multiple
10979          * sections.
10980          * @return the pages for this notification
10981          * @deprecated Multiple content pages are no longer supported.
10982          */
10983         @Deprecated
getPages()10984         public List<Notification> getPages() {
10985             return mPages;
10986         }
10987 
10988         /**
10989          * Set a background image to be displayed behind the notification content.
10990          * Contrary to the {@link android.app.Notification.BigPictureStyle}, this background
10991          * will work with any notification style.
10992          *
10993          * @param background the background bitmap
10994          * @return this object for method chaining
10995          * @see android.app.Notification.WearableExtender#getBackground
10996          * @deprecated Background images are no longer supported.
10997          */
10998         @Deprecated
setBackground(Bitmap background)10999         public WearableExtender setBackground(Bitmap background) {
11000             mBackground = background;
11001             return this;
11002         }
11003 
11004         /**
11005          * Get a background image to be displayed behind the notification content.
11006          * Contrary to the {@link android.app.Notification.BigPictureStyle}, this background
11007          * will work with any notification style.
11008          *
11009          * @return the background image
11010          * @see android.app.Notification.WearableExtender#setBackground
11011          * @deprecated Background images are no longer supported.
11012          */
11013         @Deprecated
getBackground()11014         public Bitmap getBackground() {
11015             return mBackground;
11016         }
11017 
11018         /**
11019          * Set an icon that goes with the content of this notification.
11020          */
11021         @Deprecated
setContentIcon(int icon)11022         public WearableExtender setContentIcon(int icon) {
11023             mContentIcon = icon;
11024             return this;
11025         }
11026 
11027         /**
11028          * Get an icon that goes with the content of this notification.
11029          */
11030         @Deprecated
getContentIcon()11031         public int getContentIcon() {
11032             return mContentIcon;
11033         }
11034 
11035         /**
11036          * Set the gravity that the content icon should have within the notification display.
11037          * Supported values include {@link android.view.Gravity#START} and
11038          * {@link android.view.Gravity#END}. The default value is {@link android.view.Gravity#END}.
11039          * @see #setContentIcon
11040          */
11041         @Deprecated
setContentIconGravity(int contentIconGravity)11042         public WearableExtender setContentIconGravity(int contentIconGravity) {
11043             mContentIconGravity = contentIconGravity;
11044             return this;
11045         }
11046 
11047         /**
11048          * Get the gravity that the content icon should have within the notification display.
11049          * Supported values include {@link android.view.Gravity#START} and
11050          * {@link android.view.Gravity#END}. The default value is {@link android.view.Gravity#END}.
11051          * @see #getContentIcon
11052          */
11053         @Deprecated
getContentIconGravity()11054         public int getContentIconGravity() {
11055             return mContentIconGravity;
11056         }
11057 
11058         /**
11059          * Set an action from this notification's actions as the primary action. If the action has a
11060          * {@link RemoteInput} associated with it, shortcuts to the options for that input are shown
11061          * directly on the notification.
11062          *
11063          * @param actionIndex The index of the primary action.
11064          *                    If wearable actions were added to the main notification, this index
11065          *                    will apply to that list, otherwise it will apply to the regular
11066          *                    actions list.
11067          */
setContentAction(int actionIndex)11068         public WearableExtender setContentAction(int actionIndex) {
11069             mContentActionIndex = actionIndex;
11070             return this;
11071         }
11072 
11073         /**
11074          * Get the index of the notification action, if any, that was specified as the primary
11075          * action.
11076          *
11077          * <p>If wearable specific actions were added to the main notification, this index will
11078          * apply to that list, otherwise it will apply to the regular actions list.
11079          *
11080          * @return the action index or {@link #UNSET_ACTION_INDEX} if no action was selected.
11081          */
getContentAction()11082         public int getContentAction() {
11083             return mContentActionIndex;
11084         }
11085 
11086         /**
11087          * Set the gravity that this notification should have within the available viewport space.
11088          * Supported values include {@link android.view.Gravity#TOP},
11089          * {@link android.view.Gravity#CENTER_VERTICAL} and {@link android.view.Gravity#BOTTOM}.
11090          * The default value is {@link android.view.Gravity#BOTTOM}.
11091          */
11092         @Deprecated
setGravity(int gravity)11093         public WearableExtender setGravity(int gravity) {
11094             mGravity = gravity;
11095             return this;
11096         }
11097 
11098         /**
11099          * Get the gravity that this notification should have within the available viewport space.
11100          * Supported values include {@link android.view.Gravity#TOP},
11101          * {@link android.view.Gravity#CENTER_VERTICAL} and {@link android.view.Gravity#BOTTOM}.
11102          * The default value is {@link android.view.Gravity#BOTTOM}.
11103          */
11104         @Deprecated
getGravity()11105         public int getGravity() {
11106             return mGravity;
11107         }
11108 
11109         /**
11110          * Set the custom size preset for the display of this notification out of the available
11111          * presets found in {@link android.app.Notification.WearableExtender}, e.g.
11112          * {@link #SIZE_LARGE}.
11113          * <p>Some custom size presets are only applicable for custom display notifications created
11114          * using {@link android.app.Notification.WearableExtender#setDisplayIntent}. Check the
11115          * documentation for the preset in question. See also
11116          * {@link #setCustomContentHeight} and {@link #getCustomSizePreset}.
11117          */
11118         @Deprecated
setCustomSizePreset(int sizePreset)11119         public WearableExtender setCustomSizePreset(int sizePreset) {
11120             mCustomSizePreset = sizePreset;
11121             return this;
11122         }
11123 
11124         /**
11125          * Get the custom size preset for the display of this notification out of the available
11126          * presets found in {@link android.app.Notification.WearableExtender}, e.g.
11127          * {@link #SIZE_LARGE}.
11128          * <p>Some custom size presets are only applicable for custom display notifications created
11129          * using {@link #setDisplayIntent}. Check the documentation for the preset in question.
11130          * See also {@link #setCustomContentHeight} and {@link #setCustomSizePreset}.
11131          */
11132         @Deprecated
getCustomSizePreset()11133         public int getCustomSizePreset() {
11134             return mCustomSizePreset;
11135         }
11136 
11137         /**
11138          * Set the custom height in pixels for the display of this notification's content.
11139          * <p>This option is only available for custom display notifications created
11140          * using {@link android.app.Notification.WearableExtender#setDisplayIntent}. See also
11141          * {@link android.app.Notification.WearableExtender#setCustomSizePreset} and
11142          * {@link #getCustomContentHeight}.
11143          */
11144         @Deprecated
setCustomContentHeight(int height)11145         public WearableExtender setCustomContentHeight(int height) {
11146             mCustomContentHeight = height;
11147             return this;
11148         }
11149 
11150         /**
11151          * Get the custom height in pixels for the display of this notification's content.
11152          * <p>This option is only available for custom display notifications created
11153          * using {@link #setDisplayIntent}. See also {@link #setCustomSizePreset} and
11154          * {@link #setCustomContentHeight}.
11155          */
11156         @Deprecated
getCustomContentHeight()11157         public int getCustomContentHeight() {
11158             return mCustomContentHeight;
11159         }
11160 
11161         /**
11162          * Set whether the scrolling position for the contents of this notification should start
11163          * at the bottom of the contents instead of the top when the contents are too long to
11164          * display within the screen.  Default is false (start scroll at the top).
11165          */
setStartScrollBottom(boolean startScrollBottom)11166         public WearableExtender setStartScrollBottom(boolean startScrollBottom) {
11167             setFlag(FLAG_START_SCROLL_BOTTOM, startScrollBottom);
11168             return this;
11169         }
11170 
11171         /**
11172          * Get whether the scrolling position for the contents of this notification should start
11173          * at the bottom of the contents instead of the top when the contents are too long to
11174          * display within the screen. Default is false (start scroll at the top).
11175          */
getStartScrollBottom()11176         public boolean getStartScrollBottom() {
11177             return (mFlags & FLAG_START_SCROLL_BOTTOM) != 0;
11178         }
11179 
11180         /**
11181          * Set whether the content intent is available when the wearable device is not connected
11182          * to a companion device.  The user can still trigger this intent when the wearable device
11183          * is offline, but a visual hint will indicate that the content intent may not be available.
11184          * Defaults to true.
11185          */
setContentIntentAvailableOffline( boolean contentIntentAvailableOffline)11186         public WearableExtender setContentIntentAvailableOffline(
11187                 boolean contentIntentAvailableOffline) {
11188             setFlag(FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE, contentIntentAvailableOffline);
11189             return this;
11190         }
11191 
11192         /**
11193          * Get whether the content intent is available when the wearable device is not connected
11194          * to a companion device.  The user can still trigger this intent when the wearable device
11195          * is offline, but a visual hint will indicate that the content intent may not be available.
11196          * Defaults to true.
11197          */
getContentIntentAvailableOffline()11198         public boolean getContentIntentAvailableOffline() {
11199             return (mFlags & FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE) != 0;
11200         }
11201 
11202         /**
11203          * Set a hint that this notification's icon should not be displayed.
11204          * @param hintHideIcon {@code true} to hide the icon, {@code false} otherwise.
11205          * @return this object for method chaining
11206          */
11207         @Deprecated
setHintHideIcon(boolean hintHideIcon)11208         public WearableExtender setHintHideIcon(boolean hintHideIcon) {
11209             setFlag(FLAG_HINT_HIDE_ICON, hintHideIcon);
11210             return this;
11211         }
11212 
11213         /**
11214          * Get a hint that this notification's icon should not be displayed.
11215          * @return {@code true} if this icon should not be displayed, false otherwise.
11216          * The default value is {@code false} if this was never set.
11217          */
11218         @Deprecated
getHintHideIcon()11219         public boolean getHintHideIcon() {
11220             return (mFlags & FLAG_HINT_HIDE_ICON) != 0;
11221         }
11222 
11223         /**
11224          * Set a visual hint that only the background image of this notification should be
11225          * displayed, and other semantic content should be hidden. This hint is only applicable
11226          * to sub-pages added using {@link #addPage}.
11227          */
11228         @Deprecated
setHintShowBackgroundOnly(boolean hintShowBackgroundOnly)11229         public WearableExtender setHintShowBackgroundOnly(boolean hintShowBackgroundOnly) {
11230             setFlag(FLAG_HINT_SHOW_BACKGROUND_ONLY, hintShowBackgroundOnly);
11231             return this;
11232         }
11233 
11234         /**
11235          * Get a visual hint that only the background image of this notification should be
11236          * displayed, and other semantic content should be hidden. This hint is only applicable
11237          * to sub-pages added using {@link android.app.Notification.WearableExtender#addPage}.
11238          */
11239         @Deprecated
getHintShowBackgroundOnly()11240         public boolean getHintShowBackgroundOnly() {
11241             return (mFlags & FLAG_HINT_SHOW_BACKGROUND_ONLY) != 0;
11242         }
11243 
11244         /**
11245          * Set a hint that this notification's background should not be clipped if possible,
11246          * and should instead be resized to fully display on the screen, retaining the aspect
11247          * ratio of the image. This can be useful for images like barcodes or qr codes.
11248          * @param hintAvoidBackgroundClipping {@code true} to avoid clipping if possible.
11249          * @return this object for method chaining
11250          */
11251         @Deprecated
setHintAvoidBackgroundClipping( boolean hintAvoidBackgroundClipping)11252         public WearableExtender setHintAvoidBackgroundClipping(
11253                 boolean hintAvoidBackgroundClipping) {
11254             setFlag(FLAG_HINT_AVOID_BACKGROUND_CLIPPING, hintAvoidBackgroundClipping);
11255             return this;
11256         }
11257 
11258         /**
11259          * Get a hint that this notification's background should not be clipped if possible,
11260          * and should instead be resized to fully display on the screen, retaining the aspect
11261          * ratio of the image. This can be useful for images like barcodes or qr codes.
11262          * @return {@code true} if it's ok if the background is clipped on the screen, false
11263          * otherwise. The default value is {@code false} if this was never set.
11264          */
11265         @Deprecated
getHintAvoidBackgroundClipping()11266         public boolean getHintAvoidBackgroundClipping() {
11267             return (mFlags & FLAG_HINT_AVOID_BACKGROUND_CLIPPING) != 0;
11268         }
11269 
11270         /**
11271          * Set a hint that the screen should remain on for at least this duration when
11272          * this notification is displayed on the screen.
11273          * @param timeout The requested screen timeout in milliseconds. Can also be either
11274          *     {@link #SCREEN_TIMEOUT_SHORT} or {@link #SCREEN_TIMEOUT_LONG}.
11275          * @return this object for method chaining
11276          */
11277         @Deprecated
setHintScreenTimeout(int timeout)11278         public WearableExtender setHintScreenTimeout(int timeout) {
11279             mHintScreenTimeout = timeout;
11280             return this;
11281         }
11282 
11283         /**
11284          * Get the duration, in milliseconds, that the screen should remain on for
11285          * when this notification is displayed.
11286          * @return the duration in milliseconds if > 0, or either one of the sentinel values
11287          *     {@link #SCREEN_TIMEOUT_SHORT} or {@link #SCREEN_TIMEOUT_LONG}.
11288          */
11289         @Deprecated
getHintScreenTimeout()11290         public int getHintScreenTimeout() {
11291             return mHintScreenTimeout;
11292         }
11293 
11294         /**
11295          * Set a hint that this notification's {@link BigPictureStyle} (if present) should be
11296          * converted to low-bit and displayed in ambient mode, especially useful for barcodes and
11297          * qr codes, as well as other simple black-and-white tickets.
11298          * @param hintAmbientBigPicture {@code true} to enable converstion and ambient.
11299          * @return this object for method chaining
11300          * @deprecated This feature is no longer supported.
11301          */
11302         @Deprecated
setHintAmbientBigPicture(boolean hintAmbientBigPicture)11303         public WearableExtender setHintAmbientBigPicture(boolean hintAmbientBigPicture) {
11304             setFlag(FLAG_BIG_PICTURE_AMBIENT, hintAmbientBigPicture);
11305             return this;
11306         }
11307 
11308         /**
11309          * Get a hint that this notification's {@link BigPictureStyle} (if present) should be
11310          * converted to low-bit and displayed in ambient mode, especially useful for barcodes and
11311          * qr codes, as well as other simple black-and-white tickets.
11312          * @return {@code true} if it should be displayed in ambient, false otherwise
11313          * otherwise. The default value is {@code false} if this was never set.
11314          * @deprecated This feature is no longer supported.
11315          */
11316         @Deprecated
getHintAmbientBigPicture()11317         public boolean getHintAmbientBigPicture() {
11318             return (mFlags & FLAG_BIG_PICTURE_AMBIENT) != 0;
11319         }
11320 
11321         /**
11322          * Set a hint that this notification's content intent will launch an {@link Activity}
11323          * directly, telling the platform that it can generate the appropriate transitions.
11324          * @param hintContentIntentLaunchesActivity {@code true} if the content intent will launch
11325          * an activity and transitions should be generated, false otherwise.
11326          * @return this object for method chaining
11327          */
setHintContentIntentLaunchesActivity( boolean hintContentIntentLaunchesActivity)11328         public WearableExtender setHintContentIntentLaunchesActivity(
11329                 boolean hintContentIntentLaunchesActivity) {
11330             setFlag(FLAG_HINT_CONTENT_INTENT_LAUNCHES_ACTIVITY, hintContentIntentLaunchesActivity);
11331             return this;
11332         }
11333 
11334         /**
11335          * Get a hint that this notification's content intent will launch an {@link Activity}
11336          * directly, telling the platform that it can generate the appropriate transitions
11337          * @return {@code true} if the content intent will launch an activity and transitions should
11338          * be generated, false otherwise. The default value is {@code false} if this was never set.
11339          */
getHintContentIntentLaunchesActivity()11340         public boolean getHintContentIntentLaunchesActivity() {
11341             return (mFlags & FLAG_HINT_CONTENT_INTENT_LAUNCHES_ACTIVITY) != 0;
11342         }
11343 
11344         /**
11345          * Sets the dismissal id for this notification. If a notification is posted with a
11346          * dismissal id, then when that notification is canceled, notifications on other wearables
11347          * and the paired Android phone having that same dismissal id will also be canceled. See
11348          * <a href="{@docRoot}wear/notifications/index.html">Adding Wearable Features to
11349          * Notifications</a> for more information.
11350          * @param dismissalId the dismissal id of the notification.
11351          * @return this object for method chaining
11352          */
setDismissalId(String dismissalId)11353         public WearableExtender setDismissalId(String dismissalId) {
11354             mDismissalId = dismissalId;
11355             return this;
11356         }
11357 
11358         /**
11359          * Returns the dismissal id of the notification.
11360          * @return the dismissal id of the notification or null if it has not been set.
11361          */
getDismissalId()11362         public String getDismissalId() {
11363             return mDismissalId;
11364         }
11365 
11366         /**
11367          * Sets a bridge tag for this notification. A bridge tag can be set for notifications
11368          * posted from a phone to provide finer-grained control on what notifications are bridged
11369          * to wearables. See <a href="{@docRoot}wear/notifications/index.html">Adding Wearable
11370          * Features to Notifications</a> for more information.
11371          * @param bridgeTag the bridge tag of the notification.
11372          * @return this object for method chaining
11373          */
setBridgeTag(String bridgeTag)11374         public WearableExtender setBridgeTag(String bridgeTag) {
11375             mBridgeTag = bridgeTag;
11376             return this;
11377         }
11378 
11379         /**
11380          * Returns the bridge tag of the notification.
11381          * @return the bridge tag or null if not present.
11382          */
getBridgeTag()11383         public String getBridgeTag() {
11384             return mBridgeTag;
11385         }
11386 
setFlag(int mask, boolean value)11387         private void setFlag(int mask, boolean value) {
11388             if (value) {
11389                 mFlags |= mask;
11390             } else {
11391                 mFlags &= ~mask;
11392             }
11393         }
11394     }
11395 
11396     /**
11397      * <p>Helper class to add Android Auto extensions to notifications. To create a notification
11398      * with car extensions:
11399      *
11400      * <ol>
11401      *  <li>Create an {@link Notification.Builder}, setting any desired
11402      *  properties.
11403      *  <li>Create a {@link CarExtender}.
11404      *  <li>Set car-specific properties using the {@code add} and {@code set} methods of
11405      *  {@link CarExtender}.
11406      *  <li>Call {@link Notification.Builder#extend(Notification.Extender)}
11407      *  to apply the extensions to a notification.
11408      * </ol>
11409      *
11410      * <pre class="prettyprint">
11411      * Notification notification = new Notification.Builder(context)
11412      *         ...
11413      *         .extend(new CarExtender()
11414      *                 .set*(...))
11415      *         .build();
11416      * </pre>
11417      *
11418      * <p>Car extensions can be accessed on an existing notification by using the
11419      * {@code CarExtender(Notification)} constructor, and then using the {@code get} methods
11420      * to access values.
11421      */
11422     public static final class CarExtender implements Extender {
11423         private static final String TAG = "CarExtender";
11424 
11425         private static final String EXTRA_CAR_EXTENDER = "android.car.EXTENSIONS";
11426         private static final String EXTRA_LARGE_ICON = "large_icon";
11427         private static final String EXTRA_CONVERSATION = "car_conversation";
11428         private static final String EXTRA_COLOR = "app_color";
11429 
11430         private Bitmap mLargeIcon;
11431         private UnreadConversation mUnreadConversation;
11432         private int mColor = Notification.COLOR_DEFAULT;
11433 
11434         /**
11435          * Create a {@link CarExtender} with default options.
11436          */
CarExtender()11437         public CarExtender() {
11438         }
11439 
11440         /**
11441          * Create a {@link CarExtender} from the CarExtender options of an existing Notification.
11442          *
11443          * @param notif The notification from which to copy options.
11444          */
CarExtender(Notification notif)11445         public CarExtender(Notification notif) {
11446             Bundle carBundle = notif.extras == null ?
11447                     null : notif.extras.getBundle(EXTRA_CAR_EXTENDER);
11448             if (carBundle != null) {
11449                 mLargeIcon = carBundle.getParcelable(EXTRA_LARGE_ICON);
11450                 mColor = carBundle.getInt(EXTRA_COLOR, Notification.COLOR_DEFAULT);
11451 
11452                 Bundle b = carBundle.getBundle(EXTRA_CONVERSATION);
11453                 mUnreadConversation = UnreadConversation.getUnreadConversationFromBundle(b);
11454             }
11455         }
11456 
11457         /**
11458          * Apply car extensions to a notification that is being built. This is typically called by
11459          * the {@link Notification.Builder#extend(Notification.Extender)}
11460          * method of {@link Notification.Builder}.
11461          */
11462         @Override
extend(Notification.Builder builder)11463         public Notification.Builder extend(Notification.Builder builder) {
11464             Bundle carExtensions = new Bundle();
11465 
11466             if (mLargeIcon != null) {
11467                 carExtensions.putParcelable(EXTRA_LARGE_ICON, mLargeIcon);
11468             }
11469             if (mColor != Notification.COLOR_DEFAULT) {
11470                 carExtensions.putInt(EXTRA_COLOR, mColor);
11471             }
11472 
11473             if (mUnreadConversation != null) {
11474                 Bundle b = mUnreadConversation.getBundleForUnreadConversation();
11475                 carExtensions.putBundle(EXTRA_CONVERSATION, b);
11476             }
11477 
11478             builder.getExtras().putBundle(EXTRA_CAR_EXTENDER, carExtensions);
11479             return builder;
11480         }
11481 
11482         /**
11483          * Sets the accent color to use when Android Auto presents the notification.
11484          *
11485          * Android Auto uses the color set with {@link Notification.Builder#setColor(int)}
11486          * to accent the displayed notification. However, not all colors are acceptable in an
11487          * automotive setting. This method can be used to override the color provided in the
11488          * notification in such a situation.
11489          */
setColor(@olorInt int color)11490         public CarExtender setColor(@ColorInt int color) {
11491             mColor = color;
11492             return this;
11493         }
11494 
11495         /**
11496          * Gets the accent color.
11497          *
11498          * @see #setColor
11499          */
11500         @ColorInt
getColor()11501         public int getColor() {
11502             return mColor;
11503         }
11504 
11505         /**
11506          * Sets the large icon of the car notification.
11507          *
11508          * If no large icon is set in the extender, Android Auto will display the icon
11509          * specified by {@link Notification.Builder#setLargeIcon(android.graphics.Bitmap)}
11510          *
11511          * @param largeIcon The large icon to use in the car notification.
11512          * @return This object for method chaining.
11513          */
setLargeIcon(Bitmap largeIcon)11514         public CarExtender setLargeIcon(Bitmap largeIcon) {
11515             mLargeIcon = largeIcon;
11516             return this;
11517         }
11518 
11519         /**
11520          * Gets the large icon used in this car notification, or null if no icon has been set.
11521          *
11522          * @return The large icon for the car notification.
11523          * @see CarExtender#setLargeIcon
11524          */
getLargeIcon()11525         public Bitmap getLargeIcon() {
11526             return mLargeIcon;
11527         }
11528 
11529         /**
11530          * Sets the unread conversation in a message notification.
11531          *
11532          * @param unreadConversation The unread part of the conversation this notification conveys.
11533          * @return This object for method chaining.
11534          */
setUnreadConversation(UnreadConversation unreadConversation)11535         public CarExtender setUnreadConversation(UnreadConversation unreadConversation) {
11536             mUnreadConversation = unreadConversation;
11537             return this;
11538         }
11539 
11540         /**
11541          * Returns the unread conversation conveyed by this notification.
11542          * @see #setUnreadConversation(UnreadConversation)
11543          */
getUnreadConversation()11544         public UnreadConversation getUnreadConversation() {
11545             return mUnreadConversation;
11546         }
11547 
11548         /**
11549          * A class which holds the unread messages from a conversation.
11550          */
11551         public static class UnreadConversation {
11552             private static final String KEY_AUTHOR = "author";
11553             private static final String KEY_TEXT = "text";
11554             private static final String KEY_MESSAGES = "messages";
11555             private static final String KEY_REMOTE_INPUT = "remote_input";
11556             private static final String KEY_ON_REPLY = "on_reply";
11557             private static final String KEY_ON_READ = "on_read";
11558             private static final String KEY_PARTICIPANTS = "participants";
11559             private static final String KEY_TIMESTAMP = "timestamp";
11560 
11561             private final String[] mMessages;
11562             private final RemoteInput mRemoteInput;
11563             private final PendingIntent mReplyPendingIntent;
11564             private final PendingIntent mReadPendingIntent;
11565             private final String[] mParticipants;
11566             private final long mLatestTimestamp;
11567 
UnreadConversation(String[] messages, RemoteInput remoteInput, PendingIntent replyPendingIntent, PendingIntent readPendingIntent, String[] participants, long latestTimestamp)11568             UnreadConversation(String[] messages, RemoteInput remoteInput,
11569                     PendingIntent replyPendingIntent, PendingIntent readPendingIntent,
11570                     String[] participants, long latestTimestamp) {
11571                 mMessages = messages;
11572                 mRemoteInput = remoteInput;
11573                 mReadPendingIntent = readPendingIntent;
11574                 mReplyPendingIntent = replyPendingIntent;
11575                 mParticipants = participants;
11576                 mLatestTimestamp = latestTimestamp;
11577             }
11578 
11579             /**
11580              * Gets the list of messages conveyed by this notification.
11581              */
getMessages()11582             public String[] getMessages() {
11583                 return mMessages;
11584             }
11585 
11586             /**
11587              * Gets the remote input that will be used to convey the response to a message list, or
11588              * null if no such remote input exists.
11589              */
getRemoteInput()11590             public RemoteInput getRemoteInput() {
11591                 return mRemoteInput;
11592             }
11593 
11594             /**
11595              * Gets the pending intent that will be triggered when the user replies to this
11596              * notification.
11597              */
getReplyPendingIntent()11598             public PendingIntent getReplyPendingIntent() {
11599                 return mReplyPendingIntent;
11600             }
11601 
11602             /**
11603              * Gets the pending intent that Android Auto will send after it reads aloud all messages
11604              * in this object's message list.
11605              */
getReadPendingIntent()11606             public PendingIntent getReadPendingIntent() {
11607                 return mReadPendingIntent;
11608             }
11609 
11610             /**
11611              * Gets the participants in the conversation.
11612              */
getParticipants()11613             public String[] getParticipants() {
11614                 return mParticipants;
11615             }
11616 
11617             /**
11618              * Gets the firs participant in the conversation.
11619              */
getParticipant()11620             public String getParticipant() {
11621                 return mParticipants.length > 0 ? mParticipants[0] : null;
11622             }
11623 
11624             /**
11625              * Gets the timestamp of the conversation.
11626              */
getLatestTimestamp()11627             public long getLatestTimestamp() {
11628                 return mLatestTimestamp;
11629             }
11630 
getBundleForUnreadConversation()11631             Bundle getBundleForUnreadConversation() {
11632                 Bundle b = new Bundle();
11633                 String author = null;
11634                 if (mParticipants != null && mParticipants.length > 1) {
11635                     author = mParticipants[0];
11636                 }
11637                 Parcelable[] messages = new Parcelable[mMessages.length];
11638                 for (int i = 0; i < messages.length; i++) {
11639                     Bundle m = new Bundle();
11640                     m.putString(KEY_TEXT, mMessages[i]);
11641                     m.putString(KEY_AUTHOR, author);
11642                     messages[i] = m;
11643                 }
11644                 b.putParcelableArray(KEY_MESSAGES, messages);
11645                 if (mRemoteInput != null) {
11646                     b.putParcelable(KEY_REMOTE_INPUT, mRemoteInput);
11647                 }
11648                 b.putParcelable(KEY_ON_REPLY, mReplyPendingIntent);
11649                 b.putParcelable(KEY_ON_READ, mReadPendingIntent);
11650                 b.putStringArray(KEY_PARTICIPANTS, mParticipants);
11651                 b.putLong(KEY_TIMESTAMP, mLatestTimestamp);
11652                 return b;
11653             }
11654 
getUnreadConversationFromBundle(Bundle b)11655             static UnreadConversation getUnreadConversationFromBundle(Bundle b) {
11656                 if (b == null) {
11657                     return null;
11658                 }
11659                 Parcelable[] parcelableMessages = b.getParcelableArray(KEY_MESSAGES);
11660                 String[] messages = null;
11661                 if (parcelableMessages != null) {
11662                     String[] tmp = new String[parcelableMessages.length];
11663                     boolean success = true;
11664                     for (int i = 0; i < tmp.length; i++) {
11665                         if (!(parcelableMessages[i] instanceof Bundle)) {
11666                             success = false;
11667                             break;
11668                         }
11669                         tmp[i] = ((Bundle) parcelableMessages[i]).getString(KEY_TEXT);
11670                         if (tmp[i] == null) {
11671                             success = false;
11672                             break;
11673                         }
11674                     }
11675                     if (success) {
11676                         messages = tmp;
11677                     } else {
11678                         return null;
11679                     }
11680                 }
11681 
11682                 PendingIntent onRead = b.getParcelable(KEY_ON_READ);
11683                 PendingIntent onReply = b.getParcelable(KEY_ON_REPLY);
11684 
11685                 RemoteInput remoteInput = b.getParcelable(KEY_REMOTE_INPUT);
11686 
11687                 String[] participants = b.getStringArray(KEY_PARTICIPANTS);
11688                 if (participants == null || participants.length != 1) {
11689                     return null;
11690                 }
11691 
11692                 return new UnreadConversation(messages,
11693                         remoteInput,
11694                         onReply,
11695                         onRead,
11696                         participants, b.getLong(KEY_TIMESTAMP));
11697             }
11698         };
11699 
11700         /**
11701          * Builder class for {@link CarExtender.UnreadConversation} objects.
11702          */
11703         public static class Builder {
11704             private final List<String> mMessages = new ArrayList<String>();
11705             private final String mParticipant;
11706             private RemoteInput mRemoteInput;
11707             private PendingIntent mReadPendingIntent;
11708             private PendingIntent mReplyPendingIntent;
11709             private long mLatestTimestamp;
11710 
11711             /**
11712              * Constructs a new builder for {@link CarExtender.UnreadConversation}.
11713              *
11714              * @param name The name of the other participant in the conversation.
11715              */
Builder(String name)11716             public Builder(String name) {
11717                 mParticipant = name;
11718             }
11719 
11720             /**
11721              * Appends a new unread message to the list of messages for this conversation.
11722              *
11723              * The messages should be added from oldest to newest.
11724              *
11725              * @param message The text of the new unread message.
11726              * @return This object for method chaining.
11727              */
addMessage(String message)11728             public Builder addMessage(String message) {
11729                 mMessages.add(message);
11730                 return this;
11731             }
11732 
11733             /**
11734              * Sets the pending intent and remote input which will convey the reply to this
11735              * notification.
11736              *
11737              * @param pendingIntent The pending intent which will be triggered on a reply.
11738              * @param remoteInput The remote input parcelable which will carry the reply.
11739              * @return This object for method chaining.
11740              *
11741              * @see CarExtender.UnreadConversation#getRemoteInput
11742              * @see CarExtender.UnreadConversation#getReplyPendingIntent
11743              */
setReplyAction( PendingIntent pendingIntent, RemoteInput remoteInput)11744             public Builder setReplyAction(
11745                     PendingIntent pendingIntent, RemoteInput remoteInput) {
11746                 mRemoteInput = remoteInput;
11747                 mReplyPendingIntent = pendingIntent;
11748 
11749                 return this;
11750             }
11751 
11752             /**
11753              * Sets the pending intent that will be sent once the messages in this notification
11754              * are read.
11755              *
11756              * @param pendingIntent The pending intent to use.
11757              * @return This object for method chaining.
11758              */
setReadPendingIntent(PendingIntent pendingIntent)11759             public Builder setReadPendingIntent(PendingIntent pendingIntent) {
11760                 mReadPendingIntent = pendingIntent;
11761                 return this;
11762             }
11763 
11764             /**
11765              * Sets the timestamp of the most recent message in an unread conversation.
11766              *
11767              * If a messaging notification has been posted by your application and has not
11768              * yet been cancelled, posting a later notification with the same id and tag
11769              * but without a newer timestamp may result in Android Auto not displaying a
11770              * heads up notification for the later notification.
11771              *
11772              * @param timestamp The timestamp of the most recent message in the conversation.
11773              * @return This object for method chaining.
11774              */
setLatestTimestamp(long timestamp)11775             public Builder setLatestTimestamp(long timestamp) {
11776                 mLatestTimestamp = timestamp;
11777                 return this;
11778             }
11779 
11780             /**
11781              * Builds a new unread conversation object.
11782              *
11783              * @return The new unread conversation object.
11784              */
build()11785             public UnreadConversation build() {
11786                 String[] messages = mMessages.toArray(new String[mMessages.size()]);
11787                 String[] participants = { mParticipant };
11788                 return new UnreadConversation(messages, mRemoteInput, mReplyPendingIntent,
11789                         mReadPendingIntent, participants, mLatestTimestamp);
11790             }
11791         }
11792     }
11793 
11794     /**
11795      * <p>Helper class to add Android TV extensions to notifications. To create a notification
11796      * with a TV extension:
11797      *
11798      * <ol>
11799      *  <li>Create an {@link Notification.Builder}, setting any desired properties.
11800      *  <li>Create a {@link TvExtender}.
11801      *  <li>Set TV-specific properties using the {@code set} methods of
11802      *  {@link TvExtender}.
11803      *  <li>Call {@link Notification.Builder#extend(Notification.Extender)}
11804      *  to apply the extension to a notification.
11805      * </ol>
11806      *
11807      * <pre class="prettyprint">
11808      * Notification notification = new Notification.Builder(context)
11809      *         ...
11810      *         .extend(new TvExtender()
11811      *                 .set*(...))
11812      *         .build();
11813      * </pre>
11814      *
11815      * <p>TV extensions can be accessed on an existing notification by using the
11816      * {@code TvExtender(Notification)} constructor, and then using the {@code get} methods
11817      * to access values.
11818      *
11819      * @hide
11820      */
11821     @SystemApi
11822     public static final class TvExtender implements Extender {
11823         private static final String TAG = "TvExtender";
11824 
11825         private static final String EXTRA_TV_EXTENDER = "android.tv.EXTENSIONS";
11826         private static final String EXTRA_FLAGS = "flags";
11827         private static final String EXTRA_CONTENT_INTENT = "content_intent";
11828         private static final String EXTRA_DELETE_INTENT = "delete_intent";
11829         private static final String EXTRA_CHANNEL_ID = "channel_id";
11830         private static final String EXTRA_SUPPRESS_SHOW_OVER_APPS = "suppressShowOverApps";
11831 
11832         // Flags bitwise-ored to mFlags
11833         private static final int FLAG_AVAILABLE_ON_TV = 0x1;
11834 
11835         private int mFlags;
11836         private String mChannelId;
11837         private PendingIntent mContentIntent;
11838         private PendingIntent mDeleteIntent;
11839         private boolean mSuppressShowOverApps;
11840 
11841         /**
11842          * Create a {@link TvExtender} with default options.
11843          */
TvExtender()11844         public TvExtender() {
11845             mFlags = FLAG_AVAILABLE_ON_TV;
11846         }
11847 
11848         /**
11849          * Create a {@link TvExtender} from the TvExtender options of an existing Notification.
11850          *
11851          * @param notif The notification from which to copy options.
11852          */
TvExtender(Notification notif)11853         public TvExtender(Notification notif) {
11854             Bundle bundle = notif.extras == null ?
11855                 null : notif.extras.getBundle(EXTRA_TV_EXTENDER);
11856             if (bundle != null) {
11857                 mFlags = bundle.getInt(EXTRA_FLAGS);
11858                 mChannelId = bundle.getString(EXTRA_CHANNEL_ID);
11859                 mSuppressShowOverApps = bundle.getBoolean(EXTRA_SUPPRESS_SHOW_OVER_APPS);
11860                 mContentIntent = bundle.getParcelable(EXTRA_CONTENT_INTENT);
11861                 mDeleteIntent = bundle.getParcelable(EXTRA_DELETE_INTENT);
11862             }
11863         }
11864 
11865         /**
11866          * Apply a TV extension to a notification that is being built. This is typically called by
11867          * the {@link Notification.Builder#extend(Notification.Extender)}
11868          * method of {@link Notification.Builder}.
11869          */
11870         @Override
extend(Notification.Builder builder)11871         public Notification.Builder extend(Notification.Builder builder) {
11872             Bundle bundle = new Bundle();
11873 
11874             bundle.putInt(EXTRA_FLAGS, mFlags);
11875             bundle.putString(EXTRA_CHANNEL_ID, mChannelId);
11876             bundle.putBoolean(EXTRA_SUPPRESS_SHOW_OVER_APPS, mSuppressShowOverApps);
11877             if (mContentIntent != null) {
11878                 bundle.putParcelable(EXTRA_CONTENT_INTENT, mContentIntent);
11879             }
11880 
11881             if (mDeleteIntent != null) {
11882                 bundle.putParcelable(EXTRA_DELETE_INTENT, mDeleteIntent);
11883             }
11884 
11885             builder.getExtras().putBundle(EXTRA_TV_EXTENDER, bundle);
11886             return builder;
11887         }
11888 
11889         /**
11890          * Returns true if this notification should be shown on TV. This method return true
11891          * if the notification was extended with a TvExtender.
11892          */
isAvailableOnTv()11893         public boolean isAvailableOnTv() {
11894             return (mFlags & FLAG_AVAILABLE_ON_TV) != 0;
11895         }
11896 
11897         /**
11898          * Specifies the channel the notification should be delivered on when shown on TV.
11899          * It can be different from the channel that the notification is delivered to when
11900          * posting on a non-TV device.
11901          */
setChannel(String channelId)11902         public TvExtender setChannel(String channelId) {
11903             mChannelId = channelId;
11904             return this;
11905         }
11906 
11907         /**
11908          * Specifies the channel the notification should be delivered on when shown on TV.
11909          * It can be different from the channel that the notification is delivered to when
11910          * posting on a non-TV device.
11911          */
setChannelId(String channelId)11912         public TvExtender setChannelId(String channelId) {
11913             mChannelId = channelId;
11914             return this;
11915         }
11916 
11917         /** @removed */
11918         @Deprecated
getChannel()11919         public String getChannel() {
11920             return mChannelId;
11921         }
11922 
11923         /**
11924          * Returns the id of the channel this notification posts to on TV.
11925          */
getChannelId()11926         public String getChannelId() {
11927             return mChannelId;
11928         }
11929 
11930         /**
11931          * Supplies a {@link PendingIntent} to be sent when the notification is selected on TV.
11932          * If provided, it is used instead of the content intent specified
11933          * at the level of Notification.
11934          */
setContentIntent(PendingIntent intent)11935         public TvExtender setContentIntent(PendingIntent intent) {
11936             mContentIntent = intent;
11937             return this;
11938         }
11939 
11940         /**
11941          * Returns the TV-specific content intent.  If this method returns null, the
11942          * main content intent on the notification should be used.
11943          *
11944          * @see {@link Notification#contentIntent}
11945          */
getContentIntent()11946         public PendingIntent getContentIntent() {
11947             return mContentIntent;
11948         }
11949 
11950         /**
11951          * Supplies a {@link PendingIntent} to send when the notification is cleared explicitly
11952          * by the user on TV.  If provided, it is used instead of the delete intent specified
11953          * at the level of Notification.
11954          */
setDeleteIntent(PendingIntent intent)11955         public TvExtender setDeleteIntent(PendingIntent intent) {
11956             mDeleteIntent = intent;
11957             return this;
11958         }
11959 
11960         /**
11961          * Returns the TV-specific delete intent.  If this method returns null, the
11962          * main delete intent on the notification should be used.
11963          *
11964          * @see {@link Notification#deleteIntent}
11965          */
getDeleteIntent()11966         public PendingIntent getDeleteIntent() {
11967             return mDeleteIntent;
11968         }
11969 
11970         /**
11971          * Specifies whether this notification should suppress showing a message over top of apps
11972          * outside of the launcher.
11973          */
setSuppressShowOverApps(boolean suppress)11974         public TvExtender setSuppressShowOverApps(boolean suppress) {
11975             mSuppressShowOverApps = suppress;
11976             return this;
11977         }
11978 
11979         /**
11980          * Returns true if this notification should not show messages over top of apps
11981          * outside of the launcher.
11982          */
getSuppressShowOverApps()11983         public boolean getSuppressShowOverApps() {
11984             return mSuppressShowOverApps;
11985         }
11986     }
11987 
11988     /**
11989      * Get an array of Parcelable objects from a parcelable array bundle field.
11990      * Update the bundle to have a typed array so fetches in the future don't need
11991      * to do an array copy.
11992      */
11993     @Nullable
getParcelableArrayFromBundle( Bundle bundle, String key, Class<T> itemClass)11994     private static <T extends Parcelable> T[] getParcelableArrayFromBundle(
11995             Bundle bundle, String key, Class<T> itemClass) {
11996         final Parcelable[] array = bundle.getParcelableArray(key);
11997         final Class<?> arrayClass = Array.newInstance(itemClass, 0).getClass();
11998         if (arrayClass.isInstance(array) || array == null) {
11999             return (T[]) array;
12000         }
12001         final T[] typedArray = (T[]) Array.newInstance(itemClass, array.length);
12002         for (int i = 0; i < array.length; i++) {
12003             typedArray[i] = (T) array[i];
12004         }
12005         bundle.putParcelableArray(key, typedArray);
12006         return typedArray;
12007     }
12008 
12009     private static class BuilderRemoteViews extends RemoteViews {
BuilderRemoteViews(Parcel parcel)12010         public BuilderRemoteViews(Parcel parcel) {
12011             super(parcel);
12012         }
12013 
BuilderRemoteViews(ApplicationInfo appInfo, int layoutId)12014         public BuilderRemoteViews(ApplicationInfo appInfo, int layoutId) {
12015             super(appInfo, layoutId);
12016         }
12017 
12018         @Override
clone()12019         public BuilderRemoteViews clone() {
12020             Parcel p = Parcel.obtain();
12021             writeToParcel(p, 0);
12022             p.setDataPosition(0);
12023             BuilderRemoteViews brv = new BuilderRemoteViews(p);
12024             p.recycle();
12025             return brv;
12026         }
12027 
12028         /**
12029          * Override and return true, since {@link RemoteViews#onLoadClass(Class)} is not overridden.
12030          *
12031          * @see RemoteViews#shouldUseStaticFilter()
12032          */
12033         @Override
shouldUseStaticFilter()12034         protected boolean shouldUseStaticFilter() {
12035             return true;
12036         }
12037     }
12038 
12039     /**
12040      * A result object where information about the template that was created is saved.
12041      */
12042     private static class TemplateBindResult {
12043         boolean mRightIconVisible;
12044         float mRightIconWidthDp;
12045         float mRightIconHeightDp;
12046 
12047         /**
12048          * The margin end that needs to be added to the heading so that it won't overlap
12049          * with the large icon.  This value includes the space required to accommodate the large
12050          * icon, but should be added to the space needed to accommodate the expander. This does
12051          * not include the 16dp content margin that all notification views must have.
12052          */
12053         public final MarginSet mHeadingExtraMarginSet = new MarginSet();
12054 
12055         /**
12056          * The margin end that needs to be added to the heading so that it won't overlap
12057          * with the large icon.  This value includes the space required to accommodate the large
12058          * icon as well as the expander.  This does not include the 16dp content margin that all
12059          * notification views must have.
12060          */
12061         public final MarginSet mHeadingFullMarginSet = new MarginSet();
12062 
12063         /**
12064          * The margin end that needs to be added to the title text of the big state
12065          * so that it won't overlap with the large icon, but assuming the text can run under
12066          * the expander when that icon is not visible.
12067          */
12068         public final MarginSet mTitleMarginSet = new MarginSet();
12069 
setRightIconState(boolean visible, float widthDp, float heightDp, float marginEndDpIfVisible, float expanderSizeDp)12070         public void setRightIconState(boolean visible, float widthDp, float heightDp,
12071                 float marginEndDpIfVisible, float expanderSizeDp) {
12072             mRightIconVisible = visible;
12073             mRightIconWidthDp = widthDp;
12074             mRightIconHeightDp = heightDp;
12075             mHeadingExtraMarginSet.setValues(0, marginEndDpIfVisible);
12076             mHeadingFullMarginSet.setValues(expanderSizeDp, marginEndDpIfVisible + expanderSizeDp);
12077             mTitleMarginSet.setValues(0, marginEndDpIfVisible + expanderSizeDp);
12078         }
12079 
12080         /**
12081          * This contains the end margins for a view when the right icon is visible or not.  These
12082          * values are both needed so that NotificationGroupingUtil can 'move' the right_icon to the
12083          * left_icon and adjust the margins, and to undo that change as well.
12084          */
12085         private class MarginSet {
12086             private float mValueIfGone;
12087             private float mValueIfVisible;
12088 
setValues(float valueIfGone, float valueIfVisible)12089             public void setValues(float valueIfGone, float valueIfVisible) {
12090                 mValueIfGone = valueIfGone;
12091                 mValueIfVisible = valueIfVisible;
12092             }
12093 
applyToView(@onNull RemoteViews views, @IdRes int viewId)12094             public void applyToView(@NonNull RemoteViews views, @IdRes int viewId) {
12095                 applyToView(views, viewId, 0);
12096             }
12097 
applyToView(@onNull RemoteViews views, @IdRes int viewId, float extraMarginDp)12098             public void applyToView(@NonNull RemoteViews views, @IdRes int viewId,
12099                     float extraMarginDp) {
12100                 final float marginEndDp = getDpValue() + extraMarginDp;
12101                 if (viewId == R.id.notification_header) {
12102                     views.setFloat(R.id.notification_header,
12103                             "setTopLineExtraMarginEndDp", marginEndDp);
12104                 } else if (viewId == R.id.text || viewId == R.id.big_text) {
12105                     if (mValueIfGone != 0) {
12106                         throw new RuntimeException("Programming error: `text` and `big_text` use "
12107                                 + "ImageFloatingTextView which can either show a margin or not; "
12108                                 + "thus mValueIfGone must be 0, but it was " + mValueIfGone);
12109                     }
12110                     // Note that the caller must set "setNumIndentLines" to a positive int in order
12111                     //  for this margin to do anything at all.
12112                     views.setFloat(viewId, "setImageEndMarginDp", mValueIfVisible);
12113                     views.setBoolean(viewId, "setHasImage", mRightIconVisible);
12114                     // Apply just the *extra* margin as the view layout margin; this will be
12115                     //  unchanged depending on the visibility of the image, but it means that the
12116                     //  extra margin applies to *every* line of text instead of just indented lines.
12117                     views.setViewLayoutMargin(viewId, RemoteViews.MARGIN_END,
12118                             extraMarginDp, TypedValue.COMPLEX_UNIT_DIP);
12119                 } else {
12120                     views.setViewLayoutMargin(viewId, RemoteViews.MARGIN_END,
12121                                     marginEndDp, TypedValue.COMPLEX_UNIT_DIP);
12122                 }
12123                 if (mRightIconVisible) {
12124                     views.setIntTag(viewId, R.id.tag_margin_end_when_icon_visible,
12125                             TypedValue.createComplexDimension(
12126                                     mValueIfVisible + extraMarginDp, TypedValue.COMPLEX_UNIT_DIP));
12127                     views.setIntTag(viewId, R.id.tag_margin_end_when_icon_gone,
12128                             TypedValue.createComplexDimension(
12129                                     mValueIfGone + extraMarginDp, TypedValue.COMPLEX_UNIT_DIP));
12130                 }
12131             }
12132 
getDpValue()12133             public float getDpValue() {
12134                 return mRightIconVisible ? mValueIfVisible : mValueIfGone;
12135             }
12136         }
12137     }
12138 
12139     private static class StandardTemplateParams {
12140         /**
12141          * Notifications will be minimally decorated with ONLY an icon and expander:
12142          * <li>A large icon is never shown.
12143          * <li>A progress bar is never shown.
12144          * <li>The expanded and heads up states do not show actions, even if provided.
12145          */
12146         public static final int DECORATION_MINIMAL = 1;
12147 
12148         /**
12149          * Notifications will be partially decorated with AT LEAST an icon and expander:
12150          * <li>A large icon is shown if provided.
12151          * <li>A progress bar is shown if provided and enough space remains below the content.
12152          * <li>Actions are shown in the expanded and heads up states.
12153          */
12154         public static final int DECORATION_PARTIAL = 2;
12155 
12156         public static int VIEW_TYPE_UNSPECIFIED = 0;
12157         public static int VIEW_TYPE_NORMAL = 1;
12158         public static int VIEW_TYPE_BIG = 2;
12159         public static int VIEW_TYPE_HEADS_UP = 3;
12160         public static int VIEW_TYPE_MINIMIZED = 4;    // header only for minimized state
12161         public static int VIEW_TYPE_PUBLIC = 5;       // header only for automatic public version
12162         public static int VIEW_TYPE_GROUP_HEADER = 6; // header only for top of group
12163 
12164         int mViewType = VIEW_TYPE_UNSPECIFIED;
12165         boolean mHeaderless;
12166         boolean mHideAppName;
12167         boolean mHideTitle;
12168         boolean mHideSubText;
12169         boolean mHideTime;
12170         boolean mHideActions;
12171         boolean mHideProgress;
12172         boolean mHideSnoozeButton;
12173         boolean mHideLeftIcon;
12174         boolean mHideRightIcon;
12175         Icon mPromotedPicture;
12176         boolean mCallStyleActions;
12177         boolean mAllowTextWithProgress;
12178         int mTitleViewId;
12179         int mTextViewId;
12180         CharSequence title;
12181         CharSequence text;
12182         CharSequence headerTextSecondary;
12183         CharSequence summaryText;
12184         int maxRemoteInputHistory = Style.MAX_REMOTE_INPUT_HISTORY_LINES;
12185         boolean allowColorization  = true;
12186         boolean mHighlightExpander = false;
12187 
reset()12188         final StandardTemplateParams reset() {
12189             mViewType = VIEW_TYPE_UNSPECIFIED;
12190             mHeaderless = false;
12191             mHideAppName = false;
12192             mHideTitle = false;
12193             mHideSubText = false;
12194             mHideTime = false;
12195             mHideActions = false;
12196             mHideProgress = false;
12197             mHideSnoozeButton = false;
12198             mHideLeftIcon = false;
12199             mHideRightIcon = false;
12200             mPromotedPicture = null;
12201             mCallStyleActions = false;
12202             mAllowTextWithProgress = false;
12203             mTitleViewId = R.id.title;
12204             mTextViewId = R.id.text;
12205             title = null;
12206             text = null;
12207             summaryText = null;
12208             headerTextSecondary = null;
12209             maxRemoteInputHistory = Style.MAX_REMOTE_INPUT_HISTORY_LINES;
12210             allowColorization = true;
12211             mHighlightExpander = false;
12212             return this;
12213         }
12214 
hasTitle()12215         final boolean hasTitle() {
12216             return !TextUtils.isEmpty(title) && !mHideTitle;
12217         }
12218 
viewType(int viewType)12219         final StandardTemplateParams viewType(int viewType) {
12220             mViewType = viewType;
12221             return this;
12222         }
12223 
headerless(boolean headerless)12224         public StandardTemplateParams headerless(boolean headerless) {
12225             mHeaderless = headerless;
12226             return this;
12227         }
12228 
hideAppName(boolean hideAppName)12229         public StandardTemplateParams hideAppName(boolean hideAppName) {
12230             mHideAppName = hideAppName;
12231             return this;
12232         }
12233 
hideSubText(boolean hideSubText)12234         public StandardTemplateParams hideSubText(boolean hideSubText) {
12235             mHideSubText = hideSubText;
12236             return this;
12237         }
12238 
hideTime(boolean hideTime)12239         public StandardTemplateParams hideTime(boolean hideTime) {
12240             mHideTime = hideTime;
12241             return this;
12242         }
12243 
hideActions(boolean hideActions)12244         final StandardTemplateParams hideActions(boolean hideActions) {
12245             this.mHideActions = hideActions;
12246             return this;
12247         }
12248 
hideProgress(boolean hideProgress)12249         final StandardTemplateParams hideProgress(boolean hideProgress) {
12250             this.mHideProgress = hideProgress;
12251             return this;
12252         }
12253 
hideTitle(boolean hideTitle)12254         final StandardTemplateParams hideTitle(boolean hideTitle) {
12255             this.mHideTitle = hideTitle;
12256             return this;
12257         }
12258 
callStyleActions(boolean callStyleActions)12259         final StandardTemplateParams callStyleActions(boolean callStyleActions) {
12260             this.mCallStyleActions = callStyleActions;
12261             return this;
12262         }
12263 
allowTextWithProgress(boolean allowTextWithProgress)12264         final StandardTemplateParams allowTextWithProgress(boolean allowTextWithProgress) {
12265             this.mAllowTextWithProgress = allowTextWithProgress;
12266             return this;
12267         }
12268 
hideSnoozeButton(boolean hideSnoozeButton)12269         final StandardTemplateParams hideSnoozeButton(boolean hideSnoozeButton) {
12270             this.mHideSnoozeButton = hideSnoozeButton;
12271             return this;
12272         }
12273 
promotedPicture(Icon promotedPicture)12274         final StandardTemplateParams promotedPicture(Icon promotedPicture) {
12275             this.mPromotedPicture = promotedPicture;
12276             return this;
12277         }
12278 
titleViewId(int titleViewId)12279         public StandardTemplateParams titleViewId(int titleViewId) {
12280             mTitleViewId = titleViewId;
12281             return this;
12282         }
12283 
textViewId(int textViewId)12284         public StandardTemplateParams textViewId(int textViewId) {
12285             mTextViewId = textViewId;
12286             return this;
12287         }
12288 
title(CharSequence title)12289         final StandardTemplateParams title(CharSequence title) {
12290             this.title = title;
12291             return this;
12292         }
12293 
text(CharSequence text)12294         final StandardTemplateParams text(CharSequence text) {
12295             this.text = text;
12296             return this;
12297         }
12298 
summaryText(CharSequence text)12299         final StandardTemplateParams summaryText(CharSequence text) {
12300             this.summaryText = text;
12301             return this;
12302         }
12303 
headerTextSecondary(CharSequence text)12304         final StandardTemplateParams headerTextSecondary(CharSequence text) {
12305             this.headerTextSecondary = text;
12306             return this;
12307         }
12308 
12309 
hideLeftIcon(boolean hideLeftIcon)12310         final StandardTemplateParams hideLeftIcon(boolean hideLeftIcon) {
12311             this.mHideLeftIcon = hideLeftIcon;
12312             return this;
12313         }
12314 
hideRightIcon(boolean hideRightIcon)12315         final StandardTemplateParams hideRightIcon(boolean hideRightIcon) {
12316             this.mHideRightIcon = hideRightIcon;
12317             return this;
12318         }
12319 
disallowColorization()12320         final StandardTemplateParams disallowColorization() {
12321             this.allowColorization = false;
12322             return this;
12323         }
12324 
highlightExpander(boolean highlight)12325         final StandardTemplateParams highlightExpander(boolean highlight) {
12326             this.mHighlightExpander = highlight;
12327             return this;
12328         }
12329 
fillTextsFrom(Builder b)12330         final StandardTemplateParams fillTextsFrom(Builder b) {
12331             Bundle extras = b.mN.extras;
12332             this.title = b.processLegacyText(extras.getCharSequence(EXTRA_TITLE));
12333             this.text = b.processLegacyText(extras.getCharSequence(EXTRA_TEXT));
12334             this.summaryText = extras.getCharSequence(EXTRA_SUB_TEXT);
12335             return this;
12336         }
12337 
12338         /**
12339          * Set the maximum lines of remote input history lines allowed.
12340          * @param maxRemoteInputHistory The number of lines.
12341          * @return The builder for method chaining.
12342          */
setMaxRemoteInputHistory(int maxRemoteInputHistory)12343         public StandardTemplateParams setMaxRemoteInputHistory(int maxRemoteInputHistory) {
12344             this.maxRemoteInputHistory = maxRemoteInputHistory;
12345             return this;
12346         }
12347 
decorationType(int decorationType)12348         public StandardTemplateParams decorationType(int decorationType) {
12349             hideTitle(true);
12350             // Minimally decorated custom views do not show certain pieces of chrome that have
12351             // always been shown when using DecoratedCustomViewStyle.
12352             boolean hideOtherFields = decorationType <= DECORATION_MINIMAL;
12353             hideLeftIcon(false);  // The left icon decoration is better than showing nothing.
12354             hideRightIcon(hideOtherFields);
12355             hideProgress(hideOtherFields);
12356             hideActions(hideOtherFields);
12357             return this;
12358         }
12359     }
12360 
12361     /**
12362      * A utility which stores and calculates the palette of colors used to color notifications.
12363      * @hide
12364      */
12365     @VisibleForTesting
12366     public static class Colors {
12367         private int mPaletteIsForRawColor = COLOR_INVALID;
12368         private boolean mPaletteIsForColorized = false;
12369         private boolean mPaletteIsForNightMode = false;
12370         // The following colors are the palette
12371         private int mBackgroundColor = COLOR_INVALID;
12372         private int mProtectionColor = COLOR_INVALID;
12373         private int mPrimaryTextColor = COLOR_INVALID;
12374         private int mSecondaryTextColor = COLOR_INVALID;
12375         private int mPrimaryAccentColor = COLOR_INVALID;
12376         private int mSecondaryAccentColor = COLOR_INVALID;
12377         private int mTertiaryAccentColor = COLOR_INVALID;
12378         private int mOnAccentTextColor = COLOR_INVALID;
12379         private int mErrorColor = COLOR_INVALID;
12380         private int mContrastColor = COLOR_INVALID;
12381         private int mRippleAlpha = 0x33;
12382 
12383         /**
12384          * A utility for obtaining a TypedArray of the given DayNight-styled attributes, which
12385          * returns null when the context is a mock with no theme.
12386          *
12387          * NOTE: Calling this method is expensive, as creating a new ContextThemeWrapper
12388          * instances can allocate as much as 5MB of memory, so its important to call this method
12389          * only when necessary, getting as many attributes as possible from each call.
12390          *
12391          * @see Resources.Theme#obtainStyledAttributes(int[])
12392          */
12393         @Nullable
obtainDayNightAttributes(@onNull Context ctx, @NonNull @StyleableRes int[] attrs)12394         private static TypedArray obtainDayNightAttributes(@NonNull Context ctx,
12395                 @NonNull @StyleableRes int[] attrs) {
12396             // when testing, the mock context may have no theme
12397             if (ctx.getTheme() == null) {
12398                 return null;
12399             }
12400             Resources.Theme theme = new ContextThemeWrapper(ctx,
12401                     R.style.Theme_DeviceDefault_DayNight).getTheme();
12402             return theme.obtainStyledAttributes(attrs);
12403         }
12404 
12405         /** A null-safe wrapper of TypedArray.getColor because mocks return null */
getColor(@ullable TypedArray ta, int index, @ColorInt int defValue)12406         private static @ColorInt int getColor(@Nullable TypedArray ta, int index,
12407                 @ColorInt int defValue) {
12408             return ta == null ? defValue : ta.getColor(index, defValue);
12409         }
12410 
12411         /**
12412          * Resolve the palette.  If the inputs have not changed, this will be a no-op.
12413          * This does not handle invalidating the resolved colors when the context itself changes,
12414          * because that case does not happen in the current notification inflation pipeline; we will
12415          * recreate a new builder (and thus a new palette) when reinflating notifications for a new
12416          * theme (admittedly, we do the same for night mode, but that's easy to check).
12417          *
12418          * @param ctx the builder context.
12419          * @param rawColor the notification's color; may be COLOR_DEFAULT, but may never have alpha.
12420          * @param isColorized whether the notification is colorized.
12421          * @param nightMode whether the UI is in night mode.
12422          */
resolvePalette(Context ctx, int rawColor, boolean isColorized, boolean nightMode)12423         public void resolvePalette(Context ctx, int rawColor,
12424                 boolean isColorized, boolean nightMode) {
12425             if (mPaletteIsForRawColor == rawColor
12426                     && mPaletteIsForColorized == isColorized
12427                     && mPaletteIsForNightMode == nightMode) {
12428                 return;
12429             }
12430             mPaletteIsForRawColor = rawColor;
12431             mPaletteIsForColorized = isColorized;
12432             mPaletteIsForNightMode = nightMode;
12433 
12434             if (isColorized) {
12435                 if (rawColor == COLOR_DEFAULT) {
12436                     int[] attrs = {R.attr.colorAccentSecondary};
12437                     try (TypedArray ta = obtainDayNightAttributes(ctx, attrs)) {
12438                         mBackgroundColor = getColor(ta, 0, Color.WHITE);
12439                     }
12440                 } else {
12441                     mBackgroundColor = rawColor;
12442                 }
12443                 mProtectionColor = COLOR_INVALID;  // filled in at the end
12444                 mPrimaryTextColor = ContrastColorUtil.findAlphaToMeetContrast(
12445                         ContrastColorUtil.resolvePrimaryColor(ctx, mBackgroundColor, nightMode),
12446                         mBackgroundColor, 4.5);
12447                 mSecondaryTextColor = ContrastColorUtil.findAlphaToMeetContrast(
12448                         ContrastColorUtil.resolveSecondaryColor(ctx, mBackgroundColor, nightMode),
12449                         mBackgroundColor, 4.5);
12450                 mContrastColor = mPrimaryTextColor;
12451                 mPrimaryAccentColor = mPrimaryTextColor;
12452                 mSecondaryAccentColor = mSecondaryTextColor;
12453                 mTertiaryAccentColor = flattenAlpha(mPrimaryTextColor, mBackgroundColor);
12454                 mOnAccentTextColor = mBackgroundColor;
12455                 mErrorColor = mPrimaryTextColor;
12456                 mRippleAlpha = 0x33;
12457             } else {
12458                 int[] attrs = {
12459                         R.attr.colorSurface,
12460                         R.attr.colorBackgroundFloating,
12461                         R.attr.textColorPrimary,
12462                         R.attr.textColorSecondary,
12463                         R.attr.colorAccent,
12464                         R.attr.colorAccentSecondary,
12465                         R.attr.colorAccentTertiary,
12466                         R.attr.textColorOnAccent,
12467                         R.attr.colorError,
12468                         R.attr.colorControlHighlight
12469                 };
12470                 try (TypedArray ta = obtainDayNightAttributes(ctx, attrs)) {
12471                     mBackgroundColor = getColor(ta, 0, nightMode ? Color.BLACK : Color.WHITE);
12472                     mProtectionColor = getColor(ta, 1, COLOR_INVALID);
12473                     mPrimaryTextColor = getColor(ta, 2, COLOR_INVALID);
12474                     mSecondaryTextColor = getColor(ta, 3, COLOR_INVALID);
12475                     mPrimaryAccentColor = getColor(ta, 4, COLOR_INVALID);
12476                     mSecondaryAccentColor = getColor(ta, 5, COLOR_INVALID);
12477                     mTertiaryAccentColor = getColor(ta, 6, COLOR_INVALID);
12478                     mOnAccentTextColor = getColor(ta, 7, COLOR_INVALID);
12479                     mErrorColor = getColor(ta, 8, COLOR_INVALID);
12480                     mRippleAlpha = Color.alpha(getColor(ta, 9, 0x33ffffff));
12481                 }
12482                 mContrastColor = calculateContrastColor(ctx, rawColor, mPrimaryAccentColor,
12483                         mBackgroundColor, nightMode);
12484 
12485                 // make sure every color has a valid value
12486                 if (mPrimaryTextColor == COLOR_INVALID) {
12487                     mPrimaryTextColor = ContrastColorUtil.resolvePrimaryColor(
12488                             ctx, mBackgroundColor, nightMode);
12489                 }
12490                 if (mSecondaryTextColor == COLOR_INVALID) {
12491                     mSecondaryTextColor = ContrastColorUtil.resolveSecondaryColor(
12492                             ctx, mBackgroundColor, nightMode);
12493                 }
12494                 if (mPrimaryAccentColor == COLOR_INVALID) {
12495                     mPrimaryAccentColor = mContrastColor;
12496                 }
12497                 if (mSecondaryAccentColor == COLOR_INVALID) {
12498                     mSecondaryAccentColor = mContrastColor;
12499                 }
12500                 if (mTertiaryAccentColor == COLOR_INVALID) {
12501                     mTertiaryAccentColor = mContrastColor;
12502                 }
12503                 if (mOnAccentTextColor == COLOR_INVALID) {
12504                     mOnAccentTextColor = ColorUtils.setAlphaComponent(
12505                             ContrastColorUtil.resolvePrimaryColor(
12506                                     ctx, mTertiaryAccentColor, nightMode), 0xFF);
12507                 }
12508                 if (mErrorColor == COLOR_INVALID) {
12509                     mErrorColor = mPrimaryTextColor;
12510                 }
12511             }
12512             // make sure every color has a valid value
12513             if (mProtectionColor == COLOR_INVALID) {
12514                 mProtectionColor = ColorUtils.blendARGB(mPrimaryTextColor, mBackgroundColor, 0.8f);
12515             }
12516         }
12517 
12518         /** calculates the contrast color for the non-colorized notifications */
calculateContrastColor(Context ctx, @ColorInt int rawColor, @ColorInt int accentColor, @ColorInt int backgroundColor, boolean nightMode)12519         private static @ColorInt int calculateContrastColor(Context ctx, @ColorInt int rawColor,
12520                 @ColorInt int accentColor, @ColorInt int backgroundColor, boolean nightMode) {
12521             int color;
12522             if (rawColor == COLOR_DEFAULT) {
12523                 color = accentColor;
12524                 if (color == COLOR_INVALID) {
12525                     color = ContrastColorUtil.resolveDefaultColor(ctx, backgroundColor, nightMode);
12526                 }
12527             } else {
12528                 color = ContrastColorUtil.resolveContrastColor(ctx, rawColor, backgroundColor,
12529                         nightMode);
12530             }
12531             return flattenAlpha(color, backgroundColor);
12532         }
12533 
12534         /** remove any alpha by manually blending it with the given background. */
flattenAlpha(@olorInt int color, @ColorInt int background)12535         private static @ColorInt int flattenAlpha(@ColorInt int color, @ColorInt int background) {
12536             return Color.alpha(color) == 0xff ? color
12537                     : ContrastColorUtil.compositeColors(color, background);
12538         }
12539 
12540         /** @return the notification's background color */
getBackgroundColor()12541         public @ColorInt int getBackgroundColor() {
12542             return mBackgroundColor;
12543         }
12544 
12545         /**
12546          * @return the "surface protection" color from the theme,
12547          * or a variant of the normal background color when colorized.
12548          */
getProtectionColor()12549         public @ColorInt int getProtectionColor() {
12550             return mProtectionColor;
12551         }
12552 
12553         /** @return the color for the most prominent text */
getPrimaryTextColor()12554         public @ColorInt int getPrimaryTextColor() {
12555             return mPrimaryTextColor;
12556         }
12557 
12558         /** @return the color for less prominent text */
getSecondaryTextColor()12559         public @ColorInt int getSecondaryTextColor() {
12560             return mSecondaryTextColor;
12561         }
12562 
12563         /** @return the theme's accent color for colored UI elements. */
getPrimaryAccentColor()12564         public @ColorInt int getPrimaryAccentColor() {
12565             return mPrimaryAccentColor;
12566         }
12567 
12568         /** @return the theme's secondary accent color for colored UI elements. */
getSecondaryAccentColor()12569         public @ColorInt int getSecondaryAccentColor() {
12570             return mSecondaryAccentColor;
12571         }
12572 
12573         /** @return the theme's tertiary accent color for colored UI elements. */
getTertiaryAccentColor()12574         public @ColorInt int getTertiaryAccentColor() {
12575             return mTertiaryAccentColor;
12576         }
12577 
12578         /** @return the theme's text color to be used on the tertiary accent color. */
getOnAccentTextColor()12579         public @ColorInt int getOnAccentTextColor() {
12580             return mOnAccentTextColor;
12581         }
12582 
12583         /**
12584          * @return the contrast-adjusted version of the color provided by the app, or the
12585          * primary text color when colorized.
12586          */
getContrastColor()12587         public @ColorInt int getContrastColor() {
12588             return mContrastColor;
12589         }
12590 
12591         /** @return the theme's error color, or the primary text color when colorized. */
getErrorColor()12592         public @ColorInt int getErrorColor() {
12593             return mErrorColor;
12594         }
12595 
12596         /** @return the alpha component of the current theme's control highlight color. */
getRippleAlpha()12597         public int getRippleAlpha() {
12598             return mRippleAlpha;
12599         }
12600     }
12601 }
12602