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.app.admin.DevicePolicyResources.Drawables.Source.NOTIFICATION;
21 import static android.app.admin.DevicePolicyResources.Drawables.Style.SOLID_COLORED;
22 import static android.app.admin.DevicePolicyResources.Drawables.WORK_PROFILE_ICON;
23 import static android.app.admin.DevicePolicyResources.UNDEFINED;
24 import static android.graphics.drawable.Icon.TYPE_URI;
25 import static android.graphics.drawable.Icon.TYPE_URI_ADAPTIVE_BITMAP;
26 
27 import static java.util.Objects.requireNonNull;
28 
29 import android.annotation.ColorInt;
30 import android.annotation.ColorRes;
31 import android.annotation.DimenRes;
32 import android.annotation.Dimension;
33 import android.annotation.DrawableRes;
34 import android.annotation.IdRes;
35 import android.annotation.IntDef;
36 import android.annotation.NonNull;
37 import android.annotation.Nullable;
38 import android.annotation.RequiresPermission;
39 import android.annotation.SdkConstant;
40 import android.annotation.SdkConstant.SdkConstantType;
41 import android.annotation.StringRes;
42 import android.annotation.StyleableRes;
43 import android.annotation.SuppressLint;
44 import android.annotation.SystemApi;
45 import android.annotation.TestApi;
46 import android.app.admin.DevicePolicyManager;
47 import android.compat.annotation.UnsupportedAppUsage;
48 import android.content.Context;
49 import android.content.Intent;
50 import android.content.LocusId;
51 import android.content.pm.ApplicationInfo;
52 import android.content.pm.PackageManager;
53 import android.content.pm.PackageManager.NameNotFoundException;
54 import android.content.pm.ShortcutInfo;
55 import android.content.res.ColorStateList;
56 import android.content.res.Configuration;
57 import android.content.res.Resources;
58 import android.content.res.TypedArray;
59 import android.graphics.Bitmap;
60 import android.graphics.Canvas;
61 import android.graphics.Color;
62 import android.graphics.PorterDuff;
63 import android.graphics.drawable.Drawable;
64 import android.graphics.drawable.Icon;
65 import android.media.AudioAttributes;
66 import android.media.AudioManager;
67 import android.media.PlayerBase;
68 import android.media.session.MediaSession;
69 import android.net.Uri;
70 import android.os.BadParcelableException;
71 import android.os.Build;
72 import android.os.Bundle;
73 import android.os.IBinder;
74 import android.os.Parcel;
75 import android.os.Parcelable;
76 import android.os.SystemClock;
77 import android.os.SystemProperties;
78 import android.os.UserHandle;
79 import android.os.UserManager;
80 import android.provider.Settings;
81 import android.text.BidiFormatter;
82 import android.text.SpannableStringBuilder;
83 import android.text.Spanned;
84 import android.text.TextUtils;
85 import android.text.style.AbsoluteSizeSpan;
86 import android.text.style.CharacterStyle;
87 import android.text.style.ForegroundColorSpan;
88 import android.text.style.RelativeSizeSpan;
89 import android.text.style.TextAppearanceSpan;
90 import android.util.ArraySet;
91 import android.util.Log;
92 import android.util.Pair;
93 import android.util.SparseArray;
94 import android.util.TypedValue;
95 import android.util.proto.ProtoOutputStream;
96 import android.view.ContextThemeWrapper;
97 import android.view.Gravity;
98 import android.view.View;
99 import android.view.contentcapture.ContentCaptureContext;
100 import android.widget.ProgressBar;
101 import android.widget.RemoteViews;
102 
103 import com.android.internal.R;
104 import com.android.internal.annotations.VisibleForTesting;
105 import com.android.internal.graphics.ColorUtils;
106 import com.android.internal.util.ArrayUtils;
107 import com.android.internal.util.ContrastColorUtil;
108 
109 import java.lang.annotation.Retention;
110 import java.lang.annotation.RetentionPolicy;
111 import java.lang.reflect.Array;
112 import java.lang.reflect.Constructor;
113 import java.util.ArrayList;
114 import java.util.Arrays;
115 import java.util.Collections;
116 import java.util.List;
117 import java.util.Objects;
118 import java.util.Set;
119 import java.util.function.Consumer;
120 
121 /**
122  * A class that represents how a persistent notification is to be presented to
123  * the user using the {@link android.app.NotificationManager}.
124  *
125  * <p>The {@link Notification.Builder Notification.Builder} has been added to make it
126  * easier to construct Notifications.</p>
127  *
128  * <div class="special reference">
129  * <h3>Developer Guides</h3>
130  * <p>For a guide to creating notifications, read the
131  * <a href="{@docRoot}guide/topics/ui/notifiers/notifications.html">Status Bar Notifications</a>
132  * developer guide.</p>
133  * </div>
134  */
135 public class Notification implements Parcelable
136 {
137     private static final String TAG = "Notification";
138 
139     /**
140      * @hide
141      */
142     @Retention(RetentionPolicy.SOURCE)
143     @IntDef({
144             FOREGROUND_SERVICE_DEFAULT,
145             FOREGROUND_SERVICE_IMMEDIATE,
146             FOREGROUND_SERVICE_DEFERRED
147     })
148     public @interface ServiceNotificationPolicy {};
149 
150     /**
151      * If the Notification associated with starting a foreground service has been
152      * built using setForegroundServiceBehavior() with this behavior, display of
153      * the notification will usually be suppressed for a short time to avoid visual
154      * disturbances to the user.
155      * @see Notification.Builder#setForegroundServiceBehavior(int)
156      * @see #FOREGROUND_SERVICE_IMMEDIATE
157      * @see #FOREGROUND_SERVICE_DEFERRED
158      */
159     public static final @ServiceNotificationPolicy int FOREGROUND_SERVICE_DEFAULT = 0;
160 
161     /**
162      * If the Notification associated with starting a foreground service has been
163      * built using setForegroundServiceBehavior() with this behavior, display of
164      * the notification will be immediate even if the default behavior would be
165      * to defer visibility for a short time.
166      * @see Notification.Builder#setForegroundServiceBehavior(int)
167      * @see #FOREGROUND_SERVICE_DEFAULT
168      * @see #FOREGROUND_SERVICE_DEFERRED
169      */
170     public static final @ServiceNotificationPolicy int FOREGROUND_SERVICE_IMMEDIATE = 1;
171 
172     /**
173      * If the Notification associated with starting a foreground service has been
174      * built using setForegroundServiceBehavior() with this behavior, display of
175      * the notification will usually be suppressed for a short time to avoid visual
176      * disturbances to the user.
177      * @see Notification.Builder#setForegroundServiceBehavior(int)
178      * @see #FOREGROUND_SERVICE_DEFAULT
179      * @see #FOREGROUND_SERVICE_IMMEDIATE
180      */
181     public static final @ServiceNotificationPolicy int FOREGROUND_SERVICE_DEFERRED = 2;
182 
183     @ServiceNotificationPolicy
184     private int mFgsDeferBehavior;
185 
186     /**
187      * An activity that provides a user interface for adjusting notification preferences for its
188      * containing application.
189      */
190     @SdkConstant(SdkConstantType.INTENT_CATEGORY)
191     public static final String INTENT_CATEGORY_NOTIFICATION_PREFERENCES
192             = "android.intent.category.NOTIFICATION_PREFERENCES";
193 
194     /**
195      * Optional extra for {@link #INTENT_CATEGORY_NOTIFICATION_PREFERENCES}. If provided, will
196      * contain a {@link NotificationChannel#getId() channel id} that can be used to narrow down
197      * what settings should be shown in the target app.
198      */
199     public static final String EXTRA_CHANNEL_ID = "android.intent.extra.CHANNEL_ID";
200 
201     /**
202      * Optional extra for {@link #INTENT_CATEGORY_NOTIFICATION_PREFERENCES}. If provided, will
203      * contain a {@link NotificationChannelGroup#getId() group id} that can be used to narrow down
204      * what settings should be shown in the target app.
205      */
206     public static final String EXTRA_CHANNEL_GROUP_ID = "android.intent.extra.CHANNEL_GROUP_ID";
207 
208     /**
209      * Optional extra for {@link #INTENT_CATEGORY_NOTIFICATION_PREFERENCES}. If provided, will
210      * contain the tag provided to {@link NotificationManager#notify(String, int, Notification)}
211      * that can be used to narrow down what settings should be shown in the target app.
212      */
213     public static final String EXTRA_NOTIFICATION_TAG = "android.intent.extra.NOTIFICATION_TAG";
214 
215     /**
216      * Optional extra for {@link #INTENT_CATEGORY_NOTIFICATION_PREFERENCES}. If provided, will
217      * contain the id provided to {@link NotificationManager#notify(String, int, Notification)}
218      * that can be used to narrow down what settings should be shown in the target app.
219      */
220     public static final String EXTRA_NOTIFICATION_ID = "android.intent.extra.NOTIFICATION_ID";
221 
222     /**
223      * Use all default values (where applicable).
224      */
225     public static final int DEFAULT_ALL = ~0;
226 
227     /**
228      * Use the default notification sound. This will ignore any given
229      * {@link #sound}.
230      *
231      * <p>
232      * A notification that is noisy is more likely to be presented as a heads-up notification.
233      * </p>
234      *
235      * @see #defaults
236      */
237 
238     public static final int DEFAULT_SOUND = 1;
239 
240     /**
241      * Use the default notification vibrate. This will ignore any given
242      * {@link #vibrate}. Using phone vibration requires the
243      * {@link android.Manifest.permission#VIBRATE VIBRATE} permission.
244      *
245      * <p>
246      * A notification that vibrates is more likely to be presented as a heads-up notification.
247      * </p>
248      *
249      * @see #defaults
250      */
251 
252     public static final int DEFAULT_VIBRATE = 2;
253 
254     /**
255      * Use the default notification lights. This will ignore the
256      * {@link #FLAG_SHOW_LIGHTS} bit, and {@link #ledARGB}, {@link #ledOffMS}, or
257      * {@link #ledOnMS}.
258      *
259      * @see #defaults
260      */
261 
262     public static final int DEFAULT_LIGHTS = 4;
263 
264     /**
265      * Maximum length of CharSequences accepted by Builder and friends.
266      *
267      * <p>
268      * Avoids spamming the system with overly large strings such as full e-mails.
269      */
270     private static final int MAX_CHARSEQUENCE_LENGTH = 1024;
271 
272     /**
273      * Maximum entries of reply text that are accepted by Builder and friends.
274      */
275     private static final int MAX_REPLY_HISTORY = 5;
276 
277     /**
278      * Maximum aspect ratio of the large icon. 16:9
279      */
280     private static final float MAX_LARGE_ICON_ASPECT_RATIO = 16f / 9f;
281 
282     /**
283      * Maximum number of (generic) action buttons in a notification (contextual action buttons are
284      * handled separately).
285      * @hide
286      */
287     public static final int MAX_ACTION_BUTTONS = 3;
288 
289     /**
290      * If the notification contained an unsent draft for a RemoteInput when the user clicked on it,
291      * we're adding the draft as a String extra to the {@link #contentIntent} using this key.
292      *
293      * <p>Apps may use this extra to prepopulate text fields in the app, where the user usually
294      * sends messages.</p>
295      */
296     public static final String EXTRA_REMOTE_INPUT_DRAFT = "android.remoteInputDraft";
297 
298     /**
299      * A timestamp related to this notification, in milliseconds since the epoch.
300      *
301      * Default value: {@link System#currentTimeMillis() Now}.
302      *
303      * Choose a timestamp that will be most relevant to the user. For most finite events, this
304      * corresponds to the time the event happened (or will happen, in the case of events that have
305      * yet to occur but about which the user is being informed). Indefinite events should be
306      * timestamped according to when the activity began.
307      *
308      * Some examples:
309      *
310      * <ul>
311      *   <li>Notification of a new chat message should be stamped when the message was received.</li>
312      *   <li>Notification of an ongoing file download (with a progress bar, for example) should be stamped when the download started.</li>
313      *   <li>Notification of a completed file download should be stamped when the download finished.</li>
314      *   <li>Notification of an upcoming meeting should be stamped with the time the meeting will begin (that is, in the future).</li>
315      *   <li>Notification of an ongoing stopwatch (increasing timer) should be stamped with the watch's start time.
316      *   <li>Notification of an ongoing countdown timer should be stamped with the timer's end time.
317      * </ul>
318      *
319      * For apps targeting {@link android.os.Build.VERSION_CODES#N} and above, this time is not shown
320      * anymore by default and must be opted into by using
321      * {@link android.app.Notification.Builder#setShowWhen(boolean)}
322      */
323     public long when;
324 
325     /**
326      * The creation time of the notification
327      */
328     private long creationTime;
329 
330     /**
331      * The resource id of a drawable to use as the icon in the status bar.
332      *
333      * @deprecated Use {@link Builder#setSmallIcon(Icon)} instead.
334      */
335     @Deprecated
336     @DrawableRes
337     public int icon;
338 
339     /**
340      * If the icon in the status bar is to have more than one level, you can set this.  Otherwise,
341      * leave it at its default value of 0.
342      *
343      * @see android.widget.ImageView#setImageLevel
344      * @see android.graphics.drawable.Drawable#setLevel
345      */
346     public int iconLevel;
347 
348     /**
349      * The number of events that this notification represents. For example, in a new mail
350      * notification, this could be the number of unread messages.
351      *
352      * The system may or may not use this field to modify the appearance of the notification.
353      * Starting with {@link android.os.Build.VERSION_CODES#O}, the number may be displayed as a
354      * badge icon in Launchers that support badging.
355      */
356     public int number = 0;
357 
358     /**
359      * The intent to execute when the expanded status entry is clicked.  If
360      * this is an activity, it must include the
361      * {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK} flag, which requires
362      * that you take care of task management as described in the
363      * <a href="{@docRoot}guide/topics/fundamentals/tasks-and-back-stack.html">Tasks and Back
364      * Stack</a> document.  In particular, make sure to read the
365      * <a href="{@docRoot}/training/notify-user/navigation">Start
366      * an Activity from a Notification</a> page for the correct ways to launch an application from a
367      * notification.
368      */
369     public PendingIntent contentIntent;
370 
371     /**
372      * The intent to execute when the notification is explicitly dismissed by the user, either with
373      * the "Clear All" button or by swiping it away individually.
374      *
375      * This probably shouldn't be launching an activity since several of those will be sent
376      * at the same time.
377      */
378     public PendingIntent deleteIntent;
379 
380     /**
381      * An intent to launch instead of posting the notification to the status bar.
382      *
383      * <p>
384      * The system UI may choose to display a heads-up notification, instead of
385      * launching this intent, while the user is using the device.
386      * </p>
387      *
388      * @see Notification.Builder#setFullScreenIntent
389      */
390     public PendingIntent fullScreenIntent;
391 
392     /**
393      * Text that summarizes this notification for accessibility services.
394      *
395      * As of the L release, this text is no longer shown on screen, but it is still useful to
396      * accessibility services (where it serves as an audible announcement of the notification's
397      * appearance).
398      *
399      * @see #tickerView
400      */
401     public CharSequence tickerText;
402 
403     /**
404      * Formerly, a view showing the {@link #tickerText}.
405      *
406      * No longer displayed in the status bar as of API 21.
407      */
408     @Deprecated
409     public RemoteViews tickerView;
410 
411     /**
412      * The view that will represent this notification in the notification list (which is pulled
413      * down from the status bar).
414      *
415      * As of N, this field may be null. The notification view is determined by the inputs
416      * to {@link Notification.Builder}; a custom RemoteViews can optionally be
417      * supplied with {@link Notification.Builder#setCustomContentView(RemoteViews)}.
418      */
419     @Deprecated
420     public RemoteViews contentView;
421 
422     /**
423      * A large-format version of {@link #contentView}, giving the Notification an
424      * opportunity to show more detail. The system UI may choose to show this
425      * instead of the normal content view at its discretion.
426      *
427      * As of N, this field may be null. The expanded notification view is determined by the
428      * inputs to {@link Notification.Builder}; a custom RemoteViews can optionally be
429      * supplied with {@link Notification.Builder#setCustomBigContentView(RemoteViews)}.
430      */
431     @Deprecated
432     public RemoteViews bigContentView;
433 
434 
435     /**
436      * A medium-format version of {@link #contentView}, providing the Notification an
437      * opportunity to add action buttons to contentView. At its discretion, the system UI may
438      * choose to show this as a heads-up notification, which will pop up so the user can see
439      * it without leaving their current activity.
440      *
441      * As of N, this field may be null. The heads-up notification view is determined by the
442      * inputs to {@link Notification.Builder}; a custom RemoteViews can optionally be
443      * supplied with {@link Notification.Builder#setCustomHeadsUpContentView(RemoteViews)}.
444      */
445     @Deprecated
446     public RemoteViews headsUpContentView;
447 
448     private boolean mUsesStandardHeader;
449 
450     private static final ArraySet<Integer> STANDARD_LAYOUTS = new ArraySet<>();
451     static {
452         STANDARD_LAYOUTS.add(R.layout.notification_template_material_base);
453         STANDARD_LAYOUTS.add(R.layout.notification_template_material_heads_up_base);
454         STANDARD_LAYOUTS.add(R.layout.notification_template_material_big_base);
455         STANDARD_LAYOUTS.add(R.layout.notification_template_material_big_picture);
456         STANDARD_LAYOUTS.add(R.layout.notification_template_material_big_text);
457         STANDARD_LAYOUTS.add(R.layout.notification_template_material_inbox);
458         STANDARD_LAYOUTS.add(R.layout.notification_template_material_messaging);
459         STANDARD_LAYOUTS.add(R.layout.notification_template_material_big_messaging);
460         STANDARD_LAYOUTS.add(R.layout.notification_template_material_conversation);
461         STANDARD_LAYOUTS.add(R.layout.notification_template_material_media);
462         STANDARD_LAYOUTS.add(R.layout.notification_template_material_big_media);
463         STANDARD_LAYOUTS.add(R.layout.notification_template_material_call);
464         STANDARD_LAYOUTS.add(R.layout.notification_template_material_big_call);
465         STANDARD_LAYOUTS.add(R.layout.notification_template_header);
466     }
467 
468     /**
469      * A large bitmap to be shown in the notification content area.
470      *
471      * @deprecated Use {@link Builder#setLargeIcon(Icon)} instead.
472      */
473     @Deprecated
474     public Bitmap largeIcon;
475 
476     /**
477      * The sound to play.
478      *
479      * <p>
480      * A notification that is noisy is more likely to be presented as a heads-up notification.
481      * </p>
482      *
483      * <p>
484      * To play the default notification sound, see {@link #defaults}.
485      * </p>
486      * @deprecated use {@link NotificationChannel#getSound()}.
487      */
488     @Deprecated
489     public Uri sound;
490 
491     /**
492      * Use this constant as the value for audioStreamType to request that
493      * the default stream type for notifications be used.  Currently the
494      * default stream type is {@link AudioManager#STREAM_NOTIFICATION}.
495      *
496      * @deprecated Use {@link NotificationChannel#getAudioAttributes()} instead.
497      */
498     @Deprecated
499     public static final int STREAM_DEFAULT = -1;
500 
501     /**
502      * The audio stream type to use when playing the sound.
503      * Should be one of the STREAM_ constants from
504      * {@link android.media.AudioManager}.
505      *
506      * @deprecated Use {@link #audioAttributes} instead.
507      */
508     @Deprecated
509     public int audioStreamType = STREAM_DEFAULT;
510 
511     /**
512      * The default value of {@link #audioAttributes}.
513      */
514     public static final AudioAttributes AUDIO_ATTRIBUTES_DEFAULT = new AudioAttributes.Builder()
515             .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
516             .setUsage(AudioAttributes.USAGE_NOTIFICATION)
517             .build();
518 
519     /**
520      * The {@link AudioAttributes audio attributes} to use when playing the sound.
521      *
522      * @deprecated use {@link NotificationChannel#getAudioAttributes()} instead.
523      */
524     @Deprecated
525     public AudioAttributes audioAttributes = AUDIO_ATTRIBUTES_DEFAULT;
526 
527     /**
528      * The pattern with which to vibrate.
529      *
530      * <p>
531      * To vibrate the default pattern, see {@link #defaults}.
532      * </p>
533      *
534      * @see android.os.Vibrator#vibrate(long[],int)
535      * @deprecated use {@link NotificationChannel#getVibrationPattern()}.
536      */
537     @Deprecated
538     public long[] vibrate;
539 
540     /**
541      * The color of the led.  The hardware will do its best approximation.
542      *
543      * @see #FLAG_SHOW_LIGHTS
544      * @see #flags
545      * @deprecated use {@link NotificationChannel#shouldShowLights()}.
546      */
547     @ColorInt
548     @Deprecated
549     public int ledARGB;
550 
551     /**
552      * The number of milliseconds for the LED to be on while it's flashing.
553      * The hardware will do its best approximation.
554      *
555      * @see #FLAG_SHOW_LIGHTS
556      * @see #flags
557      * @deprecated use {@link NotificationChannel#shouldShowLights()}.
558      */
559     @Deprecated
560     public int ledOnMS;
561 
562     /**
563      * The number of milliseconds for the LED to be off while it's flashing.
564      * The hardware will do its best approximation.
565      *
566      * @see #FLAG_SHOW_LIGHTS
567      * @see #flags
568      *
569      * @deprecated use {@link NotificationChannel#shouldShowLights()}.
570      */
571     @Deprecated
572     public int ledOffMS;
573 
574     /**
575      * Specifies which values should be taken from the defaults.
576      * <p>
577      * To set, OR the desired from {@link #DEFAULT_SOUND},
578      * {@link #DEFAULT_VIBRATE}, {@link #DEFAULT_LIGHTS}. For all default
579      * values, use {@link #DEFAULT_ALL}.
580      * </p>
581      *
582      * @deprecated use {@link NotificationChannel#getSound()} and
583      * {@link NotificationChannel#shouldShowLights()} and
584      * {@link NotificationChannel#shouldVibrate()}.
585      */
586     @Deprecated
587     public int defaults;
588 
589     /**
590      * Bit to be bitwise-ored into the {@link #flags} field that should be
591      * set if you want the LED on for this notification.
592      * <ul>
593      * <li>To turn the LED off, pass 0 in the alpha channel for colorARGB
594      *      or 0 for both ledOnMS and ledOffMS.</li>
595      * <li>To turn the LED on, pass 1 for ledOnMS and 0 for ledOffMS.</li>
596      * <li>To flash the LED, pass the number of milliseconds that it should
597      *      be on and off to ledOnMS and ledOffMS.</li>
598      * </ul>
599      * <p>
600      * Since hardware varies, you are not guaranteed that any of the values
601      * you pass are honored exactly.  Use the system defaults if possible
602      * because they will be set to values that work on any given hardware.
603      * <p>
604      * The alpha channel must be set for forward compatibility.
605      *
606      * @deprecated use {@link NotificationChannel#shouldShowLights()}.
607      */
608     @Deprecated
609     public static final int FLAG_SHOW_LIGHTS        = 0x00000001;
610 
611     /**
612      * Bit to be bitwise-ored into the {@link #flags} field that should be
613      * set if this notification is in reference to something that is ongoing,
614      * like a phone call.  It should not be set if this notification is in
615      * reference to something that happened at a particular point in time,
616      * like a missed phone call.
617      */
618     public static final int FLAG_ONGOING_EVENT      = 0x00000002;
619 
620     /**
621      * Bit to be bitwise-ored into the {@link #flags} field that if set,
622      * the audio will be repeated until the notification is
623      * cancelled or the notification window is opened.
624      */
625     public static final int FLAG_INSISTENT          = 0x00000004;
626 
627     /**
628      * Bit to be bitwise-ored into the {@link #flags} field that should be
629      * set if you would only like the sound, vibrate and ticker to be played
630      * if the notification was not already showing.
631      *
632      * Note that using this flag will stop any ongoing alerting behaviour such
633      * as sound, vibration or blinking notification LED.
634      */
635     public static final int FLAG_ONLY_ALERT_ONCE    = 0x00000008;
636 
637     /**
638      * Bit to be bitwise-ored into the {@link #flags} field that should be
639      * set if the notification should be canceled when it is clicked by the
640      * user.
641      */
642     public static final int FLAG_AUTO_CANCEL        = 0x00000010;
643 
644     /**
645      * Bit to be bitwise-ored into the {@link #flags} field that should be
646      * set if the notification should not be canceled when the user clicks
647      * the Clear all button.
648      */
649     public static final int FLAG_NO_CLEAR           = 0x00000020;
650 
651     /**
652      * Bit to be bitwise-ored into the {@link #flags} field that should be
653      * set if this notification represents a currently running service.  This
654      * will normally be set for you by {@link Service#startForeground}.
655      */
656     public static final int FLAG_FOREGROUND_SERVICE = 0x00000040;
657 
658     /**
659      * Obsolete flag indicating high-priority notifications; use the priority field instead.
660      *
661      * @deprecated Use {@link #priority} with a positive value.
662      */
663     @Deprecated
664     public static final int FLAG_HIGH_PRIORITY      = 0x00000080;
665 
666     /**
667      * Bit to be bitswise-ored into the {@link #flags} field that should be
668      * set if this notification is relevant to the current device only
669      * and it is not recommended that it bridge to other devices.
670      */
671     public static final int FLAG_LOCAL_ONLY         = 0x00000100;
672 
673     /**
674      * Bit to be bitswise-ored into the {@link #flags} field that should be
675      * set if this notification is the group summary for a group of notifications.
676      * Grouped notifications may display in a cluster or stack on devices which
677      * support such rendering. Requires a group key also be set using {@link Builder#setGroup}.
678      */
679     public static final int FLAG_GROUP_SUMMARY      = 0x00000200;
680 
681     /**
682      * Bit to be bitswise-ored into the {@link #flags} field that should be
683      * set if this notification is the group summary for an auto-group of notifications.
684      *
685      * @hide
686      */
687     @SystemApi
688     public static final int FLAG_AUTOGROUP_SUMMARY  = 0x00000400;
689 
690     /**
691      * @hide
692      */
693     public static final int FLAG_CAN_COLORIZE = 0x00000800;
694 
695     /**
696      * Bit to be bitswised-ored into the {@link #flags} field that should be
697      * set by the system if this notification is showing as a bubble.
698      *
699      * Applications cannot set this flag directly; they should instead call
700      * {@link Notification.Builder#setBubbleMetadata(BubbleMetadata)} to
701      * request that a notification be displayed as a bubble, and then check
702      * this flag to see whether that request was honored by the system.
703      */
704     public static final int FLAG_BUBBLE = 0x00001000;
705 
706     /**
707      * Bit to be bitswised-ored into the {@link #flags} field that should be
708      * set by the system if this notification is not dismissible.
709      *
710      * This flag is for internal use only; applications cannot set this flag directly.
711      * @hide
712      */
713     public static final int FLAG_NO_DISMISS = 0x00002000;
714 
715     /**
716      * Bit to be bitwise-ORed into the {@link #flags} field that should be
717      * set by the system if the app that sent this notification does not have the permission to send
718      * full screen intents.
719      *
720      * This flag is for internal use only; applications cannot set this flag directly.
721      * @hide
722      */
723     public static final int FLAG_FSI_REQUESTED_BUT_DENIED = 0x00004000;
724 
725     /**
726      * Bit to be bitwise-ored into the {@link #flags} field that should be
727      * set if this notification represents a currently running user-initiated job.
728      *
729      * This flag is for internal use only; applications cannot set this flag directly.
730      * @hide
731      */
732     @TestApi
733     public static final int FLAG_USER_INITIATED_JOB = 0x00008000;
734 
735     private static final List<Class<? extends Style>> PLATFORM_STYLE_CLASSES = Arrays.asList(
736             BigTextStyle.class, BigPictureStyle.class, InboxStyle.class, MediaStyle.class,
737             DecoratedCustomViewStyle.class, DecoratedMediaCustomViewStyle.class,
738             MessagingStyle.class, CallStyle.class);
739 
740     /** @hide */
741     @IntDef(flag = true, prefix = { "FLAG_" }, value = {FLAG_SHOW_LIGHTS, FLAG_ONGOING_EVENT,
742             FLAG_INSISTENT, FLAG_ONLY_ALERT_ONCE,
743             FLAG_AUTO_CANCEL, FLAG_NO_CLEAR, FLAG_FOREGROUND_SERVICE, FLAG_HIGH_PRIORITY,
744             FLAG_LOCAL_ONLY, FLAG_GROUP_SUMMARY, FLAG_AUTOGROUP_SUMMARY, FLAG_BUBBLE,
745             FLAG_USER_INITIATED_JOB})
746     @Retention(RetentionPolicy.SOURCE)
747     public @interface NotificationFlags{};
748 
749     public int flags;
750 
751     /** @hide */
752     @IntDef(prefix = { "PRIORITY_" }, value = {
753             PRIORITY_DEFAULT,
754             PRIORITY_LOW,
755             PRIORITY_MIN,
756             PRIORITY_HIGH,
757             PRIORITY_MAX
758     })
759     @Retention(RetentionPolicy.SOURCE)
760     public @interface Priority {}
761 
762     /**
763      * Default notification {@link #priority}. If your application does not prioritize its own
764      * notifications, use this value for all notifications.
765      *
766      * @deprecated use {@link NotificationManager#IMPORTANCE_DEFAULT} instead.
767      */
768     @Deprecated
769     public static final int PRIORITY_DEFAULT = 0;
770 
771     /**
772      * Lower {@link #priority}, for items that are less important. The UI may choose to show these
773      * items smaller, or at a different position in the list, compared with your app's
774      * {@link #PRIORITY_DEFAULT} items.
775      *
776      * @deprecated use {@link NotificationManager#IMPORTANCE_LOW} instead.
777      */
778     @Deprecated
779     public static final int PRIORITY_LOW = -1;
780 
781     /**
782      * Lowest {@link #priority}; these items might not be shown to the user except under special
783      * circumstances, such as detailed notification logs.
784      *
785      * @deprecated use {@link NotificationManager#IMPORTANCE_MIN} instead.
786      */
787     @Deprecated
788     public static final int PRIORITY_MIN = -2;
789 
790     /**
791      * Higher {@link #priority}, for more important notifications or alerts. The UI may choose to
792      * show these items larger, or at a different position in notification lists, compared with
793      * your app's {@link #PRIORITY_DEFAULT} items.
794      *
795      * @deprecated use {@link NotificationManager#IMPORTANCE_HIGH} instead.
796      */
797     @Deprecated
798     public static final int PRIORITY_HIGH = 1;
799 
800     /**
801      * Highest {@link #priority}, for your application's most important items that require the
802      * user's prompt attention or input.
803      *
804      * @deprecated use {@link NotificationManager#IMPORTANCE_HIGH} instead.
805      */
806     @Deprecated
807     public static final int PRIORITY_MAX = 2;
808 
809     /**
810      * Relative priority for this notification.
811      *
812      * Priority is an indication of how much of the user's valuable attention should be consumed by
813      * this notification. Low-priority notifications may be hidden from the user in certain
814      * situations, while the user might be interrupted for a higher-priority notification. The
815      * system will make a determination about how to interpret this priority when presenting
816      * the notification.
817      *
818      * <p>
819      * A notification that is at least {@link #PRIORITY_HIGH} is more likely to be presented
820      * as a heads-up notification.
821      * </p>
822      *
823      * @deprecated use {@link NotificationChannel#getImportance()} instead.
824      */
825     @Priority
826     @Deprecated
827     public int priority;
828 
829     /**
830      * Accent color (an ARGB integer like the constants in {@link android.graphics.Color})
831      * to be applied by the standard Style templates when presenting this notification.
832      *
833      * The current template design constructs a colorful header image by overlaying the
834      * {@link #icon} image (stenciled in white) atop a field of this color. Alpha components are
835      * ignored.
836      */
837     @ColorInt
838     public int color = COLOR_DEFAULT;
839 
840     /**
841      * Special value of {@link #color} telling the system not to decorate this notification with
842      * any special color but instead use default colors when presenting this notification.
843      */
844     @ColorInt
845     public static final int COLOR_DEFAULT = 0; // AKA Color.TRANSPARENT
846 
847     /**
848      * Special value of {@link #color} used as a place holder for an invalid color.
849      * @hide
850      */
851     @ColorInt
852     public static final int COLOR_INVALID = 1;
853 
854     /**
855      * Sphere of visibility of this notification, which affects how and when the SystemUI reveals
856      * the notification's presence and contents in untrusted situations (namely, on the secure
857      * lockscreen).
858      *
859      * The default level, {@link #VISIBILITY_PRIVATE}, behaves exactly as notifications have always
860      * done on Android: The notification's {@link #icon} and {@link #tickerText} (if available) are
861      * shown in all situations, but the contents are only available if the device is unlocked for
862      * the appropriate user.
863      *
864      * A more permissive policy can be expressed by {@link #VISIBILITY_PUBLIC}; such a notification
865      * can be read even in an "insecure" context (that is, above a secure lockscreen).
866      * To modify the public version of this notification—for example, to redact some portions—see
867      * {@link Builder#setPublicVersion(Notification)}.
868      *
869      * Finally, a notification can be made {@link #VISIBILITY_SECRET}, which will suppress its icon
870      * and ticker until the user has bypassed the lockscreen.
871      */
872     public @Visibility int visibility;
873 
874     /** @hide */
875     @IntDef(prefix = { "VISIBILITY_" }, value = {
876             VISIBILITY_PUBLIC,
877             VISIBILITY_PRIVATE,
878             VISIBILITY_SECRET,
879     })
880     @Retention(RetentionPolicy.SOURCE)
881     public @interface Visibility {}
882 
883     /**
884      * Notification visibility: Show this notification in its entirety on all lockscreens.
885      *
886      * {@see #visibility}
887      */
888     public static final int VISIBILITY_PUBLIC = 1;
889 
890     /**
891      * Notification visibility: Show this notification on all lockscreens, but conceal sensitive or
892      * private information on secure lockscreens.
893      *
894      * {@see #visibility}
895      */
896     public static final int VISIBILITY_PRIVATE = 0;
897 
898     /**
899      * Notification visibility: Do not reveal any part of this notification on a secure lockscreen.
900      *
901      * {@see #visibility}
902      */
903     public static final int VISIBILITY_SECRET = -1;
904 
905     /**
906      * @hide
907      */
908     @IntDef(prefix = "VISIBILITY_", value = {
909             VISIBILITY_PUBLIC,
910             VISIBILITY_PRIVATE,
911             VISIBILITY_SECRET,
912             NotificationManager.VISIBILITY_NO_OVERRIDE
913     })
914     public @interface NotificationVisibilityOverride{};
915 
916     /**
917      * Notification category: incoming call (voice or video) or similar synchronous communication request.
918      */
919     public static final String CATEGORY_CALL = "call";
920 
921     /**
922      * Notification category: map turn-by-turn navigation.
923      */
924     public static final String CATEGORY_NAVIGATION = "navigation";
925 
926     /**
927      * Notification category: incoming direct message (SMS, instant message, etc.).
928      */
929     public static final String CATEGORY_MESSAGE = "msg";
930 
931     /**
932      * Notification category: asynchronous bulk message (email).
933      */
934     public static final String CATEGORY_EMAIL = "email";
935 
936     /**
937      * Notification category: calendar event.
938      */
939     public static final String CATEGORY_EVENT = "event";
940 
941     /**
942      * Notification category: promotion or advertisement.
943      */
944     public static final String CATEGORY_PROMO = "promo";
945 
946     /**
947      * Notification category: alarm or timer.
948      */
949     public static final String CATEGORY_ALARM = "alarm";
950 
951     /**
952      * Notification category: progress of a long-running background operation.
953      */
954     public static final String CATEGORY_PROGRESS = "progress";
955 
956     /**
957      * Notification category: social network or sharing update.
958      */
959     public static final String CATEGORY_SOCIAL = "social";
960 
961     /**
962      * Notification category: error in background operation or authentication status.
963      */
964     public static final String CATEGORY_ERROR = "err";
965 
966     /**
967      * Notification category: media transport control for playback.
968      */
969     public static final String CATEGORY_TRANSPORT = "transport";
970 
971     /**
972      * Notification category: system or device status update.  Reserved for system use.
973      */
974     public static final String CATEGORY_SYSTEM = "sys";
975 
976     /**
977      * Notification category: indication of running background service.
978      */
979     public static final String CATEGORY_SERVICE = "service";
980 
981     /**
982      * Notification category: a specific, timely recommendation for a single thing.
983      * For example, a news app might want to recommend a news story it believes the user will
984      * want to read next.
985      */
986     public static final String CATEGORY_RECOMMENDATION = "recommendation";
987 
988     /**
989      * Notification category: ongoing information about device or contextual status.
990      */
991     public static final String CATEGORY_STATUS = "status";
992 
993     /**
994      * Notification category: user-scheduled reminder.
995      */
996     public static final String CATEGORY_REMINDER = "reminder";
997 
998     /**
999      * Notification category: extreme car emergencies.
1000      * @hide
1001      */
1002     @SystemApi
1003     public static final String CATEGORY_CAR_EMERGENCY = "car_emergency";
1004 
1005     /**
1006      * Notification category: car warnings.
1007      * @hide
1008      */
1009     @SystemApi
1010     public static final String CATEGORY_CAR_WARNING = "car_warning";
1011 
1012     /**
1013      * Notification category: general car system information.
1014      * @hide
1015      */
1016     @SystemApi
1017     public static final String CATEGORY_CAR_INFORMATION = "car_information";
1018 
1019     /**
1020      * Notification category: tracking a user's workout.
1021      */
1022     public static final String CATEGORY_WORKOUT = "workout";
1023 
1024     /**
1025      * Notification category: temporarily sharing location.
1026      */
1027     public static final String CATEGORY_LOCATION_SHARING = "location_sharing";
1028 
1029     /**
1030      * Notification category: running stopwatch.
1031      */
1032     public static final String CATEGORY_STOPWATCH = "stopwatch";
1033 
1034     /**
1035      * Notification category: missed call.
1036      */
1037     public static final String CATEGORY_MISSED_CALL = "missed_call";
1038 
1039     /**
1040      * One of the predefined notification categories (see the <code>CATEGORY_*</code> constants)
1041      * that best describes this Notification.  May be used by the system for ranking and filtering.
1042      */
1043     public String category;
1044 
1045     @UnsupportedAppUsage
1046     private String mGroupKey;
1047 
1048     /**
1049      * Get the key used to group this notification into a cluster or stack
1050      * with other notifications on devices which support such rendering.
1051      */
getGroup()1052     public String getGroup() {
1053         return mGroupKey;
1054     }
1055 
1056     private String mSortKey;
1057 
1058     /**
1059      * Get a sort key that orders this notification among other notifications from the
1060      * same package. This can be useful if an external sort was already applied and an app
1061      * would like to preserve this. Notifications will be sorted lexicographically using this
1062      * value, although providing different priorities in addition to providing sort key may
1063      * cause this value to be ignored.
1064      *
1065      * <p>This sort key can also be used to order members of a notification group. See
1066      * {@link Builder#setGroup}.
1067      *
1068      * @see String#compareTo(String)
1069      */
getSortKey()1070     public String getSortKey() {
1071         return mSortKey;
1072     }
1073 
1074     /**
1075      * Additional semantic data to be carried around with this Notification.
1076      * <p>
1077      * The extras keys defined here are intended to capture the original inputs to {@link Builder}
1078      * APIs, and are intended to be used by
1079      * {@link android.service.notification.NotificationListenerService} implementations to extract
1080      * detailed information from notification objects.
1081      */
1082     public Bundle extras = new Bundle();
1083 
1084     /**
1085      * All pending intents in the notification as the system needs to be able to access them but
1086      * touching the extras bundle in the system process is not safe because the bundle may contain
1087      * custom parcelable objects.
1088      *
1089      * @hide
1090      */
1091     @UnsupportedAppUsage
1092     public ArraySet<PendingIntent> allPendingIntents;
1093 
1094     /**
1095      * Token identifying the notification that is applying doze/bgcheck allowlisting to the
1096      * pending intents inside of it, so only those will get the behavior.
1097      *
1098      * @hide
1099      */
1100     private IBinder mAllowlistToken;
1101 
1102     /**
1103      * Must be set by a process to start associating tokens with Notification objects
1104      * coming in to it.  This is set by NotificationManagerService.
1105      *
1106      * @hide
1107      */
1108     static public IBinder processAllowlistToken;
1109 
1110     /**
1111      * {@link #extras} key: this is the title of the notification,
1112      * as supplied to {@link Builder#setContentTitle(CharSequence)}.
1113      */
1114     public static final String EXTRA_TITLE = "android.title";
1115 
1116     /**
1117      * {@link #extras} key: this is the title of the notification when shown in expanded form,
1118      * e.g. as supplied to {@link BigTextStyle#setBigContentTitle(CharSequence)}.
1119      */
1120     public static final String EXTRA_TITLE_BIG = EXTRA_TITLE + ".big";
1121 
1122     /**
1123      * {@link #extras} key: this is the main text payload, as supplied to
1124      * {@link Builder#setContentText(CharSequence)}.
1125      */
1126     public static final String EXTRA_TEXT = "android.text";
1127 
1128     /**
1129      * {@link #extras} key: this is a third line of text, as supplied to
1130      * {@link Builder#setSubText(CharSequence)}.
1131      */
1132     public static final String EXTRA_SUB_TEXT = "android.subText";
1133 
1134     /**
1135      * {@link #extras} key: this is the remote input history, as supplied to
1136      * {@link Builder#setRemoteInputHistory(CharSequence[])}.
1137      *
1138      * Apps can fill this through {@link Builder#setRemoteInputHistory(CharSequence[])}
1139      * with the most recent inputs that have been sent through a {@link RemoteInput} of this
1140      * Notification and are expected to clear it once the it is no longer relevant (e.g. for chat
1141      * notifications once the other party has responded).
1142      *
1143      * The extra with this key is of type CharSequence[] and contains the most recent entry at
1144      * the 0 index, the second most recent at the 1 index, etc.
1145      *
1146      * @see Builder#setRemoteInputHistory(CharSequence[])
1147      */
1148     public static final String EXTRA_REMOTE_INPUT_HISTORY = "android.remoteInputHistory";
1149 
1150 
1151     /**
1152      * {@link #extras} key: this is a remote input history which can include media messages
1153      * in addition to text, as supplied to
1154      * {@link Builder#setRemoteInputHistory(RemoteInputHistoryItem[])} or
1155      * {@link Builder#setRemoteInputHistory(CharSequence[])}.
1156      *
1157      * SystemUI can populate this through
1158      * {@link Builder#setRemoteInputHistory(RemoteInputHistoryItem[])} with the most recent inputs
1159      * that have been sent through a {@link RemoteInput} of this Notification. These items can
1160      * represent either media content (specified by a URI and a MIME type) or a text message
1161      * (described by a CharSequence).
1162      *
1163      * To maintain compatibility, this can also be set by apps with
1164      * {@link Builder#setRemoteInputHistory(CharSequence[])}, which will create a
1165      * {@link RemoteInputHistoryItem} for each of the provided text-only messages.
1166      *
1167      * The extra with this key is of type {@link RemoteInputHistoryItem[]} and contains the most
1168      * recent entry at the 0 index, the second most recent at the 1 index, etc.
1169      *
1170      * @see Builder#setRemoteInputHistory(RemoteInputHistoryItem[])
1171      * @hide
1172      */
1173     public static final String EXTRA_REMOTE_INPUT_HISTORY_ITEMS = "android.remoteInputHistoryItems";
1174 
1175     /**
1176      * {@link #extras} key: boolean as supplied to
1177      * {@link Builder#setShowRemoteInputSpinner(boolean)}.
1178      *
1179      * If set to true, then the view displaying the remote input history from
1180      * {@link Builder#setRemoteInputHistory(CharSequence[])} will have a progress spinner.
1181      *
1182      * @see Builder#setShowRemoteInputSpinner(boolean)
1183      * @hide
1184      */
1185     public static final String EXTRA_SHOW_REMOTE_INPUT_SPINNER = "android.remoteInputSpinner";
1186 
1187     /**
1188      * {@link #extras} key: boolean as supplied to
1189      * {@link Builder#setHideSmartReplies(boolean)}.
1190      *
1191      * If set to true, then any smart reply buttons will be hidden.
1192      *
1193      * @see Builder#setHideSmartReplies(boolean)
1194      * @hide
1195      */
1196     public static final String EXTRA_HIDE_SMART_REPLIES = "android.hideSmartReplies";
1197 
1198     /**
1199      * {@link #extras} key: this is a small piece of additional text as supplied to
1200      * {@link Builder#setContentInfo(CharSequence)}.
1201      */
1202     public static final String EXTRA_INFO_TEXT = "android.infoText";
1203 
1204     /**
1205      * {@link #extras} key: this is a line of summary information intended to be shown
1206      * alongside expanded notifications, as supplied to (e.g.)
1207      * {@link BigTextStyle#setSummaryText(CharSequence)}.
1208      */
1209     public static final String EXTRA_SUMMARY_TEXT = "android.summaryText";
1210 
1211     /**
1212      * {@link #extras} key: this is the longer text shown in the big form of a
1213      * {@link BigTextStyle} notification, as supplied to
1214      * {@link BigTextStyle#bigText(CharSequence)}.
1215      */
1216     public static final String EXTRA_BIG_TEXT = "android.bigText";
1217 
1218     /**
1219      * {@link #extras} key: this is the resource ID of the notification's main small icon, as
1220      * supplied to {@link Builder#setSmallIcon(int)}.
1221      *
1222      * @deprecated Use {@link #getSmallIcon()}, which supports a wider variety of icon sources.
1223      */
1224     @Deprecated
1225     public static final String EXTRA_SMALL_ICON = "android.icon";
1226 
1227     /**
1228      * {@link #extras} key: this is a bitmap to be used instead of the small icon when showing the
1229      * notification payload, as
1230      * supplied to {@link Builder#setLargeIcon(android.graphics.Bitmap)}.
1231      *
1232      * @deprecated Use {@link #getLargeIcon()}, which supports a wider variety of icon sources.
1233      */
1234     @Deprecated
1235     public static final String EXTRA_LARGE_ICON = "android.largeIcon";
1236 
1237     /**
1238      * {@link #extras} key: this is a bitmap to be used instead of the one from
1239      * {@link Builder#setLargeIcon(android.graphics.Bitmap)} when the notification is
1240      * shown in its expanded form, as supplied to
1241      * {@link BigPictureStyle#bigLargeIcon(android.graphics.Bitmap)}.
1242      */
1243     public static final String EXTRA_LARGE_ICON_BIG = EXTRA_LARGE_ICON + ".big";
1244 
1245     /**
1246      * {@link #extras} key: this is the progress value supplied to
1247      * {@link Builder#setProgress(int, int, boolean)}.
1248      */
1249     public static final String EXTRA_PROGRESS = "android.progress";
1250 
1251     /**
1252      * {@link #extras} key: this is the maximum value supplied to
1253      * {@link Builder#setProgress(int, int, boolean)}.
1254      */
1255     public static final String EXTRA_PROGRESS_MAX = "android.progressMax";
1256 
1257     /**
1258      * {@link #extras} key: whether the progress bar is indeterminate, supplied to
1259      * {@link Builder#setProgress(int, int, boolean)}.
1260      */
1261     public static final String EXTRA_PROGRESS_INDETERMINATE = "android.progressIndeterminate";
1262 
1263     /**
1264      * {@link #extras} key: whether {@link #when} should be shown as a count-up timer (specifically
1265      * a {@link android.widget.Chronometer}) instead of a timestamp, as supplied to
1266      * {@link Builder#setUsesChronometer(boolean)}.
1267      */
1268     public static final String EXTRA_SHOW_CHRONOMETER = "android.showChronometer";
1269 
1270     /**
1271      * {@link #extras} key: whether the chronometer set on the notification should count down
1272      * instead of counting up. Is only relevant if key {@link #EXTRA_SHOW_CHRONOMETER} is present.
1273      * This extra is a boolean. The default is false.
1274      */
1275     public static final String EXTRA_CHRONOMETER_COUNT_DOWN = "android.chronometerCountDown";
1276 
1277     /**
1278      * {@link #extras} key: whether {@link #when} should be shown,
1279      * as supplied to {@link Builder#setShowWhen(boolean)}.
1280      */
1281     public static final String EXTRA_SHOW_WHEN = "android.showWhen";
1282 
1283     /**
1284      * {@link #extras} key: this is a bitmap to be shown in {@link BigPictureStyle} expanded
1285      * notifications, supplied to {@link BigPictureStyle#bigPicture(android.graphics.Bitmap)}.
1286      */
1287     public static final String EXTRA_PICTURE = "android.picture";
1288 
1289     /**
1290      * {@link #extras} key: this is an {@link Icon} of an image to be
1291      * shown in {@link BigPictureStyle} expanded notifications, supplied to
1292      * {@link BigPictureStyle#bigPicture(Icon)}.
1293      */
1294     public static final String EXTRA_PICTURE_ICON = "android.pictureIcon";
1295 
1296     /**
1297      * {@link #extras} key: this is a content description of the big picture supplied from
1298      * {@link BigPictureStyle#bigPicture(Bitmap)}, supplied to
1299      * {@link BigPictureStyle#setContentDescription(CharSequence)}.
1300      */
1301     public static final String EXTRA_PICTURE_CONTENT_DESCRIPTION =
1302             "android.pictureContentDescription";
1303 
1304     /**
1305      * {@link #extras} key: this is a boolean to indicate that the
1306      * {@link BigPictureStyle#bigPicture(Bitmap) big picture} is to be shown in the collapsed state
1307      * of a {@link BigPictureStyle} notification.  This will replace a
1308      * {@link Builder#setLargeIcon(Icon) large icon} in that state if one was provided.
1309      */
1310     public static final String EXTRA_SHOW_BIG_PICTURE_WHEN_COLLAPSED =
1311             "android.showBigPictureWhenCollapsed";
1312 
1313     /**
1314      * {@link #extras} key: An array of CharSequences to show in {@link InboxStyle} expanded
1315      * notifications, each of which was supplied to {@link InboxStyle#addLine(CharSequence)}.
1316      */
1317     public static final String EXTRA_TEXT_LINES = "android.textLines";
1318 
1319     /**
1320      * {@link #extras} key: A string representing the name of the specific
1321      * {@link android.app.Notification.Style} used to create this notification.
1322      */
1323     public static final String EXTRA_TEMPLATE = "android.template";
1324 
1325     /**
1326      * {@link #extras} key: A String array containing the people that this notification relates to,
1327      * each of which was supplied to {@link Builder#addPerson(String)}.
1328      *
1329      * @deprecated the actual objects are now in {@link #EXTRA_PEOPLE_LIST}
1330      */
1331     public static final String EXTRA_PEOPLE = "android.people";
1332 
1333     /**
1334      * {@link #extras} key: An arrayList of {@link Person} objects containing the people that
1335      * this notification relates to.
1336      */
1337     public static final String EXTRA_PEOPLE_LIST = "android.people.list";
1338 
1339     /**
1340      * Allow certain system-generated notifications to appear before the device is provisioned.
1341      * Only available to notifications coming from the android package.
1342      * @hide
1343      */
1344     @SystemApi
1345     @RequiresPermission(android.Manifest.permission.NOTIFICATION_DURING_SETUP)
1346     public static final String EXTRA_ALLOW_DURING_SETUP = "android.allowDuringSetup";
1347 
1348     /**
1349      * {@link #extras} key:
1350      * flat {@link String} representation of a {@link android.content.ContentUris content URI}
1351      * pointing to an image that can be displayed in the background when the notification is
1352      * selected. Used on television platforms. The URI must point to an image stream suitable for
1353      * passing into {@link android.graphics.BitmapFactory#decodeStream(java.io.InputStream)
1354      * BitmapFactory.decodeStream}; all other content types will be ignored.
1355      */
1356     public static final String EXTRA_BACKGROUND_IMAGE_URI = "android.backgroundImageUri";
1357 
1358     /**
1359      * {@link #extras} key: A
1360      * {@link android.media.session.MediaSession.Token} associated with a
1361      * {@link android.app.Notification.MediaStyle} notification.
1362      */
1363     public static final String EXTRA_MEDIA_SESSION = "android.mediaSession";
1364 
1365     /**
1366      * {@link #extras} key: A {@code CharSequence} name of a remote device used for a media session
1367      * associated with a {@link Notification.MediaStyle} notification. This will show in the media
1368      * controls output switcher instead of the local device name.
1369      * @hide
1370      */
1371     @TestApi
1372     public static final String EXTRA_MEDIA_REMOTE_DEVICE = "android.mediaRemoteDevice";
1373 
1374     /**
1375      * {@link #extras} key: A {@code int} resource ID for an icon that should show in the output
1376      * switcher of the media controls for a {@link Notification.MediaStyle} notification.
1377      * @hide
1378      */
1379     @TestApi
1380     public static final String EXTRA_MEDIA_REMOTE_ICON = "android.mediaRemoteIcon";
1381 
1382     /**
1383      * {@link #extras} key: A {@code PendingIntent} that will replace the default action for the
1384      * media controls output switcher chip, associated with a {@link Notification.MediaStyle}
1385      * notification. This should launch an activity.
1386      * @hide
1387      */
1388     @TestApi
1389     public static final String EXTRA_MEDIA_REMOTE_INTENT = "android.mediaRemoteIntent";
1390 
1391     /**
1392      * {@link #extras} key: the indices of actions to be shown in the compact view,
1393      * as supplied to (e.g.) {@link MediaStyle#setShowActionsInCompactView(int...)}.
1394      */
1395     public static final String EXTRA_COMPACT_ACTIONS = "android.compactActions";
1396 
1397     /**
1398      * {@link #extras} key: the username to be displayed for all messages sent by the user including
1399      * direct replies
1400      * {@link android.app.Notification.MessagingStyle} notification. This extra is a
1401      * {@link CharSequence}
1402      *
1403      * @deprecated use {@link #EXTRA_MESSAGING_PERSON}
1404      */
1405     public static final String EXTRA_SELF_DISPLAY_NAME = "android.selfDisplayName";
1406 
1407     /**
1408      * {@link #extras} key: the person to be displayed for all messages sent by the user including
1409      * direct replies
1410      * {@link android.app.Notification.MessagingStyle} notification. This extra is a
1411      * {@link Person}
1412      */
1413     public static final String EXTRA_MESSAGING_PERSON = "android.messagingUser";
1414 
1415     /**
1416      * {@link #extras} key: a {@link CharSequence} to be displayed as the title to a conversation
1417      * represented by a {@link android.app.Notification.MessagingStyle}
1418      */
1419     public static final String EXTRA_CONVERSATION_TITLE = "android.conversationTitle";
1420 
1421     /** @hide */
1422     public static final String EXTRA_CONVERSATION_ICON = "android.conversationIcon";
1423 
1424     /** @hide */
1425     public static final String EXTRA_CONVERSATION_UNREAD_MESSAGE_COUNT =
1426             "android.conversationUnreadMessageCount";
1427 
1428     /**
1429      * {@link #extras} key: an array of {@link android.app.Notification.MessagingStyle.Message}
1430      * bundles provided by a
1431      * {@link android.app.Notification.MessagingStyle} notification. This extra is a parcelable
1432      * array of bundles.
1433      */
1434     public static final String EXTRA_MESSAGES = "android.messages";
1435 
1436     /**
1437      * {@link #extras} key: an array of
1438      * {@link android.app.Notification.MessagingStyle#addHistoricMessage historic}
1439      * {@link android.app.Notification.MessagingStyle.Message} bundles provided by a
1440      * {@link android.app.Notification.MessagingStyle} notification. This extra is a parcelable
1441      * array of bundles.
1442      */
1443     public static final String EXTRA_HISTORIC_MESSAGES = "android.messages.historic";
1444 
1445     /**
1446      * {@link #extras} key: whether the {@link android.app.Notification.MessagingStyle} notification
1447      * represents a group conversation.
1448      */
1449     public static final String EXTRA_IS_GROUP_CONVERSATION = "android.isGroupConversation";
1450 
1451     /**
1452      * {@link #extras} key: the type of call represented by the
1453      * {@link android.app.Notification.CallStyle} notification. This extra is an int.
1454      */
1455     public static final String EXTRA_CALL_TYPE = "android.callType";
1456 
1457     /**
1458      * {@link #extras} key: whether the  {@link android.app.Notification.CallStyle} notification
1459      * is for a call that will activate video when answered. This extra is a boolean.
1460      */
1461     public static final String EXTRA_CALL_IS_VIDEO = "android.callIsVideo";
1462 
1463     /**
1464      * {@link #extras} key: the person to be displayed as calling for the
1465      * {@link android.app.Notification.CallStyle} notification. This extra is a {@link Person}.
1466      */
1467     public static final String EXTRA_CALL_PERSON = "android.callPerson";
1468 
1469     /**
1470      * {@link #extras} key: the icon to be displayed as a verification status of the caller on a
1471      * {@link android.app.Notification.CallStyle} notification. This extra is an {@link Icon}.
1472      */
1473     public static final String EXTRA_VERIFICATION_ICON = "android.verificationIcon";
1474 
1475     /**
1476      * {@link #extras} key: the text to be displayed as a verification status of the caller on a
1477      * {@link android.app.Notification.CallStyle} notification. This extra is a
1478      * {@link CharSequence}.
1479      */
1480     public static final String EXTRA_VERIFICATION_TEXT = "android.verificationText";
1481 
1482     /**
1483      * {@link #extras} key: the intent to be sent when the users answers a
1484      * {@link android.app.Notification.CallStyle} notification. This extra is a
1485      * {@link PendingIntent}.
1486      */
1487     public static final String EXTRA_ANSWER_INTENT = "android.answerIntent";
1488 
1489     /**
1490      * {@link #extras} key: the intent to be sent when the users declines a
1491      * {@link android.app.Notification.CallStyle} notification. This extra is a
1492      * {@link PendingIntent}.
1493      */
1494     public static final String EXTRA_DECLINE_INTENT = "android.declineIntent";
1495 
1496     /**
1497      * {@link #extras} key: the intent to be sent when the users hangs up a
1498      * {@link android.app.Notification.CallStyle} notification. This extra is a
1499      * {@link PendingIntent}.
1500      */
1501     public static final String EXTRA_HANG_UP_INTENT = "android.hangUpIntent";
1502 
1503     /**
1504      * {@link #extras} key: the color used as a hint for the Answer action button of a
1505      * {@link android.app.Notification.CallStyle} notification. This extra is a {@link ColorInt}.
1506      */
1507     public static final String EXTRA_ANSWER_COLOR = "android.answerColor";
1508 
1509     /**
1510      * {@link #extras} key: the color used as a hint for the Decline or Hang Up action button of a
1511      * {@link android.app.Notification.CallStyle} notification. This extra is a {@link ColorInt}.
1512      */
1513     public static final String EXTRA_DECLINE_COLOR = "android.declineColor";
1514 
1515     /**
1516      * {@link #extras} key: whether the notification should be colorized as
1517      * supplied to {@link Builder#setColorized(boolean)}.
1518      */
1519     public static final String EXTRA_COLORIZED = "android.colorized";
1520 
1521     /**
1522      * @hide
1523      */
1524     public static final String EXTRA_BUILDER_APPLICATION_INFO = "android.appInfo";
1525 
1526     /**
1527      * @hide
1528      */
1529     public static final String EXTRA_CONTAINS_CUSTOM_VIEW = "android.contains.customView";
1530 
1531     /**
1532      * @hide
1533      */
1534     public static final String EXTRA_REDUCED_IMAGES = "android.reduced.images";
1535 
1536     /**
1537      * {@link #extras} key: the audio contents of this notification.
1538      *
1539      * This is for use when rendering the notification on an audio-focused interface;
1540      * the audio contents are a complete sound sample that contains the contents/body of the
1541      * notification. This may be used in substitute of a Text-to-Speech reading of the
1542      * notification. For example if the notification represents a voice message this should point
1543      * to the audio of that message.
1544      *
1545      * The data stored under this key should be a String representation of a Uri that contains the
1546      * audio contents in one of the following formats: WAV, PCM 16-bit, AMR-WB.
1547      *
1548      * This extra is unnecessary if you are using {@code MessagingStyle} since each {@code Message}
1549      * has a field for holding data URI. That field can be used for audio.
1550      * See {@code Message#setData}.
1551      *
1552      * Example usage:
1553      * <pre>
1554      * {@code
1555      * Notification.Builder myBuilder = (build your Notification as normal);
1556      * myBuilder.getExtras().putString(EXTRA_AUDIO_CONTENTS_URI, myAudioUri.toString());
1557      * }
1558      * </pre>
1559      */
1560     public static final String EXTRA_AUDIO_CONTENTS_URI = "android.audioContents";
1561 
1562     /** @hide */
1563     @SystemApi
1564     @RequiresPermission(android.Manifest.permission.SUBSTITUTE_NOTIFICATION_APP_NAME)
1565     public static final String EXTRA_SUBSTITUTE_APP_NAME = "android.substName";
1566 
1567     /**
1568      * This is set on the notifications shown by system_server about apps running foreground
1569      * services. It indicates that the notification should be shown
1570      * only if any of the given apps do not already have a properly tagged
1571      * {@link #FLAG_FOREGROUND_SERVICE} notification currently visible to the user.
1572      * This is a string array of all package names of the apps.
1573      * @hide
1574      */
1575     public static final String EXTRA_FOREGROUND_APPS = "android.foregroundApps";
1576 
1577     @UnsupportedAppUsage
1578     private Icon mSmallIcon;
1579     @UnsupportedAppUsage
1580     private Icon mLargeIcon;
1581 
1582     @UnsupportedAppUsage
1583     private String mChannelId;
1584     private long mTimeout;
1585 
1586     private String mShortcutId;
1587     private LocusId mLocusId;
1588     private CharSequence mSettingsText;
1589 
1590     private BubbleMetadata mBubbleMetadata;
1591 
1592     /** @hide */
1593     @IntDef(prefix = { "GROUP_ALERT_" }, value = {
1594             GROUP_ALERT_ALL, GROUP_ALERT_CHILDREN, GROUP_ALERT_SUMMARY
1595     })
1596     @Retention(RetentionPolicy.SOURCE)
1597     public @interface GroupAlertBehavior {}
1598 
1599     /**
1600      * Constant for {@link Builder#setGroupAlertBehavior(int)}, meaning that all notifications in a
1601      * group with sound or vibration ought to make sound or vibrate (respectively), so this
1602      * notification will not be muted when it is in a group.
1603      */
1604     public static final int GROUP_ALERT_ALL = 0;
1605 
1606     /**
1607      * Constant for {@link Builder#setGroupAlertBehavior(int)}, meaning that all children
1608      * notification in a group should be silenced (no sound or vibration) even if they are posted
1609      * to a {@link NotificationChannel} that has sound and/or vibration. Use this constant to
1610      * mute this notification if this notification is a group child. This must be applied to all
1611      * children notifications you want to mute.
1612      *
1613      * <p> For example, you might want to use this constant if you post a number of children
1614      * notifications at once (say, after a periodic sync), and only need to notify the user
1615      * audibly once.
1616      */
1617     public static final int GROUP_ALERT_SUMMARY = 1;
1618 
1619     /**
1620      * Constant for {@link Builder#setGroupAlertBehavior(int)}, meaning that the summary
1621      * notification in a group should be silenced (no sound or vibration) even if they are
1622      * posted to a {@link NotificationChannel} that has sound and/or vibration. Use this constant
1623      * to mute this notification if this notification is a group summary.
1624      *
1625      * <p>For example, you might want to use this constant if only the children notifications
1626      * in your group have content and the summary is only used to visually group notifications
1627      * rather than to alert the user that new information is available.
1628      */
1629     public static final int GROUP_ALERT_CHILDREN = 2;
1630 
1631     private int mGroupAlertBehavior = GROUP_ALERT_ALL;
1632 
1633     /**
1634      * If this notification is being shown as a badge, always show as a number.
1635      */
1636     public static final int BADGE_ICON_NONE = 0;
1637 
1638     /**
1639      * If this notification is being shown as a badge, use the {@link #getSmallIcon()} to
1640      * represent this notification.
1641      */
1642     public static final int BADGE_ICON_SMALL = 1;
1643 
1644     /**
1645      * If this notification is being shown as a badge, use the {@link #getLargeIcon()} to
1646      * represent this notification.
1647      */
1648     public static final int BADGE_ICON_LARGE = 2;
1649     private int mBadgeIcon = BADGE_ICON_NONE;
1650 
1651     /**
1652      * Determines whether the platform can generate contextual actions for a notification.
1653      */
1654     private boolean mAllowSystemGeneratedContextualActions = true;
1655 
1656     /**
1657      * Structure to encapsulate a named action that can be shown as part of this notification.
1658      * It must include an icon, a label, and a {@link PendingIntent} to be fired when the action is
1659      * selected by the user.
1660      * <p>
1661      * Apps should use {@link Notification.Builder#addAction(int, CharSequence, PendingIntent)}
1662      * or {@link Notification.Builder#addAction(Notification.Action)}
1663      * to attach actions.
1664      * <p>
1665      * As of Android {@link android.os.Build.VERSION_CODES#S}, apps targeting API level {@link
1666      * android.os.Build.VERSION_CODES#S} or higher won't be able to start activities while
1667      * processing broadcast receivers or services in response to notification action clicks. To
1668      * launch an activity in those cases, provide a {@link PendingIntent} for the activity itself.
1669      */
1670     public static class Action implements Parcelable {
1671         /**
1672          * {@link #extras} key: Keys to a {@link Parcelable} {@link ArrayList} of
1673          * {@link RemoteInput}s.
1674          *
1675          * This is intended for {@link RemoteInput}s that only accept data, meaning
1676          * {@link RemoteInput#getAllowFreeFormInput} is false, {@link RemoteInput#getChoices}
1677          * is null or empty, and {@link RemoteInput#getAllowedDataTypes} is non-null and not
1678          * empty. These {@link RemoteInput}s will be ignored by devices that do not
1679          * support non-text-based {@link RemoteInput}s. See {@link Builder#build}.
1680          *
1681          * You can test if a RemoteInput matches these constraints using
1682          * {@link RemoteInput#isDataOnly}.
1683          */
1684         private static final String EXTRA_DATA_ONLY_INPUTS = "android.extra.DATA_ONLY_INPUTS";
1685 
1686         /**
1687          * {@link }: No semantic action defined.
1688          */
1689         public static final int SEMANTIC_ACTION_NONE = 0;
1690 
1691         /**
1692          * {@code SemanticAction}: Reply to a conversation, chat, group, or wherever replies
1693          * may be appropriate.
1694          */
1695         public static final int SEMANTIC_ACTION_REPLY = 1;
1696 
1697         /**
1698          * {@code SemanticAction}: Mark content as read.
1699          */
1700         public static final int SEMANTIC_ACTION_MARK_AS_READ = 2;
1701 
1702         /**
1703          * {@code SemanticAction}: Mark content as unread.
1704          */
1705         public static final int SEMANTIC_ACTION_MARK_AS_UNREAD = 3;
1706 
1707         /**
1708          * {@code SemanticAction}: Delete the content associated with the notification. This
1709          * could mean deleting an email, message, etc.
1710          */
1711         public static final int SEMANTIC_ACTION_DELETE = 4;
1712 
1713         /**
1714          * {@code SemanticAction}: Archive the content associated with the notification. This
1715          * could mean archiving an email, message, etc.
1716          */
1717         public static final int SEMANTIC_ACTION_ARCHIVE = 5;
1718 
1719         /**
1720          * {@code SemanticAction}: Mute the content associated with the notification. This could
1721          * mean silencing a conversation or currently playing media.
1722          */
1723         public static final int SEMANTIC_ACTION_MUTE = 6;
1724 
1725         /**
1726          * {@code SemanticAction}: Unmute the content associated with the notification. This could
1727          * mean un-silencing a conversation or currently playing media.
1728          */
1729         public static final int SEMANTIC_ACTION_UNMUTE = 7;
1730 
1731         /**
1732          * {@code SemanticAction}: Mark content with a thumbs up.
1733          */
1734         public static final int SEMANTIC_ACTION_THUMBS_UP = 8;
1735 
1736         /**
1737          * {@code SemanticAction}: Mark content with a thumbs down.
1738          */
1739         public static final int SEMANTIC_ACTION_THUMBS_DOWN = 9;
1740 
1741         /**
1742          * {@code SemanticAction}: Call a contact, group, etc.
1743          */
1744         public static final int SEMANTIC_ACTION_CALL = 10;
1745 
1746         /**
1747          * {@code SemanticAction}: Mark the conversation associated with the notification as a
1748          * priority. Note that this is only for use by the notification assistant services. The
1749          * type will be ignored for actions an app adds to its own notifications.
1750          * @hide
1751          */
1752         @SystemApi
1753         public static final int SEMANTIC_ACTION_MARK_CONVERSATION_AS_PRIORITY = 11;
1754 
1755         /**
1756          * {@code SemanticAction}: Mark content as a potential phishing attempt.
1757          * Note that this is only for use by the notification assistant services. The type will
1758          * be ignored for actions an app adds to its own notifications.
1759          * @hide
1760          */
1761         @SystemApi
1762         public static final int SEMANTIC_ACTION_CONVERSATION_IS_PHISHING = 12;
1763 
1764         private final Bundle mExtras;
1765         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
1766         private Icon mIcon;
1767         private final RemoteInput[] mRemoteInputs;
1768         private boolean mAllowGeneratedReplies = true;
1769         private final @SemanticAction int mSemanticAction;
1770         private final boolean mIsContextual;
1771         private boolean mAuthenticationRequired;
1772 
1773         /**
1774          * Small icon representing the action.
1775          *
1776          * @deprecated Use {@link Action#getIcon()} instead.
1777          */
1778         @Deprecated
1779         public int icon;
1780 
1781         /**
1782          * Title of the action.
1783          */
1784         public CharSequence title;
1785 
1786         /**
1787          * Intent to send when the user invokes this action. May be null, in which case the action
1788          * may be rendered in a disabled presentation by the system UI.
1789          */
1790         public PendingIntent actionIntent;
1791 
Action(Parcel in)1792         private Action(Parcel in) {
1793             if (in.readInt() != 0) {
1794                 mIcon = Icon.CREATOR.createFromParcel(in);
1795                 if (mIcon.getType() == Icon.TYPE_RESOURCE) {
1796                     icon = mIcon.getResId();
1797                 }
1798             }
1799             title = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
1800             if (in.readInt() == 1) {
1801                 actionIntent = PendingIntent.CREATOR.createFromParcel(in);
1802             }
1803             mExtras = Bundle.setDefusable(in.readBundle(), true);
1804             mRemoteInputs = in.createTypedArray(RemoteInput.CREATOR);
1805             mAllowGeneratedReplies = in.readInt() == 1;
1806             mSemanticAction = in.readInt();
1807             mIsContextual = in.readInt() == 1;
1808             mAuthenticationRequired = in.readInt() == 1;
1809         }
1810 
1811         /**
1812          * @deprecated Use {@link android.app.Notification.Action.Builder}.
1813          */
1814         @Deprecated
Action(int icon, CharSequence title, @Nullable PendingIntent intent)1815         public Action(int icon, CharSequence title, @Nullable PendingIntent intent) {
1816             this(Icon.createWithResource("", icon), title, intent, new Bundle(), null, true,
1817                     SEMANTIC_ACTION_NONE, false /* isContextual */, false /* requireAuth */);
1818         }
1819 
1820         /** 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)1821         private Action(Icon icon, CharSequence title, PendingIntent intent, Bundle extras,
1822                 RemoteInput[] remoteInputs, boolean allowGeneratedReplies,
1823                 @SemanticAction int semanticAction, boolean isContextual,
1824                 boolean requireAuth) {
1825             this.mIcon = icon;
1826             if (icon != null && icon.getType() == Icon.TYPE_RESOURCE) {
1827                 this.icon = icon.getResId();
1828             }
1829             this.title = title;
1830             this.actionIntent = intent;
1831             this.mExtras = extras != null ? extras : new Bundle();
1832             this.mRemoteInputs = remoteInputs;
1833             this.mAllowGeneratedReplies = allowGeneratedReplies;
1834             this.mSemanticAction = semanticAction;
1835             this.mIsContextual = isContextual;
1836             this.mAuthenticationRequired = requireAuth;
1837         }
1838 
1839         /**
1840          * Return an icon representing the action.
1841          */
getIcon()1842         public Icon getIcon() {
1843             if (mIcon == null && icon != 0) {
1844                 // you snuck an icon in here without using the builder; let's try to keep it
1845                 mIcon = Icon.createWithResource("", icon);
1846             }
1847             return mIcon;
1848         }
1849 
1850         /**
1851          * Get additional metadata carried around with this Action.
1852          */
getExtras()1853         public Bundle getExtras() {
1854             return mExtras;
1855         }
1856 
1857         /**
1858          * Return whether the platform should automatically generate possible replies for this
1859          * {@link Action}
1860          */
getAllowGeneratedReplies()1861         public boolean getAllowGeneratedReplies() {
1862             return mAllowGeneratedReplies;
1863         }
1864 
1865         /**
1866          * Get the list of inputs to be collected from the user when this action is sent.
1867          * May return null if no remote inputs were added. Only returns inputs which accept
1868          * a text input. For inputs which only accept data use {@link #getDataOnlyRemoteInputs}.
1869          */
getRemoteInputs()1870         public RemoteInput[] getRemoteInputs() {
1871             return mRemoteInputs;
1872         }
1873 
1874         /**
1875          * Returns the {@code SemanticAction} associated with this {@link Action}. A
1876          * {@code SemanticAction} denotes what an {@link Action}'s {@link PendingIntent} will do
1877          * (eg. reply, mark as read, delete, etc).
1878          */
getSemanticAction()1879         public @SemanticAction int getSemanticAction() {
1880             return mSemanticAction;
1881         }
1882 
1883         /**
1884          * Returns whether this is a contextual Action, i.e. whether the action is dependent on the
1885          * notification message body. An example of a contextual action could be an action opening a
1886          * map application with an address shown in the notification.
1887          */
isContextual()1888         public boolean isContextual() {
1889             return mIsContextual;
1890         }
1891 
1892         /**
1893          * Get the list of inputs to be collected from the user that ONLY accept data when this
1894          * action is sent. These remote inputs are guaranteed to return true on a call to
1895          * {@link RemoteInput#isDataOnly}.
1896          *
1897          * Returns null if there are no data-only remote inputs.
1898          *
1899          * This method exists so that legacy RemoteInput collectors that pre-date the addition
1900          * of non-textual RemoteInputs do not access these remote inputs.
1901          */
getDataOnlyRemoteInputs()1902         public RemoteInput[] getDataOnlyRemoteInputs() {
1903             return getParcelableArrayFromBundle(mExtras, EXTRA_DATA_ONLY_INPUTS, RemoteInput.class);
1904         }
1905 
1906         /**
1907          * Returns whether the OS should only send this action's {@link PendingIntent} on an
1908          * unlocked device.
1909          *
1910          * If the device is locked when the action is invoked, the OS should show the keyguard and
1911          * require successful authentication before invoking the intent.
1912          */
isAuthenticationRequired()1913         public boolean isAuthenticationRequired() {
1914             return mAuthenticationRequired;
1915         }
1916 
1917         /**
1918          * Builder class for {@link Action} objects.
1919          */
1920         public static final class Builder {
1921             @Nullable private final Icon mIcon;
1922             @Nullable private final CharSequence mTitle;
1923             @Nullable private final PendingIntent mIntent;
1924             private boolean mAllowGeneratedReplies = true;
1925             @NonNull private final Bundle mExtras;
1926             @Nullable private ArrayList<RemoteInput> mRemoteInputs;
1927             private @SemanticAction int mSemanticAction;
1928             private boolean mIsContextual;
1929             private boolean mAuthenticationRequired;
1930 
1931             /**
1932              * Construct a new builder for {@link Action} object.
1933              * <p>As of Android {@link android.os.Build.VERSION_CODES#N},
1934              * action button icons will not be displayed on action buttons, but are still required
1935              * and are available to
1936              * {@link android.service.notification.NotificationListenerService notification listeners},
1937              * which may display them in other contexts, for example on a wearable device.
1938              * @param icon icon to show for this action
1939              * @param title the title of the action
1940              * @param intent the {@link PendingIntent} to fire when users trigger this action. May
1941              * be null, in which case the action may be rendered in a disabled presentation by the
1942              * system UI.
1943              */
1944             @Deprecated
Builder(int icon, CharSequence title, @Nullable PendingIntent intent)1945             public Builder(int icon, CharSequence title, @Nullable PendingIntent intent) {
1946                 this(Icon.createWithResource("", icon), title, intent);
1947             }
1948 
1949             /**
1950              * Construct a new builder for {@link Action} object.
1951              *
1952              * <p>As of Android {@link android.os.Build.VERSION_CODES#S}, apps targeting API level
1953              * {@link android.os.Build.VERSION_CODES#S} or higher won't be able to start activities
1954              * while processing broadcast receivers or services in response to notification action
1955              * clicks. To launch an activity in those cases, provide a {@link PendingIntent} for the
1956              * activity itself.
1957              *
1958              * <p>How an Action is displayed, including whether the {@code icon}, {@code text}, or
1959              * both are displayed or required, depends on where and how the action is used, and the
1960              * {@link Style} applied to the Notification.
1961              *
1962              * <p>As of Android {@link android.os.Build.VERSION_CODES#N}, action button icons
1963              * will not be displayed on action buttons, but are still required and are available
1964              * to {@link android.service.notification.NotificationListenerService notification
1965              * listeners}, which may display them in other contexts, for example on a wearable
1966              * device.
1967              *
1968              * <p>When the {@code title} is a {@link android.text.Spanned}, any colors set by a
1969              * {@link ForegroundColorSpan} or {@link TextAppearanceSpan} may be removed or displayed
1970              * with an altered in luminance to ensure proper contrast within the Notification.
1971              *
1972              * @param icon icon to show for this action
1973              * @param title the title of the action
1974              * @param intent the {@link PendingIntent} to fire when users trigger this action. May
1975              * be null, in which case the action may be rendered in a disabled presentation by the
1976              * system UI.
1977              */
Builder(Icon icon, CharSequence title, @Nullable PendingIntent intent)1978             public Builder(Icon icon, CharSequence title, @Nullable PendingIntent intent) {
1979                 this(icon, title, intent, new Bundle(), null, true, SEMANTIC_ACTION_NONE, false);
1980             }
1981 
1982             /**
1983              * Construct a new builder for {@link Action} object using the fields from an
1984              * {@link Action}.
1985              * @param action the action to read fields from.
1986              */
Builder(Action action)1987             public Builder(Action action) {
1988                 this(action.getIcon(), action.title, action.actionIntent,
1989                         new Bundle(action.mExtras), action.getRemoteInputs(),
1990                         action.getAllowGeneratedReplies(), action.getSemanticAction(),
1991                         action.isAuthenticationRequired());
1992             }
1993 
Builder(@ullable Icon icon, @Nullable CharSequence title, @Nullable PendingIntent intent, @NonNull Bundle extras, @Nullable RemoteInput[] remoteInputs, boolean allowGeneratedReplies, @SemanticAction int semanticAction, boolean authRequired)1994             private Builder(@Nullable Icon icon, @Nullable CharSequence title,
1995                     @Nullable PendingIntent intent, @NonNull Bundle extras,
1996                     @Nullable RemoteInput[] remoteInputs, boolean allowGeneratedReplies,
1997                     @SemanticAction int semanticAction, boolean authRequired) {
1998                 mIcon = icon;
1999                 mTitle = title;
2000                 mIntent = intent;
2001                 mExtras = extras;
2002                 if (remoteInputs != null) {
2003                     mRemoteInputs = new ArrayList<>(remoteInputs.length);
2004                     Collections.addAll(mRemoteInputs, remoteInputs);
2005                 }
2006                 mAllowGeneratedReplies = allowGeneratedReplies;
2007                 mSemanticAction = semanticAction;
2008                 mAuthenticationRequired = authRequired;
2009             }
2010 
2011             /**
2012              * Merge additional metadata into this builder.
2013              *
2014              * <p>Values within the Bundle will replace existing extras values in this Builder.
2015              *
2016              * @see Notification.Action#extras
2017              */
2018             @NonNull
addExtras(Bundle extras)2019             public Builder addExtras(Bundle extras) {
2020                 if (extras != null) {
2021                     mExtras.putAll(extras);
2022                 }
2023                 return this;
2024             }
2025 
2026             /**
2027              * Get the metadata Bundle used by this Builder.
2028              *
2029              * <p>The returned Bundle is shared with this Builder.
2030              */
2031             @NonNull
getExtras()2032             public Bundle getExtras() {
2033                 return mExtras;
2034             }
2035 
2036             /**
2037              * Add an input to be collected from the user when this action is sent.
2038              * Response values can be retrieved from the fired intent by using the
2039              * {@link RemoteInput#getResultsFromIntent} function.
2040              * @param remoteInput a {@link RemoteInput} to add to the action
2041              * @return this object for method chaining
2042              */
2043             @NonNull
addRemoteInput(RemoteInput remoteInput)2044             public Builder addRemoteInput(RemoteInput remoteInput) {
2045                 if (mRemoteInputs == null) {
2046                     mRemoteInputs = new ArrayList<RemoteInput>();
2047                 }
2048                 mRemoteInputs.add(remoteInput);
2049                 return this;
2050             }
2051 
2052             /**
2053              * Set whether the platform should automatically generate possible replies to add to
2054              * {@link RemoteInput#getChoices()}. If the {@link Action} doesn't have a
2055              * {@link RemoteInput}, this has no effect.
2056              * @param allowGeneratedReplies {@code true} to allow generated replies, {@code false}
2057              * otherwise
2058              * @return this object for method chaining
2059              * The default value is {@code true}
2060              */
2061             @NonNull
setAllowGeneratedReplies(boolean allowGeneratedReplies)2062             public Builder setAllowGeneratedReplies(boolean allowGeneratedReplies) {
2063                 mAllowGeneratedReplies = allowGeneratedReplies;
2064                 return this;
2065             }
2066 
2067             /**
2068              * Sets the {@code SemanticAction} for this {@link Action}. A
2069              * {@code SemanticAction} denotes what an {@link Action}'s
2070              * {@link PendingIntent} will do (eg. reply, mark as read, delete, etc).
2071              * @param semanticAction a SemanticAction defined within {@link Action} with
2072              * {@code SEMANTIC_ACTION_} prefixes
2073              * @return this object for method chaining
2074              */
2075             @NonNull
setSemanticAction(@emanticAction int semanticAction)2076             public Builder setSemanticAction(@SemanticAction int semanticAction) {
2077                 mSemanticAction = semanticAction;
2078                 return this;
2079             }
2080 
2081             /**
2082              * Sets whether this {@link Action} is a contextual action, i.e. whether the action is
2083              * dependent on the notification message body. An example of a contextual action could
2084              * be an action opening a map application with an address shown in the notification.
2085              */
2086             @NonNull
setContextual(boolean isContextual)2087             public Builder setContextual(boolean isContextual) {
2088                 mIsContextual = isContextual;
2089                 return this;
2090             }
2091 
2092             /**
2093              * Apply an extender to this action builder. Extenders may be used to add
2094              * metadata or change options on this builder.
2095              */
2096             @NonNull
extend(Extender extender)2097             public Builder extend(Extender extender) {
2098                 extender.extend(this);
2099                 return this;
2100             }
2101 
2102             /**
2103              * Sets whether the OS should only send this action's {@link PendingIntent} on an
2104              * unlocked device.
2105              *
2106              * If this is true and the device is locked when the action is invoked, the OS will
2107              * show the keyguard and require successful authentication before invoking the intent.
2108              * If this is false and the device is locked, the OS will decide whether authentication
2109              * should be required.
2110              */
2111             @NonNull
setAuthenticationRequired(boolean authenticationRequired)2112             public Builder setAuthenticationRequired(boolean authenticationRequired) {
2113                 mAuthenticationRequired = authenticationRequired;
2114                 return this;
2115             }
2116 
2117             /**
2118              * Throws an NPE if we are building a contextual action missing one of the fields
2119              * necessary to display the action.
2120              */
checkContextualActionNullFields()2121             private void checkContextualActionNullFields() {
2122                 if (!mIsContextual) return;
2123 
2124                 if (mIcon == null) {
2125                     throw new NullPointerException("Contextual Actions must contain a valid icon");
2126                 }
2127 
2128                 if (mIntent == null) {
2129                     throw new NullPointerException(
2130                             "Contextual Actions must contain a valid PendingIntent");
2131                 }
2132             }
2133 
2134             /**
2135              * Combine all of the options that have been set and return a new {@link Action}
2136              * object.
2137              * @return the built action
2138              */
2139             @NonNull
build()2140             public Action build() {
2141                 checkContextualActionNullFields();
2142 
2143                 ArrayList<RemoteInput> dataOnlyInputs = new ArrayList<>();
2144                 RemoteInput[] previousDataInputs = getParcelableArrayFromBundle(
2145                         mExtras, EXTRA_DATA_ONLY_INPUTS, RemoteInput.class);
2146                 if (previousDataInputs != null) {
2147                     for (RemoteInput input : previousDataInputs) {
2148                         dataOnlyInputs.add(input);
2149                     }
2150                 }
2151                 List<RemoteInput> textInputs = new ArrayList<>();
2152                 if (mRemoteInputs != null) {
2153                     for (RemoteInput input : mRemoteInputs) {
2154                         if (input.isDataOnly()) {
2155                             dataOnlyInputs.add(input);
2156                         } else {
2157                             textInputs.add(input);
2158                         }
2159                     }
2160                 }
2161                 if (!dataOnlyInputs.isEmpty()) {
2162                     RemoteInput[] dataInputsArr =
2163                             dataOnlyInputs.toArray(new RemoteInput[dataOnlyInputs.size()]);
2164                     mExtras.putParcelableArray(EXTRA_DATA_ONLY_INPUTS, dataInputsArr);
2165                 }
2166                 RemoteInput[] textInputsArr = textInputs.isEmpty()
2167                         ? null : textInputs.toArray(new RemoteInput[textInputs.size()]);
2168                 return new Action(mIcon, mTitle, mIntent, mExtras, textInputsArr,
2169                         mAllowGeneratedReplies, mSemanticAction, mIsContextual,
2170                         mAuthenticationRequired);
2171             }
2172         }
2173 
visitUris(@onNull Consumer<Uri> visitor)2174         private void visitUris(@NonNull Consumer<Uri> visitor) {
2175             visitIconUri(visitor, getIcon());
2176         }
2177 
2178         @Override
clone()2179         public Action clone() {
2180             return new Action(
2181                     getIcon(),
2182                     title,
2183                     actionIntent, // safe to alias
2184                     mExtras == null ? new Bundle() : new Bundle(mExtras),
2185                     getRemoteInputs(),
2186                     getAllowGeneratedReplies(),
2187                     getSemanticAction(),
2188                     isContextual(),
2189                     isAuthenticationRequired());
2190         }
2191 
2192         @Override
describeContents()2193         public int describeContents() {
2194             return 0;
2195         }
2196 
2197         @Override
writeToParcel(Parcel out, int flags)2198         public void writeToParcel(Parcel out, int flags) {
2199             final Icon ic = getIcon();
2200             if (ic != null) {
2201                 out.writeInt(1);
2202                 ic.writeToParcel(out, 0);
2203             } else {
2204                 out.writeInt(0);
2205             }
2206             TextUtils.writeToParcel(title, out, flags);
2207             if (actionIntent != null) {
2208                 out.writeInt(1);
2209                 actionIntent.writeToParcel(out, flags);
2210             } else {
2211                 out.writeInt(0);
2212             }
2213             out.writeBundle(mExtras);
2214             out.writeTypedArray(mRemoteInputs, flags);
2215             out.writeInt(mAllowGeneratedReplies ? 1 : 0);
2216             out.writeInt(mSemanticAction);
2217             out.writeInt(mIsContextual ? 1 : 0);
2218             out.writeInt(mAuthenticationRequired ? 1 : 0);
2219         }
2220 
2221         public static final @android.annotation.NonNull Parcelable.Creator<Action> CREATOR =
2222                 new Parcelable.Creator<Action>() {
2223             public Action createFromParcel(Parcel in) {
2224                 return new Action(in);
2225             }
2226             public Action[] newArray(int size) {
2227                 return new Action[size];
2228             }
2229         };
2230 
2231         /**
2232          * Extender interface for use with {@link Builder#extend}. Extenders may be used to add
2233          * metadata or change options on an action builder.
2234          */
2235         public interface Extender {
2236             /**
2237              * Apply this extender to a notification action builder.
2238              * @param builder the builder to be modified.
2239              * @return the build object for chaining.
2240              */
extend(Builder builder)2241             public Builder extend(Builder builder);
2242         }
2243 
2244         /**
2245          * Wearable extender for notification actions. To add extensions to an action,
2246          * create a new {@link android.app.Notification.Action.WearableExtender} object using
2247          * the {@code WearableExtender()} constructor and apply it to a
2248          * {@link android.app.Notification.Action.Builder} using
2249          * {@link android.app.Notification.Action.Builder#extend}.
2250          *
2251          * <pre class="prettyprint">
2252          * Notification.Action action = new Notification.Action.Builder(
2253          *         R.drawable.archive_all, "Archive all", actionIntent)
2254          *         .extend(new Notification.Action.WearableExtender()
2255          *                 .setAvailableOffline(false))
2256          *         .build();</pre>
2257          */
2258         public static final class WearableExtender implements Extender {
2259             /** Notification action extra which contains wearable extensions */
2260             private static final String EXTRA_WEARABLE_EXTENSIONS = "android.wearable.EXTENSIONS";
2261 
2262             // Keys within EXTRA_WEARABLE_EXTENSIONS for wearable options.
2263             private static final String KEY_FLAGS = "flags";
2264             private static final String KEY_IN_PROGRESS_LABEL = "inProgressLabel";
2265             private static final String KEY_CONFIRM_LABEL = "confirmLabel";
2266             private static final String KEY_CANCEL_LABEL = "cancelLabel";
2267 
2268             // Flags bitwise-ored to mFlags
2269             private static final int FLAG_AVAILABLE_OFFLINE = 0x1;
2270             private static final int FLAG_HINT_LAUNCHES_ACTIVITY = 1 << 1;
2271             private static final int FLAG_HINT_DISPLAY_INLINE = 1 << 2;
2272 
2273             // Default value for flags integer
2274             private static final int DEFAULT_FLAGS = FLAG_AVAILABLE_OFFLINE;
2275 
2276             private int mFlags = DEFAULT_FLAGS;
2277 
2278             private CharSequence mInProgressLabel;
2279             private CharSequence mConfirmLabel;
2280             private CharSequence mCancelLabel;
2281 
2282             /**
2283              * Create a {@link android.app.Notification.Action.WearableExtender} with default
2284              * options.
2285              */
WearableExtender()2286             public WearableExtender() {
2287             }
2288 
2289             /**
2290              * Create a {@link android.app.Notification.Action.WearableExtender} by reading
2291              * wearable options present in an existing notification action.
2292              * @param action the notification action to inspect.
2293              */
WearableExtender(Action action)2294             public WearableExtender(Action action) {
2295                 Bundle wearableBundle = action.getExtras().getBundle(EXTRA_WEARABLE_EXTENSIONS);
2296                 if (wearableBundle != null) {
2297                     mFlags = wearableBundle.getInt(KEY_FLAGS, DEFAULT_FLAGS);
2298                     mInProgressLabel = wearableBundle.getCharSequence(KEY_IN_PROGRESS_LABEL);
2299                     mConfirmLabel = wearableBundle.getCharSequence(KEY_CONFIRM_LABEL);
2300                     mCancelLabel = wearableBundle.getCharSequence(KEY_CANCEL_LABEL);
2301                 }
2302             }
2303 
2304             /**
2305              * Apply wearable extensions to a notification action that is being built. This is
2306              * typically called by the {@link android.app.Notification.Action.Builder#extend}
2307              * method of {@link android.app.Notification.Action.Builder}.
2308              */
2309             @Override
extend(Action.Builder builder)2310             public Action.Builder extend(Action.Builder builder) {
2311                 Bundle wearableBundle = new Bundle();
2312 
2313                 if (mFlags != DEFAULT_FLAGS) {
2314                     wearableBundle.putInt(KEY_FLAGS, mFlags);
2315                 }
2316                 if (mInProgressLabel != null) {
2317                     wearableBundle.putCharSequence(KEY_IN_PROGRESS_LABEL, mInProgressLabel);
2318                 }
2319                 if (mConfirmLabel != null) {
2320                     wearableBundle.putCharSequence(KEY_CONFIRM_LABEL, mConfirmLabel);
2321                 }
2322                 if (mCancelLabel != null) {
2323                     wearableBundle.putCharSequence(KEY_CANCEL_LABEL, mCancelLabel);
2324                 }
2325 
2326                 builder.getExtras().putBundle(EXTRA_WEARABLE_EXTENSIONS, wearableBundle);
2327                 return builder;
2328             }
2329 
2330             @Override
clone()2331             public WearableExtender clone() {
2332                 WearableExtender that = new WearableExtender();
2333                 that.mFlags = this.mFlags;
2334                 that.mInProgressLabel = this.mInProgressLabel;
2335                 that.mConfirmLabel = this.mConfirmLabel;
2336                 that.mCancelLabel = this.mCancelLabel;
2337                 return that;
2338             }
2339 
2340             /**
2341              * Set whether this action is available when the wearable device is not connected to
2342              * a companion device. The user can still trigger this action when the wearable device is
2343              * offline, but a visual hint will indicate that the action may not be available.
2344              * Defaults to true.
2345              */
setAvailableOffline(boolean availableOffline)2346             public WearableExtender setAvailableOffline(boolean availableOffline) {
2347                 setFlag(FLAG_AVAILABLE_OFFLINE, availableOffline);
2348                 return this;
2349             }
2350 
2351             /**
2352              * Get whether this action is available when the wearable device is not connected to
2353              * a companion device. The user can still trigger this action when the wearable device is
2354              * offline, but a visual hint will indicate that the action may not be available.
2355              * Defaults to true.
2356              */
isAvailableOffline()2357             public boolean isAvailableOffline() {
2358                 return (mFlags & FLAG_AVAILABLE_OFFLINE) != 0;
2359             }
2360 
setFlag(int mask, boolean value)2361             private void setFlag(int mask, boolean value) {
2362                 if (value) {
2363                     mFlags |= mask;
2364                 } else {
2365                     mFlags &= ~mask;
2366                 }
2367             }
2368 
2369             /**
2370              * Set a label to display while the wearable is preparing to automatically execute the
2371              * action. This is usually a 'ing' verb ending in ellipsis like "Sending..."
2372              *
2373              * @param label the label to display while the action is being prepared to execute
2374              * @return this object for method chaining
2375              */
2376             @Deprecated
setInProgressLabel(CharSequence label)2377             public WearableExtender setInProgressLabel(CharSequence label) {
2378                 mInProgressLabel = label;
2379                 return this;
2380             }
2381 
2382             /**
2383              * Get the label to display while the wearable is preparing to automatically execute
2384              * the action. This is usually a 'ing' verb ending in ellipsis like "Sending..."
2385              *
2386              * @return the label to display while the action is being prepared to execute
2387              */
2388             @Deprecated
getInProgressLabel()2389             public CharSequence getInProgressLabel() {
2390                 return mInProgressLabel;
2391             }
2392 
2393             /**
2394              * Set a label to display to confirm that the action should be executed.
2395              * This is usually an imperative verb like "Send".
2396              *
2397              * @param label the label to confirm the action should be executed
2398              * @return this object for method chaining
2399              */
2400             @Deprecated
setConfirmLabel(CharSequence label)2401             public WearableExtender setConfirmLabel(CharSequence label) {
2402                 mConfirmLabel = label;
2403                 return this;
2404             }
2405 
2406             /**
2407              * Get the label to display to confirm that the action should be executed.
2408              * This is usually an imperative verb like "Send".
2409              *
2410              * @return the label to confirm the action should be executed
2411              */
2412             @Deprecated
getConfirmLabel()2413             public CharSequence getConfirmLabel() {
2414                 return mConfirmLabel;
2415             }
2416 
2417             /**
2418              * Set a label to display to cancel the action.
2419              * This is usually an imperative verb, like "Cancel".
2420              *
2421              * @param label the label to display to cancel the action
2422              * @return this object for method chaining
2423              */
2424             @Deprecated
setCancelLabel(CharSequence label)2425             public WearableExtender setCancelLabel(CharSequence label) {
2426                 mCancelLabel = label;
2427                 return this;
2428             }
2429 
2430             /**
2431              * Get the label to display to cancel the action.
2432              * This is usually an imperative verb like "Cancel".
2433              *
2434              * @return the label to display to cancel the action
2435              */
2436             @Deprecated
getCancelLabel()2437             public CharSequence getCancelLabel() {
2438                 return mCancelLabel;
2439             }
2440 
2441             /**
2442              * Set a hint that this Action will launch an {@link Activity} directly, telling the
2443              * platform that it can generate the appropriate transitions.
2444              * @param hintLaunchesActivity {@code true} if the content intent will launch
2445              * an activity and transitions should be generated, false otherwise.
2446              * @return this object for method chaining
2447              */
setHintLaunchesActivity( boolean hintLaunchesActivity)2448             public WearableExtender setHintLaunchesActivity(
2449                     boolean hintLaunchesActivity) {
2450                 setFlag(FLAG_HINT_LAUNCHES_ACTIVITY, hintLaunchesActivity);
2451                 return this;
2452             }
2453 
2454             /**
2455              * Get a hint that this Action will launch an {@link Activity} directly, telling the
2456              * platform that it can generate the appropriate transitions
2457              * @return {@code true} if the content intent will launch an activity and transitions
2458              * should be generated, false otherwise. The default value is {@code false} if this was
2459              * never set.
2460              */
getHintLaunchesActivity()2461             public boolean getHintLaunchesActivity() {
2462                 return (mFlags & FLAG_HINT_LAUNCHES_ACTIVITY) != 0;
2463             }
2464 
2465             /**
2466              * Set a hint that this Action should be displayed inline.
2467              *
2468              * @param hintDisplayInline {@code true} if action should be displayed inline, false
2469              *        otherwise
2470              * @return this object for method chaining
2471              */
setHintDisplayActionInline( boolean hintDisplayInline)2472             public WearableExtender setHintDisplayActionInline(
2473                     boolean hintDisplayInline) {
2474                 setFlag(FLAG_HINT_DISPLAY_INLINE, hintDisplayInline);
2475                 return this;
2476             }
2477 
2478             /**
2479              * Get a hint that this Action should be displayed inline.
2480              *
2481              * @return {@code true} if the Action should be displayed inline, {@code false}
2482              *         otherwise. The default value is {@code false} if this was never set.
2483              */
getHintDisplayActionInline()2484             public boolean getHintDisplayActionInline() {
2485                 return (mFlags & FLAG_HINT_DISPLAY_INLINE) != 0;
2486             }
2487         }
2488 
2489         /**
2490          * Provides meaning to an {@link Action} that hints at what the associated
2491          * {@link PendingIntent} will do. For example, an {@link Action} with a
2492          * {@link PendingIntent} that replies to a text message notification may have the
2493          * {@link #SEMANTIC_ACTION_REPLY} {@code SemanticAction} set within it.
2494          *
2495          * @hide
2496          */
2497         @IntDef(prefix = { "SEMANTIC_ACTION_" }, value = {
2498                 SEMANTIC_ACTION_NONE,
2499                 SEMANTIC_ACTION_REPLY,
2500                 SEMANTIC_ACTION_MARK_AS_READ,
2501                 SEMANTIC_ACTION_MARK_AS_UNREAD,
2502                 SEMANTIC_ACTION_DELETE,
2503                 SEMANTIC_ACTION_ARCHIVE,
2504                 SEMANTIC_ACTION_MUTE,
2505                 SEMANTIC_ACTION_UNMUTE,
2506                 SEMANTIC_ACTION_THUMBS_UP,
2507                 SEMANTIC_ACTION_THUMBS_DOWN,
2508                 SEMANTIC_ACTION_CALL,
2509                 SEMANTIC_ACTION_MARK_CONVERSATION_AS_PRIORITY,
2510                 SEMANTIC_ACTION_CONVERSATION_IS_PHISHING
2511         })
2512         @Retention(RetentionPolicy.SOURCE)
2513         public @interface SemanticAction {}
2514     }
2515 
2516     /**
2517      * Array of all {@link Action} structures attached to this notification by
2518      * {@link Builder#addAction(int, CharSequence, PendingIntent)}. Mostly useful for instances of
2519      * {@link android.service.notification.NotificationListenerService} that provide an alternative
2520      * interface for invoking actions.
2521      */
2522     public Action[] actions;
2523 
2524     /**
2525      * Replacement version of this notification whose content will be shown
2526      * in an insecure context such as atop a secure keyguard. See {@link #visibility}
2527      * and {@link #VISIBILITY_PUBLIC}.
2528      */
2529     public Notification publicVersion;
2530 
2531     /**
2532      * Constructs a Notification object with default values.
2533      * You might want to consider using {@link Builder} instead.
2534      */
Notification()2535     public Notification()
2536     {
2537         this.when = System.currentTimeMillis();
2538         this.creationTime = System.currentTimeMillis();
2539         this.priority = PRIORITY_DEFAULT;
2540     }
2541 
2542     /**
2543      * @hide
2544      */
2545     @UnsupportedAppUsage
Notification(Context context, int icon, CharSequence tickerText, long when, CharSequence contentTitle, CharSequence contentText, Intent contentIntent)2546     public Notification(Context context, int icon, CharSequence tickerText, long when,
2547             CharSequence contentTitle, CharSequence contentText, Intent contentIntent)
2548     {
2549         new Builder(context)
2550                 .setWhen(when)
2551                 .setSmallIcon(icon)
2552                 .setTicker(tickerText)
2553                 .setContentTitle(contentTitle)
2554                 .setContentText(contentText)
2555                 .setContentIntent(PendingIntent.getActivity(
2556                         context, 0, contentIntent, PendingIntent.FLAG_MUTABLE))
2557                 .buildInto(this);
2558     }
2559 
2560     /**
2561      * Constructs a Notification object with the information needed to
2562      * have a status bar icon without the standard expanded view.
2563      *
2564      * @param icon          The resource id of the icon to put in the status bar.
2565      * @param tickerText    The text that flows by in the status bar when the notification first
2566      *                      activates.
2567      * @param when          The time to show in the time field.  In the System.currentTimeMillis
2568      *                      timebase.
2569      *
2570      * @deprecated Use {@link Builder} instead.
2571      */
2572     @Deprecated
Notification(int icon, CharSequence tickerText, long when)2573     public Notification(int icon, CharSequence tickerText, long when)
2574     {
2575         this.icon = icon;
2576         this.tickerText = tickerText;
2577         this.when = when;
2578         this.creationTime = System.currentTimeMillis();
2579     }
2580 
2581     /**
2582      * Unflatten the notification from a parcel.
2583      */
2584     @SuppressWarnings("unchecked")
Notification(Parcel parcel)2585     public Notification(Parcel parcel) {
2586         // IMPORTANT: Add unmarshaling code in readFromParcel as the pending
2587         // intents in extras are always written as the last entry.
2588         readFromParcelImpl(parcel);
2589         // Must be read last!
2590         allPendingIntents = (ArraySet<PendingIntent>) parcel.readArraySet(null);
2591     }
2592 
readFromParcelImpl(Parcel parcel)2593     private void readFromParcelImpl(Parcel parcel)
2594     {
2595         int version = parcel.readInt();
2596 
2597         mAllowlistToken = parcel.readStrongBinder();
2598         if (mAllowlistToken == null) {
2599             mAllowlistToken = processAllowlistToken;
2600         }
2601         // Propagate this token to all pending intents that are unmarshalled from the parcel.
2602         parcel.setClassCookie(PendingIntent.class, mAllowlistToken);
2603 
2604         when = parcel.readLong();
2605         creationTime = parcel.readLong();
2606         if (parcel.readInt() != 0) {
2607             mSmallIcon = Icon.CREATOR.createFromParcel(parcel);
2608             if (mSmallIcon.getType() == Icon.TYPE_RESOURCE) {
2609                 icon = mSmallIcon.getResId();
2610             }
2611         }
2612         number = parcel.readInt();
2613         if (parcel.readInt() != 0) {
2614             contentIntent = PendingIntent.CREATOR.createFromParcel(parcel);
2615         }
2616         if (parcel.readInt() != 0) {
2617             deleteIntent = PendingIntent.CREATOR.createFromParcel(parcel);
2618         }
2619         if (parcel.readInt() != 0) {
2620             tickerText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
2621         }
2622         if (parcel.readInt() != 0) {
2623             tickerView = RemoteViews.CREATOR.createFromParcel(parcel);
2624         }
2625         if (parcel.readInt() != 0) {
2626             contentView = RemoteViews.CREATOR.createFromParcel(parcel);
2627         }
2628         if (parcel.readInt() != 0) {
2629             mLargeIcon = Icon.CREATOR.createFromParcel(parcel);
2630         }
2631         defaults = parcel.readInt();
2632         flags = parcel.readInt();
2633         if (parcel.readInt() != 0) {
2634             sound = Uri.CREATOR.createFromParcel(parcel);
2635         }
2636 
2637         audioStreamType = parcel.readInt();
2638         if (parcel.readInt() != 0) {
2639             audioAttributes = AudioAttributes.CREATOR.createFromParcel(parcel);
2640         }
2641         vibrate = parcel.createLongArray();
2642         ledARGB = parcel.readInt();
2643         ledOnMS = parcel.readInt();
2644         ledOffMS = parcel.readInt();
2645         iconLevel = parcel.readInt();
2646 
2647         if (parcel.readInt() != 0) {
2648             fullScreenIntent = PendingIntent.CREATOR.createFromParcel(parcel);
2649         }
2650 
2651         priority = parcel.readInt();
2652 
2653         category = parcel.readString8();
2654 
2655         mGroupKey = parcel.readString8();
2656 
2657         mSortKey = parcel.readString8();
2658 
2659         extras = Bundle.setDefusable(parcel.readBundle(), true); // may be null
2660         fixDuplicateExtras();
2661 
2662         actions = parcel.createTypedArray(Action.CREATOR); // may be null
2663 
2664         if (parcel.readInt() != 0) {
2665             bigContentView = RemoteViews.CREATOR.createFromParcel(parcel);
2666         }
2667 
2668         if (parcel.readInt() != 0) {
2669             headsUpContentView = RemoteViews.CREATOR.createFromParcel(parcel);
2670         }
2671 
2672         visibility = parcel.readInt();
2673 
2674         if (parcel.readInt() != 0) {
2675             publicVersion = Notification.CREATOR.createFromParcel(parcel);
2676         }
2677 
2678         color = parcel.readInt();
2679 
2680         if (parcel.readInt() != 0) {
2681             mChannelId = parcel.readString8();
2682         }
2683         mTimeout = parcel.readLong();
2684 
2685         if (parcel.readInt() != 0) {
2686             mShortcutId = parcel.readString8();
2687         }
2688 
2689         if (parcel.readInt() != 0) {
2690             mLocusId = LocusId.CREATOR.createFromParcel(parcel);
2691         }
2692 
2693         mBadgeIcon = parcel.readInt();
2694 
2695         if (parcel.readInt() != 0) {
2696             mSettingsText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
2697         }
2698 
2699         mGroupAlertBehavior = parcel.readInt();
2700         if (parcel.readInt() != 0) {
2701             mBubbleMetadata = BubbleMetadata.CREATOR.createFromParcel(parcel);
2702         }
2703 
2704         mAllowSystemGeneratedContextualActions = parcel.readBoolean();
2705 
2706         mFgsDeferBehavior = parcel.readInt();
2707     }
2708 
2709     @Override
clone()2710     public Notification clone() {
2711         Notification that = new Notification();
2712         cloneInto(that, true);
2713         return that;
2714     }
2715 
2716     /**
2717      * Copy all (or if heavy is false, all except Bitmaps and RemoteViews) members
2718      * of this into that.
2719      * @hide
2720      */
cloneInto(Notification that, boolean heavy)2721     public void cloneInto(Notification that, boolean heavy) {
2722         that.mAllowlistToken = this.mAllowlistToken;
2723         that.when = this.when;
2724         that.creationTime = this.creationTime;
2725         that.mSmallIcon = this.mSmallIcon;
2726         that.number = this.number;
2727 
2728         // PendingIntents are global, so there's no reason (or way) to clone them.
2729         that.contentIntent = this.contentIntent;
2730         that.deleteIntent = this.deleteIntent;
2731         that.fullScreenIntent = this.fullScreenIntent;
2732 
2733         if (this.tickerText != null) {
2734             that.tickerText = this.tickerText.toString();
2735         }
2736         if (heavy && this.tickerView != null) {
2737             that.tickerView = this.tickerView.clone();
2738         }
2739         if (heavy && this.contentView != null) {
2740             that.contentView = this.contentView.clone();
2741         }
2742         if (heavy && this.mLargeIcon != null) {
2743             that.mLargeIcon = this.mLargeIcon;
2744         }
2745         that.iconLevel = this.iconLevel;
2746         that.sound = this.sound; // android.net.Uri is immutable
2747         that.audioStreamType = this.audioStreamType;
2748         if (this.audioAttributes != null) {
2749             that.audioAttributes = new AudioAttributes.Builder(this.audioAttributes).build();
2750         }
2751 
2752         final long[] vibrate = this.vibrate;
2753         if (vibrate != null) {
2754             final int N = vibrate.length;
2755             final long[] vib = that.vibrate = new long[N];
2756             System.arraycopy(vibrate, 0, vib, 0, N);
2757         }
2758 
2759         that.ledARGB = this.ledARGB;
2760         that.ledOnMS = this.ledOnMS;
2761         that.ledOffMS = this.ledOffMS;
2762         that.defaults = this.defaults;
2763 
2764         that.flags = this.flags;
2765 
2766         that.priority = this.priority;
2767 
2768         that.category = this.category;
2769 
2770         that.mGroupKey = this.mGroupKey;
2771 
2772         that.mSortKey = this.mSortKey;
2773 
2774         if (this.extras != null) {
2775             try {
2776                 that.extras = new Bundle(this.extras);
2777                 // will unparcel
2778                 that.extras.size();
2779             } catch (BadParcelableException e) {
2780                 Log.e(TAG, "could not unparcel extras from notification: " + this, e);
2781                 that.extras = null;
2782             }
2783         }
2784 
2785         if (!ArrayUtils.isEmpty(allPendingIntents)) {
2786             that.allPendingIntents = new ArraySet<>(allPendingIntents);
2787         }
2788 
2789         if (this.actions != null) {
2790             that.actions = new Action[this.actions.length];
2791             for(int i=0; i<this.actions.length; i++) {
2792                 if ( this.actions[i] != null) {
2793                     that.actions[i] = this.actions[i].clone();
2794                 }
2795             }
2796         }
2797 
2798         if (heavy && this.bigContentView != null) {
2799             that.bigContentView = this.bigContentView.clone();
2800         }
2801 
2802         if (heavy && this.headsUpContentView != null) {
2803             that.headsUpContentView = this.headsUpContentView.clone();
2804         }
2805 
2806         that.visibility = this.visibility;
2807 
2808         if (this.publicVersion != null) {
2809             that.publicVersion = new Notification();
2810             this.publicVersion.cloneInto(that.publicVersion, heavy);
2811         }
2812 
2813         that.color = this.color;
2814 
2815         that.mChannelId = this.mChannelId;
2816         that.mTimeout = this.mTimeout;
2817         that.mShortcutId = this.mShortcutId;
2818         that.mLocusId = this.mLocusId;
2819         that.mBadgeIcon = this.mBadgeIcon;
2820         that.mSettingsText = this.mSettingsText;
2821         that.mGroupAlertBehavior = this.mGroupAlertBehavior;
2822         that.mFgsDeferBehavior = this.mFgsDeferBehavior;
2823         that.mBubbleMetadata = this.mBubbleMetadata;
2824         that.mAllowSystemGeneratedContextualActions = this.mAllowSystemGeneratedContextualActions;
2825 
2826         if (!heavy) {
2827             that.lightenPayload(); // will clean out extras
2828         }
2829     }
2830 
visitIconUri(@onNull Consumer<Uri> visitor, @Nullable Icon icon)2831     private static void visitIconUri(@NonNull Consumer<Uri> visitor, @Nullable Icon icon) {
2832         if (icon == null) return;
2833         final int iconType = icon.getType();
2834         if (iconType == TYPE_URI || iconType == TYPE_URI_ADAPTIVE_BITMAP) {
2835             visitor.accept(icon.getUri());
2836         }
2837     }
2838 
2839     /**
2840     * Note all {@link Uri} that are referenced internally, with the expectation that Uri permission
2841     * grants will need to be issued to ensure the recipient of this object is able to render its
2842     * contents.
2843     * See b/281044385 for more context and examples about what happens when this isn't done
2844     * correctly.
2845     *
2846     * @hide
2847     */
visitUris(@onNull Consumer<Uri> visitor)2848     public void visitUris(@NonNull Consumer<Uri> visitor) {
2849         if (publicVersion != null) {
2850             publicVersion.visitUris(visitor);
2851         }
2852 
2853         visitor.accept(sound);
2854 
2855         if (tickerView != null) tickerView.visitUris(visitor);
2856         if (contentView != null) contentView.visitUris(visitor);
2857         if (bigContentView != null) bigContentView.visitUris(visitor);
2858         if (headsUpContentView != null) headsUpContentView.visitUris(visitor);
2859 
2860         visitIconUri(visitor, mSmallIcon);
2861         visitIconUri(visitor, mLargeIcon);
2862 
2863         if (actions != null) {
2864             for (Action action : actions) {
2865                 action.visitUris(visitor);
2866             }
2867         }
2868 
2869         if (extras != null) {
2870             visitIconUri(visitor, extras.getParcelable(EXTRA_LARGE_ICON_BIG, Icon.class));
2871             visitIconUri(visitor, extras.getParcelable(EXTRA_PICTURE_ICON, Icon.class));
2872 
2873             // NOTE: The documentation of EXTRA_AUDIO_CONTENTS_URI explicitly says that it is a
2874             // String representation of a Uri, but the previous implementation (and unit test) of
2875             // this method has always treated it as a Uri object. Given the inconsistency,
2876             // supporting both going forward is the safest choice.
2877             Object audioContentsUri = extras.get(EXTRA_AUDIO_CONTENTS_URI);
2878             if (audioContentsUri instanceof Uri) {
2879                 visitor.accept((Uri) audioContentsUri);
2880             } else if (audioContentsUri instanceof String) {
2881                 visitor.accept(Uri.parse((String) audioContentsUri));
2882             }
2883 
2884             if (extras.containsKey(EXTRA_BACKGROUND_IMAGE_URI)) {
2885                 visitor.accept(Uri.parse(extras.getString(EXTRA_BACKGROUND_IMAGE_URI)));
2886             }
2887 
2888             ArrayList<Person> people = extras.getParcelableArrayList(EXTRA_PEOPLE_LIST, android.app.Person.class);
2889             if (people != null && !people.isEmpty()) {
2890                 for (Person p : people) {
2891                     p.visitUris(visitor);
2892                 }
2893             }
2894 
2895             final RemoteInputHistoryItem[] history = extras.getParcelableArray(
2896                     Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS,
2897                     RemoteInputHistoryItem.class);
2898             if (history != null) {
2899                 for (int i = 0; i < history.length; i++) {
2900                     RemoteInputHistoryItem item = history[i];
2901                     if (item.getUri() != null) {
2902                         visitor.accept(item.getUri());
2903                     }
2904                 }
2905             }
2906 
2907             // Extras for MessagingStyle. We visit them even if not isStyle(MessagingStyle), since
2908             // Notification Listeners might use directly (without the isStyle check).
2909             final Person person = extras.getParcelable(EXTRA_MESSAGING_PERSON, Person.class);
2910             if (person != null) {
2911                 person.visitUris(visitor);
2912             }
2913 
2914             final Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES,
2915                     Parcelable.class);
2916             if (!ArrayUtils.isEmpty(messages)) {
2917                 for (MessagingStyle.Message message : MessagingStyle.Message
2918                         .getMessagesFromBundleArray(messages)) {
2919                     message.visitUris(visitor);
2920                 }
2921             }
2922 
2923             final Parcelable[] historic = extras.getParcelableArray(EXTRA_HISTORIC_MESSAGES,
2924                     Parcelable.class);
2925             if (!ArrayUtils.isEmpty(historic)) {
2926                 for (MessagingStyle.Message message : MessagingStyle.Message
2927                         .getMessagesFromBundleArray(historic)) {
2928                     message.visitUris(visitor);
2929                 }
2930             }
2931 
2932             visitIconUri(visitor, extras.getParcelable(EXTRA_CONVERSATION_ICON, Icon.class));
2933 
2934             // Extras for CallStyle (same reason for visiting without checking isStyle).
2935             Person callPerson = extras.getParcelable(EXTRA_CALL_PERSON, Person.class);
2936             if (callPerson != null) {
2937                 callPerson.visitUris(visitor);
2938             }
2939             visitIconUri(visitor, extras.getParcelable(EXTRA_VERIFICATION_ICON, Icon.class));
2940         }
2941 
2942         if (mBubbleMetadata != null) {
2943             visitIconUri(visitor, mBubbleMetadata.getIcon());
2944         }
2945 
2946         if (extras != null && extras.containsKey(WearableExtender.EXTRA_WEARABLE_EXTENSIONS)) {
2947             WearableExtender extender = new WearableExtender(this);
2948             extender.visitUris(visitor);
2949         }
2950     }
2951 
2952     /**
2953      * Removes heavyweight parts of the Notification object for archival or for sending to
2954      * listeners when the full contents are not necessary.
2955      * @hide
2956      */
lightenPayload()2957     public final void lightenPayload() {
2958         tickerView = null;
2959         contentView = null;
2960         bigContentView = null;
2961         headsUpContentView = null;
2962         mLargeIcon = null;
2963         if (extras != null && !extras.isEmpty()) {
2964             final Set<String> keyset = extras.keySet();
2965             final int N = keyset.size();
2966             final String[] keys = keyset.toArray(new String[N]);
2967             for (int i=0; i<N; i++) {
2968                 final String key = keys[i];
2969                 if (TvExtender.EXTRA_TV_EXTENDER.equals(key)) {
2970                     continue;
2971                 }
2972                 final Object obj = extras.get(key);
2973                 if (obj != null &&
2974                     (  obj instanceof Parcelable
2975                     || obj instanceof Parcelable[]
2976                     || obj instanceof SparseArray
2977                     || obj instanceof ArrayList)) {
2978                     extras.remove(key);
2979                 }
2980             }
2981         }
2982     }
2983 
2984     /**
2985      * Make sure this CharSequence is safe to put into a bundle, which basically
2986      * means it had better not be some custom Parcelable implementation.
2987      * @hide
2988      */
safeCharSequence(CharSequence cs)2989     public static CharSequence safeCharSequence(CharSequence cs) {
2990         if (cs == null) return cs;
2991         if (cs.length() > MAX_CHARSEQUENCE_LENGTH) {
2992             cs = cs.subSequence(0, MAX_CHARSEQUENCE_LENGTH);
2993         }
2994         if (cs instanceof Parcelable) {
2995             Log.e(TAG, "warning: " + cs.getClass().getCanonicalName()
2996                     + " instance is a custom Parcelable and not allowed in Notification");
2997             return cs.toString();
2998         }
2999         return removeTextSizeSpans(cs);
3000     }
3001 
removeTextSizeSpans(CharSequence charSequence)3002     private static CharSequence removeTextSizeSpans(CharSequence charSequence) {
3003         if (charSequence instanceof Spanned) {
3004             Spanned ss = (Spanned) charSequence;
3005             Object[] spans = ss.getSpans(0, ss.length(), Object.class);
3006             SpannableStringBuilder builder = new SpannableStringBuilder(ss.toString());
3007             for (Object span : spans) {
3008                 Object resultSpan = span;
3009                 if (resultSpan instanceof CharacterStyle) {
3010                     resultSpan = ((CharacterStyle) span).getUnderlying();
3011                 }
3012                 if (resultSpan instanceof TextAppearanceSpan) {
3013                     TextAppearanceSpan originalSpan = (TextAppearanceSpan) resultSpan;
3014                     resultSpan = new TextAppearanceSpan(
3015                             originalSpan.getFamily(),
3016                             originalSpan.getTextStyle(),
3017                             -1,
3018                             originalSpan.getTextColor(),
3019                             originalSpan.getLinkTextColor());
3020                 } else if (resultSpan instanceof RelativeSizeSpan
3021                         || resultSpan instanceof AbsoluteSizeSpan) {
3022                     continue;
3023                 } else {
3024                     resultSpan = span;
3025                 }
3026                 builder.setSpan(resultSpan, ss.getSpanStart(span), ss.getSpanEnd(span),
3027                         ss.getSpanFlags(span));
3028             }
3029             return builder;
3030         }
3031         return charSequence;
3032     }
3033 
describeContents()3034     public int describeContents() {
3035         return 0;
3036     }
3037 
3038     /**
3039      * Flatten this notification into a parcel.
3040      */
writeToParcel(Parcel parcel, int flags)3041     public void writeToParcel(Parcel parcel, int flags) {
3042         // We need to mark all pending intents getting into the notification
3043         // system as being put there to later allow the notification ranker
3044         // to launch them and by doing so add the app to the battery saver white
3045         // list for a short period of time. The problem is that the system
3046         // cannot look into the extras as there may be parcelables there that
3047         // the platform does not know how to handle. To go around that we have
3048         // an explicit list of the pending intents in the extras bundle.
3049         PendingIntent.OnMarshaledListener addedListener = null;
3050         if (allPendingIntents == null) {
3051             addedListener = (PendingIntent intent, Parcel out, int outFlags) -> {
3052                 if (parcel == out) {
3053                     synchronized (this) {
3054                         if (allPendingIntents == null) {
3055                             allPendingIntents = new ArraySet<>();
3056                         }
3057                         allPendingIntents.add(intent);
3058                     }
3059                 }
3060             };
3061             PendingIntent.addOnMarshaledListener(addedListener);
3062         }
3063         try {
3064             // IMPORTANT: Add marshaling code in writeToParcelImpl as we
3065             // want to intercept all pending events written to the parcel.
3066             writeToParcelImpl(parcel, flags);
3067             synchronized (this) {
3068                 // Must be written last!
3069                 parcel.writeArraySet(allPendingIntents);
3070             }
3071         } finally {
3072             if (addedListener != null) {
3073                 PendingIntent.removeOnMarshaledListener(addedListener);
3074             }
3075         }
3076     }
3077 
writeToParcelImpl(Parcel parcel, int flags)3078     private void writeToParcelImpl(Parcel parcel, int flags) {
3079         parcel.writeInt(1);
3080 
3081         parcel.writeStrongBinder(mAllowlistToken);
3082         parcel.writeLong(when);
3083         parcel.writeLong(creationTime);
3084         if (mSmallIcon == null && icon != 0) {
3085             // you snuck an icon in here without using the builder; let's try to keep it
3086             mSmallIcon = Icon.createWithResource("", icon);
3087         }
3088         if (mSmallIcon != null) {
3089             parcel.writeInt(1);
3090             mSmallIcon.writeToParcel(parcel, 0);
3091         } else {
3092             parcel.writeInt(0);
3093         }
3094         parcel.writeInt(number);
3095         if (contentIntent != null) {
3096             parcel.writeInt(1);
3097             contentIntent.writeToParcel(parcel, 0);
3098         } else {
3099             parcel.writeInt(0);
3100         }
3101         if (deleteIntent != null) {
3102             parcel.writeInt(1);
3103             deleteIntent.writeToParcel(parcel, 0);
3104         } else {
3105             parcel.writeInt(0);
3106         }
3107         if (tickerText != null) {
3108             parcel.writeInt(1);
3109             TextUtils.writeToParcel(tickerText, parcel, flags);
3110         } else {
3111             parcel.writeInt(0);
3112         }
3113         if (tickerView != null) {
3114             parcel.writeInt(1);
3115             tickerView.writeToParcel(parcel, 0);
3116         } else {
3117             parcel.writeInt(0);
3118         }
3119         if (contentView != null) {
3120             parcel.writeInt(1);
3121             contentView.writeToParcel(parcel, 0);
3122         } else {
3123             parcel.writeInt(0);
3124         }
3125         if (mLargeIcon == null && largeIcon != null) {
3126             // you snuck an icon in here without using the builder; let's try to keep it
3127             mLargeIcon = Icon.createWithBitmap(largeIcon);
3128         }
3129         if (mLargeIcon != null) {
3130             parcel.writeInt(1);
3131             mLargeIcon.writeToParcel(parcel, 0);
3132         } else {
3133             parcel.writeInt(0);
3134         }
3135 
3136         parcel.writeInt(defaults);
3137         parcel.writeInt(this.flags);
3138 
3139         if (sound != null) {
3140             parcel.writeInt(1);
3141             sound.writeToParcel(parcel, 0);
3142         } else {
3143             parcel.writeInt(0);
3144         }
3145         parcel.writeInt(audioStreamType);
3146 
3147         if (audioAttributes != null) {
3148             parcel.writeInt(1);
3149             audioAttributes.writeToParcel(parcel, 0);
3150         } else {
3151             parcel.writeInt(0);
3152         }
3153 
3154         parcel.writeLongArray(vibrate);
3155         parcel.writeInt(ledARGB);
3156         parcel.writeInt(ledOnMS);
3157         parcel.writeInt(ledOffMS);
3158         parcel.writeInt(iconLevel);
3159 
3160         if (fullScreenIntent != null) {
3161             parcel.writeInt(1);
3162             fullScreenIntent.writeToParcel(parcel, 0);
3163         } else {
3164             parcel.writeInt(0);
3165         }
3166 
3167         parcel.writeInt(priority);
3168 
3169         parcel.writeString8(category);
3170 
3171         parcel.writeString8(mGroupKey);
3172 
3173         parcel.writeString8(mSortKey);
3174 
3175         parcel.writeBundle(extras); // null ok
3176 
3177         parcel.writeTypedArray(actions, 0); // null ok
3178 
3179         if (bigContentView != null) {
3180             parcel.writeInt(1);
3181             bigContentView.writeToParcel(parcel, 0);
3182         } else {
3183             parcel.writeInt(0);
3184         }
3185 
3186         if (headsUpContentView != null) {
3187             parcel.writeInt(1);
3188             headsUpContentView.writeToParcel(parcel, 0);
3189         } else {
3190             parcel.writeInt(0);
3191         }
3192 
3193         parcel.writeInt(visibility);
3194 
3195         if (publicVersion != null) {
3196             parcel.writeInt(1);
3197             publicVersion.writeToParcel(parcel, 0);
3198         } else {
3199             parcel.writeInt(0);
3200         }
3201 
3202         parcel.writeInt(color);
3203 
3204         if (mChannelId != null) {
3205             parcel.writeInt(1);
3206             parcel.writeString8(mChannelId);
3207         } else {
3208             parcel.writeInt(0);
3209         }
3210         parcel.writeLong(mTimeout);
3211 
3212         if (mShortcutId != null) {
3213             parcel.writeInt(1);
3214             parcel.writeString8(mShortcutId);
3215         } else {
3216             parcel.writeInt(0);
3217         }
3218 
3219         if (mLocusId != null) {
3220             parcel.writeInt(1);
3221             mLocusId.writeToParcel(parcel, 0);
3222         } else {
3223             parcel.writeInt(0);
3224         }
3225 
3226         parcel.writeInt(mBadgeIcon);
3227 
3228         if (mSettingsText != null) {
3229             parcel.writeInt(1);
3230             TextUtils.writeToParcel(mSettingsText, parcel, flags);
3231         } else {
3232             parcel.writeInt(0);
3233         }
3234 
3235         parcel.writeInt(mGroupAlertBehavior);
3236 
3237         if (mBubbleMetadata != null) {
3238             parcel.writeInt(1);
3239             mBubbleMetadata.writeToParcel(parcel, 0);
3240         } else {
3241             parcel.writeInt(0);
3242         }
3243 
3244         parcel.writeBoolean(mAllowSystemGeneratedContextualActions);
3245 
3246         parcel.writeInt(mFgsDeferBehavior);
3247 
3248         // mUsesStandardHeader is not written because it should be recomputed in listeners
3249     }
3250 
3251     /**
3252      * Parcelable.Creator that instantiates Notification objects
3253      */
3254     public static final @android.annotation.NonNull Parcelable.Creator<Notification> CREATOR
3255             = new Parcelable.Creator<Notification>()
3256     {
3257         public Notification createFromParcel(Parcel parcel)
3258         {
3259             return new Notification(parcel);
3260         }
3261 
3262         public Notification[] newArray(int size)
3263         {
3264             return new Notification[size];
3265         }
3266     };
3267 
3268     /**
3269      * @hide
3270      */
areActionsVisiblyDifferent(Notification first, Notification second)3271     public static boolean areActionsVisiblyDifferent(Notification first, Notification second) {
3272         Notification.Action[] firstAs = first.actions;
3273         Notification.Action[] secondAs = second.actions;
3274         if (firstAs == null && secondAs != null || firstAs != null && secondAs == null) {
3275             return true;
3276         }
3277         if (firstAs != null && secondAs != null) {
3278             if (firstAs.length != secondAs.length) {
3279                 return true;
3280             }
3281             for (int i = 0; i < firstAs.length; i++) {
3282                 if (!Objects.equals(String.valueOf(firstAs[i].title),
3283                         String.valueOf(secondAs[i].title))) {
3284                     return true;
3285                 }
3286                 RemoteInput[] firstRs = firstAs[i].getRemoteInputs();
3287                 RemoteInput[] secondRs = secondAs[i].getRemoteInputs();
3288                 if (firstRs == null) {
3289                     firstRs = new RemoteInput[0];
3290                 }
3291                 if (secondRs == null) {
3292                     secondRs = new RemoteInput[0];
3293                 }
3294                 if (firstRs.length != secondRs.length) {
3295                     return true;
3296                 }
3297                 for (int j = 0; j < firstRs.length; j++) {
3298                     if (!Objects.equals(String.valueOf(firstRs[j].getLabel()),
3299                             String.valueOf(secondRs[j].getLabel()))) {
3300                         return true;
3301                     }
3302                 }
3303             }
3304         }
3305         return false;
3306     }
3307 
3308     /**
3309      * @hide
3310      */
areIconsDifferent(Notification first, Notification second)3311     public static boolean areIconsDifferent(Notification first, Notification second) {
3312         return areIconsMaybeDifferent(first.getSmallIcon(), second.getSmallIcon())
3313                 || areIconsMaybeDifferent(first.getLargeIcon(), second.getLargeIcon());
3314     }
3315 
3316     /**
3317      * Note that we aren't actually comparing the contents of the bitmaps here; this is only a
3318      * cursory inspection. We will not return false negatives, but false positives are likely.
3319      */
areIconsMaybeDifferent(Icon a, Icon b)3320     private static boolean areIconsMaybeDifferent(Icon a, Icon b) {
3321         if (a == b) {
3322             return false;
3323         }
3324         if (a == null || b == null) {
3325             return true;
3326         }
3327         if (a.sameAs(b)) {
3328             return false;
3329         }
3330         final int aType = a.getType();
3331         if (aType != b.getType()) {
3332             return true;
3333         }
3334         if (aType == Icon.TYPE_BITMAP || aType == Icon.TYPE_ADAPTIVE_BITMAP) {
3335             final Bitmap aBitmap = a.getBitmap();
3336             final Bitmap bBitmap = b.getBitmap();
3337             return aBitmap.getWidth() != bBitmap.getWidth()
3338                     || aBitmap.getHeight() != bBitmap.getHeight()
3339                     || aBitmap.getConfig() != bBitmap.getConfig()
3340                     || aBitmap.getGenerationId() != bBitmap.getGenerationId();
3341         }
3342         return true;
3343     }
3344 
3345     /**
3346      * @hide
3347      */
areStyledNotificationsVisiblyDifferent(Builder first, Builder second)3348     public static boolean areStyledNotificationsVisiblyDifferent(Builder first, Builder second) {
3349         if (first.getStyle() == null) {
3350             return second.getStyle() != null;
3351         }
3352         if (second.getStyle() == null) {
3353             return true;
3354         }
3355         return first.getStyle().areNotificationsVisiblyDifferent(second.getStyle());
3356     }
3357 
3358     /**
3359      * @hide
3360      */
areRemoteViewsChanged(Builder first, Builder second)3361     public static boolean areRemoteViewsChanged(Builder first, Builder second) {
3362         if (!Objects.equals(first.usesStandardHeader(), second.usesStandardHeader())) {
3363             return true;
3364         }
3365 
3366         if (areRemoteViewsChanged(first.mN.contentView, second.mN.contentView)) {
3367             return true;
3368         }
3369         if (areRemoteViewsChanged(first.mN.bigContentView, second.mN.bigContentView)) {
3370             return true;
3371         }
3372         if (areRemoteViewsChanged(first.mN.headsUpContentView, second.mN.headsUpContentView)) {
3373             return true;
3374         }
3375 
3376         return false;
3377     }
3378 
areRemoteViewsChanged(RemoteViews first, RemoteViews second)3379     private static boolean areRemoteViewsChanged(RemoteViews first, RemoteViews second) {
3380         if (first == null && second == null) {
3381             return false;
3382         }
3383         if (first == null && second != null || first != null && second == null) {
3384             return true;
3385         }
3386 
3387         if (!Objects.equals(first.getLayoutId(), second.getLayoutId())) {
3388             return true;
3389         }
3390 
3391         if (!Objects.equals(first.getSequenceNumber(), second.getSequenceNumber())) {
3392             return true;
3393         }
3394 
3395         return false;
3396     }
3397 
3398     /**
3399      * Parcelling creates multiple copies of objects in {@code extras}. Fix them.
3400      * <p>
3401      * For backwards compatibility {@code extras} holds some references to "real" member data such
3402      * as {@link getLargeIcon()} which is mirrored by {@link #EXTRA_LARGE_ICON}. This is mostly
3403      * fine as long as the object stays in one process.
3404      * <p>
3405      * However, once the notification goes into a parcel each reference gets marshalled separately,
3406      * wasting memory. Especially with large images on Auto and TV, this is worth fixing.
3407      */
fixDuplicateExtras()3408     private void fixDuplicateExtras() {
3409         if (extras != null) {
3410             fixDuplicateExtra(mLargeIcon, EXTRA_LARGE_ICON);
3411         }
3412     }
3413 
3414     /**
3415      * If we find an extra that's exactly the same as one of the "real" fields but refers to a
3416      * separate object, replace it with the field's version to avoid holding duplicate copies.
3417      */
fixDuplicateExtra(@ullable Parcelable original, @NonNull String extraName)3418     private void fixDuplicateExtra(@Nullable Parcelable original, @NonNull String extraName) {
3419         if (original != null && extras.getParcelable(extraName, Parcelable.class) != null) {
3420             extras.putParcelable(extraName, original);
3421         }
3422     }
3423 
3424     /**
3425      * Sets the {@link #contentView} field to be a view with the standard "Latest Event"
3426      * layout.
3427      *
3428      * <p>Uses the {@link #icon} and {@link #when} fields to set the icon and time fields
3429      * in the view.</p>
3430      * @param context       The context for your application / activity.
3431      * @param contentTitle The title that goes in the expanded entry.
3432      * @param contentText  The text that goes in the expanded entry.
3433      * @param contentIntent The intent to launch when the user clicks the expanded notification.
3434      * If this is an activity, it must include the
3435      * {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK} flag, which requires
3436      * that you take care of task management as described in the
3437      * <a href="{@docRoot}guide/topics/fundamentals/tasks-and-back-stack.html">Tasks and Back
3438      * Stack</a> document.
3439      *
3440      * @deprecated Use {@link Builder} instead.
3441      * @removed
3442      */
3443     @Deprecated
setLatestEventInfo(Context context, CharSequence contentTitle, CharSequence contentText, PendingIntent contentIntent)3444     public void setLatestEventInfo(Context context,
3445             CharSequence contentTitle, CharSequence contentText, PendingIntent contentIntent) {
3446         if (context.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1){
3447             Log.e(TAG, "setLatestEventInfo() is deprecated and you should feel deprecated.",
3448                     new Throwable());
3449         }
3450 
3451         if (context.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N) {
3452             extras.putBoolean(EXTRA_SHOW_WHEN, true);
3453         }
3454 
3455         // ensure that any information already set directly is preserved
3456         final Notification.Builder builder = new Notification.Builder(context, this);
3457 
3458         // now apply the latestEventInfo fields
3459         if (contentTitle != null) {
3460             builder.setContentTitle(contentTitle);
3461         }
3462         if (contentText != null) {
3463             builder.setContentText(contentText);
3464         }
3465         builder.setContentIntent(contentIntent);
3466 
3467         builder.build(); // callers expect this notification to be ready to use
3468     }
3469 
3470     /**
3471      * Sets the token used for background operations for the pending intents associated with this
3472      * notification.
3473      *
3474      * This token is automatically set during deserialization for you, you usually won't need to
3475      * call this unless you want to change the existing token, if any.
3476      *
3477      * @hide
3478      */
clearAllowlistToken()3479     public void clearAllowlistToken() {
3480         mAllowlistToken = null;
3481         if (publicVersion != null) {
3482             publicVersion.clearAllowlistToken();
3483         }
3484     }
3485 
3486     /**
3487      * @hide
3488      */
addFieldsFromContext(Context context, Notification notification)3489     public static void addFieldsFromContext(Context context, Notification notification) {
3490         addFieldsFromContext(context.getApplicationInfo(), notification);
3491     }
3492 
3493     /**
3494      * @hide
3495      */
addFieldsFromContext(ApplicationInfo ai, Notification notification)3496     public static void addFieldsFromContext(ApplicationInfo ai, Notification notification) {
3497         notification.extras.putParcelable(EXTRA_BUILDER_APPLICATION_INFO, ai);
3498     }
3499 
3500     /**
3501      * @hide
3502      */
dumpDebug(ProtoOutputStream proto, long fieldId)3503     public void dumpDebug(ProtoOutputStream proto, long fieldId) {
3504         long token = proto.start(fieldId);
3505         proto.write(NotificationProto.CHANNEL_ID, getChannelId());
3506         proto.write(NotificationProto.HAS_TICKER_TEXT, this.tickerText != null);
3507         proto.write(NotificationProto.FLAGS, this.flags);
3508         proto.write(NotificationProto.COLOR, this.color);
3509         proto.write(NotificationProto.CATEGORY, this.category);
3510         proto.write(NotificationProto.GROUP_KEY, this.mGroupKey);
3511         proto.write(NotificationProto.SORT_KEY, this.mSortKey);
3512         if (this.actions != null) {
3513             proto.write(NotificationProto.ACTION_LENGTH, this.actions.length);
3514         }
3515         if (this.visibility >= VISIBILITY_SECRET && this.visibility <= VISIBILITY_PUBLIC) {
3516             proto.write(NotificationProto.VISIBILITY, this.visibility);
3517         }
3518         if (publicVersion != null) {
3519             publicVersion.dumpDebug(proto, NotificationProto.PUBLIC_VERSION);
3520         }
3521         proto.end(token);
3522     }
3523 
3524     @Override
toString()3525     public String toString() {
3526         StringBuilder sb = new StringBuilder();
3527         sb.append("Notification(channel=");
3528         sb.append(getChannelId());
3529         sb.append(" shortcut=");
3530         sb.append(getShortcutId());
3531         sb.append(" contentView=");
3532         if (contentView != null) {
3533             sb.append(contentView.getPackage());
3534             sb.append("/0x");
3535             sb.append(Integer.toHexString(contentView.getLayoutId()));
3536         } else {
3537             sb.append("null");
3538         }
3539         sb.append(" vibrate=");
3540         if ((this.defaults & DEFAULT_VIBRATE) != 0) {
3541             sb.append("default");
3542         } else if (this.vibrate != null) {
3543             int N = this.vibrate.length-1;
3544             sb.append("[");
3545             for (int i=0; i<N; i++) {
3546                 sb.append(this.vibrate[i]);
3547                 sb.append(',');
3548             }
3549             if (N != -1) {
3550                 sb.append(this.vibrate[N]);
3551             }
3552             sb.append("]");
3553         } else {
3554             sb.append("null");
3555         }
3556         sb.append(" sound=");
3557         if ((this.defaults & DEFAULT_SOUND) != 0) {
3558             sb.append("default");
3559         } else if (this.sound != null) {
3560             sb.append(this.sound.toString());
3561         } else {
3562             sb.append("null");
3563         }
3564         if (this.tickerText != null) {
3565             sb.append(" tick");
3566         }
3567         sb.append(" defaults=0x");
3568         sb.append(Integer.toHexString(this.defaults));
3569         sb.append(" flags=0x");
3570         sb.append(Integer.toHexString(this.flags));
3571         sb.append(String.format(" color=0x%08x", this.color));
3572         if (this.category != null) {
3573             sb.append(" category=");
3574             sb.append(this.category);
3575         }
3576         if (this.mGroupKey != null) {
3577             sb.append(" groupKey=");
3578             sb.append(this.mGroupKey);
3579         }
3580         if (this.mSortKey != null) {
3581             sb.append(" sortKey=");
3582             sb.append(this.mSortKey);
3583         }
3584         if (actions != null) {
3585             sb.append(" actions=");
3586             sb.append(actions.length);
3587         }
3588         sb.append(" vis=");
3589         sb.append(visibilityToString(this.visibility));
3590         if (this.publicVersion != null) {
3591             sb.append(" publicVersion=");
3592             sb.append(publicVersion.toString());
3593         }
3594         if (this.mLocusId != null) {
3595             sb.append(" locusId=");
3596             sb.append(this.mLocusId); // LocusId.toString() is PII safe.
3597         }
3598         sb.append(")");
3599         return sb.toString();
3600     }
3601 
3602     /**
3603      * {@hide}
3604      */
visibilityToString(int vis)3605     public static String visibilityToString(int vis) {
3606         switch (vis) {
3607             case VISIBILITY_PRIVATE:
3608                 return "PRIVATE";
3609             case VISIBILITY_PUBLIC:
3610                 return "PUBLIC";
3611             case VISIBILITY_SECRET:
3612                 return "SECRET";
3613             default:
3614                 return "UNKNOWN(" + String.valueOf(vis) + ")";
3615         }
3616     }
3617 
3618     /**
3619      * {@hide}
3620      */
priorityToString(@riority int pri)3621     public static String priorityToString(@Priority int pri) {
3622         switch (pri) {
3623             case PRIORITY_MIN:
3624                 return "MIN";
3625             case PRIORITY_LOW:
3626                 return "LOW";
3627             case PRIORITY_DEFAULT:
3628                 return "DEFAULT";
3629             case PRIORITY_HIGH:
3630                 return "HIGH";
3631             case PRIORITY_MAX:
3632                 return "MAX";
3633             default:
3634                 return "UNKNOWN(" + String.valueOf(pri) + ")";
3635         }
3636     }
3637 
3638     /**
3639      * @hide
3640      */
hasCompletedProgress()3641     public boolean hasCompletedProgress() {
3642         // not a progress notification; can't be complete
3643         if (!extras.containsKey(EXTRA_PROGRESS)
3644                 || !extras.containsKey(EXTRA_PROGRESS_MAX)) {
3645             return false;
3646         }
3647         // many apps use max 0 for 'indeterminate'; not complete
3648         if (extras.getInt(EXTRA_PROGRESS_MAX) == 0) {
3649             return false;
3650         }
3651         return extras.getInt(EXTRA_PROGRESS) == extras.getInt(EXTRA_PROGRESS_MAX);
3652     }
3653 
3654     /** @removed */
3655     @Deprecated
getChannel()3656     public String getChannel() {
3657         return mChannelId;
3658     }
3659 
3660     /**
3661      * Returns the id of the channel this notification posts to.
3662      */
getChannelId()3663     public String getChannelId() {
3664         return mChannelId;
3665     }
3666 
3667     /** @removed */
3668     @Deprecated
getTimeout()3669     public long getTimeout() {
3670         return mTimeout;
3671     }
3672 
3673     /**
3674      * Returns the duration from posting after which this notification should be canceled by the
3675      * system, if it's not canceled already.
3676      */
getTimeoutAfter()3677     public long getTimeoutAfter() {
3678         return mTimeout;
3679     }
3680 
3681     /**
3682      * Returns what icon should be shown for this notification if it is being displayed in a
3683      * Launcher that supports badging. Will be one of {@link #BADGE_ICON_NONE},
3684      * {@link #BADGE_ICON_SMALL}, or {@link #BADGE_ICON_LARGE}.
3685      */
getBadgeIconType()3686     public int getBadgeIconType() {
3687         return mBadgeIcon;
3688     }
3689 
3690     /**
3691      * Returns the {@link ShortcutInfo#getId() id} that this notification supersedes, if any.
3692      *
3693      * <p>Used by some Launchers that display notification content to hide shortcuts that duplicate
3694      * notifications.
3695      */
getShortcutId()3696     public String getShortcutId() {
3697         return mShortcutId;
3698     }
3699 
3700     /**
3701      * Gets the {@link LocusId} associated with this notification.
3702      *
3703      * <p>Used by the device's intelligence services to correlate objects (such as
3704      * {@link ShortcutInfo} and {@link ContentCaptureContext}) that are correlated.
3705      */
3706     @Nullable
getLocusId()3707     public LocusId getLocusId() {
3708         return mLocusId;
3709     }
3710 
3711     /**
3712      * Returns the settings text provided to {@link Builder#setSettingsText(CharSequence)}.
3713      */
getSettingsText()3714     public CharSequence getSettingsText() {
3715         return mSettingsText;
3716     }
3717 
3718     /**
3719      * Returns which type of notifications in a group are responsible for audibly alerting the
3720      * user. See {@link #GROUP_ALERT_ALL}, {@link #GROUP_ALERT_CHILDREN},
3721      * {@link #GROUP_ALERT_SUMMARY}.
3722      */
getGroupAlertBehavior()3723     public @GroupAlertBehavior int getGroupAlertBehavior() {
3724         return mGroupAlertBehavior;
3725     }
3726 
3727     /**
3728      * Returns the bubble metadata that will be used to display app content in a floating window
3729      * over the existing foreground activity.
3730      */
3731     @Nullable
getBubbleMetadata()3732     public BubbleMetadata getBubbleMetadata() {
3733         return mBubbleMetadata;
3734     }
3735 
3736     /**
3737      * Sets the {@link BubbleMetadata} for this notification.
3738      * @hide
3739      */
setBubbleMetadata(BubbleMetadata data)3740     public void setBubbleMetadata(BubbleMetadata data) {
3741         mBubbleMetadata = data;
3742     }
3743 
3744     /**
3745      * Returns whether the platform is allowed (by the app developer) to generate contextual actions
3746      * for this notification.
3747      */
getAllowSystemGeneratedContextualActions()3748     public boolean getAllowSystemGeneratedContextualActions() {
3749         return mAllowSystemGeneratedContextualActions;
3750     }
3751 
3752     /**
3753      * The small icon representing this notification in the status bar and content view.
3754      *
3755      * @return the small icon representing this notification.
3756      *
3757      * @see Builder#getSmallIcon()
3758      * @see Builder#setSmallIcon(Icon)
3759      */
getSmallIcon()3760     public Icon getSmallIcon() {
3761         return mSmallIcon;
3762     }
3763 
3764     /**
3765      * Used when notifying to clean up legacy small icons.
3766      * @hide
3767      */
3768     @UnsupportedAppUsage
setSmallIcon(Icon icon)3769     public void setSmallIcon(Icon icon) {
3770         mSmallIcon = icon;
3771     }
3772 
3773     /**
3774      * The large icon shown in this notification's content view.
3775      * @see Builder#getLargeIcon()
3776      * @see Builder#setLargeIcon(Icon)
3777      */
getLargeIcon()3778     public Icon getLargeIcon() {
3779         return mLargeIcon;
3780     }
3781 
3782     /**
3783      * @hide
3784      */
3785     @UnsupportedAppUsage
isGroupSummary()3786     public boolean isGroupSummary() {
3787         return mGroupKey != null && (flags & FLAG_GROUP_SUMMARY) != 0;
3788     }
3789 
3790     /**
3791      * @hide
3792      */
3793     @UnsupportedAppUsage
isGroupChild()3794     public boolean isGroupChild() {
3795         return mGroupKey != null && (flags & FLAG_GROUP_SUMMARY) == 0;
3796     }
3797 
3798     /**
3799      * @hide
3800      */
suppressAlertingDueToGrouping()3801     public boolean suppressAlertingDueToGrouping() {
3802         if (isGroupSummary()
3803                 && getGroupAlertBehavior() == Notification.GROUP_ALERT_CHILDREN) {
3804             return true;
3805         } else if (isGroupChild()
3806                 && getGroupAlertBehavior() == Notification.GROUP_ALERT_SUMMARY) {
3807             return true;
3808         }
3809         return false;
3810     }
3811 
3812 
3813     /**
3814      * Finds and returns a remote input and its corresponding action.
3815      *
3816      * @param requiresFreeform requires the remoteinput to allow freeform or not.
3817      * @return the result pair, {@code null} if no result is found.
3818      */
3819     @Nullable
findRemoteInputActionPair(boolean requiresFreeform)3820     public Pair<RemoteInput, Action> findRemoteInputActionPair(boolean requiresFreeform) {
3821         if (actions == null) {
3822             return null;
3823         }
3824         for (Notification.Action action : actions) {
3825             if (action.getRemoteInputs() == null) {
3826                 continue;
3827             }
3828             RemoteInput resultRemoteInput = null;
3829             for (RemoteInput remoteInput : action.getRemoteInputs()) {
3830                 if (remoteInput.getAllowFreeFormInput() || !requiresFreeform) {
3831                     resultRemoteInput = remoteInput;
3832                 }
3833             }
3834             if (resultRemoteInput != null) {
3835                 return Pair.create(resultRemoteInput, action);
3836             }
3837         }
3838         return null;
3839     }
3840 
3841     /**
3842      * Returns the actions that are contextual (that is, suggested because of the content of the
3843      * notification) out of the actions in this notification.
3844      */
getContextualActions()3845     public @NonNull List<Notification.Action> getContextualActions() {
3846         if (actions == null) return Collections.emptyList();
3847 
3848         List<Notification.Action> contextualActions = new ArrayList<>();
3849         for (Notification.Action action : actions) {
3850             if (action.isContextual()) {
3851                 contextualActions.add(action);
3852             }
3853         }
3854         return contextualActions;
3855     }
3856 
3857     /**
3858      * Builder class for {@link Notification} objects.
3859      *
3860      * Provides a convenient way to set the various fields of a {@link Notification} and generate
3861      * content views using the platform's notification layout template. If your app supports
3862      * versions of Android as old as API level 4, you can instead use
3863      * {@link androidx.core.app.NotificationCompat.Builder NotificationCompat.Builder},
3864      * available in the <a href="{@docRoot}tools/extras/support-library.html">Android Support
3865      * library</a>.
3866      *
3867      * <p>Example:
3868      *
3869      * <pre class="prettyprint">
3870      * Notification noti = new Notification.Builder(mContext)
3871      *         .setContentTitle(&quot;New mail from &quot; + sender.toString())
3872      *         .setContentText(subject)
3873      *         .setSmallIcon(R.drawable.new_mail)
3874      *         .setLargeIcon(aBitmap)
3875      *         .build();
3876      * </pre>
3877      */
3878     public static class Builder {
3879         /**
3880          * @hide
3881          */
3882         public static final String EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT =
3883                 "android.rebuild.contentViewActionCount";
3884         /**
3885          * @hide
3886          */
3887         public static final String EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT
3888                 = "android.rebuild.bigViewActionCount";
3889         /**
3890          * @hide
3891          */
3892         public static final String EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT
3893                 = "android.rebuild.hudViewActionCount";
3894 
3895         private static final boolean USE_ONLY_TITLE_IN_LOW_PRIORITY_SUMMARY =
3896                 SystemProperties.getBoolean("notifications.only_title", true);
3897 
3898         /**
3899          * The lightness difference that has to be added to the primary text color to obtain the
3900          * secondary text color when the background is light.
3901          */
3902         private static final int LIGHTNESS_TEXT_DIFFERENCE_LIGHT = 20;
3903 
3904         /**
3905          * The lightness difference that has to be added to the primary text color to obtain the
3906          * secondary text color when the background is dark.
3907          * A bit less then the above value, since it looks better on dark backgrounds.
3908          */
3909         private static final int LIGHTNESS_TEXT_DIFFERENCE_DARK = -10;
3910 
3911         private Context mContext;
3912         private Notification mN;
3913         private Bundle mUserExtras = new Bundle();
3914         private Style mStyle;
3915         @UnsupportedAppUsage
3916         private ArrayList<Action> mActions = new ArrayList<>(MAX_ACTION_BUTTONS);
3917         private ArrayList<Person> mPersonList = new ArrayList<>();
3918         private ContrastColorUtil mColorUtil;
3919         private boolean mIsLegacy;
3920         private boolean mIsLegacyInitialized;
3921 
3922         /**
3923          * Caches an instance of StandardTemplateParams. Note that this may have been used before,
3924          * so make sure to call {@link StandardTemplateParams#reset()} before using it.
3925          */
3926         StandardTemplateParams mParams = new StandardTemplateParams();
3927         Colors mColors = new Colors();
3928 
3929         private boolean mTintActionButtons;
3930         private boolean mInNightMode;
3931 
3932         /**
3933          * Constructs a new Builder with the defaults:
3934          *
3935          * @param context
3936          *            A {@link Context} that will be used by the Builder to construct the
3937          *            RemoteViews. The Context will not be held past the lifetime of this Builder
3938          *            object.
3939          * @param channelId
3940          *            The constructed Notification will be posted on this
3941          *            {@link NotificationChannel}. To use a NotificationChannel, it must first be
3942          *            created using {@link NotificationManager#createNotificationChannel}.
3943          */
Builder(Context context, String channelId)3944         public Builder(Context context, String channelId) {
3945             this(context, (Notification) null);
3946             mN.mChannelId = channelId;
3947         }
3948 
3949         /**
3950          * @deprecated use {@link #Builder(Context, String)}
3951          * instead. All posted Notifications must specify a NotificationChannel Id.
3952          */
3953         @Deprecated
Builder(Context context)3954         public Builder(Context context) {
3955             this(context, (Notification) null);
3956         }
3957 
3958         /**
3959          * @hide
3960          */
Builder(Context context, Notification toAdopt)3961         public Builder(Context context, Notification toAdopt) {
3962             mContext = context;
3963             Resources res = mContext.getResources();
3964             mTintActionButtons = res.getBoolean(R.bool.config_tintNotificationActionButtons);
3965 
3966             if (res.getBoolean(R.bool.config_enableNightMode)) {
3967                 Configuration currentConfig = res.getConfiguration();
3968                 mInNightMode = (currentConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK)
3969                         == Configuration.UI_MODE_NIGHT_YES;
3970             }
3971 
3972             if (toAdopt == null) {
3973                 mN = new Notification();
3974                 if (context.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N) {
3975                     mN.extras.putBoolean(EXTRA_SHOW_WHEN, true);
3976                 }
3977                 mN.priority = PRIORITY_DEFAULT;
3978                 mN.visibility = VISIBILITY_PRIVATE;
3979             } else {
3980                 mN = toAdopt;
3981                 if (mN.actions != null) {
3982                     Collections.addAll(mActions, mN.actions);
3983                 }
3984 
3985                 if (mN.extras.containsKey(EXTRA_PEOPLE_LIST)) {
3986                     ArrayList<Person> people = mN.extras.getParcelableArrayList(EXTRA_PEOPLE_LIST,
3987                             android.app.Person.class);
3988                     if (people != null && !people.isEmpty()) {
3989                         mPersonList.addAll(people);
3990                     }
3991                 }
3992 
3993                 if (mN.getSmallIcon() == null && mN.icon != 0) {
3994                     setSmallIcon(mN.icon);
3995                 }
3996 
3997                 if (mN.getLargeIcon() == null && mN.largeIcon != null) {
3998                     setLargeIcon(mN.largeIcon);
3999                 }
4000 
4001                 String templateClass = mN.extras.getString(EXTRA_TEMPLATE);
4002                 if (!TextUtils.isEmpty(templateClass)) {
4003                     final Class<? extends Style> styleClass
4004                             = getNotificationStyleClass(templateClass);
4005                     if (styleClass == null) {
4006                         Log.d(TAG, "Unknown style class: " + templateClass);
4007                     } else {
4008                         try {
4009                             final Constructor<? extends Style> ctor =
4010                                     styleClass.getDeclaredConstructor();
4011                             ctor.setAccessible(true);
4012                             final Style style = ctor.newInstance();
4013                             style.restoreFromExtras(mN.extras);
4014 
4015                             if (style != null) {
4016                                 setStyle(style);
4017                             }
4018                         } catch (Throwable t) {
4019                             Log.e(TAG, "Could not create Style", t);
4020                         }
4021                     }
4022                 }
4023             }
4024         }
4025 
getColorUtil()4026         private ContrastColorUtil getColorUtil() {
4027             if (mColorUtil == null) {
4028                 mColorUtil = ContrastColorUtil.getInstance(mContext);
4029             }
4030             return mColorUtil;
4031         }
4032 
4033         /**
4034          * From Android 11, messaging notifications (those that use {@link MessagingStyle}) that
4035          * use this method to link to a published long-lived sharing shortcut may appear in a
4036          * dedicated Conversation section of the shade and may show configuration options that
4037          * are unique to conversations. This behavior should be reserved for person to person(s)
4038          * conversations where there is a likely social obligation for an individual to respond.
4039          * <p>
4040          * For example, the following are some examples of notifications that belong in the
4041          * conversation space:
4042          * <ul>
4043          * <li>1:1 conversations between two individuals</li>
4044          * <li>Group conversations between individuals where everyone can contribute</li>
4045          * </ul>
4046          * And the following are some examples of notifications that do not belong in the
4047          * conversation space:
4048          * <ul>
4049          * <li>Advertisements from a bot (even if personal and contextualized)</li>
4050          * <li>Engagement notifications from a bot</li>
4051          * <li>Directional conversations where there is an active speaker and many passive
4052          * individuals</li>
4053          * <li>Stream / posting updates from other individuals</li>
4054          * <li>Email, document comments, or other conversation types that are not real-time</li>
4055          * </ul>
4056          * </p>
4057          *
4058          * <p>
4059          * Additionally, this method can be used for all types of notifications to mark this
4060          * notification as duplicative of a Launcher shortcut. Launchers that show badges or
4061          * notification content may then suppress the shortcut in favor of the content of this
4062          * notification.
4063          * <p>
4064          * If this notification has {@link BubbleMetadata} attached that was created with
4065          * a shortcutId a check will be performed to ensure the shortcutId supplied to bubble
4066          * metadata matches the shortcutId set here, if one was set. If the shortcutId's were
4067          * specified but do not match, an exception is thrown.
4068          *
4069          * @param shortcutId the {@link ShortcutInfo#getId() id} of the shortcut this notification
4070          *                   is linked to
4071          *
4072          * @see BubbleMetadata.Builder#Builder(String)
4073          */
4074         @NonNull
setShortcutId(String shortcutId)4075         public Builder setShortcutId(String shortcutId) {
4076             mN.mShortcutId = shortcutId;
4077             return this;
4078         }
4079 
4080         /**
4081          * Sets the {@link LocusId} associated with this notification.
4082          *
4083          * <p>This method should be called when the {@link LocusId} is used in other places (such
4084          * as {@link ShortcutInfo} and {@link ContentCaptureContext}) so the device's intelligence
4085          * services can correlate them.
4086          */
4087         @NonNull
setLocusId(@ullable LocusId locusId)4088         public Builder setLocusId(@Nullable LocusId locusId) {
4089             mN.mLocusId = locusId;
4090             return this;
4091         }
4092 
4093         /**
4094          * Sets which icon to display as a badge for this notification.
4095          *
4096          * Must be one of {@link #BADGE_ICON_NONE}, {@link #BADGE_ICON_SMALL},
4097          * {@link #BADGE_ICON_LARGE}.
4098          *
4099          * Note: This value might be ignored, for launchers that don't support badge icons.
4100          */
4101         @NonNull
setBadgeIconType(int icon)4102         public Builder setBadgeIconType(int icon) {
4103             mN.mBadgeIcon = icon;
4104             return this;
4105         }
4106 
4107         /**
4108          * Sets the group alert behavior for this notification. Use this method to mute this
4109          * notification if alerts for this notification's group should be handled by a different
4110          * notification. This is only applicable for notifications that belong to a
4111          * {@link #setGroup(String) group}. This must be called on all notifications you want to
4112          * mute. For example, if you want only the summary of your group to make noise and/or peek
4113          * on screen, all children in the group should have the group alert behavior
4114          * {@link #GROUP_ALERT_SUMMARY}.
4115          *
4116          * <p> The default value is {@link #GROUP_ALERT_ALL}.</p>
4117          */
4118         @NonNull
setGroupAlertBehavior(@roupAlertBehavior int groupAlertBehavior)4119         public Builder setGroupAlertBehavior(@GroupAlertBehavior int groupAlertBehavior) {
4120             mN.mGroupAlertBehavior = groupAlertBehavior;
4121             return this;
4122         }
4123 
4124         /**
4125          * Sets the {@link BubbleMetadata} that will be used to display app content in a floating
4126          * window over the existing foreground activity.
4127          *
4128          * <p>This data will be ignored unless the notification is posted to a channel that
4129          * allows {@link NotificationChannel#canBubble() bubbles}.</p>
4130          *
4131          * <p>Notifications allowed to bubble that have valid bubble metadata will display in
4132          * collapsed state outside of the notification shade on unlocked devices. When a user
4133          * interacts with the collapsed state, the bubble intent will be invoked and displayed.</p>
4134          */
4135         @NonNull
setBubbleMetadata(@ullable BubbleMetadata data)4136         public Builder setBubbleMetadata(@Nullable BubbleMetadata data) {
4137             mN.mBubbleMetadata = data;
4138             return this;
4139         }
4140 
4141         /** @removed */
4142         @Deprecated
setChannel(String channelId)4143         public Builder setChannel(String channelId) {
4144             mN.mChannelId = channelId;
4145             return this;
4146         }
4147 
4148         /**
4149          * Specifies the channel the notification should be delivered on.
4150          */
4151         @NonNull
setChannelId(String channelId)4152         public Builder setChannelId(String channelId) {
4153             mN.mChannelId = channelId;
4154             return this;
4155         }
4156 
4157         /** @removed */
4158         @Deprecated
setTimeout(long durationMs)4159         public Builder setTimeout(long durationMs) {
4160             mN.mTimeout = durationMs;
4161             return this;
4162         }
4163 
4164         /**
4165          * Specifies a duration in milliseconds after which this notification should be canceled,
4166          * if it is not already canceled.
4167          */
4168         @NonNull
setTimeoutAfter(long durationMs)4169         public Builder setTimeoutAfter(long durationMs) {
4170             mN.mTimeout = durationMs;
4171             return this;
4172         }
4173 
4174         /**
4175          * Add a timestamp pertaining to the notification (usually the time the event occurred).
4176          *
4177          * For apps targeting {@link android.os.Build.VERSION_CODES#N} and above, this time is not
4178          * shown anymore by default and must be opted into by using
4179          * {@link android.app.Notification.Builder#setShowWhen(boolean)}
4180          *
4181          * @see Notification#when
4182          */
4183         @NonNull
setWhen(long when)4184         public Builder setWhen(long when) {
4185             mN.when = when;
4186             return this;
4187         }
4188 
4189         /**
4190          * Control whether the timestamp set with {@link #setWhen(long) setWhen} is shown
4191          * in the content view.
4192          * For apps targeting {@link android.os.Build.VERSION_CODES#N} and above, this defaults to
4193          * {@code false}. For earlier apps, the default is {@code true}.
4194          */
4195         @NonNull
setShowWhen(boolean show)4196         public Builder setShowWhen(boolean show) {
4197             mN.extras.putBoolean(EXTRA_SHOW_WHEN, show);
4198             return this;
4199         }
4200 
4201         /**
4202          * Show the {@link Notification#when} field as a stopwatch.
4203          *
4204          * Instead of presenting <code>when</code> as a timestamp, the notification will show an
4205          * automatically updating display of the minutes and seconds since <code>when</code>.
4206          *
4207          * Useful when showing an elapsed time (like an ongoing phone call).
4208          *
4209          * The counter can also be set to count down to <code>when</code> when using
4210          * {@link #setChronometerCountDown(boolean)}.
4211          *
4212          * @see android.widget.Chronometer
4213          * @see Notification#when
4214          * @see #setChronometerCountDown(boolean)
4215          */
4216         @NonNull
setUsesChronometer(boolean b)4217         public Builder setUsesChronometer(boolean b) {
4218             mN.extras.putBoolean(EXTRA_SHOW_CHRONOMETER, b);
4219             return this;
4220         }
4221 
4222         /**
4223          * Sets the Chronometer to count down instead of counting up.
4224          *
4225          * <p>This is only relevant if {@link #setUsesChronometer(boolean)} has been set to true.
4226          * If it isn't set the chronometer will count up.
4227          *
4228          * @see #setUsesChronometer(boolean)
4229          */
4230         @NonNull
setChronometerCountDown(boolean countDown)4231         public Builder setChronometerCountDown(boolean countDown) {
4232             mN.extras.putBoolean(EXTRA_CHRONOMETER_COUNT_DOWN, countDown);
4233             return this;
4234         }
4235 
4236         /**
4237          * Set the small icon resource, which will be used to represent the notification in the
4238          * status bar.
4239          *
4240 
4241          * The platform template for the expanded view will draw this icon in the left, unless a
4242          * {@link #setLargeIcon(Bitmap) large icon} has also been specified, in which case the small
4243          * icon will be moved to the right-hand side.
4244          *
4245 
4246          * @param icon
4247          *            A resource ID in the application's package of the drawable to use.
4248          * @see Notification#icon
4249          */
4250         @NonNull
setSmallIcon(@rawableRes int icon)4251         public Builder setSmallIcon(@DrawableRes int icon) {
4252             return setSmallIcon(icon != 0
4253                     ? Icon.createWithResource(mContext, icon)
4254                     : null);
4255         }
4256 
4257         /**
4258          * A variant of {@link #setSmallIcon(int) setSmallIcon(int)} that takes an additional
4259          * level parameter for when the icon is a {@link android.graphics.drawable.LevelListDrawable
4260          * LevelListDrawable}.
4261          *
4262          * @param icon A resource ID in the application's package of the drawable to use.
4263          * @param level The level to use for the icon.
4264          *
4265          * @see Notification#icon
4266          * @see Notification#iconLevel
4267          */
4268         @NonNull
setSmallIcon(@rawableRes int icon, int level)4269         public Builder setSmallIcon(@DrawableRes int icon, int level) {
4270             mN.iconLevel = level;
4271             return setSmallIcon(icon);
4272         }
4273 
4274         /**
4275          * Set the small icon, which will be used to represent the notification in the
4276          * status bar and content view (unless overridden there by a
4277          * {@link #setLargeIcon(Bitmap) large icon}).
4278          *
4279          * @param icon An Icon object to use.
4280          * @see Notification#icon
4281          */
4282         @NonNull
setSmallIcon(Icon icon)4283         public Builder setSmallIcon(Icon icon) {
4284             mN.setSmallIcon(icon);
4285             if (icon != null && icon.getType() == Icon.TYPE_RESOURCE) {
4286                 mN.icon = icon.getResId();
4287             }
4288             return this;
4289         }
4290 
4291         /**
4292          * Set the first line of text in the platform notification template.
4293          */
4294         @NonNull
setContentTitle(CharSequence title)4295         public Builder setContentTitle(CharSequence title) {
4296             mN.extras.putCharSequence(EXTRA_TITLE, safeCharSequence(title));
4297             return this;
4298         }
4299 
4300         /**
4301          * Set the second line of text in the platform notification template.
4302          */
4303         @NonNull
setContentText(CharSequence text)4304         public Builder setContentText(CharSequence text) {
4305             mN.extras.putCharSequence(EXTRA_TEXT, safeCharSequence(text));
4306             return this;
4307         }
4308 
4309         /**
4310          * This provides some additional information that is displayed in the notification. No
4311          * guarantees are given where exactly it is displayed.
4312          *
4313          * <p>This information should only be provided if it provides an essential
4314          * benefit to the understanding of the notification. The more text you provide the
4315          * less readable it becomes. For example, an email client should only provide the account
4316          * name here if more than one email account has been added.</p>
4317          *
4318          * <p>As of {@link android.os.Build.VERSION_CODES#N} this information is displayed in the
4319          * notification header area.
4320          *
4321          * On Android versions before {@link android.os.Build.VERSION_CODES#N}
4322          * this will be shown in the third line of text in the platform notification template.
4323          * You should not be using {@link #setProgress(int, int, boolean)} at the
4324          * same time on those versions; they occupy the same place.
4325          * </p>
4326          */
4327         @NonNull
setSubText(CharSequence text)4328         public Builder setSubText(CharSequence text) {
4329             mN.extras.putCharSequence(EXTRA_SUB_TEXT, safeCharSequence(text));
4330             return this;
4331         }
4332 
4333         /**
4334          * Provides text that will appear as a link to your application's settings.
4335          *
4336          * <p>This text does not appear within notification {@link Style templates} but may
4337          * appear when the user uses an affordance to learn more about the notification.
4338          * Additionally, this text will not appear unless you provide a valid link target by
4339          * handling {@link #INTENT_CATEGORY_NOTIFICATION_PREFERENCES}.
4340          *
4341          * <p>This text is meant to be concise description about what the user can customize
4342          * when they click on this link. The recommended maximum length is 40 characters.
4343          * @param text
4344          * @return
4345          */
4346         @NonNull
setSettingsText(CharSequence text)4347         public Builder setSettingsText(CharSequence text) {
4348             mN.mSettingsText = safeCharSequence(text);
4349             return this;
4350         }
4351 
4352         /**
4353          * Set the remote input history.
4354          *
4355          * This should be set to the most recent inputs that have been sent
4356          * through a {@link RemoteInput} of this Notification and cleared once the it is no
4357          * longer relevant (e.g. for chat notifications once the other party has responded).
4358          *
4359          * The most recent input must be stored at the 0 index, the second most recent at the
4360          * 1 index, etc. Note that the system will limit both how far back the inputs will be shown
4361          * and how much of each individual input is shown.
4362          *
4363          * <p>Note: The reply text will only be shown on notifications that have least one action
4364          * with a {@code RemoteInput}.</p>
4365          */
4366         @NonNull
setRemoteInputHistory(CharSequence[] text)4367         public Builder setRemoteInputHistory(CharSequence[] text) {
4368             if (text == null) {
4369                 mN.extras.putCharSequenceArray(EXTRA_REMOTE_INPUT_HISTORY, null);
4370             } else {
4371                 final int itemCount = Math.min(MAX_REPLY_HISTORY, text.length);
4372                 CharSequence[] safe = new CharSequence[itemCount];
4373                 RemoteInputHistoryItem[] items = new RemoteInputHistoryItem[itemCount];
4374                 for (int i = 0; i < itemCount; i++) {
4375                     safe[i] = safeCharSequence(text[i]);
4376                     items[i] = new RemoteInputHistoryItem(text[i]);
4377                 }
4378                 mN.extras.putCharSequenceArray(EXTRA_REMOTE_INPUT_HISTORY, safe);
4379 
4380                 // Also add these messages as structured history items.
4381                 mN.extras.putParcelableArray(EXTRA_REMOTE_INPUT_HISTORY_ITEMS, items);
4382             }
4383             return this;
4384         }
4385 
4386         /**
4387          * Set the remote input history, with support for embedding URIs and mime types for
4388          * images and other media.
4389          * @hide
4390          */
4391         @NonNull
setRemoteInputHistory(RemoteInputHistoryItem[] items)4392         public Builder setRemoteInputHistory(RemoteInputHistoryItem[] items) {
4393             if (items == null) {
4394                 mN.extras.putParcelableArray(EXTRA_REMOTE_INPUT_HISTORY_ITEMS, null);
4395             } else {
4396                 final int itemCount = Math.min(MAX_REPLY_HISTORY, items.length);
4397                 RemoteInputHistoryItem[] history = new RemoteInputHistoryItem[itemCount];
4398                 for (int i = 0; i < itemCount; i++) {
4399                     history[i] = items[i];
4400                 }
4401                 mN.extras.putParcelableArray(EXTRA_REMOTE_INPUT_HISTORY_ITEMS, history);
4402             }
4403             return this;
4404         }
4405 
4406         /**
4407          * Sets whether remote history entries view should have a spinner.
4408          * @hide
4409          */
4410         @NonNull
setShowRemoteInputSpinner(boolean showSpinner)4411         public Builder setShowRemoteInputSpinner(boolean showSpinner) {
4412             mN.extras.putBoolean(EXTRA_SHOW_REMOTE_INPUT_SPINNER, showSpinner);
4413             return this;
4414         }
4415 
4416         /**
4417          * Sets whether smart reply buttons should be hidden.
4418          * @hide
4419          */
4420         @NonNull
setHideSmartReplies(boolean hideSmartReplies)4421         public Builder setHideSmartReplies(boolean hideSmartReplies) {
4422             mN.extras.putBoolean(EXTRA_HIDE_SMART_REPLIES, hideSmartReplies);
4423             return this;
4424         }
4425 
4426         /**
4427          * Sets the number of items this notification represents. May be displayed as a badge count
4428          * for Launchers that support badging.
4429          */
4430         @NonNull
setNumber(int number)4431         public Builder setNumber(int number) {
4432             mN.number = number;
4433             return this;
4434         }
4435 
4436         /**
4437          * A small piece of additional information pertaining to this notification.
4438          *
4439          * The platform template will draw this on the last line of the notification, at the far
4440          * right (to the right of a smallIcon if it has been placed there).
4441          *
4442          * @deprecated use {@link #setSubText(CharSequence)} instead to set a text in the header.
4443          * For legacy apps targeting a version below {@link android.os.Build.VERSION_CODES#N} this
4444          * field will still show up, but the subtext will take precedence.
4445          */
4446         @Deprecated
setContentInfo(CharSequence info)4447         public Builder setContentInfo(CharSequence info) {
4448             mN.extras.putCharSequence(EXTRA_INFO_TEXT, safeCharSequence(info));
4449             return this;
4450         }
4451 
4452         /**
4453          * Set the progress this notification represents.
4454          *
4455          * The platform template will represent this using a {@link ProgressBar}.
4456          */
4457         @NonNull
setProgress(int max, int progress, boolean indeterminate)4458         public Builder setProgress(int max, int progress, boolean indeterminate) {
4459             mN.extras.putInt(EXTRA_PROGRESS, progress);
4460             mN.extras.putInt(EXTRA_PROGRESS_MAX, max);
4461             mN.extras.putBoolean(EXTRA_PROGRESS_INDETERMINATE, indeterminate);
4462             return this;
4463         }
4464 
4465         /**
4466          * Supply a custom RemoteViews to use instead of the platform template.
4467          *
4468          * Use {@link #setCustomContentView(RemoteViews)} instead.
4469          */
4470         @Deprecated
setContent(RemoteViews views)4471         public Builder setContent(RemoteViews views) {
4472             return setCustomContentView(views);
4473         }
4474 
4475         /**
4476          * Supply custom RemoteViews to use instead of the platform template.
4477          *
4478          * This will override the layout that would otherwise be constructed by this Builder
4479          * object.
4480          */
4481         @NonNull
setCustomContentView(RemoteViews contentView)4482         public Builder setCustomContentView(RemoteViews contentView) {
4483             mN.contentView = contentView;
4484             return this;
4485         }
4486 
4487         /**
4488          * Supply custom RemoteViews to use instead of the platform template in the expanded form.
4489          *
4490          * This will override the expanded layout that would otherwise be constructed by this
4491          * Builder object.
4492          */
4493         @NonNull
setCustomBigContentView(RemoteViews contentView)4494         public Builder setCustomBigContentView(RemoteViews contentView) {
4495             mN.bigContentView = contentView;
4496             return this;
4497         }
4498 
4499         /**
4500          * Supply custom RemoteViews to use instead of the platform template in the heads up dialog.
4501          *
4502          * This will override the heads-up layout that would otherwise be constructed by this
4503          * Builder object.
4504          */
4505         @NonNull
setCustomHeadsUpContentView(RemoteViews contentView)4506         public Builder setCustomHeadsUpContentView(RemoteViews contentView) {
4507             mN.headsUpContentView = contentView;
4508             return this;
4509         }
4510 
4511         /**
4512          * Supply a {@link PendingIntent} to be sent when the notification is clicked.
4513          *
4514          * <p>As of Android {@link android.os.Build.VERSION_CODES#S}, apps targeting API level
4515          * {@link android.os.Build.VERSION_CODES#S} or higher won't be able to start activities
4516          * while processing broadcast receivers or services in response to notification clicks. To
4517          * launch an activity in those cases, provide a {@link PendingIntent} for the activity
4518          * itself.
4519          *
4520          * <p>As of {@link android.os.Build.VERSION_CODES#HONEYCOMB}, if this field is unset and you
4521          * have specified a custom RemoteViews with {@link #setContent(RemoteViews)}, you can use
4522          * {@link RemoteViews#setOnClickPendingIntent RemoteViews.setOnClickPendingIntent(int,PendingIntent)}
4523          * to assign PendingIntents to individual views in that custom layout (i.e., to create
4524          * clickable buttons inside the notification view).
4525          *
4526          * @see Notification#contentIntent Notification.contentIntent
4527          */
4528         @NonNull
setContentIntent(PendingIntent intent)4529         public Builder setContentIntent(PendingIntent intent) {
4530             mN.contentIntent = intent;
4531             return this;
4532         }
4533 
4534         /**
4535          * Supply a {@link PendingIntent} to send when the notification is cleared explicitly by the user.
4536          *
4537          * @see Notification#deleteIntent
4538          */
4539         @NonNull
setDeleteIntent(PendingIntent intent)4540         public Builder setDeleteIntent(PendingIntent intent) {
4541             mN.deleteIntent = intent;
4542             return this;
4543         }
4544 
4545         /**
4546          * An intent to launch instead of posting the notification to the status bar.
4547          * Only for use with extremely high-priority notifications demanding the user's
4548          * <strong>immediate</strong> attention, such as an incoming phone call or
4549          * alarm clock that the user has explicitly set to a particular time.
4550          * If this facility is used for something else, please give the user an option
4551          * to turn it off and use a normal notification, as this can be extremely
4552          * disruptive.
4553          *
4554          * <p>
4555          * The system UI may choose to display a heads-up notification, instead of
4556          * launching this intent, while the user is using the device.
4557          * </p>
4558          * <p>Apps targeting {@link Build.VERSION_CODES#Q} and above will have to request
4559          * a permission ({@link android.Manifest.permission#USE_FULL_SCREEN_INTENT}) in order to
4560          * use full screen intents.</p>
4561          * <p>
4562          * To be launched as a full screen intent, the notification must also be posted to a
4563          * channel with importance level set to IMPORTANCE_HIGH or higher.
4564          * </p>
4565          *
4566          * @param intent The pending intent to launch.
4567          * @param highPriority Passing true will cause this notification to be sent
4568          *          even if other notifications are suppressed.
4569          *
4570          * @see Notification#fullScreenIntent
4571          */
4572         @NonNull
setFullScreenIntent(PendingIntent intent, boolean highPriority)4573         public Builder setFullScreenIntent(PendingIntent intent, boolean highPriority) {
4574             mN.fullScreenIntent = intent;
4575             setFlag(FLAG_HIGH_PRIORITY, highPriority);
4576             return this;
4577         }
4578 
4579         /**
4580          * Set the "ticker" text which is sent to accessibility services.
4581          *
4582          * @see Notification#tickerText
4583          */
4584         @NonNull
setTicker(CharSequence tickerText)4585         public Builder setTicker(CharSequence tickerText) {
4586             mN.tickerText = safeCharSequence(tickerText);
4587             return this;
4588         }
4589 
4590         /**
4591          * Obsolete version of {@link #setTicker(CharSequence)}.
4592          *
4593          */
4594         @Deprecated
setTicker(CharSequence tickerText, RemoteViews views)4595         public Builder setTicker(CharSequence tickerText, RemoteViews views) {
4596             setTicker(tickerText);
4597             // views is ignored
4598             return this;
4599         }
4600 
4601         /**
4602          * Add a large icon to the notification content view.
4603          *
4604          * In the platform template, this image will be shown either on the right of the
4605          * notification, with an aspect ratio of up to 16:9, or (when the notification is grouped)
4606          * on the left in place of the {@link #setSmallIcon(Icon) small icon}.
4607          */
4608         @NonNull
setLargeIcon(Bitmap b)4609         public Builder setLargeIcon(Bitmap b) {
4610             return setLargeIcon(b != null ? Icon.createWithBitmap(b) : null);
4611         }
4612 
4613         /**
4614          * Add a large icon to the notification content view.
4615          *
4616          * In the platform template, this image will be shown either on the right of the
4617          * notification, with an aspect ratio of up to 16:9, or (when the notification is grouped)
4618          * on the left in place of the {@link #setSmallIcon(Icon) small icon}.
4619          */
4620         @NonNull
setLargeIcon(Icon icon)4621         public Builder setLargeIcon(Icon icon) {
4622             mN.mLargeIcon = icon;
4623             mN.extras.putParcelable(EXTRA_LARGE_ICON, icon);
4624             return this;
4625         }
4626 
4627         /**
4628          * Set the sound to play.
4629          *
4630          * It will be played using the {@link #AUDIO_ATTRIBUTES_DEFAULT default audio attributes}
4631          * for notifications.
4632          *
4633          * @deprecated use {@link NotificationChannel#setSound(Uri, AudioAttributes)} instead.
4634          */
4635         @Deprecated
setSound(Uri sound)4636         public Builder setSound(Uri sound) {
4637             mN.sound = sound;
4638             mN.audioAttributes = AUDIO_ATTRIBUTES_DEFAULT;
4639             return this;
4640         }
4641 
4642         /**
4643          * Set the sound to play, along with a specific stream on which to play it.
4644          *
4645          * See {@link android.media.AudioManager} for the <code>STREAM_</code> constants.
4646          *
4647          * @deprecated use {@link NotificationChannel#setSound(Uri, AudioAttributes)}.
4648          */
4649         @Deprecated
setSound(Uri sound, int streamType)4650         public Builder setSound(Uri sound, int streamType) {
4651             PlayerBase.deprecateStreamTypeForPlayback(streamType, "Notification", "setSound()");
4652             mN.sound = sound;
4653             mN.audioStreamType = streamType;
4654             return this;
4655         }
4656 
4657         /**
4658          * Set the sound to play, along with specific {@link AudioAttributes audio attributes} to
4659          * use during playback.
4660          *
4661          * @deprecated use {@link NotificationChannel#setSound(Uri, AudioAttributes)} instead.
4662          * @see Notification#sound
4663          */
4664         @Deprecated
setSound(Uri sound, AudioAttributes audioAttributes)4665         public Builder setSound(Uri sound, AudioAttributes audioAttributes) {
4666             mN.sound = sound;
4667             mN.audioAttributes = audioAttributes;
4668             return this;
4669         }
4670 
4671         /**
4672          * Set the vibration pattern to use.
4673          *
4674          * See {@link android.os.Vibrator#vibrate(long[], int)} for a discussion of the
4675          * <code>pattern</code> parameter.
4676          *
4677          * <p>
4678          * A notification that vibrates is more likely to be presented as a heads-up notification.
4679          * </p>
4680          *
4681          * @deprecated use {@link NotificationChannel#setVibrationPattern(long[])} instead.
4682          * @see Notification#vibrate
4683          */
4684         @Deprecated
setVibrate(long[] pattern)4685         public Builder setVibrate(long[] pattern) {
4686             mN.vibrate = pattern;
4687             return this;
4688         }
4689 
4690         /**
4691          * Set the desired color for the indicator LED on the device, as well as the
4692          * blink duty cycle (specified in milliseconds).
4693          *
4694 
4695          * Not all devices will honor all (or even any) of these values.
4696          *
4697          * @deprecated use {@link NotificationChannel#enableLights(boolean)} instead.
4698          * @see Notification#ledARGB
4699          * @see Notification#ledOnMS
4700          * @see Notification#ledOffMS
4701          */
4702         @Deprecated
setLights(@olorInt int argb, int onMs, int offMs)4703         public Builder setLights(@ColorInt int argb, int onMs, int offMs) {
4704             mN.ledARGB = argb;
4705             mN.ledOnMS = onMs;
4706             mN.ledOffMS = offMs;
4707             if (onMs != 0 || offMs != 0) {
4708                 mN.flags |= FLAG_SHOW_LIGHTS;
4709             }
4710             return this;
4711         }
4712 
4713         /**
4714          * Set whether this is an "ongoing" notification.
4715          *
4716          * Ongoing notifications cannot be dismissed by the user on locked devices, or by
4717          * notification listeners, and some notifications (call, device management, media) cannot
4718          * be dismissed on unlocked devices, so your application or service must take care of
4719          * canceling them.
4720          *
4721          * They are typically used to indicate a background task that the user is actively engaged
4722          * with (e.g., playing music) or is pending in some way and therefore occupying the device
4723          * (e.g., a file download, sync operation, active network connection).
4724          *
4725          * @see Notification#FLAG_ONGOING_EVENT
4726          */
4727         @NonNull
setOngoing(boolean ongoing)4728         public Builder setOngoing(boolean ongoing) {
4729             setFlag(FLAG_ONGOING_EVENT, ongoing);
4730             return this;
4731         }
4732 
4733         /**
4734          * Set whether this notification should be colorized. When set, the color set with
4735          * {@link #setColor(int)} will be used as the background color of this notification.
4736          * <p>
4737          * This should only be used for high priority ongoing tasks like navigation, an ongoing
4738          * call, or other similarly high-priority events for the user.
4739          * <p>
4740          * For most styles, the coloring will only be applied if the notification is for a
4741          * foreground service notification.
4742          * However, for {@link MediaStyle} and {@link DecoratedMediaCustomViewStyle} notifications
4743          * that have a media session attached there is no such requirement.
4744          *
4745          * @see #setColor(int)
4746          * @see MediaStyle#setMediaSession(MediaSession.Token)
4747          */
4748         @NonNull
setColorized(boolean colorize)4749         public Builder setColorized(boolean colorize) {
4750             mN.extras.putBoolean(EXTRA_COLORIZED, colorize);
4751             return this;
4752         }
4753 
4754         /**
4755          * Set this flag if you would only like the sound, vibrate
4756          * and ticker to be played if the notification is not already showing.
4757          *
4758          * Note that using this flag will stop any ongoing alerting behaviour such
4759          * as sound, vibration or blinking notification LED.
4760          *
4761          * @see Notification#FLAG_ONLY_ALERT_ONCE
4762          */
4763         @NonNull
setOnlyAlertOnce(boolean onlyAlertOnce)4764         public Builder setOnlyAlertOnce(boolean onlyAlertOnce) {
4765             setFlag(FLAG_ONLY_ALERT_ONCE, onlyAlertOnce);
4766             return this;
4767         }
4768 
4769         /**
4770          * Specify a desired visibility policy for a Notification associated with a
4771          * foreground service.  By default, the system can choose to defer
4772          * visibility of the notification for a short time after the service is
4773          * started.  Pass
4774          * {@link Notification#FOREGROUND_SERVICE_IMMEDIATE FOREGROUND_SERVICE_IMMEDIATE}
4775          * to this method in order to guarantee that visibility is never deferred.  Pass
4776          * {@link Notification#FOREGROUND_SERVICE_DEFERRED FOREGROUND_SERVICE_DEFERRED}
4777          * to request that visibility is deferred whenever possible.
4778          *
4779          * <p class="note">Note that deferred visibility is not guaranteed.  There
4780          * may be some circumstances under which the system will show the foreground
4781          * service's associated Notification immediately even when the app has used
4782          * this method to explicitly request deferred display.</p>
4783          * @param behavior One of
4784          * {@link Notification#FOREGROUND_SERVICE_DEFAULT FOREGROUND_SERVICE_DEFAULT},
4785          * {@link Notification#FOREGROUND_SERVICE_IMMEDIATE FOREGROUND_SERVICE_IMMEDIATE},
4786          * or {@link Notification#FOREGROUND_SERVICE_DEFERRED FOREGROUND_SERVICE_DEFERRED}
4787          * @return
4788          */
4789         @NonNull
setForegroundServiceBehavior(@erviceNotificationPolicy int behavior)4790         public Builder setForegroundServiceBehavior(@ServiceNotificationPolicy int behavior) {
4791             mN.mFgsDeferBehavior = behavior;
4792             return this;
4793         }
4794 
4795         /**
4796          * Make this notification automatically dismissed when the user touches it.
4797          *
4798          * @see Notification#FLAG_AUTO_CANCEL
4799          */
4800         @NonNull
setAutoCancel(boolean autoCancel)4801         public Builder setAutoCancel(boolean autoCancel) {
4802             setFlag(FLAG_AUTO_CANCEL, autoCancel);
4803             return this;
4804         }
4805 
4806         /**
4807          * Set whether or not this notification should not bridge to other devices.
4808          *
4809          * <p>Some notifications can be bridged to other devices for remote display.
4810          * This hint can be set to recommend this notification not be bridged.
4811          */
4812         @NonNull
setLocalOnly(boolean localOnly)4813         public Builder setLocalOnly(boolean localOnly) {
4814             setFlag(FLAG_LOCAL_ONLY, localOnly);
4815             return this;
4816         }
4817 
4818         /**
4819          * Set which notification properties will be inherited from system defaults.
4820          * <p>
4821          * The value should be one or more of the following fields combined with
4822          * bitwise-or:
4823          * {@link #DEFAULT_SOUND}, {@link #DEFAULT_VIBRATE}, {@link #DEFAULT_LIGHTS}.
4824          * <p>
4825          * For all default values, use {@link #DEFAULT_ALL}.
4826          *
4827          * @deprecated use {@link NotificationChannel#enableVibration(boolean)} and
4828          * {@link NotificationChannel#enableLights(boolean)} and
4829          * {@link NotificationChannel#setSound(Uri, AudioAttributes)} instead.
4830          */
4831         @Deprecated
setDefaults(int defaults)4832         public Builder setDefaults(int defaults) {
4833             mN.defaults = defaults;
4834             return this;
4835         }
4836 
4837         /**
4838          * Set the priority of this notification.
4839          *
4840          * @see Notification#priority
4841          * @deprecated use {@link NotificationChannel#setImportance(int)} instead.
4842          */
4843         @Deprecated
setPriority(@riority int pri)4844         public Builder setPriority(@Priority int pri) {
4845             mN.priority = pri;
4846             return this;
4847         }
4848 
4849         /**
4850          * Set the notification category.
4851          *
4852          * @see Notification#category
4853          */
4854         @NonNull
setCategory(String category)4855         public Builder setCategory(String category) {
4856             mN.category = category;
4857             return this;
4858         }
4859 
4860         /**
4861          * Add a person that is relevant to this notification.
4862          *
4863          * <P>
4864          * Depending on user preferences, this annotation may allow the notification to pass
4865          * through interruption filters, if this notification is of category {@link #CATEGORY_CALL}
4866          * or {@link #CATEGORY_MESSAGE}. The addition of people may also cause this notification to
4867          * appear more prominently in the user interface.
4868          * </P>
4869          *
4870          * <P>
4871          * The person should be specified by the {@code String} representation of a
4872          * {@link android.provider.ContactsContract.Contacts#CONTENT_LOOKUP_URI}.
4873          * </P>
4874          *
4875          * <P>The system will also attempt to resolve {@code mailto:} and {@code tel:} schema
4876          * URIs.  The path part of these URIs must exist in the contacts database, in the
4877          * appropriate column, or the reference will be discarded as invalid. Telephone schema
4878          * URIs will be resolved by {@link android.provider.ContactsContract.PhoneLookup}.
4879          * It is also possible to provide a URI with the schema {@code name:} in order to uniquely
4880          * identify a person without an entry in the contacts database.
4881          * </P>
4882          *
4883          * @param uri A URI for the person.
4884          * @see Notification#EXTRA_PEOPLE
4885          * @deprecated use {@link #addPerson(Person)}
4886          */
addPerson(String uri)4887         public Builder addPerson(String uri) {
4888             addPerson(new Person.Builder().setUri(uri).build());
4889             return this;
4890         }
4891 
4892         /**
4893          * Add a person that is relevant to this notification.
4894          *
4895          * <P>
4896          * Depending on user preferences, this annotation may allow the notification to pass
4897          * through interruption filters, if this notification is of category {@link #CATEGORY_CALL}
4898          * or {@link #CATEGORY_MESSAGE}. The addition of people may also cause this notification to
4899          * appear more prominently in the user interface.
4900          * </P>
4901          *
4902          * <P>
4903          * A person should usually contain a uri in order to benefit from the ranking boost.
4904          * However, even if no uri is provided, it's beneficial to provide other people in the
4905          * notification, such that listeners and voice only devices can announce and handle them
4906          * properly.
4907          * </P>
4908          *
4909          * @param person the person to add.
4910          * @see Notification#EXTRA_PEOPLE_LIST
4911          */
4912         @NonNull
addPerson(Person person)4913         public Builder addPerson(Person person) {
4914             mPersonList.add(person);
4915             return this;
4916         }
4917 
4918         /**
4919          * Set this notification to be part of a group of notifications sharing the same key.
4920          * Grouped notifications may display in a cluster or stack on devices which
4921          * support such rendering.
4922          *
4923          * <p>To make this notification the summary for its group, also call
4924          * {@link #setGroupSummary}. A sort order can be specified for group members by using
4925          * {@link #setSortKey}.
4926          * @param groupKey The group key of the group.
4927          * @return this object for method chaining
4928          */
4929         @NonNull
setGroup(String groupKey)4930         public Builder setGroup(String groupKey) {
4931             mN.mGroupKey = groupKey;
4932             return this;
4933         }
4934 
4935         /**
4936          * Set this notification to be the group summary for a group of notifications.
4937          * Grouped notifications may display in a cluster or stack on devices which
4938          * support such rendering. If thereRequires a group key also be set using {@link #setGroup}.
4939          * The group summary may be suppressed if too few notifications are included in the group.
4940          * @param isGroupSummary Whether this notification should be a group summary.
4941          * @return this object for method chaining
4942          */
4943         @NonNull
setGroupSummary(boolean isGroupSummary)4944         public Builder setGroupSummary(boolean isGroupSummary) {
4945             setFlag(FLAG_GROUP_SUMMARY, isGroupSummary);
4946             return this;
4947         }
4948 
4949         /**
4950          * Set a sort key that orders this notification among other notifications from the
4951          * same package. This can be useful if an external sort was already applied and an app
4952          * would like to preserve this. Notifications will be sorted lexicographically using this
4953          * value, although providing different priorities in addition to providing sort key may
4954          * cause this value to be ignored.
4955          *
4956          * <p>This sort key can also be used to order members of a notification group. See
4957          * {@link #setGroup}.
4958          *
4959          * @see String#compareTo(String)
4960          */
4961         @NonNull
setSortKey(String sortKey)4962         public Builder setSortKey(String sortKey) {
4963             mN.mSortKey = sortKey;
4964             return this;
4965         }
4966 
4967         /**
4968          * Merge additional metadata into this notification.
4969          *
4970          * <p>Values within the Bundle will replace existing extras values in this Builder.
4971          *
4972          * @see Notification#extras
4973          */
4974         @NonNull
addExtras(Bundle extras)4975         public Builder addExtras(Bundle extras) {
4976             if (extras != null) {
4977                 mUserExtras.putAll(extras);
4978             }
4979             return this;
4980         }
4981 
4982         /**
4983          * Set metadata for this notification.
4984          *
4985          * <p>A reference to the Bundle is held for the lifetime of this Builder, and the Bundle's
4986          * current contents are copied into the Notification each time {@link #build()} is
4987          * called.
4988          *
4989          * <p>Replaces any existing extras values with those from the provided Bundle.
4990          * Use {@link #addExtras} to merge in metadata instead.
4991          *
4992          * @see Notification#extras
4993          */
4994         @NonNull
setExtras(Bundle extras)4995         public Builder setExtras(Bundle extras) {
4996             if (extras != null) {
4997                 mUserExtras = extras;
4998             }
4999             return this;
5000         }
5001 
5002         /**
5003          * Get the current metadata Bundle used by this notification Builder.
5004          *
5005          * <p>The returned Bundle is shared with this Builder.
5006          *
5007          * <p>The current contents of this Bundle are copied into the Notification each time
5008          * {@link #build()} is called.
5009          *
5010          * @see Notification#extras
5011          */
getExtras()5012         public Bundle getExtras() {
5013             return mUserExtras;
5014         }
5015 
5016         /**
5017          * Add an action to this notification. Actions are typically displayed by
5018          * the system as a button adjacent to the notification content.
5019          * <p>
5020          * Every action must have an icon (32dp square and matching the
5021          * <a href="{@docRoot}design/style/iconography.html#action-bar">Holo
5022          * Dark action bar</a> visual style), a textual label, and a {@link PendingIntent}.
5023          * <p>
5024          * A notification in its expanded form can display up to 3 actions, from left to right in
5025          * the order they were added. Actions will not be displayed when the notification is
5026          * collapsed, however, so be sure that any essential functions may be accessed by the user
5027          * in some other way (for example, in the Activity pointed to by {@link #contentIntent}).
5028          * <p>
5029          * As of Android {@link android.os.Build.VERSION_CODES#S}, apps targeting API level
5030          * {@link android.os.Build.VERSION_CODES#S} or higher won't be able to start activities
5031          * while processing broadcast receivers or services in response to notification action
5032          * clicks. To launch an activity in those cases, provide a {@link PendingIntent} to the
5033          * activity itself.
5034          * <p>
5035          * As of Android {@link android.os.Build.VERSION_CODES#N},
5036          * action button icons will not be displayed on action buttons, but are still required
5037          * and are available to
5038          * {@link android.service.notification.NotificationListenerService notification listeners},
5039          * which may display them in other contexts, for example on a wearable device.
5040          *
5041          * @param icon Resource ID of a drawable that represents the action.
5042          * @param title Text describing the action.
5043          * @param intent PendingIntent to be fired when the action is invoked.
5044          *
5045          * @deprecated Use {@link #addAction(Action)} instead.
5046          */
5047         @Deprecated
addAction(int icon, CharSequence title, PendingIntent intent)5048         public Builder addAction(int icon, CharSequence title, PendingIntent intent) {
5049             mActions.add(new Action(icon, safeCharSequence(title), intent));
5050             return this;
5051         }
5052 
5053         /**
5054          * Add an action to this notification. Actions are typically displayed by
5055          * the system as a button adjacent to the notification content.
5056          * <p>
5057          * Every action must have an icon (32dp square and matching the
5058          * <a href="{@docRoot}design/style/iconography.html#action-bar">Holo
5059          * Dark action bar</a> visual style), a textual label, and a {@link PendingIntent}.
5060          * <p>
5061          * A notification in its expanded form can display up to 3 actions, from left to right in
5062          * the order they were added. Actions will not be displayed when the notification is
5063          * collapsed, however, so be sure that any essential functions may be accessed by the user
5064          * in some other way (for example, in the Activity pointed to by {@link #contentIntent}).
5065          *
5066          * @param action The action to add.
5067          */
5068         @NonNull
addAction(Action action)5069         public Builder addAction(Action action) {
5070             if (action != null) {
5071                 mActions.add(action);
5072             }
5073             return this;
5074         }
5075 
5076         /**
5077          * Alter the complete list of actions attached to this notification.
5078          * @see #addAction(Action).
5079          *
5080          * @param actions
5081          * @return
5082          */
5083         @NonNull
setActions(Action... actions)5084         public Builder setActions(Action... actions) {
5085             mActions.clear();
5086             for (int i = 0; i < actions.length; i++) {
5087                 if (actions[i] != null) {
5088                     mActions.add(actions[i]);
5089                 }
5090             }
5091             return this;
5092         }
5093 
5094         /**
5095          * Add a rich notification style to be applied at build time.
5096          *
5097          * @param style Object responsible for modifying the notification style.
5098          */
5099         @NonNull
setStyle(Style style)5100         public Builder setStyle(Style style) {
5101             if (mStyle != style) {
5102                 mStyle = style;
5103                 if (mStyle != null) {
5104                     mStyle.setBuilder(this);
5105                     mN.extras.putString(EXTRA_TEMPLATE, style.getClass().getName());
5106                 }  else {
5107                     mN.extras.remove(EXTRA_TEMPLATE);
5108                 }
5109             }
5110             return this;
5111         }
5112 
5113         /**
5114          * Returns the style set by {@link #setStyle(Style)}.
5115          */
getStyle()5116         public Style getStyle() {
5117             return mStyle;
5118         }
5119 
5120         /**
5121          * Specify the value of {@link #visibility}.
5122          *
5123          * @return The same Builder.
5124          */
5125         @NonNull
setVisibility(@isibility int visibility)5126         public Builder setVisibility(@Visibility int visibility) {
5127             mN.visibility = visibility;
5128             return this;
5129         }
5130 
5131         /**
5132          * Supply a replacement Notification whose contents should be shown in insecure contexts
5133          * (i.e. atop the secure lockscreen). See {@link #visibility} and {@link #VISIBILITY_PUBLIC}.
5134          * @param n A replacement notification, presumably with some or all info redacted.
5135          * @return The same Builder.
5136          */
5137         @NonNull
setPublicVersion(Notification n)5138         public Builder setPublicVersion(Notification n) {
5139             if (n != null) {
5140                 mN.publicVersion = new Notification();
5141                 n.cloneInto(mN.publicVersion, /*heavy=*/ true);
5142             } else {
5143                 mN.publicVersion = null;
5144             }
5145             return this;
5146         }
5147 
5148         /**
5149          * Apply an extender to this notification builder. Extenders may be used to add
5150          * metadata or change options on this builder.
5151          */
5152         @NonNull
extend(Extender extender)5153         public Builder extend(Extender extender) {
5154             extender.extend(this);
5155             return this;
5156         }
5157 
5158         /**
5159          * Set the value for a notification flag
5160          *
5161          * @param mask Bit mask of the flag
5162          * @param value Status (on/off) of the flag
5163          *
5164          * @return The same Builder.
5165          */
5166         @NonNull
setFlag(@otificationFlags int mask, boolean value)5167         public Builder setFlag(@NotificationFlags int mask, boolean value) {
5168             if (value) {
5169                 mN.flags |= mask;
5170             } else {
5171                 mN.flags &= ~mask;
5172             }
5173             return this;
5174         }
5175 
5176         /**
5177          * Sets {@link Notification#color}.
5178          *
5179          * @param argb The accent color to use
5180          *
5181          * @return The same Builder.
5182          */
5183         @NonNull
setColor(@olorInt int argb)5184         public Builder setColor(@ColorInt int argb) {
5185             mN.color = argb;
5186             sanitizeColor();
5187             return this;
5188         }
5189 
bindPhishingAlertIcon(RemoteViews contentView, StandardTemplateParams p)5190         private void bindPhishingAlertIcon(RemoteViews contentView, StandardTemplateParams p) {
5191             contentView.setDrawableTint(
5192                     R.id.phishing_alert,
5193                     false /* targetBackground */,
5194                     getColors(p).getErrorColor(),
5195                     PorterDuff.Mode.SRC_ATOP);
5196         }
5197 
getProfileBadgeDrawable()5198         private Drawable getProfileBadgeDrawable() {
5199             if (mContext.getUserId() == UserHandle.USER_SYSTEM) {
5200                 // This user can never be a badged profile,
5201                 // and also includes USER_ALL system notifications.
5202                 return null;
5203             }
5204             // Note: This assumes that the current user can read the profile badge of the
5205             // originating user.
5206             DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
5207             return dpm.getResources().getDrawable(
5208                     getUpdatableProfileBadgeId(), SOLID_COLORED, NOTIFICATION,
5209                     this::getDefaultProfileBadgeDrawable);
5210         }
5211 
getUpdatableProfileBadgeId()5212         private String getUpdatableProfileBadgeId() {
5213             return mContext.getSystemService(UserManager.class).isManagedProfile()
5214                     ? WORK_PROFILE_ICON : UNDEFINED;
5215         }
5216 
getDefaultProfileBadgeDrawable()5217         private Drawable getDefaultProfileBadgeDrawable() {
5218             return mContext.getPackageManager().getUserBadgeForDensityNoBackground(
5219                     new UserHandle(mContext.getUserId()), 0);
5220         }
5221 
getProfileBadge()5222         private Bitmap getProfileBadge() {
5223             Drawable badge = getProfileBadgeDrawable();
5224             if (badge == null) {
5225                 return null;
5226             }
5227             final int size = mContext.getResources().getDimensionPixelSize(
5228                     R.dimen.notification_badge_size);
5229             Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
5230             Canvas canvas = new Canvas(bitmap);
5231             badge.setBounds(0, 0, size, size);
5232             badge.draw(canvas);
5233             return bitmap;
5234         }
5235 
bindProfileBadge(RemoteViews contentView, StandardTemplateParams p)5236         private void bindProfileBadge(RemoteViews contentView, StandardTemplateParams p) {
5237             Bitmap profileBadge = getProfileBadge();
5238 
5239             if (profileBadge != null) {
5240                 contentView.setImageViewBitmap(R.id.profile_badge, profileBadge);
5241                 contentView.setViewVisibility(R.id.profile_badge, View.VISIBLE);
5242                 if (isBackgroundColorized(p)) {
5243                     contentView.setDrawableTint(R.id.profile_badge, false,
5244                             getPrimaryTextColor(p), PorterDuff.Mode.SRC_ATOP);
5245                 }
5246             }
5247         }
5248 
bindAlertedIcon(RemoteViews contentView, StandardTemplateParams p)5249         private void bindAlertedIcon(RemoteViews contentView, StandardTemplateParams p) {
5250             contentView.setDrawableTint(
5251                     R.id.alerted_icon,
5252                     false /* targetBackground */,
5253                     getColors(p).getSecondaryTextColor(),
5254                     PorterDuff.Mode.SRC_IN);
5255         }
5256 
5257         /**
5258          * @hide
5259          */
usesStandardHeader()5260         public boolean usesStandardHeader() {
5261             if (mN.mUsesStandardHeader) {
5262                 return true;
5263             }
5264             if (mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.N) {
5265                 if (mN.contentView == null && mN.bigContentView == null) {
5266                     return true;
5267                 }
5268             }
5269             boolean contentViewUsesHeader = mN.contentView == null
5270                     || STANDARD_LAYOUTS.contains(mN.contentView.getLayoutId());
5271             boolean bigContentViewUsesHeader = mN.bigContentView == null
5272                     || STANDARD_LAYOUTS.contains(mN.bigContentView.getLayoutId());
5273             return contentViewUsesHeader && bigContentViewUsesHeader;
5274         }
5275 
resetStandardTemplate(RemoteViews contentView)5276         private void resetStandardTemplate(RemoteViews contentView) {
5277             resetNotificationHeader(contentView);
5278             contentView.setViewVisibility(R.id.right_icon, View.GONE);
5279             contentView.setViewVisibility(R.id.title, View.GONE);
5280             contentView.setTextViewText(R.id.title, null);
5281             contentView.setViewVisibility(R.id.text, View.GONE);
5282             contentView.setTextViewText(R.id.text, null);
5283         }
5284 
5285         /**
5286          * Resets the notification header to its original state
5287          */
resetNotificationHeader(RemoteViews contentView)5288         private void resetNotificationHeader(RemoteViews contentView) {
5289             // Small icon doesn't need to be reset, as it's always set. Resetting would prevent
5290             // re-using the drawable when the notification is updated.
5291             contentView.setBoolean(R.id.expand_button, "setExpanded", false);
5292             contentView.setViewVisibility(R.id.app_name_text, View.GONE);
5293             contentView.setTextViewText(R.id.app_name_text, null);
5294             contentView.setViewVisibility(R.id.chronometer, View.GONE);
5295             contentView.setViewVisibility(R.id.header_text, View.GONE);
5296             contentView.setTextViewText(R.id.header_text, null);
5297             contentView.setViewVisibility(R.id.header_text_secondary, View.GONE);
5298             contentView.setTextViewText(R.id.header_text_secondary, null);
5299             contentView.setViewVisibility(R.id.header_text_divider, View.GONE);
5300             contentView.setViewVisibility(R.id.header_text_secondary_divider, View.GONE);
5301             contentView.setViewVisibility(R.id.time_divider, View.GONE);
5302             contentView.setViewVisibility(R.id.time, View.GONE);
5303             contentView.setImageViewIcon(R.id.profile_badge, null);
5304             contentView.setViewVisibility(R.id.profile_badge, View.GONE);
5305             mN.mUsesStandardHeader = false;
5306         }
5307 
applyStandardTemplate(int resId, StandardTemplateParams p, TemplateBindResult result)5308         private RemoteViews applyStandardTemplate(int resId, StandardTemplateParams p,
5309                 TemplateBindResult result) {
5310             p.headerless(resId == getBaseLayoutResource()
5311                     || resId == getHeadsUpBaseLayoutResource()
5312                     || resId == getMessagingLayoutResource()
5313                     || resId == R.layout.notification_template_material_media);
5314             RemoteViews contentView = new BuilderRemoteViews(mContext.getApplicationInfo(), resId);
5315 
5316             resetStandardTemplate(contentView);
5317 
5318             final Bundle ex = mN.extras;
5319             updateBackgroundColor(contentView, p);
5320             bindNotificationHeader(contentView, p);
5321             bindLargeIconAndApplyMargin(contentView, p, result);
5322             boolean showProgress = handleProgressBar(contentView, ex, p);
5323             boolean hasSecondLine = showProgress;
5324             if (p.hasTitle()) {
5325                 contentView.setViewVisibility(p.mTitleViewId, View.VISIBLE);
5326                 contentView.setTextViewText(p.mTitleViewId, ensureColorSpanContrast(p.mTitle, p));
5327                 setTextViewColorPrimary(contentView, p.mTitleViewId, p);
5328             } else if (p.mTitleViewId != R.id.title) {
5329                 // This alternate title view ID is not cleared by resetStandardTemplate
5330                 contentView.setViewVisibility(p.mTitleViewId, View.GONE);
5331                 contentView.setTextViewText(p.mTitleViewId, null);
5332             }
5333             if (p.mText != null && p.mText.length() != 0
5334                     && (!showProgress || p.mAllowTextWithProgress)) {
5335                 contentView.setViewVisibility(p.mTextViewId, View.VISIBLE);
5336                 contentView.setTextViewText(p.mTextViewId, ensureColorSpanContrast(p.mText, p));
5337                 setTextViewColorSecondary(contentView, p.mTextViewId, p);
5338                 hasSecondLine = true;
5339             } else if (p.mTextViewId != R.id.text) {
5340                 // This alternate text view ID is not cleared by resetStandardTemplate
5341                 contentView.setViewVisibility(p.mTextViewId, View.GONE);
5342                 contentView.setTextViewText(p.mTextViewId, null);
5343             }
5344             setHeaderlessVerticalMargins(contentView, p, hasSecondLine);
5345 
5346             return contentView;
5347         }
5348 
setHeaderlessVerticalMargins(RemoteViews contentView, StandardTemplateParams p, boolean hasSecondLine)5349         private static void setHeaderlessVerticalMargins(RemoteViews contentView,
5350                 StandardTemplateParams p, boolean hasSecondLine) {
5351             if (!p.mHeaderless) {
5352                 return;
5353             }
5354             int marginDimen = hasSecondLine
5355                     ? R.dimen.notification_headerless_margin_twoline
5356                     : R.dimen.notification_headerless_margin_oneline;
5357             contentView.setViewLayoutMarginDimen(R.id.notification_headerless_view_column,
5358                     RemoteViews.MARGIN_TOP, marginDimen);
5359             contentView.setViewLayoutMarginDimen(R.id.notification_headerless_view_column,
5360                     RemoteViews.MARGIN_BOTTOM, marginDimen);
5361         }
5362 
setTextViewColorPrimary(RemoteViews contentView, @IdRes int id, StandardTemplateParams p)5363         private void setTextViewColorPrimary(RemoteViews contentView, @IdRes int id,
5364                 StandardTemplateParams p) {
5365             contentView.setTextColor(id, getPrimaryTextColor(p));
5366         }
5367 
5368         /**
5369          * @param p the template params to inflate this with
5370          * @return the primary text color
5371          * @hide
5372          */
5373         @VisibleForTesting
getPrimaryTextColor(StandardTemplateParams p)5374         public @ColorInt int getPrimaryTextColor(StandardTemplateParams p) {
5375             return getColors(p).getPrimaryTextColor();
5376         }
5377 
5378         /**
5379          * @param p the template params to inflate this with
5380          * @return the secondary text color
5381          * @hide
5382          */
5383         @VisibleForTesting
getSecondaryTextColor(StandardTemplateParams p)5384         public @ColorInt int getSecondaryTextColor(StandardTemplateParams p) {
5385             return getColors(p).getSecondaryTextColor();
5386         }
5387 
setTextViewColorSecondary(RemoteViews contentView, @IdRes int id, StandardTemplateParams p)5388         private void setTextViewColorSecondary(RemoteViews contentView, @IdRes int id,
5389                 StandardTemplateParams p) {
5390             contentView.setTextColor(id, getSecondaryTextColor(p));
5391         }
5392 
getColors(StandardTemplateParams p)5393         private Colors getColors(StandardTemplateParams p) {
5394             mColors.resolvePalette(mContext, mN.color, isBackgroundColorized(p), mInNightMode);
5395             return mColors;
5396         }
5397 
updateBackgroundColor(RemoteViews contentView, StandardTemplateParams p)5398         private void updateBackgroundColor(RemoteViews contentView,
5399                 StandardTemplateParams p) {
5400             if (isBackgroundColorized(p)) {
5401                 contentView.setInt(R.id.status_bar_latest_event_content, "setBackgroundColor",
5402                         getBackgroundColor(p));
5403             } else {
5404                 // Clear it!
5405                 contentView.setInt(R.id.status_bar_latest_event_content, "setBackgroundResource",
5406                         0);
5407             }
5408         }
5409 
handleProgressBar(RemoteViews contentView, Bundle ex, StandardTemplateParams p)5410         private boolean handleProgressBar(RemoteViews contentView, Bundle ex,
5411                 StandardTemplateParams p) {
5412             final int max = ex.getInt(EXTRA_PROGRESS_MAX, 0);
5413             final int progress = ex.getInt(EXTRA_PROGRESS, 0);
5414             final boolean ind = ex.getBoolean(EXTRA_PROGRESS_INDETERMINATE);
5415             if (!p.mHideProgress && (max != 0 || ind)) {
5416                 contentView.setViewVisibility(com.android.internal.R.id.progress, View.VISIBLE);
5417                 contentView.setProgressBar(R.id.progress, max, progress, ind);
5418                 contentView.setProgressBackgroundTintList(R.id.progress,
5419                         mContext.getColorStateList(R.color.notification_progress_background_color));
5420                 ColorStateList progressTint = ColorStateList.valueOf(getPrimaryAccentColor(p));
5421                 contentView.setProgressTintList(R.id.progress, progressTint);
5422                 contentView.setProgressIndeterminateTintList(R.id.progress, progressTint);
5423                 return true;
5424             } else {
5425                 contentView.setViewVisibility(R.id.progress, View.GONE);
5426                 return false;
5427             }
5428         }
5429 
bindLargeIconAndApplyMargin(RemoteViews contentView, @NonNull StandardTemplateParams p, @Nullable TemplateBindResult result)5430         private void bindLargeIconAndApplyMargin(RemoteViews contentView,
5431                 @NonNull StandardTemplateParams p,
5432                 @Nullable TemplateBindResult result) {
5433             if (result == null) {
5434                 result = new TemplateBindResult();
5435             }
5436             bindLargeIcon(contentView, p, result);
5437             if (!p.mHeaderless) {
5438                 // views in states with a header (big states)
5439                 result.mHeadingExtraMarginSet.applyToView(contentView, R.id.notification_header);
5440                 result.mTitleMarginSet.applyToView(contentView, R.id.title);
5441                 // If there is no title, the text (or big_text) needs to wrap around the image
5442                 result.mTitleMarginSet.applyToView(contentView, p.mTextViewId);
5443                 contentView.setInt(p.mTextViewId, "setNumIndentLines", p.hasTitle() ? 0 : 1);
5444             }
5445         }
5446 
5447         // This code is executed on behalf of other apps' notifications, sometimes even by 3p apps,
5448         // a use case that is not supported by the Compat Framework library.  Workarounds to resolve
5449         // the change's state in NotificationManagerService were very complex. These behavior
5450         // changes are entirely visual, and should otherwise be undetectable by apps.
5451         @SuppressWarnings("AndroidFrameworkCompatChange")
calculateRightIconDimens(Icon rightIcon, boolean isPromotedPicture, @NonNull TemplateBindResult result)5452         private void calculateRightIconDimens(Icon rightIcon, boolean isPromotedPicture,
5453                 @NonNull TemplateBindResult result) {
5454             final Resources resources = mContext.getResources();
5455             final float density = resources.getDisplayMetrics().density;
5456             final float iconMarginDp = resources.getDimension(
5457                     R.dimen.notification_right_icon_content_margin) / density;
5458             final float contentMarginDp = resources.getDimension(
5459                     R.dimen.notification_content_margin_end) / density;
5460             final float expanderSizeDp = resources.getDimension(
5461                     R.dimen.notification_header_expand_icon_size) / density - contentMarginDp;
5462             final float viewHeightDp = resources.getDimension(
5463                     R.dimen.notification_right_icon_size) / density;
5464             float viewWidthDp = viewHeightDp;  // icons are 1:1 by default
5465             if (rightIcon != null && (isPromotedPicture
5466                     || mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.S)) {
5467                 Drawable drawable = rightIcon.loadDrawable(mContext);
5468                 if (drawable != null) {
5469                     int iconWidth = drawable.getIntrinsicWidth();
5470                     int iconHeight = drawable.getIntrinsicHeight();
5471                     if (iconWidth > iconHeight && iconHeight > 0) {
5472                         final float maxViewWidthDp = viewHeightDp * MAX_LARGE_ICON_ASPECT_RATIO;
5473                         viewWidthDp = Math.min(viewHeightDp * iconWidth / iconHeight,
5474                                 maxViewWidthDp);
5475                     }
5476                 }
5477             }
5478             final float extraMarginEndDpIfVisible = viewWidthDp + iconMarginDp;
5479             result.setRightIconState(rightIcon != null /* visible */, viewWidthDp,
5480                     viewHeightDp, extraMarginEndDpIfVisible, expanderSizeDp);
5481         }
5482 
5483         /**
5484          * Bind the large icon.
5485          */
bindLargeIcon(RemoteViews contentView, @NonNull StandardTemplateParams p, @NonNull TemplateBindResult result)5486         private void bindLargeIcon(RemoteViews contentView, @NonNull StandardTemplateParams p,
5487                 @NonNull TemplateBindResult result) {
5488             if (mN.mLargeIcon == null && mN.largeIcon != null) {
5489                 mN.mLargeIcon = Icon.createWithBitmap(mN.largeIcon);
5490             }
5491 
5492             // Determine the left and right icons
5493             Icon leftIcon = p.mHideLeftIcon ? null : mN.mLargeIcon;
5494             Icon rightIcon = p.mHideRightIcon ? null
5495                     : (p.mPromotedPicture != null ? p.mPromotedPicture : mN.mLargeIcon);
5496 
5497             // Apply the left icon (without duplicating the bitmap)
5498             if (leftIcon != rightIcon || leftIcon == null) {
5499                 // If the leftIcon is explicitly hidden or different from the rightIcon, then set it
5500                 // explicitly and make sure it won't take the right_icon drawable.
5501                 contentView.setImageViewIcon(R.id.left_icon, leftIcon);
5502                 contentView.setIntTag(R.id.left_icon, R.id.tag_uses_right_icon_drawable, 0);
5503             } else {
5504                 // If the leftIcon equals the rightIcon, just set the flag to use the right_icon
5505                 // drawable.  This avoids the view having two copies of the same bitmap.
5506                 contentView.setIntTag(R.id.left_icon, R.id.tag_uses_right_icon_drawable, 1);
5507             }
5508 
5509             // Always calculate dimens to populate `result` for the GONE case
5510             boolean isPromotedPicture = p.mPromotedPicture != null;
5511             calculateRightIconDimens(rightIcon, isPromotedPicture, result);
5512 
5513             // Bind the right icon
5514             if (rightIcon != null) {
5515                 contentView.setViewLayoutWidth(R.id.right_icon,
5516                         result.mRightIconWidthDp, TypedValue.COMPLEX_UNIT_DIP);
5517                 contentView.setViewLayoutHeight(R.id.right_icon,
5518                         result.mRightIconHeightDp, TypedValue.COMPLEX_UNIT_DIP);
5519                 contentView.setViewVisibility(R.id.right_icon, View.VISIBLE);
5520                 contentView.setImageViewIcon(R.id.right_icon, rightIcon);
5521                 contentView.setIntTag(R.id.right_icon, R.id.tag_keep_when_showing_left_icon,
5522                         isPromotedPicture ? 1 : 0);
5523                 processLargeLegacyIcon(rightIcon, contentView, p);
5524             } else {
5525                 // The "reset" doesn't clear the drawable, so we do it here.  This clear is
5526                 // important because the presence of a drawable in this view (regardless of the
5527                 // visibility) is used by NotificationGroupingUtil to set the visibility.
5528                 contentView.setImageViewIcon(R.id.right_icon, null);
5529                 contentView.setIntTag(R.id.right_icon, R.id.tag_keep_when_showing_left_icon, 0);
5530             }
5531         }
5532 
bindNotificationHeader(RemoteViews contentView, StandardTemplateParams p)5533         private void bindNotificationHeader(RemoteViews contentView, StandardTemplateParams p) {
5534             bindSmallIcon(contentView, p);
5535             // Populate text left-to-right so that separators are only shown between strings
5536             boolean hasTextToLeft = bindHeaderAppName(contentView, p, false /* force */);
5537             hasTextToLeft |= bindHeaderTextSecondary(contentView, p, hasTextToLeft);
5538             hasTextToLeft |= bindHeaderText(contentView, p, hasTextToLeft);
5539             if (!hasTextToLeft) {
5540                 // If there's still no text, force add the app name so there is some text.
5541                 hasTextToLeft |= bindHeaderAppName(contentView, p, true /* force */);
5542             }
5543             bindHeaderChronometerAndTime(contentView, p, hasTextToLeft);
5544             bindPhishingAlertIcon(contentView, p);
5545             bindProfileBadge(contentView, p);
5546             bindAlertedIcon(contentView, p);
5547             bindExpandButton(contentView, p);
5548             mN.mUsesStandardHeader = true;
5549         }
5550 
bindExpandButton(RemoteViews contentView, StandardTemplateParams p)5551         private void bindExpandButton(RemoteViews contentView, StandardTemplateParams p) {
5552             // set default colors
5553             int bgColor = getBackgroundColor(p);
5554             int pillColor = Colors.flattenAlpha(getColors(p).getProtectionColor(), bgColor);
5555             int textColor = Colors.flattenAlpha(getPrimaryTextColor(p), pillColor);
5556             contentView.setInt(R.id.expand_button, "setDefaultTextColor", textColor);
5557             contentView.setInt(R.id.expand_button, "setDefaultPillColor", pillColor);
5558             // Use different highlighted colors for conversations' unread count
5559             if (p.mHighlightExpander) {
5560                 pillColor = Colors.flattenAlpha(getColors(p).getTertiaryAccentColor(), bgColor);
5561                 textColor = Colors.flattenAlpha(getColors(p).getOnAccentTextColor(), pillColor);
5562             }
5563             contentView.setInt(R.id.expand_button, "setHighlightTextColor", textColor);
5564             contentView.setInt(R.id.expand_button, "setHighlightPillColor", pillColor);
5565         }
5566 
bindHeaderChronometerAndTime(RemoteViews contentView, StandardTemplateParams p, boolean hasTextToLeft)5567         private void bindHeaderChronometerAndTime(RemoteViews contentView,
5568                 StandardTemplateParams p, boolean hasTextToLeft) {
5569             if (!p.mHideTime && showsTimeOrChronometer()) {
5570                 if (hasTextToLeft) {
5571                     contentView.setViewVisibility(R.id.time_divider, View.VISIBLE);
5572                     setTextViewColorSecondary(contentView, R.id.time_divider, p);
5573                 }
5574                 if (mN.extras.getBoolean(EXTRA_SHOW_CHRONOMETER)) {
5575                     contentView.setViewVisibility(R.id.chronometer, View.VISIBLE);
5576                     contentView.setLong(R.id.chronometer, "setBase",
5577                             mN.when + (SystemClock.elapsedRealtime() - System.currentTimeMillis()));
5578                     contentView.setBoolean(R.id.chronometer, "setStarted", true);
5579                     boolean countsDown = mN.extras.getBoolean(EXTRA_CHRONOMETER_COUNT_DOWN);
5580                     contentView.setChronometerCountDown(R.id.chronometer, countsDown);
5581                     setTextViewColorSecondary(contentView, R.id.chronometer, p);
5582                 } else {
5583                     contentView.setViewVisibility(R.id.time, View.VISIBLE);
5584                     contentView.setLong(R.id.time, "setTime", mN.when);
5585                     setTextViewColorSecondary(contentView, R.id.time, p);
5586                 }
5587             } else {
5588                 // We still want a time to be set but gone, such that we can show and hide it
5589                 // on demand in case it's a child notification without anything in the header
5590                 contentView.setLong(R.id.time, "setTime", mN.when != 0 ? mN.when : mN.creationTime);
5591                 setTextViewColorSecondary(contentView, R.id.time, p);
5592             }
5593         }
5594 
5595         /**
5596          * @return true if the header text will be visible
5597          */
bindHeaderText(RemoteViews contentView, StandardTemplateParams p, boolean hasTextToLeft)5598         private boolean bindHeaderText(RemoteViews contentView, StandardTemplateParams p,
5599                 boolean hasTextToLeft) {
5600             if (p.mHideSubText) {
5601                 return false;
5602             }
5603             CharSequence headerText = p.mSubText;
5604             if (headerText == null && mStyle != null && mStyle.mSummaryTextSet
5605                     && mStyle.hasSummaryInHeader()) {
5606                 headerText = mStyle.mSummaryText;
5607             }
5608             if (headerText == null
5609                     && mContext.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N
5610                     && mN.extras.getCharSequence(EXTRA_INFO_TEXT) != null) {
5611                 headerText = mN.extras.getCharSequence(EXTRA_INFO_TEXT);
5612             }
5613             if (!TextUtils.isEmpty(headerText)) {
5614                 contentView.setTextViewText(R.id.header_text, ensureColorSpanContrast(
5615                         processLegacyText(headerText), p));
5616                 setTextViewColorSecondary(contentView, R.id.header_text, p);
5617                 contentView.setViewVisibility(R.id.header_text, View.VISIBLE);
5618                 if (hasTextToLeft) {
5619                     contentView.setViewVisibility(R.id.header_text_divider, View.VISIBLE);
5620                     setTextViewColorSecondary(contentView, R.id.header_text_divider, p);
5621                 }
5622                 return true;
5623             }
5624             return false;
5625         }
5626 
5627         /**
5628          * @return true if the secondary header text will be visible
5629          */
bindHeaderTextSecondary(RemoteViews contentView, StandardTemplateParams p, boolean hasTextToLeft)5630         private boolean bindHeaderTextSecondary(RemoteViews contentView, StandardTemplateParams p,
5631                 boolean hasTextToLeft) {
5632             if (p.mHideSubText) {
5633                 return false;
5634             }
5635             if (!TextUtils.isEmpty(p.mHeaderTextSecondary)) {
5636                 contentView.setTextViewText(R.id.header_text_secondary, ensureColorSpanContrast(
5637                         processLegacyText(p.mHeaderTextSecondary), p));
5638                 setTextViewColorSecondary(contentView, R.id.header_text_secondary, p);
5639                 contentView.setViewVisibility(R.id.header_text_secondary, View.VISIBLE);
5640                 if (hasTextToLeft) {
5641                     contentView.setViewVisibility(R.id.header_text_secondary_divider, View.VISIBLE);
5642                     setTextViewColorSecondary(contentView, R.id.header_text_secondary_divider, p);
5643                 }
5644                 return true;
5645             }
5646             return false;
5647         }
5648 
5649         /**
5650          * @hide
5651          */
5652         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
loadHeaderAppName()5653         public String loadHeaderAppName() {
5654             CharSequence name = null;
5655             final PackageManager pm = mContext.getPackageManager();
5656             if (mN.extras.containsKey(EXTRA_SUBSTITUTE_APP_NAME)) {
5657                 // only system packages which lump together a bunch of unrelated stuff
5658                 // may substitute a different name to make the purpose of the
5659                 // notification more clear. the correct package label should always
5660                 // be accessible via SystemUI.
5661                 final String pkg = mContext.getPackageName();
5662                 final String subName = mN.extras.getString(EXTRA_SUBSTITUTE_APP_NAME);
5663                 if (PackageManager.PERMISSION_GRANTED == pm.checkPermission(
5664                         android.Manifest.permission.SUBSTITUTE_NOTIFICATION_APP_NAME, pkg)) {
5665                     name = subName;
5666                 } else {
5667                     Log.w(TAG, "warning: pkg "
5668                             + pkg + " attempting to substitute app name '" + subName
5669                             + "' without holding perm "
5670                             + android.Manifest.permission.SUBSTITUTE_NOTIFICATION_APP_NAME);
5671                 }
5672             }
5673             if (TextUtils.isEmpty(name)) {
5674                 name = pm.getApplicationLabel(mContext.getApplicationInfo());
5675             }
5676             if (TextUtils.isEmpty(name)) {
5677                 // still nothing?
5678                 return null;
5679             }
5680 
5681             return String.valueOf(name);
5682         }
5683 
5684         /**
5685          * @return true if the app name will be visible
5686          */
bindHeaderAppName(RemoteViews contentView, StandardTemplateParams p, boolean force)5687         private boolean bindHeaderAppName(RemoteViews contentView, StandardTemplateParams p,
5688                 boolean force) {
5689             if (p.mViewType == StandardTemplateParams.VIEW_TYPE_MINIMIZED && !force) {
5690                 // unless the force flag is set, don't show the app name in the minimized state.
5691                 return false;
5692             }
5693             if (p.mHeaderless && p.hasTitle()) {
5694                 // the headerless template will have the TITLE in this position; return true to
5695                 // keep the divider visible between that title and the next text element.
5696                 return true;
5697             }
5698             if (p.mHideAppName) {
5699                 // The app name is being hidden, so we definitely want to return here.
5700                 // Assume that there is a title which will replace it in the header.
5701                 return p.hasTitle();
5702             }
5703             contentView.setViewVisibility(R.id.app_name_text, View.VISIBLE);
5704             contentView.setTextViewText(R.id.app_name_text, loadHeaderAppName());
5705             contentView.setTextColor(R.id.app_name_text, getSecondaryTextColor(p));
5706             return true;
5707         }
5708 
5709         /**
5710          * Determines if the notification should be colorized *for the purposes of applying colors*.
5711          * If this is the minimized view of a colorized notification, this will return false so that
5712          * internal coloring logic can still render the notification normally.
5713          */
isBackgroundColorized(StandardTemplateParams p)5714         private boolean isBackgroundColorized(StandardTemplateParams p) {
5715             return p.allowColorization && mN.isColorized();
5716         }
5717 
isCallActionColorCustomizable()5718         private boolean isCallActionColorCustomizable() {
5719             // NOTE: this doesn't need to check StandardTemplateParams.allowColorization because
5720             //  that is only used for disallowing colorization of headers for the minimized state,
5721             //  and neither of those conditions applies when showing actions.
5722             //  Not requiring StandardTemplateParams as an argument simplifies the creation process.
5723             return mN.isColorized() && mContext.getResources().getBoolean(
5724                     R.bool.config_callNotificationActionColorsRequireColorized);
5725         }
5726 
bindSmallIcon(RemoteViews contentView, StandardTemplateParams p)5727         private void bindSmallIcon(RemoteViews contentView, StandardTemplateParams p) {
5728             if (mN.mSmallIcon == null && mN.icon != 0) {
5729                 mN.mSmallIcon = Icon.createWithResource(mContext, mN.icon);
5730             }
5731             contentView.setImageViewIcon(R.id.icon, mN.mSmallIcon);
5732             contentView.setInt(R.id.icon, "setImageLevel", mN.iconLevel);
5733             processSmallIconColor(mN.mSmallIcon, contentView, p);
5734         }
5735 
5736         /**
5737          * @return true if the built notification will show the time or the chronometer; false
5738          *         otherwise
5739          */
showsTimeOrChronometer()5740         private boolean showsTimeOrChronometer() {
5741             return mN.showsTime() || mN.showsChronometer();
5742         }
5743 
resetStandardTemplateWithActions(RemoteViews big)5744         private void resetStandardTemplateWithActions(RemoteViews big) {
5745             // actions_container is only reset when there are no actions to avoid focus issues with
5746             // remote inputs.
5747             big.setViewVisibility(R.id.actions, View.GONE);
5748             big.removeAllViews(R.id.actions);
5749 
5750             big.setViewVisibility(R.id.notification_material_reply_container, View.GONE);
5751             big.setTextViewText(R.id.notification_material_reply_text_1, null);
5752             big.setViewVisibility(R.id.notification_material_reply_text_1_container, View.GONE);
5753             big.setViewVisibility(R.id.notification_material_reply_progress, View.GONE);
5754 
5755             big.setViewVisibility(R.id.notification_material_reply_text_2, View.GONE);
5756             big.setTextViewText(R.id.notification_material_reply_text_2, null);
5757             big.setViewVisibility(R.id.notification_material_reply_text_3, View.GONE);
5758             big.setTextViewText(R.id.notification_material_reply_text_3, null);
5759 
5760             // This may get erased by bindSnoozeAction
5761             big.setViewLayoutMarginDimen(R.id.notification_action_list_margin_target,
5762                     RemoteViews.MARGIN_BOTTOM, R.dimen.notification_content_margin);
5763         }
5764 
bindSnoozeAction(RemoteViews big, StandardTemplateParams p)5765         private void bindSnoozeAction(RemoteViews big, StandardTemplateParams p) {
5766             boolean hideSnoozeButton = mN.isFgsOrUij()
5767                     || mN.fullScreenIntent != null
5768                     || isBackgroundColorized(p)
5769                     || p.mViewType != StandardTemplateParams.VIEW_TYPE_BIG;
5770             big.setBoolean(R.id.snooze_button, "setEnabled", !hideSnoozeButton);
5771             if (hideSnoozeButton) {
5772                 // Only hide; NotificationContentView will show it when it adds the click listener
5773                 big.setViewVisibility(R.id.snooze_button, View.GONE);
5774             }
5775 
5776             final boolean snoozeEnabled = !hideSnoozeButton
5777                     && mContext.getContentResolver() != null
5778                     && isSnoozeSettingEnabled();
5779             if (snoozeEnabled) {
5780                 big.setViewLayoutMarginDimen(R.id.notification_action_list_margin_target,
5781                         RemoteViews.MARGIN_BOTTOM, 0);
5782             }
5783         }
5784 
isSnoozeSettingEnabled()5785         private boolean isSnoozeSettingEnabled() {
5786             try {
5787                 return Settings.Secure.getIntForUser(mContext.getContentResolver(),
5788                     Settings.Secure.SHOW_NOTIFICATION_SNOOZE, 0, UserHandle.USER_CURRENT) == 1;
5789             } catch (SecurityException ex) {
5790                 // Most 3p apps can't access this snooze setting, so their NotificationListeners
5791                 // would be unable to create notification views if we propagated this exception.
5792                 return false;
5793             }
5794         }
5795 
5796         /**
5797          * Returns the actions that are not contextual.
5798          */
getNonContextualActions()5799         private @NonNull List<Notification.Action> getNonContextualActions() {
5800             if (mActions == null) return Collections.emptyList();
5801             List<Notification.Action> standardActions = new ArrayList<>();
5802             for (Notification.Action action : mActions) {
5803                 if (!action.isContextual()) {
5804                     standardActions.add(action);
5805                 }
5806             }
5807             return standardActions;
5808         }
5809 
applyStandardTemplateWithActions(int layoutId, StandardTemplateParams p, TemplateBindResult result)5810         private RemoteViews applyStandardTemplateWithActions(int layoutId,
5811                 StandardTemplateParams p, TemplateBindResult result) {
5812             RemoteViews big = applyStandardTemplate(layoutId, p, result);
5813 
5814             resetStandardTemplateWithActions(big);
5815             bindSnoozeAction(big, p);
5816             // color the snooze and bubble actions with the theme color
5817             ColorStateList actionColor = ColorStateList.valueOf(getStandardActionColor(p));
5818             big.setColorStateList(R.id.snooze_button, "setImageTintList", actionColor);
5819             big.setColorStateList(R.id.bubble_button, "setImageTintList", actionColor);
5820 
5821             boolean validRemoteInput = false;
5822 
5823             // In the UI, contextual actions appear separately from the standard actions, so we
5824             // filter them out here.
5825             List<Notification.Action> nonContextualActions = getNonContextualActions();
5826 
5827             int numActions = Math.min(nonContextualActions.size(), MAX_ACTION_BUTTONS);
5828             boolean emphasizedMode = mN.fullScreenIntent != null
5829                     || p.mCallStyleActions
5830                     || ((mN.flags & FLAG_FSI_REQUESTED_BUT_DENIED) != 0);
5831 
5832             if (p.mCallStyleActions) {
5833                 // Clear view padding to allow buttons to start on the left edge.
5834                 // This must be done before 'setEmphasizedMode' which sets top/bottom margins.
5835                 big.setViewPadding(R.id.actions, 0, 0, 0, 0);
5836                 // Add an optional indent that will make buttons start at the correct column when
5837                 // there is enough space to do so (and fall back to the left edge if not).
5838                 big.setInt(R.id.actions, "setCollapsibleIndentDimen",
5839                         R.dimen.call_notification_collapsible_indent);
5840             }
5841             big.setBoolean(R.id.actions, "setEmphasizedMode", emphasizedMode);
5842             if (numActions > 0 && !p.mHideActions) {
5843                 big.setViewVisibility(R.id.actions_container, View.VISIBLE);
5844                 big.setViewVisibility(R.id.actions, View.VISIBLE);
5845                 big.setViewLayoutMarginDimen(R.id.notification_action_list_margin_target,
5846                         RemoteViews.MARGIN_BOTTOM, 0);
5847                 for (int i = 0; i < numActions; i++) {
5848                     Action action = nonContextualActions.get(i);
5849 
5850                     boolean actionHasValidInput = hasValidRemoteInput(action);
5851                     validRemoteInput |= actionHasValidInput;
5852 
5853                     final RemoteViews button = generateActionButton(action, emphasizedMode, p);
5854                     if (actionHasValidInput && !emphasizedMode) {
5855                         // Clear the drawable
5856                         button.setInt(R.id.action0, "setBackgroundResource", 0);
5857                     }
5858                     if (emphasizedMode && i > 0) {
5859                         // Clear start margin from non-first buttons to reduce the gap between them.
5860                         //  (8dp remaining gap is from all buttons' standard 4dp inset).
5861                         button.setViewLayoutMarginDimen(R.id.action0, RemoteViews.MARGIN_START, 0);
5862                     }
5863                     big.addView(R.id.actions, button);
5864                 }
5865             } else {
5866                 big.setViewVisibility(R.id.actions_container, View.GONE);
5867             }
5868 
5869             RemoteInputHistoryItem[] replyText = getParcelableArrayFromBundle(
5870                     mN.extras, EXTRA_REMOTE_INPUT_HISTORY_ITEMS, RemoteInputHistoryItem.class);
5871             if (validRemoteInput && replyText != null && replyText.length > 0
5872                     && !TextUtils.isEmpty(replyText[0].getText())
5873                     && p.maxRemoteInputHistory > 0) {
5874                 boolean showSpinner = mN.extras.getBoolean(EXTRA_SHOW_REMOTE_INPUT_SPINNER);
5875                 big.setViewVisibility(R.id.notification_material_reply_container, View.VISIBLE);
5876                 big.setViewVisibility(R.id.notification_material_reply_text_1_container,
5877                         View.VISIBLE);
5878                 big.setTextViewText(R.id.notification_material_reply_text_1,
5879                         ensureColorSpanContrast(replyText[0].getText(), p));
5880                 setTextViewColorSecondary(big, R.id.notification_material_reply_text_1, p);
5881                 big.setViewVisibility(R.id.notification_material_reply_progress,
5882                         showSpinner ? View.VISIBLE : View.GONE);
5883                 big.setProgressIndeterminateTintList(
5884                         R.id.notification_material_reply_progress,
5885                         ColorStateList.valueOf(getPrimaryAccentColor(p)));
5886 
5887                 if (replyText.length > 1 && !TextUtils.isEmpty(replyText[1].getText())
5888                         && p.maxRemoteInputHistory > 1) {
5889                     big.setViewVisibility(R.id.notification_material_reply_text_2, View.VISIBLE);
5890                     big.setTextViewText(R.id.notification_material_reply_text_2,
5891                             ensureColorSpanContrast(replyText[1].getText(), p));
5892                     setTextViewColorSecondary(big, R.id.notification_material_reply_text_2, p);
5893 
5894                     if (replyText.length > 2 && !TextUtils.isEmpty(replyText[2].getText())
5895                             && p.maxRemoteInputHistory > 2) {
5896                         big.setViewVisibility(
5897                                 R.id.notification_material_reply_text_3, View.VISIBLE);
5898                         big.setTextViewText(R.id.notification_material_reply_text_3,
5899                                 ensureColorSpanContrast(replyText[2].getText(), p));
5900                         setTextViewColorSecondary(big, R.id.notification_material_reply_text_3, p);
5901                     }
5902                 }
5903             }
5904 
5905             return big;
5906         }
5907 
hasValidRemoteInput(Action action)5908         private boolean hasValidRemoteInput(Action action) {
5909             if (TextUtils.isEmpty(action.title) || action.actionIntent == null) {
5910                 // Weird actions
5911                 return false;
5912             }
5913 
5914             RemoteInput[] remoteInputs = action.getRemoteInputs();
5915             if (remoteInputs == null) {
5916                 return false;
5917             }
5918 
5919             for (RemoteInput r : remoteInputs) {
5920                 CharSequence[] choices = r.getChoices();
5921                 if (r.getAllowFreeFormInput() || (choices != null && choices.length != 0)) {
5922                     return true;
5923                 }
5924             }
5925             return false;
5926         }
5927 
5928         /**
5929          * Construct a RemoteViews for the final 1U notification layout. In order:
5930          *   1. Custom contentView from the caller
5931          *   2. Style's proposed content view
5932          *   3. Standard template view
5933          */
createContentView()5934         public RemoteViews createContentView() {
5935             return createContentView(false /* increasedheight */ );
5936         }
5937 
5938         // This code is executed on behalf of other apps' notifications, sometimes even by 3p apps,
5939         // a use case that is not supported by the Compat Framework library.  Workarounds to resolve
5940         // the change's state in NotificationManagerService were very complex. While it's possible
5941         // apps can detect the change, it's most likely that the changes will simply result in
5942         // visual regressions.
5943         @SuppressWarnings("AndroidFrameworkCompatChange")
fullyCustomViewRequiresDecoration(boolean fromStyle)5944         private boolean fullyCustomViewRequiresDecoration(boolean fromStyle) {
5945             // Custom views which come from a platform style class are safe, and thus do not need to
5946             // be wrapped.  Any subclass of those styles has the opportunity to make arbitrary
5947             // changes to the RemoteViews, and thus can't be trusted as a fully vetted view.
5948             if (fromStyle && PLATFORM_STYLE_CLASSES.contains(mStyle.getClass())) {
5949                 return false;
5950             }
5951             return mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.S;
5952         }
5953 
minimallyDecoratedContentView(@onNull RemoteViews customContent)5954         private RemoteViews minimallyDecoratedContentView(@NonNull RemoteViews customContent) {
5955             StandardTemplateParams p = mParams.reset()
5956                     .viewType(StandardTemplateParams.VIEW_TYPE_NORMAL)
5957                     .decorationType(StandardTemplateParams.DECORATION_MINIMAL)
5958                     .fillTextsFrom(this);
5959             TemplateBindResult result = new TemplateBindResult();
5960             RemoteViews standard = applyStandardTemplate(getBaseLayoutResource(), p, result);
5961             buildCustomContentIntoTemplate(mContext, standard, customContent,
5962                     p, result);
5963             return standard;
5964         }
5965 
minimallyDecoratedBigContentView(@onNull RemoteViews customContent)5966         private RemoteViews minimallyDecoratedBigContentView(@NonNull RemoteViews customContent) {
5967             StandardTemplateParams p = mParams.reset()
5968                     .viewType(StandardTemplateParams.VIEW_TYPE_BIG)
5969                     .decorationType(StandardTemplateParams.DECORATION_MINIMAL)
5970                     .fillTextsFrom(this);
5971             TemplateBindResult result = new TemplateBindResult();
5972             RemoteViews standard = applyStandardTemplateWithActions(getBigBaseLayoutResource(),
5973                     p, result);
5974             buildCustomContentIntoTemplate(mContext, standard, customContent,
5975                     p, result);
5976             makeHeaderExpanded(standard);
5977             return standard;
5978         }
5979 
minimallyDecoratedHeadsUpContentView( @onNull RemoteViews customContent)5980         private RemoteViews minimallyDecoratedHeadsUpContentView(
5981                 @NonNull RemoteViews customContent) {
5982             StandardTemplateParams p = mParams.reset()
5983                     .viewType(StandardTemplateParams.VIEW_TYPE_HEADS_UP)
5984                     .decorationType(StandardTemplateParams.DECORATION_MINIMAL)
5985                     .fillTextsFrom(this);
5986             TemplateBindResult result = new TemplateBindResult();
5987             RemoteViews standard = applyStandardTemplateWithActions(getHeadsUpBaseLayoutResource(),
5988                     p, result);
5989             buildCustomContentIntoTemplate(mContext, standard, customContent,
5990                     p, result);
5991             return standard;
5992         }
5993 
5994         /**
5995          * Construct a RemoteViews for the smaller content view.
5996          *
5997          *   @param increasedHeight true if this layout be created with an increased height. Some
5998          *   styles may support showing more then just that basic 1U size
5999          *   and the system may decide to render important notifications
6000          *   slightly bigger even when collapsed.
6001          *
6002          *   @hide
6003          */
createContentView(boolean increasedHeight)6004         public RemoteViews createContentView(boolean increasedHeight) {
6005             if (useExistingRemoteView(mN.contentView)) {
6006                 return fullyCustomViewRequiresDecoration(false /* fromStyle */)
6007                         ? minimallyDecoratedContentView(mN.contentView) : mN.contentView;
6008             } else if (mStyle != null) {
6009                 final RemoteViews styleView = mStyle.makeContentView(increasedHeight);
6010                 if (styleView != null) {
6011                     return fullyCustomViewRequiresDecoration(true /* fromStyle */)
6012                             ? minimallyDecoratedContentView(styleView) : styleView;
6013                 }
6014             }
6015             StandardTemplateParams p = mParams.reset()
6016                     .viewType(StandardTemplateParams.VIEW_TYPE_NORMAL)
6017                     .fillTextsFrom(this);
6018             return applyStandardTemplate(getBaseLayoutResource(), p, null /* result */);
6019         }
6020 
useExistingRemoteView(RemoteViews customContent)6021         private boolean useExistingRemoteView(RemoteViews customContent) {
6022             if (customContent == null) {
6023                 return false;
6024             }
6025             if (styleDisplaysCustomViewInline()) {
6026                 // the provided custom view is intended to be wrapped by the style.
6027                 return false;
6028             }
6029             if (fullyCustomViewRequiresDecoration(false)
6030                     && STANDARD_LAYOUTS.contains(customContent.getLayoutId())) {
6031                 // If the app's custom views are objects returned from Builder.create*ContentView()
6032                 // then the app is most likely attempting to spoof the user.  Even if they are not,
6033                 // the result would be broken (b/189189308) so we will ignore it.
6034                 Log.w(TAG, "For apps targeting S, a custom content view that is a modified "
6035                         + "version of any standard layout is disallowed.");
6036                 return false;
6037             }
6038             return true;
6039         }
6040 
6041         /**
6042          * Construct a RemoteViews for the final big notification layout.
6043          */
createBigContentView()6044         public RemoteViews createBigContentView() {
6045             RemoteViews result = null;
6046             if (useExistingRemoteView(mN.bigContentView)) {
6047                 return fullyCustomViewRequiresDecoration(false /* fromStyle */)
6048                         ? minimallyDecoratedBigContentView(mN.bigContentView) : mN.bigContentView;
6049             }
6050             if (mStyle != null) {
6051                 result = mStyle.makeBigContentView();
6052                 if (fullyCustomViewRequiresDecoration(true /* fromStyle */)) {
6053                     result = minimallyDecoratedBigContentView(result);
6054                 }
6055             }
6056             if (result == null) {
6057                 if (bigContentViewRequired()) {
6058                     StandardTemplateParams p = mParams.reset()
6059                             .viewType(StandardTemplateParams.VIEW_TYPE_BIG)
6060                             .allowTextWithProgress(true)
6061                             .fillTextsFrom(this);
6062                     result = applyStandardTemplateWithActions(getBigBaseLayoutResource(), p,
6063                             null /* result */);
6064                 }
6065             }
6066             makeHeaderExpanded(result);
6067             return result;
6068         }
6069 
6070         // This code is executed on behalf of other apps' notifications, sometimes even by 3p apps,
6071         // a use case that is not supported by the Compat Framework library.  Workarounds to resolve
6072         // the change's state in NotificationManagerService were very complex. While it's possible
6073         // apps can detect the change, it's most likely that the changes will simply result in
6074         // visual regressions.
6075         @SuppressWarnings("AndroidFrameworkCompatChange")
bigContentViewRequired()6076         private boolean bigContentViewRequired() {
6077             if (mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.S) {
6078                 return true;
6079             }
6080             // Notifications with contentView and without a bigContentView, style, or actions would
6081             // not have an expanded state before S, so showing the standard template expanded state
6082             // usually looks wrong, so we keep it simple and don't show the expanded state.
6083             boolean exempt = mN.contentView != null && mN.bigContentView == null
6084                     && mStyle == null && mActions.size() == 0;
6085             return !exempt;
6086         }
6087 
6088         /**
6089          * Construct a RemoteViews for the final notification header only. This will not be
6090          * colorized.
6091          *
6092          * @hide
6093          */
makeNotificationGroupHeader()6094         public RemoteViews makeNotificationGroupHeader() {
6095             return makeNotificationHeader(mParams.reset()
6096                     .viewType(StandardTemplateParams.VIEW_TYPE_GROUP_HEADER)
6097                     .fillTextsFrom(this));
6098         }
6099 
6100         /**
6101          * Construct a RemoteViews for the final notification header only. This will not be
6102          * colorized.
6103          *
6104          * @param p the template params to inflate this with
6105          */
makeNotificationHeader(StandardTemplateParams p)6106         private RemoteViews makeNotificationHeader(StandardTemplateParams p) {
6107             // Headers on their own are never colorized
6108             p.disallowColorization();
6109             RemoteViews header = new BuilderRemoteViews(mContext.getApplicationInfo(),
6110                     R.layout.notification_template_header);
6111             resetNotificationHeader(header);
6112             bindNotificationHeader(header, p);
6113             return header;
6114         }
6115 
6116         /**
6117          * Construct a RemoteViews for the ambient version of the notification.
6118          *
6119          * @hide
6120          */
makeAmbientNotification()6121         public RemoteViews makeAmbientNotification() {
6122             RemoteViews headsUpContentView = createHeadsUpContentView(false /* increasedHeight */);
6123             if (headsUpContentView != null) {
6124                 return headsUpContentView;
6125             }
6126             return createContentView();
6127         }
6128 
6129         /**
6130          * Adapt the Notification header if this view is used as an expanded view.
6131          *
6132          * @hide
6133          */
makeHeaderExpanded(RemoteViews result)6134         public static void makeHeaderExpanded(RemoteViews result) {
6135             if (result != null) {
6136                 result.setBoolean(R.id.expand_button, "setExpanded", true);
6137             }
6138         }
6139 
6140         /**
6141          * Construct a RemoteViews for the final heads-up notification layout.
6142          *
6143          * @param increasedHeight true if this layout be created with an increased height. Some
6144          * styles may support showing more then just that basic 1U size
6145          * and the system may decide to render important notifications
6146          * slightly bigger even when collapsed.
6147          *
6148          * @hide
6149          */
createHeadsUpContentView(boolean increasedHeight)6150         public RemoteViews createHeadsUpContentView(boolean increasedHeight) {
6151             if (useExistingRemoteView(mN.headsUpContentView)) {
6152                 return fullyCustomViewRequiresDecoration(false /* fromStyle */)
6153                         ? minimallyDecoratedHeadsUpContentView(mN.headsUpContentView)
6154                         : mN.headsUpContentView;
6155             } else if (mStyle != null) {
6156                 final RemoteViews styleView = mStyle.makeHeadsUpContentView(increasedHeight);
6157                 if (styleView != null) {
6158                     return fullyCustomViewRequiresDecoration(true /* fromStyle */)
6159                             ? minimallyDecoratedHeadsUpContentView(styleView) : styleView;
6160                 }
6161             } else if (mActions.size() == 0) {
6162                 return null;
6163             }
6164 
6165             // We only want at most a single remote input history to be shown here, otherwise
6166             // the content would become squished.
6167             StandardTemplateParams p = mParams.reset()
6168                     .viewType(StandardTemplateParams.VIEW_TYPE_HEADS_UP)
6169                     .fillTextsFrom(this)
6170                     .setMaxRemoteInputHistory(1);
6171             return applyStandardTemplateWithActions(getHeadsUpBaseLayoutResource(), p,
6172                     null /* result */);
6173         }
6174 
6175         /**
6176          * Construct a RemoteViews for the final heads-up notification layout.
6177          */
createHeadsUpContentView()6178         public RemoteViews createHeadsUpContentView() {
6179             return createHeadsUpContentView(false /* useIncreasedHeight */);
6180         }
6181 
6182         /**
6183          * Construct a RemoteViews for the display in public contexts like on the lockscreen.
6184          *
6185          * @param isLowPriority is this notification low priority
6186          * @hide
6187          */
6188         @UnsupportedAppUsage
makePublicContentView(boolean isLowPriority)6189         public RemoteViews makePublicContentView(boolean isLowPriority) {
6190             if (mN.publicVersion != null) {
6191                 final Builder builder = recoverBuilder(mContext, mN.publicVersion);
6192                 return builder.createContentView();
6193             }
6194             Bundle savedBundle = mN.extras;
6195             Style style = mStyle;
6196             mStyle = null;
6197             Icon largeIcon = mN.mLargeIcon;
6198             mN.mLargeIcon = null;
6199             Bitmap largeIconLegacy = mN.largeIcon;
6200             mN.largeIcon = null;
6201             ArrayList<Action> actions = mActions;
6202             mActions = new ArrayList<>();
6203             Bundle publicExtras = new Bundle();
6204             publicExtras.putBoolean(EXTRA_SHOW_WHEN,
6205                     savedBundle.getBoolean(EXTRA_SHOW_WHEN));
6206             publicExtras.putBoolean(EXTRA_SHOW_CHRONOMETER,
6207                     savedBundle.getBoolean(EXTRA_SHOW_CHRONOMETER));
6208             publicExtras.putBoolean(EXTRA_CHRONOMETER_COUNT_DOWN,
6209                     savedBundle.getBoolean(EXTRA_CHRONOMETER_COUNT_DOWN));
6210             String appName = savedBundle.getString(EXTRA_SUBSTITUTE_APP_NAME);
6211             if (appName != null) {
6212                 publicExtras.putString(EXTRA_SUBSTITUTE_APP_NAME, appName);
6213             }
6214             mN.extras = publicExtras;
6215             RemoteViews view;
6216             StandardTemplateParams params = mParams.reset()
6217                     .viewType(StandardTemplateParams.VIEW_TYPE_PUBLIC)
6218                     .fillTextsFrom(this);
6219             if (isLowPriority) {
6220                 params.highlightExpander(false);
6221             }
6222             view = makeNotificationHeader(params);
6223             view.setBoolean(R.id.notification_header, "setExpandOnlyOnButton", true);
6224             mN.extras = savedBundle;
6225             mN.mLargeIcon = largeIcon;
6226             mN.largeIcon = largeIconLegacy;
6227             mActions = actions;
6228             mStyle = style;
6229             return view;
6230         }
6231 
6232         /**
6233          * Construct a content view for the display when low - priority
6234          *
6235          * @param useRegularSubtext uses the normal subtext set if there is one available. Otherwise
6236          *                          a new subtext is created consisting of the content of the
6237          *                          notification.
6238          * @hide
6239          */
makeLowPriorityContentView(boolean useRegularSubtext)6240         public RemoteViews makeLowPriorityContentView(boolean useRegularSubtext) {
6241             StandardTemplateParams p = mParams.reset()
6242                     .viewType(StandardTemplateParams.VIEW_TYPE_MINIMIZED)
6243                     .highlightExpander(false)
6244                     .fillTextsFrom(this);
6245             if (!useRegularSubtext || TextUtils.isEmpty(p.mSubText)) {
6246                 p.summaryText(createSummaryText());
6247             }
6248             RemoteViews header = makeNotificationHeader(p);
6249             header.setBoolean(R.id.notification_header, "setAcceptAllTouches", true);
6250             // The low priority header has no app name and shows the text
6251             header.setBoolean(R.id.notification_header, "styleTextAsTitle", true);
6252             return header;
6253         }
6254 
createSummaryText()6255         private CharSequence createSummaryText() {
6256             CharSequence titleText = mN.extras.getCharSequence(Notification.EXTRA_TITLE);
6257             if (USE_ONLY_TITLE_IN_LOW_PRIORITY_SUMMARY) {
6258                 return titleText;
6259             }
6260             SpannableStringBuilder summary = new SpannableStringBuilder();
6261             if (titleText == null) {
6262                 titleText = mN.extras.getCharSequence(Notification.EXTRA_TITLE_BIG);
6263             }
6264             BidiFormatter bidi = BidiFormatter.getInstance();
6265             if (titleText != null) {
6266                 summary.append(bidi.unicodeWrap(titleText));
6267             }
6268             CharSequence contentText = mN.extras.getCharSequence(Notification.EXTRA_TEXT);
6269             if (titleText != null && contentText != null) {
6270                 summary.append(bidi.unicodeWrap(mContext.getText(
6271                         R.string.notification_header_divider_symbol_with_spaces)));
6272             }
6273             if (contentText != null) {
6274                 summary.append(bidi.unicodeWrap(contentText));
6275             }
6276             return summary;
6277         }
6278 
generateActionButton(Action action, boolean emphasizedMode, StandardTemplateParams p)6279         private RemoteViews generateActionButton(Action action, boolean emphasizedMode,
6280                 StandardTemplateParams p) {
6281             final boolean tombstone = (action.actionIntent == null);
6282             final RemoteViews button = new BuilderRemoteViews(mContext.getApplicationInfo(),
6283                     getActionButtonLayoutResource(emphasizedMode, tombstone));
6284             if (!tombstone) {
6285                 button.setOnClickPendingIntent(R.id.action0, action.actionIntent);
6286             }
6287             button.setContentDescription(R.id.action0, action.title);
6288             if (action.mRemoteInputs != null) {
6289                 button.setRemoteInputs(R.id.action0, action.mRemoteInputs);
6290             }
6291             if (emphasizedMode) {
6292                 // change the background bgColor
6293                 CharSequence title = action.title;
6294                 int buttonFillColor = getColors(p).getSecondaryAccentColor();
6295                 if (tombstone) {
6296                     buttonFillColor = setAlphaComponentByFloatDimen(mContext,
6297                             ContrastColorUtil.resolveSecondaryColor(
6298                                     mContext, getColors(p).getBackgroundColor(), mInNightMode),
6299                             R.dimen.notification_action_disabled_container_alpha);
6300                 }
6301                 if (isLegacy()) {
6302                     title = ContrastColorUtil.clearColorSpans(title);
6303                 } else {
6304                     // Check for a full-length span color to use as the button fill color.
6305                     Integer fullLengthColor = getFullLengthSpanColor(title);
6306                     if (fullLengthColor != null) {
6307                         // Ensure the custom button fill has 1.3:1 contrast w/ notification bg.
6308                         int notifBackgroundColor = getColors(p).getBackgroundColor();
6309                         buttonFillColor = ensureButtonFillContrast(
6310                                 fullLengthColor, notifBackgroundColor);
6311                     }
6312                     // Remove full-length color spans and ensure text contrast with the button fill.
6313                     title = ContrastColorUtil.ensureColorSpanContrast(title, buttonFillColor);
6314                 }
6315                 button.setTextViewText(R.id.action0, ensureColorSpanContrast(title, p));
6316                 int textColor = ContrastColorUtil.resolvePrimaryColor(mContext,
6317                         buttonFillColor, mInNightMode);
6318                 if (tombstone) {
6319                     textColor = setAlphaComponentByFloatDimen(mContext,
6320                             ContrastColorUtil.resolveSecondaryColor(
6321                                     mContext, getColors(p).getBackgroundColor(), mInNightMode),
6322                             R.dimen.notification_action_disabled_content_alpha);
6323                 }
6324                 button.setTextColor(R.id.action0, textColor);
6325                 // We only want about 20% alpha for the ripple
6326                 final int rippleColor = (textColor & 0x00ffffff) | 0x33000000;
6327                 button.setColorStateList(R.id.action0, "setRippleColor",
6328                         ColorStateList.valueOf(rippleColor));
6329                 button.setColorStateList(R.id.action0, "setButtonBackground",
6330                         ColorStateList.valueOf(buttonFillColor));
6331                 if (p.mCallStyleActions) {
6332                     button.setImageViewIcon(R.id.action0, action.getIcon());
6333                     boolean priority = action.getExtras().getBoolean(CallStyle.KEY_ACTION_PRIORITY);
6334                     button.setBoolean(R.id.action0, "setIsPriority", priority);
6335                     int minWidthDimen =
6336                             priority ? R.dimen.call_notification_system_action_min_width : 0;
6337                     button.setIntDimen(R.id.action0, "setMinimumWidth", minWidthDimen);
6338                 }
6339             } else {
6340                 button.setTextViewText(R.id.action0, ensureColorSpanContrast(
6341                         action.title, p));
6342                 button.setTextColor(R.id.action0, getStandardActionColor(p));
6343             }
6344             // CallStyle notifications add action buttons which don't actually exist in mActions,
6345             //  so we have to omit the index in that case.
6346             int actionIndex = mActions.indexOf(action);
6347             if (actionIndex != -1) {
6348                 button.setIntTag(R.id.action0, R.id.notification_action_index_tag, actionIndex);
6349             }
6350             return button;
6351         }
6352 
getActionButtonLayoutResource(boolean emphasizedMode, boolean tombstone)6353         private int getActionButtonLayoutResource(boolean emphasizedMode, boolean tombstone) {
6354             if (emphasizedMode) {
6355                 return tombstone ? getEmphasizedTombstoneActionLayoutResource()
6356                         : getEmphasizedActionLayoutResource();
6357             } else {
6358                 return tombstone ? getActionTombstoneLayoutResource()
6359                         : getActionLayoutResource();
6360             }
6361         }
6362 
6363         /**
6364          * Set the alpha component of {@code color} to be {@code alphaDimenResId}.
6365          */
setAlphaComponentByFloatDimen(Context context, @ColorInt int color, @DimenRes int alphaDimenResId)6366         private static int setAlphaComponentByFloatDimen(Context context, @ColorInt int color,
6367                 @DimenRes int alphaDimenResId) {
6368             final TypedValue alphaValue = new TypedValue();
6369             context.getResources().getValue(alphaDimenResId, alphaValue, true);
6370             return ColorUtils.setAlphaComponent(color, Math.round(alphaValue.getFloat() * 255));
6371         }
6372 
6373         /**
6374          * Extract the color from a full-length span from the text.
6375          *
6376          * @param charSequence the charSequence containing spans
6377          * @return the raw color of the text's last full-length span containing a color, or null if
6378          * no full-length span sets the text color.
6379          * @hide
6380          */
6381         @VisibleForTesting
6382         @Nullable
getFullLengthSpanColor(CharSequence charSequence)6383         public static Integer getFullLengthSpanColor(CharSequence charSequence) {
6384             // NOTE: this method preserves the functionality that for a CharSequence with multiple
6385             // full-length spans, the color of the last one is used.
6386             Integer result = null;
6387             if (charSequence instanceof Spanned) {
6388                 Spanned ss = (Spanned) charSequence;
6389                 Object[] spans = ss.getSpans(0, ss.length(), Object.class);
6390                 // First read through all full-length spans to get the button fill color, which will
6391                 //  be used as the background color for ensuring contrast of non-full-length spans.
6392                 for (Object span : spans) {
6393                     int spanStart = ss.getSpanStart(span);
6394                     int spanEnd = ss.getSpanEnd(span);
6395                     boolean fullLength = (spanEnd - spanStart) == charSequence.length();
6396                     if (!fullLength) {
6397                         continue;
6398                     }
6399                     if (span instanceof TextAppearanceSpan) {
6400                         TextAppearanceSpan originalSpan = (TextAppearanceSpan) span;
6401                         ColorStateList textColor = originalSpan.getTextColor();
6402                         if (textColor != null) {
6403                             result = textColor.getDefaultColor();
6404                         }
6405                     } else if (span instanceof ForegroundColorSpan) {
6406                         ForegroundColorSpan originalSpan = (ForegroundColorSpan) span;
6407                         result = originalSpan.getForegroundColor();
6408                     }
6409                 }
6410             }
6411             return result;
6412         }
6413 
6414         /**
6415          * Ensures contrast on color spans against a background color.
6416          * Note that any full-length color spans will be removed instead of being contrasted.
6417          *
6418          * @hide
6419          */
6420         @VisibleForTesting
ensureColorSpanContrast(CharSequence charSequence, StandardTemplateParams p)6421         public CharSequence ensureColorSpanContrast(CharSequence charSequence,
6422                 StandardTemplateParams p) {
6423             return ContrastColorUtil.ensureColorSpanContrast(charSequence, getBackgroundColor(p));
6424         }
6425 
6426         /**
6427          * Determines if the color is light or dark.  Specifically, this is using the same metric as
6428          * {@link ContrastColorUtil#resolvePrimaryColor(Context, int, boolean)} and peers so that
6429          * the direction of color shift is consistent.
6430          *
6431          * @param color the color to check
6432          * @return true if the color has higher contrast with white than black
6433          * @hide
6434          */
isColorDark(int color)6435         public static boolean isColorDark(int color) {
6436             // as per ContrastColorUtil.shouldUseDark, this uses the color contrast midpoint.
6437             return ContrastColorUtil.calculateLuminance(color) <= 0.17912878474;
6438         }
6439 
6440         /**
6441          * Finds a button fill color with sufficient contrast over bg (1.3:1) that has the same hue
6442          * as the original color, but is lightened or darkened depending on whether the background
6443          * is dark or light.
6444          *
6445          * @hide
6446          */
6447         @VisibleForTesting
ensureButtonFillContrast(int color, int bg)6448         public static int ensureButtonFillContrast(int color, int bg) {
6449             return isColorDark(bg)
6450                     ? ContrastColorUtil.findContrastColorAgainstDark(color, bg, true, 1.3)
6451                     : ContrastColorUtil.findContrastColor(color, bg, true, 1.3);
6452         }
6453 
6454 
6455         /**
6456          * @return Whether we are currently building a notification from a legacy (an app that
6457          *         doesn't create material notifications by itself) app.
6458          */
isLegacy()6459         private boolean isLegacy() {
6460             if (!mIsLegacyInitialized) {
6461                 mIsLegacy = mContext.getApplicationInfo().targetSdkVersion
6462                         < Build.VERSION_CODES.LOLLIPOP;
6463                 mIsLegacyInitialized = true;
6464             }
6465             return mIsLegacy;
6466         }
6467 
6468         private CharSequence processLegacyText(CharSequence charSequence) {
6469             boolean isAlreadyLightText = isLegacy() || textColorsNeedInversion();
6470             if (isAlreadyLightText) {
6471                 return getColorUtil().invertCharSequenceColors(charSequence);
6472             } else {
6473                 return charSequence;
6474             }
6475         }
6476 
6477         /**
6478          * Apply any necessary colors to the small icon
6479          */
6480         private void processSmallIconColor(Icon smallIcon, RemoteViews contentView,
6481                 StandardTemplateParams p) {
6482             boolean colorable = !isLegacy() || getColorUtil().isGrayscaleIcon(mContext, smallIcon);
6483             int color = getSmallIconColor(p);
6484             contentView.setInt(R.id.icon, "setBackgroundColor",
6485                     getBackgroundColor(p));
6486             contentView.setInt(R.id.icon, "setOriginalIconColor",
6487                     colorable ? color : COLOR_INVALID);
6488         }
6489 
6490         /**
6491          * Make the largeIcon dark if it's a fake smallIcon (that is,
6492          * if it's grayscale).
6493          */
6494         // TODO: also check bounds, transparency, that sort of thing.
6495         private void processLargeLegacyIcon(Icon largeIcon, RemoteViews contentView,
6496                 StandardTemplateParams p) {
6497             if (largeIcon != null && isLegacy()
6498                     && getColorUtil().isGrayscaleIcon(mContext, largeIcon)) {
6499                 // resolve color will fall back to the default when legacy
6500                 int color = getSmallIconColor(p);
6501                 contentView.setInt(R.id.icon, "setOriginalIconColor", color);
6502             }
6503         }
6504 
6505         private void sanitizeColor() {
6506             if (mN.color != COLOR_DEFAULT) {
6507                 mN.color |= 0xFF000000; // no alpha for custom colors
6508             }
6509         }
6510 
6511         /**
6512          * Gets the standard action button color
6513          */
6514         private @ColorInt int getStandardActionColor(Notification.StandardTemplateParams p) {
6515             return mTintActionButtons || isBackgroundColorized(p)
6516                     ? getPrimaryAccentColor(p) : getSecondaryTextColor(p);
6517         }
6518 
6519         /**
6520          * Gets the foreground color of the small icon.  If the notification is colorized, this
6521          * is the primary text color, otherwise it's the contrast-adjusted app-provided color.
6522          */
6523         private @ColorInt int getSmallIconColor(StandardTemplateParams p) {
6524             return getColors(p).getContrastColor();
6525         }
6526 
6527         /** @return the theme's accent color for colored UI elements. */
6528         private @ColorInt int getPrimaryAccentColor(StandardTemplateParams p) {
6529             return getColors(p).getPrimaryAccentColor();
6530         }
6531 
6532         /**
6533          * Apply the unstyled operations and return a new {@link Notification} object.
6534          * @hide
6535          */
6536         @NonNull
6537         public Notification buildUnstyled() {
6538             if (mActions.size() > 0) {
6539                 mN.actions = new Action[mActions.size()];
6540                 mActions.toArray(mN.actions);
6541             }
6542             if (!mPersonList.isEmpty()) {
6543                 mN.extras.putParcelableArrayList(EXTRA_PEOPLE_LIST, mPersonList);
6544             }
6545             if (mN.bigContentView != null || mN.contentView != null
6546                     || mN.headsUpContentView != null) {
6547                 mN.extras.putBoolean(EXTRA_CONTAINS_CUSTOM_VIEW, true);
6548             }
6549             return mN;
6550         }
6551 
6552         /**
6553          * Creates a Builder from an existing notification so further changes can be made.
6554          * @param context The context for your application / activity.
6555          * @param n The notification to create a Builder from.
6556          */
6557         @NonNull
recoverBuilder(Context context, Notification n)6558         public static Notification.Builder recoverBuilder(Context context, Notification n) {
6559             // Re-create notification context so we can access app resources.
6560             ApplicationInfo applicationInfo = n.extras.getParcelable(
6561                     EXTRA_BUILDER_APPLICATION_INFO, ApplicationInfo.class);
6562             Context builderContext;
6563             if (applicationInfo != null) {
6564                 try {
6565                     builderContext = context.createApplicationContext(applicationInfo,
6566                             Context.CONTEXT_RESTRICTED);
6567                 } catch (NameNotFoundException e) {
6568                     Log.e(TAG, "ApplicationInfo " + applicationInfo + " not found");
6569                     builderContext = context;  // try with our context
6570                 }
6571             } else {
6572                 builderContext = context; // try with given context
6573             }
6574 
6575             return new Builder(builderContext, n);
6576         }
6577 
6578         /**
6579          * Determines whether the platform can generate contextual actions for a notification.
6580          * By default this is true.
6581          */
6582         @NonNull
setAllowSystemGeneratedContextualActions(boolean allowed)6583         public Builder setAllowSystemGeneratedContextualActions(boolean allowed) {
6584             mN.mAllowSystemGeneratedContextualActions = allowed;
6585             return this;
6586         }
6587 
6588         /**
6589          * @deprecated Use {@link #build()} instead.
6590          */
6591         @Deprecated
getNotification()6592         public Notification getNotification() {
6593             return build();
6594         }
6595 
6596         /**
6597          * Combine all of the options that have been set and return a new {@link Notification}
6598          * object.
6599          *
6600          * If this notification has {@link BubbleMetadata} attached that was created with
6601          * a shortcutId a check will be performed to ensure the shortcutId supplied to bubble
6602          * metadata matches the shortcutId set on the  notification builder, if one was set.
6603          * If the shortcutId's were specified but do not match, an exception is thrown here.
6604          *
6605          * @see BubbleMetadata.Builder#Builder(String)
6606          * @see #setShortcutId(String)
6607          */
6608         @NonNull
build()6609         public Notification build() {
6610             // Check shortcut id matches
6611             if (mN.mShortcutId != null
6612                     && mN.mBubbleMetadata != null
6613                     && mN.mBubbleMetadata.getShortcutId() != null
6614                     && !mN.mShortcutId.equals(mN.mBubbleMetadata.getShortcutId())) {
6615                 throw new IllegalArgumentException(
6616                         "Notification and BubbleMetadata shortcut id's don't match,"
6617                                 + " notification: " + mN.mShortcutId
6618                                 + " vs bubble: " + mN.mBubbleMetadata.getShortcutId());
6619             }
6620 
6621             // Adds any new extras provided by the user.
6622             if (mUserExtras != null) {
6623                 final Bundle saveExtras = (Bundle) mUserExtras.clone();
6624                 if (SystemProperties.getBoolean(
6625                         "persist.sysui.notification.builder_extras_override", false)) {
6626                     mN.extras.putAll(saveExtras);
6627                 } else {
6628                     saveExtras.putAll(mN.extras);
6629                     mN.extras = saveExtras;
6630                 }
6631             }
6632 
6633             mN.creationTime = System.currentTimeMillis();
6634 
6635             // lazy stuff from mContext; see comment in Builder(Context, Notification)
6636             Notification.addFieldsFromContext(mContext, mN);
6637 
6638             buildUnstyled();
6639 
6640             if (mStyle != null) {
6641                 mStyle.reduceImageSizes(mContext);
6642                 mStyle.purgeResources();
6643                 mStyle.validate(mContext);
6644                 mStyle.buildStyled(mN);
6645             }
6646             mN.reduceImageSizes(mContext);
6647 
6648             if (mContext.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N
6649                     && !styleDisplaysCustomViewInline()) {
6650                 RemoteViews newContentView = mN.contentView;
6651                 RemoteViews newBigContentView = mN.bigContentView;
6652                 RemoteViews newHeadsUpContentView = mN.headsUpContentView;
6653                 if (newContentView == null) {
6654                     newContentView = createContentView();
6655                     mN.extras.putInt(EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT,
6656                             newContentView.getSequenceNumber());
6657                 }
6658                 if (newBigContentView == null) {
6659                     newBigContentView = createBigContentView();
6660                     if (newBigContentView != null) {
6661                         mN.extras.putInt(EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT,
6662                                 newBigContentView.getSequenceNumber());
6663                     }
6664                 }
6665                 if (newHeadsUpContentView == null) {
6666                     newHeadsUpContentView = createHeadsUpContentView();
6667                     if (newHeadsUpContentView != null) {
6668                         mN.extras.putInt(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT,
6669                                 newHeadsUpContentView.getSequenceNumber());
6670                     }
6671                 }
6672                 // Don't set any of the content views until after they have all been generated,
6673                 //  to avoid the generated .contentView triggering the logic which skips generating
6674                 //  the .bigContentView.
6675                 mN.contentView = newContentView;
6676                 mN.bigContentView = newBigContentView;
6677                 mN.headsUpContentView = newHeadsUpContentView;
6678             }
6679 
6680             if ((mN.defaults & DEFAULT_LIGHTS) != 0) {
6681                 mN.flags |= FLAG_SHOW_LIGHTS;
6682             }
6683 
6684             mN.allPendingIntents = null;
6685 
6686             return mN;
6687         }
6688 
styleDisplaysCustomViewInline()6689         private boolean styleDisplaysCustomViewInline() {
6690             return mStyle != null && mStyle.displayCustomViewInline();
6691         }
6692 
6693         /**
6694          * Apply this Builder to an existing {@link Notification} object.
6695          *
6696          * @hide
6697          */
6698         @NonNull
buildInto(@onNull Notification n)6699         public Notification buildInto(@NonNull Notification n) {
6700             build().cloneInto(n, true);
6701             return n;
6702         }
6703 
6704         /**
6705          * Removes RemoteViews that were created for compatibility from {@param n}, if they did not
6706          * change.
6707          *
6708          * @return {@param n}, if no stripping is needed, otherwise a stripped clone of {@param n}.
6709          *
6710          * @hide
6711          */
maybeCloneStrippedForDelivery(Notification n)6712         public static Notification maybeCloneStrippedForDelivery(Notification n) {
6713             String templateClass = n.extras.getString(EXTRA_TEMPLATE);
6714 
6715             // Only strip views for known Styles because we won't know how to
6716             // re-create them otherwise.
6717             if (!TextUtils.isEmpty(templateClass)
6718                     && getNotificationStyleClass(templateClass) == null) {
6719                 return n;
6720             }
6721 
6722             // Only strip unmodified BuilderRemoteViews.
6723             boolean stripContentView = n.contentView instanceof BuilderRemoteViews &&
6724                     n.extras.getInt(EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT, -1) ==
6725                             n.contentView.getSequenceNumber();
6726             boolean stripBigContentView = n.bigContentView instanceof BuilderRemoteViews &&
6727                     n.extras.getInt(EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT, -1) ==
6728                             n.bigContentView.getSequenceNumber();
6729             boolean stripHeadsUpContentView = n.headsUpContentView instanceof BuilderRemoteViews &&
6730                     n.extras.getInt(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT, -1) ==
6731                             n.headsUpContentView.getSequenceNumber();
6732 
6733             // Nothing to do here, no need to clone.
6734             if (!stripContentView && !stripBigContentView && !stripHeadsUpContentView) {
6735                 return n;
6736             }
6737 
6738             Notification clone = n.clone();
6739             if (stripContentView) {
6740                 clone.contentView = null;
6741                 clone.extras.remove(EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT);
6742             }
6743             if (stripBigContentView) {
6744                 clone.bigContentView = null;
6745                 clone.extras.remove(EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT);
6746             }
6747             if (stripHeadsUpContentView) {
6748                 clone.headsUpContentView = null;
6749                 clone.extras.remove(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT);
6750             }
6751             return clone;
6752         }
6753 
6754         @UnsupportedAppUsage
getBaseLayoutResource()6755         private int getBaseLayoutResource() {
6756             return R.layout.notification_template_material_base;
6757         }
6758 
getHeadsUpBaseLayoutResource()6759         private int getHeadsUpBaseLayoutResource() {
6760             return R.layout.notification_template_material_heads_up_base;
6761         }
6762 
getBigBaseLayoutResource()6763         private int getBigBaseLayoutResource() {
6764             return R.layout.notification_template_material_big_base;
6765         }
6766 
getBigPictureLayoutResource()6767         private int getBigPictureLayoutResource() {
6768             return R.layout.notification_template_material_big_picture;
6769         }
6770 
getBigTextLayoutResource()6771         private int getBigTextLayoutResource() {
6772             return R.layout.notification_template_material_big_text;
6773         }
6774 
getInboxLayoutResource()6775         private int getInboxLayoutResource() {
6776             return R.layout.notification_template_material_inbox;
6777         }
6778 
getMessagingLayoutResource()6779         private int getMessagingLayoutResource() {
6780             return R.layout.notification_template_material_messaging;
6781         }
6782 
getBigMessagingLayoutResource()6783         private int getBigMessagingLayoutResource() {
6784             return R.layout.notification_template_material_big_messaging;
6785         }
6786 
getConversationLayoutResource()6787         private int getConversationLayoutResource() {
6788             return R.layout.notification_template_material_conversation;
6789         }
6790 
getActionLayoutResource()6791         private int getActionLayoutResource() {
6792             return R.layout.notification_material_action;
6793         }
6794 
getEmphasizedActionLayoutResource()6795         private int getEmphasizedActionLayoutResource() {
6796             return R.layout.notification_material_action_emphasized;
6797         }
6798 
getEmphasizedTombstoneActionLayoutResource()6799         private int getEmphasizedTombstoneActionLayoutResource() {
6800             return R.layout.notification_material_action_emphasized_tombstone;
6801         }
6802 
getActionTombstoneLayoutResource()6803         private int getActionTombstoneLayoutResource() {
6804             return R.layout.notification_material_action_tombstone;
6805         }
6806 
getBackgroundColor(StandardTemplateParams p)6807         private @ColorInt int getBackgroundColor(StandardTemplateParams p) {
6808             return getColors(p).getBackgroundColor();
6809         }
6810 
textColorsNeedInversion()6811         private boolean textColorsNeedInversion() {
6812             if (mStyle == null || !MediaStyle.class.equals(mStyle.getClass())) {
6813                 return false;
6814             }
6815             int targetSdkVersion = mContext.getApplicationInfo().targetSdkVersion;
6816             return targetSdkVersion > Build.VERSION_CODES.M
6817                     && targetSdkVersion < Build.VERSION_CODES.O;
6818         }
6819 
6820         /**
6821          * Get the text that should be displayed in the statusBar when heads upped. This is
6822          * usually just the app name, but may be different depending on the style.
6823          *
6824          * @param publicMode If true, return a text that is safe to display in public.
6825          *
6826          * @hide
6827          */
getHeadsUpStatusBarText(boolean publicMode)6828         public CharSequence getHeadsUpStatusBarText(boolean publicMode) {
6829             if (mStyle != null && !publicMode) {
6830                 CharSequence text = mStyle.getHeadsUpStatusBarText();
6831                 if (!TextUtils.isEmpty(text)) {
6832                     return text;
6833                 }
6834             }
6835             return loadHeaderAppName();
6836         }
6837 
6838         /**
6839          * @return if this builder uses a template
6840          *
6841          * @hide
6842          */
usesTemplate()6843         public boolean usesTemplate() {
6844             return (mN.contentView == null && mN.headsUpContentView == null
6845                     && mN.bigContentView == null)
6846                     || styleDisplaysCustomViewInline();
6847         }
6848     }
6849 
6850     /**
6851      * Reduces the image sizes to conform to a maximum allowed size. This also processes all custom
6852      * remote views.
6853      *
6854      * @hide
6855      */
reduceImageSizes(Context context)6856     void reduceImageSizes(Context context) {
6857         if (extras.getBoolean(EXTRA_REDUCED_IMAGES)) {
6858             return;
6859         }
6860         boolean isLowRam = ActivityManager.isLowRamDeviceStatic();
6861 
6862         if (mSmallIcon != null
6863                 // Only bitmap icons can be downscaled.
6864                 && (mSmallIcon.getType() == Icon.TYPE_BITMAP
6865                         || mSmallIcon.getType() == Icon.TYPE_ADAPTIVE_BITMAP)) {
6866             Resources resources = context.getResources();
6867             int maxSize = resources.getDimensionPixelSize(
6868                     isLowRam ? R.dimen.notification_small_icon_size_low_ram
6869                             : R.dimen.notification_small_icon_size);
6870             mSmallIcon.scaleDownIfNecessary(maxSize, maxSize);
6871         }
6872 
6873         if (mLargeIcon != null || largeIcon != null) {
6874             Resources resources = context.getResources();
6875             Class<? extends Style> style = getNotificationStyle();
6876             int maxSize = resources.getDimensionPixelSize(isLowRam
6877                     ? R.dimen.notification_right_icon_size_low_ram
6878                     : R.dimen.notification_right_icon_size);
6879             if (mLargeIcon != null) {
6880                 mLargeIcon.scaleDownIfNecessary(maxSize, maxSize);
6881             }
6882             if (largeIcon != null) {
6883                 largeIcon = Icon.scaleDownIfNecessary(largeIcon, maxSize, maxSize);
6884             }
6885         }
6886         reduceImageSizesForRemoteView(contentView, context, isLowRam);
6887         reduceImageSizesForRemoteView(headsUpContentView, context, isLowRam);
6888         reduceImageSizesForRemoteView(bigContentView, context, isLowRam);
6889         extras.putBoolean(EXTRA_REDUCED_IMAGES, true);
6890     }
6891 
reduceImageSizesForRemoteView(RemoteViews remoteView, Context context, boolean isLowRam)6892     private void reduceImageSizesForRemoteView(RemoteViews remoteView, Context context,
6893             boolean isLowRam) {
6894         if (remoteView != null) {
6895             Resources resources = context.getResources();
6896             int maxWidth = resources.getDimensionPixelSize(isLowRam
6897                     ? R.dimen.notification_custom_view_max_image_width_low_ram
6898                     : R.dimen.notification_custom_view_max_image_width);
6899             int maxHeight = resources.getDimensionPixelSize(isLowRam
6900                     ? R.dimen.notification_custom_view_max_image_height_low_ram
6901                     : R.dimen.notification_custom_view_max_image_height);
6902             remoteView.reduceImageSizes(maxWidth, maxHeight);
6903         }
6904     }
6905 
6906     /**
6907      * @return whether this notification is a foreground service notification
6908      * @hide
6909      */
isForegroundService()6910     public boolean isForegroundService() {
6911         return (flags & Notification.FLAG_FOREGROUND_SERVICE) != 0;
6912     }
6913 
6914     /**
6915      * @return whether this notification is associated with a user initiated job
6916      * @hide
6917      */
6918     @TestApi
isUserInitiatedJob()6919     public boolean isUserInitiatedJob() {
6920         return (flags & Notification.FLAG_USER_INITIATED_JOB) != 0;
6921     }
6922 
6923     /**
6924      * @return whether this notification is associated with either a foreground service or
6925      * a user initiated job
6926      * @hide
6927      */
isFgsOrUij()6928     public boolean isFgsOrUij() {
6929         return isForegroundService() || isUserInitiatedJob();
6930     }
6931 
6932     /**
6933      * Describe whether this notification's content such that it should always display
6934      * immediately when tied to a foreground service, even if the system might generally
6935      * avoid showing the notifications for short-lived foreground service lifetimes.
6936      *
6937      * Immediate visibility of the Notification is indicated when:
6938      * <ul>
6939      *     <li>The app specifically indicated it with
6940      *         {@link Notification.Builder#setForegroundServiceBehavior(int)
6941      *         setForegroundServiceBehavior(BEHAVIOR_IMMEDIATE_DISPLAY)}</li>
6942      *     <li>It is a media notification or has an associated media session</li>
6943      *     <li>It is a call or navigation notification</li>
6944      *     <li>It provides additional action affordances</li>
6945      * </ul>
6946      *
6947      * If the app has specified
6948      * {@code NotificationBuilder.setForegroundServiceBehavior(BEHAVIOR_DEFERRED_DISPLAY)}
6949      * then this method will return {@code false} and notification visibility will be
6950      * deferred following the service's transition to the foreground state even in the
6951      * circumstances described above.
6952      *
6953      * @return whether this notification should be displayed immediately when
6954      * its associated service transitions to the foreground state
6955      * @hide
6956      */
6957     @TestApi
shouldShowForegroundImmediately()6958     public boolean shouldShowForegroundImmediately() {
6959         // Has the app demanded immediate display?
6960         if (mFgsDeferBehavior == FOREGROUND_SERVICE_IMMEDIATE) {
6961             return true;
6962         }
6963 
6964         // Has the app demanded deferred display?
6965         if (mFgsDeferBehavior == FOREGROUND_SERVICE_DEFERRED) {
6966             return false;
6967         }
6968 
6969         // We show these sorts of notifications immediately in the absence of
6970         // any explicit app declaration
6971         if (isMediaNotification()
6972                     || CATEGORY_CALL.equals(category)
6973                     || CATEGORY_NAVIGATION.equals(category)
6974                     || (actions != null && actions.length > 0)) {
6975             return true;
6976         }
6977 
6978         // No extenuating circumstances: defer visibility
6979         return false;
6980     }
6981 
6982     /**
6983      * Has forced deferral for FGS purposes been specified?
6984      * @hide
6985      */
isForegroundDisplayForceDeferred()6986     public boolean isForegroundDisplayForceDeferred() {
6987         return FOREGROUND_SERVICE_DEFERRED == mFgsDeferBehavior;
6988     }
6989 
6990     /**
6991      * @return the style class of this notification
6992      * @hide
6993      */
getNotificationStyle()6994     public Class<? extends Notification.Style> getNotificationStyle() {
6995         String templateClass = extras.getString(Notification.EXTRA_TEMPLATE);
6996 
6997         if (!TextUtils.isEmpty(templateClass)) {
6998             return Notification.getNotificationStyleClass(templateClass);
6999         }
7000         return null;
7001     }
7002 
7003     /**
7004      * @return whether the style of this notification is the one provided
7005      * @hide
7006      */
isStyle(@onNull Class<? extends Style> styleClass)7007     public boolean isStyle(@NonNull Class<? extends Style> styleClass) {
7008         String templateClass = extras.getString(Notification.EXTRA_TEMPLATE);
7009         return Objects.equals(templateClass, styleClass.getName());
7010     }
7011 
7012     /**
7013      * @return true if this notification is colorized *for the purposes of ranking*.  If the
7014      * {@link #color} is {@link #COLOR_DEFAULT} this will be true, even though the actual
7015      * appearance of the notification may not be "colorized".
7016      *
7017      * @hide
7018      */
isColorized()7019     public boolean isColorized() {
7020         return extras.getBoolean(EXTRA_COLORIZED)
7021                 && (hasColorizedPermission() || isFgsOrUij());
7022     }
7023 
7024     /**
7025      * Returns whether an app can colorize due to the android.permission.USE_COLORIZED_NOTIFICATIONS
7026      * permission. The permission is checked when a notification is enqueued.
7027      *
7028      * @hide
7029      */
hasColorizedPermission()7030     public boolean hasColorizedPermission() {
7031         return (flags & Notification.FLAG_CAN_COLORIZE) != 0;
7032     }
7033 
7034     /**
7035      * @return true if this is a media style notification with a media session
7036      *
7037      * @hide
7038      */
isMediaNotification()7039     public boolean isMediaNotification() {
7040         Class<? extends Style> style = getNotificationStyle();
7041         boolean isMediaStyle = (MediaStyle.class.equals(style)
7042                 || DecoratedMediaCustomViewStyle.class.equals(style));
7043 
7044         boolean hasMediaSession = extras.getParcelable(Notification.EXTRA_MEDIA_SESSION,
7045                 MediaSession.Token.class) != null;
7046 
7047         return isMediaStyle && hasMediaSession;
7048     }
7049 
7050     /**
7051      * @return true for custom notifications, including notifications
7052      * with DecoratedCustomViewStyle or DecoratedMediaCustomViewStyle,
7053      * and other notifications with user-provided custom views.
7054      *
7055      * @hide
7056      */
isCustomNotification()7057     public Boolean isCustomNotification() {
7058         if (contentView == null
7059                 && bigContentView == null
7060                 && headsUpContentView == null) {
7061             return false;
7062         }
7063         return true;
7064     }
7065 
7066     /**
7067      * @return true if this notification is showing as a bubble
7068      *
7069      * @hide
7070      */
isBubbleNotification()7071     public boolean isBubbleNotification() {
7072         return (flags & Notification.FLAG_BUBBLE) != 0;
7073     }
7074 
hasLargeIcon()7075     private boolean hasLargeIcon() {
7076         return mLargeIcon != null || largeIcon != null;
7077     }
7078 
7079     /**
7080      * @return true if the notification will show the time; false otherwise
7081      * @hide
7082      */
showsTime()7083     public boolean showsTime() {
7084         return when != 0 && extras.getBoolean(EXTRA_SHOW_WHEN);
7085     }
7086 
7087     /**
7088      * @return true if the notification will show a chronometer; false otherwise
7089      * @hide
7090      */
showsChronometer()7091     public boolean showsChronometer() {
7092         return when != 0 && extras.getBoolean(EXTRA_SHOW_CHRONOMETER);
7093     }
7094 
7095     /**
7096      * @return true if the notification has image
7097      */
hasImage()7098     public boolean hasImage() {
7099         if (isStyle(MessagingStyle.class) && extras != null) {
7100             final Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES,
7101                     Parcelable.class);
7102             if (!ArrayUtils.isEmpty(messages)) {
7103                 for (MessagingStyle.Message m : MessagingStyle.Message
7104                         .getMessagesFromBundleArray(messages)) {
7105                     if (m.getDataUri() != null
7106                             && m.getDataMimeType() != null
7107                             && m.getDataMimeType().startsWith("image/")) {
7108                         return true;
7109                     }
7110                 }
7111             }
7112         } else if (hasLargeIcon()) {
7113             return true;
7114         } else if (extras.containsKey(EXTRA_BACKGROUND_IMAGE_URI)) {
7115             return true;
7116         }
7117         return false;
7118     }
7119 
7120 
7121     /**
7122      * @removed
7123      */
7124     @SystemApi
getNotificationStyleClass(String templateClass)7125     public static Class<? extends Style> getNotificationStyleClass(String templateClass) {
7126         for (Class<? extends Style> innerClass : PLATFORM_STYLE_CLASSES) {
7127             if (templateClass.equals(innerClass.getName())) {
7128                 return innerClass;
7129             }
7130         }
7131         return null;
7132     }
7133 
buildCustomContentIntoTemplate(@onNull Context context, @NonNull RemoteViews template, @Nullable RemoteViews customContent, @NonNull StandardTemplateParams p, @NonNull TemplateBindResult result)7134     private static void buildCustomContentIntoTemplate(@NonNull Context context,
7135             @NonNull RemoteViews template, @Nullable RemoteViews customContent,
7136             @NonNull StandardTemplateParams p, @NonNull TemplateBindResult result) {
7137         int childIndex = -1;
7138         if (customContent != null) {
7139             // Need to clone customContent before adding, because otherwise it can no longer be
7140             // parceled independently of remoteViews.
7141             customContent = customContent.clone();
7142             if (p.mHeaderless) {
7143                 template.removeFromParent(R.id.notification_top_line);
7144                 // We do not know how many lines ar emote view has, so we presume it has 2;  this
7145                 // ensures that we don't under-pad the content, which could lead to abuse, at the
7146                 // cost of making single-line custom content over-padded.
7147                 Builder.setHeaderlessVerticalMargins(template, p, true /* hasSecondLine */);
7148             } else {
7149                 // also update the end margin to account for the large icon or expander
7150                 Resources resources = context.getResources();
7151                 result.mTitleMarginSet.applyToView(template, R.id.notification_main_column,
7152                         resources.getDimension(R.dimen.notification_content_margin_end)
7153                                 / resources.getDisplayMetrics().density);
7154             }
7155             template.removeAllViewsExceptId(R.id.notification_main_column, R.id.progress);
7156             template.addView(R.id.notification_main_column, customContent, 0 /* index */);
7157             template.addFlags(RemoteViews.FLAG_REAPPLY_DISALLOWED);
7158             childIndex = 0;
7159         }
7160         template.setIntTag(R.id.notification_main_column,
7161                 com.android.internal.R.id.notification_custom_view_index_tag,
7162                 childIndex);
7163     }
7164 
7165     /**
7166      * An object that can apply a rich notification style to a {@link Notification.Builder}
7167      * object.
7168      */
7169     public static abstract class Style {
7170 
7171         /**
7172          * The number of items allowed simulatanously in the remote input history.
7173          * @hide
7174          */
7175         static final int MAX_REMOTE_INPUT_HISTORY_LINES = 3;
7176         private CharSequence mBigContentTitle;
7177 
7178         /**
7179          * @hide
7180          */
7181         protected CharSequence mSummaryText = null;
7182 
7183         /**
7184          * @hide
7185          */
7186         protected boolean mSummaryTextSet = false;
7187 
7188         protected Builder mBuilder;
7189 
7190         /**
7191          * Overrides ContentTitle in the big form of the template.
7192          * This defaults to the value passed to setContentTitle().
7193          */
internalSetBigContentTitle(CharSequence title)7194         protected void internalSetBigContentTitle(CharSequence title) {
7195             mBigContentTitle = title;
7196         }
7197 
7198         /**
7199          * Set the first line of text after the detail section in the big form of the template.
7200          */
internalSetSummaryText(CharSequence cs)7201         protected void internalSetSummaryText(CharSequence cs) {
7202             mSummaryText = cs;
7203             mSummaryTextSet = true;
7204         }
7205 
setBuilder(Builder builder)7206         public void setBuilder(Builder builder) {
7207             if (mBuilder != builder) {
7208                 mBuilder = builder;
7209                 if (mBuilder != null) {
7210                     mBuilder.setStyle(this);
7211                 }
7212             }
7213         }
7214 
checkBuilder()7215         protected void checkBuilder() {
7216             if (mBuilder == null) {
7217                 throw new IllegalArgumentException("Style requires a valid Builder object");
7218             }
7219         }
7220 
getStandardView(int layoutId)7221         protected RemoteViews getStandardView(int layoutId) {
7222             // TODO(jeffdq): set the view type based on the layout resource?
7223             StandardTemplateParams p = mBuilder.mParams.reset()
7224                     .viewType(StandardTemplateParams.VIEW_TYPE_UNSPECIFIED)
7225                     .fillTextsFrom(mBuilder);
7226             return getStandardView(layoutId, p, null);
7227         }
7228 
7229 
7230         /**
7231          * Get the standard view for this style.
7232          *
7233          * @param layoutId The layout id to use.
7234          * @param p the params for this inflation.
7235          * @param result The result where template bind information is saved.
7236          * @return A remoteView for this style.
7237          * @hide
7238          */
getStandardView(int layoutId, StandardTemplateParams p, TemplateBindResult result)7239         protected RemoteViews getStandardView(int layoutId, StandardTemplateParams p,
7240                 TemplateBindResult result) {
7241             checkBuilder();
7242 
7243             if (mBigContentTitle != null) {
7244                 p.mTitle = mBigContentTitle;
7245             }
7246 
7247             return mBuilder.applyStandardTemplateWithActions(layoutId, p, result);
7248         }
7249 
7250         /**
7251          * Construct a Style-specific RemoteViews for the collapsed notification layout.
7252          * The default implementation has nothing additional to add.
7253          *
7254          * @param increasedHeight true if this layout be created with an increased height.
7255          * @hide
7256          */
makeContentView(boolean increasedHeight)7257         public RemoteViews makeContentView(boolean increasedHeight) {
7258             return null;
7259         }
7260 
7261         /**
7262          * Construct a Style-specific RemoteViews for the final big notification layout.
7263          * @hide
7264          */
makeBigContentView()7265         public RemoteViews makeBigContentView() {
7266             return null;
7267         }
7268 
7269         /**
7270          * Construct a Style-specific RemoteViews for the final HUN layout.
7271          *
7272          * @param increasedHeight true if this layout be created with an increased height.
7273          * @hide
7274          */
makeHeadsUpContentView(boolean increasedHeight)7275         public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
7276             return null;
7277         }
7278 
7279         /**
7280          * Apply any style-specific extras to this notification before shipping it out.
7281          * @hide
7282          */
addExtras(Bundle extras)7283         public void addExtras(Bundle extras) {
7284             if (mSummaryTextSet) {
7285                 extras.putCharSequence(EXTRA_SUMMARY_TEXT, mSummaryText);
7286             }
7287             if (mBigContentTitle != null) {
7288                 extras.putCharSequence(EXTRA_TITLE_BIG, mBigContentTitle);
7289             }
7290             extras.putString(EXTRA_TEMPLATE, this.getClass().getName());
7291         }
7292 
7293         /**
7294          * Reconstruct the internal state of this Style object from extras.
7295          * @hide
7296          */
restoreFromExtras(Bundle extras)7297         protected void restoreFromExtras(Bundle extras) {
7298             if (extras.containsKey(EXTRA_SUMMARY_TEXT)) {
7299                 mSummaryText = extras.getCharSequence(EXTRA_SUMMARY_TEXT);
7300                 mSummaryTextSet = true;
7301             }
7302             if (extras.containsKey(EXTRA_TITLE_BIG)) {
7303                 mBigContentTitle = extras.getCharSequence(EXTRA_TITLE_BIG);
7304             }
7305         }
7306 
7307 
7308         /**
7309          * @hide
7310          */
buildStyled(Notification wip)7311         public Notification buildStyled(Notification wip) {
7312             addExtras(wip.extras);
7313             return wip;
7314         }
7315 
7316         /**
7317          * @hide
7318          */
purgeResources()7319         public void purgeResources() {}
7320 
7321         /**
7322          * Calls {@link android.app.Notification.Builder#build()} on the Builder this Style is
7323          * attached to.
7324          *
7325          * @return the fully constructed Notification.
7326          */
build()7327         public Notification build() {
7328             checkBuilder();
7329             return mBuilder.build();
7330         }
7331 
7332         /**
7333          * @hide
7334          * @return Whether we should put the summary be put into the notification header
7335          */
hasSummaryInHeader()7336         public boolean hasSummaryInHeader() {
7337             return true;
7338         }
7339 
7340         /**
7341          * @hide
7342          * @return Whether custom content views are displayed inline in the style
7343          */
displayCustomViewInline()7344         public boolean displayCustomViewInline() {
7345             return false;
7346         }
7347 
7348         /**
7349          * Reduces the image sizes contained in this style.
7350          *
7351          * @hide
7352          */
reduceImageSizes(Context context)7353         public void reduceImageSizes(Context context) {
7354         }
7355 
7356         /**
7357          * Validate that this style was properly composed. This is called at build time.
7358          * @hide
7359          */
validate(Context context)7360         public void validate(Context context) {
7361         }
7362 
7363         /**
7364          * @hide
7365          */
areNotificationsVisiblyDifferent(Style other)7366         public abstract boolean areNotificationsVisiblyDifferent(Style other);
7367 
7368         /**
7369          * @return the text that should be displayed in the statusBar when heads-upped.
7370          * If {@code null} is returned, the default implementation will be used.
7371          *
7372          * @hide
7373          */
getHeadsUpStatusBarText()7374         public CharSequence getHeadsUpStatusBarText() {
7375             return null;
7376         }
7377     }
7378 
7379     /**
7380      * Helper class for generating large-format notifications that include a large image attachment.
7381      *
7382      * Here's how you'd set the <code>BigPictureStyle</code> on a notification:
7383      * <pre class="prettyprint">
7384      * Notification notif = new Notification.Builder(mContext)
7385      *     .setContentTitle(&quot;New photo from &quot; + sender.toString())
7386      *     .setContentText(subject)
7387      *     .setSmallIcon(R.drawable.new_post)
7388      *     .setLargeIcon(aBitmap)
7389      *     .setStyle(new Notification.BigPictureStyle()
7390      *         .bigPicture(aBigBitmap))
7391      *     .build();
7392      * </pre>
7393      *
7394      * @see Notification#bigContentView
7395      */
7396     public static class BigPictureStyle extends Style {
7397         private Icon mPictureIcon;
7398         private Icon mBigLargeIcon;
7399         private boolean mBigLargeIconSet = false;
7400         private CharSequence mPictureContentDescription;
7401         private boolean mShowBigPictureWhenCollapsed;
7402 
BigPictureStyle()7403         public BigPictureStyle() {
7404         }
7405 
7406         /**
7407          * @deprecated use {@code BigPictureStyle()}.
7408          */
7409         @Deprecated
BigPictureStyle(Builder builder)7410         public BigPictureStyle(Builder builder) {
7411             setBuilder(builder);
7412         }
7413 
7414         /**
7415          * Overrides ContentTitle in the big form of the template.
7416          * This defaults to the value passed to setContentTitle().
7417          */
7418         @NonNull
setBigContentTitle(@ullable CharSequence title)7419         public BigPictureStyle setBigContentTitle(@Nullable CharSequence title) {
7420             internalSetBigContentTitle(safeCharSequence(title));
7421             return this;
7422         }
7423 
7424         /**
7425          * Set the first line of text after the detail section in the big form of the template.
7426          */
7427         @NonNull
setSummaryText(@ullable CharSequence cs)7428         public BigPictureStyle setSummaryText(@Nullable CharSequence cs) {
7429             internalSetSummaryText(safeCharSequence(cs));
7430             return this;
7431         }
7432 
7433         /**
7434          * Set the content description of the big picture.
7435          */
7436         @NonNull
setContentDescription( @ullable CharSequence contentDescription)7437         public BigPictureStyle setContentDescription(
7438                 @Nullable CharSequence contentDescription) {
7439             mPictureContentDescription = contentDescription;
7440             return this;
7441         }
7442 
7443         /**
7444          * @hide
7445          */
7446         @Nullable
getBigPicture()7447         public Icon getBigPicture() {
7448             if (mPictureIcon != null) {
7449                 return mPictureIcon;
7450             }
7451             return null;
7452         }
7453 
7454         /**
7455          * Provide the bitmap to be used as the payload for the BigPicture notification.
7456          */
7457         @NonNull
bigPicture(@ullable Bitmap b)7458         public BigPictureStyle bigPicture(@Nullable Bitmap b) {
7459             mPictureIcon = b == null ? null : Icon.createWithBitmap(b);
7460             return this;
7461         }
7462 
7463         /**
7464          * Provide the content Uri to be used as the payload for the BigPicture notification.
7465          */
7466         @NonNull
bigPicture(@ullable Icon icon)7467         public BigPictureStyle bigPicture(@Nullable Icon icon) {
7468             mPictureIcon = icon;
7469             return this;
7470         }
7471 
7472         /**
7473          * When set, the {@link #bigPicture(Bitmap) big picture} of this style will be promoted and
7474          * shown in place of the {@link Builder#setLargeIcon(Icon) large icon} in the collapsed
7475          * state of this notification.
7476          */
7477         @NonNull
showBigPictureWhenCollapsed(boolean show)7478         public BigPictureStyle showBigPictureWhenCollapsed(boolean show) {
7479             mShowBigPictureWhenCollapsed = show;
7480             return this;
7481         }
7482 
7483         /**
7484          * Override the large icon when the big notification is shown.
7485          */
7486         @NonNull
bigLargeIcon(@ullable Bitmap b)7487         public BigPictureStyle bigLargeIcon(@Nullable Bitmap b) {
7488             return bigLargeIcon(b != null ? Icon.createWithBitmap(b) : null);
7489         }
7490 
7491         /**
7492          * Override the large icon when the big notification is shown.
7493          */
7494         @NonNull
bigLargeIcon(@ullable Icon icon)7495         public BigPictureStyle bigLargeIcon(@Nullable Icon icon) {
7496             mBigLargeIconSet = true;
7497             mBigLargeIcon = icon;
7498             return this;
7499         }
7500 
7501         /** @hide */
7502         public static final int MIN_ASHMEM_BITMAP_SIZE = 128 * (1 << 10);
7503 
7504         /**
7505          * @hide
7506          */
7507         @Override
purgeResources()7508         public void purgeResources() {
7509             super.purgeResources();
7510             if (mPictureIcon != null) {
7511                 mPictureIcon.convertToAshmem();
7512             }
7513             if (mBigLargeIcon != null) {
7514                 mBigLargeIcon.convertToAshmem();
7515             }
7516         }
7517 
7518         /**
7519          * @hide
7520          */
7521         @Override
reduceImageSizes(Context context)7522         public void reduceImageSizes(Context context) {
7523             super.reduceImageSizes(context);
7524             Resources resources = context.getResources();
7525             boolean isLowRam = ActivityManager.isLowRamDeviceStatic();
7526             if (mPictureIcon != null) {
7527                 int maxPictureHeight = resources.getDimensionPixelSize(isLowRam
7528                         ? R.dimen.notification_big_picture_max_height_low_ram
7529                         : R.dimen.notification_big_picture_max_height);
7530                 int maxPictureWidth = resources.getDimensionPixelSize(isLowRam
7531                         ? R.dimen.notification_big_picture_max_width_low_ram
7532                         : R.dimen.notification_big_picture_max_width);
7533                 mPictureIcon.scaleDownIfNecessary(maxPictureWidth, maxPictureHeight);
7534             }
7535             if (mBigLargeIcon != null) {
7536                 int rightIconSize = resources.getDimensionPixelSize(isLowRam
7537                         ? R.dimen.notification_right_icon_size_low_ram
7538                         : R.dimen.notification_right_icon_size);
7539                 mBigLargeIcon.scaleDownIfNecessary(rightIconSize, rightIconSize);
7540             }
7541         }
7542 
7543         /**
7544          * @hide
7545          */
7546         @Override
makeContentView(boolean increasedHeight)7547         public RemoteViews makeContentView(boolean increasedHeight) {
7548             if (mPictureIcon == null || !mShowBigPictureWhenCollapsed) {
7549                 return super.makeContentView(increasedHeight);
7550             }
7551 
7552             StandardTemplateParams p = mBuilder.mParams.reset()
7553                     .viewType(StandardTemplateParams.VIEW_TYPE_NORMAL)
7554                     .fillTextsFrom(mBuilder)
7555                     .promotedPicture(mPictureIcon);
7556             return getStandardView(mBuilder.getBaseLayoutResource(), p, null /* result */);
7557         }
7558 
7559         /**
7560          * @hide
7561          */
7562         @Override
makeHeadsUpContentView(boolean increasedHeight)7563         public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
7564             if (mPictureIcon == null || !mShowBigPictureWhenCollapsed) {
7565                 return super.makeHeadsUpContentView(increasedHeight);
7566             }
7567 
7568             StandardTemplateParams p = mBuilder.mParams.reset()
7569                     .viewType(StandardTemplateParams.VIEW_TYPE_HEADS_UP)
7570                     .fillTextsFrom(mBuilder)
7571                     .promotedPicture(mPictureIcon);
7572             return getStandardView(mBuilder.getHeadsUpBaseLayoutResource(), p, null /* result */);
7573         }
7574 
7575         /**
7576          * @hide
7577          */
makeBigContentView()7578         public RemoteViews makeBigContentView() {
7579             // Replace mN.mLargeIcon with mBigLargeIcon if mBigLargeIconSet
7580             // This covers the following cases:
7581             //   1. mBigLargeIconSet -> mBigLargeIcon (null or non-null) applies, overrides
7582             //          mN.mLargeIcon
7583             //   2. !mBigLargeIconSet -> mN.mLargeIcon applies
7584             Icon oldLargeIcon = null;
7585             Bitmap largeIconLegacy = null;
7586             if (mBigLargeIconSet) {
7587                 oldLargeIcon = mBuilder.mN.mLargeIcon;
7588                 mBuilder.mN.mLargeIcon = mBigLargeIcon;
7589                 // The legacy largeIcon might not allow us to clear the image, as it's taken in
7590                 // replacement if the other one is null. Because we're restoring these legacy icons
7591                 // for old listeners, this is in general non-null.
7592                 largeIconLegacy = mBuilder.mN.largeIcon;
7593                 mBuilder.mN.largeIcon = null;
7594             }
7595 
7596             StandardTemplateParams p = mBuilder.mParams.reset()
7597                     .viewType(StandardTemplateParams.VIEW_TYPE_BIG).fillTextsFrom(mBuilder);
7598             RemoteViews contentView = getStandardView(mBuilder.getBigPictureLayoutResource(),
7599                     p, null /* result */);
7600             if (mSummaryTextSet) {
7601                 contentView.setTextViewText(R.id.text, mBuilder.ensureColorSpanContrast(
7602                         mBuilder.processLegacyText(mSummaryText), p));
7603                 mBuilder.setTextViewColorSecondary(contentView, R.id.text, p);
7604                 contentView.setViewVisibility(R.id.text, View.VISIBLE);
7605             }
7606 
7607             if (mBigLargeIconSet) {
7608                 mBuilder.mN.mLargeIcon = oldLargeIcon;
7609                 mBuilder.mN.largeIcon = largeIconLegacy;
7610             }
7611 
7612             contentView.setImageViewIcon(R.id.big_picture, mPictureIcon);
7613 
7614             if (mPictureContentDescription != null) {
7615                 contentView.setContentDescription(R.id.big_picture, mPictureContentDescription);
7616             }
7617 
7618             return contentView;
7619         }
7620 
7621         /**
7622          * @hide
7623          */
addExtras(Bundle extras)7624         public void addExtras(Bundle extras) {
7625             super.addExtras(extras);
7626 
7627             if (mBigLargeIconSet) {
7628                 extras.putParcelable(EXTRA_LARGE_ICON_BIG, mBigLargeIcon);
7629             }
7630             if (mPictureContentDescription != null) {
7631                 extras.putCharSequence(EXTRA_PICTURE_CONTENT_DESCRIPTION,
7632                         mPictureContentDescription);
7633             }
7634             extras.putBoolean(EXTRA_SHOW_BIG_PICTURE_WHEN_COLLAPSED, mShowBigPictureWhenCollapsed);
7635 
7636             // If the icon contains a bitmap, use the old extra so that listeners which look for
7637             // that extra can still find the picture.  Don't include the new extra in that case,
7638             // to avoid duplicating data.
7639             if (mPictureIcon != null && mPictureIcon.getType() == Icon.TYPE_BITMAP) {
7640                 extras.putParcelable(EXTRA_PICTURE, mPictureIcon.getBitmap());
7641                 extras.putParcelable(EXTRA_PICTURE_ICON, null);
7642             } else {
7643                 extras.putParcelable(EXTRA_PICTURE, null);
7644                 extras.putParcelable(EXTRA_PICTURE_ICON, mPictureIcon);
7645             }
7646         }
7647 
7648         /**
7649          * @hide
7650          */
7651         @Override
restoreFromExtras(Bundle extras)7652         protected void restoreFromExtras(Bundle extras) {
7653             super.restoreFromExtras(extras);
7654 
7655             if (extras.containsKey(EXTRA_LARGE_ICON_BIG)) {
7656                 mBigLargeIconSet = true;
7657                 mBigLargeIcon = extras.getParcelable(EXTRA_LARGE_ICON_BIG, Icon.class);
7658             }
7659 
7660             if (extras.containsKey(EXTRA_PICTURE_CONTENT_DESCRIPTION)) {
7661                 mPictureContentDescription =
7662                         extras.getCharSequence(EXTRA_PICTURE_CONTENT_DESCRIPTION);
7663             }
7664 
7665             mShowBigPictureWhenCollapsed = extras.getBoolean(EXTRA_SHOW_BIG_PICTURE_WHEN_COLLAPSED);
7666 
7667             mPictureIcon = getPictureIcon(extras);
7668         }
7669 
7670         /** @hide */
7671         @Nullable
getPictureIcon(@ullable Bundle extras)7672         public static Icon getPictureIcon(@Nullable Bundle extras) {
7673             if (extras == null) return null;
7674             // When this style adds a picture, we only add one of the keys.  If both were added,
7675             // it would most likely be a legacy app trying to override the picture in some way.
7676             // Because of that case it's better to give precedence to the legacy field.
7677             Bitmap bitmapPicture = extras.getParcelable(EXTRA_PICTURE, Bitmap.class);
7678             if (bitmapPicture != null) {
7679                 return Icon.createWithBitmap(bitmapPicture);
7680             } else {
7681                 return extras.getParcelable(EXTRA_PICTURE_ICON, Icon.class);
7682             }
7683         }
7684 
7685         /**
7686          * @hide
7687          */
7688         @Override
hasSummaryInHeader()7689         public boolean hasSummaryInHeader() {
7690             return false;
7691         }
7692 
7693         /**
7694          * @hide
7695          */
7696         @Override
areNotificationsVisiblyDifferent(Style other)7697         public boolean areNotificationsVisiblyDifferent(Style other) {
7698             if (other == null || getClass() != other.getClass()) {
7699                 return true;
7700             }
7701             BigPictureStyle otherS = (BigPictureStyle) other;
7702             return areIconsMaybeDifferent(getBigPicture(), otherS.getBigPicture());
7703         }
7704     }
7705 
7706     /**
7707      * Helper class for generating large-format notifications that include a lot of text.
7708      *
7709      * Here's how you'd set the <code>BigTextStyle</code> on a notification:
7710      * <pre class="prettyprint">
7711      * Notification notif = new Notification.Builder(mContext)
7712      *     .setContentTitle(&quot;New mail from &quot; + sender.toString())
7713      *     .setContentText(subject)
7714      *     .setSmallIcon(R.drawable.new_mail)
7715      *     .setLargeIcon(aBitmap)
7716      *     .setStyle(new Notification.BigTextStyle()
7717      *         .bigText(aVeryLongString))
7718      *     .build();
7719      * </pre>
7720      *
7721      * @see Notification#bigContentView
7722      */
7723     public static class BigTextStyle extends Style {
7724 
7725         private CharSequence mBigText;
7726 
BigTextStyle()7727         public BigTextStyle() {
7728         }
7729 
7730         /**
7731          * @deprecated use {@code BigTextStyle()}.
7732          */
7733         @Deprecated
BigTextStyle(Builder builder)7734         public BigTextStyle(Builder builder) {
7735             setBuilder(builder);
7736         }
7737 
7738         /**
7739          * Overrides ContentTitle in the big form of the template.
7740          * This defaults to the value passed to setContentTitle().
7741          */
setBigContentTitle(CharSequence title)7742         public BigTextStyle setBigContentTitle(CharSequence title) {
7743             internalSetBigContentTitle(safeCharSequence(title));
7744             return this;
7745         }
7746 
7747         /**
7748          * Set the first line of text after the detail section in the big form of the template.
7749          */
setSummaryText(CharSequence cs)7750         public BigTextStyle setSummaryText(CharSequence cs) {
7751             internalSetSummaryText(safeCharSequence(cs));
7752             return this;
7753         }
7754 
7755         /**
7756          * Provide the longer text to be displayed in the big form of the
7757          * template in place of the content text.
7758          */
bigText(CharSequence cs)7759         public BigTextStyle bigText(CharSequence cs) {
7760             mBigText = safeCharSequence(cs);
7761             return this;
7762         }
7763 
7764         /**
7765          * @hide
7766          */
getBigText()7767         public CharSequence getBigText() {
7768             return mBigText;
7769         }
7770 
7771         /**
7772          * @hide
7773          */
addExtras(Bundle extras)7774         public void addExtras(Bundle extras) {
7775             super.addExtras(extras);
7776 
7777             extras.putCharSequence(EXTRA_BIG_TEXT, mBigText);
7778         }
7779 
7780         /**
7781          * @hide
7782          */
7783         @Override
restoreFromExtras(Bundle extras)7784         protected void restoreFromExtras(Bundle extras) {
7785             super.restoreFromExtras(extras);
7786 
7787             mBigText = extras.getCharSequence(EXTRA_BIG_TEXT);
7788         }
7789 
7790         /**
7791          * @param increasedHeight true if this layout be created with an increased height.
7792          *
7793          * @hide
7794          */
7795         @Override
makeContentView(boolean increasedHeight)7796         public RemoteViews makeContentView(boolean increasedHeight) {
7797             if (increasedHeight) {
7798                 ArrayList<Action> originalActions = mBuilder.mActions;
7799                 mBuilder.mActions = new ArrayList<>();
7800                 RemoteViews remoteViews = makeBigContentView();
7801                 mBuilder.mActions = originalActions;
7802                 return remoteViews;
7803             }
7804             return super.makeContentView(increasedHeight);
7805         }
7806 
7807         /**
7808          * @hide
7809          */
7810         @Override
makeHeadsUpContentView(boolean increasedHeight)7811         public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
7812             if (increasedHeight && mBuilder.mActions.size() > 0) {
7813                 // TODO(b/163626038): pass VIEW_TYPE_HEADS_UP?
7814                 return makeBigContentView();
7815             }
7816             return super.makeHeadsUpContentView(increasedHeight);
7817         }
7818 
7819         /**
7820          * @hide
7821          */
makeBigContentView()7822         public RemoteViews makeBigContentView() {
7823             StandardTemplateParams p = mBuilder.mParams.reset()
7824                     .viewType(StandardTemplateParams.VIEW_TYPE_BIG)
7825                     .allowTextWithProgress(true)
7826                     .textViewId(R.id.big_text)
7827                     .fillTextsFrom(mBuilder);
7828 
7829             // Replace the text with the big text, but only if the big text is not empty.
7830             CharSequence bigTextText = mBuilder.processLegacyText(mBigText);
7831             if (!TextUtils.isEmpty(bigTextText)) {
7832                 p.text(bigTextText);
7833             }
7834 
7835             return getStandardView(mBuilder.getBigTextLayoutResource(), p, null /* result */);
7836         }
7837 
7838         /**
7839          * @hide
7840          * Spans are ignored when comparing text for visual difference.
7841          */
7842         @Override
areNotificationsVisiblyDifferent(Style other)7843         public boolean areNotificationsVisiblyDifferent(Style other) {
7844             if (other == null || getClass() != other.getClass()) {
7845                 return true;
7846             }
7847             BigTextStyle newS = (BigTextStyle) other;
7848             return !Objects.equals(String.valueOf(getBigText()), String.valueOf(newS.getBigText()));
7849         }
7850 
7851     }
7852 
7853     /**
7854      * Helper class for generating large-format notifications that include multiple back-and-forth
7855      * messages of varying types between any number of people.
7856      *
7857      * <p>
7858      * If the platform does not provide large-format notifications, this method has no effect. The
7859      * user will always see the normal notification view.
7860      *
7861      * <p>
7862      * If the app is targeting Android {@link android.os.Build.VERSION_CODES#P} and above, it is
7863      * required to use the {@link Person} class in order to get an optimal rendering of the
7864      * notification and its avatars. For conversations involving multiple people, the app should
7865      * also make sure that it marks the conversation as a group with
7866      * {@link #setGroupConversation(boolean)}.
7867      *
7868      * <p>
7869      * From Android {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, messaging style
7870      * notifications that are associated with a valid conversation shortcut
7871      * (via {@link Notification.Builder#setShortcutId(String)}) are displayed in a dedicated
7872      * conversation section in the shade above non-conversation alerting and silence notifications.
7873      * To be a valid conversation shortcut, the shortcut must be a
7874      * {@link ShortcutInfo#setLongLived()} dynamic or cached sharing shortcut.
7875      *
7876      * <p>
7877      * This class is a "rebuilder": It attaches to a Builder object and modifies its behavior.
7878      * Here's an example of how this may be used:
7879      * <pre class="prettyprint">
7880      *
7881      * Person user = new Person.Builder().setIcon(userIcon).setName(userName).build();
7882      * MessagingStyle style = new MessagingStyle(user)
7883      *      .addMessage(messages[1].getText(), messages[1].getTime(), messages[1].getPerson())
7884      *      .addMessage(messages[2].getText(), messages[2].getTime(), messages[2].getPerson())
7885      *      .setGroupConversation(hasMultiplePeople());
7886      *
7887      * Notification noti = new Notification.Builder()
7888      *     .setContentTitle(&quot;2 new messages with &quot; + sender.toString())
7889      *     .setContentText(subject)
7890      *     .setSmallIcon(R.drawable.new_message)
7891      *     .setLargeIcon(aBitmap)
7892      *     .setStyle(style)
7893      *     .build();
7894      * </pre>
7895      */
7896     public static class MessagingStyle extends Style {
7897 
7898         /**
7899          * The maximum number of messages that will be retained in the Notification itself (the
7900          * number displayed is up to the platform).
7901          */
7902         public static final int MAXIMUM_RETAINED_MESSAGES = 25;
7903 
7904 
7905         /** @hide */
7906         public static final int CONVERSATION_TYPE_LEGACY = 0;
7907         /** @hide */
7908         public static final int CONVERSATION_TYPE_NORMAL = 1;
7909         /** @hide */
7910         public static final int CONVERSATION_TYPE_IMPORTANT = 2;
7911 
7912         /** @hide */
7913         @IntDef(prefix = {"CONVERSATION_TYPE_"}, value = {
7914                 CONVERSATION_TYPE_LEGACY, CONVERSATION_TYPE_NORMAL, CONVERSATION_TYPE_IMPORTANT
7915         })
7916         @Retention(RetentionPolicy.SOURCE)
7917         public @interface ConversationType {}
7918 
7919         @NonNull Person mUser;
7920         @Nullable CharSequence mConversationTitle;
7921         @Nullable Icon mShortcutIcon;
7922         List<Message> mMessages = new ArrayList<>();
7923         List<Message> mHistoricMessages = new ArrayList<>();
7924         boolean mIsGroupConversation;
7925         @ConversationType int mConversationType = CONVERSATION_TYPE_LEGACY;
7926         int mUnreadMessageCount;
7927 
MessagingStyle()7928         MessagingStyle() {
7929         }
7930 
7931         /**
7932          * @param userDisplayName Required - the name to be displayed for any replies sent by the
7933          * user before the posting app reposts the notification with those messages after they've
7934          * been actually sent and in previous messages sent by the user added in
7935          * {@link #addMessage(Notification.MessagingStyle.Message)}
7936          *
7937          * @deprecated use {@code MessagingStyle(Person)}
7938          */
MessagingStyle(@onNull CharSequence userDisplayName)7939         public MessagingStyle(@NonNull CharSequence userDisplayName) {
7940             this(new Person.Builder().setName(userDisplayName).build());
7941         }
7942 
7943         /**
7944          * @param user Required - The person displayed for any messages that are sent by the
7945          * user. Any messages added with {@link #addMessage(Notification.MessagingStyle.Message)}
7946          * who don't have a Person associated with it will be displayed as if they were sent
7947          * by this user. The user also needs to have a valid name associated with it, which is
7948          * enforced starting in Android {@link android.os.Build.VERSION_CODES#P}.
7949          */
MessagingStyle(@onNull Person user)7950         public MessagingStyle(@NonNull Person user) {
7951             mUser = user;
7952         }
7953 
7954         /**
7955          * Validate that this style was properly composed. This is called at build time.
7956          * @hide
7957          */
7958         @Override
validate(Context context)7959         public void validate(Context context) {
7960             super.validate(context);
7961             if (context.getApplicationInfo().targetSdkVersion
7962                     >= Build.VERSION_CODES.P && (mUser == null || mUser.getName() == null)) {
7963                 throw new RuntimeException("User must be valid and have a name.");
7964             }
7965         }
7966 
7967         /**
7968          * @return the text that should be displayed in the statusBar when heads upped.
7969          * If {@code null} is returned, the default implementation will be used.
7970          *
7971          * @hide
7972          */
7973         @Override
getHeadsUpStatusBarText()7974         public CharSequence getHeadsUpStatusBarText() {
7975             CharSequence conversationTitle = !TextUtils.isEmpty(super.mBigContentTitle)
7976                     ? super.mBigContentTitle
7977                     : mConversationTitle;
7978             if (mConversationType == CONVERSATION_TYPE_LEGACY
7979                     && !TextUtils.isEmpty(conversationTitle) && !hasOnlyWhiteSpaceSenders()) {
7980                 return conversationTitle;
7981             }
7982             return null;
7983         }
7984 
7985         /**
7986          * @return the user to be displayed for any replies sent by the user
7987          */
7988         @NonNull
getUser()7989         public Person getUser() {
7990             return mUser;
7991         }
7992 
7993         /**
7994          * Returns the name to be displayed for any replies sent by the user
7995          *
7996          * @deprecated use {@link #getUser()} instead
7997          */
getUserDisplayName()7998         public CharSequence getUserDisplayName() {
7999             return mUser.getName();
8000         }
8001 
8002         /**
8003          * Sets the title to be displayed on this conversation. May be set to {@code null}.
8004          *
8005          * <p>Starting in {@link Build.VERSION_CODES#R}, this conversation title will be ignored
8006          * if a valid shortcutId is added via {@link Notification.Builder#setShortcutId(String)}.
8007          * In this case, {@link ShortcutInfo#getLongLabel()} (or, if missing,
8008          * {@link ShortcutInfo#getShortLabel()}) will be shown as the conversation title
8009          * instead.
8010          *
8011          * <p>This API's behavior was changed in SDK version {@link Build.VERSION_CODES#P}. If your
8012          * application's target version is less than {@link Build.VERSION_CODES#P}, setting a
8013          * conversation title to a non-null value will make {@link #isGroupConversation()} return
8014          * {@code true} and passing {@code null} will make it return {@code false}. In
8015          * {@link Build.VERSION_CODES#P} and beyond, use {@link #setGroupConversation(boolean)}
8016          * to set group conversation status.
8017          *
8018          * @param conversationTitle Title displayed for this conversation
8019          * @return this object for method chaining
8020          */
setConversationTitle(@ullable CharSequence conversationTitle)8021         public MessagingStyle setConversationTitle(@Nullable CharSequence conversationTitle) {
8022             mConversationTitle = conversationTitle;
8023             return this;
8024         }
8025 
8026         /**
8027          * Return the title to be displayed on this conversation. May return {@code null}.
8028          */
8029         @Nullable
getConversationTitle()8030         public CharSequence getConversationTitle() {
8031             return mConversationTitle;
8032         }
8033 
8034         /**
8035          * Sets the icon to be displayed on the conversation, derived from the shortcutId.
8036          *
8037          * @hide
8038          */
setShortcutIcon(@ullable Icon conversationIcon)8039         public MessagingStyle setShortcutIcon(@Nullable Icon conversationIcon) {
8040             mShortcutIcon = conversationIcon;
8041             return this;
8042         }
8043 
8044         /**
8045          * Return the icon to be displayed on this conversation, derived from the shortcutId. May
8046          * return {@code null}.
8047          *
8048          * @hide
8049          */
8050         @Nullable
getShortcutIcon()8051         public Icon getShortcutIcon() {
8052             return mShortcutIcon;
8053         }
8054 
8055         /**
8056          * Sets the conversation type of this MessageStyle notification.
8057          * {@link #CONVERSATION_TYPE_LEGACY} will use the "older" layout from pre-R,
8058          * {@link #CONVERSATION_TYPE_NORMAL} will use the new "conversation" layout, and
8059          * {@link #CONVERSATION_TYPE_IMPORTANT} will add additional "important" treatments.
8060          *
8061          * @hide
8062          */
setConversationType(@onversationType int conversationType)8063         public MessagingStyle setConversationType(@ConversationType int conversationType) {
8064             mConversationType = conversationType;
8065             return this;
8066         }
8067 
8068         /** @hide */
8069         @ConversationType
getConversationType()8070         public int getConversationType() {
8071             return mConversationType;
8072         }
8073 
8074         /** @hide */
getUnreadMessageCount()8075         public int getUnreadMessageCount() {
8076             return mUnreadMessageCount;
8077         }
8078 
8079         /** @hide */
setUnreadMessageCount(int unreadMessageCount)8080         public MessagingStyle setUnreadMessageCount(int unreadMessageCount) {
8081             mUnreadMessageCount = unreadMessageCount;
8082             return this;
8083         }
8084 
8085         /**
8086          * Adds a message for display by this notification. Convenience call for a simple
8087          * {@link Message} in {@link #addMessage(Notification.MessagingStyle.Message)}.
8088          * @param text A {@link CharSequence} to be displayed as the message content
8089          * @param timestamp Time in milliseconds at which the message arrived
8090          * @param sender A {@link CharSequence} to be used for displaying the name of the
8091          * sender. Should be <code>null</code> for messages by the current user, in which case
8092          * the platform will insert {@link #getUserDisplayName()}.
8093          * Should be unique amongst all individuals in the conversation, and should be
8094          * consistent during re-posts of the notification.
8095          *
8096          * @see Message#Message(CharSequence, long, CharSequence)
8097          *
8098          * @return this object for method chaining
8099          *
8100          * @deprecated use {@link #addMessage(CharSequence, long, Person)}
8101          */
addMessage(CharSequence text, long timestamp, CharSequence sender)8102         public MessagingStyle addMessage(CharSequence text, long timestamp, CharSequence sender) {
8103             return addMessage(text, timestamp,
8104                     sender == null ? null : new Person.Builder().setName(sender).build());
8105         }
8106 
8107         /**
8108          * Adds a message for display by this notification. Convenience call for a simple
8109          * {@link Message} in {@link #addMessage(Notification.MessagingStyle.Message)}.
8110          * @param text A {@link CharSequence} to be displayed as the message content
8111          * @param timestamp Time in milliseconds at which the message arrived
8112          * @param sender The {@link Person} who sent the message.
8113          * Should be <code>null</code> for messages by the current user, in which case
8114          * the platform will insert the user set in {@code MessagingStyle(Person)}.
8115          *
8116          * @see Message#Message(CharSequence, long, CharSequence)
8117          *
8118          * @return this object for method chaining
8119          */
addMessage(@onNull CharSequence text, long timestamp, @Nullable Person sender)8120         public MessagingStyle addMessage(@NonNull CharSequence text, long timestamp,
8121                 @Nullable Person sender) {
8122             return addMessage(new Message(text, timestamp, sender));
8123         }
8124 
8125         /**
8126          * Adds a {@link Message} for display in this notification.
8127          *
8128          * <p>The messages should be added in chronologic order, i.e. the oldest first,
8129          * the newest last.
8130          *
8131          * @param message The {@link Message} to be displayed
8132          * @return this object for method chaining
8133          */
addMessage(Message message)8134         public MessagingStyle addMessage(Message message) {
8135             mMessages.add(message);
8136             if (mMessages.size() > MAXIMUM_RETAINED_MESSAGES) {
8137                 mMessages.remove(0);
8138             }
8139             return this;
8140         }
8141 
8142         /**
8143          * Adds a {@link Message} for historic context in this notification.
8144          *
8145          * <p>Messages should be added as historic if they are not the main subject of the
8146          * notification but may give context to a conversation. The system may choose to present
8147          * them only when relevant, e.g. when replying to a message through a {@link RemoteInput}.
8148          *
8149          * <p>The messages should be added in chronologic order, i.e. the oldest first,
8150          * the newest last.
8151          *
8152          * @param message The historic {@link Message} to be added
8153          * @return this object for method chaining
8154          */
addHistoricMessage(Message message)8155         public MessagingStyle addHistoricMessage(Message message) {
8156             mHistoricMessages.add(message);
8157             if (mHistoricMessages.size() > MAXIMUM_RETAINED_MESSAGES) {
8158                 mHistoricMessages.remove(0);
8159             }
8160             return this;
8161         }
8162 
8163         /**
8164          * Gets the list of {@code Message} objects that represent the notification.
8165          */
getMessages()8166         public List<Message> getMessages() {
8167             return mMessages;
8168         }
8169 
8170         /**
8171          * Gets the list of historic {@code Message}s in the notification.
8172          */
getHistoricMessages()8173         public List<Message> getHistoricMessages() {
8174             return mHistoricMessages;
8175         }
8176 
8177         /**
8178          * Sets whether this conversation notification represents a group. If the app is targeting
8179          * Android P, this is required if the app wants to display the largeIcon set with
8180          * {@link Notification.Builder#setLargeIcon(Bitmap)}, otherwise it will be hidden.
8181          *
8182          * @param isGroupConversation {@code true} if the conversation represents a group,
8183          * {@code false} otherwise.
8184          * @return this object for method chaining
8185          */
setGroupConversation(boolean isGroupConversation)8186         public MessagingStyle setGroupConversation(boolean isGroupConversation) {
8187             mIsGroupConversation = isGroupConversation;
8188             return this;
8189         }
8190 
8191         /**
8192          * Returns {@code true} if this notification represents a group conversation, otherwise
8193          * {@code false}.
8194          *
8195          * <p> If the application that generated this {@link MessagingStyle} targets an SDK version
8196          * less than {@link Build.VERSION_CODES#P}, this method becomes dependent on whether or
8197          * not the conversation title is set; returning {@code true} if the conversation title is
8198          * a non-null value, or {@code false} otherwise. From {@link Build.VERSION_CODES#P} forward,
8199          * this method returns what's set by {@link #setGroupConversation(boolean)} allowing for
8200          * named, non-group conversations.
8201          *
8202          * @see #setConversationTitle(CharSequence)
8203          */
isGroupConversation()8204         public boolean isGroupConversation() {
8205             // When target SDK version is < P, a non-null conversation title dictates if this is
8206             // as group conversation.
8207             if (mBuilder != null
8208                     && mBuilder.mContext.getApplicationInfo().targetSdkVersion
8209                             < Build.VERSION_CODES.P) {
8210                 return mConversationTitle != null;
8211             }
8212 
8213             return mIsGroupConversation;
8214         }
8215 
8216         /**
8217          * @hide
8218          */
8219         @Override
addExtras(Bundle extras)8220         public void addExtras(Bundle extras) {
8221             super.addExtras(extras);
8222             addExtras(extras, false, 0);
8223         }
8224 
8225         /**
8226          * @hide
8227          */
addExtras(Bundle extras, boolean ensureContrast, int backgroundColor)8228         public void addExtras(Bundle extras, boolean ensureContrast, int backgroundColor) {
8229             if (mUser != null) {
8230                 // For legacy usages
8231                 extras.putCharSequence(EXTRA_SELF_DISPLAY_NAME, mUser.getName());
8232                 extras.putParcelable(EXTRA_MESSAGING_PERSON, mUser);
8233             }
8234             if (mConversationTitle != null) {
8235                 extras.putCharSequence(EXTRA_CONVERSATION_TITLE, mConversationTitle);
8236             }
8237             if (!mMessages.isEmpty()) {
8238                 extras.putParcelableArray(EXTRA_MESSAGES,
8239                         getBundleArrayForMessages(mMessages, ensureContrast, backgroundColor));
8240             }
8241             if (!mHistoricMessages.isEmpty()) {
8242                 extras.putParcelableArray(EXTRA_HISTORIC_MESSAGES, getBundleArrayForMessages(
8243                         mHistoricMessages, ensureContrast, backgroundColor));
8244             }
8245             if (mShortcutIcon != null) {
8246                 extras.putParcelable(EXTRA_CONVERSATION_ICON, mShortcutIcon);
8247             }
8248             extras.putInt(EXTRA_CONVERSATION_UNREAD_MESSAGE_COUNT, mUnreadMessageCount);
8249 
8250             fixTitleAndTextExtras(extras);
8251             extras.putBoolean(EXTRA_IS_GROUP_CONVERSATION, mIsGroupConversation);
8252         }
8253 
getBundleArrayForMessages(List<Message> messages, boolean ensureContrast, int backgroundColor)8254         private static Bundle[] getBundleArrayForMessages(List<Message> messages,
8255                 boolean ensureContrast, int backgroundColor) {
8256             Bundle[] bundles = new Bundle[messages.size()];
8257             final int N = messages.size();
8258             for (int i = 0; i < N; i++) {
8259                 final Message m = messages.get(i);
8260                 if (ensureContrast) {
8261                     m.ensureColorContrast(backgroundColor);
8262                 }
8263                 bundles[i] = m.toBundle();
8264             }
8265             return bundles;
8266         }
8267 
fixTitleAndTextExtras(Bundle extras)8268         private void fixTitleAndTextExtras(Bundle extras) {
8269             Message m = findLatestIncomingMessage();
8270             CharSequence text = (m == null) ? null : m.mText;
8271             CharSequence sender = m == null ? null
8272                     : m.mSender == null || TextUtils.isEmpty(m.mSender.getName())
8273                             ? mUser.getName() : m.mSender.getName();
8274             CharSequence title;
8275             if (!TextUtils.isEmpty(mConversationTitle)) {
8276                 if (!TextUtils.isEmpty(sender)) {
8277                     BidiFormatter bidi = BidiFormatter.getInstance();
8278                     title = mBuilder.mContext.getString(
8279                             com.android.internal.R.string.notification_messaging_title_template,
8280                             bidi.unicodeWrap(mConversationTitle), bidi.unicodeWrap(sender));
8281                 } else {
8282                     title = mConversationTitle;
8283                 }
8284             } else {
8285                 title = sender;
8286             }
8287 
8288             if (title != null) {
8289                 extras.putCharSequence(EXTRA_TITLE, title);
8290             }
8291             if (text != null) {
8292                 extras.putCharSequence(EXTRA_TEXT, text);
8293             }
8294         }
8295 
8296         /**
8297          * @hide
8298          */
8299         @Override
restoreFromExtras(Bundle extras)8300         protected void restoreFromExtras(Bundle extras) {
8301             super.restoreFromExtras(extras);
8302 
8303             Person user = extras.getParcelable(EXTRA_MESSAGING_PERSON, Person.class);
8304             if (user == null) {
8305                 CharSequence displayName = extras.getCharSequence(EXTRA_SELF_DISPLAY_NAME);
8306                 mUser = new Person.Builder().setName(displayName).build();
8307             } else {
8308                 mUser = user;
8309             }
8310             mConversationTitle = extras.getCharSequence(EXTRA_CONVERSATION_TITLE);
8311             Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES, Parcelable.class);
8312             mMessages = Message.getMessagesFromBundleArray(messages);
8313             Parcelable[] histMessages = extras.getParcelableArray(EXTRA_HISTORIC_MESSAGES,
8314                     Parcelable.class);
8315             mHistoricMessages = Message.getMessagesFromBundleArray(histMessages);
8316             mIsGroupConversation = extras.getBoolean(EXTRA_IS_GROUP_CONVERSATION);
8317             mUnreadMessageCount = extras.getInt(EXTRA_CONVERSATION_UNREAD_MESSAGE_COUNT);
8318             mShortcutIcon = extras.getParcelable(EXTRA_CONVERSATION_ICON, Icon.class);
8319         }
8320 
8321         /**
8322          * @hide
8323          */
8324         @Override
makeContentView(boolean increasedHeight)8325         public RemoteViews makeContentView(boolean increasedHeight) {
8326             // All messaging templates contain the actions
8327             ArrayList<Action> originalActions = mBuilder.mActions;
8328             try {
8329                 mBuilder.mActions = new ArrayList<>();
8330                 return makeMessagingView(StandardTemplateParams.VIEW_TYPE_NORMAL);
8331             } finally {
8332                 mBuilder.mActions = originalActions;
8333             }
8334         }
8335 
8336         /**
8337          * @hide
8338          * Spans are ignored when comparing text for visual difference.
8339          */
8340         @Override
areNotificationsVisiblyDifferent(Style other)8341         public boolean areNotificationsVisiblyDifferent(Style other) {
8342             if (other == null || getClass() != other.getClass()) {
8343                 return true;
8344             }
8345             MessagingStyle newS = (MessagingStyle) other;
8346             List<MessagingStyle.Message> oldMs = getMessages();
8347             List<MessagingStyle.Message> newMs = newS.getMessages();
8348 
8349             if (oldMs == null || newMs == null) {
8350                 newMs = new ArrayList<>();
8351             }
8352 
8353             int n = oldMs.size();
8354             if (n != newMs.size()) {
8355                 return true;
8356             }
8357             for (int i = 0; i < n; i++) {
8358                 MessagingStyle.Message oldM = oldMs.get(i);
8359                 MessagingStyle.Message newM = newMs.get(i);
8360                 if (!Objects.equals(
8361                         String.valueOf(oldM.getText()),
8362                         String.valueOf(newM.getText()))) {
8363                     return true;
8364                 }
8365                 if (!Objects.equals(oldM.getDataUri(), newM.getDataUri())) {
8366                     return true;
8367                 }
8368                 String oldSender = String.valueOf(oldM.getSenderPerson() == null
8369                         ? oldM.getSender()
8370                         : oldM.getSenderPerson().getName());
8371                 String newSender = String.valueOf(newM.getSenderPerson() == null
8372                         ? newM.getSender()
8373                         : newM.getSenderPerson().getName());
8374                 if (!Objects.equals(oldSender, newSender)) {
8375                     return true;
8376                 }
8377 
8378                 String oldKey = oldM.getSenderPerson() == null
8379                         ? null : oldM.getSenderPerson().getKey();
8380                 String newKey = newM.getSenderPerson() == null
8381                         ? null : newM.getSenderPerson().getKey();
8382                 if (!Objects.equals(oldKey, newKey)) {
8383                     return true;
8384                 }
8385                 // Other fields (like timestamp) intentionally excluded
8386             }
8387             return false;
8388         }
8389 
findLatestIncomingMessage()8390         private Message findLatestIncomingMessage() {
8391             return findLatestIncomingMessage(mMessages);
8392         }
8393 
8394         /**
8395          * @hide
8396          */
8397         @Nullable
findLatestIncomingMessage( List<Message> messages)8398         public static Message findLatestIncomingMessage(
8399                 List<Message> messages) {
8400             for (int i = messages.size() - 1; i >= 0; i--) {
8401                 Message m = messages.get(i);
8402                 // Incoming messages have a non-empty sender.
8403                 if (m.mSender != null && !TextUtils.isEmpty(m.mSender.getName())) {
8404                     return m;
8405                 }
8406             }
8407             if (!messages.isEmpty()) {
8408                 // No incoming messages, fall back to outgoing message
8409                 return messages.get(messages.size() - 1);
8410             }
8411             return null;
8412         }
8413 
8414         /**
8415          * @hide
8416          */
8417         @Override
makeBigContentView()8418         public RemoteViews makeBigContentView() {
8419             return makeMessagingView(StandardTemplateParams.VIEW_TYPE_BIG);
8420         }
8421 
8422         /**
8423          * Create a messaging layout.
8424          *
8425          * @param viewType one of StandardTemplateParams.VIEW_TYPE_NORMAL, VIEW_TYPE_BIG,
8426          *                VIEW_TYPE_HEADS_UP
8427          * @return the created remoteView.
8428          */
8429         @NonNull
makeMessagingView(int viewType)8430         private RemoteViews makeMessagingView(int viewType) {
8431             boolean isCollapsed = viewType != StandardTemplateParams.VIEW_TYPE_BIG;
8432             boolean hideRightIcons = viewType != StandardTemplateParams.VIEW_TYPE_NORMAL;
8433             boolean isConversationLayout = mConversationType != CONVERSATION_TYPE_LEGACY;
8434             boolean isImportantConversation = mConversationType == CONVERSATION_TYPE_IMPORTANT;
8435             boolean isHeaderless = !isConversationLayout && isCollapsed;
8436 
8437             CharSequence conversationTitle = !TextUtils.isEmpty(super.mBigContentTitle)
8438                     ? super.mBigContentTitle
8439                     : mConversationTitle;
8440             boolean atLeastP = mBuilder.mContext.getApplicationInfo().targetSdkVersion
8441                     >= Build.VERSION_CODES.P;
8442             boolean isOneToOne;
8443             CharSequence nameReplacement = null;
8444             if (!atLeastP) {
8445                 isOneToOne = TextUtils.isEmpty(conversationTitle);
8446                 if (hasOnlyWhiteSpaceSenders()) {
8447                     isOneToOne = true;
8448                     nameReplacement = conversationTitle;
8449                     conversationTitle = null;
8450                 }
8451             } else {
8452                 isOneToOne = !isGroupConversation();
8453             }
8454             if (isHeaderless && isOneToOne && TextUtils.isEmpty(conversationTitle)) {
8455                 conversationTitle = getOtherPersonName();
8456             }
8457 
8458             Icon largeIcon = mBuilder.mN.mLargeIcon;
8459             TemplateBindResult bindResult = new TemplateBindResult();
8460             StandardTemplateParams p = mBuilder.mParams.reset()
8461                     .viewType(viewType)
8462                     .highlightExpander(isConversationLayout)
8463                     .hideProgress(true)
8464                     .title(isHeaderless ? conversationTitle : null)
8465                     .text(null)
8466                     .hideLeftIcon(isOneToOne)
8467                     .hideRightIcon(hideRightIcons || isOneToOne)
8468                     .headerTextSecondary(isHeaderless ? null : conversationTitle);
8469             RemoteViews contentView = mBuilder.applyStandardTemplateWithActions(
8470                     isConversationLayout
8471                             ? mBuilder.getConversationLayoutResource()
8472                             : isCollapsed
8473                                     ? mBuilder.getMessagingLayoutResource()
8474                                     : mBuilder.getBigMessagingLayoutResource(),
8475                     p,
8476                     bindResult);
8477             if (isConversationLayout) {
8478                 mBuilder.setTextViewColorPrimary(contentView, R.id.conversation_text, p);
8479                 mBuilder.setTextViewColorSecondary(contentView, R.id.app_name_divider, p);
8480             }
8481 
8482             addExtras(mBuilder.mN.extras, true, mBuilder.getBackgroundColor(p));
8483             contentView.setInt(R.id.status_bar_latest_event_content, "setLayoutColor",
8484                     mBuilder.getSmallIconColor(p));
8485             contentView.setInt(R.id.status_bar_latest_event_content, "setSenderTextColor",
8486                     mBuilder.getPrimaryTextColor(p));
8487             contentView.setInt(R.id.status_bar_latest_event_content, "setMessageTextColor",
8488                     mBuilder.getSecondaryTextColor(p));
8489             contentView.setInt(R.id.status_bar_latest_event_content,
8490                     "setNotificationBackgroundColor",
8491                     mBuilder.getBackgroundColor(p));
8492             contentView.setBoolean(R.id.status_bar_latest_event_content, "setIsCollapsed",
8493                     isCollapsed);
8494             contentView.setIcon(R.id.status_bar_latest_event_content, "setAvatarReplacement",
8495                     mBuilder.mN.mLargeIcon);
8496             contentView.setCharSequence(R.id.status_bar_latest_event_content, "setNameReplacement",
8497                     nameReplacement);
8498             contentView.setBoolean(R.id.status_bar_latest_event_content, "setIsOneToOne",
8499                     isOneToOne);
8500             contentView.setCharSequence(R.id.status_bar_latest_event_content,
8501                     "setConversationTitle", conversationTitle);
8502             if (isConversationLayout) {
8503                 contentView.setIcon(R.id.status_bar_latest_event_content,
8504                         "setShortcutIcon", mShortcutIcon);
8505                 contentView.setBoolean(R.id.status_bar_latest_event_content,
8506                         "setIsImportantConversation", isImportantConversation);
8507             }
8508             if (isHeaderless) {
8509                 // Collapsed legacy messaging style has a 1-line limit.
8510                 contentView.setInt(R.id.notification_messaging, "setMaxDisplayedLines", 1);
8511             }
8512             contentView.setIcon(R.id.status_bar_latest_event_content, "setLargeIcon",
8513                     largeIcon);
8514             contentView.setBundle(R.id.status_bar_latest_event_content, "setData",
8515                     mBuilder.mN.extras);
8516             return contentView;
8517         }
8518 
getKey(Person person)8519         private CharSequence getKey(Person person) {
8520             return person == null ? null
8521                     : person.getKey() == null ? person.getName() : person.getKey();
8522         }
8523 
getOtherPersonName()8524         private CharSequence getOtherPersonName() {
8525             CharSequence userKey = getKey(mUser);
8526             for (int i = mMessages.size() - 1; i >= 0; i--) {
8527                 Person sender = mMessages.get(i).getSenderPerson();
8528                 if (sender != null && !TextUtils.equals(userKey, getKey(sender))) {
8529                     return sender.getName();
8530                 }
8531             }
8532             return null;
8533         }
8534 
hasOnlyWhiteSpaceSenders()8535         private boolean hasOnlyWhiteSpaceSenders() {
8536             for (int i = 0; i < mMessages.size(); i++) {
8537                 Message m = mMessages.get(i);
8538                 Person sender = m.getSenderPerson();
8539                 if (sender != null && !isWhiteSpace(sender.getName())) {
8540                     return false;
8541                 }
8542             }
8543             return true;
8544         }
8545 
isWhiteSpace(CharSequence sender)8546         private boolean isWhiteSpace(CharSequence sender) {
8547             if (TextUtils.isEmpty(sender)) {
8548                 return true;
8549             }
8550             if (sender.toString().matches("^\\s*$")) {
8551                 return true;
8552             }
8553             // Let's check if we only have 0 whitespace chars. Some apps did this as a workaround
8554             // For the presentation that we had.
8555             for (int i = 0; i < sender.length(); i++) {
8556                 char c = sender.charAt(i);
8557                 if (c != '\u200B') {
8558                     return false;
8559                 }
8560             }
8561             return true;
8562         }
8563 
8564         /**
8565          * @hide
8566          */
8567         @Override
makeHeadsUpContentView(boolean increasedHeight)8568         public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
8569             return makeMessagingView(StandardTemplateParams.VIEW_TYPE_HEADS_UP);
8570         }
8571 
8572         /**
8573          * @hide
8574          */
8575         @Override
reduceImageSizes(Context context)8576         public void reduceImageSizes(Context context) {
8577             super.reduceImageSizes(context);
8578             Resources resources = context.getResources();
8579             boolean isLowRam = ActivityManager.isLowRamDeviceStatic();
8580             if (mShortcutIcon != null) {
8581                 int maxSize = resources.getDimensionPixelSize(
8582                         isLowRam ? R.dimen.notification_small_icon_size_low_ram
8583                                 : R.dimen.notification_small_icon_size);
8584                 mShortcutIcon.scaleDownIfNecessary(maxSize, maxSize);
8585             }
8586 
8587             int maxAvatarSize = resources.getDimensionPixelSize(
8588                     isLowRam ? R.dimen.notification_person_icon_max_size_low_ram
8589                             : R.dimen.notification_person_icon_max_size);
8590             if (mUser != null && mUser.getIcon() != null) {
8591                 mUser.getIcon().scaleDownIfNecessary(maxAvatarSize, maxAvatarSize);
8592             }
8593 
8594             reduceMessagesIconSizes(mMessages, maxAvatarSize);
8595             reduceMessagesIconSizes(mHistoricMessages, maxAvatarSize);
8596         }
8597 
8598         /**
8599          * @hide
8600          */
reduceMessagesIconSizes(@ullable List<Message> messages, int maxSize)8601         private static void reduceMessagesIconSizes(@Nullable List<Message> messages, int maxSize) {
8602             if (messages == null) {
8603                 return;
8604             }
8605 
8606             for (Message message : messages) {
8607                 Person sender = message.mSender;
8608                 if (sender != null) {
8609                     Icon icon = sender.getIcon();
8610                     if (icon != null) {
8611                         icon.scaleDownIfNecessary(maxSize, maxSize);
8612                     }
8613                 }
8614             }
8615         }
8616 
8617         public static final class Message {
8618             /** @hide */
8619             public static final String KEY_TEXT = "text";
8620             static final String KEY_TIMESTAMP = "time";
8621             static final String KEY_SENDER = "sender";
8622             static final String KEY_SENDER_PERSON = "sender_person";
8623             static final String KEY_DATA_MIME_TYPE = "type";
8624             static final String KEY_DATA_URI= "uri";
8625             static final String KEY_EXTRAS_BUNDLE = "extras";
8626             static final String KEY_REMOTE_INPUT_HISTORY = "remote_input_history";
8627 
8628             private CharSequence mText;
8629             private final long mTimestamp;
8630             @Nullable
8631             private final Person mSender;
8632             /** True if this message was generated from the extra
8633              *  {@link Notification#EXTRA_REMOTE_INPUT_HISTORY_ITEMS}
8634              */
8635             private final boolean mRemoteInputHistory;
8636 
8637             private Bundle mExtras = new Bundle();
8638             private String mDataMimeType;
8639             private Uri mDataUri;
8640 
8641             /**
8642              * Constructor
8643              * @param text A {@link CharSequence} to be displayed as the message content
8644              * @param timestamp Time at which the message arrived
8645              * @param sender A {@link CharSequence} to be used for displaying the name of the
8646              * sender. Should be <code>null</code> for messages by the current user, in which case
8647              * the platform will insert {@link MessagingStyle#getUserDisplayName()}.
8648              * Should be unique amongst all individuals in the conversation, and should be
8649              * consistent during re-posts of the notification.
8650              *
8651              *  @deprecated use {@code Message(CharSequence, long, Person)}
8652              */
Message(CharSequence text, long timestamp, CharSequence sender)8653             public Message(CharSequence text, long timestamp, CharSequence sender){
8654                 this(text, timestamp, sender == null ? null
8655                         : new Person.Builder().setName(sender).build());
8656             }
8657 
8658             /**
8659              * Constructor
8660              * @param text A {@link CharSequence} to be displayed as the message content
8661              * @param timestamp Time at which the message arrived
8662              * @param sender The {@link Person} who sent the message.
8663              * Should be <code>null</code> for messages by the current user, in which case
8664              * the platform will insert the user set in {@code MessagingStyle(Person)}.
8665              * <p>
8666              * The person provided should contain an Icon, set with
8667              * {@link Person.Builder#setIcon(Icon)} and also have a name provided
8668              * with {@link Person.Builder#setName(CharSequence)}. If multiple users have the same
8669              * name, consider providing a key with {@link Person.Builder#setKey(String)} in order
8670              * to differentiate between the different users.
8671              * </p>
8672              */
Message(@onNull CharSequence text, long timestamp, @Nullable Person sender)8673             public Message(@NonNull CharSequence text, long timestamp, @Nullable Person sender) {
8674                 this(text, timestamp, sender, false /* remoteHistory */);
8675             }
8676 
8677             /**
8678              * Constructor
8679              * @param text A {@link CharSequence} to be displayed as the message content
8680              * @param timestamp Time at which the message arrived
8681              * @param sender The {@link Person} who sent the message.
8682              * Should be <code>null</code> for messages by the current user, in which case
8683              * the platform will insert the user set in {@code MessagingStyle(Person)}.
8684              * @param remoteInputHistory True if the messages was generated from the extra
8685              * {@link Notification#EXTRA_REMOTE_INPUT_HISTORY_ITEMS}.
8686              * <p>
8687              * The person provided should contain an Icon, set with
8688              * {@link Person.Builder#setIcon(Icon)} and also have a name provided
8689              * with {@link Person.Builder#setName(CharSequence)}. If multiple users have the same
8690              * name, consider providing a key with {@link Person.Builder#setKey(String)} in order
8691              * to differentiate between the different users.
8692              * </p>
8693              * @hide
8694              */
Message(@onNull CharSequence text, long timestamp, @Nullable Person sender, boolean remoteInputHistory)8695             public Message(@NonNull CharSequence text, long timestamp, @Nullable Person sender,
8696                     boolean remoteInputHistory) {
8697                 mText = safeCharSequence(text);
8698                 mTimestamp = timestamp;
8699                 mSender = sender;
8700                 mRemoteInputHistory = remoteInputHistory;
8701             }
8702 
8703             /**
8704              * Sets a binary blob of data and an associated MIME type for a message. In the case
8705              * where the platform doesn't support the MIME type, the original text provided in the
8706              * constructor will be used.
8707              * @param dataMimeType The MIME type of the content. See
8708              * {@link android.graphics.ImageDecoder#isMimeTypeSupported(String)} for a list of
8709              * supported image MIME types.
8710              * @param dataUri The uri containing the content whose type is given by the MIME type.
8711              * <p class="note">
8712              * Notification Listeners including the System UI need permission to access the
8713              * data the Uri points to. The recommended ways to do this are:
8714              * <ol>
8715              *   <li>Store the data in your own ContentProvider, making sure that other apps have
8716              *       the correct permission to access your provider. The preferred mechanism for
8717              *       providing access is to use per-URI permissions which are temporary and only
8718              *       grant access to the receiving application. An easy way to create a
8719              *       ContentProvider like this is to use the FileProvider helper class.</li>
8720              *   <li>Use the system MediaStore. The MediaStore is primarily aimed at video, audio
8721              *       and image MIME types, however beginning with Android 3.0 (API level 11) it can
8722              *       also store non-media types (see MediaStore.Files for more info). Files can be
8723              *       inserted into the MediaStore using scanFile() after which a content:// style
8724              *       Uri suitable for sharing is passed to the provided onScanCompleted() callback.
8725              *       Note that once added to the system MediaStore the content is accessible to any
8726              *       app on the device.</li>
8727              * </ol>
8728              * @return this object for method chaining
8729              */
setData(String dataMimeType, Uri dataUri)8730             public Message setData(String dataMimeType, Uri dataUri) {
8731                 mDataMimeType = dataMimeType;
8732                 mDataUri = dataUri;
8733                 return this;
8734             }
8735 
8736             /**
8737              * Updates TextAppearance spans in the message text so it has sufficient contrast
8738              * against its background.
8739              * @hide
8740              */
ensureColorContrast(int backgroundColor)8741             public void ensureColorContrast(int backgroundColor) {
8742                 mText = ContrastColorUtil.ensureColorSpanContrast(mText, backgroundColor);
8743             }
8744 
8745             /**
8746              * Get the text to be used for this message, or the fallback text if a type and content
8747              * Uri have been set
8748              */
getText()8749             public CharSequence getText() {
8750                 return mText;
8751             }
8752 
8753             /**
8754              * Get the time at which this message arrived
8755              */
getTimestamp()8756             public long getTimestamp() {
8757                 return mTimestamp;
8758             }
8759 
8760             /**
8761              * Get the extras Bundle for this message.
8762              */
getExtras()8763             public Bundle getExtras() {
8764                 return mExtras;
8765             }
8766 
8767             /**
8768              * Get the text used to display the contact's name in the messaging experience
8769              *
8770              * @deprecated use {@link #getSenderPerson()}
8771              */
getSender()8772             public CharSequence getSender() {
8773                 return mSender == null ? null : mSender.getName();
8774             }
8775 
8776             /**
8777              * Get the sender associated with this message.
8778              */
8779             @Nullable
getSenderPerson()8780             public Person getSenderPerson() {
8781                 return mSender;
8782             }
8783 
8784             /**
8785              * Get the MIME type of the data pointed to by the Uri
8786              */
getDataMimeType()8787             public String getDataMimeType() {
8788                 return mDataMimeType;
8789             }
8790 
8791             /**
8792              * Get the Uri pointing to the content of the message. Can be null, in which case
8793              * {@see #getText()} is used.
8794              */
getDataUri()8795             public Uri getDataUri() {
8796                 return mDataUri;
8797             }
8798 
8799             /**
8800              * @return True if the message was generated from
8801              * {@link Notification#EXTRA_REMOTE_INPUT_HISTORY_ITEMS}.
8802              * @hide
8803              */
isRemoteInputHistory()8804             public boolean isRemoteInputHistory() {
8805                 return mRemoteInputHistory;
8806             }
8807 
8808             /**
8809              * @hide
8810              */
8811             @VisibleForTesting
toBundle()8812             public Bundle toBundle() {
8813                 Bundle bundle = new Bundle();
8814                 if (mText != null) {
8815                     bundle.putCharSequence(KEY_TEXT, mText);
8816                 }
8817                 bundle.putLong(KEY_TIMESTAMP, mTimestamp);
8818                 if (mSender != null) {
8819                     // Legacy listeners need this
8820                     bundle.putCharSequence(KEY_SENDER, safeCharSequence(mSender.getName()));
8821                     bundle.putParcelable(KEY_SENDER_PERSON, mSender);
8822                 }
8823                 if (mDataMimeType != null) {
8824                     bundle.putString(KEY_DATA_MIME_TYPE, mDataMimeType);
8825                 }
8826                 if (mDataUri != null) {
8827                     bundle.putParcelable(KEY_DATA_URI, mDataUri);
8828                 }
8829                 if (mExtras != null) {
8830                     bundle.putBundle(KEY_EXTRAS_BUNDLE, mExtras);
8831                 }
8832                 if (mRemoteInputHistory) {
8833                     bundle.putBoolean(KEY_REMOTE_INPUT_HISTORY, mRemoteInputHistory);
8834                 }
8835                 return bundle;
8836             }
8837 
8838             /**
8839              * See {@link Notification#visitUris(Consumer)}.
8840              *
8841              * @hide
8842              */
visitUris(@onNull Consumer<Uri> visitor)8843             public void visitUris(@NonNull Consumer<Uri> visitor) {
8844                 visitor.accept(getDataUri());
8845                 if (mSender != null) {
8846                     mSender.visitUris(visitor);
8847                 }
8848             }
8849 
8850             /**
8851              * Returns a list of messages read from the given bundle list, e.g.
8852              * {@link #EXTRA_MESSAGES} or {@link #EXTRA_HISTORIC_MESSAGES}.
8853              */
8854             @NonNull
getMessagesFromBundleArray(@ullable Parcelable[] bundles)8855             public static List<Message> getMessagesFromBundleArray(@Nullable Parcelable[] bundles) {
8856                 if (bundles == null) {
8857                     return new ArrayList<>();
8858                 }
8859                 List<Message> messages = new ArrayList<>(bundles.length);
8860                 for (int i = 0; i < bundles.length; i++) {
8861                     if (bundles[i] instanceof Bundle) {
8862                         Message message = getMessageFromBundle((Bundle)bundles[i]);
8863                         if (message != null) {
8864                             messages.add(message);
8865                         }
8866                     }
8867                 }
8868                 return messages;
8869             }
8870 
8871             /**
8872              * Returns the message that is stored in the bundle (e.g. one of the values in the lists
8873              * in {@link #EXTRA_MESSAGES} or {@link #EXTRA_HISTORIC_MESSAGES}) or null if the
8874              * message couldn't be resolved.
8875              * @hide
8876              */
8877             @Nullable
getMessageFromBundle(@onNull Bundle bundle)8878             public static Message getMessageFromBundle(@NonNull Bundle bundle) {
8879                 try {
8880                     if (!bundle.containsKey(KEY_TEXT) || !bundle.containsKey(KEY_TIMESTAMP)) {
8881                         return null;
8882                     } else {
8883 
8884                         Person senderPerson = bundle.getParcelable(KEY_SENDER_PERSON, Person.class);
8885                         if (senderPerson == null) {
8886                             // Legacy apps that use compat don't actually provide the sender objects
8887                             // We need to fix the compat version to provide people / use
8888                             // the native api instead
8889                             CharSequence senderName = bundle.getCharSequence(KEY_SENDER);
8890                             if (senderName != null) {
8891                                 senderPerson = new Person.Builder().setName(senderName).build();
8892                             }
8893                         }
8894                         Message message = new Message(bundle.getCharSequence(KEY_TEXT),
8895                                 bundle.getLong(KEY_TIMESTAMP),
8896                                 senderPerson,
8897                                 bundle.getBoolean(KEY_REMOTE_INPUT_HISTORY, false));
8898                         if (bundle.containsKey(KEY_DATA_MIME_TYPE) &&
8899                                 bundle.containsKey(KEY_DATA_URI)) {
8900                             message.setData(bundle.getString(KEY_DATA_MIME_TYPE),
8901                                     bundle.getParcelable(KEY_DATA_URI, Uri.class));
8902                         }
8903                         if (bundle.containsKey(KEY_EXTRAS_BUNDLE)) {
8904                             message.getExtras().putAll(bundle.getBundle(KEY_EXTRAS_BUNDLE));
8905                         }
8906                         return message;
8907                     }
8908                 } catch (ClassCastException e) {
8909                     return null;
8910                 }
8911             }
8912         }
8913     }
8914 
8915     /**
8916      * Helper class for generating large-format notifications that include a list of (up to 5) strings.
8917      *
8918      * Here's how you'd set the <code>InboxStyle</code> on a notification:
8919      * <pre class="prettyprint">
8920      * Notification notif = new Notification.Builder(mContext)
8921      *     .setContentTitle(&quot;5 New mails from &quot; + sender.toString())
8922      *     .setContentText(subject)
8923      *     .setSmallIcon(R.drawable.new_mail)
8924      *     .setLargeIcon(aBitmap)
8925      *     .setStyle(new Notification.InboxStyle()
8926      *         .addLine(str1)
8927      *         .addLine(str2)
8928      *         .setContentTitle(&quot;&quot;)
8929      *         .setSummaryText(&quot;+3 more&quot;))
8930      *     .build();
8931      * </pre>
8932      *
8933      * @see Notification#bigContentView
8934      */
8935     public static class InboxStyle extends Style {
8936 
8937         /**
8938          * The number of lines of remote input history allowed until we start reducing lines.
8939          */
8940         private static final int NUMBER_OF_HISTORY_ALLOWED_UNTIL_REDUCTION = 1;
8941         private ArrayList<CharSequence> mTexts = new ArrayList<CharSequence>(5);
8942 
InboxStyle()8943         public InboxStyle() {
8944         }
8945 
8946         /**
8947          * @deprecated use {@code InboxStyle()}.
8948          */
8949         @Deprecated
InboxStyle(Builder builder)8950         public InboxStyle(Builder builder) {
8951             setBuilder(builder);
8952         }
8953 
8954         /**
8955          * Overrides ContentTitle in the big form of the template.
8956          * This defaults to the value passed to setContentTitle().
8957          */
setBigContentTitle(CharSequence title)8958         public InboxStyle setBigContentTitle(CharSequence title) {
8959             internalSetBigContentTitle(safeCharSequence(title));
8960             return this;
8961         }
8962 
8963         /**
8964          * Set the first line of text after the detail section in the big form of the template.
8965          */
setSummaryText(CharSequence cs)8966         public InboxStyle setSummaryText(CharSequence cs) {
8967             internalSetSummaryText(safeCharSequence(cs));
8968             return this;
8969         }
8970 
8971         /**
8972          * Append a line to the digest section of the Inbox notification.
8973          */
addLine(CharSequence cs)8974         public InboxStyle addLine(CharSequence cs) {
8975             mTexts.add(safeCharSequence(cs));
8976             return this;
8977         }
8978 
8979         /**
8980          * @hide
8981          */
getLines()8982         public ArrayList<CharSequence> getLines() {
8983             return mTexts;
8984         }
8985 
8986         /**
8987          * @hide
8988          */
addExtras(Bundle extras)8989         public void addExtras(Bundle extras) {
8990             super.addExtras(extras);
8991 
8992             CharSequence[] a = new CharSequence[mTexts.size()];
8993             extras.putCharSequenceArray(EXTRA_TEXT_LINES, mTexts.toArray(a));
8994         }
8995 
8996         /**
8997          * @hide
8998          */
8999         @Override
restoreFromExtras(Bundle extras)9000         protected void restoreFromExtras(Bundle extras) {
9001             super.restoreFromExtras(extras);
9002 
9003             mTexts.clear();
9004             if (extras.containsKey(EXTRA_TEXT_LINES)) {
9005                 Collections.addAll(mTexts, extras.getCharSequenceArray(EXTRA_TEXT_LINES));
9006             }
9007         }
9008 
9009         /**
9010          * @hide
9011          */
makeBigContentView()9012         public RemoteViews makeBigContentView() {
9013             StandardTemplateParams p = mBuilder.mParams.reset()
9014                     .viewType(StandardTemplateParams.VIEW_TYPE_BIG)
9015                     .fillTextsFrom(mBuilder).text(null);
9016             TemplateBindResult result = new TemplateBindResult();
9017             RemoteViews contentView = getStandardView(mBuilder.getInboxLayoutResource(), p, result);
9018 
9019             int[] rowIds = {R.id.inbox_text0, R.id.inbox_text1, R.id.inbox_text2, R.id.inbox_text3,
9020                     R.id.inbox_text4, R.id.inbox_text5, R.id.inbox_text6};
9021 
9022             // Make sure all rows are gone in case we reuse a view.
9023             for (int rowId : rowIds) {
9024                 contentView.setViewVisibility(rowId, View.GONE);
9025             }
9026 
9027             int i=0;
9028             int topPadding = mBuilder.mContext.getResources().getDimensionPixelSize(
9029                     R.dimen.notification_inbox_item_top_padding);
9030             boolean first = true;
9031             int onlyViewId = 0;
9032             int maxRows = rowIds.length;
9033             if (mBuilder.mActions.size() > 0) {
9034                 maxRows--;
9035             }
9036             RemoteInputHistoryItem[] remoteInputHistory = getParcelableArrayFromBundle(
9037                     mBuilder.mN.extras, EXTRA_REMOTE_INPUT_HISTORY_ITEMS,
9038                     RemoteInputHistoryItem.class);
9039             if (remoteInputHistory != null
9040                     && remoteInputHistory.length > NUMBER_OF_HISTORY_ALLOWED_UNTIL_REDUCTION) {
9041                 // Let's remove some messages to make room for the remote input history.
9042                 // 1 is always able to fit, but let's remove them if they are 2 or 3
9043                 int numRemoteInputs = Math.min(remoteInputHistory.length,
9044                         MAX_REMOTE_INPUT_HISTORY_LINES);
9045                 int totalNumRows = mTexts.size() + numRemoteInputs
9046                         - NUMBER_OF_HISTORY_ALLOWED_UNTIL_REDUCTION;
9047                 if (totalNumRows > maxRows) {
9048                     int overflow = totalNumRows - maxRows;
9049                     if (mTexts.size() > maxRows) {
9050                         // Heuristic: if the Texts don't fit anyway, we'll rather drop the last
9051                         // few messages, even with the remote input
9052                         maxRows -= overflow;
9053                     } else  {
9054                         // otherwise we drop the first messages
9055                         i = overflow;
9056                     }
9057                 }
9058             }
9059             while (i < mTexts.size() && i < maxRows) {
9060                 CharSequence str = mTexts.get(i);
9061                 if (!TextUtils.isEmpty(str)) {
9062                     contentView.setViewVisibility(rowIds[i], View.VISIBLE);
9063                     contentView.setTextViewText(rowIds[i],
9064                             mBuilder.ensureColorSpanContrast(mBuilder.processLegacyText(str), p));
9065                     mBuilder.setTextViewColorSecondary(contentView, rowIds[i], p);
9066                     contentView.setViewPadding(rowIds[i], 0, topPadding, 0, 0);
9067                     if (first) {
9068                         onlyViewId = rowIds[i];
9069                     } else {
9070                         onlyViewId = 0;
9071                     }
9072                     first = false;
9073                 }
9074                 i++;
9075             }
9076             if (onlyViewId != 0) {
9077                 // We only have 1 entry, lets make it look like the normal Text of a Bigtext
9078                 topPadding = mBuilder.mContext.getResources().getDimensionPixelSize(
9079                         R.dimen.notification_text_margin_top);
9080                 contentView.setViewPadding(onlyViewId, 0, topPadding, 0, 0);
9081             }
9082 
9083             return contentView;
9084         }
9085 
9086         /**
9087          * @hide
9088          */
9089         @Override
areNotificationsVisiblyDifferent(Style other)9090         public boolean areNotificationsVisiblyDifferent(Style other) {
9091             if (other == null || getClass() != other.getClass()) {
9092                 return true;
9093             }
9094             InboxStyle newS = (InboxStyle) other;
9095 
9096             final ArrayList<CharSequence> myLines = getLines();
9097             final ArrayList<CharSequence> newLines = newS.getLines();
9098             final int n = myLines.size();
9099             if (n != newLines.size()) {
9100                 return true;
9101             }
9102 
9103             for (int i = 0; i < n; i++) {
9104                 if (!Objects.equals(
9105                         String.valueOf(myLines.get(i)),
9106                         String.valueOf(newLines.get(i)))) {
9107                     return true;
9108                 }
9109             }
9110             return false;
9111         }
9112     }
9113 
9114     /**
9115      * Notification style for media playback notifications.
9116      *
9117      * In the expanded form, {@link Notification#bigContentView}, up to 5
9118      * {@link Notification.Action}s specified with
9119      * {@link Notification.Builder#addAction(Action) addAction} will be
9120      * shown as icon-only pushbuttons, suitable for transport controls. The Bitmap given to
9121      * {@link Notification.Builder#setLargeIcon(android.graphics.Bitmap) setLargeIcon()} will be
9122      * treated as album artwork.
9123      * <p>
9124      * Unlike the other styles provided here, MediaStyle can also modify the standard-size
9125      * {@link Notification#contentView}; by providing action indices to
9126      * {@link #setShowActionsInCompactView(int...)} you can promote up to 3 actions to be displayed
9127      * in the standard view alongside the usual content.
9128      * <p>
9129      * Notifications created with MediaStyle will have their category set to
9130      * {@link Notification#CATEGORY_TRANSPORT CATEGORY_TRANSPORT} unless you set a different
9131      * category using {@link Notification.Builder#setCategory(String) setCategory()}.
9132      * <p>
9133      * Finally, if you attach a {@link android.media.session.MediaSession.Token} using
9134      * {@link android.app.Notification.MediaStyle#setMediaSession(MediaSession.Token)},
9135      * the System UI can identify this as a notification representing an active media session
9136      * and respond accordingly (by showing album artwork in the lockscreen, for example).
9137      *
9138      * <p>
9139      * Starting at {@link android.os.Build.VERSION_CODES#O Android O} any notification that has a
9140      * media session attached with {@link #setMediaSession(MediaSession.Token)} will be colorized.
9141      * You can opt-out of this behavior by using {@link Notification.Builder#setColorized(boolean)}.
9142      * <p>
9143      *
9144      * To use this style with your Notification, feed it to
9145      * {@link Notification.Builder#setStyle(android.app.Notification.Style)} like so:
9146      * <pre class="prettyprint">
9147      * Notification noti = new Notification.Builder()
9148      *     .setSmallIcon(R.drawable.ic_stat_player)
9149      *     .setContentTitle(&quot;Track title&quot;)
9150      *     .setContentText(&quot;Artist - Album&quot;)
9151      *     .setLargeIcon(albumArtBitmap))
9152      *     .setStyle(<b>new Notification.MediaStyle()</b>
9153      *         .setMediaSession(mySession))
9154      *     .build();
9155      * </pre>
9156      *
9157      * @see Notification#bigContentView
9158      * @see Notification.Builder#setColorized(boolean)
9159      */
9160     public static class MediaStyle extends Style {
9161         // Changing max media buttons requires also changing templates
9162         // (notification_template_material_media and notification_template_material_big_media).
9163         static final int MAX_MEDIA_BUTTONS_IN_COMPACT = 3;
9164         static final int MAX_MEDIA_BUTTONS = 5;
9165         @IdRes private static final int[] MEDIA_BUTTON_IDS = {
9166                 R.id.action0,
9167                 R.id.action1,
9168                 R.id.action2,
9169                 R.id.action3,
9170                 R.id.action4,
9171         };
9172 
9173         private int[] mActionsToShowInCompact = null;
9174         private MediaSession.Token mToken;
9175         private CharSequence mDeviceName;
9176         private int mDeviceIcon;
9177         private PendingIntent mDeviceIntent;
9178 
MediaStyle()9179         public MediaStyle() {
9180         }
9181 
9182         /**
9183          * @deprecated use {@code MediaStyle()}.
9184          */
9185         @Deprecated
MediaStyle(Builder builder)9186         public MediaStyle(Builder builder) {
9187             setBuilder(builder);
9188         }
9189 
9190         /**
9191          * Request up to 3 actions (by index in the order of addition) to be shown in the compact
9192          * notification view.
9193          *
9194          * @param actions the indices of the actions to show in the compact notification view
9195          */
setShowActionsInCompactView(int...actions)9196         public MediaStyle setShowActionsInCompactView(int...actions) {
9197             mActionsToShowInCompact = actions;
9198             return this;
9199         }
9200 
9201         /**
9202          * Attach a {@link android.media.session.MediaSession.Token} to this Notification
9203          * to provide additional playback information and control to the SystemUI.
9204          */
setMediaSession(MediaSession.Token token)9205         public MediaStyle setMediaSession(MediaSession.Token token) {
9206             mToken = token;
9207             return this;
9208         }
9209 
9210         /**
9211          * For media notifications associated with playback on a remote device, provide device
9212          * information that will replace the default values for the output switcher chip on the
9213          * media control, as well as an intent to use when the output switcher chip is tapped,
9214          * on devices where this is supported.
9215          * <p>
9216          * This method is intended for system applications to provide information and/or
9217          * functionality that would otherwise be unavailable to the default output switcher because
9218          * the media originated on a remote device.
9219          *
9220          * @param deviceName The name of the remote device to display
9221          * @param iconResource Icon resource representing the device
9222          * @param chipIntent PendingIntent to send when the output switcher is tapped. May be
9223          *                   {@code null}, in which case the output switcher will be disabled.
9224          *                   This intent should open an Activity or it will be ignored.
9225          * @return MediaStyle
9226          */
9227         @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL)
9228         @NonNull
setRemotePlaybackInfo(@onNull CharSequence deviceName, @DrawableRes int iconResource, @Nullable PendingIntent chipIntent)9229         public MediaStyle setRemotePlaybackInfo(@NonNull CharSequence deviceName,
9230                 @DrawableRes int iconResource, @Nullable PendingIntent chipIntent) {
9231             mDeviceName = deviceName;
9232             mDeviceIcon = iconResource;
9233             mDeviceIntent = chipIntent;
9234             return this;
9235         }
9236 
9237         /**
9238          * @hide
9239          */
9240         @Override
9241         @UnsupportedAppUsage
buildStyled(Notification wip)9242         public Notification buildStyled(Notification wip) {
9243             super.buildStyled(wip);
9244             if (wip.category == null) {
9245                 wip.category = Notification.CATEGORY_TRANSPORT;
9246             }
9247             return wip;
9248         }
9249 
9250         /**
9251          * @hide
9252          */
9253         @Override
makeContentView(boolean increasedHeight)9254         public RemoteViews makeContentView(boolean increasedHeight) {
9255             return makeMediaContentView(null /* customContent */);
9256         }
9257 
9258         /**
9259          * @hide
9260          */
9261         @Override
makeBigContentView()9262         public RemoteViews makeBigContentView() {
9263             return makeMediaBigContentView(null /* customContent */);
9264         }
9265 
9266         /**
9267          * @hide
9268          */
9269         @Override
makeHeadsUpContentView(boolean increasedHeight)9270         public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
9271             return makeMediaContentView(null /* customContent */);
9272         }
9273 
9274         /** @hide */
9275         @Override
addExtras(Bundle extras)9276         public void addExtras(Bundle extras) {
9277             super.addExtras(extras);
9278 
9279             if (mToken != null) {
9280                 extras.putParcelable(EXTRA_MEDIA_SESSION, mToken);
9281             }
9282             if (mActionsToShowInCompact != null) {
9283                 extras.putIntArray(EXTRA_COMPACT_ACTIONS, mActionsToShowInCompact);
9284             }
9285             if (mDeviceName != null) {
9286                 extras.putCharSequence(EXTRA_MEDIA_REMOTE_DEVICE, mDeviceName);
9287             }
9288             if (mDeviceIcon > 0) {
9289                 extras.putInt(EXTRA_MEDIA_REMOTE_ICON, mDeviceIcon);
9290             }
9291             if (mDeviceIntent != null) {
9292                 extras.putParcelable(EXTRA_MEDIA_REMOTE_INTENT, mDeviceIntent);
9293             }
9294         }
9295 
9296         /**
9297          * @hide
9298          */
9299         @Override
restoreFromExtras(Bundle extras)9300         protected void restoreFromExtras(Bundle extras) {
9301             super.restoreFromExtras(extras);
9302 
9303             if (extras.containsKey(EXTRA_MEDIA_SESSION)) {
9304                 mToken = extras.getParcelable(EXTRA_MEDIA_SESSION, MediaSession.Token.class);
9305             }
9306             if (extras.containsKey(EXTRA_COMPACT_ACTIONS)) {
9307                 mActionsToShowInCompact = extras.getIntArray(EXTRA_COMPACT_ACTIONS);
9308             }
9309             if (extras.containsKey(EXTRA_MEDIA_REMOTE_DEVICE)) {
9310                 mDeviceName = extras.getCharSequence(EXTRA_MEDIA_REMOTE_DEVICE);
9311             }
9312             if (extras.containsKey(EXTRA_MEDIA_REMOTE_ICON)) {
9313                 mDeviceIcon = extras.getInt(EXTRA_MEDIA_REMOTE_ICON);
9314             }
9315             if (extras.containsKey(EXTRA_MEDIA_REMOTE_INTENT)) {
9316                 mDeviceIntent = extras.getParcelable(
9317                         EXTRA_MEDIA_REMOTE_INTENT, PendingIntent.class);
9318             }
9319         }
9320 
9321         /**
9322          * @hide
9323          */
9324         @Override
areNotificationsVisiblyDifferent(Style other)9325         public boolean areNotificationsVisiblyDifferent(Style other) {
9326             if (other == null || getClass() != other.getClass()) {
9327                 return true;
9328             }
9329             // All fields to compare are on the Notification object
9330             return false;
9331         }
9332 
bindMediaActionButton(RemoteViews container, @IdRes int buttonId, Action action, StandardTemplateParams p)9333         private void bindMediaActionButton(RemoteViews container, @IdRes int buttonId,
9334                 Action action, StandardTemplateParams p) {
9335             final boolean tombstone = (action.actionIntent == null);
9336             container.setViewVisibility(buttonId, View.VISIBLE);
9337             container.setImageViewIcon(buttonId, action.getIcon());
9338 
9339             // If the action buttons should not be tinted, then just use the default
9340             // notification color. Otherwise, just use the passed-in color.
9341             int tintColor = mBuilder.getStandardActionColor(p);
9342 
9343             container.setDrawableTint(buttonId, false, tintColor,
9344                     PorterDuff.Mode.SRC_ATOP);
9345 
9346             int rippleAlpha = mBuilder.getColors(p).getRippleAlpha();
9347             int rippleColor = Color.argb(rippleAlpha, Color.red(tintColor), Color.green(tintColor),
9348                     Color.blue(tintColor));
9349             container.setRippleDrawableColor(buttonId, ColorStateList.valueOf(rippleColor));
9350 
9351             if (!tombstone) {
9352                 container.setOnClickPendingIntent(buttonId, action.actionIntent);
9353             }
9354             container.setContentDescription(buttonId, action.title);
9355         }
9356 
9357         /** @hide */
makeMediaContentView(@ullable RemoteViews customContent)9358         protected RemoteViews makeMediaContentView(@Nullable RemoteViews customContent) {
9359             final int numActions = mBuilder.mActions.size();
9360             final int numActionsToShow = Math.min(mActionsToShowInCompact == null
9361                     ? 0 : mActionsToShowInCompact.length, MAX_MEDIA_BUTTONS_IN_COMPACT);
9362             if (numActionsToShow > numActions) {
9363                 throw new IllegalArgumentException(String.format(
9364                         "setShowActionsInCompactView: action %d out of bounds (max %d)",
9365                         numActions, numActions - 1));
9366             }
9367 
9368             StandardTemplateParams p = mBuilder.mParams.reset()
9369                     .viewType(StandardTemplateParams.VIEW_TYPE_NORMAL)
9370                     .hideTime(numActionsToShow > 1)       // hide if actions wider than a right icon
9371                     .hideSubText(numActionsToShow > 1)    // hide if actions wider than a right icon
9372                     .hideLeftIcon(false)                  // allow large icon on left when grouped
9373                     .hideRightIcon(numActionsToShow > 0)  // right icon or actions; not both
9374                     .hideProgress(true)
9375                     .fillTextsFrom(mBuilder);
9376             TemplateBindResult result = new TemplateBindResult();
9377             RemoteViews template = mBuilder.applyStandardTemplate(
9378                     R.layout.notification_template_material_media, p,
9379                     null /* result */);
9380 
9381             for (int i = 0; i < MAX_MEDIA_BUTTONS_IN_COMPACT; i++) {
9382                 if (i < numActionsToShow) {
9383                     final Action action = mBuilder.mActions.get(mActionsToShowInCompact[i]);
9384                     bindMediaActionButton(template, MEDIA_BUTTON_IDS[i], action, p);
9385                 } else {
9386                     template.setViewVisibility(MEDIA_BUTTON_IDS[i], View.GONE);
9387                 }
9388             }
9389             // Prevent a swooping expand animation when there are no actions
9390             boolean hasActions = numActionsToShow != 0;
9391             template.setViewVisibility(R.id.media_actions, hasActions ? View.VISIBLE : View.GONE);
9392 
9393             // Add custom view if provided by subclass.
9394             buildCustomContentIntoTemplate(mBuilder.mContext, template, customContent, p, result);
9395             return template;
9396         }
9397 
9398         /** @hide */
makeMediaBigContentView(@ullable RemoteViews customContent)9399         protected RemoteViews makeMediaBigContentView(@Nullable RemoteViews customContent) {
9400             final int actionCount = Math.min(mBuilder.mActions.size(), MAX_MEDIA_BUTTONS);
9401             StandardTemplateParams p = mBuilder.mParams.reset()
9402                     .viewType(StandardTemplateParams.VIEW_TYPE_BIG)
9403                     .hideProgress(true)
9404                     .fillTextsFrom(mBuilder);
9405             TemplateBindResult result = new TemplateBindResult();
9406             RemoteViews template = mBuilder.applyStandardTemplate(
9407                     R.layout.notification_template_material_big_media, p , result);
9408 
9409             for (int i = 0; i < MAX_MEDIA_BUTTONS; i++) {
9410                 if (i < actionCount) {
9411                     bindMediaActionButton(template,
9412                             MEDIA_BUTTON_IDS[i], mBuilder.mActions.get(i), p);
9413                 } else {
9414                     template.setViewVisibility(MEDIA_BUTTON_IDS[i], View.GONE);
9415                 }
9416             }
9417             buildCustomContentIntoTemplate(mBuilder.mContext, template, customContent, p, result);
9418             return template;
9419         }
9420     }
9421 
9422     /**
9423      * Helper class for generating large-format notifications that include a large image attachment.
9424      *
9425      * Here's how you'd set the <code>CallStyle</code> on a notification:
9426      * <pre class="prettyprint">
9427      * Notification notif = new Notification.Builder(mContext)
9428      *     .setSmallIcon(R.drawable.new_post)
9429      *     .setStyle(Notification.CallStyle.forIncomingCall(caller, declineIntent, answerIntent))
9430      *     .build();
9431      * </pre>
9432      */
9433     public static class CallStyle extends Style {
9434 
9435         /**
9436          * @hide
9437          */
9438         @Retention(RetentionPolicy.SOURCE)
9439         @IntDef({
9440                 CALL_TYPE_UNKNOWN,
9441                 CALL_TYPE_INCOMING,
9442                 CALL_TYPE_ONGOING,
9443                 CALL_TYPE_SCREENING
9444         })
9445         public @interface CallType {};
9446 
9447         /**
9448          * Unknown call type.
9449          *
9450          * See {@link #EXTRA_CALL_TYPE}.
9451          */
9452         public static final int CALL_TYPE_UNKNOWN = 0;
9453 
9454         /**
9455          *  Call type for incoming calls.
9456          *
9457          *  See {@link #EXTRA_CALL_TYPE}.
9458          */
9459         public static final int CALL_TYPE_INCOMING = 1;
9460         /**
9461          * Call type for ongoing calls.
9462          *
9463          * See {@link #EXTRA_CALL_TYPE}.
9464          */
9465         public static final int CALL_TYPE_ONGOING = 2;
9466         /**
9467          * Call type for calls that are being screened.
9468          *
9469          * See {@link #EXTRA_CALL_TYPE}.
9470          */
9471         public static final int CALL_TYPE_SCREENING = 3;
9472 
9473         /**
9474          * This is a key used privately on the action.extras to give spacing priority
9475          * to the required call actions
9476          */
9477         private static final String KEY_ACTION_PRIORITY = "key_action_priority";
9478 
9479         private int mCallType;
9480         private Person mPerson;
9481         private PendingIntent mAnswerIntent;
9482         private PendingIntent mDeclineIntent;
9483         private PendingIntent mHangUpIntent;
9484         private boolean mIsVideo;
9485         private Integer mAnswerButtonColor;
9486         private Integer mDeclineButtonColor;
9487         private Icon mVerificationIcon;
9488         private CharSequence mVerificationText;
9489 
CallStyle()9490         CallStyle() {
9491         }
9492 
9493         /**
9494          * Create a CallStyle for an incoming call.
9495          * This notification will have a decline and an answer action, will allow a single
9496          * custom {@link Builder#addAction(Action) action}, and will have a default
9497          * {@link Builder#setContentText(CharSequence) content text} for an incoming call.
9498          *
9499          * @param person        The person displayed as the caller.
9500          *                      The person also needs to have a non-empty name associated with it.
9501          * @param declineIntent The intent to be sent when the user taps the decline action
9502          * @param answerIntent  The intent to be sent when the user taps the answer action
9503          */
9504         @NonNull
forIncomingCall(@onNull Person person, @NonNull PendingIntent declineIntent, @NonNull PendingIntent answerIntent)9505         public static CallStyle forIncomingCall(@NonNull Person person,
9506                 @NonNull PendingIntent declineIntent, @NonNull PendingIntent answerIntent) {
9507             return new CallStyle(CALL_TYPE_INCOMING, person,
9508                     null /* hangUpIntent */,
9509                     requireNonNull(declineIntent, "declineIntent is required"),
9510                     requireNonNull(answerIntent, "answerIntent is required")
9511             );
9512         }
9513 
9514         /**
9515          * Create a CallStyle for an ongoing call.
9516          * This notification will have a hang up action, will allow up to two
9517          * custom {@link Builder#addAction(Action) actions}, and will have a default
9518          * {@link Builder#setContentText(CharSequence) content text} for an ongoing call.
9519          *
9520          * @param person       The person displayed as being on the other end of the call.
9521          *                     The person also needs to have a non-empty name associated with it.
9522          * @param hangUpIntent The intent to be sent when the user taps the hang up action
9523          */
9524         @NonNull
forOngoingCall(@onNull Person person, @NonNull PendingIntent hangUpIntent)9525         public static CallStyle forOngoingCall(@NonNull Person person,
9526                 @NonNull PendingIntent hangUpIntent) {
9527             return new CallStyle(CALL_TYPE_ONGOING, person,
9528                     requireNonNull(hangUpIntent, "hangUpIntent is required"),
9529                     null /* declineIntent */,
9530                     null /* answerIntent */
9531             );
9532         }
9533 
9534         /**
9535          * Create a CallStyle for a call that is being screened.
9536          * This notification will have a hang up and an answer action, will allow a single
9537          * custom {@link Builder#addAction(Action) action}, and will have a default
9538          * {@link Builder#setContentText(CharSequence) content text} for a call that is being
9539          * screened.
9540          *
9541          * @param person       The person displayed as the caller.
9542          *                     The person also needs to have a non-empty name associated with it.
9543          * @param hangUpIntent The intent to be sent when the user taps the hang up action
9544          * @param answerIntent The intent to be sent when the user taps the answer action
9545          */
9546         @NonNull
forScreeningCall(@onNull Person person, @NonNull PendingIntent hangUpIntent, @NonNull PendingIntent answerIntent)9547         public static CallStyle forScreeningCall(@NonNull Person person,
9548                 @NonNull PendingIntent hangUpIntent, @NonNull PendingIntent answerIntent) {
9549             return new CallStyle(CALL_TYPE_SCREENING, person,
9550                     requireNonNull(hangUpIntent, "hangUpIntent is required"),
9551                     null /* declineIntent */,
9552                     requireNonNull(answerIntent, "answerIntent is required")
9553             );
9554         }
9555 
9556         /**
9557          * @param callType The type of the call
9558          * @param person The person displayed for the incoming call.
9559          *             The user also needs to have a non-empty name associated with it.
9560          * @param hangUpIntent The intent to be sent when the user taps the hang up action
9561          * @param declineIntent The intent to be sent when the user taps the decline action
9562          * @param answerIntent The intent to be sent when the user taps the answer action
9563          */
CallStyle(@allType int callType, @NonNull Person person, @Nullable PendingIntent hangUpIntent, @Nullable PendingIntent declineIntent, @Nullable PendingIntent answerIntent)9564         private CallStyle(@CallType int callType, @NonNull Person person,
9565                 @Nullable PendingIntent hangUpIntent, @Nullable PendingIntent declineIntent,
9566                 @Nullable PendingIntent answerIntent) {
9567             if (person == null || TextUtils.isEmpty(person.getName())) {
9568                 throw new IllegalArgumentException("person must have a non-empty a name");
9569             }
9570             mCallType = callType;
9571             mPerson = person;
9572             mAnswerIntent = answerIntent;
9573             mDeclineIntent = declineIntent;
9574             mHangUpIntent = hangUpIntent;
9575         }
9576 
9577         /**
9578          * Sets whether the call is a video call, which may affect the icons or text used on the
9579          * required action buttons.
9580          */
9581         @NonNull
setIsVideo(boolean isVideo)9582         public CallStyle setIsVideo(boolean isVideo) {
9583             mIsVideo = isVideo;
9584             return this;
9585         }
9586 
9587         /**
9588          * Optional icon to be displayed with {@link #setVerificationText(CharSequence) text}
9589          * as a verification status of the caller.
9590          */
9591         @NonNull
setVerificationIcon(@ullable Icon verificationIcon)9592         public CallStyle setVerificationIcon(@Nullable Icon verificationIcon) {
9593             mVerificationIcon = verificationIcon;
9594             return this;
9595         }
9596 
9597         /**
9598          * Optional text to be displayed with an {@link #setVerificationIcon(Icon) icon}
9599          * as a verification status of the caller.
9600          */
9601         @NonNull
setVerificationText(@ullable CharSequence verificationText)9602         public CallStyle setVerificationText(@Nullable CharSequence verificationText) {
9603             mVerificationText = safeCharSequence(verificationText);
9604             return this;
9605         }
9606 
9607         /**
9608          * Optional color to be used as a hint for the Answer action button's color.
9609          * The system may change this color to ensure sufficient contrast with the background.
9610          * The system may choose to disregard this hint if the notification is not colorized.
9611          */
9612         @NonNull
setAnswerButtonColorHint(@olorInt int color)9613         public CallStyle setAnswerButtonColorHint(@ColorInt int color) {
9614             mAnswerButtonColor = color;
9615             return this;
9616         }
9617 
9618         /**
9619          * Optional color to be used as a hint for the Decline or Hang Up action button's color.
9620          * The system may change this color to ensure sufficient contrast with the background.
9621          * The system may choose to disregard this hint if the notification is not colorized.
9622          */
9623         @NonNull
setDeclineButtonColorHint(@olorInt int color)9624         public CallStyle setDeclineButtonColorHint(@ColorInt int color) {
9625             mDeclineButtonColor = color;
9626             return this;
9627         }
9628 
9629         /** @hide */
9630         @Override
buildStyled(Notification wip)9631         public Notification buildStyled(Notification wip) {
9632             wip = super.buildStyled(wip);
9633             // ensure that the actions in the builder and notification are corrected.
9634             mBuilder.mActions = getActionsListWithSystemActions();
9635             wip.actions = new Action[mBuilder.mActions.size()];
9636             mBuilder.mActions.toArray(wip.actions);
9637             return wip;
9638         }
9639 
9640         /**
9641          * @hide
9642          */
displayCustomViewInline()9643         public boolean displayCustomViewInline() {
9644             // This is a lie; True is returned to make sure that the custom view is not used
9645             // instead of the template, but it will not actually be included.
9646             return true;
9647         }
9648 
9649         /**
9650          * @hide
9651          */
9652         @Override
purgeResources()9653         public void purgeResources() {
9654             super.purgeResources();
9655             if (mVerificationIcon != null) {
9656                 mVerificationIcon.convertToAshmem();
9657             }
9658         }
9659 
9660         /**
9661          * @hide
9662          */
9663         @Override
reduceImageSizes(Context context)9664         public void reduceImageSizes(Context context) {
9665             super.reduceImageSizes(context);
9666             if (mVerificationIcon != null) {
9667                 int rightIconSize = context.getResources().getDimensionPixelSize(
9668                         ActivityManager.isLowRamDeviceStatic()
9669                                 ? R.dimen.notification_right_icon_size_low_ram
9670                                 : R.dimen.notification_right_icon_size);
9671                 mVerificationIcon.scaleDownIfNecessary(rightIconSize, rightIconSize);
9672             }
9673         }
9674 
9675         /**
9676          * @hide
9677          */
9678         @Override
makeContentView(boolean increasedHeight)9679         public RemoteViews makeContentView(boolean increasedHeight) {
9680             return makeCallLayout(StandardTemplateParams.VIEW_TYPE_NORMAL);
9681         }
9682 
9683         /**
9684          * @hide
9685          */
9686         @Override
makeHeadsUpContentView(boolean increasedHeight)9687         public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
9688             return makeCallLayout(StandardTemplateParams.VIEW_TYPE_HEADS_UP);
9689         }
9690 
9691         /**
9692          * @hide
9693          */
makeBigContentView()9694         public RemoteViews makeBigContentView() {
9695             return makeCallLayout(StandardTemplateParams.VIEW_TYPE_BIG);
9696         }
9697 
9698         @NonNull
makeNegativeAction()9699         private Action makeNegativeAction() {
9700             if (mDeclineIntent == null) {
9701                 return makeAction(R.drawable.ic_call_decline,
9702                         R.string.call_notification_hang_up_action,
9703                         mDeclineButtonColor, R.color.call_notification_decline_color,
9704                         mHangUpIntent);
9705             } else {
9706                 return makeAction(R.drawable.ic_call_decline,
9707                         R.string.call_notification_decline_action,
9708                         mDeclineButtonColor, R.color.call_notification_decline_color,
9709                         mDeclineIntent);
9710             }
9711         }
9712 
9713         @Nullable
makeAnswerAction()9714         private Action makeAnswerAction() {
9715             return mAnswerIntent == null ? null : makeAction(
9716                     mIsVideo ? R.drawable.ic_call_answer_video : R.drawable.ic_call_answer,
9717                     mIsVideo ? R.string.call_notification_answer_video_action
9718                             : R.string.call_notification_answer_action,
9719                     mAnswerButtonColor, R.color.call_notification_answer_color,
9720                     mAnswerIntent);
9721         }
9722 
9723         @NonNull
makeAction(@rawableRes int icon, @StringRes int title, @ColorInt Integer colorInt, @ColorRes int defaultColorRes, PendingIntent intent)9724         private Action makeAction(@DrawableRes int icon, @StringRes int title,
9725                 @ColorInt Integer colorInt, @ColorRes int defaultColorRes, PendingIntent intent) {
9726             if (colorInt == null || !mBuilder.isCallActionColorCustomizable()) {
9727                 colorInt = mBuilder.mContext.getColor(defaultColorRes);
9728             }
9729             Action action = new Action.Builder(Icon.createWithResource("", icon),
9730                     new SpannableStringBuilder().append(mBuilder.mContext.getString(title),
9731                             new ForegroundColorSpan(colorInt),
9732                             SpannableStringBuilder.SPAN_INCLUSIVE_INCLUSIVE),
9733                     intent).build();
9734             action.getExtras().putBoolean(KEY_ACTION_PRIORITY, true);
9735             return action;
9736         }
9737 
isActionAddedByCallStyle(Action action)9738         private boolean isActionAddedByCallStyle(Action action) {
9739             // This is an internal extra added by the style to these actions. If an app were to add
9740             // this extra to the action themselves, the action would be dropped.  :shrug:
9741             return action != null && action.getExtras().getBoolean(KEY_ACTION_PRIORITY);
9742         }
9743 
9744         /**
9745          * Gets the actions list for the call with the answer/decline/hangUp actions inserted in
9746          * the correct place.  This returns the correct result even if the system actions have
9747          * already been added, and even if more actions were added since then.
9748          * @hide
9749          */
9750         @NonNull
getActionsListWithSystemActions()9751         public ArrayList<Action> getActionsListWithSystemActions() {
9752             // Define the system actions we expect to see
9753             final Action firstAction = makeNegativeAction();
9754             final Action lastAction = makeAnswerAction();
9755 
9756             // Start creating the result list.
9757             int nonContextualActionSlotsRemaining = MAX_ACTION_BUTTONS;
9758             ArrayList<Action> resultActions = new ArrayList<>(MAX_ACTION_BUTTONS);
9759 
9760             // Always have a first action.
9761             resultActions.add(firstAction);
9762             --nonContextualActionSlotsRemaining;
9763 
9764             // Copy actions into the new list, correcting system actions.
9765             if (mBuilder.mActions != null) {
9766                 for (Notification.Action action : mBuilder.mActions) {
9767                     if (action.isContextual()) {
9768                         // Always include all contextual actions
9769                         resultActions.add(action);
9770                     } else if (isActionAddedByCallStyle(action)) {
9771                         // Drop any old versions of system actions
9772                     } else {
9773                         // Copy non-contextual actions; decrement the remaining action slots.
9774                         resultActions.add(action);
9775                         --nonContextualActionSlotsRemaining;
9776                     }
9777                     // If there's exactly one action slot left, fill it with the lastAction.
9778                     if (lastAction != null && nonContextualActionSlotsRemaining == 1) {
9779                         resultActions.add(lastAction);
9780                         --nonContextualActionSlotsRemaining;
9781                     }
9782                 }
9783             }
9784             // If there are any action slots left, the lastAction still needs to be added.
9785             if (lastAction != null && nonContextualActionSlotsRemaining >= 1) {
9786                 resultActions.add(lastAction);
9787             }
9788             return resultActions;
9789         }
9790 
makeCallLayout(int viewType)9791         private RemoteViews makeCallLayout(int viewType) {
9792             final boolean isCollapsed = viewType == StandardTemplateParams.VIEW_TYPE_NORMAL;
9793             Bundle extras = mBuilder.mN.extras;
9794             CharSequence title = mPerson != null ? mPerson.getName() : null;
9795             CharSequence text = mBuilder.processLegacyText(extras.getCharSequence(EXTRA_TEXT));
9796             if (text == null) {
9797                 text = getDefaultText();
9798             }
9799 
9800             // Bind standard template
9801             StandardTemplateParams p = mBuilder.mParams.reset()
9802                     .viewType(viewType)
9803                     .callStyleActions(true)
9804                     .allowTextWithProgress(true)
9805                     .hideLeftIcon(true)
9806                     .hideRightIcon(true)
9807                     .hideAppName(isCollapsed)
9808                     .titleViewId(R.id.conversation_text)
9809                     .title(title)
9810                     .text(text)
9811                     .summaryText(mBuilder.processLegacyText(mVerificationText));
9812             mBuilder.mActions = getActionsListWithSystemActions();
9813             final RemoteViews contentView;
9814             if (isCollapsed) {
9815                 contentView = mBuilder.applyStandardTemplate(
9816                         R.layout.notification_template_material_call, p, null /* result */);
9817             } else {
9818                 contentView = mBuilder.applyStandardTemplateWithActions(
9819                         R.layout.notification_template_material_big_call, p, null /* result */);
9820             }
9821 
9822             // Bind some extra conversation-specific header fields.
9823             if (!p.mHideAppName) {
9824                 mBuilder.setTextViewColorSecondary(contentView, R.id.app_name_divider, p);
9825                 contentView.setViewVisibility(R.id.app_name_divider, View.VISIBLE);
9826             }
9827             bindCallerVerification(contentView, p);
9828 
9829             // Bind some custom CallLayout properties
9830             contentView.setInt(R.id.status_bar_latest_event_content, "setLayoutColor",
9831                     mBuilder.getSmallIconColor(p));
9832             contentView.setInt(R.id.status_bar_latest_event_content,
9833                     "setNotificationBackgroundColor", mBuilder.getBackgroundColor(p));
9834             contentView.setIcon(R.id.status_bar_latest_event_content, "setLargeIcon",
9835                     mBuilder.mN.mLargeIcon);
9836             contentView.setBundle(R.id.status_bar_latest_event_content, "setData",
9837                     mBuilder.mN.extras);
9838 
9839             return contentView;
9840         }
9841 
bindCallerVerification(RemoteViews contentView, StandardTemplateParams p)9842         private void bindCallerVerification(RemoteViews contentView, StandardTemplateParams p) {
9843             String iconContentDescription = null;
9844             boolean showDivider = true;
9845             if (mVerificationIcon != null) {
9846                 contentView.setImageViewIcon(R.id.verification_icon, mVerificationIcon);
9847                 contentView.setDrawableTint(R.id.verification_icon, false /* targetBackground */,
9848                         mBuilder.getSecondaryTextColor(p), PorterDuff.Mode.SRC_ATOP);
9849                 contentView.setViewVisibility(R.id.verification_icon, View.VISIBLE);
9850                 iconContentDescription = mBuilder.mContext.getString(
9851                         R.string.notification_verified_content_description);
9852                 showDivider = false;  // the icon replaces the divider
9853             } else {
9854                 contentView.setViewVisibility(R.id.verification_icon, View.GONE);
9855             }
9856             if (!TextUtils.isEmpty(mVerificationText)) {
9857                 contentView.setTextViewText(R.id.verification_text, mVerificationText);
9858                 mBuilder.setTextViewColorSecondary(contentView, R.id.verification_text, p);
9859                 contentView.setViewVisibility(R.id.verification_text, View.VISIBLE);
9860                 iconContentDescription = null;  // let the app's text take precedence
9861             } else {
9862                 contentView.setViewVisibility(R.id.verification_text, View.GONE);
9863                 showDivider = false;  // no divider if no text
9864             }
9865             contentView.setContentDescription(R.id.verification_icon, iconContentDescription);
9866             if (showDivider) {
9867                 contentView.setViewVisibility(R.id.verification_divider, View.VISIBLE);
9868                 mBuilder.setTextViewColorSecondary(contentView, R.id.verification_divider, p);
9869             } else {
9870                 contentView.setViewVisibility(R.id.verification_divider, View.GONE);
9871             }
9872         }
9873 
9874         @Nullable
getDefaultText()9875         private String getDefaultText() {
9876             switch (mCallType) {
9877                 case CALL_TYPE_INCOMING:
9878                     return mBuilder.mContext.getString(R.string.call_notification_incoming_text);
9879                 case CALL_TYPE_ONGOING:
9880                     return mBuilder.mContext.getString(R.string.call_notification_ongoing_text);
9881                 case CALL_TYPE_SCREENING:
9882                     return mBuilder.mContext.getString(R.string.call_notification_screening_text);
9883             }
9884             return null;
9885         }
9886 
9887         /**
9888          * @hide
9889          */
addExtras(Bundle extras)9890         public void addExtras(Bundle extras) {
9891             super.addExtras(extras);
9892             extras.putInt(EXTRA_CALL_TYPE, mCallType);
9893             extras.putBoolean(EXTRA_CALL_IS_VIDEO, mIsVideo);
9894             extras.putParcelable(EXTRA_CALL_PERSON, mPerson);
9895             if (mVerificationIcon != null) {
9896                 extras.putParcelable(EXTRA_VERIFICATION_ICON, mVerificationIcon);
9897             }
9898             if (mVerificationText != null) {
9899                 extras.putCharSequence(EXTRA_VERIFICATION_TEXT, mVerificationText);
9900             }
9901             if (mAnswerIntent != null) {
9902                 extras.putParcelable(EXTRA_ANSWER_INTENT, mAnswerIntent);
9903             }
9904             if (mDeclineIntent != null) {
9905                 extras.putParcelable(EXTRA_DECLINE_INTENT, mDeclineIntent);
9906             }
9907             if (mHangUpIntent != null) {
9908                 extras.putParcelable(EXTRA_HANG_UP_INTENT, mHangUpIntent);
9909             }
9910             if (mAnswerButtonColor != null) {
9911                 extras.putInt(EXTRA_ANSWER_COLOR, mAnswerButtonColor);
9912             }
9913             if (mDeclineButtonColor != null) {
9914                 extras.putInt(EXTRA_DECLINE_COLOR, mDeclineButtonColor);
9915             }
9916             fixTitleAndTextExtras(extras);
9917         }
9918 
fixTitleAndTextExtras(Bundle extras)9919         private void fixTitleAndTextExtras(Bundle extras) {
9920             CharSequence sender = mPerson != null ? mPerson.getName() : null;
9921             if (sender != null) {
9922                 extras.putCharSequence(EXTRA_TITLE, sender);
9923             }
9924             if (extras.getCharSequence(EXTRA_TEXT) == null) {
9925                 extras.putCharSequence(EXTRA_TEXT, getDefaultText());
9926             }
9927         }
9928 
9929         /**
9930          * @hide
9931          */
9932         @Override
restoreFromExtras(Bundle extras)9933         protected void restoreFromExtras(Bundle extras) {
9934             super.restoreFromExtras(extras);
9935             mCallType = extras.getInt(EXTRA_CALL_TYPE);
9936             mIsVideo = extras.getBoolean(EXTRA_CALL_IS_VIDEO);
9937             mPerson = extras.getParcelable(EXTRA_CALL_PERSON, Person.class);
9938             mVerificationIcon = extras.getParcelable(EXTRA_VERIFICATION_ICON, android.graphics.drawable.Icon.class);
9939             mVerificationText = extras.getCharSequence(EXTRA_VERIFICATION_TEXT);
9940             mAnswerIntent = extras.getParcelable(EXTRA_ANSWER_INTENT, PendingIntent.class);
9941             mDeclineIntent = extras.getParcelable(EXTRA_DECLINE_INTENT, PendingIntent.class);
9942             mHangUpIntent = extras.getParcelable(EXTRA_HANG_UP_INTENT, PendingIntent.class);
9943             mAnswerButtonColor = extras.containsKey(EXTRA_ANSWER_COLOR)
9944                     ? extras.getInt(EXTRA_ANSWER_COLOR) : null;
9945             mDeclineButtonColor = extras.containsKey(EXTRA_DECLINE_COLOR)
9946                     ? extras.getInt(EXTRA_DECLINE_COLOR) : null;
9947         }
9948 
9949         /**
9950          * @hide
9951          */
9952         @Override
hasSummaryInHeader()9953         public boolean hasSummaryInHeader() {
9954             return false;
9955         }
9956 
9957         /**
9958          * @hide
9959          */
9960         @Override
areNotificationsVisiblyDifferent(Style other)9961         public boolean areNotificationsVisiblyDifferent(Style other) {
9962             if (other == null || getClass() != other.getClass()) {
9963                 return true;
9964             }
9965             CallStyle otherS = (CallStyle) other;
9966             return !Objects.equals(mCallType, otherS.mCallType)
9967                     || !Objects.equals(mPerson, otherS.mPerson)
9968                     || !Objects.equals(mVerificationText, otherS.mVerificationText);
9969         }
9970     }
9971 
9972     /**
9973      * Notification style for custom views that are decorated by the system
9974      *
9975      * <p>Instead of providing a notification that is completely custom, a developer can set this
9976      * style and still obtain system decorations like the notification header with the expand
9977      * affordance and actions.
9978      *
9979      * <p>Use {@link android.app.Notification.Builder#setCustomContentView(RemoteViews)},
9980      * {@link android.app.Notification.Builder#setCustomBigContentView(RemoteViews)} and
9981      * {@link android.app.Notification.Builder#setCustomHeadsUpContentView(RemoteViews)} to set the
9982      * corresponding custom views to display.
9983      *
9984      * To use this style with your Notification, feed it to
9985      * {@link Notification.Builder#setStyle(android.app.Notification.Style)} like so:
9986      * <pre class="prettyprint">
9987      * Notification noti = new Notification.Builder()
9988      *     .setSmallIcon(R.drawable.ic_stat_player)
9989      *     .setLargeIcon(albumArtBitmap))
9990      *     .setCustomContentView(contentView);
9991      *     .setStyle(<b>new Notification.DecoratedCustomViewStyle()</b>)
9992      *     .build();
9993      * </pre>
9994      */
9995     public static class DecoratedCustomViewStyle extends Style {
9996 
DecoratedCustomViewStyle()9997         public DecoratedCustomViewStyle() {
9998         }
9999 
10000         /**
10001          * @hide
10002          */
displayCustomViewInline()10003         public boolean displayCustomViewInline() {
10004             return true;
10005         }
10006 
10007         /**
10008          * @hide
10009          */
10010         @Override
makeContentView(boolean increasedHeight)10011         public RemoteViews makeContentView(boolean increasedHeight) {
10012             return makeStandardTemplateWithCustomContent(mBuilder.mN.contentView);
10013         }
10014 
10015         /**
10016          * @hide
10017          */
10018         @Override
makeBigContentView()10019         public RemoteViews makeBigContentView() {
10020             return makeDecoratedBigContentView();
10021         }
10022 
10023         /**
10024          * @hide
10025          */
10026         @Override
makeHeadsUpContentView(boolean increasedHeight)10027         public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
10028             return makeDecoratedHeadsUpContentView();
10029         }
10030 
makeDecoratedHeadsUpContentView()10031         private RemoteViews makeDecoratedHeadsUpContentView() {
10032             RemoteViews headsUpContentView = mBuilder.mN.headsUpContentView == null
10033                     ? mBuilder.mN.contentView
10034                     : mBuilder.mN.headsUpContentView;
10035             if (headsUpContentView == null) {
10036                 return null;  // no custom view; use the default behavior
10037             }
10038             if (mBuilder.mActions.size() == 0) {
10039                return makeStandardTemplateWithCustomContent(headsUpContentView);
10040             }
10041             TemplateBindResult result = new TemplateBindResult();
10042             StandardTemplateParams p = mBuilder.mParams.reset()
10043                     .viewType(StandardTemplateParams.VIEW_TYPE_HEADS_UP)
10044                     .decorationType(StandardTemplateParams.DECORATION_PARTIAL)
10045                     .fillTextsFrom(mBuilder);
10046             RemoteViews remoteViews = mBuilder.applyStandardTemplateWithActions(
10047                     mBuilder.getHeadsUpBaseLayoutResource(), p, result);
10048             buildCustomContentIntoTemplate(mBuilder.mContext, remoteViews, headsUpContentView,
10049                     p, result);
10050             return remoteViews;
10051         }
10052 
makeStandardTemplateWithCustomContent(RemoteViews customContent)10053         private RemoteViews makeStandardTemplateWithCustomContent(RemoteViews customContent) {
10054             if (customContent == null) {
10055                 return null;  // no custom view; use the default behavior
10056             }
10057             TemplateBindResult result = new TemplateBindResult();
10058             StandardTemplateParams p = mBuilder.mParams.reset()
10059                     .viewType(StandardTemplateParams.VIEW_TYPE_NORMAL)
10060                     .decorationType(StandardTemplateParams.DECORATION_PARTIAL)
10061                     .fillTextsFrom(mBuilder);
10062             RemoteViews remoteViews = mBuilder.applyStandardTemplate(
10063                     mBuilder.getBaseLayoutResource(), p, result);
10064             buildCustomContentIntoTemplate(mBuilder.mContext, remoteViews, customContent,
10065                     p, result);
10066             return remoteViews;
10067         }
10068 
makeDecoratedBigContentView()10069         private RemoteViews makeDecoratedBigContentView() {
10070             RemoteViews bigContentView = mBuilder.mN.bigContentView == null
10071                     ? mBuilder.mN.contentView
10072                     : mBuilder.mN.bigContentView;
10073             if (bigContentView == null) {
10074                 return null;  // no custom view; use the default behavior
10075             }
10076             TemplateBindResult result = new TemplateBindResult();
10077             StandardTemplateParams p = mBuilder.mParams.reset()
10078                     .viewType(StandardTemplateParams.VIEW_TYPE_BIG)
10079                     .decorationType(StandardTemplateParams.DECORATION_PARTIAL)
10080                     .fillTextsFrom(mBuilder);
10081             RemoteViews remoteViews = mBuilder.applyStandardTemplateWithActions(
10082                     mBuilder.getBigBaseLayoutResource(), p, result);
10083             buildCustomContentIntoTemplate(mBuilder.mContext, remoteViews, bigContentView,
10084                     p, result);
10085             return remoteViews;
10086         }
10087 
10088         /**
10089          * @hide
10090          */
10091         @Override
areNotificationsVisiblyDifferent(Style other)10092         public boolean areNotificationsVisiblyDifferent(Style other) {
10093             if (other == null || getClass() != other.getClass()) {
10094                 return true;
10095             }
10096             // Comparison done for all custom RemoteViews, independent of style
10097             return false;
10098         }
10099     }
10100 
10101     /**
10102      * Notification style for media custom views that are decorated by the system
10103      *
10104      * <p>Instead of providing a media notification that is completely custom, a developer can set
10105      * this style and still obtain system decorations like the notification header with the expand
10106      * affordance and actions.
10107      *
10108      * <p>Use {@link android.app.Notification.Builder#setCustomContentView(RemoteViews)},
10109      * {@link android.app.Notification.Builder#setCustomBigContentView(RemoteViews)} and
10110      * {@link android.app.Notification.Builder#setCustomHeadsUpContentView(RemoteViews)} to set the
10111      * corresponding custom views to display.
10112      * <p>
10113      * Contrary to {@link MediaStyle} a developer has to opt-in to the colorizing of the
10114      * notification by using {@link Notification.Builder#setColorized(boolean)}.
10115      * <p>
10116      * To use this style with your Notification, feed it to
10117      * {@link Notification.Builder#setStyle(android.app.Notification.Style)} like so:
10118      * <pre class="prettyprint">
10119      * Notification noti = new Notification.Builder()
10120      *     .setSmallIcon(R.drawable.ic_stat_player)
10121      *     .setLargeIcon(albumArtBitmap))
10122      *     .setCustomContentView(contentView);
10123      *     .setStyle(<b>new Notification.DecoratedMediaCustomViewStyle()</b>
10124      *          .setMediaSession(mySession))
10125      *     .build();
10126      * </pre>
10127      *
10128      * @see android.app.Notification.DecoratedCustomViewStyle
10129      * @see android.app.Notification.MediaStyle
10130      */
10131     public static class DecoratedMediaCustomViewStyle extends MediaStyle {
10132 
DecoratedMediaCustomViewStyle()10133         public DecoratedMediaCustomViewStyle() {
10134         }
10135 
10136         /**
10137          * @hide
10138          */
displayCustomViewInline()10139         public boolean displayCustomViewInline() {
10140             return true;
10141         }
10142 
10143         /**
10144          * @hide
10145          */
10146         @Override
makeContentView(boolean increasedHeight)10147         public RemoteViews makeContentView(boolean increasedHeight) {
10148             return makeMediaContentView(mBuilder.mN.contentView);
10149         }
10150 
10151         /**
10152          * @hide
10153          */
10154         @Override
makeBigContentView()10155         public RemoteViews makeBigContentView() {
10156             RemoteViews customContent = mBuilder.mN.bigContentView != null
10157                     ? mBuilder.mN.bigContentView
10158                     : mBuilder.mN.contentView;
10159             return makeMediaBigContentView(customContent);
10160         }
10161 
10162         /**
10163          * @hide
10164          */
10165         @Override
makeHeadsUpContentView(boolean increasedHeight)10166         public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
10167             RemoteViews customContent = mBuilder.mN.headsUpContentView != null
10168                     ? mBuilder.mN.headsUpContentView
10169                     : mBuilder.mN.contentView;
10170             return makeMediaBigContentView(customContent);
10171         }
10172 
10173         /**
10174          * @hide
10175          */
10176         @Override
areNotificationsVisiblyDifferent(Style other)10177         public boolean areNotificationsVisiblyDifferent(Style other) {
10178             if (other == null || getClass() != other.getClass()) {
10179                 return true;
10180             }
10181             // Comparison done for all custom RemoteViews, independent of style
10182             return false;
10183         }
10184     }
10185 
10186     /**
10187      * Encapsulates the information needed to display a notification as a bubble.
10188      *
10189      * <p>A bubble is used to display app content in a floating window over the existing
10190      * foreground activity. A bubble has a collapsed state represented by an icon and an
10191      * expanded state that displays an activity. These may be defined via
10192      * {@link Builder#Builder(PendingIntent, Icon)} or they may
10193      * be defined via an existing shortcut using {@link Builder#Builder(String)}.
10194      * </p>
10195      *
10196      * <b>Notifications with a valid and allowed bubble will display in collapsed state
10197      * outside of the notification shade on unlocked devices. When a user interacts with the
10198      * collapsed bubble, the bubble activity will be invoked and displayed.</b>
10199      *
10200      * @see Notification.Builder#setBubbleMetadata(BubbleMetadata)
10201      */
10202     public static final class BubbleMetadata implements Parcelable {
10203 
10204         private PendingIntent mPendingIntent;
10205         private PendingIntent mDeleteIntent;
10206         private Icon mIcon;
10207         private int mDesiredHeight;
10208         @DimenRes private int mDesiredHeightResId;
10209         private int mFlags;
10210         private String mShortcutId;
10211 
10212         /**
10213          * If set and the app creating the bubble is in the foreground, the bubble will be posted
10214          * in its expanded state.
10215          *
10216          * <p>This flag has no effect if the app posting the bubble is not in the foreground.
10217          * The app is considered foreground if it is visible and on the screen, note that
10218          * a foreground service does not qualify.
10219          * </p>
10220          *
10221          * <p>Generally this flag should only be set if the user has performed an action to request
10222          * or create a bubble.</p>
10223          *
10224          * @hide
10225          */
10226         public static final int FLAG_AUTO_EXPAND_BUBBLE = 0x00000001;
10227 
10228         /**
10229          * Indicates whether the notification associated with the bubble is being visually
10230          * suppressed from the notification shade. When <code>true</code> the notification is
10231          * hidden, when <code>false</code> the notification shows as normal.
10232          *
10233          * <p>Apps sending bubbles may set this flag so that the bubble is posted <b>without</b>
10234          * the associated notification in the notification shade.</p>
10235          *
10236          * <p>Generally this flag should only be set by the app if the user has performed an
10237          * action to request or create a bubble, or if the user has seen the content in the
10238          * notification and the notification is no longer relevant. </p>
10239          *
10240          * <p>The system will also update this flag with <code>true</code> to hide the notification
10241          * from the user once the bubble has been expanded. </p>
10242          *
10243          * @hide
10244          */
10245         public static final int FLAG_SUPPRESS_NOTIFICATION = 0x00000002;
10246 
10247         /**
10248          * Indicates whether the bubble should be visually suppressed from the bubble stack if the
10249          * user is viewing the same content outside of the bubble. For example, the user has a
10250          * bubble with Alice and then opens up the main app and navigates to Alice's page.
10251          *
10252          * @hide
10253          */
10254         public static final int FLAG_SUPPRESSABLE_BUBBLE = 0x00000004;
10255 
10256         /**
10257          * Indicates whether the bubble is visually suppressed from the bubble stack.
10258          *
10259          * @hide
10260          */
10261         public static final int FLAG_SUPPRESS_BUBBLE = 0x00000008;
10262 
BubbleMetadata(PendingIntent expandIntent, PendingIntent deleteIntent, Icon icon, int height, @DimenRes int heightResId, String shortcutId)10263         private BubbleMetadata(PendingIntent expandIntent, PendingIntent deleteIntent,
10264                 Icon icon, int height, @DimenRes int heightResId, String shortcutId) {
10265             mPendingIntent = expandIntent;
10266             mIcon = icon;
10267             mDesiredHeight = height;
10268             mDesiredHeightResId = heightResId;
10269             mDeleteIntent = deleteIntent;
10270             mShortcutId = shortcutId;
10271         }
10272 
BubbleMetadata(Parcel in)10273         private BubbleMetadata(Parcel in) {
10274             if (in.readInt() != 0) {
10275                 mPendingIntent = PendingIntent.CREATOR.createFromParcel(in);
10276             }
10277             if (in.readInt() != 0) {
10278                 mIcon = Icon.CREATOR.createFromParcel(in);
10279             }
10280             mDesiredHeight = in.readInt();
10281             mFlags = in.readInt();
10282             if (in.readInt() != 0) {
10283                 mDeleteIntent = PendingIntent.CREATOR.createFromParcel(in);
10284             }
10285             mDesiredHeightResId = in.readInt();
10286             if (in.readInt() != 0) {
10287                 mShortcutId = in.readString8();
10288             }
10289         }
10290 
10291         /**
10292          * @return the shortcut id used for this bubble if created via
10293          * {@link Builder#Builder(String)} or null if created
10294          * via {@link Builder#Builder(PendingIntent, Icon)}.
10295          */
10296         @Nullable
getShortcutId()10297         public String getShortcutId() {
10298             return mShortcutId;
10299         }
10300 
10301         /**
10302          * @return the pending intent used to populate the floating window for this bubble, or
10303          * null if this bubble is created via {@link Builder#Builder(String)}.
10304          */
10305         @SuppressLint("InvalidNullConversion")
10306         @Nullable
getIntent()10307         public PendingIntent getIntent() {
10308             return mPendingIntent;
10309         }
10310 
10311         /**
10312          * @deprecated use {@link #getIntent()} instead.
10313          * @removed Removed from the R SDK but was never publicly stable.
10314          */
10315         @Nullable
10316         @Deprecated
getBubbleIntent()10317         public PendingIntent getBubbleIntent() {
10318             return mPendingIntent;
10319         }
10320 
10321         /**
10322          * @return the pending intent to send when the bubble is dismissed by a user, if one exists.
10323          */
10324         @Nullable
getDeleteIntent()10325         public PendingIntent getDeleteIntent() {
10326             return mDeleteIntent;
10327         }
10328 
10329         /**
10330          * @return the icon that will be displayed for this bubble when it is collapsed, or null
10331          * if the bubble is created via {@link Builder#Builder(String)}.
10332          */
10333         @SuppressLint("InvalidNullConversion")
10334         @Nullable
getIcon()10335         public Icon getIcon() {
10336             return mIcon;
10337         }
10338 
10339         /**
10340          * @deprecated use {@link #getIcon()} instead.
10341          * @removed Removed from the R SDK but was never publicly stable.
10342          */
10343         @Nullable
10344         @Deprecated
getBubbleIcon()10345         public Icon getBubbleIcon() {
10346             return mIcon;
10347         }
10348 
10349         /**
10350          * @return the ideal height, in DPs, for the floating window that app content defined by
10351          * {@link #getIntent()} for this bubble. A value of 0 indicates a desired height has
10352          * not been set.
10353          */
10354         @Dimension(unit = DP)
getDesiredHeight()10355         public int getDesiredHeight() {
10356             return mDesiredHeight;
10357         }
10358 
10359         /**
10360          * @return the resId of ideal height for the floating window that app content defined by
10361          * {@link #getIntent()} for this bubble. A value of 0 indicates a res value has not
10362          * been provided for the desired height.
10363          */
10364         @DimenRes
getDesiredHeightResId()10365         public int getDesiredHeightResId() {
10366             return mDesiredHeightResId;
10367         }
10368 
10369         /**
10370          * @return whether this bubble should auto expand when it is posted.
10371          *
10372          * @see BubbleMetadata.Builder#setAutoExpandBubble(boolean)
10373          */
getAutoExpandBubble()10374         public boolean getAutoExpandBubble() {
10375             return (mFlags & FLAG_AUTO_EXPAND_BUBBLE) != 0;
10376         }
10377 
10378         /**
10379          * Indicates whether the notification associated with the bubble is being visually
10380          * suppressed from the notification shade. When <code>true</code> the notification is
10381          * hidden, when <code>false</code> the notification shows as normal.
10382          *
10383          * <p>Apps sending bubbles may set this flag so that the bubble is posted <b>without</b>
10384          * the associated notification in the notification shade.</p>
10385          *
10386          * <p>Generally the app should only set this flag if the user has performed an
10387          * action to request or create a bubble, or if the user has seen the content in the
10388          * notification and the notification is no longer relevant. </p>
10389          *
10390          * <p>The system will update this flag with <code>true</code> to hide the notification
10391          * from the user once the bubble has been expanded.</p>
10392          *
10393          * @return whether this bubble should suppress the notification when it is posted.
10394          *
10395          * @see BubbleMetadata.Builder#setSuppressNotification(boolean)
10396          */
isNotificationSuppressed()10397         public boolean isNotificationSuppressed() {
10398             return (mFlags & FLAG_SUPPRESS_NOTIFICATION) != 0;
10399         }
10400 
10401         /**
10402          * Indicates whether the bubble should be visually suppressed from the bubble stack if the
10403          * user is viewing the same content outside of the bubble. For example, the user has a
10404          * bubble with Alice and then opens up the main app and navigates to Alice's page.
10405          *
10406          * To match the activity and the bubble notification, the bubble notification should
10407          * have a locus id set that matches a locus id set on the activity.
10408          *
10409          * @return whether this bubble should be suppressed when the same content is visible
10410          * outside of the bubble.
10411          *
10412          * @see BubbleMetadata.Builder#setSuppressableBubble(boolean)
10413          */
isBubbleSuppressable()10414         public boolean isBubbleSuppressable() {
10415             return (mFlags & FLAG_SUPPRESSABLE_BUBBLE) != 0;
10416         }
10417 
10418         /**
10419          * Indicates whether the bubble is currently visually suppressed from the bubble stack.
10420          *
10421          * @see BubbleMetadata.Builder#setSuppressableBubble(boolean)
10422          */
isBubbleSuppressed()10423         public boolean isBubbleSuppressed() {
10424             return (mFlags & FLAG_SUPPRESS_BUBBLE) != 0;
10425         }
10426 
10427         /**
10428          * Sets whether the notification associated with the bubble is being visually
10429          * suppressed from the notification shade. When <code>true</code> the notification is
10430          * hidden, when <code>false</code> the notification shows as normal.
10431          *
10432          * @hide
10433          */
setSuppressNotification(boolean suppressed)10434         public void setSuppressNotification(boolean suppressed) {
10435             if (suppressed) {
10436                 mFlags |= Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION;
10437             } else {
10438                 mFlags &= ~Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION;
10439             }
10440         }
10441 
10442         /**
10443          * Sets whether the bubble should be visually suppressed from the bubble stack if the
10444          * user is viewing the same content outside of the bubble. For example, the user has a
10445          * bubble with Alice and then opens up the main app and navigates to Alice's page.
10446          *
10447          * @hide
10448          */
setSuppressBubble(boolean suppressed)10449         public void setSuppressBubble(boolean suppressed) {
10450             if (suppressed) {
10451                 mFlags |= Notification.BubbleMetadata.FLAG_SUPPRESS_BUBBLE;
10452             } else {
10453                 mFlags &= ~Notification.BubbleMetadata.FLAG_SUPPRESS_BUBBLE;
10454             }
10455         }
10456 
10457         /**
10458          * @hide
10459          */
setFlags(int flags)10460         public void setFlags(int flags) {
10461             mFlags = flags;
10462         }
10463 
10464         /**
10465          * @hide
10466          */
getFlags()10467         public int getFlags() {
10468             return mFlags;
10469         }
10470 
10471         public static final @android.annotation.NonNull Parcelable.Creator<BubbleMetadata> CREATOR =
10472                 new Parcelable.Creator<BubbleMetadata>() {
10473 
10474                     @Override
10475                     public BubbleMetadata createFromParcel(Parcel source) {
10476                         return new BubbleMetadata(source);
10477                     }
10478 
10479                     @Override
10480                     public BubbleMetadata[] newArray(int size) {
10481                         return new BubbleMetadata[size];
10482                     }
10483                 };
10484 
10485         @Override
describeContents()10486         public int describeContents() {
10487             return 0;
10488         }
10489 
10490         @Override
writeToParcel(Parcel out, int flags)10491         public void writeToParcel(Parcel out, int flags) {
10492             out.writeInt(mPendingIntent != null ? 1 : 0);
10493             if (mPendingIntent != null) {
10494                 mPendingIntent.writeToParcel(out, 0);
10495             }
10496             out.writeInt(mIcon != null ? 1 : 0);
10497             if (mIcon != null) {
10498                 mIcon.writeToParcel(out, 0);
10499             }
10500             out.writeInt(mDesiredHeight);
10501             out.writeInt(mFlags);
10502             out.writeInt(mDeleteIntent != null ? 1 : 0);
10503             if (mDeleteIntent != null) {
10504                 mDeleteIntent.writeToParcel(out, 0);
10505             }
10506             out.writeInt(mDesiredHeightResId);
10507             out.writeInt(TextUtils.isEmpty(mShortcutId) ? 0 : 1);
10508             if (!TextUtils.isEmpty(mShortcutId)) {
10509                 out.writeString8(mShortcutId);
10510             }
10511         }
10512 
10513         /**
10514          * Builder to construct a {@link BubbleMetadata} object.
10515          */
10516         public static final class Builder {
10517 
10518             private PendingIntent mPendingIntent;
10519             private Icon mIcon;
10520             private int mDesiredHeight;
10521             @DimenRes private int mDesiredHeightResId;
10522             private int mFlags;
10523             private PendingIntent mDeleteIntent;
10524             private String mShortcutId;
10525 
10526             /**
10527              * @deprecated use {@link Builder#Builder(String)} for a bubble created via a
10528              * {@link ShortcutInfo} or {@link Builder#Builder(PendingIntent, Icon)} for a bubble
10529              * created via a {@link PendingIntent}.
10530              */
10531             @Deprecated
Builder()10532             public Builder() {
10533             }
10534 
10535             /**
10536              * Creates a {@link BubbleMetadata.Builder} based on a {@link ShortcutInfo}. To create
10537              * a shortcut bubble, ensure that the shortcut associated with the provided
10538              * {@param shortcutId} is published as a dynamic shortcut that was built with
10539              * {@link ShortcutInfo.Builder#setLongLived(boolean)} being true, otherwise your
10540              * notification will not be able to bubble.
10541              *
10542              * <p>The shortcut icon will be used to represent the bubble when it is collapsed.</p>
10543              *
10544              * <p>The shortcut activity will be used when the bubble is expanded. This will display
10545              * the shortcut activity in a floating window over the existing foreground activity.</p>
10546              *
10547              * <p>When the activity is launched from a bubble,
10548              * {@link Activity#isLaunchedFromBubble()} will return with {@code true}.
10549              * </p>
10550              *
10551              * <p>If the shortcut has not been published when the bubble notification is sent,
10552              * no bubble will be produced. If the shortcut is deleted while the bubble is active,
10553              * the bubble will be removed.</p>
10554              *
10555              * @throws NullPointerException if shortcutId is null.
10556              *
10557              * @see ShortcutInfo
10558              * @see ShortcutInfo.Builder#setLongLived(boolean)
10559              * @see android.content.pm.ShortcutManager#addDynamicShortcuts(List)
10560              */
Builder(@onNull String shortcutId)10561             public Builder(@NonNull String shortcutId) {
10562                 if (TextUtils.isEmpty(shortcutId)) {
10563                     throw new NullPointerException("Bubble requires a non-null shortcut id");
10564                 }
10565                 mShortcutId = shortcutId;
10566             }
10567 
10568             /**
10569              * Creates a {@link BubbleMetadata.Builder} based on the provided intent and icon.
10570              *
10571              * <p>The icon will be used to represent the bubble when it is collapsed. An icon
10572              * should be representative of the content within the bubble. If your app produces
10573              * multiple bubbles, the icon should be unique for each of them.</p>
10574              *
10575              * <p>The intent that will be used when the bubble is expanded. This will display the
10576              * app content in a floating window over the existing foreground activity. The intent
10577              * should point to a resizable activity. </p>
10578              *
10579              * <p>When the activity is launched from a bubble,
10580              * {@link Activity#isLaunchedFromBubble()} will return with {@code true}.
10581              * </p>
10582              *
10583              * Note that the pending intent used here requires PendingIntent.FLAG_MUTABLE.
10584              *
10585              * @throws NullPointerException if intent is null.
10586              * @throws NullPointerException if icon is null.
10587              */
Builder(@onNull PendingIntent intent, @NonNull Icon icon)10588             public Builder(@NonNull PendingIntent intent, @NonNull Icon icon) {
10589                 if (intent == null) {
10590                     throw new NullPointerException("Bubble requires non-null pending intent");
10591                 }
10592                 if (icon == null) {
10593                     throw new NullPointerException("Bubbles require non-null icon");
10594                 }
10595                 if (icon.getType() != TYPE_URI_ADAPTIVE_BITMAP
10596                         && icon.getType() != TYPE_URI) {
10597                     Log.w(TAG, "Bubbles work best with icons of TYPE_URI or "
10598                             + "TYPE_URI_ADAPTIVE_BITMAP. "
10599                             + "In the future, using an icon of this type will be required.");
10600                 }
10601                 mPendingIntent = intent;
10602                 mIcon = icon;
10603             }
10604 
10605             /**
10606              * @deprecated use {@link Builder#Builder(String)} instead.
10607              * @removed Removed from the R SDK but was never publicly stable.
10608              */
10609             @NonNull
10610             @Deprecated
createShortcutBubble(@onNull String shortcutId)10611             public BubbleMetadata.Builder createShortcutBubble(@NonNull String shortcutId) {
10612                 if (!TextUtils.isEmpty(shortcutId)) {
10613                     // If shortcut id is set, we don't use these if they were previously set.
10614                     mPendingIntent = null;
10615                     mIcon = null;
10616                 }
10617                 mShortcutId = shortcutId;
10618                 return this;
10619             }
10620 
10621             /**
10622              * @deprecated use {@link Builder#Builder(PendingIntent, Icon)} instead.
10623              * @removed Removed from the R SDK but was never publicly stable.
10624              */
10625             @NonNull
10626             @Deprecated
createIntentBubble(@onNull PendingIntent intent, @NonNull Icon icon)10627             public BubbleMetadata.Builder createIntentBubble(@NonNull PendingIntent intent,
10628                     @NonNull Icon icon) {
10629                 if (intent == null) {
10630                     throw new IllegalArgumentException("Bubble requires non-null pending intent");
10631                 }
10632                 if (icon == null) {
10633                     throw new IllegalArgumentException("Bubbles require non-null icon");
10634                 }
10635                 if (icon.getType() != TYPE_URI_ADAPTIVE_BITMAP
10636                         && icon.getType() != TYPE_URI) {
10637                     Log.w(TAG, "Bubbles work best with icons of TYPE_URI or "
10638                             + "TYPE_URI_ADAPTIVE_BITMAP. "
10639                             + "In the future, using an icon of this type will be required.");
10640                 }
10641                 mShortcutId = null;
10642                 mPendingIntent = intent;
10643                 mIcon = icon;
10644                 return this;
10645             }
10646 
10647             /**
10648              * Sets the intent for the bubble.
10649              *
10650              * <p>The intent that will be used when the bubble is expanded. This will display the
10651              * app content in a floating window over the existing foreground activity. The intent
10652              * should point to a resizable activity. </p>
10653              *
10654              * @throws NullPointerException  if intent is null.
10655              * @throws IllegalStateException if this builder was created via
10656              *                               {@link Builder#Builder(String)}.
10657              */
10658             @NonNull
setIntent(@onNull PendingIntent intent)10659             public BubbleMetadata.Builder setIntent(@NonNull PendingIntent intent) {
10660                 if (mShortcutId != null) {
10661                     throw new IllegalStateException("Created as a shortcut bubble, cannot set a "
10662                             + "PendingIntent. Consider using "
10663                             + "BubbleMetadata.Builder(PendingIntent,Icon) instead.");
10664                 }
10665                 if (intent == null) {
10666                     throw new NullPointerException("Bubble requires non-null pending intent");
10667                 }
10668                 mPendingIntent = intent;
10669                 return this;
10670             }
10671 
10672             /**
10673              * Sets the icon for the bubble. Can only be used if the bubble was created
10674              * via {@link Builder#Builder(PendingIntent, Icon)}.
10675              *
10676              * <p>The icon will be used to represent the bubble when it is collapsed. An icon
10677              * should be representative of the content within the bubble. If your app produces
10678              * multiple bubbles, the icon should be unique for each of them.</p>
10679              *
10680              * <p>It is recommended to use an {@link Icon} of type {@link Icon#TYPE_URI}
10681              * or {@link Icon#TYPE_URI_ADAPTIVE_BITMAP}</p>
10682              *
10683              * @throws NullPointerException  if icon is null.
10684              * @throws IllegalStateException if this builder was created via
10685              *                               {@link Builder#Builder(String)}.
10686              */
10687             @NonNull
setIcon(@onNull Icon icon)10688             public BubbleMetadata.Builder setIcon(@NonNull Icon icon) {
10689                 if (mShortcutId != null) {
10690                     throw new IllegalStateException("Created as a shortcut bubble, cannot set an "
10691                             + "Icon. Consider using "
10692                             + "BubbleMetadata.Builder(PendingIntent,Icon) instead.");
10693                 }
10694                 if (icon == null) {
10695                     throw new NullPointerException("Bubbles require non-null icon");
10696                 }
10697                 if (icon.getType() != TYPE_URI_ADAPTIVE_BITMAP
10698                         && icon.getType() != TYPE_URI) {
10699                     Log.w(TAG, "Bubbles work best with icons of TYPE_URI or "
10700                             + "TYPE_URI_ADAPTIVE_BITMAP. "
10701                             + "In the future, using an icon of this type will be required.");
10702                 }
10703                 mIcon = icon;
10704                 return this;
10705             }
10706 
10707             /**
10708              * Sets the desired height in DPs for the expanded content of the bubble.
10709              *
10710              * <p>This height may not be respected if there is not enough space on the screen or if
10711              * the provided height is too small to be useful.</p>
10712              *
10713              * <p>If {@link #setDesiredHeightResId(int)} was previously called on this builder, the
10714              * previous value set will be cleared after calling this method, and this value will
10715              * be used instead.</p>
10716              *
10717              * <p>A desired height (in DPs or via resID) is optional.</p>
10718              *
10719              * @see #setDesiredHeightResId(int)
10720              */
10721             @NonNull
setDesiredHeight(@imensionunit = DP) int height)10722             public BubbleMetadata.Builder setDesiredHeight(@Dimension(unit = DP) int height) {
10723                 mDesiredHeight = Math.max(height, 0);
10724                 mDesiredHeightResId = 0;
10725                 return this;
10726             }
10727 
10728 
10729             /**
10730              * Sets the desired height via resId for the expanded content of the bubble.
10731              *
10732              * <p>This height may not be respected if there is not enough space on the screen or if
10733              * the provided height is too small to be useful.</p>
10734              *
10735              * <p>If {@link #setDesiredHeight(int)} was previously called on this builder, the
10736              * previous value set will be cleared after calling this method, and this value will
10737              * be used instead.</p>
10738              *
10739              * <p>A desired height (in DPs or via resID) is optional.</p>
10740              *
10741              * @see #setDesiredHeight(int)
10742              */
10743             @NonNull
setDesiredHeightResId(@imenRes int heightResId)10744             public BubbleMetadata.Builder setDesiredHeightResId(@DimenRes int heightResId) {
10745                 mDesiredHeightResId = heightResId;
10746                 mDesiredHeight = 0;
10747                 return this;
10748             }
10749 
10750             /**
10751              * Sets whether the bubble will be posted in its expanded state.
10752              *
10753              * <p>This flag has no effect if the app posting the bubble is not in the foreground.
10754              * The app is considered foreground if it is visible and on the screen, note that
10755              * a foreground service does not qualify.
10756              * </p>
10757              *
10758              * <p>Generally, this flag should only be set if the user has performed an action to
10759              * request or create a bubble.</p>
10760              *
10761              * <p>Setting this flag is optional; it defaults to false.</p>
10762              */
10763             @NonNull
setAutoExpandBubble(boolean shouldExpand)10764             public BubbleMetadata.Builder setAutoExpandBubble(boolean shouldExpand) {
10765                 setFlag(FLAG_AUTO_EXPAND_BUBBLE, shouldExpand);
10766                 return this;
10767             }
10768 
10769             /**
10770              * Sets whether the bubble will be posted <b>without</b> the associated notification in
10771              * the notification shade.
10772              *
10773              * <p>Generally, this flag should only be set if the user has performed an action to
10774              * request or create a bubble, or if the user has seen the content in the notification
10775              * and the notification is no longer relevant.</p>
10776              *
10777              * <p>Setting this flag is optional; it defaults to false.</p>
10778              */
10779             @NonNull
setSuppressNotification(boolean shouldSuppressNotif)10780             public BubbleMetadata.Builder setSuppressNotification(boolean shouldSuppressNotif) {
10781                 setFlag(FLAG_SUPPRESS_NOTIFICATION, shouldSuppressNotif);
10782                 return this;
10783             }
10784 
10785             /**
10786              * Indicates whether the bubble should be visually suppressed from the bubble stack if
10787              * the user is viewing the same content outside of the bubble. For example, the user has
10788              * a bubble with Alice and then opens up the main app and navigates to Alice's page.
10789              *
10790              * To match the activity and the bubble notification, the bubble notification should
10791              * have a locus id set that matches a locus id set on the activity.
10792              *
10793              * {@link Notification.Builder#setLocusId(LocusId)}
10794              * {@link Activity#setLocusContext(LocusId, Bundle)}
10795              */
10796             @NonNull
setSuppressableBubble(boolean suppressBubble)10797             public BubbleMetadata.Builder setSuppressableBubble(boolean suppressBubble) {
10798                 setFlag(FLAG_SUPPRESSABLE_BUBBLE, suppressBubble);
10799                 return this;
10800             }
10801 
10802             /**
10803              * Sets an intent to send when this bubble is explicitly removed by the user.
10804              *
10805              * <p>Setting a delete intent is optional.</p>
10806              */
10807             @NonNull
setDeleteIntent(@ullable PendingIntent deleteIntent)10808             public BubbleMetadata.Builder setDeleteIntent(@Nullable PendingIntent deleteIntent) {
10809                 mDeleteIntent = deleteIntent;
10810                 return this;
10811             }
10812 
10813             /**
10814              * Creates the {@link BubbleMetadata} defined by this builder.
10815              *
10816              * @throws NullPointerException if required elements have not been set.
10817              */
10818             @NonNull
build()10819             public BubbleMetadata build() {
10820                 if (mShortcutId == null && mPendingIntent == null) {
10821                     throw new NullPointerException(
10822                             "Must supply pending intent or shortcut to bubble");
10823                 }
10824                 if (mShortcutId == null && mIcon == null) {
10825                     throw new NullPointerException(
10826                             "Must supply an icon or shortcut for the bubble");
10827                 }
10828                 BubbleMetadata data = new BubbleMetadata(mPendingIntent, mDeleteIntent,
10829                         mIcon, mDesiredHeight, mDesiredHeightResId, mShortcutId);
10830                 data.setFlags(mFlags);
10831                 return data;
10832             }
10833 
10834             /**
10835              * @hide
10836              */
setFlag(int mask, boolean value)10837             public BubbleMetadata.Builder setFlag(int mask, boolean value) {
10838                 if (value) {
10839                     mFlags |= mask;
10840                 } else {
10841                     mFlags &= ~mask;
10842                 }
10843                 return this;
10844             }
10845         }
10846     }
10847 
10848 
10849     // When adding a new Style subclass here, don't forget to update
10850     // Builder.getNotificationStyleClass.
10851 
10852     /**
10853      * Extender interface for use with {@link Builder#extend}. Extenders may be used to add
10854      * metadata or change options on a notification builder.
10855      */
10856     public interface Extender {
10857         /**
10858          * Apply this extender to a notification builder.
10859          * @param builder the builder to be modified.
10860          * @return the build object for chaining.
10861          */
extend(Builder builder)10862         public Builder extend(Builder builder);
10863     }
10864 
10865     /**
10866      * Helper class to add wearable extensions to notifications.
10867      * <p class="note"> See
10868      * <a href="{@docRoot}wear/notifications/creating.html">Creating Notifications
10869      * for Android Wear</a> for more information on how to use this class.
10870      * <p>
10871      * To create a notification with wearable extensions:
10872      * <ol>
10873      *   <li>Create a {@link android.app.Notification.Builder}, setting any desired
10874      *   properties.
10875      *   <li>Create a {@link android.app.Notification.WearableExtender}.
10876      *   <li>Set wearable-specific properties using the
10877      *   {@code add} and {@code set} methods of {@link android.app.Notification.WearableExtender}.
10878      *   <li>Call {@link android.app.Notification.Builder#extend} to apply the extensions to a
10879      *   notification.
10880      *   <li>Post the notification to the notification system with the
10881      *   {@code NotificationManager.notify(...)} methods.
10882      * </ol>
10883      *
10884      * <pre class="prettyprint">
10885      * Notification notif = new Notification.Builder(mContext)
10886      *         .setContentTitle(&quot;New mail from &quot; + sender.toString())
10887      *         .setContentText(subject)
10888      *         .setSmallIcon(R.drawable.new_mail)
10889      *         .extend(new Notification.WearableExtender()
10890      *                 .setContentIcon(R.drawable.new_mail))
10891      *         .build();
10892      * NotificationManager notificationManger =
10893      *         (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
10894      * notificationManger.notify(0, notif);</pre>
10895      *
10896      * <p>Wearable extensions can be accessed on an existing notification by using the
10897      * {@code WearableExtender(Notification)} constructor,
10898      * and then using the {@code get} methods to access values.
10899      *
10900      * <pre class="prettyprint">
10901      * Notification.WearableExtender wearableExtender = new Notification.WearableExtender(
10902      *         notification);
10903      * List&lt;Notification&gt; pages = wearableExtender.getPages();</pre>
10904      */
10905     public static final class WearableExtender implements Extender {
10906         /**
10907          * Sentinel value for an action index that is unset.
10908          */
10909         public static final int UNSET_ACTION_INDEX = -1;
10910 
10911         /**
10912          * Size value for use with {@link #setCustomSizePreset} to show this notification with
10913          * default sizing.
10914          * <p>For custom display notifications created using {@link #setDisplayIntent},
10915          * the default is {@link #SIZE_MEDIUM}. All other notifications size automatically based
10916          * on their content.
10917          *
10918          * @deprecated Display intents are no longer supported.
10919          */
10920         @Deprecated
10921         public static final int SIZE_DEFAULT = 0;
10922 
10923         /**
10924          * Size value for use with {@link #setCustomSizePreset} to show this notification
10925          * with an extra small size.
10926          * <p>This value is only applicable for custom display notifications created using
10927          * {@link #setDisplayIntent}.
10928          *
10929          * @deprecated Display intents are no longer supported.
10930          */
10931         @Deprecated
10932         public static final int SIZE_XSMALL = 1;
10933 
10934         /**
10935          * Size value for use with {@link #setCustomSizePreset} to show this notification
10936          * with a small size.
10937          * <p>This value is only applicable for custom display notifications created using
10938          * {@link #setDisplayIntent}.
10939          *
10940          * @deprecated Display intents are no longer supported.
10941          */
10942         @Deprecated
10943         public static final int SIZE_SMALL = 2;
10944 
10945         /**
10946          * Size value for use with {@link #setCustomSizePreset} to show this notification
10947          * with a medium size.
10948          * <p>This value is only applicable for custom display notifications created using
10949          * {@link #setDisplayIntent}.
10950          *
10951          * @deprecated Display intents are no longer supported.
10952          */
10953         @Deprecated
10954         public static final int SIZE_MEDIUM = 3;
10955 
10956         /**
10957          * Size value for use with {@link #setCustomSizePreset} to show this notification
10958          * with a large size.
10959          * <p>This value is only applicable for custom display notifications created using
10960          * {@link #setDisplayIntent}.
10961          *
10962          * @deprecated Display intents are no longer supported.
10963          */
10964         @Deprecated
10965         public static final int SIZE_LARGE = 4;
10966 
10967         /**
10968          * Size value for use with {@link #setCustomSizePreset} to show this notification
10969          * full screen.
10970          * <p>This value is only applicable for custom display notifications created using
10971          * {@link #setDisplayIntent}.
10972          *
10973          * @deprecated Display intents are no longer supported.
10974          */
10975         @Deprecated
10976         public static final int SIZE_FULL_SCREEN = 5;
10977 
10978         /**
10979          * Sentinel value for use with {@link #setHintScreenTimeout} to keep the screen on for a
10980          * short amount of time when this notification is displayed on the screen. This
10981          * is the default value.
10982          *
10983          * @deprecated This feature is no longer supported.
10984          */
10985         @Deprecated
10986         public static final int SCREEN_TIMEOUT_SHORT = 0;
10987 
10988         /**
10989          * Sentinel value for use with {@link #setHintScreenTimeout} to keep the screen on
10990          * for a longer amount of time when this notification is displayed on the screen.
10991          *
10992          * @deprecated This feature is no longer supported.
10993          */
10994         @Deprecated
10995         public static final int SCREEN_TIMEOUT_LONG = -1;
10996 
10997         /** Notification extra which contains wearable extensions */
10998         private static final String EXTRA_WEARABLE_EXTENSIONS = "android.wearable.EXTENSIONS";
10999 
11000         // Keys within EXTRA_WEARABLE_EXTENSIONS for wearable options.
11001         private static final String KEY_ACTIONS = "actions";
11002         private static final String KEY_FLAGS = "flags";
11003         static final String KEY_DISPLAY_INTENT = "displayIntent";
11004         private static final String KEY_PAGES = "pages";
11005         static final String KEY_BACKGROUND = "background";
11006         private static final String KEY_CONTENT_ICON = "contentIcon";
11007         private static final String KEY_CONTENT_ICON_GRAVITY = "contentIconGravity";
11008         private static final String KEY_CONTENT_ACTION_INDEX = "contentActionIndex";
11009         private static final String KEY_CUSTOM_SIZE_PRESET = "customSizePreset";
11010         private static final String KEY_CUSTOM_CONTENT_HEIGHT = "customContentHeight";
11011         private static final String KEY_GRAVITY = "gravity";
11012         private static final String KEY_HINT_SCREEN_TIMEOUT = "hintScreenTimeout";
11013         private static final String KEY_DISMISSAL_ID = "dismissalId";
11014         private static final String KEY_BRIDGE_TAG = "bridgeTag";
11015 
11016         // Flags bitwise-ored to mFlags
11017         private static final int FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE = 0x1;
11018         private static final int FLAG_HINT_HIDE_ICON = 1 << 1;
11019         private static final int FLAG_HINT_SHOW_BACKGROUND_ONLY = 1 << 2;
11020         private static final int FLAG_START_SCROLL_BOTTOM = 1 << 3;
11021         private static final int FLAG_HINT_AVOID_BACKGROUND_CLIPPING = 1 << 4;
11022         private static final int FLAG_BIG_PICTURE_AMBIENT = 1 << 5;
11023         private static final int FLAG_HINT_CONTENT_INTENT_LAUNCHES_ACTIVITY = 1 << 6;
11024 
11025         // Default value for flags integer
11026         private static final int DEFAULT_FLAGS = FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE;
11027 
11028         private static final int DEFAULT_CONTENT_ICON_GRAVITY = Gravity.END;
11029         private static final int DEFAULT_GRAVITY = Gravity.BOTTOM;
11030 
11031         private ArrayList<Action> mActions = new ArrayList<Action>();
11032         private int mFlags = DEFAULT_FLAGS;
11033         private PendingIntent mDisplayIntent;
11034         private ArrayList<Notification> mPages = new ArrayList<Notification>();
11035         private Bitmap mBackground;
11036         private int mContentIcon;
11037         private int mContentIconGravity = DEFAULT_CONTENT_ICON_GRAVITY;
11038         private int mContentActionIndex = UNSET_ACTION_INDEX;
11039         private int mCustomSizePreset = SIZE_DEFAULT;
11040         private int mCustomContentHeight;
11041         private int mGravity = DEFAULT_GRAVITY;
11042         private int mHintScreenTimeout;
11043         private String mDismissalId;
11044         private String mBridgeTag;
11045 
11046         /**
11047          * Create a {@link android.app.Notification.WearableExtender} with default
11048          * options.
11049          */
WearableExtender()11050         public WearableExtender() {
11051         }
11052 
WearableExtender(Notification notif)11053         public WearableExtender(Notification notif) {
11054             Bundle wearableBundle = notif.extras.getBundle(EXTRA_WEARABLE_EXTENSIONS);
11055             if (wearableBundle != null) {
11056                 List<Action> actions = wearableBundle.getParcelableArrayList(KEY_ACTIONS, android.app.Notification.Action.class);
11057                 if (actions != null) {
11058                     mActions.addAll(actions);
11059                 }
11060 
11061                 mFlags = wearableBundle.getInt(KEY_FLAGS, DEFAULT_FLAGS);
11062                 mDisplayIntent = wearableBundle.getParcelable(
11063                         KEY_DISPLAY_INTENT, PendingIntent.class);
11064 
11065                 Notification[] pages = getParcelableArrayFromBundle(
11066                         wearableBundle, KEY_PAGES, Notification.class);
11067                 if (pages != null) {
11068                     Collections.addAll(mPages, pages);
11069                 }
11070 
11071                 mBackground = wearableBundle.getParcelable(KEY_BACKGROUND, Bitmap.class);
11072                 mContentIcon = wearableBundle.getInt(KEY_CONTENT_ICON);
11073                 mContentIconGravity = wearableBundle.getInt(KEY_CONTENT_ICON_GRAVITY,
11074                         DEFAULT_CONTENT_ICON_GRAVITY);
11075                 mContentActionIndex = wearableBundle.getInt(KEY_CONTENT_ACTION_INDEX,
11076                         UNSET_ACTION_INDEX);
11077                 mCustomSizePreset = wearableBundle.getInt(KEY_CUSTOM_SIZE_PRESET,
11078                         SIZE_DEFAULT);
11079                 mCustomContentHeight = wearableBundle.getInt(KEY_CUSTOM_CONTENT_HEIGHT);
11080                 mGravity = wearableBundle.getInt(KEY_GRAVITY, DEFAULT_GRAVITY);
11081                 mHintScreenTimeout = wearableBundle.getInt(KEY_HINT_SCREEN_TIMEOUT);
11082                 mDismissalId = wearableBundle.getString(KEY_DISMISSAL_ID);
11083                 mBridgeTag = wearableBundle.getString(KEY_BRIDGE_TAG);
11084             }
11085         }
11086 
11087         /**
11088          * Apply wearable extensions to a notification that is being built. This is typically
11089          * called by the {@link android.app.Notification.Builder#extend} method of
11090          * {@link android.app.Notification.Builder}.
11091          */
11092         @Override
extend(Notification.Builder builder)11093         public Notification.Builder extend(Notification.Builder builder) {
11094             Bundle wearableBundle = new Bundle();
11095 
11096             if (!mActions.isEmpty()) {
11097                 wearableBundle.putParcelableArrayList(KEY_ACTIONS, mActions);
11098             }
11099             if (mFlags != DEFAULT_FLAGS) {
11100                 wearableBundle.putInt(KEY_FLAGS, mFlags);
11101             }
11102             if (mDisplayIntent != null) {
11103                 wearableBundle.putParcelable(KEY_DISPLAY_INTENT, mDisplayIntent);
11104             }
11105             if (!mPages.isEmpty()) {
11106                 wearableBundle.putParcelableArray(KEY_PAGES, mPages.toArray(
11107                         new Notification[mPages.size()]));
11108             }
11109             if (mBackground != null) {
11110                 wearableBundle.putParcelable(KEY_BACKGROUND, mBackground);
11111             }
11112             if (mContentIcon != 0) {
11113                 wearableBundle.putInt(KEY_CONTENT_ICON, mContentIcon);
11114             }
11115             if (mContentIconGravity != DEFAULT_CONTENT_ICON_GRAVITY) {
11116                 wearableBundle.putInt(KEY_CONTENT_ICON_GRAVITY, mContentIconGravity);
11117             }
11118             if (mContentActionIndex != UNSET_ACTION_INDEX) {
11119                 wearableBundle.putInt(KEY_CONTENT_ACTION_INDEX,
11120                         mContentActionIndex);
11121             }
11122             if (mCustomSizePreset != SIZE_DEFAULT) {
11123                 wearableBundle.putInt(KEY_CUSTOM_SIZE_PRESET, mCustomSizePreset);
11124             }
11125             if (mCustomContentHeight != 0) {
11126                 wearableBundle.putInt(KEY_CUSTOM_CONTENT_HEIGHT, mCustomContentHeight);
11127             }
11128             if (mGravity != DEFAULT_GRAVITY) {
11129                 wearableBundle.putInt(KEY_GRAVITY, mGravity);
11130             }
11131             if (mHintScreenTimeout != 0) {
11132                 wearableBundle.putInt(KEY_HINT_SCREEN_TIMEOUT, mHintScreenTimeout);
11133             }
11134             if (mDismissalId != null) {
11135                 wearableBundle.putString(KEY_DISMISSAL_ID, mDismissalId);
11136             }
11137             if (mBridgeTag != null) {
11138                 wearableBundle.putString(KEY_BRIDGE_TAG, mBridgeTag);
11139             }
11140 
11141             builder.getExtras().putBundle(EXTRA_WEARABLE_EXTENSIONS, wearableBundle);
11142             return builder;
11143         }
11144 
11145         @Override
clone()11146         public WearableExtender clone() {
11147             WearableExtender that = new WearableExtender();
11148             that.mActions = new ArrayList<Action>(this.mActions);
11149             that.mFlags = this.mFlags;
11150             that.mDisplayIntent = this.mDisplayIntent;
11151             that.mPages = new ArrayList<Notification>(this.mPages);
11152             that.mBackground = this.mBackground;
11153             that.mContentIcon = this.mContentIcon;
11154             that.mContentIconGravity = this.mContentIconGravity;
11155             that.mContentActionIndex = this.mContentActionIndex;
11156             that.mCustomSizePreset = this.mCustomSizePreset;
11157             that.mCustomContentHeight = this.mCustomContentHeight;
11158             that.mGravity = this.mGravity;
11159             that.mHintScreenTimeout = this.mHintScreenTimeout;
11160             that.mDismissalId = this.mDismissalId;
11161             that.mBridgeTag = this.mBridgeTag;
11162             return that;
11163         }
11164 
11165         /**
11166          * Add a wearable action to this notification.
11167          *
11168          * <p>When wearable actions are added using this method, the set of actions that
11169          * show on a wearable device splits from devices that only show actions added
11170          * using {@link android.app.Notification.Builder#addAction}. This allows for customization
11171          * of which actions display on different devices.
11172          *
11173          * @param action the action to add to this notification
11174          * @return this object for method chaining
11175          * @see android.app.Notification.Action
11176          */
addAction(Action action)11177         public WearableExtender addAction(Action action) {
11178             mActions.add(action);
11179             return this;
11180         }
11181 
11182         /**
11183          * Adds wearable actions to this notification.
11184          *
11185          * <p>When wearable actions are added using this method, the set of actions that
11186          * show on a wearable device splits from devices that only show actions added
11187          * using {@link android.app.Notification.Builder#addAction}. This allows for customization
11188          * of which actions display on different devices.
11189          *
11190          * @param actions the actions to add to this notification
11191          * @return this object for method chaining
11192          * @see android.app.Notification.Action
11193          */
addActions(List<Action> actions)11194         public WearableExtender addActions(List<Action> actions) {
11195             mActions.addAll(actions);
11196             return this;
11197         }
11198 
11199         /**
11200          * Clear all wearable actions present on this builder.
11201          * @return this object for method chaining.
11202          * @see #addAction
11203          */
clearActions()11204         public WearableExtender clearActions() {
11205             mActions.clear();
11206             return this;
11207         }
11208 
11209         /**
11210          * Get the wearable actions present on this notification.
11211          */
getActions()11212         public List<Action> getActions() {
11213             return mActions;
11214         }
11215 
11216         /**
11217          * Set an intent to launch inside of an activity view when displaying
11218          * this notification. The {@link PendingIntent} provided should be for an activity.
11219          *
11220          * <pre class="prettyprint">
11221          * Intent displayIntent = new Intent(context, MyDisplayActivity.class);
11222          * PendingIntent displayPendingIntent = PendingIntent.getActivity(context,
11223          *         0, displayIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
11224          * Notification notif = new Notification.Builder(context)
11225          *         .extend(new Notification.WearableExtender()
11226          *                 .setDisplayIntent(displayPendingIntent)
11227          *                 .setCustomSizePreset(Notification.WearableExtender.SIZE_MEDIUM))
11228          *         .build();</pre>
11229          *
11230          * <p>The activity to launch needs to allow embedding, must be exported, and
11231          * should have an empty task affinity. It is also recommended to use the device
11232          * default light theme.
11233          *
11234          * <p>Example AndroidManifest.xml entry:
11235          * <pre class="prettyprint">
11236          * &lt;activity android:name=&quot;com.example.MyDisplayActivity&quot;
11237          *     android:exported=&quot;true&quot;
11238          *     android:allowEmbedded=&quot;true&quot;
11239          *     android:taskAffinity=&quot;&quot;
11240          *     android:theme=&quot;@android:style/Theme.DeviceDefault.Light&quot; /&gt;</pre>
11241          *
11242          * @param intent the {@link PendingIntent} for an activity
11243          * @return this object for method chaining
11244          * @see android.app.Notification.WearableExtender#getDisplayIntent
11245          * @deprecated Display intents are no longer supported.
11246          */
11247         @Deprecated
setDisplayIntent(PendingIntent intent)11248         public WearableExtender setDisplayIntent(PendingIntent intent) {
11249             mDisplayIntent = intent;
11250             return this;
11251         }
11252 
11253         /**
11254          * Get the intent to launch inside of an activity view when displaying this
11255          * notification. This {@code PendingIntent} should be for an activity.
11256          *
11257          * @deprecated Display intents are no longer supported.
11258          */
11259         @Deprecated
getDisplayIntent()11260         public PendingIntent getDisplayIntent() {
11261             return mDisplayIntent;
11262         }
11263 
11264         /**
11265          * Add an additional page of content to display with this notification. The current
11266          * notification forms the first page, and pages added using this function form
11267          * subsequent pages. This field can be used to separate a notification into multiple
11268          * sections.
11269          *
11270          * @param page the notification to add as another page
11271          * @return this object for method chaining
11272          * @see android.app.Notification.WearableExtender#getPages
11273          * @deprecated Multiple content pages are no longer supported.
11274          */
11275         @Deprecated
addPage(Notification page)11276         public WearableExtender addPage(Notification page) {
11277             mPages.add(page);
11278             return this;
11279         }
11280 
11281         /**
11282          * Add additional pages of content to display with this notification. The current
11283          * notification forms the first page, and pages added using this function form
11284          * subsequent pages. This field can be used to separate a notification into multiple
11285          * sections.
11286          *
11287          * @param pages a list of notifications
11288          * @return this object for method chaining
11289          * @see android.app.Notification.WearableExtender#getPages
11290          * @deprecated Multiple content pages are no longer supported.
11291          */
11292         @Deprecated
addPages(List<Notification> pages)11293         public WearableExtender addPages(List<Notification> pages) {
11294             mPages.addAll(pages);
11295             return this;
11296         }
11297 
11298         /**
11299          * Clear all additional pages present on this builder.
11300          * @return this object for method chaining.
11301          * @see #addPage
11302          * @deprecated Multiple content pages are no longer supported.
11303          */
11304         @Deprecated
clearPages()11305         public WearableExtender clearPages() {
11306             mPages.clear();
11307             return this;
11308         }
11309 
11310         /**
11311          * Get the array of additional pages of content for displaying this notification. The
11312          * current notification forms the first page, and elements within this array form
11313          * subsequent pages. This field can be used to separate a notification into multiple
11314          * sections.
11315          * @return the pages for this notification
11316          * @deprecated Multiple content pages are no longer supported.
11317          */
11318         @Deprecated
getPages()11319         public List<Notification> getPages() {
11320             return mPages;
11321         }
11322 
11323         /**
11324          * Set a background image to be displayed behind the notification content.
11325          * Contrary to the {@link android.app.Notification.BigPictureStyle}, this background
11326          * will work with any notification style.
11327          *
11328          * @param background the background bitmap
11329          * @return this object for method chaining
11330          * @see android.app.Notification.WearableExtender#getBackground
11331          * @deprecated Background images are no longer supported.
11332          */
11333         @Deprecated
setBackground(Bitmap background)11334         public WearableExtender setBackground(Bitmap background) {
11335             mBackground = background;
11336             return this;
11337         }
11338 
11339         /**
11340          * Get a background image to be displayed behind the notification content.
11341          * Contrary to the {@link android.app.Notification.BigPictureStyle}, this background
11342          * will work with any notification style.
11343          *
11344          * @return the background image
11345          * @see android.app.Notification.WearableExtender#setBackground
11346          * @deprecated Background images are no longer supported.
11347          */
11348         @Deprecated
getBackground()11349         public Bitmap getBackground() {
11350             return mBackground;
11351         }
11352 
11353         /**
11354          * Set an icon that goes with the content of this notification.
11355          */
11356         @Deprecated
setContentIcon(int icon)11357         public WearableExtender setContentIcon(int icon) {
11358             mContentIcon = icon;
11359             return this;
11360         }
11361 
11362         /**
11363          * Get an icon that goes with the content of this notification.
11364          */
11365         @Deprecated
getContentIcon()11366         public int getContentIcon() {
11367             return mContentIcon;
11368         }
11369 
11370         /**
11371          * Set the gravity that the content icon should have within the notification display.
11372          * Supported values include {@link android.view.Gravity#START} and
11373          * {@link android.view.Gravity#END}. The default value is {@link android.view.Gravity#END}.
11374          * @see #setContentIcon
11375          */
11376         @Deprecated
setContentIconGravity(int contentIconGravity)11377         public WearableExtender setContentIconGravity(int contentIconGravity) {
11378             mContentIconGravity = contentIconGravity;
11379             return this;
11380         }
11381 
11382         /**
11383          * Get the gravity that the content icon should have within the notification display.
11384          * Supported values include {@link android.view.Gravity#START} and
11385          * {@link android.view.Gravity#END}. The default value is {@link android.view.Gravity#END}.
11386          * @see #getContentIcon
11387          */
11388         @Deprecated
getContentIconGravity()11389         public int getContentIconGravity() {
11390             return mContentIconGravity;
11391         }
11392 
11393         /**
11394          * Set an action from this notification's actions as the primary action. If the action has a
11395          * {@link RemoteInput} associated with it, shortcuts to the options for that input are shown
11396          * directly on the notification.
11397          *
11398          * @param actionIndex The index of the primary action.
11399          *                    If wearable actions were added to the main notification, this index
11400          *                    will apply to that list, otherwise it will apply to the regular
11401          *                    actions list.
11402          */
setContentAction(int actionIndex)11403         public WearableExtender setContentAction(int actionIndex) {
11404             mContentActionIndex = actionIndex;
11405             return this;
11406         }
11407 
11408         /**
11409          * Get the index of the notification action, if any, that was specified as the primary
11410          * action.
11411          *
11412          * <p>If wearable specific actions were added to the main notification, this index will
11413          * apply to that list, otherwise it will apply to the regular actions list.
11414          *
11415          * @return the action index or {@link #UNSET_ACTION_INDEX} if no action was selected.
11416          */
getContentAction()11417         public int getContentAction() {
11418             return mContentActionIndex;
11419         }
11420 
11421         /**
11422          * Set the gravity that this notification should have within the available viewport space.
11423          * Supported values include {@link android.view.Gravity#TOP},
11424          * {@link android.view.Gravity#CENTER_VERTICAL} and {@link android.view.Gravity#BOTTOM}.
11425          * The default value is {@link android.view.Gravity#BOTTOM}.
11426          */
11427         @Deprecated
setGravity(int gravity)11428         public WearableExtender setGravity(int gravity) {
11429             mGravity = gravity;
11430             return this;
11431         }
11432 
11433         /**
11434          * Get the gravity that this notification should have within the available viewport space.
11435          * Supported values include {@link android.view.Gravity#TOP},
11436          * {@link android.view.Gravity#CENTER_VERTICAL} and {@link android.view.Gravity#BOTTOM}.
11437          * The default value is {@link android.view.Gravity#BOTTOM}.
11438          */
11439         @Deprecated
getGravity()11440         public int getGravity() {
11441             return mGravity;
11442         }
11443 
11444         /**
11445          * Set the custom size preset for the display of this notification out of the available
11446          * presets found in {@link android.app.Notification.WearableExtender}, e.g.
11447          * {@link #SIZE_LARGE}.
11448          * <p>Some custom size presets are only applicable for custom display notifications created
11449          * using {@link android.app.Notification.WearableExtender#setDisplayIntent}. Check the
11450          * documentation for the preset in question. See also
11451          * {@link #setCustomContentHeight} and {@link #getCustomSizePreset}.
11452          */
11453         @Deprecated
setCustomSizePreset(int sizePreset)11454         public WearableExtender setCustomSizePreset(int sizePreset) {
11455             mCustomSizePreset = sizePreset;
11456             return this;
11457         }
11458 
11459         /**
11460          * Get the custom size preset for the display of this notification out of the available
11461          * presets found in {@link android.app.Notification.WearableExtender}, e.g.
11462          * {@link #SIZE_LARGE}.
11463          * <p>Some custom size presets are only applicable for custom display notifications created
11464          * using {@link #setDisplayIntent}. Check the documentation for the preset in question.
11465          * See also {@link #setCustomContentHeight} and {@link #setCustomSizePreset}.
11466          */
11467         @Deprecated
getCustomSizePreset()11468         public int getCustomSizePreset() {
11469             return mCustomSizePreset;
11470         }
11471 
11472         /**
11473          * Set the custom height in pixels for the display of this notification's content.
11474          * <p>This option is only available for custom display notifications created
11475          * using {@link android.app.Notification.WearableExtender#setDisplayIntent}. See also
11476          * {@link android.app.Notification.WearableExtender#setCustomSizePreset} and
11477          * {@link #getCustomContentHeight}.
11478          */
11479         @Deprecated
setCustomContentHeight(int height)11480         public WearableExtender setCustomContentHeight(int height) {
11481             mCustomContentHeight = height;
11482             return this;
11483         }
11484 
11485         /**
11486          * Get the custom height in pixels for the display of this notification's content.
11487          * <p>This option is only available for custom display notifications created
11488          * using {@link #setDisplayIntent}. See also {@link #setCustomSizePreset} and
11489          * {@link #setCustomContentHeight}.
11490          */
11491         @Deprecated
getCustomContentHeight()11492         public int getCustomContentHeight() {
11493             return mCustomContentHeight;
11494         }
11495 
11496         /**
11497          * Set whether the scrolling position for the contents of this notification should start
11498          * at the bottom of the contents instead of the top when the contents are too long to
11499          * display within the screen.  Default is false (start scroll at the top).
11500          */
setStartScrollBottom(boolean startScrollBottom)11501         public WearableExtender setStartScrollBottom(boolean startScrollBottom) {
11502             setFlag(FLAG_START_SCROLL_BOTTOM, startScrollBottom);
11503             return this;
11504         }
11505 
11506         /**
11507          * Get whether the scrolling position for the contents of this notification should start
11508          * at the bottom of the contents instead of the top when the contents are too long to
11509          * display within the screen. Default is false (start scroll at the top).
11510          */
getStartScrollBottom()11511         public boolean getStartScrollBottom() {
11512             return (mFlags & FLAG_START_SCROLL_BOTTOM) != 0;
11513         }
11514 
11515         /**
11516          * Set whether the content intent is available when the wearable device is not connected
11517          * to a companion device.  The user can still trigger this intent when the wearable device
11518          * is offline, but a visual hint will indicate that the content intent may not be available.
11519          * Defaults to true.
11520          */
setContentIntentAvailableOffline( boolean contentIntentAvailableOffline)11521         public WearableExtender setContentIntentAvailableOffline(
11522                 boolean contentIntentAvailableOffline) {
11523             setFlag(FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE, contentIntentAvailableOffline);
11524             return this;
11525         }
11526 
11527         /**
11528          * Get whether the content intent is available when the wearable device is not connected
11529          * to a companion device.  The user can still trigger this intent when the wearable device
11530          * is offline, but a visual hint will indicate that the content intent may not be available.
11531          * Defaults to true.
11532          */
getContentIntentAvailableOffline()11533         public boolean getContentIntentAvailableOffline() {
11534             return (mFlags & FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE) != 0;
11535         }
11536 
11537         /**
11538          * Set a hint that this notification's icon should not be displayed.
11539          * @param hintHideIcon {@code true} to hide the icon, {@code false} otherwise.
11540          * @return this object for method chaining
11541          */
11542         @Deprecated
setHintHideIcon(boolean hintHideIcon)11543         public WearableExtender setHintHideIcon(boolean hintHideIcon) {
11544             setFlag(FLAG_HINT_HIDE_ICON, hintHideIcon);
11545             return this;
11546         }
11547 
11548         /**
11549          * Get a hint that this notification's icon should not be displayed.
11550          * @return {@code true} if this icon should not be displayed, false otherwise.
11551          * The default value is {@code false} if this was never set.
11552          */
11553         @Deprecated
getHintHideIcon()11554         public boolean getHintHideIcon() {
11555             return (mFlags & FLAG_HINT_HIDE_ICON) != 0;
11556         }
11557 
11558         /**
11559          * Set a visual hint that only the background image of this notification should be
11560          * displayed, and other semantic content should be hidden. This hint is only applicable
11561          * to sub-pages added using {@link #addPage}.
11562          */
11563         @Deprecated
setHintShowBackgroundOnly(boolean hintShowBackgroundOnly)11564         public WearableExtender setHintShowBackgroundOnly(boolean hintShowBackgroundOnly) {
11565             setFlag(FLAG_HINT_SHOW_BACKGROUND_ONLY, hintShowBackgroundOnly);
11566             return this;
11567         }
11568 
11569         /**
11570          * Get a visual hint that only the background image of this notification should be
11571          * displayed, and other semantic content should be hidden. This hint is only applicable
11572          * to sub-pages added using {@link android.app.Notification.WearableExtender#addPage}.
11573          */
11574         @Deprecated
getHintShowBackgroundOnly()11575         public boolean getHintShowBackgroundOnly() {
11576             return (mFlags & FLAG_HINT_SHOW_BACKGROUND_ONLY) != 0;
11577         }
11578 
11579         /**
11580          * Set a hint that this notification's background should not be clipped if possible,
11581          * and should instead be resized to fully display on the screen, retaining the aspect
11582          * ratio of the image. This can be useful for images like barcodes or qr codes.
11583          * @param hintAvoidBackgroundClipping {@code true} to avoid clipping if possible.
11584          * @return this object for method chaining
11585          */
11586         @Deprecated
setHintAvoidBackgroundClipping( boolean hintAvoidBackgroundClipping)11587         public WearableExtender setHintAvoidBackgroundClipping(
11588                 boolean hintAvoidBackgroundClipping) {
11589             setFlag(FLAG_HINT_AVOID_BACKGROUND_CLIPPING, hintAvoidBackgroundClipping);
11590             return this;
11591         }
11592 
11593         /**
11594          * Get a hint that this notification's background should not be clipped if possible,
11595          * and should instead be resized to fully display on the screen, retaining the aspect
11596          * ratio of the image. This can be useful for images like barcodes or qr codes.
11597          * @return {@code true} if it's ok if the background is clipped on the screen, false
11598          * otherwise. The default value is {@code false} if this was never set.
11599          */
11600         @Deprecated
getHintAvoidBackgroundClipping()11601         public boolean getHintAvoidBackgroundClipping() {
11602             return (mFlags & FLAG_HINT_AVOID_BACKGROUND_CLIPPING) != 0;
11603         }
11604 
11605         /**
11606          * Set a hint that the screen should remain on for at least this duration when
11607          * this notification is displayed on the screen.
11608          * @param timeout The requested screen timeout in milliseconds. Can also be either
11609          *     {@link #SCREEN_TIMEOUT_SHORT} or {@link #SCREEN_TIMEOUT_LONG}.
11610          * @return this object for method chaining
11611          */
11612         @Deprecated
setHintScreenTimeout(int timeout)11613         public WearableExtender setHintScreenTimeout(int timeout) {
11614             mHintScreenTimeout = timeout;
11615             return this;
11616         }
11617 
11618         /**
11619          * Get the duration, in milliseconds, that the screen should remain on for
11620          * when this notification is displayed.
11621          * @return the duration in milliseconds if > 0, or either one of the sentinel values
11622          *     {@link #SCREEN_TIMEOUT_SHORT} or {@link #SCREEN_TIMEOUT_LONG}.
11623          */
11624         @Deprecated
getHintScreenTimeout()11625         public int getHintScreenTimeout() {
11626             return mHintScreenTimeout;
11627         }
11628 
11629         /**
11630          * Set a hint that this notification's {@link BigPictureStyle} (if present) should be
11631          * converted to low-bit and displayed in ambient mode, especially useful for barcodes and
11632          * qr codes, as well as other simple black-and-white tickets.
11633          * @param hintAmbientBigPicture {@code true} to enable converstion and ambient.
11634          * @return this object for method chaining
11635          * @deprecated This feature is no longer supported.
11636          */
11637         @Deprecated
setHintAmbientBigPicture(boolean hintAmbientBigPicture)11638         public WearableExtender setHintAmbientBigPicture(boolean hintAmbientBigPicture) {
11639             setFlag(FLAG_BIG_PICTURE_AMBIENT, hintAmbientBigPicture);
11640             return this;
11641         }
11642 
11643         /**
11644          * Get a hint that this notification's {@link BigPictureStyle} (if present) should be
11645          * converted to low-bit and displayed in ambient mode, especially useful for barcodes and
11646          * qr codes, as well as other simple black-and-white tickets.
11647          * @return {@code true} if it should be displayed in ambient, false otherwise
11648          * otherwise. The default value is {@code false} if this was never set.
11649          * @deprecated This feature is no longer supported.
11650          */
11651         @Deprecated
getHintAmbientBigPicture()11652         public boolean getHintAmbientBigPicture() {
11653             return (mFlags & FLAG_BIG_PICTURE_AMBIENT) != 0;
11654         }
11655 
11656         /**
11657          * Set a hint that this notification's content intent will launch an {@link Activity}
11658          * directly, telling the platform that it can generate the appropriate transitions.
11659          * @param hintContentIntentLaunchesActivity {@code true} if the content intent will launch
11660          * an activity and transitions should be generated, false otherwise.
11661          * @return this object for method chaining
11662          */
setHintContentIntentLaunchesActivity( boolean hintContentIntentLaunchesActivity)11663         public WearableExtender setHintContentIntentLaunchesActivity(
11664                 boolean hintContentIntentLaunchesActivity) {
11665             setFlag(FLAG_HINT_CONTENT_INTENT_LAUNCHES_ACTIVITY, hintContentIntentLaunchesActivity);
11666             return this;
11667         }
11668 
11669         /**
11670          * Get a hint that this notification's content intent will launch an {@link Activity}
11671          * directly, telling the platform that it can generate the appropriate transitions
11672          * @return {@code true} if the content intent will launch an activity and transitions should
11673          * be generated, false otherwise. The default value is {@code false} if this was never set.
11674          */
getHintContentIntentLaunchesActivity()11675         public boolean getHintContentIntentLaunchesActivity() {
11676             return (mFlags & FLAG_HINT_CONTENT_INTENT_LAUNCHES_ACTIVITY) != 0;
11677         }
11678 
11679         /**
11680          * Sets the dismissal id for this notification. If a notification is posted with a
11681          * dismissal id, then when that notification is canceled, notifications on other wearables
11682          * and the paired Android phone having that same dismissal id will also be canceled. See
11683          * <a href="{@docRoot}wear/notifications/index.html">Adding Wearable Features to
11684          * Notifications</a> for more information.
11685          * @param dismissalId the dismissal id of the notification.
11686          * @return this object for method chaining
11687          */
setDismissalId(String dismissalId)11688         public WearableExtender setDismissalId(String dismissalId) {
11689             mDismissalId = dismissalId;
11690             return this;
11691         }
11692 
11693         /**
11694          * Returns the dismissal id of the notification.
11695          * @return the dismissal id of the notification or null if it has not been set.
11696          */
getDismissalId()11697         public String getDismissalId() {
11698             return mDismissalId;
11699         }
11700 
11701         /**
11702          * Sets a bridge tag for this notification. A bridge tag can be set for notifications
11703          * posted from a phone to provide finer-grained control on what notifications are bridged
11704          * to wearables. See <a href="{@docRoot}wear/notifications/index.html">Adding Wearable
11705          * Features to Notifications</a> for more information.
11706          * @param bridgeTag the bridge tag of the notification.
11707          * @return this object for method chaining
11708          */
setBridgeTag(String bridgeTag)11709         public WearableExtender setBridgeTag(String bridgeTag) {
11710             mBridgeTag = bridgeTag;
11711             return this;
11712         }
11713 
11714         /**
11715          * Returns the bridge tag of the notification.
11716          * @return the bridge tag or null if not present.
11717          */
getBridgeTag()11718         public String getBridgeTag() {
11719             return mBridgeTag;
11720         }
11721 
setFlag(int mask, boolean value)11722         private void setFlag(int mask, boolean value) {
11723             if (value) {
11724                 mFlags |= mask;
11725             } else {
11726                 mFlags &= ~mask;
11727             }
11728         }
11729 
visitUris(@onNull Consumer<Uri> visitor)11730         private void visitUris(@NonNull Consumer<Uri> visitor) {
11731             for (Action action : mActions) {
11732                 action.visitUris(visitor);
11733             }
11734         }
11735     }
11736 
11737     /**
11738      * <p>Helper class to add Android Auto extensions to notifications. To create a notification
11739      * with car extensions:
11740      *
11741      * <ol>
11742      *  <li>Create an {@link Notification.Builder}, setting any desired
11743      *  properties.
11744      *  <li>Create a {@link CarExtender}.
11745      *  <li>Set car-specific properties using the {@code add} and {@code set} methods of
11746      *  {@link CarExtender}.
11747      *  <li>Call {@link Notification.Builder#extend(Notification.Extender)}
11748      *  to apply the extensions to a notification.
11749      * </ol>
11750      *
11751      * <pre class="prettyprint">
11752      * Notification notification = new Notification.Builder(context)
11753      *         ...
11754      *         .extend(new CarExtender()
11755      *                 .set*(...))
11756      *         .build();
11757      * </pre>
11758      *
11759      * <p>Car extensions can be accessed on an existing notification by using the
11760      * {@code CarExtender(Notification)} constructor, and then using the {@code get} methods
11761      * to access values.
11762      */
11763     public static final class CarExtender implements Extender {
11764         private static final String TAG = "CarExtender";
11765 
11766         private static final String EXTRA_CAR_EXTENDER = "android.car.EXTENSIONS";
11767         private static final String EXTRA_LARGE_ICON = "large_icon";
11768         private static final String EXTRA_CONVERSATION = "car_conversation";
11769         private static final String EXTRA_COLOR = "app_color";
11770 
11771         private Bitmap mLargeIcon;
11772         private UnreadConversation mUnreadConversation;
11773         private int mColor = Notification.COLOR_DEFAULT;
11774 
11775         /**
11776          * Create a {@link CarExtender} with default options.
11777          */
CarExtender()11778         public CarExtender() {
11779         }
11780 
11781         /**
11782          * Create a {@link CarExtender} from the CarExtender options of an existing Notification.
11783          *
11784          * @param notif The notification from which to copy options.
11785          */
CarExtender(Notification notif)11786         public CarExtender(Notification notif) {
11787             Bundle carBundle = notif.extras == null ?
11788                     null : notif.extras.getBundle(EXTRA_CAR_EXTENDER);
11789             if (carBundle != null) {
11790                 mLargeIcon = carBundle.getParcelable(EXTRA_LARGE_ICON, Bitmap.class);
11791                 mColor = carBundle.getInt(EXTRA_COLOR, Notification.COLOR_DEFAULT);
11792 
11793                 Bundle b = carBundle.getBundle(EXTRA_CONVERSATION);
11794                 mUnreadConversation = UnreadConversation.getUnreadConversationFromBundle(b);
11795             }
11796         }
11797 
11798         /**
11799          * Apply car extensions to a notification that is being built. This is typically called by
11800          * the {@link Notification.Builder#extend(Notification.Extender)}
11801          * method of {@link Notification.Builder}.
11802          */
11803         @Override
extend(Notification.Builder builder)11804         public Notification.Builder extend(Notification.Builder builder) {
11805             Bundle carExtensions = new Bundle();
11806 
11807             if (mLargeIcon != null) {
11808                 carExtensions.putParcelable(EXTRA_LARGE_ICON, mLargeIcon);
11809             }
11810             if (mColor != Notification.COLOR_DEFAULT) {
11811                 carExtensions.putInt(EXTRA_COLOR, mColor);
11812             }
11813 
11814             if (mUnreadConversation != null) {
11815                 Bundle b = mUnreadConversation.getBundleForUnreadConversation();
11816                 carExtensions.putBundle(EXTRA_CONVERSATION, b);
11817             }
11818 
11819             builder.getExtras().putBundle(EXTRA_CAR_EXTENDER, carExtensions);
11820             return builder;
11821         }
11822 
11823         /**
11824          * Sets the accent color to use when Android Auto presents the notification.
11825          *
11826          * Android Auto uses the color set with {@link Notification.Builder#setColor(int)}
11827          * to accent the displayed notification. However, not all colors are acceptable in an
11828          * automotive setting. This method can be used to override the color provided in the
11829          * notification in such a situation.
11830          */
setColor(@olorInt int color)11831         public CarExtender setColor(@ColorInt int color) {
11832             mColor = color;
11833             return this;
11834         }
11835 
11836         /**
11837          * Gets the accent color.
11838          *
11839          * @see #setColor
11840          */
11841         @ColorInt
getColor()11842         public int getColor() {
11843             return mColor;
11844         }
11845 
11846         /**
11847          * Sets the large icon of the car notification.
11848          *
11849          * If no large icon is set in the extender, Android Auto will display the icon
11850          * specified by {@link Notification.Builder#setLargeIcon(android.graphics.Bitmap)}
11851          *
11852          * @param largeIcon The large icon to use in the car notification.
11853          * @return This object for method chaining.
11854          */
setLargeIcon(Bitmap largeIcon)11855         public CarExtender setLargeIcon(Bitmap largeIcon) {
11856             mLargeIcon = largeIcon;
11857             return this;
11858         }
11859 
11860         /**
11861          * Gets the large icon used in this car notification, or null if no icon has been set.
11862          *
11863          * @return The large icon for the car notification.
11864          * @see CarExtender#setLargeIcon
11865          */
getLargeIcon()11866         public Bitmap getLargeIcon() {
11867             return mLargeIcon;
11868         }
11869 
11870         /**
11871          * Sets the unread conversation in a message notification.
11872          *
11873          * @param unreadConversation The unread part of the conversation this notification conveys.
11874          * @return This object for method chaining.
11875          */
setUnreadConversation(UnreadConversation unreadConversation)11876         public CarExtender setUnreadConversation(UnreadConversation unreadConversation) {
11877             mUnreadConversation = unreadConversation;
11878             return this;
11879         }
11880 
11881         /**
11882          * Returns the unread conversation conveyed by this notification.
11883          * @see #setUnreadConversation(UnreadConversation)
11884          */
getUnreadConversation()11885         public UnreadConversation getUnreadConversation() {
11886             return mUnreadConversation;
11887         }
11888 
11889         /**
11890          * A class which holds the unread messages from a conversation.
11891          */
11892         public static class UnreadConversation {
11893             private static final String KEY_AUTHOR = "author";
11894             private static final String KEY_TEXT = "text";
11895             private static final String KEY_MESSAGES = "messages";
11896             static final String KEY_REMOTE_INPUT = "remote_input";
11897             static final String KEY_ON_REPLY = "on_reply";
11898             static final String KEY_ON_READ = "on_read";
11899             private static final String KEY_PARTICIPANTS = "participants";
11900             private static final String KEY_TIMESTAMP = "timestamp";
11901 
11902             private final String[] mMessages;
11903             private final RemoteInput mRemoteInput;
11904             private final PendingIntent mReplyPendingIntent;
11905             private final PendingIntent mReadPendingIntent;
11906             private final String[] mParticipants;
11907             private final long mLatestTimestamp;
11908 
UnreadConversation(String[] messages, RemoteInput remoteInput, PendingIntent replyPendingIntent, PendingIntent readPendingIntent, String[] participants, long latestTimestamp)11909             UnreadConversation(String[] messages, RemoteInput remoteInput,
11910                     PendingIntent replyPendingIntent, PendingIntent readPendingIntent,
11911                     String[] participants, long latestTimestamp) {
11912                 mMessages = messages;
11913                 mRemoteInput = remoteInput;
11914                 mReadPendingIntent = readPendingIntent;
11915                 mReplyPendingIntent = replyPendingIntent;
11916                 mParticipants = participants;
11917                 mLatestTimestamp = latestTimestamp;
11918             }
11919 
11920             /**
11921              * Gets the list of messages conveyed by this notification.
11922              */
getMessages()11923             public String[] getMessages() {
11924                 return mMessages;
11925             }
11926 
11927             /**
11928              * Gets the remote input that will be used to convey the response to a message list, or
11929              * null if no such remote input exists.
11930              */
getRemoteInput()11931             public RemoteInput getRemoteInput() {
11932                 return mRemoteInput;
11933             }
11934 
11935             /**
11936              * Gets the pending intent that will be triggered when the user replies to this
11937              * notification.
11938              */
getReplyPendingIntent()11939             public PendingIntent getReplyPendingIntent() {
11940                 return mReplyPendingIntent;
11941             }
11942 
11943             /**
11944              * Gets the pending intent that Android Auto will send after it reads aloud all messages
11945              * in this object's message list.
11946              */
getReadPendingIntent()11947             public PendingIntent getReadPendingIntent() {
11948                 return mReadPendingIntent;
11949             }
11950 
11951             /**
11952              * Gets the participants in the conversation.
11953              */
getParticipants()11954             public String[] getParticipants() {
11955                 return mParticipants;
11956             }
11957 
11958             /**
11959              * Gets the firs participant in the conversation.
11960              */
getParticipant()11961             public String getParticipant() {
11962                 return mParticipants.length > 0 ? mParticipants[0] : null;
11963             }
11964 
11965             /**
11966              * Gets the timestamp of the conversation.
11967              */
getLatestTimestamp()11968             public long getLatestTimestamp() {
11969                 return mLatestTimestamp;
11970             }
11971 
getBundleForUnreadConversation()11972             Bundle getBundleForUnreadConversation() {
11973                 Bundle b = new Bundle();
11974                 String author = null;
11975                 if (mParticipants != null && mParticipants.length > 1) {
11976                     author = mParticipants[0];
11977                 }
11978                 Parcelable[] messages = new Parcelable[mMessages.length];
11979                 for (int i = 0; i < messages.length; i++) {
11980                     Bundle m = new Bundle();
11981                     m.putString(KEY_TEXT, mMessages[i]);
11982                     m.putString(KEY_AUTHOR, author);
11983                     messages[i] = m;
11984                 }
11985                 b.putParcelableArray(KEY_MESSAGES, messages);
11986                 if (mRemoteInput != null) {
11987                     b.putParcelable(KEY_REMOTE_INPUT, mRemoteInput);
11988                 }
11989                 b.putParcelable(KEY_ON_REPLY, mReplyPendingIntent);
11990                 b.putParcelable(KEY_ON_READ, mReadPendingIntent);
11991                 b.putStringArray(KEY_PARTICIPANTS, mParticipants);
11992                 b.putLong(KEY_TIMESTAMP, mLatestTimestamp);
11993                 return b;
11994             }
11995 
getUnreadConversationFromBundle(Bundle b)11996             static UnreadConversation getUnreadConversationFromBundle(Bundle b) {
11997                 if (b == null) {
11998                     return null;
11999                 }
12000                 Parcelable[] parcelableMessages = b.getParcelableArray(KEY_MESSAGES,
12001                         Parcelable.class);
12002                 String[] messages = null;
12003                 if (parcelableMessages != null) {
12004                     String[] tmp = new String[parcelableMessages.length];
12005                     boolean success = true;
12006                     for (int i = 0; i < tmp.length; i++) {
12007                         if (!(parcelableMessages[i] instanceof Bundle)) {
12008                             success = false;
12009                             break;
12010                         }
12011                         tmp[i] = ((Bundle) parcelableMessages[i]).getString(KEY_TEXT);
12012                         if (tmp[i] == null) {
12013                             success = false;
12014                             break;
12015                         }
12016                     }
12017                     if (success) {
12018                         messages = tmp;
12019                     } else {
12020                         return null;
12021                     }
12022                 }
12023 
12024                 PendingIntent onRead = b.getParcelable(KEY_ON_READ, PendingIntent.class);
12025                 PendingIntent onReply = b.getParcelable(KEY_ON_REPLY, PendingIntent.class);
12026 
12027                 RemoteInput remoteInput = b.getParcelable(KEY_REMOTE_INPUT, RemoteInput.class);
12028 
12029                 String[] participants = b.getStringArray(KEY_PARTICIPANTS);
12030                 if (participants == null || participants.length != 1) {
12031                     return null;
12032                 }
12033 
12034                 return new UnreadConversation(messages,
12035                         remoteInput,
12036                         onReply,
12037                         onRead,
12038                         participants, b.getLong(KEY_TIMESTAMP));
12039             }
12040         };
12041 
12042         /**
12043          * Builder class for {@link CarExtender.UnreadConversation} objects.
12044          */
12045         public static class Builder {
12046             private final List<String> mMessages = new ArrayList<String>();
12047             private final String mParticipant;
12048             private RemoteInput mRemoteInput;
12049             private PendingIntent mReadPendingIntent;
12050             private PendingIntent mReplyPendingIntent;
12051             private long mLatestTimestamp;
12052 
12053             /**
12054              * Constructs a new builder for {@link CarExtender.UnreadConversation}.
12055              *
12056              * @param name The name of the other participant in the conversation.
12057              */
Builder(String name)12058             public Builder(String name) {
12059                 mParticipant = name;
12060             }
12061 
12062             /**
12063              * Appends a new unread message to the list of messages for this conversation.
12064              *
12065              * The messages should be added from oldest to newest.
12066              *
12067              * @param message The text of the new unread message.
12068              * @return This object for method chaining.
12069              */
addMessage(String message)12070             public Builder addMessage(String message) {
12071                 mMessages.add(message);
12072                 return this;
12073             }
12074 
12075             /**
12076              * Sets the pending intent and remote input which will convey the reply to this
12077              * notification.
12078              *
12079              * @param pendingIntent The pending intent which will be triggered on a reply.
12080              * @param remoteInput The remote input parcelable which will carry the reply.
12081              * @return This object for method chaining.
12082              *
12083              * @see CarExtender.UnreadConversation#getRemoteInput
12084              * @see CarExtender.UnreadConversation#getReplyPendingIntent
12085              */
setReplyAction( PendingIntent pendingIntent, RemoteInput remoteInput)12086             public Builder setReplyAction(
12087                     PendingIntent pendingIntent, RemoteInput remoteInput) {
12088                 mRemoteInput = remoteInput;
12089                 mReplyPendingIntent = pendingIntent;
12090 
12091                 return this;
12092             }
12093 
12094             /**
12095              * Sets the pending intent that will be sent once the messages in this notification
12096              * are read.
12097              *
12098              * @param pendingIntent The pending intent to use.
12099              * @return This object for method chaining.
12100              */
setReadPendingIntent(PendingIntent pendingIntent)12101             public Builder setReadPendingIntent(PendingIntent pendingIntent) {
12102                 mReadPendingIntent = pendingIntent;
12103                 return this;
12104             }
12105 
12106             /**
12107              * Sets the timestamp of the most recent message in an unread conversation.
12108              *
12109              * If a messaging notification has been posted by your application and has not
12110              * yet been cancelled, posting a later notification with the same id and tag
12111              * but without a newer timestamp may result in Android Auto not displaying a
12112              * heads up notification for the later notification.
12113              *
12114              * @param timestamp The timestamp of the most recent message in the conversation.
12115              * @return This object for method chaining.
12116              */
setLatestTimestamp(long timestamp)12117             public Builder setLatestTimestamp(long timestamp) {
12118                 mLatestTimestamp = timestamp;
12119                 return this;
12120             }
12121 
12122             /**
12123              * Builds a new unread conversation object.
12124              *
12125              * @return The new unread conversation object.
12126              */
build()12127             public UnreadConversation build() {
12128                 String[] messages = mMessages.toArray(new String[mMessages.size()]);
12129                 String[] participants = { mParticipant };
12130                 return new UnreadConversation(messages, mRemoteInput, mReplyPendingIntent,
12131                         mReadPendingIntent, participants, mLatestTimestamp);
12132             }
12133         }
12134     }
12135 
12136     /**
12137      * <p>Helper class to add Android TV extensions to notifications. To create a notification
12138      * with a TV extension:
12139      *
12140      * <ol>
12141      *  <li>Create an {@link Notification.Builder}, setting any desired properties.
12142      *  <li>Create a {@link TvExtender}.
12143      *  <li>Set TV-specific properties using the {@code set} methods of
12144      *  {@link TvExtender}.
12145      *  <li>Call {@link Notification.Builder#extend(Notification.Extender)}
12146      *  to apply the extension to a notification.
12147      * </ol>
12148      *
12149      * <pre class="prettyprint">
12150      * Notification notification = new Notification.Builder(context)
12151      *         ...
12152      *         .extend(new TvExtender()
12153      *                 .set*(...))
12154      *         .build();
12155      * </pre>
12156      *
12157      * <p>TV extensions can be accessed on an existing notification by using the
12158      * {@code TvExtender(Notification)} constructor, and then using the {@code get} methods
12159      * to access values.
12160      *
12161      * @hide
12162      */
12163     @SystemApi
12164     public static final class TvExtender implements Extender {
12165         private static final String TAG = "TvExtender";
12166 
12167         private static final String EXTRA_TV_EXTENDER = "android.tv.EXTENSIONS";
12168         private static final String EXTRA_FLAGS = "flags";
12169         static final String EXTRA_CONTENT_INTENT = "content_intent";
12170         static final String EXTRA_DELETE_INTENT = "delete_intent";
12171         private static final String EXTRA_CHANNEL_ID = "channel_id";
12172         private static final String EXTRA_SUPPRESS_SHOW_OVER_APPS = "suppressShowOverApps";
12173 
12174         // Flags bitwise-ored to mFlags
12175         private static final int FLAG_AVAILABLE_ON_TV = 0x1;
12176 
12177         private int mFlags;
12178         private String mChannelId;
12179         private PendingIntent mContentIntent;
12180         private PendingIntent mDeleteIntent;
12181         private boolean mSuppressShowOverApps;
12182 
12183         /**
12184          * Create a {@link TvExtender} with default options.
12185          */
TvExtender()12186         public TvExtender() {
12187             mFlags = FLAG_AVAILABLE_ON_TV;
12188         }
12189 
12190         /**
12191          * Create a {@link TvExtender} from the TvExtender options of an existing Notification.
12192          *
12193          * @param notif The notification from which to copy options.
12194          */
TvExtender(Notification notif)12195         public TvExtender(Notification notif) {
12196             Bundle bundle = notif.extras == null ?
12197                 null : notif.extras.getBundle(EXTRA_TV_EXTENDER);
12198             if (bundle != null) {
12199                 mFlags = bundle.getInt(EXTRA_FLAGS);
12200                 mChannelId = bundle.getString(EXTRA_CHANNEL_ID);
12201                 mSuppressShowOverApps = bundle.getBoolean(EXTRA_SUPPRESS_SHOW_OVER_APPS);
12202                 mContentIntent = bundle.getParcelable(EXTRA_CONTENT_INTENT, PendingIntent.class);
12203                 mDeleteIntent = bundle.getParcelable(EXTRA_DELETE_INTENT, PendingIntent.class);
12204             }
12205         }
12206 
12207         /**
12208          * Apply a TV extension to a notification that is being built. This is typically called by
12209          * the {@link Notification.Builder#extend(Notification.Extender)}
12210          * method of {@link Notification.Builder}.
12211          */
12212         @Override
extend(Notification.Builder builder)12213         public Notification.Builder extend(Notification.Builder builder) {
12214             Bundle bundle = new Bundle();
12215 
12216             bundle.putInt(EXTRA_FLAGS, mFlags);
12217             bundle.putString(EXTRA_CHANNEL_ID, mChannelId);
12218             bundle.putBoolean(EXTRA_SUPPRESS_SHOW_OVER_APPS, mSuppressShowOverApps);
12219             if (mContentIntent != null) {
12220                 bundle.putParcelable(EXTRA_CONTENT_INTENT, mContentIntent);
12221             }
12222 
12223             if (mDeleteIntent != null) {
12224                 bundle.putParcelable(EXTRA_DELETE_INTENT, mDeleteIntent);
12225             }
12226 
12227             builder.getExtras().putBundle(EXTRA_TV_EXTENDER, bundle);
12228             return builder;
12229         }
12230 
12231         /**
12232          * Returns true if this notification should be shown on TV. This method return true
12233          * if the notification was extended with a TvExtender.
12234          */
isAvailableOnTv()12235         public boolean isAvailableOnTv() {
12236             return (mFlags & FLAG_AVAILABLE_ON_TV) != 0;
12237         }
12238 
12239         /**
12240          * Specifies the channel the notification should be delivered on when shown on TV.
12241          * It can be different from the channel that the notification is delivered to when
12242          * posting on a non-TV device.
12243          */
setChannel(String channelId)12244         public TvExtender setChannel(String channelId) {
12245             mChannelId = channelId;
12246             return this;
12247         }
12248 
12249         /**
12250          * Specifies the channel the notification should be delivered on when shown on TV.
12251          * It can be different from the channel that the notification is delivered to when
12252          * posting on a non-TV device.
12253          */
setChannelId(String channelId)12254         public TvExtender setChannelId(String channelId) {
12255             mChannelId = channelId;
12256             return this;
12257         }
12258 
12259         /** @removed */
12260         @Deprecated
getChannel()12261         public String getChannel() {
12262             return mChannelId;
12263         }
12264 
12265         /**
12266          * Returns the id of the channel this notification posts to on TV.
12267          */
getChannelId()12268         public String getChannelId() {
12269             return mChannelId;
12270         }
12271 
12272         /**
12273          * Supplies a {@link PendingIntent} to be sent when the notification is selected on TV.
12274          * If provided, it is used instead of the content intent specified
12275          * at the level of Notification.
12276          */
setContentIntent(PendingIntent intent)12277         public TvExtender setContentIntent(PendingIntent intent) {
12278             mContentIntent = intent;
12279             return this;
12280         }
12281 
12282         /**
12283          * Returns the TV-specific content intent.  If this method returns null, the
12284          * main content intent on the notification should be used.
12285          *
12286          * @see {@link Notification#contentIntent}
12287          */
getContentIntent()12288         public PendingIntent getContentIntent() {
12289             return mContentIntent;
12290         }
12291 
12292         /**
12293          * Supplies a {@link PendingIntent} to send when the notification is cleared explicitly
12294          * by the user on TV.  If provided, it is used instead of the delete intent specified
12295          * at the level of Notification.
12296          */
setDeleteIntent(PendingIntent intent)12297         public TvExtender setDeleteIntent(PendingIntent intent) {
12298             mDeleteIntent = intent;
12299             return this;
12300         }
12301 
12302         /**
12303          * Returns the TV-specific delete intent.  If this method returns null, the
12304          * main delete intent on the notification should be used.
12305          *
12306          * @see {@link Notification#deleteIntent}
12307          */
getDeleteIntent()12308         public PendingIntent getDeleteIntent() {
12309             return mDeleteIntent;
12310         }
12311 
12312         /**
12313          * Specifies whether this notification should suppress showing a message over top of apps
12314          * outside of the launcher.
12315          */
setSuppressShowOverApps(boolean suppress)12316         public TvExtender setSuppressShowOverApps(boolean suppress) {
12317             mSuppressShowOverApps = suppress;
12318             return this;
12319         }
12320 
12321         /**
12322          * Returns true if this notification should not show messages over top of apps
12323          * outside of the launcher.
12324          */
getSuppressShowOverApps()12325         public boolean getSuppressShowOverApps() {
12326             return mSuppressShowOverApps;
12327         }
12328     }
12329 
12330     /**
12331      * Get an array of Parcelable objects from a parcelable array bundle field.
12332      * Update the bundle to have a typed array so fetches in the future don't need
12333      * to do an array copy.
12334      */
12335     @Nullable
getParcelableArrayFromBundle( Bundle bundle, String key, Class<T> itemClass)12336     private static <T extends Parcelable> T[] getParcelableArrayFromBundle(
12337             Bundle bundle, String key, Class<T> itemClass) {
12338         final Parcelable[] array = bundle.getParcelableArray(key, Parcelable.class);
12339         final Class<?> arrayClass = Array.newInstance(itemClass, 0).getClass();
12340         if (arrayClass.isInstance(array) || array == null) {
12341             return (T[]) array;
12342         }
12343         final T[] typedArray = (T[]) Array.newInstance(itemClass, array.length);
12344         for (int i = 0; i < array.length; i++) {
12345             typedArray[i] = (T) array[i];
12346         }
12347         bundle.putParcelableArray(key, typedArray);
12348         return typedArray;
12349     }
12350 
12351     private static class BuilderRemoteViews extends RemoteViews {
BuilderRemoteViews(Parcel parcel)12352         public BuilderRemoteViews(Parcel parcel) {
12353             super(parcel);
12354         }
12355 
BuilderRemoteViews(ApplicationInfo appInfo, int layoutId)12356         public BuilderRemoteViews(ApplicationInfo appInfo, int layoutId) {
12357             super(appInfo, layoutId);
12358         }
12359 
12360         @Override
clone()12361         public BuilderRemoteViews clone() {
12362             Parcel p = Parcel.obtain();
12363             writeToParcel(p, 0);
12364             p.setDataPosition(0);
12365             BuilderRemoteViews brv = new BuilderRemoteViews(p);
12366             p.recycle();
12367             return brv;
12368         }
12369 
12370         /**
12371          * Override and return true, since {@link RemoteViews#onLoadClass(Class)} is not overridden.
12372          *
12373          * @see RemoteViews#shouldUseStaticFilter()
12374          */
12375         @Override
shouldUseStaticFilter()12376         protected boolean shouldUseStaticFilter() {
12377             return true;
12378         }
12379     }
12380 
12381     /**
12382      * A result object where information about the template that was created is saved.
12383      */
12384     private static class TemplateBindResult {
12385         boolean mRightIconVisible;
12386         float mRightIconWidthDp;
12387         float mRightIconHeightDp;
12388 
12389         /**
12390          * The margin end that needs to be added to the heading so that it won't overlap
12391          * with the large icon.  This value includes the space required to accommodate the large
12392          * icon, but should be added to the space needed to accommodate the expander. This does
12393          * not include the 16dp content margin that all notification views must have.
12394          */
12395         public final MarginSet mHeadingExtraMarginSet = new MarginSet();
12396 
12397         /**
12398          * The margin end that needs to be added to the heading so that it won't overlap
12399          * with the large icon.  This value includes the space required to accommodate the large
12400          * icon as well as the expander.  This does not include the 16dp content margin that all
12401          * notification views must have.
12402          */
12403         public final MarginSet mHeadingFullMarginSet = new MarginSet();
12404 
12405         /**
12406          * The margin end that needs to be added to the title text of the big state
12407          * so that it won't overlap with the large icon, but assuming the text can run under
12408          * the expander when that icon is not visible.
12409          */
12410         public final MarginSet mTitleMarginSet = new MarginSet();
12411 
setRightIconState(boolean visible, float widthDp, float heightDp, float marginEndDpIfVisible, float expanderSizeDp)12412         public void setRightIconState(boolean visible, float widthDp, float heightDp,
12413                 float marginEndDpIfVisible, float expanderSizeDp) {
12414             mRightIconVisible = visible;
12415             mRightIconWidthDp = widthDp;
12416             mRightIconHeightDp = heightDp;
12417             mHeadingExtraMarginSet.setValues(0, marginEndDpIfVisible);
12418             mHeadingFullMarginSet.setValues(expanderSizeDp, marginEndDpIfVisible + expanderSizeDp);
12419             mTitleMarginSet.setValues(0, marginEndDpIfVisible + expanderSizeDp);
12420         }
12421 
12422         /**
12423          * This contains the end margins for a view when the right icon is visible or not.  These
12424          * values are both needed so that NotificationGroupingUtil can 'move' the right_icon to the
12425          * left_icon and adjust the margins, and to undo that change as well.
12426          */
12427         private class MarginSet {
12428             private float mValueIfGone;
12429             private float mValueIfVisible;
12430 
setValues(float valueIfGone, float valueIfVisible)12431             public void setValues(float valueIfGone, float valueIfVisible) {
12432                 mValueIfGone = valueIfGone;
12433                 mValueIfVisible = valueIfVisible;
12434             }
12435 
applyToView(@onNull RemoteViews views, @IdRes int viewId)12436             public void applyToView(@NonNull RemoteViews views, @IdRes int viewId) {
12437                 applyToView(views, viewId, 0);
12438             }
12439 
applyToView(@onNull RemoteViews views, @IdRes int viewId, float extraMarginDp)12440             public void applyToView(@NonNull RemoteViews views, @IdRes int viewId,
12441                     float extraMarginDp) {
12442                 final float marginEndDp = getDpValue() + extraMarginDp;
12443                 if (viewId == R.id.notification_header) {
12444                     views.setFloat(R.id.notification_header,
12445                             "setTopLineExtraMarginEndDp", marginEndDp);
12446                 } else if (viewId == R.id.text || viewId == R.id.big_text) {
12447                     if (mValueIfGone != 0) {
12448                         throw new RuntimeException("Programming error: `text` and `big_text` use "
12449                                 + "ImageFloatingTextView which can either show a margin or not; "
12450                                 + "thus mValueIfGone must be 0, but it was " + mValueIfGone);
12451                     }
12452                     // Note that the caller must set "setNumIndentLines" to a positive int in order
12453                     //  for this margin to do anything at all.
12454                     views.setFloat(viewId, "setImageEndMarginDp", mValueIfVisible);
12455                     views.setBoolean(viewId, "setHasImage", mRightIconVisible);
12456                     // Apply just the *extra* margin as the view layout margin; this will be
12457                     //  unchanged depending on the visibility of the image, but it means that the
12458                     //  extra margin applies to *every* line of text instead of just indented lines.
12459                     views.setViewLayoutMargin(viewId, RemoteViews.MARGIN_END,
12460                             extraMarginDp, TypedValue.COMPLEX_UNIT_DIP);
12461                 } else {
12462                     views.setViewLayoutMargin(viewId, RemoteViews.MARGIN_END,
12463                                     marginEndDp, TypedValue.COMPLEX_UNIT_DIP);
12464                 }
12465                 if (mRightIconVisible) {
12466                     views.setIntTag(viewId, R.id.tag_margin_end_when_icon_visible,
12467                             TypedValue.createComplexDimension(
12468                                     mValueIfVisible + extraMarginDp, TypedValue.COMPLEX_UNIT_DIP));
12469                     views.setIntTag(viewId, R.id.tag_margin_end_when_icon_gone,
12470                             TypedValue.createComplexDimension(
12471                                     mValueIfGone + extraMarginDp, TypedValue.COMPLEX_UNIT_DIP));
12472                 }
12473             }
12474 
getDpValue()12475             public float getDpValue() {
12476                 return mRightIconVisible ? mValueIfVisible : mValueIfGone;
12477             }
12478         }
12479     }
12480 
12481     private static class StandardTemplateParams {
12482         /**
12483          * Notifications will be minimally decorated with ONLY an icon and expander:
12484          * <li>A large icon is never shown.
12485          * <li>A progress bar is never shown.
12486          * <li>The expanded and heads up states do not show actions, even if provided.
12487          */
12488         public static final int DECORATION_MINIMAL = 1;
12489 
12490         /**
12491          * Notifications will be partially decorated with AT LEAST an icon and expander:
12492          * <li>A large icon is shown if provided.
12493          * <li>A progress bar is shown if provided and enough space remains below the content.
12494          * <li>Actions are shown in the expanded and heads up states.
12495          */
12496         public static final int DECORATION_PARTIAL = 2;
12497 
12498         public static int VIEW_TYPE_UNSPECIFIED = 0;
12499         public static int VIEW_TYPE_NORMAL = 1;
12500         public static int VIEW_TYPE_BIG = 2;
12501         public static int VIEW_TYPE_HEADS_UP = 3;
12502         public static int VIEW_TYPE_MINIMIZED = 4;    // header only for minimized state
12503         public static int VIEW_TYPE_PUBLIC = 5;       // header only for automatic public version
12504         public static int VIEW_TYPE_GROUP_HEADER = 6; // header only for top of group
12505 
12506         int mViewType = VIEW_TYPE_UNSPECIFIED;
12507         boolean mHeaderless;
12508         boolean mHideAppName;
12509         boolean mHideTitle;
12510         boolean mHideSubText;
12511         boolean mHideTime;
12512         boolean mHideActions;
12513         boolean mHideProgress;
12514         boolean mHideSnoozeButton;
12515         boolean mHideLeftIcon;
12516         boolean mHideRightIcon;
12517         Icon mPromotedPicture;
12518         boolean mCallStyleActions;
12519         boolean mAllowTextWithProgress;
12520         int mTitleViewId;
12521         int mTextViewId;
12522         @Nullable CharSequence mTitle;
12523         @Nullable CharSequence mText;
12524         @Nullable CharSequence mHeaderTextSecondary;
12525         @Nullable CharSequence mSubText;
12526         int maxRemoteInputHistory = Style.MAX_REMOTE_INPUT_HISTORY_LINES;
12527         boolean allowColorization  = true;
12528         boolean mHighlightExpander = false;
12529 
reset()12530         final StandardTemplateParams reset() {
12531             mViewType = VIEW_TYPE_UNSPECIFIED;
12532             mHeaderless = false;
12533             mHideAppName = false;
12534             mHideTitle = false;
12535             mHideSubText = false;
12536             mHideTime = false;
12537             mHideActions = false;
12538             mHideProgress = false;
12539             mHideSnoozeButton = false;
12540             mHideLeftIcon = false;
12541             mHideRightIcon = false;
12542             mPromotedPicture = null;
12543             mCallStyleActions = false;
12544             mAllowTextWithProgress = false;
12545             mTitleViewId = R.id.title;
12546             mTextViewId = R.id.text;
12547             mTitle = null;
12548             mText = null;
12549             mSubText = null;
12550             mHeaderTextSecondary = null;
12551             maxRemoteInputHistory = Style.MAX_REMOTE_INPUT_HISTORY_LINES;
12552             allowColorization = true;
12553             mHighlightExpander = false;
12554             return this;
12555         }
12556 
hasTitle()12557         final boolean hasTitle() {
12558             return !TextUtils.isEmpty(mTitle) && !mHideTitle;
12559         }
12560 
viewType(int viewType)12561         final StandardTemplateParams viewType(int viewType) {
12562             mViewType = viewType;
12563             return this;
12564         }
12565 
headerless(boolean headerless)12566         public StandardTemplateParams headerless(boolean headerless) {
12567             mHeaderless = headerless;
12568             return this;
12569         }
12570 
hideAppName(boolean hideAppName)12571         public StandardTemplateParams hideAppName(boolean hideAppName) {
12572             mHideAppName = hideAppName;
12573             return this;
12574         }
12575 
hideSubText(boolean hideSubText)12576         public StandardTemplateParams hideSubText(boolean hideSubText) {
12577             mHideSubText = hideSubText;
12578             return this;
12579         }
12580 
hideTime(boolean hideTime)12581         public StandardTemplateParams hideTime(boolean hideTime) {
12582             mHideTime = hideTime;
12583             return this;
12584         }
12585 
hideActions(boolean hideActions)12586         final StandardTemplateParams hideActions(boolean hideActions) {
12587             this.mHideActions = hideActions;
12588             return this;
12589         }
12590 
hideProgress(boolean hideProgress)12591         final StandardTemplateParams hideProgress(boolean hideProgress) {
12592             this.mHideProgress = hideProgress;
12593             return this;
12594         }
12595 
hideTitle(boolean hideTitle)12596         final StandardTemplateParams hideTitle(boolean hideTitle) {
12597             this.mHideTitle = hideTitle;
12598             return this;
12599         }
12600 
callStyleActions(boolean callStyleActions)12601         final StandardTemplateParams callStyleActions(boolean callStyleActions) {
12602             this.mCallStyleActions = callStyleActions;
12603             return this;
12604         }
12605 
allowTextWithProgress(boolean allowTextWithProgress)12606         final StandardTemplateParams allowTextWithProgress(boolean allowTextWithProgress) {
12607             this.mAllowTextWithProgress = allowTextWithProgress;
12608             return this;
12609         }
12610 
hideSnoozeButton(boolean hideSnoozeButton)12611         final StandardTemplateParams hideSnoozeButton(boolean hideSnoozeButton) {
12612             this.mHideSnoozeButton = hideSnoozeButton;
12613             return this;
12614         }
12615 
promotedPicture(Icon promotedPicture)12616         final StandardTemplateParams promotedPicture(Icon promotedPicture) {
12617             this.mPromotedPicture = promotedPicture;
12618             return this;
12619         }
12620 
titleViewId(int titleViewId)12621         public StandardTemplateParams titleViewId(int titleViewId) {
12622             mTitleViewId = titleViewId;
12623             return this;
12624         }
12625 
textViewId(int textViewId)12626         public StandardTemplateParams textViewId(int textViewId) {
12627             mTextViewId = textViewId;
12628             return this;
12629         }
12630 
title(@ullable CharSequence title)12631         final StandardTemplateParams title(@Nullable CharSequence title) {
12632             this.mTitle = title;
12633             return this;
12634         }
12635 
text(@ullable CharSequence text)12636         final StandardTemplateParams text(@Nullable CharSequence text) {
12637             this.mText = text;
12638             return this;
12639         }
12640 
summaryText(@ullable CharSequence text)12641         final StandardTemplateParams summaryText(@Nullable CharSequence text) {
12642             this.mSubText = text;
12643             return this;
12644         }
12645 
headerTextSecondary(@ullable CharSequence text)12646         final StandardTemplateParams headerTextSecondary(@Nullable CharSequence text) {
12647             this.mHeaderTextSecondary = text;
12648             return this;
12649         }
12650 
12651 
hideLeftIcon(boolean hideLeftIcon)12652         final StandardTemplateParams hideLeftIcon(boolean hideLeftIcon) {
12653             this.mHideLeftIcon = hideLeftIcon;
12654             return this;
12655         }
12656 
hideRightIcon(boolean hideRightIcon)12657         final StandardTemplateParams hideRightIcon(boolean hideRightIcon) {
12658             this.mHideRightIcon = hideRightIcon;
12659             return this;
12660         }
12661 
disallowColorization()12662         final StandardTemplateParams disallowColorization() {
12663             this.allowColorization = false;
12664             return this;
12665         }
12666 
highlightExpander(boolean highlight)12667         final StandardTemplateParams highlightExpander(boolean highlight) {
12668             this.mHighlightExpander = highlight;
12669             return this;
12670         }
12671 
fillTextsFrom(Builder b)12672         final StandardTemplateParams fillTextsFrom(Builder b) {
12673             Bundle extras = b.mN.extras;
12674             this.mTitle = b.processLegacyText(extras.getCharSequence(EXTRA_TITLE));
12675             this.mText = b.processLegacyText(extras.getCharSequence(EXTRA_TEXT));
12676             this.mSubText = extras.getCharSequence(EXTRA_SUB_TEXT);
12677             return this;
12678         }
12679 
12680         /**
12681          * Set the maximum lines of remote input history lines allowed.
12682          * @param maxRemoteInputHistory The number of lines.
12683          * @return The builder for method chaining.
12684          */
setMaxRemoteInputHistory(int maxRemoteInputHistory)12685         public StandardTemplateParams setMaxRemoteInputHistory(int maxRemoteInputHistory) {
12686             this.maxRemoteInputHistory = maxRemoteInputHistory;
12687             return this;
12688         }
12689 
decorationType(int decorationType)12690         public StandardTemplateParams decorationType(int decorationType) {
12691             hideTitle(true);
12692             // Minimally decorated custom views do not show certain pieces of chrome that have
12693             // always been shown when using DecoratedCustomViewStyle.
12694             boolean hideOtherFields = decorationType <= DECORATION_MINIMAL;
12695             hideLeftIcon(false);  // The left icon decoration is better than showing nothing.
12696             hideRightIcon(hideOtherFields);
12697             hideProgress(hideOtherFields);
12698             hideActions(hideOtherFields);
12699             return this;
12700         }
12701     }
12702 
12703     /**
12704      * A utility which stores and calculates the palette of colors used to color notifications.
12705      * @hide
12706      */
12707     @VisibleForTesting
12708     public static class Colors {
12709         private int mPaletteIsForRawColor = COLOR_INVALID;
12710         private boolean mPaletteIsForColorized = false;
12711         private boolean mPaletteIsForNightMode = false;
12712         // The following colors are the palette
12713         private int mBackgroundColor = COLOR_INVALID;
12714         private int mProtectionColor = COLOR_INVALID;
12715         private int mPrimaryTextColor = COLOR_INVALID;
12716         private int mSecondaryTextColor = COLOR_INVALID;
12717         private int mPrimaryAccentColor = COLOR_INVALID;
12718         private int mSecondaryAccentColor = COLOR_INVALID;
12719         private int mTertiaryAccentColor = COLOR_INVALID;
12720         private int mOnAccentTextColor = COLOR_INVALID;
12721         private int mErrorColor = COLOR_INVALID;
12722         private int mContrastColor = COLOR_INVALID;
12723         private int mRippleAlpha = 0x33;
12724 
12725         /**
12726          * A utility for obtaining a TypedArray of the given DayNight-styled attributes, which
12727          * returns null when the context is a mock with no theme.
12728          *
12729          * NOTE: Calling this method is expensive, as creating a new ContextThemeWrapper
12730          * instances can allocate as much as 5MB of memory, so its important to call this method
12731          * only when necessary, getting as many attributes as possible from each call.
12732          *
12733          * @see Resources.Theme#obtainStyledAttributes(int[])
12734          */
12735         @Nullable
obtainDayNightAttributes(@onNull Context ctx, @NonNull @StyleableRes int[] attrs)12736         private static TypedArray obtainDayNightAttributes(@NonNull Context ctx,
12737                 @NonNull @StyleableRes int[] attrs) {
12738             // when testing, the mock context may have no theme
12739             if (ctx.getTheme() == null) {
12740                 return null;
12741             }
12742             Resources.Theme theme = new ContextThemeWrapper(ctx,
12743                     R.style.Theme_DeviceDefault_DayNight).getTheme();
12744             return theme.obtainStyledAttributes(attrs);
12745         }
12746 
12747         /** A null-safe wrapper of TypedArray.getColor because mocks return null */
getColor(@ullable TypedArray ta, int index, @ColorInt int defValue)12748         private static @ColorInt int getColor(@Nullable TypedArray ta, int index,
12749                 @ColorInt int defValue) {
12750             return ta == null ? defValue : ta.getColor(index, defValue);
12751         }
12752 
12753         /**
12754          * Resolve the palette.  If the inputs have not changed, this will be a no-op.
12755          * This does not handle invalidating the resolved colors when the context itself changes,
12756          * because that case does not happen in the current notification inflation pipeline; we will
12757          * recreate a new builder (and thus a new palette) when reinflating notifications for a new
12758          * theme (admittedly, we do the same for night mode, but that's easy to check).
12759          *
12760          * @param ctx the builder context.
12761          * @param rawColor the notification's color; may be COLOR_DEFAULT, but may never have alpha.
12762          * @param isColorized whether the notification is colorized.
12763          * @param nightMode whether the UI is in night mode.
12764          */
resolvePalette(Context ctx, int rawColor, boolean isColorized, boolean nightMode)12765         public void resolvePalette(Context ctx, int rawColor,
12766                 boolean isColorized, boolean nightMode) {
12767             if (mPaletteIsForRawColor == rawColor
12768                     && mPaletteIsForColorized == isColorized
12769                     && mPaletteIsForNightMode == nightMode) {
12770                 return;
12771             }
12772             mPaletteIsForRawColor = rawColor;
12773             mPaletteIsForColorized = isColorized;
12774             mPaletteIsForNightMode = nightMode;
12775 
12776             if (isColorized) {
12777                 if (rawColor == COLOR_DEFAULT) {
12778                     int[] attrs = {R.attr.colorAccentSecondary};
12779                     try (TypedArray ta = obtainDayNightAttributes(ctx, attrs)) {
12780                         mBackgroundColor = getColor(ta, 0, Color.WHITE);
12781                     }
12782                 } else {
12783                     mBackgroundColor = rawColor;
12784                 }
12785                 mPrimaryTextColor = ContrastColorUtil.findAlphaToMeetContrast(
12786                         ContrastColorUtil.resolvePrimaryColor(ctx, mBackgroundColor, nightMode),
12787                         mBackgroundColor, 4.5);
12788                 mSecondaryTextColor = ContrastColorUtil.findAlphaToMeetContrast(
12789                         ContrastColorUtil.resolveSecondaryColor(ctx, mBackgroundColor, nightMode),
12790                         mBackgroundColor, 4.5);
12791                 mContrastColor = mPrimaryTextColor;
12792                 mPrimaryAccentColor = mPrimaryTextColor;
12793                 mSecondaryAccentColor = mSecondaryTextColor;
12794                 mTertiaryAccentColor = flattenAlpha(mPrimaryTextColor, mBackgroundColor);
12795                 mOnAccentTextColor = mBackgroundColor;
12796                 mErrorColor = mPrimaryTextColor;
12797                 mRippleAlpha = 0x33;
12798             } else {
12799                 int[] attrs = {
12800                         R.attr.colorSurface,
12801                         R.attr.textColorPrimary,
12802                         R.attr.textColorSecondary,
12803                         R.attr.colorAccent,
12804                         R.attr.colorAccentSecondary,
12805                         R.attr.colorAccentTertiary,
12806                         R.attr.textColorOnAccent,
12807                         R.attr.colorError,
12808                         R.attr.colorControlHighlight
12809                 };
12810                 try (TypedArray ta = obtainDayNightAttributes(ctx, attrs)) {
12811                     mBackgroundColor = getColor(ta, 0, nightMode ? Color.BLACK : Color.WHITE);
12812                     mPrimaryTextColor = getColor(ta, 1, COLOR_INVALID);
12813                     mSecondaryTextColor = getColor(ta, 2, COLOR_INVALID);
12814                     mPrimaryAccentColor = getColor(ta, 3, COLOR_INVALID);
12815                     mSecondaryAccentColor = getColor(ta, 4, COLOR_INVALID);
12816                     mTertiaryAccentColor = getColor(ta, 5, COLOR_INVALID);
12817                     mOnAccentTextColor = getColor(ta, 6, COLOR_INVALID);
12818                     mErrorColor = getColor(ta, 7, COLOR_INVALID);
12819                     mRippleAlpha = Color.alpha(getColor(ta, 8, 0x33ffffff));
12820                 }
12821                 mContrastColor = calculateContrastColor(ctx, rawColor, mPrimaryAccentColor,
12822                         mBackgroundColor, nightMode);
12823 
12824                 // make sure every color has a valid value
12825                 if (mPrimaryTextColor == COLOR_INVALID) {
12826                     mPrimaryTextColor = ContrastColorUtil.resolvePrimaryColor(
12827                             ctx, mBackgroundColor, nightMode);
12828                 }
12829                 if (mSecondaryTextColor == COLOR_INVALID) {
12830                     mSecondaryTextColor = ContrastColorUtil.resolveSecondaryColor(
12831                             ctx, mBackgroundColor, nightMode);
12832                 }
12833                 if (mPrimaryAccentColor == COLOR_INVALID) {
12834                     mPrimaryAccentColor = mContrastColor;
12835                 }
12836                 if (mSecondaryAccentColor == COLOR_INVALID) {
12837                     mSecondaryAccentColor = mContrastColor;
12838                 }
12839                 if (mTertiaryAccentColor == COLOR_INVALID) {
12840                     mTertiaryAccentColor = mContrastColor;
12841                 }
12842                 if (mOnAccentTextColor == COLOR_INVALID) {
12843                     mOnAccentTextColor = ColorUtils.setAlphaComponent(
12844                             ContrastColorUtil.resolvePrimaryColor(
12845                                     ctx, mTertiaryAccentColor, nightMode), 0xFF);
12846                 }
12847                 if (mErrorColor == COLOR_INVALID) {
12848                     mErrorColor = mPrimaryTextColor;
12849                 }
12850             }
12851             // make sure every color has a valid value
12852             mProtectionColor = ColorUtils.blendARGB(mPrimaryTextColor, mBackgroundColor, 0.9f);
12853         }
12854 
12855         /** calculates the contrast color for the non-colorized notifications */
calculateContrastColor(Context ctx, @ColorInt int rawColor, @ColorInt int accentColor, @ColorInt int backgroundColor, boolean nightMode)12856         private static @ColorInt int calculateContrastColor(Context ctx, @ColorInt int rawColor,
12857                 @ColorInt int accentColor, @ColorInt int backgroundColor, boolean nightMode) {
12858             int color;
12859             if (rawColor == COLOR_DEFAULT) {
12860                 color = accentColor;
12861                 if (color == COLOR_INVALID) {
12862                     color = ContrastColorUtil.resolveDefaultColor(ctx, backgroundColor, nightMode);
12863                 }
12864             } else {
12865                 color = ContrastColorUtil.resolveContrastColor(ctx, rawColor, backgroundColor,
12866                         nightMode);
12867             }
12868             return flattenAlpha(color, backgroundColor);
12869         }
12870 
12871         /** remove any alpha by manually blending it with the given background. */
flattenAlpha(@olorInt int color, @ColorInt int background)12872         private static @ColorInt int flattenAlpha(@ColorInt int color, @ColorInt int background) {
12873             return Color.alpha(color) == 0xff ? color
12874                     : ContrastColorUtil.compositeColors(color, background);
12875         }
12876 
12877         /** @return the notification's background color */
getBackgroundColor()12878         public @ColorInt int getBackgroundColor() {
12879             return mBackgroundColor;
12880         }
12881 
12882         /**
12883          * @return the "surface protection" color from the theme,
12884          * or a variant of the normal background color when colorized.
12885          */
getProtectionColor()12886         public @ColorInt int getProtectionColor() {
12887             return mProtectionColor;
12888         }
12889 
12890         /** @return the color for the most prominent text */
getPrimaryTextColor()12891         public @ColorInt int getPrimaryTextColor() {
12892             return mPrimaryTextColor;
12893         }
12894 
12895         /** @return the color for less prominent text */
getSecondaryTextColor()12896         public @ColorInt int getSecondaryTextColor() {
12897             return mSecondaryTextColor;
12898         }
12899 
12900         /** @return the theme's accent color for colored UI elements. */
getPrimaryAccentColor()12901         public @ColorInt int getPrimaryAccentColor() {
12902             return mPrimaryAccentColor;
12903         }
12904 
12905         /** @return the theme's secondary accent color for colored UI elements. */
getSecondaryAccentColor()12906         public @ColorInt int getSecondaryAccentColor() {
12907             return mSecondaryAccentColor;
12908         }
12909 
12910         /** @return the theme's tertiary accent color for colored UI elements. */
getTertiaryAccentColor()12911         public @ColorInt int getTertiaryAccentColor() {
12912             return mTertiaryAccentColor;
12913         }
12914 
12915         /** @return the theme's text color to be used on the tertiary accent color. */
getOnAccentTextColor()12916         public @ColorInt int getOnAccentTextColor() {
12917             return mOnAccentTextColor;
12918         }
12919 
12920         /**
12921          * @return the contrast-adjusted version of the color provided by the app, or the
12922          * primary text color when colorized.
12923          */
getContrastColor()12924         public @ColorInt int getContrastColor() {
12925             return mContrastColor;
12926         }
12927 
12928         /** @return the theme's error color, or the primary text color when colorized. */
getErrorColor()12929         public @ColorInt int getErrorColor() {
12930             return mErrorColor;
12931         }
12932 
12933         /** @return the alpha component of the current theme's control highlight color. */
getRippleAlpha()12934         public int getRippleAlpha() {
12935             return mRippleAlpha;
12936         }
12937     }
12938 }
12939