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.Flags.evenlyDividedCallStyleActionLayout;
21 import static android.app.admin.DevicePolicyResources.Drawables.Source.NOTIFICATION;
22 import static android.app.admin.DevicePolicyResources.Drawables.Style.SOLID_COLORED;
23 import static android.app.admin.DevicePolicyResources.Drawables.WORK_PROFILE_ICON;
24 import static android.app.admin.DevicePolicyResources.UNDEFINED;
25 import static android.graphics.drawable.Icon.TYPE_URI;
26 import static android.graphics.drawable.Icon.TYPE_URI_ADAPTIVE_BITMAP;
27 
28 import static java.util.Objects.requireNonNull;
29 
30 import android.annotation.ColorInt;
31 import android.annotation.ColorRes;
32 import android.annotation.DimenRes;
33 import android.annotation.Dimension;
34 import android.annotation.DrawableRes;
35 import android.annotation.FlaggedApi;
36 import android.annotation.IdRes;
37 import android.annotation.IntDef;
38 import android.annotation.NonNull;
39 import android.annotation.Nullable;
40 import android.annotation.RequiresPermission;
41 import android.annotation.SdkConstant;
42 import android.annotation.SdkConstant.SdkConstantType;
43 import android.annotation.StringRes;
44 import android.annotation.StyleableRes;
45 import android.annotation.SuppressLint;
46 import android.annotation.SystemApi;
47 import android.annotation.TestApi;
48 import android.app.admin.DevicePolicyManager;
49 import android.app.compat.CompatChanges;
50 import android.compat.annotation.ChangeId;
51 import android.compat.annotation.EnabledSince;
52 import android.compat.annotation.UnsupportedAppUsage;
53 import android.content.Context;
54 import android.content.Intent;
55 import android.content.LocusId;
56 import android.content.pm.ApplicationInfo;
57 import android.content.pm.PackageManager;
58 import android.content.pm.PackageManager.NameNotFoundException;
59 import android.content.pm.ShortcutInfo;
60 import android.content.res.ColorStateList;
61 import android.content.res.Configuration;
62 import android.content.res.Resources;
63 import android.content.res.TypedArray;
64 import android.graphics.Bitmap;
65 import android.graphics.Canvas;
66 import android.graphics.Color;
67 import android.graphics.PorterDuff;
68 import android.graphics.drawable.Drawable;
69 import android.graphics.drawable.Icon;
70 import android.media.AudioAttributes;
71 import android.media.AudioManager;
72 import android.media.PlayerBase;
73 import android.media.session.MediaSession;
74 import android.net.Uri;
75 import android.os.BadParcelableException;
76 import android.os.Build;
77 import android.os.Bundle;
78 import android.os.IBinder;
79 import android.os.Parcel;
80 import android.os.Parcelable;
81 import android.os.SystemClock;
82 import android.os.SystemProperties;
83 import android.os.Trace;
84 import android.os.UserHandle;
85 import android.os.UserManager;
86 import android.provider.Settings;
87 import android.text.BidiFormatter;
88 import android.text.SpannableStringBuilder;
89 import android.text.Spanned;
90 import android.text.TextUtils;
91 import android.text.style.AbsoluteSizeSpan;
92 import android.text.style.CharacterStyle;
93 import android.text.style.ForegroundColorSpan;
94 import android.text.style.RelativeSizeSpan;
95 import android.text.style.StrikethroughSpan;
96 import android.text.style.StyleSpan;
97 import android.text.style.TextAppearanceSpan;
98 import android.text.style.UnderlineSpan;
99 import android.util.ArraySet;
100 import android.util.Log;
101 import android.util.Pair;
102 import android.util.SparseArray;
103 import android.util.TypedValue;
104 import android.util.proto.ProtoOutputStream;
105 import android.view.ContextThemeWrapper;
106 import android.view.Gravity;
107 import android.view.View;
108 import android.view.contentcapture.ContentCaptureContext;
109 import android.widget.ProgressBar;
110 import android.widget.RemoteViews;
111 
112 import com.android.internal.R;
113 import com.android.internal.annotations.VisibleForTesting;
114 import com.android.internal.graphics.ColorUtils;
115 import com.android.internal.util.ArrayUtils;
116 import com.android.internal.util.ContrastColorUtil;
117 import com.android.internal.util.NotificationBigTextNormalizer;
118 
119 import java.lang.annotation.Retention;
120 import java.lang.annotation.RetentionPolicy;
121 import java.lang.reflect.Array;
122 import java.lang.reflect.Constructor;
123 import java.util.ArrayList;
124 import java.util.Arrays;
125 import java.util.Collections;
126 import java.util.List;
127 import java.util.Objects;
128 import java.util.Set;
129 import java.util.function.Consumer;
130 
131 /**
132  * A class that represents how a persistent notification is to be presented to
133  * the user using the {@link android.app.NotificationManager}.
134  *
135  * <p>The {@link Notification.Builder Notification.Builder} has been added to make it
136  * easier to construct Notifications.</p>
137  *
138  * <div class="special reference">
139  * <h3>Developer Guides</h3>
140  * <p>For a guide to creating notifications, read the
141  * <a href="{@docRoot}guide/topics/ui/notifiers/notifications.html">Status Bar Notifications</a>
142  * developer guide.</p>
143  * </div>
144  */
145 public class Notification implements Parcelable
146 {
147     private static final String TAG = "Notification";
148 
149     /**
150      * @hide
151      */
152     @Retention(RetentionPolicy.SOURCE)
153     @IntDef({
154             FOREGROUND_SERVICE_DEFAULT,
155             FOREGROUND_SERVICE_IMMEDIATE,
156             FOREGROUND_SERVICE_DEFERRED
157     })
158     public @interface ServiceNotificationPolicy {};
159 
160     /**
161      * If the Notification associated with starting a foreground service has been
162      * built using setForegroundServiceBehavior() with this behavior, display of
163      * the notification will usually be suppressed for a short time to avoid visual
164      * disturbances to the user.
165      * @see Notification.Builder#setForegroundServiceBehavior(int)
166      * @see #FOREGROUND_SERVICE_IMMEDIATE
167      * @see #FOREGROUND_SERVICE_DEFERRED
168      */
169     public static final @ServiceNotificationPolicy int FOREGROUND_SERVICE_DEFAULT = 0;
170 
171     /**
172      * If the Notification associated with starting a foreground service has been
173      * built using setForegroundServiceBehavior() with this behavior, display of
174      * the notification will be immediate even if the default behavior would be
175      * to defer visibility for a short time.
176      * @see Notification.Builder#setForegroundServiceBehavior(int)
177      * @see #FOREGROUND_SERVICE_DEFAULT
178      * @see #FOREGROUND_SERVICE_DEFERRED
179      */
180     public static final @ServiceNotificationPolicy int FOREGROUND_SERVICE_IMMEDIATE = 1;
181 
182     /**
183      * If the Notification associated with starting a foreground service has been
184      * built using setForegroundServiceBehavior() with this behavior, display of
185      * the notification will usually be suppressed for a short time to avoid visual
186      * disturbances to the user.
187      * @see Notification.Builder#setForegroundServiceBehavior(int)
188      * @see #FOREGROUND_SERVICE_DEFAULT
189      * @see #FOREGROUND_SERVICE_IMMEDIATE
190      */
191     public static final @ServiceNotificationPolicy int FOREGROUND_SERVICE_DEFERRED = 2;
192 
193     @ServiceNotificationPolicy
194     private int mFgsDeferBehavior;
195 
196     /**
197      * An activity that provides a user interface for adjusting notification preferences for its
198      * containing application.
199      */
200     @SdkConstant(SdkConstantType.INTENT_CATEGORY)
201     public static final String INTENT_CATEGORY_NOTIFICATION_PREFERENCES
202             = "android.intent.category.NOTIFICATION_PREFERENCES";
203 
204     /**
205      * Optional extra for {@link #INTENT_CATEGORY_NOTIFICATION_PREFERENCES}. If provided, will
206      * contain a {@link NotificationChannel#getId() channel id} that can be used to narrow down
207      * what settings should be shown in the target app.
208      */
209     public static final String EXTRA_CHANNEL_ID = "android.intent.extra.CHANNEL_ID";
210 
211     /**
212      * Optional extra for {@link #INTENT_CATEGORY_NOTIFICATION_PREFERENCES}. If provided, will
213      * contain a {@link NotificationChannelGroup#getId() group id} that can be used to narrow down
214      * what settings should be shown in the target app.
215      */
216     public static final String EXTRA_CHANNEL_GROUP_ID = "android.intent.extra.CHANNEL_GROUP_ID";
217 
218     /**
219      * Optional extra for {@link #INTENT_CATEGORY_NOTIFICATION_PREFERENCES}. If provided, will
220      * contain the tag provided to {@link NotificationManager#notify(String, int, Notification)}
221      * that can be used to narrow down what settings should be shown in the target app.
222      */
223     public static final String EXTRA_NOTIFICATION_TAG = "android.intent.extra.NOTIFICATION_TAG";
224 
225     /**
226      * Optional extra for {@link #INTENT_CATEGORY_NOTIFICATION_PREFERENCES}. If provided, will
227      * contain the id provided to {@link NotificationManager#notify(String, int, Notification)}
228      * that can be used to narrow down what settings should be shown in the target app.
229      */
230     public static final String EXTRA_NOTIFICATION_ID = "android.intent.extra.NOTIFICATION_ID";
231 
232     /**
233      * Use all default values (where applicable).
234      */
235     public static final int DEFAULT_ALL = ~0;
236 
237     /**
238      * Use the default notification sound. This will ignore any given
239      * {@link #sound}.
240      *
241      * <p>
242      * A notification that is noisy is more likely to be presented as a heads-up notification.
243      * </p>
244      *
245      * @see #defaults
246      */
247 
248     public static final int DEFAULT_SOUND = 1;
249 
250     /**
251      * Use the default notification vibrate. This will ignore any given
252      * {@link #vibrate}. Using phone vibration requires the
253      * {@link android.Manifest.permission#VIBRATE VIBRATE} permission.
254      *
255      * <p>
256      * A notification that vibrates is more likely to be presented as a heads-up notification.
257      * </p>
258      *
259      * @see #defaults
260      */
261 
262     public static final int DEFAULT_VIBRATE = 2;
263 
264     /**
265      * Use the default notification lights. This will ignore the
266      * {@link #FLAG_SHOW_LIGHTS} bit, and {@link #ledARGB}, {@link #ledOffMS}, or
267      * {@link #ledOnMS}.
268      *
269      * @see #defaults
270      */
271 
272     public static final int DEFAULT_LIGHTS = 4;
273 
274     /**
275      * Maximum length of CharSequences accepted by Builder and friends.
276      *
277      * <p>
278      * Avoids spamming the system with overly large strings such as full e-mails.
279      */
280     private static final int MAX_CHARSEQUENCE_LENGTH = 1024;
281 
282     /**
283      * Maximum entries of reply text that are accepted by Builder and friends.
284      */
285     private static final int MAX_REPLY_HISTORY = 5;
286 
287     /**
288      * Maximum aspect ratio of the large icon. 16:9
289      */
290     private static final float MAX_LARGE_ICON_ASPECT_RATIO = 16f / 9f;
291 
292     /**
293      * Maximum number of (generic) action buttons in a notification (contextual action buttons are
294      * handled separately).
295      * @hide
296      */
297     public static final int MAX_ACTION_BUTTONS = 3;
298 
299     /**
300      * If the notification contained an unsent draft for a RemoteInput when the user clicked on it,
301      * we're adding the draft as a String extra to the {@link #contentIntent} using this key.
302      *
303      * <p>Apps may use this extra to prepopulate text fields in the app, where the user usually
304      * sends messages.</p>
305      */
306     public static final String EXTRA_REMOTE_INPUT_DRAFT = "android.remoteInputDraft";
307 
308     /**
309      * The call to WearableExtender#setBackground(Bitmap) will have no effect and the passed
310      * Bitmap will not be retained in memory.
311      */
312     @ChangeId
313     @EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
314     @VisibleForTesting
315     static final long WEARABLE_EXTENDER_BACKGROUND_BLOCKED = 270551184L;
316 
317     /**
318      * A timestamp related to this notification, in milliseconds since the epoch.
319      *
320      * Default value: {@link System#currentTimeMillis() Now}.
321      *
322      * Choose a timestamp that will be most relevant to the user. For most finite events, this
323      * corresponds to the time the event happened (or will happen, in the case of events that have
324      * yet to occur but about which the user is being informed). Indefinite events should be
325      * timestamped according to when the activity began.
326      *
327      * Some examples:
328      *
329      * <ul>
330      *   <li>Notification of a new chat message should be stamped when the message was received.</li>
331      *   <li>Notification of an ongoing file download (with a progress bar, for example) should be stamped when the download started.</li>
332      *   <li>Notification of a completed file download should be stamped when the download finished.</li>
333      *   <li>Notification of an upcoming meeting should be stamped with the time the meeting will begin (that is, in the future).</li>
334      *   <li>Notification of an ongoing stopwatch (increasing timer) should be stamped with the watch's start time.
335      *   <li>Notification of an ongoing countdown timer should be stamped with the timer's end time.
336      * </ul>
337      *
338      * For apps targeting {@link android.os.Build.VERSION_CODES#N} and above, this time is not shown
339      * anymore by default and must be opted into by using
340      * {@link android.app.Notification.Builder#setShowWhen(boolean)}
341      */
342     public long when;
343 
344     /**
345      * The creation time of the notification
346      * @hide
347      */
348     public long creationTime;
349 
350     /**
351      * The resource id of a drawable to use as the icon in the status bar.
352      *
353      * @deprecated Use {@link Builder#setSmallIcon(Icon)} instead.
354      */
355     @Deprecated
356     @DrawableRes
357     public int icon;
358 
359     /**
360      * If the icon in the status bar is to have more than one level, you can set this.  Otherwise,
361      * leave it at its default value of 0.
362      *
363      * @see android.widget.ImageView#setImageLevel
364      * @see android.graphics.drawable.Drawable#setLevel
365      */
366     public int iconLevel;
367 
368     /**
369      * The number of events that this notification represents. For example, in a new mail
370      * notification, this could be the number of unread messages.
371      *
372      * The system may or may not use this field to modify the appearance of the notification.
373      * Starting with {@link android.os.Build.VERSION_CODES#O}, the number may be displayed as a
374      * badge icon in Launchers that support badging.
375      */
376     public int number = 0;
377 
378     /**
379      * The intent to execute when the expanded status entry is clicked.  If
380      * this is an activity, it must include the
381      * {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK} flag, which requires
382      * that you take care of task management as described in the
383      * <a href="{@docRoot}guide/topics/fundamentals/tasks-and-back-stack.html">Tasks and Back
384      * Stack</a> document.  In particular, make sure to read the
385      * <a href="{@docRoot}training/notify-user/navigation">Start
386      * an Activity from a Notification</a> page for the correct ways to launch an application from a
387      * notification.
388      */
389     public PendingIntent contentIntent;
390 
391     /**
392      * The intent to execute when the notification is explicitly dismissed by the user, either with
393      * the "Clear All" button or by swiping it away individually.
394      *
395      * This probably shouldn't be launching an activity since several of those will be sent
396      * at the same time.
397      */
398     public PendingIntent deleteIntent;
399 
400     /**
401      * An intent to launch instead of posting the notification to the status bar.
402      *
403      * <p>
404      * The system UI may choose to display a heads-up notification, instead of
405      * launching this intent, while the user is using the device.
406      * </p>
407      *
408      * @see Notification.Builder#setFullScreenIntent
409      */
410     public PendingIntent fullScreenIntent;
411 
412     /**
413      * Text that summarizes this notification for accessibility services.
414      *
415      * As of the L release, this text is no longer shown on screen, but it is still useful to
416      * accessibility services (where it serves as an audible announcement of the notification's
417      * appearance).
418      *
419      * @see #tickerView
420      */
421     public CharSequence tickerText;
422 
423     /**
424      * Formerly, a view showing the {@link #tickerText}.
425      *
426      * No longer displayed in the status bar as of API 21.
427      */
428     @Deprecated
429     public RemoteViews tickerView;
430 
431     /**
432      * The view that will represent this notification in the notification list (which is pulled
433      * down from the status bar).
434      *
435      * As of N, this field may be null. The notification view is determined by the inputs
436      * to {@link Notification.Builder}; a custom RemoteViews can optionally be
437      * supplied with {@link Notification.Builder#setCustomContentView(RemoteViews)}.
438      */
439     @Deprecated
440     public RemoteViews contentView;
441 
442     /**
443      * A large-format version of {@link #contentView}, giving the Notification an
444      * opportunity to show more detail. The system UI may choose to show this
445      * instead of the normal content view at its discretion.
446      *
447      * As of N, this field may be null. The expanded notification view is determined by the
448      * inputs to {@link Notification.Builder}; a custom RemoteViews can optionally be
449      * supplied with {@link Notification.Builder#setCustomBigContentView(RemoteViews)}.
450      */
451     @Deprecated
452     public RemoteViews bigContentView;
453 
454 
455     /**
456      * A medium-format version of {@link #contentView}, providing the Notification an
457      * opportunity to add action buttons to contentView. At its discretion, the system UI may
458      * choose to show this as a heads-up notification, which will pop up so the user can see
459      * it without leaving their current activity.
460      *
461      * As of N, this field may be null. The heads-up notification view is determined by the
462      * inputs to {@link Notification.Builder}; a custom RemoteViews can optionally be
463      * supplied with {@link Notification.Builder#setCustomHeadsUpContentView(RemoteViews)}.
464      */
465     @Deprecated
466     public RemoteViews headsUpContentView;
467 
468     private boolean mUsesStandardHeader;
469 
470     private static final ArraySet<Integer> STANDARD_LAYOUTS = new ArraySet<>();
471     static {
472         STANDARD_LAYOUTS.add(R.layout.notification_template_material_base);
473         STANDARD_LAYOUTS.add(R.layout.notification_template_material_heads_up_base);
474         STANDARD_LAYOUTS.add(R.layout.notification_template_material_big_base);
475         STANDARD_LAYOUTS.add(R.layout.notification_template_material_big_picture);
476         STANDARD_LAYOUTS.add(R.layout.notification_template_material_big_text);
477         STANDARD_LAYOUTS.add(R.layout.notification_template_material_inbox);
478         STANDARD_LAYOUTS.add(R.layout.notification_template_material_messaging);
479         STANDARD_LAYOUTS.add(R.layout.notification_template_material_big_messaging);
480         STANDARD_LAYOUTS.add(R.layout.notification_template_material_conversation);
481         STANDARD_LAYOUTS.add(R.layout.notification_template_material_media);
482         STANDARD_LAYOUTS.add(R.layout.notification_template_material_big_media);
483         STANDARD_LAYOUTS.add(R.layout.notification_template_material_call);
484         STANDARD_LAYOUTS.add(R.layout.notification_template_material_big_call);
485         STANDARD_LAYOUTS.add(R.layout.notification_template_header);
486     }
487 
488     /**
489      * A large bitmap to be shown in the notification content area.
490      *
491      * @deprecated Use {@link Builder#setLargeIcon(Icon)} instead.
492      */
493     @Deprecated
494     public Bitmap largeIcon;
495 
496     /**
497      * The sound to play.
498      *
499      * <p>
500      * A notification that is noisy is more likely to be presented as a heads-up notification.
501      * </p>
502      *
503      * <p>
504      * To play the default notification sound, see {@link #defaults}.
505      * </p>
506      * @deprecated use {@link NotificationChannel#getSound()}.
507      */
508     @Deprecated
509     public Uri sound;
510 
511     /**
512      * Use this constant as the value for audioStreamType to request that
513      * the default stream type for notifications be used.  Currently the
514      * default stream type is {@link AudioManager#STREAM_NOTIFICATION}.
515      *
516      * @deprecated Use {@link NotificationChannel#getAudioAttributes()} instead.
517      */
518     @Deprecated
519     public static final int STREAM_DEFAULT = -1;
520 
521     /**
522      * The audio stream type to use when playing the sound.
523      * Should be one of the STREAM_ constants from
524      * {@link android.media.AudioManager}.
525      *
526      * @deprecated Use {@link #audioAttributes} instead.
527      */
528     @Deprecated
529     public int audioStreamType = STREAM_DEFAULT;
530 
531     /**
532      * The default value of {@link #audioAttributes}.
533      */
534     public static final AudioAttributes AUDIO_ATTRIBUTES_DEFAULT = new AudioAttributes.Builder()
535             .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
536             .setUsage(AudioAttributes.USAGE_NOTIFICATION)
537             .build();
538 
539     /**
540      * The {@link AudioAttributes audio attributes} to use when playing the sound.
541      *
542      * @deprecated use {@link NotificationChannel#getAudioAttributes()} instead.
543      */
544     @Deprecated
545     public AudioAttributes audioAttributes = AUDIO_ATTRIBUTES_DEFAULT;
546 
547     /**
548      * The pattern with which to vibrate.
549      *
550      * <p>
551      * To vibrate the default pattern, see {@link #defaults}.
552      * </p>
553      *
554      * @see android.os.Vibrator#vibrate(long[],int)
555      * @deprecated use {@link NotificationChannel#getVibrationPattern()}.
556      */
557     @Deprecated
558     public long[] vibrate;
559 
560     /**
561      * The color of the led.  The hardware will do its best approximation.
562      *
563      * @see #FLAG_SHOW_LIGHTS
564      * @see #flags
565      * @deprecated use {@link NotificationChannel#shouldShowLights()}.
566      */
567     @ColorInt
568     @Deprecated
569     public int ledARGB;
570 
571     /**
572      * The number of milliseconds for the LED to be on while it's flashing.
573      * The hardware will do its best approximation.
574      *
575      * @see #FLAG_SHOW_LIGHTS
576      * @see #flags
577      * @deprecated use {@link NotificationChannel#shouldShowLights()}.
578      */
579     @Deprecated
580     public int ledOnMS;
581 
582     /**
583      * The number of milliseconds for the LED to be off while it's flashing.
584      * The hardware will do its best approximation.
585      *
586      * @see #FLAG_SHOW_LIGHTS
587      * @see #flags
588      *
589      * @deprecated use {@link NotificationChannel#shouldShowLights()}.
590      */
591     @Deprecated
592     public int ledOffMS;
593 
594     /**
595      * Specifies which values should be taken from the defaults.
596      * <p>
597      * To set, OR the desired from {@link #DEFAULT_SOUND},
598      * {@link #DEFAULT_VIBRATE}, {@link #DEFAULT_LIGHTS}. For all default
599      * values, use {@link #DEFAULT_ALL}.
600      * </p>
601      *
602      * @deprecated use {@link NotificationChannel#getSound()} and
603      * {@link NotificationChannel#shouldShowLights()} and
604      * {@link NotificationChannel#shouldVibrate()}.
605      */
606     @Deprecated
607     public int defaults;
608 
609     /**
610      * Bit to be bitwise-ored into the {@link #flags} field that should be
611      * set if you want the LED on for this notification.
612      * <ul>
613      * <li>To turn the LED off, pass 0 in the alpha channel for colorARGB
614      *      or 0 for both ledOnMS and ledOffMS.</li>
615      * <li>To turn the LED on, pass 1 for ledOnMS and 0 for ledOffMS.</li>
616      * <li>To flash the LED, pass the number of milliseconds that it should
617      *      be on and off to ledOnMS and ledOffMS.</li>
618      * </ul>
619      * <p>
620      * Since hardware varies, you are not guaranteed that any of the values
621      * you pass are honored exactly.  Use the system defaults if possible
622      * because they will be set to values that work on any given hardware.
623      * <p>
624      * The alpha channel must be set for forward compatibility.
625      *
626      * @deprecated use {@link NotificationChannel#shouldShowLights()}.
627      */
628     @Deprecated
629     public static final int FLAG_SHOW_LIGHTS        = 0x00000001;
630 
631     /**
632      * Bit to be bitwise-ored into the {@link #flags} field that should be
633      * set if this notification is in reference to something that is ongoing,
634      * like a phone call.  It should not be set if this notification is in
635      * reference to something that happened at a particular point in time,
636      * like a missed phone call.
637      */
638     public static final int FLAG_ONGOING_EVENT      = 0x00000002;
639 
640     /**
641      * Bit to be bitwise-ored into the {@link #flags} field that if set,
642      * the audio will be repeated until the notification is
643      * cancelled or the notification window is opened.
644      */
645     public static final int FLAG_INSISTENT          = 0x00000004;
646 
647     /**
648      * Bit to be bitwise-ored into the {@link #flags} field that should be
649      * set if you would only like the sound, vibrate and ticker to be played
650      * if the notification was not already showing.
651      *
652      * Note that using this flag will stop any ongoing alerting behaviour such
653      * as sound, vibration or blinking notification LED.
654      */
655     public static final int FLAG_ONLY_ALERT_ONCE    = 0x00000008;
656 
657     /**
658      * Bit to be bitwise-ored into the {@link #flags} field that should be
659      * set if the notification should be canceled when it is clicked by the
660      * user.
661      */
662     public static final int FLAG_AUTO_CANCEL        = 0x00000010;
663 
664     /**
665      * Bit to be bitwise-ored into the {@link #flags} field that should be
666      * set if the notification should not be canceled when the user clicks
667      * the Clear all button.
668      */
669     public static final int FLAG_NO_CLEAR           = 0x00000020;
670 
671     /**
672      * Bit to be bitwise-ored into the {@link #flags} field that should be
673      * set if this notification represents a currently running service.  This
674      * will normally be set for you by {@link Service#startForeground}.
675      */
676     public static final int FLAG_FOREGROUND_SERVICE = 0x00000040;
677 
678     /**
679      * Obsolete flag indicating high-priority notifications; use the priority field instead.
680      *
681      * @deprecated Use {@link #priority} with a positive value.
682      */
683     @Deprecated
684     public static final int FLAG_HIGH_PRIORITY      = 0x00000080;
685 
686     /**
687      * Bit to be bitswise-ored into the {@link #flags} field that should be
688      * set if this notification is relevant to the current device only
689      * and it is not recommended that it bridge to other devices.
690      */
691     public static final int FLAG_LOCAL_ONLY         = 0x00000100;
692 
693     /**
694      * Bit to be bitswise-ored into the {@link #flags} field that should be
695      * set if this notification is the group summary for a group of notifications.
696      * Grouped notifications may display in a cluster or stack on devices which
697      * support such rendering. Requires a group key also be set using {@link Builder#setGroup}.
698      */
699     public static final int FLAG_GROUP_SUMMARY      = 0x00000200;
700 
701     /**
702      * Bit to be bitswise-ored into the {@link #flags} field that should be
703      * set if this notification is the group summary for an auto-group of notifications.
704      *
705      * @hide
706      */
707     @SystemApi
708     public static final int FLAG_AUTOGROUP_SUMMARY  = 0x00000400;
709 
710     /**
711      * @hide
712      */
713     public static final int FLAG_CAN_COLORIZE = 0x00000800;
714 
715     /**
716      * Bit to be bitswised-ored into the {@link #flags} field that should be
717      * set by the system if this notification is showing as a bubble.
718      *
719      * Applications cannot set this flag directly; they should instead call
720      * {@link Notification.Builder#setBubbleMetadata(BubbleMetadata)} to
721      * request that a notification be displayed as a bubble, and then check
722      * this flag to see whether that request was honored by the system.
723      */
724     public static final int FLAG_BUBBLE = 0x00001000;
725 
726     /**
727      * Bit to be bitswised-ored into the {@link #flags} field that should be
728      * set by the system if this notification is not dismissible.
729      *
730      * This flag is for internal use only; applications cannot set this flag directly.
731      * @hide
732      */
733     public static final int FLAG_NO_DISMISS = 0x00002000;
734 
735     /**
736      * Bit to be bitwise-ORed into the {@link #flags} field that should be
737      * set by the system if the app that sent this notification does not have the permission to send
738      * full screen intents.
739      *
740      * This flag is for internal use only; applications cannot set this flag directly.
741      * @hide
742      */
743     public static final int FLAG_FSI_REQUESTED_BUT_DENIED = 0x00004000;
744 
745     /**
746      * Bit to be bitwise-ored into the {@link #flags} field that should be
747      * set if this notification represents a currently running user-initiated job.
748      *
749      * This flag is for internal use only; applications cannot set this flag directly.
750      * @hide
751      */
752     @TestApi
753     public static final int FLAG_USER_INITIATED_JOB = 0x00008000;
754 
755     /**
756      * Bit to be bitwise-ored into the {@link #flags} field that should be
757      * set if this notification has been lifetime extended due to a direct reply.
758      *
759      * This flag is for internal use only; applications cannot set this flag directly.
760      * @hide
761      */
762     @FlaggedApi(Flags.FLAG_LIFETIME_EXTENSION_REFACTOR)
763     public static final int FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY = 0x00010000;
764 
765     private static final List<Class<? extends Style>> PLATFORM_STYLE_CLASSES = Arrays.asList(
766             BigTextStyle.class, BigPictureStyle.class, InboxStyle.class, MediaStyle.class,
767             DecoratedCustomViewStyle.class, DecoratedMediaCustomViewStyle.class,
768             MessagingStyle.class, CallStyle.class);
769 
770     /** @hide */
771     @IntDef(flag = true, prefix = {"FLAG_"}, value = {
772             FLAG_SHOW_LIGHTS,
773             FLAG_ONGOING_EVENT,
774             FLAG_INSISTENT,
775             FLAG_ONLY_ALERT_ONCE,
776             FLAG_AUTO_CANCEL,
777             FLAG_NO_CLEAR,
778             FLAG_FOREGROUND_SERVICE,
779             FLAG_HIGH_PRIORITY,
780             FLAG_LOCAL_ONLY,
781             FLAG_GROUP_SUMMARY,
782             FLAG_AUTOGROUP_SUMMARY,
783             FLAG_CAN_COLORIZE,
784             FLAG_BUBBLE,
785             FLAG_NO_DISMISS,
786             FLAG_FSI_REQUESTED_BUT_DENIED,
787             FLAG_USER_INITIATED_JOB
788     })
789     @Retention(RetentionPolicy.SOURCE)
790     public @interface NotificationFlags{};
791 
792     public int flags;
793 
794     /** @hide */
795     @IntDef(prefix = { "PRIORITY_" }, value = {
796             PRIORITY_DEFAULT,
797             PRIORITY_LOW,
798             PRIORITY_MIN,
799             PRIORITY_HIGH,
800             PRIORITY_MAX
801     })
802     @Retention(RetentionPolicy.SOURCE)
803     public @interface Priority {}
804 
805     /**
806      * Default notification {@link #priority}. If your application does not prioritize its own
807      * notifications, use this value for all notifications.
808      *
809      * @deprecated use {@link NotificationManager#IMPORTANCE_DEFAULT} instead.
810      */
811     @Deprecated
812     public static final int PRIORITY_DEFAULT = 0;
813 
814     /**
815      * Lower {@link #priority}, for items that are less important. The UI may choose to show these
816      * items smaller, or at a different position in the list, compared with your app's
817      * {@link #PRIORITY_DEFAULT} items.
818      *
819      * @deprecated use {@link NotificationManager#IMPORTANCE_LOW} instead.
820      */
821     @Deprecated
822     public static final int PRIORITY_LOW = -1;
823 
824     /**
825      * Lowest {@link #priority}; these items might not be shown to the user except under special
826      * circumstances, such as detailed notification logs.
827      *
828      * @deprecated use {@link NotificationManager#IMPORTANCE_MIN} instead.
829      */
830     @Deprecated
831     public static final int PRIORITY_MIN = -2;
832 
833     /**
834      * Higher {@link #priority}, for more important notifications or alerts. The UI may choose to
835      * show these items larger, or at a different position in notification lists, compared with
836      * your app's {@link #PRIORITY_DEFAULT} items.
837      *
838      * @deprecated use {@link NotificationManager#IMPORTANCE_HIGH} instead.
839      */
840     @Deprecated
841     public static final int PRIORITY_HIGH = 1;
842 
843     /**
844      * Highest {@link #priority}, for your application's most important items that require the
845      * user's prompt attention or input.
846      *
847      * @deprecated use {@link NotificationManager#IMPORTANCE_HIGH} instead.
848      */
849     @Deprecated
850     public static final int PRIORITY_MAX = 2;
851 
852     /**
853      * Relative priority for this notification.
854      *
855      * Priority is an indication of how much of the user's valuable attention should be consumed by
856      * this notification. Low-priority notifications may be hidden from the user in certain
857      * situations, while the user might be interrupted for a higher-priority notification. The
858      * system will make a determination about how to interpret this priority when presenting
859      * the notification.
860      *
861      * <p>
862      * A notification that is at least {@link #PRIORITY_HIGH} is more likely to be presented
863      * as a heads-up notification.
864      * </p>
865      *
866      * @deprecated use {@link NotificationChannel#getImportance()} instead.
867      */
868     @Priority
869     @Deprecated
870     public int priority;
871 
872     /**
873      * Accent color (an ARGB integer like the constants in {@link android.graphics.Color})
874      * to be applied by the standard Style templates when presenting this notification.
875      *
876      * The current template design constructs a colorful header image by overlaying the
877      * {@link #icon} image (stenciled in white) atop a field of this color. Alpha components are
878      * ignored.
879      */
880     @ColorInt
881     public int color = COLOR_DEFAULT;
882 
883     /**
884      * Special value of {@link #color} telling the system not to decorate this notification with
885      * any special color but instead use default colors when presenting this notification.
886      */
887     @ColorInt
888     public static final int COLOR_DEFAULT = 0; // AKA Color.TRANSPARENT
889 
890     /**
891      * Special value of {@link #color} used as a place holder for an invalid color.
892      * @hide
893      */
894     @ColorInt
895     public static final int COLOR_INVALID = 1;
896 
897     /**
898      * Sphere of visibility of this notification, which affects how and when the SystemUI reveals
899      * the notification's presence and contents in untrusted situations (namely, on the secure
900      * lockscreen and during screen sharing).
901      *
902      * The default level, {@link #VISIBILITY_PRIVATE}, behaves exactly as notifications have always
903      * done on Android: The notification's {@link #icon} and {@link #tickerText} (if available) are
904      * shown in all situations, but the contents are only available if the device is unlocked for
905      * the appropriate user and there is no active screen sharing session.
906      *
907      * A more permissive policy can be expressed by {@link #VISIBILITY_PUBLIC}; such a notification
908      * can be read even in an "insecure" context (that is, above a secure lockscreen or while
909      * screen sharing with a remote viewer).
910      * To modify the public version of this notification—for example, to redact some portions—see
911      * {@link Builder#setPublicVersion(Notification)}.
912      *
913      * Finally, a notification can be made {@link #VISIBILITY_SECRET}, which will suppress its icon
914      * and ticker until the user has bypassed the lockscreen.
915      */
916     public @Visibility int visibility;
917 
918     /** @hide */
919     @IntDef(prefix = { "VISIBILITY_" }, value = {
920             VISIBILITY_PUBLIC,
921             VISIBILITY_PRIVATE,
922             VISIBILITY_SECRET,
923     })
924     @Retention(RetentionPolicy.SOURCE)
925     public @interface Visibility {}
926 
927     /**
928      * Notification visibility: Show this notification in its entirety on all lockscreens and while
929      * screen sharing.
930      *
931      * {@see #visibility}
932      */
933     public static final int VISIBILITY_PUBLIC = 1;
934 
935     /**
936      * Notification visibility: Show this notification on all lockscreens, but conceal sensitive or
937      * private information on secure lockscreens. Conceal sensitive or private information while
938      * screen sharing.
939      *
940      * {@see #visibility}
941      */
942     public static final int VISIBILITY_PRIVATE = 0;
943 
944     /**
945      * Notification visibility: Do not reveal any part of this notification on a secure lockscreen
946      * or while screen sharing.
947      *
948      * {@see #visibility}
949      */
950     public static final int VISIBILITY_SECRET = -1;
951 
952     /**
953      * @hide
954      */
955     @IntDef(prefix = "VISIBILITY_", value = {
956             VISIBILITY_PUBLIC,
957             VISIBILITY_PRIVATE,
958             VISIBILITY_SECRET,
959             NotificationManager.VISIBILITY_NO_OVERRIDE
960     })
961     @Retention(RetentionPolicy.SOURCE)
962     public @interface NotificationVisibilityOverride{};
963 
964     /**
965      * Notification category: incoming call (voice or video) or similar synchronous communication request.
966      */
967     public static final String CATEGORY_CALL = "call";
968 
969     /**
970      * Notification category: map turn-by-turn navigation.
971      */
972     public static final String CATEGORY_NAVIGATION = "navigation";
973 
974     /**
975      * Notification category: incoming direct message (SMS, instant message, etc.).
976      */
977     public static final String CATEGORY_MESSAGE = "msg";
978 
979     /**
980      * Notification category: asynchronous bulk message (email).
981      */
982     public static final String CATEGORY_EMAIL = "email";
983 
984     /**
985      * Notification category: calendar event.
986      */
987     public static final String CATEGORY_EVENT = "event";
988 
989     /**
990      * Notification category: promotion or advertisement.
991      */
992     public static final String CATEGORY_PROMO = "promo";
993 
994     /**
995      * Notification category: alarm or timer.
996      */
997     public static final String CATEGORY_ALARM = "alarm";
998 
999     /**
1000      * Notification category: progress of a long-running background operation.
1001      */
1002     public static final String CATEGORY_PROGRESS = "progress";
1003 
1004     /**
1005      * Notification category: social network or sharing update.
1006      */
1007     public static final String CATEGORY_SOCIAL = "social";
1008 
1009     /**
1010      * Notification category: error in background operation or authentication status.
1011      */
1012     public static final String CATEGORY_ERROR = "err";
1013 
1014     /**
1015      * Notification category: media transport control for playback.
1016      */
1017     public static final String CATEGORY_TRANSPORT = "transport";
1018 
1019     /**
1020      * Notification category: system or device status update.  Reserved for system use.
1021      */
1022     public static final String CATEGORY_SYSTEM = "sys";
1023 
1024     /**
1025      * Notification category: indication of running background service.
1026      */
1027     public static final String CATEGORY_SERVICE = "service";
1028 
1029     /**
1030      * Notification category: a specific, timely recommendation for a single thing.
1031      * For example, a news app might want to recommend a news story it believes the user will
1032      * want to read next.
1033      */
1034     public static final String CATEGORY_RECOMMENDATION = "recommendation";
1035 
1036     /**
1037      * Notification category: ongoing information about device or contextual status.
1038      */
1039     public static final String CATEGORY_STATUS = "status";
1040 
1041     /**
1042      * Notification category: user-scheduled reminder.
1043      */
1044     public static final String CATEGORY_REMINDER = "reminder";
1045 
1046     /**
1047      * Notification category: extreme car emergencies.
1048      * @hide
1049      */
1050     @SystemApi
1051     public static final String CATEGORY_CAR_EMERGENCY = "car_emergency";
1052 
1053     /**
1054      * Notification category: car warnings.
1055      * @hide
1056      */
1057     @SystemApi
1058     public static final String CATEGORY_CAR_WARNING = "car_warning";
1059 
1060     /**
1061      * Notification category: general car system information.
1062      * @hide
1063      */
1064     @SystemApi
1065     public static final String CATEGORY_CAR_INFORMATION = "car_information";
1066 
1067     /**
1068      * Notification category: tracking a user's workout.
1069      */
1070     public static final String CATEGORY_WORKOUT = "workout";
1071 
1072     /**
1073      * Notification category: temporarily sharing location.
1074      */
1075     public static final String CATEGORY_LOCATION_SHARING = "location_sharing";
1076 
1077     /**
1078      * Notification category: running stopwatch.
1079      */
1080     public static final String CATEGORY_STOPWATCH = "stopwatch";
1081 
1082     /**
1083      * Notification category: missed call.
1084      */
1085     public static final String CATEGORY_MISSED_CALL = "missed_call";
1086 
1087     /**
1088      * Notification category: voicemail.
1089      */
1090     @FlaggedApi(Flags.FLAG_CATEGORY_VOICEMAIL)
1091     public static final String CATEGORY_VOICEMAIL = "voicemail";
1092 
1093     /**
1094      * One of the predefined notification categories (see the <code>CATEGORY_*</code> constants)
1095      * that best describes this Notification.  May be used by the system for ranking and filtering.
1096      */
1097     public String category;
1098 
1099     @UnsupportedAppUsage
1100     private String mGroupKey;
1101 
1102     /**
1103      * Get the key used to group this notification into a cluster or stack
1104      * with other notifications on devices which support such rendering.
1105      */
getGroup()1106     public String getGroup() {
1107         return mGroupKey;
1108     }
1109 
1110     private String mSortKey;
1111 
1112     /**
1113      * Get a sort key that orders this notification among other notifications from the
1114      * same package. This can be useful if an external sort was already applied and an app
1115      * would like to preserve this. Notifications will be sorted lexicographically using this
1116      * value, although providing different priorities in addition to providing sort key may
1117      * cause this value to be ignored.
1118      *
1119      * <p>This sort key can also be used to order members of a notification group. See
1120      * {@link Builder#setGroup}.
1121      *
1122      * @see String#compareTo(String)
1123      */
getSortKey()1124     public String getSortKey() {
1125         return mSortKey;
1126     }
1127 
1128     /**
1129      * Additional semantic data to be carried around with this Notification.
1130      * <p>
1131      * The extras keys defined here are intended to capture the original inputs to {@link Builder}
1132      * APIs, and are intended to be used by
1133      * {@link android.service.notification.NotificationListenerService} implementations to extract
1134      * detailed information from notification objects.
1135      */
1136     public Bundle extras = new Bundle();
1137 
1138     /**
1139      * All pending intents in the notification as the system needs to be able to access them but
1140      * touching the extras bundle in the system process is not safe because the bundle may contain
1141      * custom parcelable objects.
1142      *
1143      * @hide
1144      */
1145     @UnsupportedAppUsage
1146     public ArraySet<PendingIntent> allPendingIntents;
1147 
1148     /**
1149      * Token identifying the notification that is applying doze/bgcheck allowlisting to the
1150      * pending intents inside of it, so only those will get the behavior.
1151      *
1152      * @hide
1153      */
1154     private IBinder mAllowlistToken;
1155 
1156     /**
1157      * Must be set by a process to start associating tokens with Notification objects
1158      * coming in to it.  This is set by NotificationManagerService.
1159      *
1160      * @hide
1161      */
1162     static public IBinder processAllowlistToken;
1163 
1164     /**
1165      * {@link #extras} key: this is the title of the notification,
1166      * as supplied to {@link Builder#setContentTitle(CharSequence)}.
1167      */
1168     public static final String EXTRA_TITLE = "android.title";
1169 
1170     /**
1171      * {@link #extras} key: this is the title of the notification when shown in expanded form,
1172      * e.g. as supplied to {@link BigTextStyle#setBigContentTitle(CharSequence)}.
1173      */
1174     public static final String EXTRA_TITLE_BIG = EXTRA_TITLE + ".big";
1175 
1176     /**
1177      * {@link #extras} key: this is the main text payload, as supplied to
1178      * {@link Builder#setContentText(CharSequence)}.
1179      */
1180     public static final String EXTRA_TEXT = "android.text";
1181 
1182     /**
1183      * {@link #extras} key: this is a third line of text, as supplied to
1184      * {@link Builder#setSubText(CharSequence)}.
1185      */
1186     public static final String EXTRA_SUB_TEXT = "android.subText";
1187 
1188     /**
1189      * {@link #extras} key: this is the remote input history, as supplied to
1190      * {@link Builder#setRemoteInputHistory(CharSequence[])}.
1191      *
1192      * Apps can fill this through {@link Builder#setRemoteInputHistory(CharSequence[])}
1193      * with the most recent inputs that have been sent through a {@link RemoteInput} of this
1194      * Notification and are expected to clear it once the it is no longer relevant (e.g. for chat
1195      * notifications once the other party has responded).
1196      *
1197      * The extra with this key is of type CharSequence[] and contains the most recent entry at
1198      * the 0 index, the second most recent at the 1 index, etc.
1199      *
1200      * @see Builder#setRemoteInputHistory(CharSequence[])
1201      */
1202     public static final String EXTRA_REMOTE_INPUT_HISTORY = "android.remoteInputHistory";
1203 
1204 
1205     /**
1206      * {@link #extras} key: this is a remote input history which can include media messages
1207      * in addition to text, as supplied to
1208      * {@link Builder#setRemoteInputHistory(RemoteInputHistoryItem[])} or
1209      * {@link Builder#setRemoteInputHistory(CharSequence[])}.
1210      *
1211      * SystemUI can populate this through
1212      * {@link Builder#setRemoteInputHistory(RemoteInputHistoryItem[])} with the most recent inputs
1213      * that have been sent through a {@link RemoteInput} of this Notification. These items can
1214      * represent either media content (specified by a URI and a MIME type) or a text message
1215      * (described by a CharSequence).
1216      *
1217      * To maintain compatibility, this can also be set by apps with
1218      * {@link Builder#setRemoteInputHistory(CharSequence[])}, which will create a
1219      * {@link RemoteInputHistoryItem} for each of the provided text-only messages.
1220      *
1221      * The extra with this key is of type {@link RemoteInputHistoryItem[]} and contains the most
1222      * recent entry at the 0 index, the second most recent at the 1 index, etc.
1223      *
1224      * @see Builder#setRemoteInputHistory(RemoteInputHistoryItem[])
1225      * @hide
1226      */
1227     public static final String EXTRA_REMOTE_INPUT_HISTORY_ITEMS = "android.remoteInputHistoryItems";
1228 
1229     /**
1230      * {@link #extras} key: boolean as supplied to
1231      * {@link Builder#setShowRemoteInputSpinner(boolean)}.
1232      *
1233      * If set to true, then the view displaying the remote input history from
1234      * {@link Builder#setRemoteInputHistory(CharSequence[])} will have a progress spinner.
1235      *
1236      * @see Builder#setShowRemoteInputSpinner(boolean)
1237      * @hide
1238      */
1239     public static final String EXTRA_SHOW_REMOTE_INPUT_SPINNER = "android.remoteInputSpinner";
1240 
1241     /**
1242      * {@link #extras} key: boolean as supplied to
1243      * {@link Builder#setHideSmartReplies(boolean)}.
1244      *
1245      * If set to true, then any smart reply buttons will be hidden.
1246      *
1247      * @see Builder#setHideSmartReplies(boolean)
1248      * @hide
1249      */
1250     public static final String EXTRA_HIDE_SMART_REPLIES = "android.hideSmartReplies";
1251 
1252     /**
1253      * {@link #extras} key: this is a small piece of additional text as supplied to
1254      * {@link Builder#setContentInfo(CharSequence)}.
1255      */
1256     public static final String EXTRA_INFO_TEXT = "android.infoText";
1257 
1258     /**
1259      * {@link #extras} key: this is a line of summary information intended to be shown
1260      * alongside expanded notifications, as supplied to (e.g.)
1261      * {@link BigTextStyle#setSummaryText(CharSequence)}.
1262      */
1263     public static final String EXTRA_SUMMARY_TEXT = "android.summaryText";
1264 
1265     /**
1266      * {@link #extras} key: this is the longer text shown in the big form of a
1267      * {@link BigTextStyle} notification, as supplied to
1268      * {@link BigTextStyle#bigText(CharSequence)}.
1269      */
1270     public static final String EXTRA_BIG_TEXT = "android.bigText";
1271 
1272     /**
1273      * {@link #extras} key: this is the resource ID of the notification's main small icon, as
1274      * supplied to {@link Builder#setSmallIcon(int)}.
1275      *
1276      * @deprecated Use {@link #getSmallIcon()}, which supports a wider variety of icon sources.
1277      */
1278     @Deprecated
1279     public static final String EXTRA_SMALL_ICON = "android.icon";
1280 
1281     /**
1282      * {@link #extras} key: this is a bitmap to be used instead of the small icon when showing the
1283      * notification payload, as
1284      * supplied to {@link Builder#setLargeIcon(android.graphics.Bitmap)}.
1285      *
1286      * @deprecated Use {@link #getLargeIcon()}, which supports a wider variety of icon sources.
1287      */
1288     @Deprecated
1289     public static final String EXTRA_LARGE_ICON = "android.largeIcon";
1290 
1291     /**
1292      * {@link #extras} key: this is a bitmap to be used instead of the one from
1293      * {@link Builder#setLargeIcon(android.graphics.Bitmap)} when the notification is
1294      * shown in its expanded form, as supplied to
1295      * {@link BigPictureStyle#bigLargeIcon(android.graphics.Bitmap)}.
1296      */
1297     public static final String EXTRA_LARGE_ICON_BIG = EXTRA_LARGE_ICON + ".big";
1298 
1299     /**
1300      * {@link #extras} key: this is the progress value supplied to
1301      * {@link Builder#setProgress(int, int, boolean)}.
1302      */
1303     public static final String EXTRA_PROGRESS = "android.progress";
1304 
1305     /**
1306      * {@link #extras} key: this is the maximum value supplied to
1307      * {@link Builder#setProgress(int, int, boolean)}.
1308      */
1309     public static final String EXTRA_PROGRESS_MAX = "android.progressMax";
1310 
1311     /**
1312      * {@link #extras} key: whether the progress bar is indeterminate, supplied to
1313      * {@link Builder#setProgress(int, int, boolean)}.
1314      */
1315     public static final String EXTRA_PROGRESS_INDETERMINATE = "android.progressIndeterminate";
1316 
1317     /**
1318      * {@link #extras} key: whether {@link #when} should be shown as a count-up timer (specifically
1319      * a {@link android.widget.Chronometer}) instead of a timestamp, as supplied to
1320      * {@link Builder#setUsesChronometer(boolean)}.
1321      */
1322     public static final String EXTRA_SHOW_CHRONOMETER = "android.showChronometer";
1323 
1324     /**
1325      * {@link #extras} key: whether the chronometer set on the notification should count down
1326      * instead of counting up. Is only relevant if key {@link #EXTRA_SHOW_CHRONOMETER} is present.
1327      * This extra is a boolean. The default is false.
1328      */
1329     public static final String EXTRA_CHRONOMETER_COUNT_DOWN = "android.chronometerCountDown";
1330 
1331     /**
1332      * {@link #extras} key: whether {@link #when} should be shown,
1333      * as supplied to {@link Builder#setShowWhen(boolean)}.
1334      */
1335     public static final String EXTRA_SHOW_WHEN = "android.showWhen";
1336 
1337     /**
1338      * {@link #extras} key: this is a bitmap to be shown in {@link BigPictureStyle} expanded
1339      * notifications, supplied to {@link BigPictureStyle#bigPicture(android.graphics.Bitmap)}.
1340      */
1341     public static final String EXTRA_PICTURE = "android.picture";
1342 
1343     /**
1344      * {@link #extras} key: this is an {@link Icon} of an image to be
1345      * shown in {@link BigPictureStyle} expanded notifications, supplied to
1346      * {@link BigPictureStyle#bigPicture(Icon)}.
1347      */
1348     public static final String EXTRA_PICTURE_ICON = "android.pictureIcon";
1349 
1350     /**
1351      * {@link #extras} key: this is a content description of the big picture supplied from
1352      * {@link BigPictureStyle#bigPicture(Bitmap)}, supplied to
1353      * {@link BigPictureStyle#setContentDescription(CharSequence)}.
1354      */
1355     public static final String EXTRA_PICTURE_CONTENT_DESCRIPTION =
1356             "android.pictureContentDescription";
1357 
1358     /**
1359      * {@link #extras} key: this is a boolean to indicate that the
1360      * {@link BigPictureStyle#bigPicture(Bitmap) big picture} is to be shown in the collapsed state
1361      * of a {@link BigPictureStyle} notification.  This will replace a
1362      * {@link Builder#setLargeIcon(Icon) large icon} in that state if one was provided.
1363      */
1364     public static final String EXTRA_SHOW_BIG_PICTURE_WHEN_COLLAPSED =
1365             "android.showBigPictureWhenCollapsed";
1366 
1367     /**
1368      * {@link #extras} key: An array of CharSequences to show in {@link InboxStyle} expanded
1369      * notifications, each of which was supplied to {@link InboxStyle#addLine(CharSequence)}.
1370      */
1371     public static final String EXTRA_TEXT_LINES = "android.textLines";
1372 
1373     /**
1374      * {@link #extras} key: A string representing the name of the specific
1375      * {@link android.app.Notification.Style} used to create this notification.
1376      */
1377     public static final String EXTRA_TEMPLATE = "android.template";
1378 
1379     /**
1380      * {@link #extras} key: A String array containing the people that this notification relates to,
1381      * each of which was supplied to {@link Builder#addPerson(String)}.
1382      *
1383      * @deprecated the actual objects are now in {@link #EXTRA_PEOPLE_LIST}
1384      */
1385     public static final String EXTRA_PEOPLE = "android.people";
1386 
1387     /**
1388      * {@link #extras} key: An arrayList of {@link Person} objects containing the people that
1389      * this notification relates to.
1390      */
1391     public static final String EXTRA_PEOPLE_LIST = "android.people.list";
1392 
1393     /**
1394      * Allow certain system-generated notifications to appear before the device is provisioned.
1395      * Only available to notifications coming from the android package.
1396      * @hide
1397      */
1398     @SystemApi
1399     @RequiresPermission(android.Manifest.permission.NOTIFICATION_DURING_SETUP)
1400     public static final String EXTRA_ALLOW_DURING_SETUP = "android.allowDuringSetup";
1401 
1402     /**
1403      * {@link #extras} key:
1404      * flat {@link String} representation of a {@link android.content.ContentUris content URI}
1405      * pointing to an image that can be displayed in the background when the notification is
1406      * selected. Used on television platforms. The URI must point to an image stream suitable for
1407      * passing into {@link android.graphics.BitmapFactory#decodeStream(java.io.InputStream)
1408      * BitmapFactory.decodeStream}; all other content types will be ignored.
1409      */
1410     public static final String EXTRA_BACKGROUND_IMAGE_URI = "android.backgroundImageUri";
1411 
1412     /**
1413      * {@link #extras} key: A
1414      * {@link android.media.session.MediaSession.Token} associated with a
1415      * {@link android.app.Notification.MediaStyle} notification.
1416      */
1417     public static final String EXTRA_MEDIA_SESSION = "android.mediaSession";
1418 
1419     /**
1420      * {@link #extras} key: A {@code CharSequence} name of a remote device used for a media session
1421      * associated with a {@link Notification.MediaStyle} notification. This will show in the media
1422      * controls output switcher instead of the local device name.
1423      * @hide
1424      */
1425     @TestApi
1426     public static final String EXTRA_MEDIA_REMOTE_DEVICE = "android.mediaRemoteDevice";
1427 
1428     /**
1429      * {@link #extras} key: A {@code int} resource ID for an icon that should show in the output
1430      * switcher of the media controls for a {@link Notification.MediaStyle} notification.
1431      * @hide
1432      */
1433     @TestApi
1434     public static final String EXTRA_MEDIA_REMOTE_ICON = "android.mediaRemoteIcon";
1435 
1436     /**
1437      * {@link #extras} key: A {@code PendingIntent} that will replace the default action for the
1438      * media controls output switcher chip, associated with a {@link Notification.MediaStyle}
1439      * notification. This should launch an activity.
1440      * @hide
1441      */
1442     @TestApi
1443     public static final String EXTRA_MEDIA_REMOTE_INTENT = "android.mediaRemoteIntent";
1444 
1445     /**
1446      * {@link #extras} key: the indices of actions to be shown in the compact view,
1447      * as supplied to (e.g.) {@link MediaStyle#setShowActionsInCompactView(int...)}.
1448      */
1449     public static final String EXTRA_COMPACT_ACTIONS = "android.compactActions";
1450 
1451     /**
1452      * {@link #extras} key: the username to be displayed for all messages sent by the user including
1453      * direct replies
1454      * {@link android.app.Notification.MessagingStyle} notification. This extra is a
1455      * {@link CharSequence}
1456      *
1457      * @deprecated use {@link #EXTRA_MESSAGING_PERSON}
1458      */
1459     public static final String EXTRA_SELF_DISPLAY_NAME = "android.selfDisplayName";
1460 
1461     /**
1462      * {@link #extras} key: the person to be displayed for all messages sent by the user including
1463      * direct replies
1464      * {@link android.app.Notification.MessagingStyle} notification. This extra is a
1465      * {@link Person}
1466      */
1467     public static final String EXTRA_MESSAGING_PERSON = "android.messagingUser";
1468 
1469     /**
1470      * {@link #extras} key: a {@link CharSequence} to be displayed as the title to a conversation
1471      * represented by a {@link android.app.Notification.MessagingStyle}
1472      */
1473     public static final String EXTRA_CONVERSATION_TITLE = "android.conversationTitle";
1474 
1475     /** @hide */
1476     public static final String EXTRA_CONVERSATION_ICON = "android.conversationIcon";
1477 
1478     /** @hide */
1479     public static final String EXTRA_CONVERSATION_UNREAD_MESSAGE_COUNT =
1480             "android.conversationUnreadMessageCount";
1481 
1482     /**
1483      * {@link #extras} key: an array of {@link android.app.Notification.MessagingStyle.Message}
1484      * bundles provided by a
1485      * {@link android.app.Notification.MessagingStyle} notification. This extra is a parcelable
1486      * array of bundles.
1487      */
1488     public static final String EXTRA_MESSAGES = "android.messages";
1489 
1490     /**
1491      * {@link #extras} key: an array of
1492      * {@link android.app.Notification.MessagingStyle#addHistoricMessage historic}
1493      * {@link android.app.Notification.MessagingStyle.Message} bundles provided by a
1494      * {@link android.app.Notification.MessagingStyle} notification. This extra is a parcelable
1495      * array of bundles.
1496      */
1497     public static final String EXTRA_HISTORIC_MESSAGES = "android.messages.historic";
1498 
1499     /**
1500      * {@link #extras} key: whether the {@link android.app.Notification.MessagingStyle} notification
1501      * represents a group conversation.
1502      */
1503     public static final String EXTRA_IS_GROUP_CONVERSATION = "android.isGroupConversation";
1504 
1505     /**
1506      * {@link #extras} key: the type of call represented by the
1507      * {@link android.app.Notification.CallStyle} notification. This extra is an int.
1508      */
1509     public static final String EXTRA_CALL_TYPE = "android.callType";
1510 
1511     /**
1512      * {@link #extras} key: whether the  {@link android.app.Notification.CallStyle} notification
1513      * is for a call that will activate video when answered. This extra is a boolean.
1514      */
1515     public static final String EXTRA_CALL_IS_VIDEO = "android.callIsVideo";
1516 
1517     /**
1518      * {@link #extras} key: the person to be displayed as calling for the
1519      * {@link android.app.Notification.CallStyle} notification. This extra is a {@link Person}.
1520      */
1521     public static final String EXTRA_CALL_PERSON = "android.callPerson";
1522 
1523     /**
1524      * {@link #extras} key: the icon to be displayed as a verification status of the caller on a
1525      * {@link android.app.Notification.CallStyle} notification. This extra is an {@link Icon}.
1526      */
1527     public static final String EXTRA_VERIFICATION_ICON = "android.verificationIcon";
1528 
1529     /**
1530      * {@link #extras} key: the text to be displayed as a verification status of the caller on a
1531      * {@link android.app.Notification.CallStyle} notification. This extra is a
1532      * {@link CharSequence}.
1533      */
1534     public static final String EXTRA_VERIFICATION_TEXT = "android.verificationText";
1535 
1536     /**
1537      * {@link #extras} key: the intent to be sent when the users answers a
1538      * {@link android.app.Notification.CallStyle} notification. This extra is a
1539      * {@link PendingIntent}.
1540      */
1541     public static final String EXTRA_ANSWER_INTENT = "android.answerIntent";
1542 
1543     /**
1544      * {@link #extras} key: the intent to be sent when the users declines a
1545      * {@link android.app.Notification.CallStyle} notification. This extra is a
1546      * {@link PendingIntent}.
1547      */
1548     public static final String EXTRA_DECLINE_INTENT = "android.declineIntent";
1549 
1550     /**
1551      * {@link #extras} key: the intent to be sent when the users hangs up a
1552      * {@link android.app.Notification.CallStyle} notification. This extra is a
1553      * {@link PendingIntent}.
1554      */
1555     public static final String EXTRA_HANG_UP_INTENT = "android.hangUpIntent";
1556 
1557     /**
1558      * {@link #extras} key: the color used as a hint for the Answer action button of a
1559      * {@link android.app.Notification.CallStyle} notification. This extra is a {@code ColorInt}.
1560      */
1561     public static final String EXTRA_ANSWER_COLOR = "android.answerColor";
1562 
1563     /**
1564      * {@link #extras} key: the color used as a hint for the Decline or Hang Up action button of a
1565      * {@link android.app.Notification.CallStyle} notification. This extra is a {@code ColorInt}.
1566      */
1567     public static final String EXTRA_DECLINE_COLOR = "android.declineColor";
1568 
1569     /**
1570      * {@link #extras} key: whether the notification should be colorized as
1571      * supplied to {@link Builder#setColorized(boolean)}.
1572      */
1573     public static final String EXTRA_COLORIZED = "android.colorized";
1574 
1575     /**
1576      * @hide
1577      */
1578     public static final String EXTRA_BUILDER_APPLICATION_INFO = "android.appInfo";
1579 
1580     /**
1581      * @hide
1582      */
1583     public static final String EXTRA_CONTAINS_CUSTOM_VIEW = "android.contains.customView";
1584 
1585     /**
1586      * @hide
1587      */
1588     public static final String EXTRA_REDUCED_IMAGES = "android.reduced.images";
1589 
1590     /**
1591      * {@link #extras} key: the audio contents of this notification.
1592      *
1593      * This is for use when rendering the notification on an audio-focused interface;
1594      * the audio contents are a complete sound sample that contains the contents/body of the
1595      * notification. This may be used in substitute of a Text-to-Speech reading of the
1596      * notification. For example if the notification represents a voice message this should point
1597      * to the audio of that message.
1598      *
1599      * The data stored under this key should be a String representation of a Uri that contains the
1600      * audio contents in one of the following formats: WAV, PCM 16-bit, AMR-WB.
1601      *
1602      * This extra is unnecessary if you are using {@code MessagingStyle} since each {@code Message}
1603      * has a field for holding data URI. That field can be used for audio.
1604      * See {@code Message#setData}.
1605      *
1606      * Example usage:
1607      * <pre>
1608      * {@code
1609      * Notification.Builder myBuilder = (build your Notification as normal);
1610      * myBuilder.getExtras().putString(EXTRA_AUDIO_CONTENTS_URI, myAudioUri.toString());
1611      * }
1612      * </pre>
1613      */
1614     public static final String EXTRA_AUDIO_CONTENTS_URI = "android.audioContents";
1615 
1616     /** @hide */
1617     @SystemApi
1618     @RequiresPermission(android.Manifest.permission.SUBSTITUTE_NOTIFICATION_APP_NAME)
1619     public static final String EXTRA_SUBSTITUTE_APP_NAME = "android.substName";
1620 
1621     /**
1622      * This is set on the notifications shown by system_server about apps running foreground
1623      * services. It indicates that the notification should be shown
1624      * only if any of the given apps do not already have a properly tagged
1625      * {@link #FLAG_FOREGROUND_SERVICE} notification currently visible to the user.
1626      * This is a string array of all package names of the apps.
1627      * @hide
1628      */
1629     public static final String EXTRA_FOREGROUND_APPS = "android.foregroundApps";
1630 
1631     @UnsupportedAppUsage
1632     private Icon mSmallIcon;
1633     @UnsupportedAppUsage
1634     private Icon mLargeIcon;
1635     private Icon mAppIcon;
1636 
1637     /** Cache for whether the notification was posted by a headless system app. */
1638     private Boolean mBelongsToHeadlessSystemApp = null;
1639 
1640     @UnsupportedAppUsage
1641     private String mChannelId;
1642     private long mTimeout;
1643 
1644     private String mShortcutId;
1645     private LocusId mLocusId;
1646     private CharSequence mSettingsText;
1647 
1648     private BubbleMetadata mBubbleMetadata;
1649 
1650     /** @hide */
1651     @IntDef(prefix = { "GROUP_ALERT_" }, value = {
1652             GROUP_ALERT_ALL, GROUP_ALERT_CHILDREN, GROUP_ALERT_SUMMARY
1653     })
1654     @Retention(RetentionPolicy.SOURCE)
1655     public @interface GroupAlertBehavior {}
1656 
1657     /**
1658      * Constant for {@link Builder#setGroupAlertBehavior(int)}, meaning that all notifications in a
1659      * group with sound or vibration ought to make sound or vibrate (respectively), so this
1660      * notification will not be muted when it is in a group.
1661      */
1662     public static final int GROUP_ALERT_ALL = 0;
1663 
1664     /**
1665      * Constant for {@link Builder#setGroupAlertBehavior(int)}, meaning that all children
1666      * notification in a group should be silenced (no sound or vibration) even if they are posted
1667      * to a {@link NotificationChannel} that has sound and/or vibration. Use this constant to
1668      * mute this notification if this notification is a group child. This must be applied to all
1669      * children notifications you want to mute.
1670      *
1671      * <p> For example, you might want to use this constant if you post a number of children
1672      * notifications at once (say, after a periodic sync), and only need to notify the user
1673      * audibly once.
1674      */
1675     public static final int GROUP_ALERT_SUMMARY = 1;
1676 
1677     /**
1678      * Constant for {@link Builder#setGroupAlertBehavior(int)}, meaning that the summary
1679      * notification in a group should be silenced (no sound or vibration) even if they are
1680      * posted to a {@link NotificationChannel} that has sound and/or vibration. Use this constant
1681      * to mute this notification if this notification is a group summary.
1682      *
1683      * <p>For example, you might want to use this constant if only the children notifications
1684      * in your group have content and the summary is only used to visually group notifications
1685      * rather than to alert the user that new information is available.
1686      */
1687     public static final int GROUP_ALERT_CHILDREN = 2;
1688 
1689     /**
1690      * Constant for the {@link Builder#setGroup(String) group key} that is added to notifications
1691      * that are not already grouped when {@link Builder#setSilent()} is used.
1692      *
1693      * @hide
1694      */
1695     public static final String GROUP_KEY_SILENT = "silent";
1696 
1697     private int mGroupAlertBehavior = GROUP_ALERT_ALL;
1698 
1699     /**
1700      * If this notification is being shown as a badge, always show as a number.
1701      */
1702     public static final int BADGE_ICON_NONE = 0;
1703 
1704     /**
1705      * If this notification is being shown as a badge, use the {@link #getSmallIcon()} to
1706      * represent this notification.
1707      */
1708     public static final int BADGE_ICON_SMALL = 1;
1709 
1710     /**
1711      * If this notification is being shown as a badge, use the {@link #getLargeIcon()} to
1712      * represent this notification.
1713      */
1714     public static final int BADGE_ICON_LARGE = 2;
1715     private int mBadgeIcon = BADGE_ICON_NONE;
1716 
1717     /**
1718      * Determines whether the platform can generate contextual actions for a notification.
1719      */
1720     private boolean mAllowSystemGeneratedContextualActions = true;
1721 
1722     /**
1723      * Structure to encapsulate a named action that can be shown as part of this notification.
1724      * It must include an icon, a label, and a {@link PendingIntent} to be fired when the action is
1725      * selected by the user.
1726      * <p>
1727      * Apps should use {@link Notification.Builder#addAction(int, CharSequence, PendingIntent)}
1728      * or {@link Notification.Builder#addAction(Notification.Action)}
1729      * to attach actions.
1730      * <p>
1731      * As of Android {@link android.os.Build.VERSION_CODES#S}, apps targeting API level {@link
1732      * android.os.Build.VERSION_CODES#S} or higher won't be able to start activities while
1733      * processing broadcast receivers or services in response to notification action clicks. To
1734      * launch an activity in those cases, provide a {@link PendingIntent} for the activity itself.
1735      */
1736     public static class Action implements Parcelable {
1737         /**
1738          * {@link #extras} key: Keys to a {@link Parcelable} {@link ArrayList} of
1739          * {@link RemoteInput}s.
1740          *
1741          * This is intended for {@link RemoteInput}s that only accept data, meaning
1742          * {@link RemoteInput#getAllowFreeFormInput} is false, {@link RemoteInput#getChoices}
1743          * is null or empty, and {@link RemoteInput#getAllowedDataTypes} is non-null and not
1744          * empty. These {@link RemoteInput}s will be ignored by devices that do not
1745          * support non-text-based {@link RemoteInput}s. See {@link Builder#build}.
1746          *
1747          * You can test if a RemoteInput matches these constraints using
1748          * {@link RemoteInput#isDataOnly}.
1749          */
1750         private static final String EXTRA_DATA_ONLY_INPUTS = "android.extra.DATA_ONLY_INPUTS";
1751 
1752         /**
1753          * No semantic action defined.
1754          */
1755         public static final int SEMANTIC_ACTION_NONE = 0;
1756 
1757         /**
1758          * {@code SemanticAction}: Reply to a conversation, chat, group, or wherever replies
1759          * may be appropriate.
1760          */
1761         public static final int SEMANTIC_ACTION_REPLY = 1;
1762 
1763         /**
1764          * {@code SemanticAction}: Mark content as read.
1765          */
1766         public static final int SEMANTIC_ACTION_MARK_AS_READ = 2;
1767 
1768         /**
1769          * {@code SemanticAction}: Mark content as unread.
1770          */
1771         public static final int SEMANTIC_ACTION_MARK_AS_UNREAD = 3;
1772 
1773         /**
1774          * {@code SemanticAction}: Delete the content associated with the notification. This
1775          * could mean deleting an email, message, etc.
1776          */
1777         public static final int SEMANTIC_ACTION_DELETE = 4;
1778 
1779         /**
1780          * {@code SemanticAction}: Archive the content associated with the notification. This
1781          * could mean archiving an email, message, etc.
1782          */
1783         public static final int SEMANTIC_ACTION_ARCHIVE = 5;
1784 
1785         /**
1786          * {@code SemanticAction}: Mute the content associated with the notification. This could
1787          * mean silencing a conversation or currently playing media.
1788          */
1789         public static final int SEMANTIC_ACTION_MUTE = 6;
1790 
1791         /**
1792          * {@code SemanticAction}: Unmute the content associated with the notification. This could
1793          * mean un-silencing a conversation or currently playing media.
1794          */
1795         public static final int SEMANTIC_ACTION_UNMUTE = 7;
1796 
1797         /**
1798          * {@code SemanticAction}: Mark content with a thumbs up.
1799          */
1800         public static final int SEMANTIC_ACTION_THUMBS_UP = 8;
1801 
1802         /**
1803          * {@code SemanticAction}: Mark content with a thumbs down.
1804          */
1805         public static final int SEMANTIC_ACTION_THUMBS_DOWN = 9;
1806 
1807         /**
1808          * {@code SemanticAction}: Call a contact, group, etc.
1809          */
1810         public static final int SEMANTIC_ACTION_CALL = 10;
1811 
1812         /**
1813          * {@code SemanticAction}: Mark the conversation associated with the notification as a
1814          * priority. Note that this is only for use by the notification assistant services. The
1815          * type will be ignored for actions an app adds to its own notifications.
1816          * @hide
1817          */
1818         @SystemApi
1819         public static final int SEMANTIC_ACTION_MARK_CONVERSATION_AS_PRIORITY = 11;
1820 
1821         /**
1822          * {@code SemanticAction}: Mark content as a potential phishing attempt.
1823          * Note that this is only for use by the notification assistant services. The type will
1824          * be ignored for actions an app adds to its own notifications.
1825          * @hide
1826          */
1827         @SystemApi
1828         public static final int SEMANTIC_ACTION_CONVERSATION_IS_PHISHING = 12;
1829 
1830         private final Bundle mExtras;
1831         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
1832         private Icon mIcon;
1833         private final RemoteInput[] mRemoteInputs;
1834         private boolean mAllowGeneratedReplies = true;
1835         private final @SemanticAction int mSemanticAction;
1836         private final boolean mIsContextual;
1837         private boolean mAuthenticationRequired;
1838 
1839         /**
1840          * Small icon representing the action.
1841          *
1842          * @deprecated Use {@link Action#getIcon()} instead.
1843          */
1844         @Deprecated
1845         public int icon;
1846 
1847         /**
1848          * Title of the action.
1849          */
1850         public CharSequence title;
1851 
1852         /**
1853          * Intent to send when the user invokes this action. May be null, in which case the action
1854          * may be rendered in a disabled presentation by the system UI.
1855          */
1856         public PendingIntent actionIntent;
1857 
Action(Parcel in)1858         private Action(Parcel in) {
1859             if (in.readInt() != 0) {
1860                 mIcon = Icon.CREATOR.createFromParcel(in);
1861                 if (mIcon.getType() == Icon.TYPE_RESOURCE) {
1862                     icon = mIcon.getResId();
1863                 }
1864             }
1865             title = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
1866             if (in.readInt() == 1) {
1867                 actionIntent = PendingIntent.CREATOR.createFromParcel(in);
1868             }
1869             mExtras = Bundle.setDefusable(in.readBundle(), true);
1870             mRemoteInputs = in.createTypedArray(RemoteInput.CREATOR);
1871             mAllowGeneratedReplies = in.readInt() == 1;
1872             mSemanticAction = in.readInt();
1873             mIsContextual = in.readInt() == 1;
1874             mAuthenticationRequired = in.readInt() == 1;
1875         }
1876 
1877         /**
1878          * @deprecated Use {@link android.app.Notification.Action.Builder}.
1879          */
1880         @Deprecated
Action(int icon, CharSequence title, @Nullable PendingIntent intent)1881         public Action(int icon, CharSequence title, @Nullable PendingIntent intent) {
1882             this(Icon.createWithResource("", icon), title, intent, new Bundle(), null, true,
1883                     SEMANTIC_ACTION_NONE, false /* isContextual */, false /* requireAuth */);
1884         }
1885 
1886         /** 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)1887         private Action(Icon icon, CharSequence title, PendingIntent intent, Bundle extras,
1888                 RemoteInput[] remoteInputs, boolean allowGeneratedReplies,
1889                 @SemanticAction int semanticAction, boolean isContextual,
1890                 boolean requireAuth) {
1891             this.mIcon = icon;
1892             if (icon != null && icon.getType() == Icon.TYPE_RESOURCE) {
1893                 this.icon = icon.getResId();
1894             }
1895             this.title = title;
1896             this.actionIntent = intent;
1897             this.mExtras = extras != null ? extras : new Bundle();
1898             this.mRemoteInputs = remoteInputs;
1899             this.mAllowGeneratedReplies = allowGeneratedReplies;
1900             this.mSemanticAction = semanticAction;
1901             this.mIsContextual = isContextual;
1902             this.mAuthenticationRequired = requireAuth;
1903         }
1904 
1905         /**
1906          * Return an icon representing the action.
1907          */
getIcon()1908         public Icon getIcon() {
1909             if (mIcon == null && icon != 0) {
1910                 // you snuck an icon in here without using the builder; let's try to keep it
1911                 mIcon = Icon.createWithResource("", icon);
1912             }
1913             return mIcon;
1914         }
1915 
1916         /**
1917          * Get additional metadata carried around with this Action.
1918          */
getExtras()1919         public Bundle getExtras() {
1920             return mExtras;
1921         }
1922 
1923         /**
1924          * Return whether the platform should automatically generate possible replies for this
1925          * {@link Action}
1926          */
getAllowGeneratedReplies()1927         public boolean getAllowGeneratedReplies() {
1928             return mAllowGeneratedReplies;
1929         }
1930 
1931         /**
1932          * Get the list of inputs to be collected from the user when this action is sent.
1933          * May return null if no remote inputs were added. Only returns inputs which accept
1934          * a text input. For inputs which only accept data use {@link #getDataOnlyRemoteInputs}.
1935          */
getRemoteInputs()1936         public RemoteInput[] getRemoteInputs() {
1937             return mRemoteInputs;
1938         }
1939 
1940         /**
1941          * Returns the {@code SemanticAction} associated with this {@link Action}. A
1942          * {@code SemanticAction} denotes what an {@link Action}'s {@link PendingIntent} will do
1943          * (eg. reply, mark as read, delete, etc).
1944          */
getSemanticAction()1945         public @SemanticAction int getSemanticAction() {
1946             return mSemanticAction;
1947         }
1948 
1949         /**
1950          * Returns whether this is a contextual Action, i.e. whether the action is dependent on the
1951          * notification message body. An example of a contextual action could be an action opening a
1952          * map application with an address shown in the notification.
1953          */
isContextual()1954         public boolean isContextual() {
1955             return mIsContextual;
1956         }
1957 
1958         /**
1959          * Get the list of inputs to be collected from the user that ONLY accept data when this
1960          * action is sent. These remote inputs are guaranteed to return true on a call to
1961          * {@link RemoteInput#isDataOnly}.
1962          *
1963          * Returns null if there are no data-only remote inputs.
1964          *
1965          * This method exists so that legacy RemoteInput collectors that pre-date the addition
1966          * of non-textual RemoteInputs do not access these remote inputs.
1967          */
getDataOnlyRemoteInputs()1968         public RemoteInput[] getDataOnlyRemoteInputs() {
1969             return getParcelableArrayFromBundle(mExtras, EXTRA_DATA_ONLY_INPUTS, RemoteInput.class);
1970         }
1971 
1972         /**
1973          * Returns whether the OS should only send this action's {@link PendingIntent} on an
1974          * unlocked device.
1975          *
1976          * If the device is locked when the action is invoked, the OS should show the keyguard and
1977          * require successful authentication before invoking the intent.
1978          */
isAuthenticationRequired()1979         public boolean isAuthenticationRequired() {
1980             return mAuthenticationRequired;
1981         }
1982 
1983         /**
1984          * Builder class for {@link Action} objects.
1985          */
1986         public static final class Builder {
1987             @Nullable private final Icon mIcon;
1988             @Nullable private final CharSequence mTitle;
1989             @Nullable private final PendingIntent mIntent;
1990             private boolean mAllowGeneratedReplies = true;
1991             @NonNull private final Bundle mExtras;
1992             @Nullable private ArrayList<RemoteInput> mRemoteInputs;
1993             private @SemanticAction int mSemanticAction;
1994             private boolean mIsContextual;
1995             private boolean mAuthenticationRequired;
1996 
1997             /**
1998              * Construct a new builder for {@link Action} object.
1999              * <p>As of Android {@link android.os.Build.VERSION_CODES#N},
2000              * action button icons will not be displayed on action buttons, but are still required
2001              * and are available to
2002              * {@link android.service.notification.NotificationListenerService notification listeners},
2003              * which may display them in other contexts, for example on a wearable device.
2004              * @param icon icon to show for this action
2005              * @param title the title of the action
2006              * @param intent the {@link PendingIntent} to fire when users trigger this action. May
2007              * be null, in which case the action may be rendered in a disabled presentation by the
2008              * system UI.
2009              */
2010             @Deprecated
Builder(int icon, CharSequence title, @Nullable PendingIntent intent)2011             public Builder(int icon, CharSequence title, @Nullable PendingIntent intent) {
2012                 this(Icon.createWithResource("", icon), title, intent);
2013             }
2014 
2015             /**
2016              * Construct a new builder for {@link Action} object.
2017              *
2018              * <p>As of Android {@link android.os.Build.VERSION_CODES#S}, apps targeting API level
2019              * {@link android.os.Build.VERSION_CODES#S} or higher won't be able to start activities
2020              * while processing broadcast receivers or services in response to notification action
2021              * clicks. To launch an activity in those cases, provide a {@link PendingIntent} for the
2022              * activity itself.
2023              *
2024              * <p>How an Action is displayed, including whether the {@code icon}, {@code text}, or
2025              * both are displayed or required, depends on where and how the action is used, and the
2026              * {@link Style} applied to the Notification.
2027              *
2028              * <p>As of Android {@link android.os.Build.VERSION_CODES#N}, action button icons
2029              * will not be displayed on action buttons, but are still required and are available
2030              * to {@link android.service.notification.NotificationListenerService notification
2031              * listeners}, which may display them in other contexts, for example on a wearable
2032              * device.
2033              *
2034              * <p>When the {@code title} is a {@link android.text.Spanned}, any colors set by a
2035              * {@link ForegroundColorSpan} or {@link TextAppearanceSpan} may be removed or displayed
2036              * with an altered in luminance to ensure proper contrast within the Notification.
2037              *
2038              * @param icon icon to show for this action
2039              * @param title the title of the action
2040              * @param intent the {@link PendingIntent} to fire when users trigger this action. May
2041              * be null, in which case the action may be rendered in a disabled presentation by the
2042              * system UI.
2043              */
Builder(Icon icon, CharSequence title, @Nullable PendingIntent intent)2044             public Builder(Icon icon, CharSequence title, @Nullable PendingIntent intent) {
2045                 this(icon, title, intent, new Bundle(), null, true, SEMANTIC_ACTION_NONE, false);
2046             }
2047 
2048             /**
2049              * Construct a new builder for {@link Action} object using the fields from an
2050              * {@link Action}.
2051              * @param action the action to read fields from.
2052              */
Builder(Action action)2053             public Builder(Action action) {
2054                 this(action.getIcon(), action.title, action.actionIntent,
2055                         new Bundle(action.mExtras), action.getRemoteInputs(),
2056                         action.getAllowGeneratedReplies(), action.getSemanticAction(),
2057                         action.isAuthenticationRequired());
2058             }
2059 
Builder(@ullable Icon icon, @Nullable CharSequence title, @Nullable PendingIntent intent, @NonNull Bundle extras, @Nullable RemoteInput[] remoteInputs, boolean allowGeneratedReplies, @SemanticAction int semanticAction, boolean authRequired)2060             private Builder(@Nullable Icon icon, @Nullable CharSequence title,
2061                     @Nullable PendingIntent intent, @NonNull Bundle extras,
2062                     @Nullable RemoteInput[] remoteInputs, boolean allowGeneratedReplies,
2063                     @SemanticAction int semanticAction, boolean authRequired) {
2064                 mIcon = icon;
2065                 mTitle = title;
2066                 mIntent = intent;
2067                 mExtras = extras;
2068                 if (remoteInputs != null) {
2069                     mRemoteInputs = new ArrayList<>(remoteInputs.length);
2070                     Collections.addAll(mRemoteInputs, remoteInputs);
2071                 }
2072                 mAllowGeneratedReplies = allowGeneratedReplies;
2073                 mSemanticAction = semanticAction;
2074                 mAuthenticationRequired = authRequired;
2075             }
2076 
2077             /**
2078              * Merge additional metadata into this builder.
2079              *
2080              * <p>Values within the Bundle will replace existing extras values in this Builder.
2081              *
2082              * @see Notification.Action#extras
2083              */
2084             @NonNull
addExtras(Bundle extras)2085             public Builder addExtras(Bundle extras) {
2086                 if (extras != null) {
2087                     mExtras.putAll(extras);
2088                 }
2089                 return this;
2090             }
2091 
2092             /**
2093              * Get the metadata Bundle used by this Builder.
2094              *
2095              * <p>The returned Bundle is shared with this Builder.
2096              */
2097             @NonNull
getExtras()2098             public Bundle getExtras() {
2099                 return mExtras;
2100             }
2101 
2102             /**
2103              * Add an input to be collected from the user when this action is sent.
2104              * Response values can be retrieved from the fired intent by using the
2105              * {@link RemoteInput#getResultsFromIntent} function.
2106              * @param remoteInput a {@link RemoteInput} to add to the action
2107              * @return this object for method chaining
2108              */
2109             @NonNull
addRemoteInput(RemoteInput remoteInput)2110             public Builder addRemoteInput(RemoteInput remoteInput) {
2111                 if (mRemoteInputs == null) {
2112                     mRemoteInputs = new ArrayList<RemoteInput>();
2113                 }
2114                 mRemoteInputs.add(remoteInput);
2115                 return this;
2116             }
2117 
2118             /**
2119              * Set whether the platform should automatically generate possible replies to add to
2120              * {@link RemoteInput#getChoices()}. If the {@link Action} doesn't have a
2121              * {@link RemoteInput}, this has no effect.
2122              * @param allowGeneratedReplies {@code true} to allow generated replies, {@code false}
2123              * otherwise
2124              * @return this object for method chaining
2125              * The default value is {@code true}
2126              */
2127             @NonNull
setAllowGeneratedReplies(boolean allowGeneratedReplies)2128             public Builder setAllowGeneratedReplies(boolean allowGeneratedReplies) {
2129                 mAllowGeneratedReplies = allowGeneratedReplies;
2130                 return this;
2131             }
2132 
2133             /**
2134              * Sets the {@code SemanticAction} for this {@link Action}. A
2135              * {@code SemanticAction} denotes what an {@link Action}'s
2136              * {@link PendingIntent} will do (eg. reply, mark as read, delete, etc).
2137              * @param semanticAction a SemanticAction defined within {@link Action} with
2138              * {@code SEMANTIC_ACTION_} prefixes
2139              * @return this object for method chaining
2140              */
2141             @NonNull
setSemanticAction(@emanticAction int semanticAction)2142             public Builder setSemanticAction(@SemanticAction int semanticAction) {
2143                 mSemanticAction = semanticAction;
2144                 return this;
2145             }
2146 
2147             /**
2148              * Sets whether this {@link Action} is a contextual action, i.e. whether the action is
2149              * dependent on the notification message body. An example of a contextual action could
2150              * be an action opening a map application with an address shown in the notification.
2151              */
2152             @NonNull
setContextual(boolean isContextual)2153             public Builder setContextual(boolean isContextual) {
2154                 mIsContextual = isContextual;
2155                 return this;
2156             }
2157 
2158             /**
2159              * Apply an extender to this action builder. Extenders may be used to add
2160              * metadata or change options on this builder.
2161              */
2162             @NonNull
extend(Extender extender)2163             public Builder extend(Extender extender) {
2164                 extender.extend(this);
2165                 return this;
2166             }
2167 
2168             /**
2169              * Sets whether the OS should only send this action's {@link PendingIntent} on an
2170              * unlocked device.
2171              *
2172              * If this is true and the device is locked when the action is invoked, the OS will
2173              * show the keyguard and require successful authentication before invoking the intent.
2174              * If this is false and the device is locked, the OS will decide whether authentication
2175              * should be required.
2176              */
2177             @NonNull
setAuthenticationRequired(boolean authenticationRequired)2178             public Builder setAuthenticationRequired(boolean authenticationRequired) {
2179                 mAuthenticationRequired = authenticationRequired;
2180                 return this;
2181             }
2182 
2183             /**
2184              * Throws an NPE if we are building a contextual action missing one of the fields
2185              * necessary to display the action.
2186              */
checkContextualActionNullFields()2187             private void checkContextualActionNullFields() {
2188                 if (!mIsContextual) return;
2189 
2190                 if (mIcon == null) {
2191                     throw new NullPointerException("Contextual Actions must contain a valid icon");
2192                 }
2193 
2194                 if (mIntent == null) {
2195                     throw new NullPointerException(
2196                             "Contextual Actions must contain a valid PendingIntent");
2197                 }
2198             }
2199 
2200             /**
2201              * Combine all of the options that have been set and return a new {@link Action}
2202              * object.
2203              * @return the built action
2204              */
2205             @NonNull
build()2206             public Action build() {
2207                 checkContextualActionNullFields();
2208 
2209                 ArrayList<RemoteInput> dataOnlyInputs = new ArrayList<>();
2210                 RemoteInput[] previousDataInputs = getParcelableArrayFromBundle(
2211                         mExtras, EXTRA_DATA_ONLY_INPUTS, RemoteInput.class);
2212                 if (previousDataInputs != null) {
2213                     for (RemoteInput input : previousDataInputs) {
2214                         dataOnlyInputs.add(input);
2215                     }
2216                 }
2217                 List<RemoteInput> textInputs = new ArrayList<>();
2218                 if (mRemoteInputs != null) {
2219                     for (RemoteInput input : mRemoteInputs) {
2220                         if (input.isDataOnly()) {
2221                             dataOnlyInputs.add(input);
2222                         } else {
2223                             textInputs.add(input);
2224                         }
2225                     }
2226                 }
2227                 if (!dataOnlyInputs.isEmpty()) {
2228                     RemoteInput[] dataInputsArr =
2229                             dataOnlyInputs.toArray(new RemoteInput[dataOnlyInputs.size()]);
2230                     mExtras.putParcelableArray(EXTRA_DATA_ONLY_INPUTS, dataInputsArr);
2231                 }
2232                 RemoteInput[] textInputsArr = textInputs.isEmpty()
2233                         ? null : textInputs.toArray(new RemoteInput[textInputs.size()]);
2234                 return new Action(mIcon, mTitle, mIntent, mExtras, textInputsArr,
2235                         mAllowGeneratedReplies, mSemanticAction, mIsContextual,
2236                         mAuthenticationRequired);
2237             }
2238         }
2239 
visitUris(@onNull Consumer<Uri> visitor)2240         private void visitUris(@NonNull Consumer<Uri> visitor) {
2241             visitIconUri(visitor, getIcon());
2242         }
2243 
2244         @Override
clone()2245         public Action clone() {
2246             return new Action(
2247                     getIcon(),
2248                     title,
2249                     actionIntent, // safe to alias
2250                     mExtras == null ? new Bundle() : new Bundle(mExtras),
2251                     getRemoteInputs(),
2252                     getAllowGeneratedReplies(),
2253                     getSemanticAction(),
2254                     isContextual(),
2255                     isAuthenticationRequired());
2256         }
2257 
2258         @Override
describeContents()2259         public int describeContents() {
2260             return 0;
2261         }
2262 
2263         @Override
writeToParcel(Parcel out, int flags)2264         public void writeToParcel(Parcel out, int flags) {
2265             final Icon ic = getIcon();
2266             if (ic != null) {
2267                 out.writeInt(1);
2268                 ic.writeToParcel(out, 0);
2269             } else {
2270                 out.writeInt(0);
2271             }
2272             TextUtils.writeToParcel(title, out, flags);
2273             if (actionIntent != null) {
2274                 out.writeInt(1);
2275                 actionIntent.writeToParcel(out, flags);
2276             } else {
2277                 out.writeInt(0);
2278             }
2279             out.writeBundle(mExtras);
2280             out.writeTypedArray(mRemoteInputs, flags);
2281             out.writeInt(mAllowGeneratedReplies ? 1 : 0);
2282             out.writeInt(mSemanticAction);
2283             out.writeInt(mIsContextual ? 1 : 0);
2284             out.writeInt(mAuthenticationRequired ? 1 : 0);
2285         }
2286 
2287         public static final @android.annotation.NonNull Parcelable.Creator<Action> CREATOR =
2288                 new Parcelable.Creator<Action>() {
2289             public Action createFromParcel(Parcel in) {
2290                 return new Action(in);
2291             }
2292             public Action[] newArray(int size) {
2293                 return new Action[size];
2294             }
2295         };
2296 
2297         /**
2298          * Extender interface for use with {@link Builder#extend}. Extenders may be used to add
2299          * metadata or change options on an action builder.
2300          */
2301         public interface Extender {
2302             /**
2303              * Apply this extender to a notification action builder.
2304              * @param builder the builder to be modified.
2305              * @return the build object for chaining.
2306              */
extend(Builder builder)2307             public Builder extend(Builder builder);
2308         }
2309 
2310         /**
2311          * Wearable extender for notification actions. To add extensions to an action,
2312          * create a new {@link android.app.Notification.Action.WearableExtender} object using
2313          * the {@code WearableExtender()} constructor and apply it to a
2314          * {@link android.app.Notification.Action.Builder} using
2315          * {@link android.app.Notification.Action.Builder#extend}.
2316          *
2317          * <pre class="prettyprint">
2318          * Notification.Action action = new Notification.Action.Builder(
2319          *         R.drawable.archive_all, "Archive all", actionIntent)
2320          *         .extend(new Notification.Action.WearableExtender()
2321          *                 .setAvailableOffline(false))
2322          *         .build();</pre>
2323          */
2324         public static final class WearableExtender implements Extender {
2325             /** Notification action extra which contains wearable extensions */
2326             private static final String EXTRA_WEARABLE_EXTENSIONS = "android.wearable.EXTENSIONS";
2327 
2328             // Keys within EXTRA_WEARABLE_EXTENSIONS for wearable options.
2329             private static final String KEY_FLAGS = "flags";
2330             private static final String KEY_IN_PROGRESS_LABEL = "inProgressLabel";
2331             private static final String KEY_CONFIRM_LABEL = "confirmLabel";
2332             private static final String KEY_CANCEL_LABEL = "cancelLabel";
2333 
2334             // Flags bitwise-ored to mFlags
2335             private static final int FLAG_AVAILABLE_OFFLINE = 0x1;
2336             private static final int FLAG_HINT_LAUNCHES_ACTIVITY = 1 << 1;
2337             private static final int FLAG_HINT_DISPLAY_INLINE = 1 << 2;
2338 
2339             // Default value for flags integer
2340             private static final int DEFAULT_FLAGS = FLAG_AVAILABLE_OFFLINE;
2341 
2342             private int mFlags = DEFAULT_FLAGS;
2343 
2344             private CharSequence mInProgressLabel;
2345             private CharSequence mConfirmLabel;
2346             private CharSequence mCancelLabel;
2347 
2348             /**
2349              * Create a {@link android.app.Notification.Action.WearableExtender} with default
2350              * options.
2351              */
WearableExtender()2352             public WearableExtender() {
2353             }
2354 
2355             /**
2356              * Create a {@link android.app.Notification.Action.WearableExtender} by reading
2357              * wearable options present in an existing notification action.
2358              * @param action the notification action to inspect.
2359              */
WearableExtender(Action action)2360             public WearableExtender(Action action) {
2361                 Bundle wearableBundle = action.getExtras().getBundle(EXTRA_WEARABLE_EXTENSIONS);
2362                 if (wearableBundle != null) {
2363                     mFlags = wearableBundle.getInt(KEY_FLAGS, DEFAULT_FLAGS);
2364                     mInProgressLabel = wearableBundle.getCharSequence(KEY_IN_PROGRESS_LABEL);
2365                     mConfirmLabel = wearableBundle.getCharSequence(KEY_CONFIRM_LABEL);
2366                     mCancelLabel = wearableBundle.getCharSequence(KEY_CANCEL_LABEL);
2367                 }
2368             }
2369 
2370             /**
2371              * Apply wearable extensions to a notification action that is being built. This is
2372              * typically called by the {@link android.app.Notification.Action.Builder#extend}
2373              * method of {@link android.app.Notification.Action.Builder}.
2374              */
2375             @Override
extend(Action.Builder builder)2376             public Action.Builder extend(Action.Builder builder) {
2377                 Bundle wearableBundle = new Bundle();
2378 
2379                 if (mFlags != DEFAULT_FLAGS) {
2380                     wearableBundle.putInt(KEY_FLAGS, mFlags);
2381                 }
2382                 if (mInProgressLabel != null) {
2383                     wearableBundle.putCharSequence(KEY_IN_PROGRESS_LABEL, mInProgressLabel);
2384                 }
2385                 if (mConfirmLabel != null) {
2386                     wearableBundle.putCharSequence(KEY_CONFIRM_LABEL, mConfirmLabel);
2387                 }
2388                 if (mCancelLabel != null) {
2389                     wearableBundle.putCharSequence(KEY_CANCEL_LABEL, mCancelLabel);
2390                 }
2391 
2392                 builder.getExtras().putBundle(EXTRA_WEARABLE_EXTENSIONS, wearableBundle);
2393                 return builder;
2394             }
2395 
2396             @Override
clone()2397             public WearableExtender clone() {
2398                 WearableExtender that = new WearableExtender();
2399                 that.mFlags = this.mFlags;
2400                 that.mInProgressLabel = this.mInProgressLabel;
2401                 that.mConfirmLabel = this.mConfirmLabel;
2402                 that.mCancelLabel = this.mCancelLabel;
2403                 return that;
2404             }
2405 
2406             /**
2407              * Set whether this action is available when the wearable device is not connected to
2408              * a companion device. The user can still trigger this action when the wearable device is
2409              * offline, but a visual hint will indicate that the action may not be available.
2410              * Defaults to true.
2411              */
setAvailableOffline(boolean availableOffline)2412             public WearableExtender setAvailableOffline(boolean availableOffline) {
2413                 setFlag(FLAG_AVAILABLE_OFFLINE, availableOffline);
2414                 return this;
2415             }
2416 
2417             /**
2418              * Get whether this action is available when the wearable device is not connected to
2419              * a companion device. The user can still trigger this action when the wearable device is
2420              * offline, but a visual hint will indicate that the action may not be available.
2421              * Defaults to true.
2422              */
isAvailableOffline()2423             public boolean isAvailableOffline() {
2424                 return (mFlags & FLAG_AVAILABLE_OFFLINE) != 0;
2425             }
2426 
setFlag(int mask, boolean value)2427             private void setFlag(int mask, boolean value) {
2428                 if (value) {
2429                     mFlags |= mask;
2430                 } else {
2431                     mFlags &= ~mask;
2432                 }
2433             }
2434 
2435             /**
2436              * Set a label to display while the wearable is preparing to automatically execute the
2437              * action. This is usually a 'ing' verb ending in ellipsis like "Sending..."
2438              *
2439              * @param label the label to display while the action is being prepared to execute
2440              * @return this object for method chaining
2441              */
2442             @Deprecated
setInProgressLabel(CharSequence label)2443             public WearableExtender setInProgressLabel(CharSequence label) {
2444                 mInProgressLabel = label;
2445                 return this;
2446             }
2447 
2448             /**
2449              * Get the label to display while the wearable is preparing to automatically execute
2450              * the action. This is usually a 'ing' verb ending in ellipsis like "Sending..."
2451              *
2452              * @return the label to display while the action is being prepared to execute
2453              */
2454             @Deprecated
getInProgressLabel()2455             public CharSequence getInProgressLabel() {
2456                 return mInProgressLabel;
2457             }
2458 
2459             /**
2460              * Set a label to display to confirm that the action should be executed.
2461              * This is usually an imperative verb like "Send".
2462              *
2463              * @param label the label to confirm the action should be executed
2464              * @return this object for method chaining
2465              */
2466             @Deprecated
setConfirmLabel(CharSequence label)2467             public WearableExtender setConfirmLabel(CharSequence label) {
2468                 mConfirmLabel = label;
2469                 return this;
2470             }
2471 
2472             /**
2473              * Get the label to display to confirm that the action should be executed.
2474              * This is usually an imperative verb like "Send".
2475              *
2476              * @return the label to confirm the action should be executed
2477              */
2478             @Deprecated
getConfirmLabel()2479             public CharSequence getConfirmLabel() {
2480                 return mConfirmLabel;
2481             }
2482 
2483             /**
2484              * Set a label to display to cancel the action.
2485              * This is usually an imperative verb, like "Cancel".
2486              *
2487              * @param label the label to display to cancel the action
2488              * @return this object for method chaining
2489              */
2490             @Deprecated
setCancelLabel(CharSequence label)2491             public WearableExtender setCancelLabel(CharSequence label) {
2492                 mCancelLabel = label;
2493                 return this;
2494             }
2495 
2496             /**
2497              * Get the label to display to cancel the action.
2498              * This is usually an imperative verb like "Cancel".
2499              *
2500              * @return the label to display to cancel the action
2501              */
2502             @Deprecated
getCancelLabel()2503             public CharSequence getCancelLabel() {
2504                 return mCancelLabel;
2505             }
2506 
2507             /**
2508              * Set a hint that this Action will launch an {@link Activity} directly, telling the
2509              * platform that it can generate the appropriate transitions.
2510              * @param hintLaunchesActivity {@code true} if the content intent will launch
2511              * an activity and transitions should be generated, false otherwise.
2512              * @return this object for method chaining
2513              */
setHintLaunchesActivity( boolean hintLaunchesActivity)2514             public WearableExtender setHintLaunchesActivity(
2515                     boolean hintLaunchesActivity) {
2516                 setFlag(FLAG_HINT_LAUNCHES_ACTIVITY, hintLaunchesActivity);
2517                 return this;
2518             }
2519 
2520             /**
2521              * Get a hint that this Action will launch an {@link Activity} directly, telling the
2522              * platform that it can generate the appropriate transitions
2523              * @return {@code true} if the content intent will launch an activity and transitions
2524              * should be generated, false otherwise. The default value is {@code false} if this was
2525              * never set.
2526              */
getHintLaunchesActivity()2527             public boolean getHintLaunchesActivity() {
2528                 return (mFlags & FLAG_HINT_LAUNCHES_ACTIVITY) != 0;
2529             }
2530 
2531             /**
2532              * Set a hint that this Action should be displayed inline.
2533              *
2534              * @param hintDisplayInline {@code true} if action should be displayed inline, false
2535              *        otherwise
2536              * @return this object for method chaining
2537              */
setHintDisplayActionInline( boolean hintDisplayInline)2538             public WearableExtender setHintDisplayActionInline(
2539                     boolean hintDisplayInline) {
2540                 setFlag(FLAG_HINT_DISPLAY_INLINE, hintDisplayInline);
2541                 return this;
2542             }
2543 
2544             /**
2545              * Get a hint that this Action should be displayed inline.
2546              *
2547              * @return {@code true} if the Action should be displayed inline, {@code false}
2548              *         otherwise. The default value is {@code false} if this was never set.
2549              */
getHintDisplayActionInline()2550             public boolean getHintDisplayActionInline() {
2551                 return (mFlags & FLAG_HINT_DISPLAY_INLINE) != 0;
2552             }
2553         }
2554 
2555         /**
2556          * Provides meaning to an {@link Action} that hints at what the associated
2557          * {@link PendingIntent} will do. For example, an {@link Action} with a
2558          * {@link PendingIntent} that replies to a text message notification may have the
2559          * {@link #SEMANTIC_ACTION_REPLY} {@code SemanticAction} set within it.
2560          *
2561          * @hide
2562          */
2563         @IntDef(prefix = { "SEMANTIC_ACTION_" }, value = {
2564                 SEMANTIC_ACTION_NONE,
2565                 SEMANTIC_ACTION_REPLY,
2566                 SEMANTIC_ACTION_MARK_AS_READ,
2567                 SEMANTIC_ACTION_MARK_AS_UNREAD,
2568                 SEMANTIC_ACTION_DELETE,
2569                 SEMANTIC_ACTION_ARCHIVE,
2570                 SEMANTIC_ACTION_MUTE,
2571                 SEMANTIC_ACTION_UNMUTE,
2572                 SEMANTIC_ACTION_THUMBS_UP,
2573                 SEMANTIC_ACTION_THUMBS_DOWN,
2574                 SEMANTIC_ACTION_CALL,
2575                 SEMANTIC_ACTION_MARK_CONVERSATION_AS_PRIORITY,
2576                 SEMANTIC_ACTION_CONVERSATION_IS_PHISHING
2577         })
2578         @Retention(RetentionPolicy.SOURCE)
2579         public @interface SemanticAction {}
2580     }
2581 
2582     /**
2583      * Array of all {@link Action} structures attached to this notification by
2584      * {@link Builder#addAction(int, CharSequence, PendingIntent)}. Mostly useful for instances of
2585      * {@link android.service.notification.NotificationListenerService} that provide an alternative
2586      * interface for invoking actions.
2587      */
2588     public Action[] actions;
2589 
2590     /**
2591      * Replacement version of this notification whose content will be shown
2592      * in an insecure context such as atop a secure keyguard. See {@link #visibility}
2593      * and {@link #VISIBILITY_PUBLIC}.
2594      */
2595     public Notification publicVersion;
2596 
2597     /**
2598      * Constructs a Notification object with default values.
2599      * You might want to consider using {@link Builder} instead.
2600      */
Notification()2601     public Notification()
2602     {
2603         this.when = System.currentTimeMillis();
2604         if (Flags.sortSectionByTime()) {
2605             creationTime = when;
2606             extras.putBoolean(EXTRA_SHOW_WHEN, true);
2607         } else {
2608             this.creationTime = System.currentTimeMillis();
2609         }
2610         this.priority = PRIORITY_DEFAULT;
2611     }
2612 
2613     /**
2614      * @hide
2615      */
2616     @UnsupportedAppUsage
Notification(Context context, int icon, CharSequence tickerText, long when, CharSequence contentTitle, CharSequence contentText, Intent contentIntent)2617     public Notification(Context context, int icon, CharSequence tickerText, long when,
2618             CharSequence contentTitle, CharSequence contentText, Intent contentIntent)
2619     {
2620         if (Flags.sortSectionByTime()) {
2621             creationTime = when;
2622             extras.putBoolean(EXTRA_SHOW_WHEN, true);
2623         }
2624         new Builder(context)
2625                 .setWhen(when)
2626                 .setSmallIcon(icon)
2627                 .setTicker(tickerText)
2628                 .setContentTitle(contentTitle)
2629                 .setContentText(contentText)
2630                 .setContentIntent(PendingIntent.getActivity(
2631                         context, 0, contentIntent, PendingIntent.FLAG_MUTABLE))
2632                 .buildInto(this);
2633     }
2634 
2635     /**
2636      * Constructs a Notification object with the information needed to
2637      * have a status bar icon without the standard expanded view.
2638      *
2639      * @param icon          The resource id of the icon to put in the status bar.
2640      * @param tickerText    The text that flows by in the status bar when the notification first
2641      *                      activates.
2642      * @param when          The time to show in the time field.  In the System.currentTimeMillis
2643      *                      timebase.
2644      *
2645      * @deprecated Use {@link Builder} instead.
2646      */
2647     @Deprecated
Notification(int icon, CharSequence tickerText, long when)2648     public Notification(int icon, CharSequence tickerText, long when)
2649     {
2650         this.icon = icon;
2651         this.tickerText = tickerText;
2652         this.when = when;
2653         if (Flags.sortSectionByTime()) {
2654             creationTime = when;
2655             extras.putBoolean(EXTRA_SHOW_WHEN, true);
2656         } else {
2657             this.creationTime = System.currentTimeMillis();
2658         }
2659     }
2660 
2661     /**
2662      * Unflatten the notification from a parcel.
2663      */
2664     @SuppressWarnings("unchecked")
Notification(Parcel parcel)2665     public Notification(Parcel parcel) {
2666         // IMPORTANT: Add unmarshaling code in readFromParcel as the pending
2667         // intents in extras are always written as the last entry.
2668         readFromParcelImpl(parcel);
2669         // Must be read last!
2670         allPendingIntents = (ArraySet<PendingIntent>) parcel.readArraySet(null);
2671     }
2672 
readFromParcelImpl(Parcel parcel)2673     private void readFromParcelImpl(Parcel parcel)
2674     {
2675         int version = parcel.readInt();
2676 
2677         mAllowlistToken = parcel.readStrongBinder();
2678         if (mAllowlistToken == null) {
2679             mAllowlistToken = processAllowlistToken;
2680         }
2681         if (Flags.secureAllowlistToken()) {
2682             // Propagate this token to all pending intents that are unmarshalled from the parcel,
2683             // or keep the one we're already propagating, if that's the case.
2684             if (!parcel.hasClassCookie(PendingIntent.class)) {
2685                 parcel.setClassCookie(PendingIntent.class, mAllowlistToken);
2686             }
2687         } else {
2688             // Propagate this token to all pending intents that are unmarshalled from the parcel.
2689             parcel.setClassCookie(PendingIntent.class, mAllowlistToken);
2690         }
2691 
2692         when = parcel.readLong();
2693         creationTime = parcel.readLong();
2694         if (parcel.readInt() != 0) {
2695             mSmallIcon = Icon.CREATOR.createFromParcel(parcel);
2696             if (mSmallIcon.getType() == Icon.TYPE_RESOURCE) {
2697                 icon = mSmallIcon.getResId();
2698             }
2699         }
2700         number = parcel.readInt();
2701         if (parcel.readInt() != 0) {
2702             contentIntent = PendingIntent.CREATOR.createFromParcel(parcel);
2703         }
2704         if (parcel.readInt() != 0) {
2705             deleteIntent = PendingIntent.CREATOR.createFromParcel(parcel);
2706         }
2707         if (parcel.readInt() != 0) {
2708             tickerText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
2709         }
2710         if (parcel.readInt() != 0) {
2711             tickerView = RemoteViews.CREATOR.createFromParcel(parcel);
2712         }
2713         if (parcel.readInt() != 0) {
2714             contentView = RemoteViews.CREATOR.createFromParcel(parcel);
2715         }
2716         if (parcel.readInt() != 0) {
2717             mLargeIcon = Icon.CREATOR.createFromParcel(parcel);
2718         }
2719         defaults = parcel.readInt();
2720         flags = parcel.readInt();
2721         if (parcel.readInt() != 0) {
2722             sound = Uri.CREATOR.createFromParcel(parcel);
2723         }
2724 
2725         audioStreamType = parcel.readInt();
2726         if (parcel.readInt() != 0) {
2727             audioAttributes = AudioAttributes.CREATOR.createFromParcel(parcel);
2728         }
2729         vibrate = parcel.createLongArray();
2730         ledARGB = parcel.readInt();
2731         ledOnMS = parcel.readInt();
2732         ledOffMS = parcel.readInt();
2733         iconLevel = parcel.readInt();
2734 
2735         if (parcel.readInt() != 0) {
2736             fullScreenIntent = PendingIntent.CREATOR.createFromParcel(parcel);
2737         }
2738 
2739         priority = parcel.readInt();
2740 
2741         category = parcel.readString8();
2742 
2743         mGroupKey = parcel.readString8();
2744 
2745         mSortKey = parcel.readString8();
2746 
2747         extras = Bundle.setDefusable(parcel.readBundle(), true); // may be null
2748         fixDuplicateExtras();
2749 
2750         actions = parcel.createTypedArray(Action.CREATOR); // may be null
2751 
2752         if (parcel.readInt() != 0) {
2753             bigContentView = RemoteViews.CREATOR.createFromParcel(parcel);
2754         }
2755 
2756         if (parcel.readInt() != 0) {
2757             headsUpContentView = RemoteViews.CREATOR.createFromParcel(parcel);
2758         }
2759 
2760         visibility = parcel.readInt();
2761 
2762         if (parcel.readInt() != 0) {
2763             publicVersion = Notification.CREATOR.createFromParcel(parcel);
2764         }
2765 
2766         color = parcel.readInt();
2767 
2768         if (parcel.readInt() != 0) {
2769             mChannelId = parcel.readString8();
2770         }
2771         mTimeout = parcel.readLong();
2772 
2773         if (parcel.readInt() != 0) {
2774             mShortcutId = parcel.readString8();
2775         }
2776 
2777         if (parcel.readInt() != 0) {
2778             mLocusId = LocusId.CREATOR.createFromParcel(parcel);
2779         }
2780 
2781         mBadgeIcon = parcel.readInt();
2782 
2783         if (parcel.readInt() != 0) {
2784             mSettingsText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
2785         }
2786 
2787         mGroupAlertBehavior = parcel.readInt();
2788         if (parcel.readInt() != 0) {
2789             mBubbleMetadata = BubbleMetadata.CREATOR.createFromParcel(parcel);
2790         }
2791 
2792         mAllowSystemGeneratedContextualActions = parcel.readBoolean();
2793 
2794         mFgsDeferBehavior = parcel.readInt();
2795     }
2796 
2797     @Override
clone()2798     public Notification clone() {
2799         Notification that = new Notification();
2800         cloneInto(that, true);
2801         return that;
2802     }
2803 
2804     /**
2805      * Copy all (or if heavy is false, all except Bitmaps and RemoteViews) members
2806      * of this into that.
2807      * @hide
2808      */
cloneInto(Notification that, boolean heavy)2809     public void cloneInto(Notification that, boolean heavy) {
2810         that.mAllowlistToken = this.mAllowlistToken;
2811         that.when = this.when;
2812         that.creationTime = this.creationTime;
2813         that.mSmallIcon = this.mSmallIcon;
2814         that.number = this.number;
2815 
2816         // PendingIntents are global, so there's no reason (or way) to clone them.
2817         that.contentIntent = this.contentIntent;
2818         that.deleteIntent = this.deleteIntent;
2819         that.fullScreenIntent = this.fullScreenIntent;
2820 
2821         if (this.tickerText != null) {
2822             that.tickerText = this.tickerText.toString();
2823         }
2824         if (heavy && this.tickerView != null) {
2825             that.tickerView = this.tickerView.clone();
2826         }
2827         if (heavy && this.contentView != null) {
2828             that.contentView = this.contentView.clone();
2829         }
2830         if (heavy && this.mLargeIcon != null) {
2831             that.mLargeIcon = this.mLargeIcon;
2832         }
2833         that.iconLevel = this.iconLevel;
2834         that.sound = this.sound; // android.net.Uri is immutable
2835         that.audioStreamType = this.audioStreamType;
2836         if (this.audioAttributes != null) {
2837             that.audioAttributes = new AudioAttributes.Builder(this.audioAttributes).build();
2838         }
2839 
2840         final long[] vibrate = this.vibrate;
2841         if (vibrate != null) {
2842             final int N = vibrate.length;
2843             final long[] vib = that.vibrate = new long[N];
2844             System.arraycopy(vibrate, 0, vib, 0, N);
2845         }
2846 
2847         that.ledARGB = this.ledARGB;
2848         that.ledOnMS = this.ledOnMS;
2849         that.ledOffMS = this.ledOffMS;
2850         that.defaults = this.defaults;
2851 
2852         that.flags = this.flags;
2853 
2854         that.priority = this.priority;
2855 
2856         that.category = this.category;
2857 
2858         that.mGroupKey = this.mGroupKey;
2859 
2860         that.mSortKey = this.mSortKey;
2861 
2862         if (this.extras != null) {
2863             try {
2864                 that.extras = new Bundle(this.extras);
2865                 // will unparcel
2866                 that.extras.size();
2867             } catch (BadParcelableException e) {
2868                 Log.e(TAG, "could not unparcel extras from notification: " + this, e);
2869                 that.extras = null;
2870             }
2871         }
2872 
2873         if (!ArrayUtils.isEmpty(allPendingIntents)) {
2874             that.allPendingIntents = new ArraySet<>(allPendingIntents);
2875         }
2876 
2877         if (this.actions != null) {
2878             that.actions = new Action[this.actions.length];
2879             for(int i=0; i<this.actions.length; i++) {
2880                 if ( this.actions[i] != null) {
2881                     that.actions[i] = this.actions[i].clone();
2882                 }
2883             }
2884         }
2885 
2886         if (heavy && this.bigContentView != null) {
2887             that.bigContentView = this.bigContentView.clone();
2888         }
2889 
2890         if (heavy && this.headsUpContentView != null) {
2891             that.headsUpContentView = this.headsUpContentView.clone();
2892         }
2893 
2894         that.visibility = this.visibility;
2895 
2896         if (this.publicVersion != null) {
2897             that.publicVersion = new Notification();
2898             this.publicVersion.cloneInto(that.publicVersion, heavy);
2899         }
2900 
2901         that.color = this.color;
2902 
2903         that.mChannelId = this.mChannelId;
2904         that.mTimeout = this.mTimeout;
2905         that.mShortcutId = this.mShortcutId;
2906         that.mLocusId = this.mLocusId;
2907         that.mBadgeIcon = this.mBadgeIcon;
2908         that.mSettingsText = this.mSettingsText;
2909         that.mGroupAlertBehavior = this.mGroupAlertBehavior;
2910         that.mFgsDeferBehavior = this.mFgsDeferBehavior;
2911         that.mBubbleMetadata = this.mBubbleMetadata;
2912         that.mAllowSystemGeneratedContextualActions = this.mAllowSystemGeneratedContextualActions;
2913 
2914         if (!heavy) {
2915             that.lightenPayload(); // will clean out extras
2916         }
2917     }
2918 
visitIconUri(@onNull Consumer<Uri> visitor, @Nullable Icon icon)2919     private static void visitIconUri(@NonNull Consumer<Uri> visitor, @Nullable Icon icon) {
2920         if (icon == null) return;
2921         final int iconType = icon.getType();
2922         if (iconType == TYPE_URI || iconType == TYPE_URI_ADAPTIVE_BITMAP) {
2923             visitor.accept(icon.getUri());
2924         }
2925     }
2926 
2927     /**
2928     * Note all {@link Uri} that are referenced internally, with the expectation that Uri permission
2929     * grants will need to be issued to ensure the recipient of this object is able to render its
2930     * contents.
2931     * See b/281044385 for more context and examples about what happens when this isn't done
2932     * correctly.
2933     *
2934     * @hide
2935     */
visitUris(@onNull Consumer<Uri> visitor)2936     public void visitUris(@NonNull Consumer<Uri> visitor) {
2937         if (publicVersion != null) {
2938             publicVersion.visitUris(visitor);
2939         }
2940 
2941         visitor.accept(sound);
2942 
2943         if (tickerView != null) tickerView.visitUris(visitor);
2944         if (contentView != null) contentView.visitUris(visitor);
2945         if (bigContentView != null) bigContentView.visitUris(visitor);
2946         if (headsUpContentView != null) headsUpContentView.visitUris(visitor);
2947 
2948         visitIconUri(visitor, mSmallIcon);
2949         visitIconUri(visitor, mLargeIcon);
2950 
2951         if (actions != null) {
2952             for (Action action : actions) {
2953                 action.visitUris(visitor);
2954             }
2955         }
2956 
2957         if (extras != null) {
2958             visitIconUri(visitor, extras.getParcelable(EXTRA_LARGE_ICON_BIG, Icon.class));
2959             visitIconUri(visitor, extras.getParcelable(EXTRA_PICTURE_ICON, Icon.class));
2960 
2961             // NOTE: The documentation of EXTRA_AUDIO_CONTENTS_URI explicitly says that it is a
2962             // String representation of a Uri, but the previous implementation (and unit test) of
2963             // this method has always treated it as a Uri object. Given the inconsistency,
2964             // supporting both going forward is the safest choice.
2965             Object audioContentsUri = extras.get(EXTRA_AUDIO_CONTENTS_URI);
2966             if (audioContentsUri instanceof Uri) {
2967                 visitor.accept((Uri) audioContentsUri);
2968             } else if (audioContentsUri instanceof String) {
2969                 visitor.accept(Uri.parse((String) audioContentsUri));
2970             }
2971 
2972             if (extras.containsKey(EXTRA_BACKGROUND_IMAGE_URI)) {
2973                 visitor.accept(Uri.parse(extras.getString(EXTRA_BACKGROUND_IMAGE_URI)));
2974             }
2975 
2976             ArrayList<Person> people = extras.getParcelableArrayList(EXTRA_PEOPLE_LIST, android.app.Person.class);
2977             if (people != null && !people.isEmpty()) {
2978                 for (Person p : people) {
2979                     p.visitUris(visitor);
2980                 }
2981             }
2982 
2983             final RemoteInputHistoryItem[] history = extras.getParcelableArray(
2984                     Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS,
2985                     RemoteInputHistoryItem.class);
2986             if (history != null) {
2987                 for (int i = 0; i < history.length; i++) {
2988                     RemoteInputHistoryItem item = history[i];
2989                     if (item.getUri() != null) {
2990                         visitor.accept(item.getUri());
2991                     }
2992                 }
2993             }
2994 
2995             // Extras for MessagingStyle. We visit them even if not isStyle(MessagingStyle), since
2996             // Notification Listeners might use directly (without the isStyle check).
2997             final Person person = extras.getParcelable(EXTRA_MESSAGING_PERSON, Person.class);
2998             if (person != null) {
2999                 person.visitUris(visitor);
3000             }
3001 
3002             final Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES,
3003                     Parcelable.class);
3004             if (!ArrayUtils.isEmpty(messages)) {
3005                 for (MessagingStyle.Message message : MessagingStyle.Message
3006                         .getMessagesFromBundleArray(messages)) {
3007                     message.visitUris(visitor);
3008                 }
3009             }
3010 
3011             final Parcelable[] historic = extras.getParcelableArray(EXTRA_HISTORIC_MESSAGES,
3012                     Parcelable.class);
3013             if (!ArrayUtils.isEmpty(historic)) {
3014                 for (MessagingStyle.Message message : MessagingStyle.Message
3015                         .getMessagesFromBundleArray(historic)) {
3016                     message.visitUris(visitor);
3017                 }
3018             }
3019 
3020             visitIconUri(visitor, extras.getParcelable(EXTRA_CONVERSATION_ICON, Icon.class));
3021 
3022             // Extras for CallStyle (same reason for visiting without checking isStyle).
3023             Person callPerson = extras.getParcelable(EXTRA_CALL_PERSON, Person.class);
3024             if (callPerson != null) {
3025                 callPerson.visitUris(visitor);
3026             }
3027             visitIconUri(visitor, extras.getParcelable(EXTRA_VERIFICATION_ICON, Icon.class));
3028         }
3029 
3030         if (mBubbleMetadata != null) {
3031             visitIconUri(visitor, mBubbleMetadata.getIcon());
3032         }
3033 
3034         if (extras != null && extras.containsKey(WearableExtender.EXTRA_WEARABLE_EXTENSIONS)) {
3035             WearableExtender extender = new WearableExtender(this);
3036             extender.visitUris(visitor);
3037         }
3038     }
3039 
3040     /**
3041      * @hide
3042      */
loadHeaderAppName(Context context)3043     public String loadHeaderAppName(Context context) {
3044         Trace.beginSection("Notification#loadHeaderAppName");
3045 
3046         try {
3047             CharSequence name = null;
3048             // Check if there is a non-empty substitute app name and return that.
3049             if (extras.containsKey(EXTRA_SUBSTITUTE_APP_NAME)) {
3050                 name = extras.getString(EXTRA_SUBSTITUTE_APP_NAME);
3051                 if (!TextUtils.isEmpty(name)) {
3052                     return name.toString();
3053                 }
3054             }
3055             // If not, try getting the name from the app info.
3056             if (context == null) {
3057                 return null;
3058             }
3059             if (TextUtils.isEmpty(name)) {
3060                 ApplicationInfo info = getApplicationInfo(context);
3061                 if (info != null) {
3062                     final PackageManager pm = context.getPackageManager();
3063                     name = pm.getApplicationLabel(getApplicationInfo(context));
3064                 }
3065             }
3066             // If there's still nothing, ¯\_(ツ)_/¯
3067             if (TextUtils.isEmpty(name)) {
3068                 return null;
3069             }
3070             return name.toString();
3071         } finally {
3072             Trace.endSection();
3073         }
3074     }
3075 
3076     /**
3077      * Whether this notification was posted by a headless system app.
3078      *
3079      * If we don't have enough information to figure this out, this will return false. Therefore,
3080      * false negatives are possible, but false positives should not be.
3081      *
3082      * @hide
3083      */
belongsToHeadlessSystemApp(Context context)3084     public boolean belongsToHeadlessSystemApp(Context context) {
3085         Trace.beginSection("Notification#belongsToHeadlessSystemApp");
3086 
3087         try {
3088             if (mBelongsToHeadlessSystemApp != null) {
3089                 return mBelongsToHeadlessSystemApp;
3090             }
3091 
3092             if (context == null) {
3093                 // Without a valid context, we don't know exactly. Let's assume it doesn't belong to
3094                 // a system app, but not cache the value.
3095                 return false;
3096             }
3097 
3098             ApplicationInfo info = getApplicationInfo(context);
3099             if (info != null) {
3100                 if ((info.flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
3101                     // It's not a system app at all.
3102                     mBelongsToHeadlessSystemApp = false;
3103                 } else {
3104                     // If there's no launch intent, it's probably a headless app.
3105                     final PackageManager pm = context.getPackageManager();
3106                     mBelongsToHeadlessSystemApp = pm.getLaunchIntentForPackage(info.packageName)
3107                             == null;
3108                 }
3109             } else {
3110                 // If for some reason we don't have the app info, we don't know; best assume it's
3111                 // not a system app.
3112                 return false;
3113             }
3114             return mBelongsToHeadlessSystemApp;
3115         } finally {
3116             Trace.endSection();
3117         }
3118     }
3119 
3120     /**
3121      * Get the resource ID of the app icon from application info.
3122      * @hide
3123      */
getHeaderAppIconRes(Context context)3124     public int getHeaderAppIconRes(Context context) {
3125         ApplicationInfo info = getApplicationInfo(context);
3126         if (info != null) {
3127             return info.icon;
3128         }
3129         return 0;
3130     }
3131 
3132     /**
3133      * Load the app icon drawable from the package manager. This could result in a binder call.
3134      * @hide
3135      */
loadHeaderAppIcon(Context context)3136     public Drawable loadHeaderAppIcon(Context context) {
3137         Trace.beginSection("Notification#loadHeaderAppIcon");
3138 
3139         try {
3140             if (context == null) {
3141                 Log.e(TAG, "Cannot load the app icon drawable with a null context");
3142                 return null;
3143             }
3144             final PackageManager pm = context.getPackageManager();
3145             ApplicationInfo info = getApplicationInfo(context);
3146             if (info == null) {
3147                 Log.e(TAG, "Cannot load the app icon drawable: no application info");
3148                 return null;
3149             }
3150             return pm.getApplicationIcon(info);
3151         } finally {
3152             Trace.endSection();
3153         }
3154     }
3155 
3156     /**
3157      * Fetch the application info from the notification, or the context if that isn't available.
3158      */
getApplicationInfo(Context context)3159     private ApplicationInfo getApplicationInfo(Context context) {
3160         ApplicationInfo info = null;
3161         if (extras.containsKey(EXTRA_BUILDER_APPLICATION_INFO)) {
3162             info = extras.getParcelable(
3163                     EXTRA_BUILDER_APPLICATION_INFO,
3164                     ApplicationInfo.class);
3165         }
3166         if (info == null) {
3167             if (context == null) {
3168                 return null;
3169             }
3170             info = context.getApplicationInfo();
3171         }
3172         return info;
3173     }
3174 
3175     /**
3176      * Removes heavyweight parts of the Notification object for archival or for sending to
3177      * listeners when the full contents are not necessary.
3178      * @hide
3179      */
lightenPayload()3180     public final void lightenPayload() {
3181         tickerView = null;
3182         contentView = null;
3183         bigContentView = null;
3184         headsUpContentView = null;
3185         mLargeIcon = null;
3186         if (extras != null && !extras.isEmpty()) {
3187             final Set<String> keyset = extras.keySet();
3188             final int N = keyset.size();
3189             final String[] keys = keyset.toArray(new String[N]);
3190             for (int i=0; i<N; i++) {
3191                 final String key = keys[i];
3192                 if (TvExtender.EXTRA_TV_EXTENDER.equals(key)) {
3193                     continue;
3194                 }
3195                 final Object obj = extras.get(key);
3196                 if (obj != null &&
3197                     (  obj instanceof Parcelable
3198                     || obj instanceof Parcelable[]
3199                     || obj instanceof SparseArray
3200                     || obj instanceof ArrayList)) {
3201                     extras.remove(key);
3202                 }
3203             }
3204         }
3205     }
3206 
3207     /**
3208      * Make sure this CharSequence is safe to put into a bundle, which basically
3209      * means it had better not be some custom Parcelable implementation.
3210      * @hide
3211      */
safeCharSequence(CharSequence cs)3212     public static CharSequence safeCharSequence(CharSequence cs) {
3213         if (cs == null) return cs;
3214         if (cs.length() > MAX_CHARSEQUENCE_LENGTH) {
3215             cs = cs.subSequence(0, MAX_CHARSEQUENCE_LENGTH);
3216         }
3217         if (cs instanceof Parcelable) {
3218             Log.e(TAG, "warning: " + cs.getClass().getCanonicalName()
3219                     + " instance is a custom Parcelable and not allowed in Notification");
3220             return cs.toString();
3221         }
3222 
3223         return removeTextSizeSpans(cs);
3224     }
3225 
stripStyling(@ullable CharSequence cs)3226     private static CharSequence stripStyling(@Nullable CharSequence cs) {
3227         if (cs == null) {
3228             return cs;
3229         }
3230 
3231         return cs.toString();
3232     }
3233 
normalizeBigText(@ullable CharSequence charSequence)3234     private static CharSequence normalizeBigText(@Nullable CharSequence charSequence) {
3235         if (charSequence == null) {
3236             return charSequence;
3237         }
3238 
3239         return NotificationBigTextNormalizer.normalizeBigText(charSequence.toString());
3240     }
3241 
removeTextSizeSpans(CharSequence charSequence)3242     private static CharSequence removeTextSizeSpans(CharSequence charSequence) {
3243         if (charSequence instanceof Spanned) {
3244             Spanned ss = (Spanned) charSequence;
3245             Object[] spans = ss.getSpans(0, ss.length(), Object.class);
3246             SpannableStringBuilder builder = new SpannableStringBuilder(ss.toString());
3247             for (Object span : spans) {
3248                 Object resultSpan = span;
3249                 if (resultSpan instanceof CharacterStyle) {
3250                     resultSpan = ((CharacterStyle) span).getUnderlying();
3251                 }
3252                 if (resultSpan instanceof TextAppearanceSpan) {
3253                     TextAppearanceSpan originalSpan = (TextAppearanceSpan) resultSpan;
3254                     resultSpan = new TextAppearanceSpan(
3255                             originalSpan.getFamily(),
3256                             originalSpan.getTextStyle(),
3257                             -1,
3258                             originalSpan.getTextColor(),
3259                             originalSpan.getLinkTextColor());
3260                 } else if (resultSpan instanceof RelativeSizeSpan
3261                         || resultSpan instanceof AbsoluteSizeSpan) {
3262                     continue;
3263                 } else {
3264                     resultSpan = span;
3265                 }
3266                 builder.setSpan(resultSpan, ss.getSpanStart(span), ss.getSpanEnd(span),
3267                         ss.getSpanFlags(span));
3268             }
3269             return builder;
3270         }
3271         return charSequence;
3272     }
3273 
describeContents()3274     public int describeContents() {
3275         return 0;
3276     }
3277 
3278     /**
3279      * Flatten this notification into a parcel.
3280      */
writeToParcel(Parcel parcel, int flags)3281     public void writeToParcel(Parcel parcel, int flags) {
3282         // We need to mark all pending intents getting into the notification
3283         // system as being put there to later allow the notification ranker
3284         // to launch them and by doing so add the app to the battery saver white
3285         // list for a short period of time. The problem is that the system
3286         // cannot look into the extras as there may be parcelables there that
3287         // the platform does not know how to handle. To go around that we have
3288         // an explicit list of the pending intents in the extras bundle.
3289         PendingIntent.OnMarshaledListener addedListener = null;
3290         if (allPendingIntents == null) {
3291             addedListener = (PendingIntent intent, Parcel out, int outFlags) -> {
3292                 if (parcel == out) {
3293                     synchronized (this) {
3294                         if (allPendingIntents == null) {
3295                             allPendingIntents = new ArraySet<>();
3296                         }
3297                         allPendingIntents.add(intent);
3298                     }
3299                 }
3300             };
3301             PendingIntent.addOnMarshaledListener(addedListener);
3302         }
3303         try {
3304             if (Flags.secureAllowlistToken()) {
3305                 boolean mustClearCookie = false;
3306                 if (!parcel.hasClassCookie(Notification.class)) {
3307                     // This is the "root" notification, and not an "inner" notification (including
3308                     // publicVersion or anything else that might be embedded in extras). So we want
3309                     // to use its token for every inner notification (might be null).
3310                     parcel.setClassCookie(Notification.class, mAllowlistToken);
3311                     mustClearCookie = true;
3312                 }
3313                 try {
3314                     // IMPORTANT: Add marshaling code in writeToParcelImpl as we
3315                     // want to intercept all pending events written to the parcel.
3316                     writeToParcelImpl(parcel, flags);
3317                 } finally {
3318                     if (mustClearCookie) {
3319                         parcel.removeClassCookie(Notification.class, mAllowlistToken);
3320                     }
3321                 }
3322             } else {
3323                 // IMPORTANT: Add marshaling code in writeToParcelImpl as we
3324                 // want to intercept all pending events written to the parcel.
3325                 writeToParcelImpl(parcel, flags);
3326             }
3327 
3328             synchronized (this) {
3329                 // Must be written last!
3330                 parcel.writeArraySet(allPendingIntents);
3331             }
3332         } finally {
3333             if (addedListener != null) {
3334                 PendingIntent.removeOnMarshaledListener(addedListener);
3335             }
3336         }
3337     }
3338 
writeToParcelImpl(Parcel parcel, int flags)3339     private void writeToParcelImpl(Parcel parcel, int flags) {
3340         parcel.writeInt(1);
3341 
3342         if (Flags.secureAllowlistToken()) {
3343             // Always use the same token as the root notification (might be null).
3344             IBinder rootNotificationToken = (IBinder) parcel.getClassCookie(Notification.class);
3345             parcel.writeStrongBinder(rootNotificationToken);
3346         } else {
3347             parcel.writeStrongBinder(mAllowlistToken);
3348         }
3349 
3350         parcel.writeLong(when);
3351         parcel.writeLong(creationTime);
3352         if (mSmallIcon == null && icon != 0) {
3353             // you snuck an icon in here without using the builder; let's try to keep it
3354             mSmallIcon = Icon.createWithResource("", icon);
3355         }
3356         if (mSmallIcon != null) {
3357             parcel.writeInt(1);
3358             mSmallIcon.writeToParcel(parcel, 0);
3359         } else {
3360             parcel.writeInt(0);
3361         }
3362         parcel.writeInt(number);
3363         if (contentIntent != null) {
3364             parcel.writeInt(1);
3365             contentIntent.writeToParcel(parcel, 0);
3366         } else {
3367             parcel.writeInt(0);
3368         }
3369         if (deleteIntent != null) {
3370             parcel.writeInt(1);
3371             deleteIntent.writeToParcel(parcel, 0);
3372         } else {
3373             parcel.writeInt(0);
3374         }
3375         if (tickerText != null) {
3376             parcel.writeInt(1);
3377             TextUtils.writeToParcel(tickerText, parcel, flags);
3378         } else {
3379             parcel.writeInt(0);
3380         }
3381         if (tickerView != null) {
3382             parcel.writeInt(1);
3383             tickerView.writeToParcel(parcel, 0);
3384         } else {
3385             parcel.writeInt(0);
3386         }
3387         if (contentView != null) {
3388             parcel.writeInt(1);
3389             contentView.writeToParcel(parcel, 0);
3390         } else {
3391             parcel.writeInt(0);
3392         }
3393         if (mLargeIcon == null && largeIcon != null) {
3394             // you snuck an icon in here without using the builder; let's try to keep it
3395             mLargeIcon = Icon.createWithBitmap(largeIcon);
3396         }
3397         if (mLargeIcon != null) {
3398             parcel.writeInt(1);
3399             mLargeIcon.writeToParcel(parcel, 0);
3400         } else {
3401             parcel.writeInt(0);
3402         }
3403 
3404         parcel.writeInt(defaults);
3405         parcel.writeInt(this.flags);
3406 
3407         if (sound != null) {
3408             parcel.writeInt(1);
3409             sound.writeToParcel(parcel, 0);
3410         } else {
3411             parcel.writeInt(0);
3412         }
3413         parcel.writeInt(audioStreamType);
3414 
3415         if (audioAttributes != null) {
3416             parcel.writeInt(1);
3417             audioAttributes.writeToParcel(parcel, 0);
3418         } else {
3419             parcel.writeInt(0);
3420         }
3421 
3422         parcel.writeLongArray(vibrate);
3423         parcel.writeInt(ledARGB);
3424         parcel.writeInt(ledOnMS);
3425         parcel.writeInt(ledOffMS);
3426         parcel.writeInt(iconLevel);
3427 
3428         if (fullScreenIntent != null) {
3429             parcel.writeInt(1);
3430             fullScreenIntent.writeToParcel(parcel, 0);
3431         } else {
3432             parcel.writeInt(0);
3433         }
3434 
3435         parcel.writeInt(priority);
3436 
3437         parcel.writeString8(category);
3438 
3439         parcel.writeString8(mGroupKey);
3440 
3441         parcel.writeString8(mSortKey);
3442 
3443         parcel.writeBundle(extras); // null ok
3444 
3445         parcel.writeTypedArray(actions, 0); // null ok
3446 
3447         if (bigContentView != null) {
3448             parcel.writeInt(1);
3449             bigContentView.writeToParcel(parcel, 0);
3450         } else {
3451             parcel.writeInt(0);
3452         }
3453 
3454         if (headsUpContentView != null) {
3455             parcel.writeInt(1);
3456             headsUpContentView.writeToParcel(parcel, 0);
3457         } else {
3458             parcel.writeInt(0);
3459         }
3460 
3461         parcel.writeInt(visibility);
3462 
3463         if (publicVersion != null) {
3464             parcel.writeInt(1);
3465             publicVersion.writeToParcel(parcel, 0);
3466         } else {
3467             parcel.writeInt(0);
3468         }
3469 
3470         parcel.writeInt(color);
3471 
3472         if (mChannelId != null) {
3473             parcel.writeInt(1);
3474             parcel.writeString8(mChannelId);
3475         } else {
3476             parcel.writeInt(0);
3477         }
3478         parcel.writeLong(mTimeout);
3479 
3480         if (mShortcutId != null) {
3481             parcel.writeInt(1);
3482             parcel.writeString8(mShortcutId);
3483         } else {
3484             parcel.writeInt(0);
3485         }
3486 
3487         if (mLocusId != null) {
3488             parcel.writeInt(1);
3489             mLocusId.writeToParcel(parcel, 0);
3490         } else {
3491             parcel.writeInt(0);
3492         }
3493 
3494         parcel.writeInt(mBadgeIcon);
3495 
3496         if (mSettingsText != null) {
3497             parcel.writeInt(1);
3498             TextUtils.writeToParcel(mSettingsText, parcel, flags);
3499         } else {
3500             parcel.writeInt(0);
3501         }
3502 
3503         parcel.writeInt(mGroupAlertBehavior);
3504 
3505         if (mBubbleMetadata != null) {
3506             parcel.writeInt(1);
3507             mBubbleMetadata.writeToParcel(parcel, 0);
3508         } else {
3509             parcel.writeInt(0);
3510         }
3511 
3512         parcel.writeBoolean(mAllowSystemGeneratedContextualActions);
3513 
3514         parcel.writeInt(mFgsDeferBehavior);
3515 
3516         // mUsesStandardHeader is not written because it should be recomputed in listeners
3517     }
3518 
3519     /**
3520      * Parcelable.Creator that instantiates Notification objects
3521      */
3522     public static final @android.annotation.NonNull Parcelable.Creator<Notification> CREATOR
3523             = new Parcelable.Creator<Notification>()
3524     {
3525         public Notification createFromParcel(Parcel parcel)
3526         {
3527             return new Notification(parcel);
3528         }
3529 
3530         public Notification[] newArray(int size)
3531         {
3532             return new Notification[size];
3533         }
3534     };
3535 
3536     /**
3537      * @hide
3538      */
areActionsVisiblyDifferent(Notification first, Notification second)3539     public static boolean areActionsVisiblyDifferent(Notification first, Notification second) {
3540         Notification.Action[] firstAs = first.actions;
3541         Notification.Action[] secondAs = second.actions;
3542         if (firstAs == null && secondAs != null || firstAs != null && secondAs == null) {
3543             return true;
3544         }
3545         if (firstAs != null && secondAs != null) {
3546             if (firstAs.length != secondAs.length) {
3547                 return true;
3548             }
3549             for (int i = 0; i < firstAs.length; i++) {
3550                 if (!Objects.equals(String.valueOf(firstAs[i].title),
3551                         String.valueOf(secondAs[i].title))) {
3552                     return true;
3553                 }
3554                 RemoteInput[] firstRs = firstAs[i].getRemoteInputs();
3555                 RemoteInput[] secondRs = secondAs[i].getRemoteInputs();
3556                 if (firstRs == null) {
3557                     firstRs = new RemoteInput[0];
3558                 }
3559                 if (secondRs == null) {
3560                     secondRs = new RemoteInput[0];
3561                 }
3562                 if (firstRs.length != secondRs.length) {
3563                     return true;
3564                 }
3565                 for (int j = 0; j < firstRs.length; j++) {
3566                     if (!Objects.equals(String.valueOf(firstRs[j].getLabel()),
3567                             String.valueOf(secondRs[j].getLabel()))) {
3568                         return true;
3569                     }
3570                 }
3571             }
3572         }
3573         return false;
3574     }
3575 
3576     /**
3577      * @hide
3578      */
areIconsDifferent(Notification first, Notification second)3579     public static boolean areIconsDifferent(Notification first, Notification second) {
3580         return areIconsMaybeDifferent(first.getSmallIcon(), second.getSmallIcon())
3581                 || areIconsMaybeDifferent(first.getLargeIcon(), second.getLargeIcon());
3582     }
3583 
3584     /**
3585      * Note that we aren't actually comparing the contents of the bitmaps here; this is only a
3586      * cursory inspection. We will not return false negatives, but false positives are likely.
3587      */
areIconsMaybeDifferent(Icon a, Icon b)3588     private static boolean areIconsMaybeDifferent(Icon a, Icon b) {
3589         if (a == b) {
3590             return false;
3591         }
3592         if (a == null || b == null) {
3593             return true;
3594         }
3595         if (a.sameAs(b)) {
3596             return false;
3597         }
3598         final int aType = a.getType();
3599         if (aType != b.getType()) {
3600             return true;
3601         }
3602         if (aType == Icon.TYPE_BITMAP || aType == Icon.TYPE_ADAPTIVE_BITMAP) {
3603             final Bitmap aBitmap = a.getBitmap();
3604             final Bitmap bBitmap = b.getBitmap();
3605             return aBitmap.getWidth() != bBitmap.getWidth()
3606                     || aBitmap.getHeight() != bBitmap.getHeight()
3607                     || aBitmap.getConfig() != bBitmap.getConfig()
3608                     || aBitmap.getGenerationId() != bBitmap.getGenerationId();
3609         }
3610         return true;
3611     }
3612 
3613     /**
3614      * @hide
3615      */
areStyledNotificationsVisiblyDifferent(Builder first, Builder second)3616     public static boolean areStyledNotificationsVisiblyDifferent(Builder first, Builder second) {
3617         if (first.getStyle() == null) {
3618             return second.getStyle() != null;
3619         }
3620         if (second.getStyle() == null) {
3621             return true;
3622         }
3623         return first.getStyle().areNotificationsVisiblyDifferent(second.getStyle());
3624     }
3625 
3626     /**
3627      * @hide
3628      */
areRemoteViewsChanged(Builder first, Builder second)3629     public static boolean areRemoteViewsChanged(Builder first, Builder second) {
3630         if (!Objects.equals(first.usesStandardHeader(), second.usesStandardHeader())) {
3631             return true;
3632         }
3633 
3634         if (areRemoteViewsChanged(first.mN.contentView, second.mN.contentView)) {
3635             return true;
3636         }
3637         if (areRemoteViewsChanged(first.mN.bigContentView, second.mN.bigContentView)) {
3638             return true;
3639         }
3640         if (areRemoteViewsChanged(first.mN.headsUpContentView, second.mN.headsUpContentView)) {
3641             return true;
3642         }
3643 
3644         return false;
3645     }
3646 
areRemoteViewsChanged(RemoteViews first, RemoteViews second)3647     private static boolean areRemoteViewsChanged(RemoteViews first, RemoteViews second) {
3648         if (first == null && second == null) {
3649             return false;
3650         }
3651         if (first == null && second != null || first != null && second == null) {
3652             return true;
3653         }
3654 
3655         if (!Objects.equals(first.getLayoutId(), second.getLayoutId())) {
3656             return true;
3657         }
3658 
3659         if (!Objects.equals(first.getSequenceNumber(), second.getSequenceNumber())) {
3660             return true;
3661         }
3662 
3663         return false;
3664     }
3665 
3666     /**
3667      * Parcelling creates multiple copies of objects in {@code extras}. Fix them.
3668      * <p>
3669      * For backwards compatibility {@code extras} holds some references to "real" member data such
3670      * as {@link getLargeIcon()} which is mirrored by {@link #EXTRA_LARGE_ICON}. This is mostly
3671      * fine as long as the object stays in one process.
3672      * <p>
3673      * However, once the notification goes into a parcel each reference gets marshalled separately,
3674      * wasting memory. Especially with large images on Auto and TV, this is worth fixing.
3675      */
fixDuplicateExtras()3676     private void fixDuplicateExtras() {
3677         if (extras != null) {
3678             fixDuplicateExtra(mLargeIcon, EXTRA_LARGE_ICON);
3679         }
3680     }
3681 
3682     /**
3683      * If we find an extra that's exactly the same as one of the "real" fields but refers to a
3684      * separate object, replace it with the field's version to avoid holding duplicate copies.
3685      */
fixDuplicateExtra(@ullable Parcelable original, @NonNull String extraName)3686     private void fixDuplicateExtra(@Nullable Parcelable original, @NonNull String extraName) {
3687         if (original != null && extras.getParcelable(extraName, Parcelable.class) != null) {
3688             extras.putParcelable(extraName, original);
3689         }
3690     }
3691 
3692     /**
3693      * Sets the {@link #contentView} field to be a view with the standard "Latest Event"
3694      * layout.
3695      *
3696      * <p>Uses the {@link #icon} and {@link #when} fields to set the icon and time fields
3697      * in the view.</p>
3698      * @param context       The context for your application / activity.
3699      * @param contentTitle The title that goes in the expanded entry.
3700      * @param contentText  The text that goes in the expanded entry.
3701      * @param contentIntent The intent to launch when the user clicks the expanded notification.
3702      * If this is an activity, it must include the
3703      * {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK} flag, which requires
3704      * that you take care of task management as described in the
3705      * <a href="{@docRoot}guide/topics/fundamentals/tasks-and-back-stack.html">Tasks and Back
3706      * Stack</a> document.
3707      *
3708      * @deprecated Use {@link Builder} instead.
3709      * @removed
3710      */
3711     @Deprecated
setLatestEventInfo(Context context, CharSequence contentTitle, CharSequence contentText, PendingIntent contentIntent)3712     public void setLatestEventInfo(Context context,
3713             CharSequence contentTitle, CharSequence contentText, PendingIntent contentIntent) {
3714         if (context.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1){
3715             Log.e(TAG, "setLatestEventInfo() is deprecated and you should feel deprecated.",
3716                     new Throwable());
3717         }
3718 
3719         if (context.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N) {
3720             extras.putBoolean(EXTRA_SHOW_WHEN, true);
3721         }
3722 
3723         // ensure that any information already set directly is preserved
3724         final Notification.Builder builder = new Notification.Builder(context, this);
3725 
3726         // now apply the latestEventInfo fields
3727         if (contentTitle != null) {
3728             builder.setContentTitle(contentTitle);
3729         }
3730         if (contentText != null) {
3731             builder.setContentText(contentText);
3732         }
3733         builder.setContentIntent(contentIntent);
3734 
3735         builder.build(); // callers expect this notification to be ready to use
3736     }
3737 
3738     /**
3739      * Sets the token used for background operations for the pending intents associated with this
3740      * notification.
3741      *
3742      * Note: Should <em>only</em> be invoked by NotificationManagerService, since this is normally
3743      * populated by unparceling (and also used there). Any other usage is suspect.
3744      *
3745      * @hide
3746      */
overrideAllowlistToken(IBinder token)3747     public void overrideAllowlistToken(IBinder token) {
3748         mAllowlistToken = token;
3749         if (publicVersion != null) {
3750             publicVersion.overrideAllowlistToken(token);
3751         }
3752     }
3753 
3754     /** @hide */
getAllowlistToken()3755     public IBinder getAllowlistToken() {
3756         return mAllowlistToken;
3757     }
3758 
3759     /**
3760      * @hide
3761      */
addFieldsFromContext(Context context, Notification notification)3762     public static void addFieldsFromContext(Context context, Notification notification) {
3763         addFieldsFromContext(context.getApplicationInfo(), notification);
3764     }
3765 
3766     /**
3767      * @hide
3768      */
addFieldsFromContext(ApplicationInfo ai, Notification notification)3769     public static void addFieldsFromContext(ApplicationInfo ai, Notification notification) {
3770         notification.extras.putParcelable(EXTRA_BUILDER_APPLICATION_INFO, ai);
3771     }
3772 
3773     /**
3774      * @hide
3775      */
dumpDebug(ProtoOutputStream proto, long fieldId)3776     public void dumpDebug(ProtoOutputStream proto, long fieldId) {
3777         long token = proto.start(fieldId);
3778         proto.write(NotificationProto.CHANNEL_ID, getChannelId());
3779         proto.write(NotificationProto.HAS_TICKER_TEXT, this.tickerText != null);
3780         proto.write(NotificationProto.FLAGS, this.flags);
3781         proto.write(NotificationProto.COLOR, this.color);
3782         proto.write(NotificationProto.CATEGORY, this.category);
3783         proto.write(NotificationProto.GROUP_KEY, this.mGroupKey);
3784         proto.write(NotificationProto.SORT_KEY, this.mSortKey);
3785         if (this.actions != null) {
3786             proto.write(NotificationProto.ACTION_LENGTH, this.actions.length);
3787         }
3788         if (this.visibility >= VISIBILITY_SECRET && this.visibility <= VISIBILITY_PUBLIC) {
3789             proto.write(NotificationProto.VISIBILITY, this.visibility);
3790         }
3791         if (publicVersion != null) {
3792             publicVersion.dumpDebug(proto, NotificationProto.PUBLIC_VERSION);
3793         }
3794         proto.end(token);
3795     }
3796 
3797     @Override
toString()3798     public String toString() {
3799         StringBuilder sb = new StringBuilder();
3800         sb.append("Notification(channel=");
3801         sb.append(getChannelId());
3802         sb.append(" shortcut=");
3803         sb.append(getShortcutId());
3804         sb.append(" contentView=");
3805         if (contentView != null) {
3806             sb.append(contentView.getPackage());
3807             sb.append("/0x");
3808             sb.append(Integer.toHexString(contentView.getLayoutId()));
3809         } else {
3810             sb.append("null");
3811         }
3812         sb.append(" vibrate=");
3813         if ((this.defaults & DEFAULT_VIBRATE) != 0) {
3814             sb.append("default");
3815         } else if (this.vibrate != null) {
3816             int N = this.vibrate.length-1;
3817             sb.append("[");
3818             for (int i=0; i<N; i++) {
3819                 sb.append(this.vibrate[i]);
3820                 sb.append(',');
3821             }
3822             if (N != -1) {
3823                 sb.append(this.vibrate[N]);
3824             }
3825             sb.append("]");
3826         } else {
3827             sb.append("null");
3828         }
3829         sb.append(" sound=");
3830         if ((this.defaults & DEFAULT_SOUND) != 0) {
3831             sb.append("default");
3832         } else if (this.sound != null) {
3833             sb.append(this.sound.toString());
3834         } else {
3835             sb.append("null");
3836         }
3837         if (this.tickerText != null) {
3838             sb.append(" tick");
3839         }
3840         sb.append(" defaults=");
3841         sb.append(defaultsToString(this.defaults));
3842         sb.append(" flags=");
3843         sb.append(flagsToString(this.flags));
3844         sb.append(String.format(" color=0x%08x", this.color));
3845         if (this.category != null) {
3846             sb.append(" category=");
3847             sb.append(this.category);
3848         }
3849         if (this.mGroupKey != null) {
3850             sb.append(" groupKey=");
3851             sb.append(this.mGroupKey);
3852         }
3853         if (this.mSortKey != null) {
3854             sb.append(" sortKey=");
3855             sb.append(this.mSortKey);
3856         }
3857         if (actions != null) {
3858             sb.append(" actions=");
3859             sb.append(actions.length);
3860         }
3861         sb.append(" vis=");
3862         sb.append(visibilityToString(this.visibility));
3863         if (this.publicVersion != null) {
3864             sb.append(" publicVersion=");
3865             sb.append(publicVersion.toString());
3866         }
3867         if (this.mLocusId != null) {
3868             sb.append(" locusId=");
3869             sb.append(this.mLocusId); // LocusId.toString() is PII safe.
3870         }
3871         sb.append(")");
3872         return sb.toString();
3873     }
3874 
3875     /**
3876      * {@hide}
3877      */
visibilityToString(int vis)3878     public static String visibilityToString(int vis) {
3879         switch (vis) {
3880             case VISIBILITY_PRIVATE:
3881                 return "PRIVATE";
3882             case VISIBILITY_PUBLIC:
3883                 return "PUBLIC";
3884             case VISIBILITY_SECRET:
3885                 return "SECRET";
3886             default:
3887                 return "UNKNOWN(" + String.valueOf(vis) + ")";
3888         }
3889     }
3890 
3891     /**
3892      * {@hide}
3893      */
priorityToString(@riority int pri)3894     public static String priorityToString(@Priority int pri) {
3895         switch (pri) {
3896             case PRIORITY_MIN:
3897                 return "MIN";
3898             case PRIORITY_LOW:
3899                 return "LOW";
3900             case PRIORITY_DEFAULT:
3901                 return "DEFAULT";
3902             case PRIORITY_HIGH:
3903                 return "HIGH";
3904             case PRIORITY_MAX:
3905                 return "MAX";
3906             default:
3907                 return "UNKNOWN(" + String.valueOf(pri) + ")";
3908         }
3909     }
3910 
3911     /**
3912      * {@hide}
3913      */
flagsToString(@otificationFlags int flags)3914     public static String flagsToString(@NotificationFlags int flags) {
3915         final List<String> flagStrings = new ArrayList<String>();
3916         if ((flags & FLAG_SHOW_LIGHTS) != 0) {
3917             flagStrings.add("SHOW_LIGHTS");
3918             flags &= ~FLAG_SHOW_LIGHTS;
3919         }
3920         if ((flags & FLAG_ONGOING_EVENT) != 0) {
3921             flagStrings.add("ONGOING_EVENT");
3922             flags &= ~FLAG_ONGOING_EVENT;
3923         }
3924         if ((flags & FLAG_INSISTENT) != 0) {
3925             flagStrings.add("INSISTENT");
3926             flags &= ~FLAG_INSISTENT;
3927         }
3928         if ((flags & FLAG_ONLY_ALERT_ONCE) != 0) {
3929             flagStrings.add("ONLY_ALERT_ONCE");
3930             flags &= ~FLAG_ONLY_ALERT_ONCE;
3931         }
3932         if ((flags & FLAG_AUTO_CANCEL) != 0) {
3933             flagStrings.add("AUTO_CANCEL");
3934             flags &= ~FLAG_AUTO_CANCEL;
3935         }
3936         if ((flags & FLAG_NO_CLEAR) != 0) {
3937             flagStrings.add("NO_CLEAR");
3938             flags &= ~FLAG_NO_CLEAR;
3939         }
3940         if ((flags & FLAG_FOREGROUND_SERVICE) != 0) {
3941             flagStrings.add("FOREGROUND_SERVICE");
3942             flags &= ~FLAG_FOREGROUND_SERVICE;
3943         }
3944         if ((flags & FLAG_HIGH_PRIORITY) != 0) {
3945             flagStrings.add("HIGH_PRIORITY");
3946             flags &= ~FLAG_HIGH_PRIORITY;
3947         }
3948         if ((flags & FLAG_LOCAL_ONLY) != 0) {
3949             flagStrings.add("LOCAL_ONLY");
3950             flags &= ~FLAG_LOCAL_ONLY;
3951         }
3952         if ((flags & FLAG_GROUP_SUMMARY) != 0) {
3953             flagStrings.add("GROUP_SUMMARY");
3954             flags &= ~FLAG_GROUP_SUMMARY;
3955         }
3956         if ((flags & FLAG_AUTOGROUP_SUMMARY) != 0) {
3957             flagStrings.add("AUTOGROUP_SUMMARY");
3958             flags &= ~FLAG_AUTOGROUP_SUMMARY;
3959         }
3960         if ((flags & FLAG_CAN_COLORIZE) != 0) {
3961             flagStrings.add("CAN_COLORIZE");
3962             flags &= ~FLAG_CAN_COLORIZE;
3963         }
3964         if ((flags & FLAG_BUBBLE) != 0) {
3965             flagStrings.add("BUBBLE");
3966             flags &= ~FLAG_BUBBLE;
3967         }
3968         if ((flags & FLAG_NO_DISMISS) != 0) {
3969             flagStrings.add("NO_DISMISS");
3970             flags &= ~FLAG_NO_DISMISS;
3971         }
3972         if ((flags & FLAG_FSI_REQUESTED_BUT_DENIED) != 0) {
3973             flagStrings.add("FSI_REQUESTED_BUT_DENIED");
3974             flags &= ~FLAG_FSI_REQUESTED_BUT_DENIED;
3975         }
3976         if ((flags & FLAG_USER_INITIATED_JOB) != 0) {
3977             flagStrings.add("USER_INITIATED_JOB");
3978             flags &= ~FLAG_USER_INITIATED_JOB;
3979         }
3980         if (Flags.lifetimeExtensionRefactor()) {
3981             if ((flags & FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY) != 0) {
3982                 flagStrings.add("LIFETIME_EXTENDED_BY_DIRECT_REPLY");
3983                 flags &= ~FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY;
3984             }
3985         }
3986 
3987         if (flagStrings.isEmpty()) {
3988             return "0";
3989         }
3990 
3991         if (flags != 0) {
3992             flagStrings.add(String.format("UNKNOWN(0x%08x)", flags));
3993         }
3994 
3995         return String.join("|", flagStrings);
3996     }
3997 
3998     /** @hide */
defaultsToString(int defaults)3999     public static String defaultsToString(int defaults) {
4000         final List<String> defaultStrings = new ArrayList<String>();
4001         if ((defaults & DEFAULT_ALL) == DEFAULT_ALL) {
4002             defaultStrings.add("ALL");
4003             defaults &= ~DEFAULT_ALL;
4004         }
4005         if ((defaults & DEFAULT_SOUND) != 0) {
4006             defaultStrings.add("SOUND");
4007             defaults &= ~DEFAULT_SOUND;
4008         }
4009         if ((defaults & DEFAULT_VIBRATE) != 0) {
4010             defaultStrings.add("VIBRATE");
4011             defaults &= ~DEFAULT_VIBRATE;
4012         }
4013         if ((defaults & DEFAULT_LIGHTS) != 0) {
4014             defaultStrings.add("LIGHTS");
4015             defaults &= ~DEFAULT_LIGHTS;
4016         }
4017 
4018         if (defaultStrings.isEmpty()) {
4019             return "0";
4020         }
4021 
4022         if (defaults != 0) {
4023             defaultStrings.add(String.format("UNKNOWN(0x%08x)", defaults));
4024         }
4025 
4026         return String.join("|", defaultStrings);
4027     }
4028 
4029     /**
4030      * @hide
4031      */
hasCompletedProgress()4032     public boolean hasCompletedProgress() {
4033         // not a progress notification; can't be complete
4034         if (!extras.containsKey(EXTRA_PROGRESS)
4035                 || !extras.containsKey(EXTRA_PROGRESS_MAX)) {
4036             return false;
4037         }
4038         // many apps use max 0 for 'indeterminate'; not complete
4039         if (extras.getInt(EXTRA_PROGRESS_MAX) == 0) {
4040             return false;
4041         }
4042         return extras.getInt(EXTRA_PROGRESS) == extras.getInt(EXTRA_PROGRESS_MAX);
4043     }
4044 
4045     /** @removed */
4046     @Deprecated
getChannel()4047     public String getChannel() {
4048         return mChannelId;
4049     }
4050 
4051     /**
4052      * Returns the id of the channel this notification posts to.
4053      */
getChannelId()4054     public String getChannelId() {
4055         return mChannelId;
4056     }
4057 
4058     /** @removed */
4059     @Deprecated
getTimeout()4060     public long getTimeout() {
4061         return mTimeout;
4062     }
4063 
4064     /**
4065      * Returns the duration from posting after which this notification should be canceled by the
4066      * system, if it's not canceled already.
4067      */
getTimeoutAfter()4068     public long getTimeoutAfter() {
4069         return mTimeout;
4070     }
4071 
4072     /**
4073      * @hide
4074      */
setTimeoutAfter(long timeout)4075     public void setTimeoutAfter(long timeout) {
4076         mTimeout = timeout;
4077     }
4078 
4079     /**
4080      * Returns what icon should be shown for this notification if it is being displayed in a
4081      * Launcher that supports badging. Will be one of {@link #BADGE_ICON_NONE},
4082      * {@link #BADGE_ICON_SMALL}, or {@link #BADGE_ICON_LARGE}.
4083      */
getBadgeIconType()4084     public int getBadgeIconType() {
4085         return mBadgeIcon;
4086     }
4087 
4088     /**
4089      * Returns the {@link ShortcutInfo#getId() id} that this notification supersedes, if any.
4090      *
4091      * <p>Used by some Launchers that display notification content to hide shortcuts that duplicate
4092      * notifications.
4093      */
getShortcutId()4094     public String getShortcutId() {
4095         return mShortcutId;
4096     }
4097 
4098     /**
4099      * Gets the {@link LocusId} associated with this notification.
4100      *
4101      * <p>Used by the device's intelligence services to correlate objects (such as
4102      * {@link ShortcutInfo} and {@link ContentCaptureContext}) that are correlated.
4103      */
4104     @Nullable
getLocusId()4105     public LocusId getLocusId() {
4106         return mLocusId;
4107     }
4108 
4109     /**
4110      * Returns the settings text provided to {@link Builder#setSettingsText(CharSequence)}.
4111      */
getSettingsText()4112     public CharSequence getSettingsText() {
4113         return mSettingsText;
4114     }
4115 
4116     /**
4117      * Returns which type of notifications in a group are responsible for audibly alerting the
4118      * user. See {@link #GROUP_ALERT_ALL}, {@link #GROUP_ALERT_CHILDREN},
4119      * {@link #GROUP_ALERT_SUMMARY}.
4120      */
getGroupAlertBehavior()4121     public @GroupAlertBehavior int getGroupAlertBehavior() {
4122         return mGroupAlertBehavior;
4123     }
4124 
4125     /**
4126      * Returns the bubble metadata that will be used to display app content in a floating window
4127      * over the existing foreground activity.
4128      */
4129     @Nullable
getBubbleMetadata()4130     public BubbleMetadata getBubbleMetadata() {
4131         return mBubbleMetadata;
4132     }
4133 
4134     /**
4135      * Sets the {@link BubbleMetadata} for this notification.
4136      * @hide
4137      */
setBubbleMetadata(BubbleMetadata data)4138     public void setBubbleMetadata(BubbleMetadata data) {
4139         mBubbleMetadata = data;
4140     }
4141 
4142     /**
4143      * Returns whether the platform is allowed (by the app developer) to generate contextual actions
4144      * for this notification.
4145      */
getAllowSystemGeneratedContextualActions()4146     public boolean getAllowSystemGeneratedContextualActions() {
4147         return mAllowSystemGeneratedContextualActions;
4148     }
4149 
4150     /**
4151      * The small icon representing this notification in the status bar and content view.
4152      *
4153      * @return the small icon representing this notification.
4154      *
4155      * @see Builder#getSmallIcon()
4156      * @see Builder#setSmallIcon(Icon)
4157      */
getSmallIcon()4158     public Icon getSmallIcon() {
4159         return mSmallIcon;
4160     }
4161 
4162     /**
4163      * Used when notifying to clean up legacy small icons.
4164      * @hide
4165      */
4166     @UnsupportedAppUsage
setSmallIcon(Icon icon)4167     public void setSmallIcon(Icon icon) {
4168         mSmallIcon = icon;
4169     }
4170 
4171     /**
4172      * The colored app icon that can replace the small icon in the notification starting in V.
4173      *
4174      * Before using this value, you should first check whether it's actually being used by the
4175      * notification by calling {@link Notification#shouldUseAppIcon()}.
4176      *
4177      * @hide
4178      */
getAppIcon()4179     public Icon getAppIcon() {
4180         if (mAppIcon != null) {
4181             return mAppIcon;
4182         }
4183         // If the app icon hasn't been loaded yet, check if we can load it without a context.
4184         if (extras.containsKey(EXTRA_BUILDER_APPLICATION_INFO)) {
4185             final ApplicationInfo info = extras.getParcelable(
4186                     EXTRA_BUILDER_APPLICATION_INFO,
4187                     ApplicationInfo.class);
4188             if (info != null) {
4189                 int appIconRes = info.icon;
4190                 if (appIconRes == 0) {
4191                     Log.w(TAG, "Failed to get the app icon: no icon in application info");
4192                     return null;
4193                 }
4194                 mAppIcon = Icon.createWithResource(info.packageName, appIconRes);
4195                 return mAppIcon;
4196             } else {
4197                 Log.e(TAG, "Failed to get the app icon: "
4198                         + "there's an EXTRA_BUILDER_APPLICATION_INFO in extras but it's null");
4199             }
4200         } else {
4201             Log.w(TAG, "Failed to get the app icon: no application info in extras");
4202         }
4203         return null;
4204     }
4205 
4206     /**
4207      * Whether the notification is using the app icon instead of the small icon.
4208      * @hide
4209      */
shouldUseAppIcon()4210     public boolean shouldUseAppIcon() {
4211         if (Flags.notificationsUseAppIconInRow()) {
4212             if (belongsToHeadlessSystemApp(/* context = */ null)) {
4213                 return false;
4214             }
4215             return getAppIcon() != null;
4216         }
4217         return false;
4218     }
4219 
4220     /**
4221      * The large icon shown in this notification's content view.
4222      * @see Builder#getLargeIcon()
4223      * @see Builder#setLargeIcon(Icon)
4224      */
getLargeIcon()4225     public Icon getLargeIcon() {
4226         return mLargeIcon;
4227     }
4228 
4229     /**
4230      * @hide
4231      */
hasAppProvidedWhen()4232     public boolean hasAppProvidedWhen() {
4233         return when != 0 && when != creationTime;
4234     }
4235 
4236     /**
4237      * @hide
4238      */
4239     @UnsupportedAppUsage
isGroupSummary()4240     public boolean isGroupSummary() {
4241         return mGroupKey != null && (flags & FLAG_GROUP_SUMMARY) != 0;
4242     }
4243 
4244     /**
4245      * @hide
4246      */
4247     @UnsupportedAppUsage
isGroupChild()4248     public boolean isGroupChild() {
4249         return mGroupKey != null && (flags & FLAG_GROUP_SUMMARY) == 0;
4250     }
4251 
4252     /**
4253      * @hide
4254      */
suppressAlertingDueToGrouping()4255     public boolean suppressAlertingDueToGrouping() {
4256         if (isGroupSummary()
4257                 && getGroupAlertBehavior() == Notification.GROUP_ALERT_CHILDREN) {
4258             return true;
4259         } else if (isGroupChild()
4260                 && getGroupAlertBehavior() == Notification.GROUP_ALERT_SUMMARY) {
4261             return true;
4262         }
4263         return false;
4264     }
4265 
4266 
4267     /**
4268      * Finds and returns a remote input and its corresponding action.
4269      *
4270      * @param requiresFreeform requires the remoteinput to allow freeform or not.
4271      * @return the result pair, {@code null} if no result is found.
4272      */
4273     @Nullable
findRemoteInputActionPair(boolean requiresFreeform)4274     public Pair<RemoteInput, Action> findRemoteInputActionPair(boolean requiresFreeform) {
4275         if (actions == null) {
4276             return null;
4277         }
4278         for (Notification.Action action : actions) {
4279             if (action.getRemoteInputs() == null) {
4280                 continue;
4281             }
4282             RemoteInput resultRemoteInput = null;
4283             for (RemoteInput remoteInput : action.getRemoteInputs()) {
4284                 if (remoteInput.getAllowFreeFormInput() || !requiresFreeform) {
4285                     resultRemoteInput = remoteInput;
4286                 }
4287             }
4288             if (resultRemoteInput != null) {
4289                 return Pair.create(resultRemoteInput, action);
4290             }
4291         }
4292         return null;
4293     }
4294 
4295     /**
4296      * Returns the actions that are contextual (that is, suggested because of the content of the
4297      * notification) out of the actions in this notification.
4298      */
getContextualActions()4299     public @NonNull List<Notification.Action> getContextualActions() {
4300         if (actions == null) return Collections.emptyList();
4301 
4302         List<Notification.Action> contextualActions = new ArrayList<>();
4303         for (Notification.Action action : actions) {
4304             if (action.isContextual()) {
4305                 contextualActions.add(action);
4306             }
4307         }
4308         return contextualActions;
4309     }
4310 
4311     /**
4312      * Builder class for {@link Notification} objects.
4313      *
4314      * Provides a convenient way to set the various fields of a {@link Notification} and generate
4315      * content views using the platform's notification layout template. If your app supports
4316      * versions of Android as old as API level 4, you can instead use
4317      * {@link androidx.core.app.NotificationCompat.Builder NotificationCompat.Builder},
4318      * available in the <a href="{@docRoot}tools/extras/support-library.html">Android Support
4319      * library</a>.
4320      *
4321      * <p>Example:
4322      *
4323      * <pre class="prettyprint">
4324      * Notification noti = new Notification.Builder(mContext)
4325      *         .setContentTitle(&quot;New mail from &quot; + sender.toString())
4326      *         .setContentText(subject)
4327      *         .setSmallIcon(R.drawable.new_mail)
4328      *         .setLargeIcon(aBitmap)
4329      *         .build();
4330      * </pre>
4331      */
4332     public static class Builder {
4333         /**
4334          * @hide
4335          */
4336         public static final String EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT =
4337                 "android.rebuild.contentViewActionCount";
4338         /**
4339          * @hide
4340          */
4341         public static final String EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT
4342                 = "android.rebuild.bigViewActionCount";
4343         /**
4344          * @hide
4345          */
4346         public static final String EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT
4347                 = "android.rebuild.hudViewActionCount";
4348 
4349         private static final boolean USE_ONLY_TITLE_IN_LOW_PRIORITY_SUMMARY =
4350                 SystemProperties.getBoolean("notifications.only_title", true);
4351 
4352         /**
4353          * The lightness difference that has to be added to the primary text color to obtain the
4354          * secondary text color when the background is light.
4355          */
4356         private static final int LIGHTNESS_TEXT_DIFFERENCE_LIGHT = 20;
4357 
4358         /**
4359          * The lightness difference that has to be added to the primary text color to obtain the
4360          * secondary text color when the background is dark.
4361          * A bit less then the above value, since it looks better on dark backgrounds.
4362          */
4363         private static final int LIGHTNESS_TEXT_DIFFERENCE_DARK = -10;
4364 
4365         private Context mContext;
4366         private Notification mN;
4367         private Bundle mUserExtras = new Bundle();
4368         private Style mStyle;
4369         @UnsupportedAppUsage
4370         private ArrayList<Action> mActions = new ArrayList<>(MAX_ACTION_BUTTONS);
4371         private ArrayList<Person> mPersonList = new ArrayList<>();
4372         private ContrastColorUtil mColorUtil;
4373         private boolean mIsLegacy;
4374         private boolean mIsLegacyInitialized;
4375 
4376         /**
4377          * Caches an instance of StandardTemplateParams. Note that this may have been used before,
4378          * so make sure to call {@link StandardTemplateParams#reset()} before using it.
4379          */
4380         StandardTemplateParams mParams = new StandardTemplateParams();
4381         Colors mColors = new Colors();
4382 
4383         private boolean mTintActionButtons;
4384         private boolean mInNightMode;
4385 
4386         /**
4387          * Constructs a new Builder with the defaults:
4388          *
4389          * @param context
4390          *            A {@link Context} that will be used by the Builder to construct the
4391          *            RemoteViews. The Context will not be held past the lifetime of this Builder
4392          *            object.
4393          * @param channelId
4394          *            The constructed Notification will be posted on this
4395          *            {@link NotificationChannel}. To use a NotificationChannel, it must first be
4396          *            created using {@link NotificationManager#createNotificationChannel}.
4397          */
Builder(Context context, String channelId)4398         public Builder(Context context, String channelId) {
4399             this(context, (Notification) null);
4400             mN.mChannelId = channelId;
4401         }
4402 
4403         /**
4404          * @deprecated use {@link #Builder(Context, String)}
4405          * instead. All posted Notifications must specify a NotificationChannel Id.
4406          */
4407         @Deprecated
Builder(Context context)4408         public Builder(Context context) {
4409             this(context, (Notification) null);
4410         }
4411 
4412         /**
4413          * @hide
4414          */
Builder(Context context, Notification toAdopt)4415         public Builder(Context context, Notification toAdopt) {
4416             mContext = context;
4417             Resources res = mContext.getResources();
4418             mTintActionButtons = res.getBoolean(R.bool.config_tintNotificationActionButtons);
4419 
4420             if (res.getBoolean(R.bool.config_enableNightMode)) {
4421                 Configuration currentConfig = res.getConfiguration();
4422                 mInNightMode = (currentConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK)
4423                         == Configuration.UI_MODE_NIGHT_YES;
4424             }
4425 
4426             if (toAdopt == null) {
4427                 mN = new Notification();
4428                 if (context.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N) {
4429                     mN.extras.putBoolean(EXTRA_SHOW_WHEN, true);
4430                 }
4431                 mN.priority = PRIORITY_DEFAULT;
4432                 mN.visibility = VISIBILITY_PRIVATE;
4433             } else {
4434                 mN = toAdopt;
4435                 if (mN.actions != null) {
4436                     Collections.addAll(mActions, mN.actions);
4437                 }
4438 
4439                 if (mN.extras.containsKey(EXTRA_PEOPLE_LIST)) {
4440                     ArrayList<Person> people = mN.extras.getParcelableArrayList(EXTRA_PEOPLE_LIST,
4441                             android.app.Person.class);
4442                     if (people != null && !people.isEmpty()) {
4443                         mPersonList.addAll(people);
4444                     }
4445                 }
4446 
4447                 if (mN.getSmallIcon() == null && mN.icon != 0) {
4448                     setSmallIcon(mN.icon);
4449                 }
4450 
4451                 if (mN.getLargeIcon() == null && mN.largeIcon != null) {
4452                     setLargeIcon(mN.largeIcon);
4453                 }
4454 
4455                 String templateClass = mN.extras.getString(EXTRA_TEMPLATE);
4456                 if (!TextUtils.isEmpty(templateClass)) {
4457                     final Class<? extends Style> styleClass
4458                             = getNotificationStyleClass(templateClass);
4459                     if (styleClass == null) {
4460                         Log.d(TAG, "Unknown style class: " + templateClass);
4461                     } else {
4462                         try {
4463                             final Constructor<? extends Style> ctor =
4464                                     styleClass.getDeclaredConstructor();
4465                             ctor.setAccessible(true);
4466                             final Style style = ctor.newInstance();
4467                             style.restoreFromExtras(mN.extras);
4468 
4469                             if (style != null) {
4470                                 setStyle(style);
4471                             }
4472                         } catch (Throwable t) {
4473                             Log.e(TAG, "Could not create Style", t);
4474                         }
4475                     }
4476                 }
4477             }
4478         }
4479 
getColorUtil()4480         private ContrastColorUtil getColorUtil() {
4481             if (mColorUtil == null) {
4482                 mColorUtil = ContrastColorUtil.getInstance(mContext);
4483             }
4484             return mColorUtil;
4485         }
4486 
4487         /**
4488          * From Android 11, messaging notifications (those that use {@link MessagingStyle}) that
4489          * use this method to link to a published long-lived sharing shortcut may appear in a
4490          * dedicated Conversation section of the shade and may show configuration options that
4491          * are unique to conversations. This behavior should be reserved for person to person(s)
4492          * conversations where there is a likely social obligation for an individual to respond.
4493          * <p>
4494          * For example, the following are some examples of notifications that belong in the
4495          * conversation space:
4496          * <ul>
4497          * <li>1:1 conversations between two individuals</li>
4498          * <li>Group conversations between individuals where everyone can contribute</li>
4499          * </ul>
4500          * And the following are some examples of notifications that do not belong in the
4501          * conversation space:
4502          * <ul>
4503          * <li>Advertisements from a bot (even if personal and contextualized)</li>
4504          * <li>Engagement notifications from a bot</li>
4505          * <li>Directional conversations where there is an active speaker and many passive
4506          * individuals</li>
4507          * <li>Stream / posting updates from other individuals</li>
4508          * <li>Email, document comments, or other conversation types that are not real-time</li>
4509          * </ul>
4510          * </p>
4511          *
4512          * <p>
4513          * Additionally, this method can be used for all types of notifications to mark this
4514          * notification as duplicative of a Launcher shortcut. Launchers that show badges or
4515          * notification content may then suppress the shortcut in favor of the content of this
4516          * notification.
4517          * <p>
4518          * If this notification has {@link BubbleMetadata} attached that was created with
4519          * a shortcutId a check will be performed to ensure the shortcutId supplied to bubble
4520          * metadata matches the shortcutId set here, if one was set. If the shortcutId's were
4521          * specified but do not match, an exception is thrown.
4522          *
4523          * @param shortcutId the {@link ShortcutInfo#getId() id} of the shortcut this notification
4524          *                   is linked to
4525          *
4526          * @see BubbleMetadata.Builder#Builder(String)
4527          */
4528         @NonNull
setShortcutId(String shortcutId)4529         public Builder setShortcutId(String shortcutId) {
4530             mN.mShortcutId = shortcutId;
4531             return this;
4532         }
4533 
4534         /**
4535          * Sets the {@link LocusId} associated with this notification.
4536          *
4537          * <p>This method should be called when the {@link LocusId} is used in other places (such
4538          * as {@link ShortcutInfo} and {@link ContentCaptureContext}) so the device's intelligence
4539          * services can correlate them.
4540          */
4541         @NonNull
setLocusId(@ullable LocusId locusId)4542         public Builder setLocusId(@Nullable LocusId locusId) {
4543             mN.mLocusId = locusId;
4544             return this;
4545         }
4546 
4547         /**
4548          * Sets which icon to display as a badge for this notification.
4549          *
4550          * Must be one of {@link #BADGE_ICON_NONE}, {@link #BADGE_ICON_SMALL},
4551          * {@link #BADGE_ICON_LARGE}.
4552          *
4553          * Note: This value might be ignored, for launchers that don't support badge icons.
4554          */
4555         @NonNull
setBadgeIconType(int icon)4556         public Builder setBadgeIconType(int icon) {
4557             mN.mBadgeIcon = icon;
4558             return this;
4559         }
4560 
4561         /**
4562          * Sets the group alert behavior for this notification. Use this method to mute this
4563          * notification if alerts for this notification's group should be handled by a different
4564          * notification. This is only applicable for notifications that belong to a
4565          * {@link #setGroup(String) group}. This must be called on all notifications you want to
4566          * mute. For example, if you want only the summary of your group to make noise and/or peek
4567          * on screen, all children in the group should have the group alert behavior
4568          * {@link #GROUP_ALERT_SUMMARY}.
4569          *
4570          * <p> The default value is {@link #GROUP_ALERT_ALL}.</p>
4571          */
4572         @NonNull
setGroupAlertBehavior(@roupAlertBehavior int groupAlertBehavior)4573         public Builder setGroupAlertBehavior(@GroupAlertBehavior int groupAlertBehavior) {
4574             mN.mGroupAlertBehavior = groupAlertBehavior;
4575             return this;
4576         }
4577 
4578         /**
4579          * Sets the {@link BubbleMetadata} that will be used to display app content in a floating
4580          * window over the existing foreground activity.
4581          *
4582          * <p>This data will be ignored unless the notification is posted to a channel that
4583          * allows {@link NotificationChannel#canBubble() bubbles}.</p>
4584          *
4585          * <p>Notifications allowed to bubble that have valid bubble metadata will display in
4586          * collapsed state outside of the notification shade on unlocked devices. When a user
4587          * interacts with the collapsed state, the bubble intent will be invoked and displayed.</p>
4588          */
4589         @NonNull
setBubbleMetadata(@ullable BubbleMetadata data)4590         public Builder setBubbleMetadata(@Nullable BubbleMetadata data) {
4591             mN.mBubbleMetadata = data;
4592             return this;
4593         }
4594 
4595         /** @removed */
4596         @Deprecated
setChannel(String channelId)4597         public Builder setChannel(String channelId) {
4598             mN.mChannelId = channelId;
4599             return this;
4600         }
4601 
4602         /**
4603          * Specifies the channel the notification should be delivered on.
4604          */
4605         @NonNull
setChannelId(String channelId)4606         public Builder setChannelId(String channelId) {
4607             mN.mChannelId = channelId;
4608             return this;
4609         }
4610 
4611         /** @removed */
4612         @Deprecated
setTimeout(long durationMs)4613         public Builder setTimeout(long durationMs) {
4614             mN.mTimeout = durationMs;
4615             return this;
4616         }
4617 
4618         /**
4619          * Specifies a duration in milliseconds after which this notification should be canceled,
4620          * if it is not already canceled.
4621          */
4622         @NonNull
setTimeoutAfter(long durationMs)4623         public Builder setTimeoutAfter(long durationMs) {
4624             mN.mTimeout = durationMs;
4625             return this;
4626         }
4627 
4628         /**
4629          * Add a timestamp pertaining to the notification (usually the time the event occurred).
4630          *
4631          * @see Notification#when
4632          */
4633         @NonNull
setWhen(long when)4634         public Builder setWhen(long when) {
4635             mN.when = when;
4636             return this;
4637         }
4638 
4639         /**
4640          * Control whether the timestamp set with {@link #setWhen(long) setWhen} is shown
4641          * in the content view.
4642          */
4643         @NonNull
setShowWhen(boolean show)4644         public Builder setShowWhen(boolean show) {
4645             mN.extras.putBoolean(EXTRA_SHOW_WHEN, show);
4646             return this;
4647         }
4648 
4649         /**
4650          * Show the {@link Notification#when} field as a stopwatch.
4651          *
4652          * Instead of presenting <code>when</code> as a timestamp, the notification will show an
4653          * automatically updating display of the minutes and seconds since <code>when</code>.
4654          *
4655          * Useful when showing an elapsed time (like an ongoing phone call).
4656          *
4657          * The counter can also be set to count down to <code>when</code> when using
4658          * {@link #setChronometerCountDown(boolean)}.
4659          *
4660          * @see android.widget.Chronometer
4661          * @see Notification#when
4662          * @see #setChronometerCountDown(boolean)
4663          */
4664         @NonNull
setUsesChronometer(boolean b)4665         public Builder setUsesChronometer(boolean b) {
4666             mN.extras.putBoolean(EXTRA_SHOW_CHRONOMETER, b);
4667             return this;
4668         }
4669 
4670         /**
4671          * Sets the Chronometer to count down instead of counting up.
4672          *
4673          * <p>This is only relevant if {@link #setUsesChronometer(boolean)} has been set to true.
4674          * If it isn't set the chronometer will count up.
4675          *
4676          * @see #setUsesChronometer(boolean)
4677          */
4678         @NonNull
setChronometerCountDown(boolean countDown)4679         public Builder setChronometerCountDown(boolean countDown) {
4680             mN.extras.putBoolean(EXTRA_CHRONOMETER_COUNT_DOWN, countDown);
4681             return this;
4682         }
4683 
4684         /**
4685          * Set the small icon resource, which will be used to represent the notification in the
4686          * status bar.
4687          *
4688 
4689          * The platform template for the expanded view will draw this icon in the left, unless a
4690          * {@link #setLargeIcon(Bitmap) large icon} has also been specified, in which case the small
4691          * icon will be moved to the right-hand side.
4692          *
4693 
4694          * @param icon
4695          *            A resource ID in the application's package of the drawable to use.
4696          * @see Notification#icon
4697          */
4698         @NonNull
setSmallIcon(@rawableRes int icon)4699         public Builder setSmallIcon(@DrawableRes int icon) {
4700             return setSmallIcon(icon != 0
4701                     ? Icon.createWithResource(mContext, icon)
4702                     : null);
4703         }
4704 
4705         /**
4706          * A variant of {@link #setSmallIcon(int) setSmallIcon(int)} that takes an additional
4707          * level parameter for when the icon is a {@link android.graphics.drawable.LevelListDrawable
4708          * LevelListDrawable}.
4709          *
4710          * @param icon A resource ID in the application's package of the drawable to use.
4711          * @param level The level to use for the icon.
4712          *
4713          * @see Notification#icon
4714          * @see Notification#iconLevel
4715          */
4716         @NonNull
setSmallIcon(@rawableRes int icon, int level)4717         public Builder setSmallIcon(@DrawableRes int icon, int level) {
4718             mN.iconLevel = level;
4719             return setSmallIcon(icon);
4720         }
4721 
4722         /**
4723          * Set the small icon, which will be used to represent the notification in the
4724          * status bar and content view (unless overridden there by a
4725          * {@link #setLargeIcon(Bitmap) large icon}).
4726          *
4727          * @param icon An Icon object to use.
4728          * @see Notification#icon
4729          */
4730         @NonNull
setSmallIcon(Icon icon)4731         public Builder setSmallIcon(Icon icon) {
4732             mN.setSmallIcon(icon);
4733             if (icon != null && icon.getType() == Icon.TYPE_RESOURCE) {
4734                 mN.icon = icon.getResId();
4735             }
4736             return this;
4737         }
4738 
4739         /**
4740          * If {@code true}, silences this instance of the notification, regardless of the sounds or
4741          * vibrations set on the notification or notification channel. If {@code false}, then the
4742          * normal sound and vibration logic applies.
4743          *
4744          * @hide
4745          */
setSilent(boolean silent)4746         public @NonNull Builder setSilent(boolean silent) {
4747             if (!silent) {
4748                 return this;
4749             }
4750             if (mN.isGroupSummary()) {
4751                 setGroupAlertBehavior(GROUP_ALERT_CHILDREN);
4752             } else {
4753                 setGroupAlertBehavior(GROUP_ALERT_SUMMARY);
4754             }
4755 
4756             setVibrate(null);
4757             setSound(null);
4758             mN.defaults &= ~DEFAULT_SOUND;
4759             mN.defaults &= ~DEFAULT_VIBRATE;
4760             setDefaults(mN.defaults);
4761 
4762             if (TextUtils.isEmpty(mN.mGroupKey)) {
4763                 setGroup(GROUP_KEY_SILENT);
4764             }
4765             return this;
4766         }
4767 
4768         /**
4769          * Set the first line of text in the platform notification template.
4770          */
4771         @NonNull
setContentTitle(CharSequence title)4772         public Builder setContentTitle(CharSequence title) {
4773             mN.extras.putCharSequence(EXTRA_TITLE, safeCharSequence(title));
4774             return this;
4775         }
4776 
4777         /**
4778          * Set the second line of text in the platform notification template.
4779          */
4780         @NonNull
setContentText(CharSequence text)4781         public Builder setContentText(CharSequence text) {
4782             mN.extras.putCharSequence(EXTRA_TEXT, safeCharSequence(text));
4783             return this;
4784         }
4785 
4786         /**
4787          * This provides some additional information that is displayed in the notification. No
4788          * guarantees are given where exactly it is displayed.
4789          *
4790          * <p>This information should only be provided if it provides an essential
4791          * benefit to the understanding of the notification. The more text you provide the
4792          * less readable it becomes. For example, an email client should only provide the account
4793          * name here if more than one email account has been added.</p>
4794          *
4795          * <p>As of {@link android.os.Build.VERSION_CODES#N} this information is displayed in the
4796          * notification header area.
4797          *
4798          * On Android versions before {@link android.os.Build.VERSION_CODES#N}
4799          * this will be shown in the third line of text in the platform notification template.
4800          * You should not be using {@link #setProgress(int, int, boolean)} at the
4801          * same time on those versions; they occupy the same place.
4802          * </p>
4803          */
4804         @NonNull
setSubText(CharSequence text)4805         public Builder setSubText(CharSequence text) {
4806             mN.extras.putCharSequence(EXTRA_SUB_TEXT, safeCharSequence(text));
4807             return this;
4808         }
4809 
4810         /**
4811          * Provides text that will appear as a link to your application's settings.
4812          *
4813          * <p>This text does not appear within notification {@link Style templates} but may
4814          * appear when the user uses an affordance to learn more about the notification.
4815          * Additionally, this text will not appear unless you provide a valid link target by
4816          * handling {@link #INTENT_CATEGORY_NOTIFICATION_PREFERENCES}.
4817          *
4818          * <p>This text is meant to be concise description about what the user can customize
4819          * when they click on this link. The recommended maximum length is 40 characters.
4820          * @param text
4821          * @return
4822          */
4823         @NonNull
setSettingsText(CharSequence text)4824         public Builder setSettingsText(CharSequence text) {
4825             mN.mSettingsText = safeCharSequence(text);
4826             return this;
4827         }
4828 
4829         /**
4830          * Set the remote input history.
4831          *
4832          * This should be set to the most recent inputs that have been sent
4833          * through a {@link RemoteInput} of this Notification and cleared once the it is no
4834          * longer relevant (e.g. for chat notifications once the other party has responded).
4835          *
4836          * The most recent input must be stored at the 0 index, the second most recent at the
4837          * 1 index, etc. Note that the system will limit both how far back the inputs will be shown
4838          * and how much of each individual input is shown.
4839          *
4840          * <p>Note: The reply text will only be shown on notifications that have least one action
4841          * with a {@code RemoteInput}.</p>
4842          */
4843         @NonNull
setRemoteInputHistory(CharSequence[] text)4844         public Builder setRemoteInputHistory(CharSequence[] text) {
4845             if (text == null) {
4846                 mN.extras.putCharSequenceArray(EXTRA_REMOTE_INPUT_HISTORY, null);
4847             } else {
4848                 final int itemCount = Math.min(MAX_REPLY_HISTORY, text.length);
4849                 CharSequence[] safe = new CharSequence[itemCount];
4850                 RemoteInputHistoryItem[] items = new RemoteInputHistoryItem[itemCount];
4851                 for (int i = 0; i < itemCount; i++) {
4852                     safe[i] = safeCharSequence(text[i]);
4853                     items[i] = new RemoteInputHistoryItem(text[i]);
4854                 }
4855                 mN.extras.putCharSequenceArray(EXTRA_REMOTE_INPUT_HISTORY, safe);
4856 
4857                 // Also add these messages as structured history items.
4858                 mN.extras.putParcelableArray(EXTRA_REMOTE_INPUT_HISTORY_ITEMS, items);
4859             }
4860             return this;
4861         }
4862 
4863         /**
4864          * Set the remote input history, with support for embedding URIs and mime types for
4865          * images and other media.
4866          * @hide
4867          */
4868         @NonNull
setRemoteInputHistory(RemoteInputHistoryItem[] items)4869         public Builder setRemoteInputHistory(RemoteInputHistoryItem[] items) {
4870             if (items == null) {
4871                 mN.extras.putParcelableArray(EXTRA_REMOTE_INPUT_HISTORY_ITEMS, null);
4872             } else {
4873                 final int itemCount = Math.min(MAX_REPLY_HISTORY, items.length);
4874                 RemoteInputHistoryItem[] history = new RemoteInputHistoryItem[itemCount];
4875                 for (int i = 0; i < itemCount; i++) {
4876                     history[i] = items[i];
4877                 }
4878                 mN.extras.putParcelableArray(EXTRA_REMOTE_INPUT_HISTORY_ITEMS, history);
4879             }
4880             return this;
4881         }
4882 
4883         /**
4884          * Sets whether remote history entries view should have a spinner.
4885          * @hide
4886          */
4887         @NonNull
setShowRemoteInputSpinner(boolean showSpinner)4888         public Builder setShowRemoteInputSpinner(boolean showSpinner) {
4889             mN.extras.putBoolean(EXTRA_SHOW_REMOTE_INPUT_SPINNER, showSpinner);
4890             return this;
4891         }
4892 
4893         /**
4894          * Sets whether smart reply buttons should be hidden.
4895          * @hide
4896          */
4897         @NonNull
setHideSmartReplies(boolean hideSmartReplies)4898         public Builder setHideSmartReplies(boolean hideSmartReplies) {
4899             mN.extras.putBoolean(EXTRA_HIDE_SMART_REPLIES, hideSmartReplies);
4900             return this;
4901         }
4902 
4903         /**
4904          * Sets the number of items this notification represents. May be displayed as a badge count
4905          * for Launchers that support badging.
4906          */
4907         @NonNull
setNumber(int number)4908         public Builder setNumber(int number) {
4909             mN.number = number;
4910             return this;
4911         }
4912 
4913         /**
4914          * A small piece of additional information pertaining to this notification.
4915          *
4916          * The platform template will draw this on the last line of the notification, at the far
4917          * right (to the right of a smallIcon if it has been placed there).
4918          *
4919          * @deprecated use {@link #setSubText(CharSequence)} instead to set a text in the header.
4920          * For legacy apps targeting a version below {@link android.os.Build.VERSION_CODES#N} this
4921          * field will still show up, but the subtext will take precedence.
4922          */
4923         @Deprecated
setContentInfo(CharSequence info)4924         public Builder setContentInfo(CharSequence info) {
4925             mN.extras.putCharSequence(EXTRA_INFO_TEXT, safeCharSequence(info));
4926             return this;
4927         }
4928 
4929         /**
4930          * Set the progress this notification represents.
4931          *
4932          * The platform template will represent this using a {@link ProgressBar}.
4933          */
4934         @NonNull
setProgress(int max, int progress, boolean indeterminate)4935         public Builder setProgress(int max, int progress, boolean indeterminate) {
4936             mN.extras.putInt(EXTRA_PROGRESS, progress);
4937             mN.extras.putInt(EXTRA_PROGRESS_MAX, max);
4938             mN.extras.putBoolean(EXTRA_PROGRESS_INDETERMINATE, indeterminate);
4939             return this;
4940         }
4941 
4942         /**
4943          * Supply a custom RemoteViews to use instead of the platform template.
4944          *
4945          * Use {@link #setCustomContentView(RemoteViews)} instead.
4946          */
4947         @Deprecated
setContent(RemoteViews views)4948         public Builder setContent(RemoteViews views) {
4949             return setCustomContentView(views);
4950         }
4951 
4952         /**
4953          * Supply custom RemoteViews to use instead of the platform template.
4954          *
4955          * This will override the layout that would otherwise be constructed by this Builder
4956          * object.
4957          */
4958         @NonNull
setCustomContentView(RemoteViews contentView)4959         public Builder setCustomContentView(RemoteViews contentView) {
4960             mN.contentView = contentView;
4961             return this;
4962         }
4963 
4964         /**
4965          * Supply custom RemoteViews to use instead of the platform template in the expanded form.
4966          *
4967          * This will override the expanded layout that would otherwise be constructed by this
4968          * Builder object.
4969          */
4970         @NonNull
setCustomBigContentView(RemoteViews contentView)4971         public Builder setCustomBigContentView(RemoteViews contentView) {
4972             mN.bigContentView = contentView;
4973             return this;
4974         }
4975 
4976         /**
4977          * Supply custom RemoteViews to use instead of the platform template in the heads up dialog.
4978          *
4979          * This will override the heads-up layout that would otherwise be constructed by this
4980          * Builder object.
4981          */
4982         @NonNull
setCustomHeadsUpContentView(RemoteViews contentView)4983         public Builder setCustomHeadsUpContentView(RemoteViews contentView) {
4984             mN.headsUpContentView = contentView;
4985             return this;
4986         }
4987 
4988         /**
4989          * Supply a {@link PendingIntent} to be sent when the notification is clicked.
4990          *
4991          * <p>As of Android {@link android.os.Build.VERSION_CODES#S}, apps targeting API level
4992          * {@link android.os.Build.VERSION_CODES#S} or higher won't be able to start activities
4993          * while processing broadcast receivers or services in response to notification clicks. To
4994          * launch an activity in those cases, provide a {@link PendingIntent} for the activity
4995          * itself.
4996          *
4997          * <p>As of {@link android.os.Build.VERSION_CODES#HONEYCOMB}, if this field is unset and you
4998          * have specified a custom RemoteViews with {@link #setContent(RemoteViews)}, you can use
4999          * {@link RemoteViews#setOnClickPendingIntent RemoteViews.setOnClickPendingIntent(int,PendingIntent)}
5000          * to assign PendingIntents to individual views in that custom layout (i.e., to create
5001          * clickable buttons inside the notification view).
5002          *
5003          * @see Notification#contentIntent Notification.contentIntent
5004          */
5005         @NonNull
setContentIntent(PendingIntent intent)5006         public Builder setContentIntent(PendingIntent intent) {
5007             mN.contentIntent = intent;
5008             return this;
5009         }
5010 
5011         /**
5012          * Supply a {@link PendingIntent} to send when the notification is cleared explicitly by the user.
5013          *
5014          * @see Notification#deleteIntent
5015          */
5016         @NonNull
setDeleteIntent(PendingIntent intent)5017         public Builder setDeleteIntent(PendingIntent intent) {
5018             mN.deleteIntent = intent;
5019             return this;
5020         }
5021 
5022         /**
5023          * An intent to launch instead of posting the notification to the status bar.
5024          * Only for use with extremely high-priority notifications demanding the user's
5025          * <strong>immediate</strong> attention, such as an incoming phone call or
5026          * alarm clock that the user has explicitly set to a particular time.
5027          * If this facility is used for something else, please give the user an option
5028          * to turn it off and use a normal notification, as this can be extremely
5029          * disruptive.
5030          *
5031          * <p>Apps targeting {@link Build.VERSION_CODES#Q} and above will have to request
5032          * a permission ({@link android.Manifest.permission#USE_FULL_SCREEN_INTENT}) in order to
5033          * use full screen intents. </p>
5034          * <p>
5035          * Prior to {@link Build.VERSION_CODES#TIRAMISU}, the system may display a
5036          * heads up notification (which may display on screen longer than other heads up
5037          * notifications), instead of launching the intent, while the user is using the device.
5038          * From {@link Build.VERSION_CODES#TIRAMISU},
5039          * the system UI will display a heads up notification, instead of launching this intent,
5040          * while the user is using the device. This notification will display with emphasized
5041          * action buttons. If the posting app holds
5042          * {@link android.Manifest.permission#USE_FULL_SCREEN_INTENT}, then the heads
5043          * up notification will appear persistently until the user dismisses or snoozes it, or
5044          * the app cancels it. If the posting app does not hold
5045          * {@link android.Manifest.permission#USE_FULL_SCREEN_INTENT}, then the notification will
5046          * appear as heads up notification even when the screen is locked or turned off, and this
5047          * notification will only be persistent for 60 seconds.
5048          * </p>
5049          * <p>
5050          * To be launched as a full screen intent, the notification must also be posted to a
5051          * channel with importance level set to IMPORTANCE_HIGH or higher.
5052          * </p>
5053          *
5054          * @param intent The pending intent to launch.
5055          * @param highPriority Passing true will cause this notification to be sent
5056          *          even if other notifications are suppressed.
5057          *
5058          * @see Notification#fullScreenIntent
5059          */
5060         @NonNull
setFullScreenIntent(PendingIntent intent, boolean highPriority)5061         public Builder setFullScreenIntent(PendingIntent intent, boolean highPriority) {
5062             mN.fullScreenIntent = intent;
5063             setFlag(FLAG_HIGH_PRIORITY, highPriority);
5064             return this;
5065         }
5066 
5067         /**
5068          * Set the "ticker" text which is sent to accessibility services.
5069          *
5070          * @see Notification#tickerText
5071          */
5072         @NonNull
setTicker(CharSequence tickerText)5073         public Builder setTicker(CharSequence tickerText) {
5074             mN.tickerText = safeCharSequence(tickerText);
5075             return this;
5076         }
5077 
5078         /**
5079          * Obsolete version of {@link #setTicker(CharSequence)}.
5080          *
5081          */
5082         @Deprecated
setTicker(CharSequence tickerText, RemoteViews views)5083         public Builder setTicker(CharSequence tickerText, RemoteViews views) {
5084             setTicker(tickerText);
5085             // views is ignored
5086             return this;
5087         }
5088 
5089         /**
5090          * Add a large icon to the notification content view.
5091          *
5092          * In the platform template, this image will be shown either on the right of the
5093          * notification, with an aspect ratio of up to 16:9, or (when the notification is grouped)
5094          * on the left in place of the {@link #setSmallIcon(Icon) small icon}.
5095          */
5096         @NonNull
setLargeIcon(Bitmap b)5097         public Builder setLargeIcon(Bitmap b) {
5098             return setLargeIcon(b != null ? Icon.createWithBitmap(b) : null);
5099         }
5100 
5101         /**
5102          * Add a large icon to the notification content view.
5103          *
5104          * In the platform template, this image will be shown either on the right of the
5105          * notification, with an aspect ratio of up to 16:9, or (when the notification is grouped)
5106          * on the left in place of the {@link #setSmallIcon(Icon) small icon}.
5107          */
5108         @NonNull
setLargeIcon(Icon icon)5109         public Builder setLargeIcon(Icon icon) {
5110             mN.mLargeIcon = icon;
5111             mN.extras.putParcelable(EXTRA_LARGE_ICON, icon);
5112             return this;
5113         }
5114 
5115         /**
5116          * Set the sound to play.
5117          *
5118          * It will be played using the {@link #AUDIO_ATTRIBUTES_DEFAULT default audio attributes}
5119          * for notifications.
5120          *
5121          * @deprecated use {@link NotificationChannel#setSound(Uri, AudioAttributes)} instead.
5122          */
5123         @Deprecated
setSound(Uri sound)5124         public Builder setSound(Uri sound) {
5125             mN.sound = sound;
5126             mN.audioAttributes = AUDIO_ATTRIBUTES_DEFAULT;
5127             return this;
5128         }
5129 
5130         /**
5131          * Set the sound to play, along with a specific stream on which to play it.
5132          *
5133          * See {@link android.media.AudioManager} for the <code>STREAM_</code> constants.
5134          *
5135          * @deprecated use {@link NotificationChannel#setSound(Uri, AudioAttributes)}.
5136          */
5137         @Deprecated
setSound(Uri sound, int streamType)5138         public Builder setSound(Uri sound, int streamType) {
5139             PlayerBase.deprecateStreamTypeForPlayback(streamType, "Notification", "setSound()");
5140             mN.sound = sound;
5141             mN.audioStreamType = streamType;
5142             return this;
5143         }
5144 
5145         /**
5146          * Set the sound to play, along with specific {@link AudioAttributes audio attributes} to
5147          * use during playback.
5148          *
5149          * @deprecated use {@link NotificationChannel#setSound(Uri, AudioAttributes)} instead.
5150          * @see Notification#sound
5151          */
5152         @Deprecated
setSound(Uri sound, AudioAttributes audioAttributes)5153         public Builder setSound(Uri sound, AudioAttributes audioAttributes) {
5154             mN.sound = sound;
5155             mN.audioAttributes = audioAttributes;
5156             return this;
5157         }
5158 
5159         /**
5160          * Set the vibration pattern to use.
5161          *
5162          * See {@link android.os.Vibrator#vibrate(long[], int)} for a discussion of the
5163          * <code>pattern</code> parameter.
5164          *
5165          * <p>
5166          * A notification that vibrates is more likely to be presented as a heads-up notification.
5167          * </p>
5168          *
5169          * @deprecated use {@link NotificationChannel#setVibrationPattern(long[])} instead.
5170          * @see Notification#vibrate
5171          */
5172         @Deprecated
setVibrate(long[] pattern)5173         public Builder setVibrate(long[] pattern) {
5174             mN.vibrate = pattern;
5175             return this;
5176         }
5177 
5178         /**
5179          * Set the desired color for the indicator LED on the device, as well as the
5180          * blink duty cycle (specified in milliseconds).
5181          *
5182 
5183          * Not all devices will honor all (or even any) of these values.
5184          *
5185          * @deprecated use {@link NotificationChannel#enableLights(boolean)} instead.
5186          * @see Notification#ledARGB
5187          * @see Notification#ledOnMS
5188          * @see Notification#ledOffMS
5189          */
5190         @Deprecated
setLights(@olorInt int argb, int onMs, int offMs)5191         public Builder setLights(@ColorInt int argb, int onMs, int offMs) {
5192             mN.ledARGB = argb;
5193             mN.ledOnMS = onMs;
5194             mN.ledOffMS = offMs;
5195             if (onMs != 0 || offMs != 0) {
5196                 mN.flags |= FLAG_SHOW_LIGHTS;
5197             }
5198             return this;
5199         }
5200 
5201         /**
5202          * Set whether this is an "ongoing" notification.
5203          *
5204          * Ongoing notifications cannot be dismissed by the user on locked devices, or by
5205          * notification listeners, and some notifications (call, device management, media) cannot
5206          * be dismissed on unlocked devices, so your application or service must take care of
5207          * canceling them.
5208          *
5209          * They are typically used to indicate a background task that the user is actively engaged
5210          * with (e.g., playing music) or is pending in some way and therefore occupying the device
5211          * (e.g., a file download, sync operation, active network connection).
5212          *
5213          * @see Notification#FLAG_ONGOING_EVENT
5214          */
5215         @NonNull
setOngoing(boolean ongoing)5216         public Builder setOngoing(boolean ongoing) {
5217             setFlag(FLAG_ONGOING_EVENT, ongoing);
5218             return this;
5219         }
5220 
5221         /**
5222          * Set whether this notification should be colorized. When set, the color set with
5223          * {@link #setColor(int)} will be used as the background color of this notification.
5224          * <p>
5225          * This should only be used for high priority ongoing tasks like navigation, an ongoing
5226          * call, or other similarly high-priority events for the user.
5227          * <p>
5228          * For most styles, the coloring will only be applied if the notification is for a
5229          * foreground service notification.
5230          * However, for {@link MediaStyle} and {@link DecoratedMediaCustomViewStyle} notifications
5231          * that have a media session attached there is no such requirement.
5232          *
5233          * @see #setColor(int)
5234          * @see MediaStyle#setMediaSession(MediaSession.Token)
5235          */
5236         @NonNull
setColorized(boolean colorize)5237         public Builder setColorized(boolean colorize) {
5238             mN.extras.putBoolean(EXTRA_COLORIZED, colorize);
5239             return this;
5240         }
5241 
5242         /**
5243          * Set this flag if you would only like the sound, vibrate
5244          * and ticker to be played if the notification is not already showing.
5245          *
5246          * Note that using this flag will stop any ongoing alerting behaviour such
5247          * as sound, vibration or blinking notification LED.
5248          *
5249          * @see Notification#FLAG_ONLY_ALERT_ONCE
5250          */
5251         @NonNull
setOnlyAlertOnce(boolean onlyAlertOnce)5252         public Builder setOnlyAlertOnce(boolean onlyAlertOnce) {
5253             setFlag(FLAG_ONLY_ALERT_ONCE, onlyAlertOnce);
5254             return this;
5255         }
5256 
5257         /**
5258          * Specify a desired visibility policy for a Notification associated with a
5259          * foreground service.  By default, the system can choose to defer
5260          * visibility of the notification for a short time after the service is
5261          * started.  Pass
5262          * {@link Notification#FOREGROUND_SERVICE_IMMEDIATE FOREGROUND_SERVICE_IMMEDIATE}
5263          * to this method in order to guarantee that visibility is never deferred.  Pass
5264          * {@link Notification#FOREGROUND_SERVICE_DEFERRED FOREGROUND_SERVICE_DEFERRED}
5265          * to request that visibility is deferred whenever possible.
5266          *
5267          * <p class="note">Note that deferred visibility is not guaranteed.  There
5268          * may be some circumstances under which the system will show the foreground
5269          * service's associated Notification immediately even when the app has used
5270          * this method to explicitly request deferred display.</p>
5271          * @param behavior One of
5272          * {@link Notification#FOREGROUND_SERVICE_DEFAULT FOREGROUND_SERVICE_DEFAULT},
5273          * {@link Notification#FOREGROUND_SERVICE_IMMEDIATE FOREGROUND_SERVICE_IMMEDIATE},
5274          * or {@link Notification#FOREGROUND_SERVICE_DEFERRED FOREGROUND_SERVICE_DEFERRED}
5275          * @return
5276          */
5277         @NonNull
setForegroundServiceBehavior(@erviceNotificationPolicy int behavior)5278         public Builder setForegroundServiceBehavior(@ServiceNotificationPolicy int behavior) {
5279             mN.mFgsDeferBehavior = behavior;
5280             return this;
5281         }
5282 
5283         /**
5284          * Make this notification automatically dismissed when the user touches it.
5285          *
5286          * @see Notification#FLAG_AUTO_CANCEL
5287          */
5288         @NonNull
setAutoCancel(boolean autoCancel)5289         public Builder setAutoCancel(boolean autoCancel) {
5290             setFlag(FLAG_AUTO_CANCEL, autoCancel);
5291             return this;
5292         }
5293 
5294         /**
5295          * Set whether or not this notification should not bridge to other devices.
5296          *
5297          * <p>Some notifications can be bridged to other devices for remote display.
5298          * This hint can be set to recommend this notification not be bridged.
5299          */
5300         @NonNull
setLocalOnly(boolean localOnly)5301         public Builder setLocalOnly(boolean localOnly) {
5302             setFlag(FLAG_LOCAL_ONLY, localOnly);
5303             return this;
5304         }
5305 
5306         /**
5307          * Set which notification properties will be inherited from system defaults.
5308          * <p>
5309          * The value should be one or more of the following fields combined with
5310          * bitwise-or:
5311          * {@link #DEFAULT_SOUND}, {@link #DEFAULT_VIBRATE}, {@link #DEFAULT_LIGHTS}.
5312          * <p>
5313          * For all default values, use {@link #DEFAULT_ALL}.
5314          *
5315          * @deprecated use {@link NotificationChannel#enableVibration(boolean)} and
5316          * {@link NotificationChannel#enableLights(boolean)} and
5317          * {@link NotificationChannel#setSound(Uri, AudioAttributes)} instead.
5318          */
5319         @Deprecated
setDefaults(int defaults)5320         public Builder setDefaults(int defaults) {
5321             mN.defaults = defaults;
5322             return this;
5323         }
5324 
5325         /**
5326          * Set the priority of this notification.
5327          *
5328          * @see Notification#priority
5329          * @deprecated use {@link NotificationChannel#setImportance(int)} instead.
5330          */
5331         @Deprecated
setPriority(@riority int pri)5332         public Builder setPriority(@Priority int pri) {
5333             mN.priority = pri;
5334             return this;
5335         }
5336 
5337         /**
5338          * Set the notification category.
5339          *
5340          * @see Notification#category
5341          */
5342         @NonNull
setCategory(String category)5343         public Builder setCategory(String category) {
5344             mN.category = category;
5345             return this;
5346         }
5347 
5348         /**
5349          * Add a person that is relevant to this notification.
5350          *
5351          * <P>
5352          * Depending on user preferences, this annotation may allow the notification to pass
5353          * through interruption filters, if this notification is of category {@link #CATEGORY_CALL}
5354          * or {@link #CATEGORY_MESSAGE}. The addition of people may also cause this notification to
5355          * appear more prominently in the user interface.
5356          * </P>
5357          *
5358          * <P>
5359          * The person should be specified by the {@code String} representation of a
5360          * {@link android.provider.ContactsContract.Contacts#CONTENT_LOOKUP_URI}.
5361          * </P>
5362          *
5363          * <P>The system will also attempt to resolve {@code mailto:} and {@code tel:} schema
5364          * URIs.  The path part of these URIs must exist in the contacts database, in the
5365          * appropriate column, or the reference will be discarded as invalid. Telephone schema
5366          * URIs will be resolved by {@link android.provider.ContactsContract.PhoneLookup}.
5367          * It is also possible to provide a URI with the schema {@code name:} in order to uniquely
5368          * identify a person without an entry in the contacts database.
5369          * </P>
5370          *
5371          * @param uri A URI for the person.
5372          * @see Notification#EXTRA_PEOPLE
5373          * @deprecated use {@link #addPerson(Person)}
5374          */
addPerson(String uri)5375         public Builder addPerson(String uri) {
5376             addPerson(new Person.Builder().setUri(uri).build());
5377             return this;
5378         }
5379 
5380         /**
5381          * Add a person that is relevant to this notification.
5382          *
5383          * <P>
5384          * Depending on user preferences, this annotation may allow the notification to pass
5385          * through interruption filters, if this notification is of category {@link #CATEGORY_CALL}
5386          * or {@link #CATEGORY_MESSAGE}. The addition of people may also cause this notification to
5387          * appear more prominently in the user interface.
5388          * </P>
5389          *
5390          * <P>
5391          * A person should usually contain a uri in order to benefit from the ranking boost.
5392          * However, even if no uri is provided, it's beneficial to provide other people in the
5393          * notification, such that listeners and voice only devices can announce and handle them
5394          * properly.
5395          * </P>
5396          *
5397          * @param person the person to add.
5398          * @see Notification#EXTRA_PEOPLE_LIST
5399          */
5400         @NonNull
addPerson(Person person)5401         public Builder addPerson(Person person) {
5402             mPersonList.add(person);
5403             return this;
5404         }
5405 
5406         /**
5407          * Set this notification to be part of a group of notifications sharing the same key.
5408          * Grouped notifications may display in a cluster or stack on devices which
5409          * support such rendering.
5410          *
5411          * <p>To make this notification the summary for its group, also call
5412          * {@link #setGroupSummary}. A sort order can be specified for group members by using
5413          * {@link #setSortKey}.
5414          * @param groupKey The group key of the group.
5415          * @return this object for method chaining
5416          */
5417         @NonNull
setGroup(String groupKey)5418         public Builder setGroup(String groupKey) {
5419             mN.mGroupKey = groupKey;
5420             return this;
5421         }
5422 
5423         /**
5424          * Set this notification to be the group summary for a group of notifications.
5425          * Grouped notifications may display in a cluster or stack on devices which
5426          * support such rendering. If thereRequires a group key also be set using {@link #setGroup}.
5427          * The group summary may be suppressed if too few notifications are included in the group.
5428          * @param isGroupSummary Whether this notification should be a group summary.
5429          * @return this object for method chaining
5430          */
5431         @NonNull
setGroupSummary(boolean isGroupSummary)5432         public Builder setGroupSummary(boolean isGroupSummary) {
5433             setFlag(FLAG_GROUP_SUMMARY, isGroupSummary);
5434             return this;
5435         }
5436 
5437         /**
5438          * Set a sort key that orders this notification among other notifications from the
5439          * same package. This can be useful if an external sort was already applied and an app
5440          * would like to preserve this. Notifications will be sorted lexicographically using this
5441          * value, although providing different priorities in addition to providing sort key may
5442          * cause this value to be ignored.
5443          *
5444          * <p>This sort key can also be used to order members of a notification group. See
5445          * {@link #setGroup}.
5446          *
5447          * @see String#compareTo(String)
5448          */
5449         @NonNull
setSortKey(String sortKey)5450         public Builder setSortKey(String sortKey) {
5451             mN.mSortKey = sortKey;
5452             return this;
5453         }
5454 
5455         /**
5456          * Merge additional metadata into this notification.
5457          *
5458          * <p>Values within the Bundle will replace existing extras values in this Builder.
5459          *
5460          * @see Notification#extras
5461          */
5462         @NonNull
addExtras(Bundle extras)5463         public Builder addExtras(Bundle extras) {
5464             if (extras != null) {
5465                 mUserExtras.putAll(extras);
5466             }
5467             return this;
5468         }
5469 
5470         /**
5471          * Set metadata for this notification.
5472          *
5473          * <p>A reference to the Bundle is held for the lifetime of this Builder, and the Bundle's
5474          * current contents are copied into the Notification each time {@link #build()} is
5475          * called.
5476          *
5477          * <p>Replaces any existing extras values with those from the provided Bundle.
5478          * Use {@link #addExtras} to merge in metadata instead.
5479          *
5480          * @see Notification#extras
5481          */
5482         @NonNull
setExtras(Bundle extras)5483         public Builder setExtras(Bundle extras) {
5484             if (extras != null) {
5485                 mUserExtras = extras;
5486             }
5487             return this;
5488         }
5489 
5490         /**
5491          * Get the current metadata Bundle used by this notification Builder.
5492          *
5493          * <p>The returned Bundle is shared with this Builder.
5494          *
5495          * <p>The current contents of this Bundle are copied into the Notification each time
5496          * {@link #build()} is called.
5497          *
5498          * @see Notification#extras
5499          */
getExtras()5500         public Bundle getExtras() {
5501             return mUserExtras;
5502         }
5503 
5504         /**
5505          * Add an action to this notification. Actions are typically displayed by
5506          * the system as a button adjacent to the notification content.
5507          * <p>
5508          * Every action must have an icon (32dp square and matching the
5509          * <a href="{@docRoot}design/style/iconography.html#action-bar">Holo
5510          * Dark action bar</a> visual style), a textual label, and a {@link PendingIntent}.
5511          * <p>
5512          * A notification in its expanded form can display up to 3 actions, from left to right in
5513          * the order they were added. Actions will not be displayed when the notification is
5514          * collapsed, however, so be sure that any essential functions may be accessed by the user
5515          * in some other way (for example, in the Activity pointed to by {@link #contentIntent}).
5516          * <p>
5517          * As of Android {@link android.os.Build.VERSION_CODES#S}, apps targeting API level
5518          * {@link android.os.Build.VERSION_CODES#S} or higher won't be able to start activities
5519          * while processing broadcast receivers or services in response to notification action
5520          * clicks. To launch an activity in those cases, provide a {@link PendingIntent} to the
5521          * activity itself.
5522          * <p>
5523          * As of Android {@link android.os.Build.VERSION_CODES#N},
5524          * action button icons will not be displayed on action buttons, but are still required
5525          * and are available to
5526          * {@link android.service.notification.NotificationListenerService notification listeners},
5527          * which may display them in other contexts, for example on a wearable device.
5528          *
5529          * @param icon Resource ID of a drawable that represents the action.
5530          * @param title Text describing the action.
5531          * @param intent PendingIntent to be fired when the action is invoked.
5532          *
5533          * @deprecated Use {@link #addAction(Action)} instead.
5534          */
5535         @Deprecated
addAction(int icon, CharSequence title, PendingIntent intent)5536         public Builder addAction(int icon, CharSequence title, PendingIntent intent) {
5537             mActions.add(new Action(icon, safeCharSequence(title), intent));
5538             return this;
5539         }
5540 
5541         /**
5542          * Add an action to this notification. Actions are typically displayed by
5543          * the system as a button adjacent to the notification content.
5544          * <p>
5545          * Every action must have an icon (32dp square and matching the
5546          * <a href="{@docRoot}design/style/iconography.html#action-bar">Holo
5547          * Dark action bar</a> visual style), a textual label, and a {@link PendingIntent}.
5548          * <p>
5549          * A notification in its expanded form can display up to 3 actions, from left to right in
5550          * the order they were added. Actions will not be displayed when the notification is
5551          * collapsed, however, so be sure that any essential functions may be accessed by the user
5552          * in some other way (for example, in the Activity pointed to by {@link #contentIntent}).
5553          *
5554          * @param action The action to add.
5555          */
5556         @NonNull
addAction(Action action)5557         public Builder addAction(Action action) {
5558             if (action != null) {
5559                 mActions.add(action);
5560             }
5561             return this;
5562         }
5563 
5564         /**
5565          * Alter the complete list of actions attached to this notification.
5566          * @see #addAction(Action).
5567          *
5568          * @param actions
5569          * @return
5570          */
5571         @NonNull
setActions(Action... actions)5572         public Builder setActions(Action... actions) {
5573             mActions.clear();
5574             for (int i = 0; i < actions.length; i++) {
5575                 if (actions[i] != null) {
5576                     mActions.add(actions[i]);
5577                 }
5578             }
5579             return this;
5580         }
5581 
5582         /**
5583          * Add a rich notification style to be applied at build time.
5584          *
5585          * @param style Object responsible for modifying the notification style.
5586          */
5587         @NonNull
setStyle(Style style)5588         public Builder setStyle(Style style) {
5589             if (mStyle != style) {
5590                 mStyle = style;
5591                 if (mStyle != null) {
5592                     mStyle.setBuilder(this);
5593                     mN.extras.putString(EXTRA_TEMPLATE, style.getClass().getName());
5594                 }  else {
5595                     mN.extras.remove(EXTRA_TEMPLATE);
5596                 }
5597             }
5598             return this;
5599         }
5600 
5601         /**
5602          * Returns the style set by {@link #setStyle(Style)}.
5603          */
getStyle()5604         public Style getStyle() {
5605             return mStyle;
5606         }
5607 
5608         /**
5609          * Specify the value of {@link #visibility}.
5610          *
5611          * @return The same Builder.
5612          */
5613         @NonNull
setVisibility(@isibility int visibility)5614         public Builder setVisibility(@Visibility int visibility) {
5615             mN.visibility = visibility;
5616             return this;
5617         }
5618 
5619         /**
5620          * Supply a replacement Notification whose contents should be shown in insecure contexts
5621          * (i.e. atop the secure lockscreen). See {@link #visibility} and {@link #VISIBILITY_PUBLIC}.
5622          * @param n A replacement notification, presumably with some or all info redacted.
5623          * @return The same Builder.
5624          */
5625         @NonNull
setPublicVersion(Notification n)5626         public Builder setPublicVersion(Notification n) {
5627             if (n != null) {
5628                 mN.publicVersion = new Notification();
5629                 n.cloneInto(mN.publicVersion, /*heavy=*/ true);
5630             } else {
5631                 mN.publicVersion = null;
5632             }
5633             return this;
5634         }
5635 
5636         /**
5637          * Apply an extender to this notification builder. Extenders may be used to add
5638          * metadata or change options on this builder.
5639          */
5640         @NonNull
extend(Extender extender)5641         public Builder extend(Extender extender) {
5642             extender.extend(this);
5643             return this;
5644         }
5645 
5646         /**
5647          * Set the value for a notification flag
5648          *
5649          * @param mask Bit mask of the flag
5650          * @param value Status (on/off) of the flag
5651          *
5652          * @return The same Builder.
5653          */
5654         @NonNull
setFlag(@otificationFlags int mask, boolean value)5655         public Builder setFlag(@NotificationFlags int mask, boolean value) {
5656             if (value) {
5657                 mN.flags |= mask;
5658             } else {
5659                 mN.flags &= ~mask;
5660             }
5661             return this;
5662         }
5663 
5664         /**
5665          * Sets {@link Notification#color}.
5666          *
5667          * @param argb The accent color to use
5668          *
5669          * @return The same Builder.
5670          */
5671         @NonNull
setColor(@olorInt int argb)5672         public Builder setColor(@ColorInt int argb) {
5673             mN.color = argb;
5674             sanitizeColor();
5675             return this;
5676         }
5677 
bindPhishingAlertIcon(RemoteViews contentView, StandardTemplateParams p)5678         private void bindPhishingAlertIcon(RemoteViews contentView, StandardTemplateParams p) {
5679             contentView.setDrawableTint(
5680                     R.id.phishing_alert,
5681                     false /* targetBackground */,
5682                     getColors(p).getErrorColor(),
5683                     PorterDuff.Mode.SRC_ATOP);
5684         }
5685 
getProfileBadgeDrawable()5686         private Drawable getProfileBadgeDrawable() {
5687             if (mContext.getUserId() == UserHandle.USER_SYSTEM) {
5688                 // This user can never be a badged profile,
5689                 // and also includes USER_ALL system notifications.
5690                 return null;
5691             }
5692             // Note: This assumes that the current user can read the profile badge of the
5693             // originating user.
5694             DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
5695             return dpm.getResources().getDrawable(
5696                     getUpdatableProfileBadgeId(), SOLID_COLORED, NOTIFICATION,
5697                     this::getDefaultProfileBadgeDrawable);
5698         }
5699 
getUpdatableProfileBadgeId()5700         private String getUpdatableProfileBadgeId() {
5701             return mContext.getSystemService(UserManager.class).isManagedProfile()
5702                     ? WORK_PROFILE_ICON : UNDEFINED;
5703         }
5704 
getDefaultProfileBadgeDrawable()5705         private Drawable getDefaultProfileBadgeDrawable() {
5706             return mContext.getPackageManager().getUserBadgeForDensityNoBackground(
5707                     new UserHandle(mContext.getUserId()), 0);
5708         }
5709 
getProfileBadge()5710         private Bitmap getProfileBadge() {
5711             Drawable badge = getProfileBadgeDrawable();
5712             if (badge == null) {
5713                 return null;
5714             }
5715             final int size = mContext.getResources().getDimensionPixelSize(
5716                     R.dimen.notification_badge_size);
5717             Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
5718             Canvas canvas = new Canvas(bitmap);
5719             badge.setBounds(0, 0, size, size);
5720             badge.draw(canvas);
5721             return bitmap;
5722         }
5723 
bindProfileBadge(RemoteViews contentView, StandardTemplateParams p)5724         private void bindProfileBadge(RemoteViews contentView, StandardTemplateParams p) {
5725             Bitmap profileBadge = getProfileBadge();
5726 
5727             if (profileBadge != null) {
5728                 contentView.setImageViewBitmap(R.id.profile_badge, profileBadge);
5729                 contentView.setViewVisibility(R.id.profile_badge, View.VISIBLE);
5730                 if (isBackgroundColorized(p)) {
5731                     contentView.setDrawableTint(R.id.profile_badge, false,
5732                             getPrimaryTextColor(p), PorterDuff.Mode.SRC_ATOP);
5733                 }
5734                 contentView.setContentDescription(
5735                         R.id.profile_badge,
5736                         mContext.getSystemService(UserManager.class)
5737                                 .getProfileAccessibilityString(mContext.getUserId()));
5738             }
5739         }
5740 
bindAlertedIcon(RemoteViews contentView, StandardTemplateParams p)5741         private void bindAlertedIcon(RemoteViews contentView, StandardTemplateParams p) {
5742             contentView.setDrawableTint(
5743                     R.id.alerted_icon,
5744                     false /* targetBackground */,
5745                     getColors(p).getSecondaryTextColor(),
5746                     PorterDuff.Mode.SRC_IN);
5747         }
5748 
5749         /**
5750          * @hide
5751          */
usesStandardHeader()5752         public boolean usesStandardHeader() {
5753             if (mN.mUsesStandardHeader) {
5754                 return true;
5755             }
5756             if (mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.N) {
5757                 if (mN.contentView == null && mN.bigContentView == null) {
5758                     return true;
5759                 }
5760             }
5761             boolean contentViewUsesHeader = mN.contentView == null
5762                     || STANDARD_LAYOUTS.contains(mN.contentView.getLayoutId());
5763             boolean bigContentViewUsesHeader = mN.bigContentView == null
5764                     || STANDARD_LAYOUTS.contains(mN.bigContentView.getLayoutId());
5765             return contentViewUsesHeader && bigContentViewUsesHeader;
5766         }
5767 
resetStandardTemplate(RemoteViews contentView)5768         private void resetStandardTemplate(RemoteViews contentView) {
5769             resetNotificationHeader(contentView);
5770             contentView.setViewVisibility(R.id.right_icon, View.GONE);
5771             contentView.setViewVisibility(R.id.title, View.GONE);
5772             contentView.setTextViewText(R.id.title, null);
5773             contentView.setViewVisibility(R.id.text, View.GONE);
5774             contentView.setTextViewText(R.id.text, null);
5775         }
5776 
5777         /**
5778          * Resets the notification header to its original state
5779          */
resetNotificationHeader(RemoteViews contentView)5780         private void resetNotificationHeader(RemoteViews contentView) {
5781             // Small icon doesn't need to be reset, as it's always set. Resetting would prevent
5782             // re-using the drawable when the notification is updated.
5783             contentView.setBoolean(R.id.expand_button, "setExpanded", false);
5784             contentView.setViewVisibility(R.id.app_name_text, View.GONE);
5785             contentView.setTextViewText(R.id.app_name_text, null);
5786             contentView.setViewVisibility(R.id.chronometer, View.GONE);
5787             contentView.setViewVisibility(R.id.header_text, View.GONE);
5788             contentView.setTextViewText(R.id.header_text, null);
5789             contentView.setViewVisibility(R.id.header_text_secondary, View.GONE);
5790             contentView.setTextViewText(R.id.header_text_secondary, null);
5791             contentView.setViewVisibility(R.id.header_text_divider, View.GONE);
5792             contentView.setViewVisibility(R.id.header_text_secondary_divider, View.GONE);
5793             contentView.setViewVisibility(R.id.time_divider, View.GONE);
5794             contentView.setViewVisibility(R.id.time, View.GONE);
5795             contentView.setImageViewIcon(R.id.profile_badge, null);
5796             contentView.setViewVisibility(R.id.profile_badge, View.GONE);
5797             mN.mUsesStandardHeader = false;
5798         }
5799 
applyStandardTemplate(int resId, StandardTemplateParams p, TemplateBindResult result)5800         private RemoteViews applyStandardTemplate(int resId, StandardTemplateParams p,
5801                 TemplateBindResult result) {
5802             p.headerless(resId == getBaseLayoutResource()
5803                     || resId == getHeadsUpBaseLayoutResource()
5804                     || resId == getCompactHeadsUpBaseLayoutResource()
5805                     || resId == getMessagingCompactHeadsUpLayoutResource()
5806                     || resId == getMessagingLayoutResource()
5807                     || resId == R.layout.notification_template_material_media);
5808             RemoteViews contentView = new BuilderRemoteViews(mContext.getApplicationInfo(), resId);
5809 
5810             resetStandardTemplate(contentView);
5811 
5812             final Bundle ex = mN.extras;
5813             updateBackgroundColor(contentView, p);
5814             bindNotificationHeader(contentView, p);
5815             bindLargeIconAndApplyMargin(contentView, p, result);
5816             boolean showProgress = handleProgressBar(contentView, ex, p);
5817             boolean hasSecondLine = showProgress;
5818             if (p.hasTitle()) {
5819                 contentView.setViewVisibility(p.mTitleViewId, View.VISIBLE);
5820                 contentView.setTextViewText(p.mTitleViewId,
5821                         ensureColorSpanContrastOrStripStyling(p.mTitle, p));
5822                 setTextViewColorPrimary(contentView, p.mTitleViewId, p);
5823             } else if (p.mTitleViewId != R.id.title) {
5824                 // This alternate title view ID is not cleared by resetStandardTemplate
5825                 contentView.setViewVisibility(p.mTitleViewId, View.GONE);
5826                 contentView.setTextViewText(p.mTitleViewId, null);
5827             }
5828             if (p.mText != null && p.mText.length() != 0
5829                     && (!showProgress || p.mAllowTextWithProgress)) {
5830                 contentView.setViewVisibility(p.mTextViewId, View.VISIBLE);
5831                 contentView.setTextViewText(p.mTextViewId,
5832                         ensureColorSpanContrastOrStripStyling(p.mText, p));
5833                 setTextViewColorSecondary(contentView, p.mTextViewId, p);
5834                 hasSecondLine = true;
5835             } else if (p.mTextViewId != R.id.text) {
5836                 // This alternate text view ID is not cleared by resetStandardTemplate
5837                 contentView.setViewVisibility(p.mTextViewId, View.GONE);
5838                 contentView.setTextViewText(p.mTextViewId, null);
5839             }
5840             setHeaderlessVerticalMargins(contentView, p, hasSecondLine);
5841 
5842             return contentView;
5843         }
5844 
setHeaderlessVerticalMargins(RemoteViews contentView, StandardTemplateParams p, boolean hasSecondLine)5845         private static void setHeaderlessVerticalMargins(RemoteViews contentView,
5846                 StandardTemplateParams p, boolean hasSecondLine) {
5847             if (!p.mHeaderless) {
5848                 return;
5849             }
5850             int marginDimen = hasSecondLine
5851                     ? R.dimen.notification_headerless_margin_twoline
5852                     : R.dimen.notification_headerless_margin_oneline;
5853             contentView.setViewLayoutMarginDimen(R.id.notification_headerless_view_column,
5854                     RemoteViews.MARGIN_TOP, marginDimen);
5855             contentView.setViewLayoutMarginDimen(R.id.notification_headerless_view_column,
5856                     RemoteViews.MARGIN_BOTTOM, marginDimen);
5857         }
5858 
setTextViewColorPrimary(RemoteViews contentView, @IdRes int id, StandardTemplateParams p)5859         private void setTextViewColorPrimary(RemoteViews contentView, @IdRes int id,
5860                 StandardTemplateParams p) {
5861             contentView.setTextColor(id, getPrimaryTextColor(p));
5862         }
5863 
5864         /**
5865          * @param p the template params to inflate this with
5866          * @return the primary text color
5867          * @hide
5868          */
5869         @VisibleForTesting
getPrimaryTextColor(StandardTemplateParams p)5870         public @ColorInt int getPrimaryTextColor(StandardTemplateParams p) {
5871             return getColors(p).getPrimaryTextColor();
5872         }
5873 
5874         /**
5875          * @param p the template params to inflate this with
5876          * @return the secondary text color
5877          * @hide
5878          */
5879         @VisibleForTesting
getSecondaryTextColor(StandardTemplateParams p)5880         public @ColorInt int getSecondaryTextColor(StandardTemplateParams p) {
5881             return getColors(p).getSecondaryTextColor();
5882         }
5883 
setTextViewColorSecondary(RemoteViews contentView, @IdRes int id, StandardTemplateParams p)5884         private void setTextViewColorSecondary(RemoteViews contentView, @IdRes int id,
5885                 StandardTemplateParams p) {
5886             contentView.setTextColor(id, getSecondaryTextColor(p));
5887         }
5888 
getColors(StandardTemplateParams p)5889         private Colors getColors(StandardTemplateParams p) {
5890             mColors.resolvePalette(mContext, mN.color, isBackgroundColorized(p), mInNightMode);
5891             return mColors;
5892         }
5893 
5894         /**
5895          * @param isHeader If the notification is a notification header
5896          * @return An instance of mColors after resolving the palette
5897          */
getColors(boolean isHeader)5898         private Colors getColors(boolean isHeader) {
5899             mColors.resolvePalette(mContext, mN.color, !isHeader && mN.isColorized(), mInNightMode);
5900             return mColors;
5901         }
5902 
updateBackgroundColor(RemoteViews contentView, StandardTemplateParams p)5903         private void updateBackgroundColor(RemoteViews contentView,
5904                 StandardTemplateParams p) {
5905             if (isBackgroundColorized(p)) {
5906                 contentView.setInt(R.id.status_bar_latest_event_content, "setBackgroundColor",
5907                         getBackgroundColor(p));
5908             } else {
5909                 // Clear it!
5910                 contentView.setInt(R.id.status_bar_latest_event_content, "setBackgroundResource",
5911                         0);
5912             }
5913         }
5914 
handleProgressBar(RemoteViews contentView, Bundle ex, StandardTemplateParams p)5915         private boolean handleProgressBar(RemoteViews contentView, Bundle ex,
5916                 StandardTemplateParams p) {
5917             final int max = ex.getInt(EXTRA_PROGRESS_MAX, 0);
5918             final int progress = ex.getInt(EXTRA_PROGRESS, 0);
5919             final boolean ind = ex.getBoolean(EXTRA_PROGRESS_INDETERMINATE);
5920             if (!p.mHideProgress && (max != 0 || ind)) {
5921                 contentView.setViewVisibility(com.android.internal.R.id.progress, View.VISIBLE);
5922                 contentView.setProgressBar(R.id.progress, max, progress, ind);
5923                 contentView.setProgressBackgroundTintList(R.id.progress,
5924                         mContext.getColorStateList(R.color.notification_progress_background_color));
5925                 ColorStateList progressTint = ColorStateList.valueOf(getPrimaryAccentColor(p));
5926                 contentView.setProgressTintList(R.id.progress, progressTint);
5927                 contentView.setProgressIndeterminateTintList(R.id.progress, progressTint);
5928                 return true;
5929             } else {
5930                 contentView.setViewVisibility(R.id.progress, View.GONE);
5931                 return false;
5932             }
5933         }
5934 
bindLargeIconAndApplyMargin(RemoteViews contentView, @NonNull StandardTemplateParams p, @Nullable TemplateBindResult result)5935         private void bindLargeIconAndApplyMargin(RemoteViews contentView,
5936                 @NonNull StandardTemplateParams p,
5937                 @Nullable TemplateBindResult result) {
5938             if (result == null) {
5939                 result = new TemplateBindResult();
5940             }
5941             bindLargeIcon(contentView, p, result);
5942             if (!p.mHeaderless) {
5943                 // views in states with a header (big states)
5944                 result.mHeadingExtraMarginSet.applyToView(contentView, R.id.notification_header);
5945                 result.mTitleMarginSet.applyToView(contentView, R.id.title);
5946                 // If there is no title, the text (or big_text) needs to wrap around the image
5947                 result.mTitleMarginSet.applyToView(contentView, p.mTextViewId);
5948                 contentView.setInt(p.mTextViewId, "setNumIndentLines", p.hasTitle() ? 0 : 1);
5949             }
5950         }
5951 
5952         // This code is executed on behalf of other apps' notifications, sometimes even by 3p apps,
5953         // a use case that is not supported by the Compat Framework library.  Workarounds to resolve
5954         // the change's state in NotificationManagerService were very complex. These behavior
5955         // changes are entirely visual, and should otherwise be undetectable by apps.
5956         @SuppressWarnings("AndroidFrameworkCompatChange")
calculateRightIconDimens(Icon rightIcon, boolean isPromotedPicture, @NonNull TemplateBindResult result)5957         private void calculateRightIconDimens(Icon rightIcon, boolean isPromotedPicture,
5958                 @NonNull TemplateBindResult result) {
5959             final Resources resources = mContext.getResources();
5960             final float density = resources.getDisplayMetrics().density;
5961             final float iconMarginDp = resources.getDimension(
5962                     R.dimen.notification_right_icon_content_margin) / density;
5963             final float contentMarginDp = resources.getDimension(
5964                     R.dimen.notification_content_margin_end) / density;
5965             final float expanderSizeDp = resources.getDimension(
5966                     R.dimen.notification_header_expand_icon_size) / density - contentMarginDp;
5967             final float viewHeightDp = resources.getDimension(
5968                     R.dimen.notification_right_icon_size) / density;
5969             float viewWidthDp = viewHeightDp;  // icons are 1:1 by default
5970             if (rightIcon != null && (isPromotedPicture
5971                     || mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.S)) {
5972                 Drawable drawable = rightIcon.loadDrawable(mContext);
5973                 if (drawable != null) {
5974                     int iconWidth = drawable.getIntrinsicWidth();
5975                     int iconHeight = drawable.getIntrinsicHeight();
5976                     if (iconWidth > iconHeight && iconHeight > 0) {
5977                         final float maxViewWidthDp = viewHeightDp * MAX_LARGE_ICON_ASPECT_RATIO;
5978                         viewWidthDp = Math.min(viewHeightDp * iconWidth / iconHeight,
5979                                 maxViewWidthDp);
5980                     }
5981                 }
5982             }
5983             final float extraMarginEndDpIfVisible = viewWidthDp + iconMarginDp;
5984             result.setRightIconState(rightIcon != null /* visible */, viewWidthDp,
5985                     viewHeightDp, extraMarginEndDpIfVisible, expanderSizeDp);
5986         }
5987 
5988         /**
5989          * Bind the large icon.
5990          */
bindLargeIcon(RemoteViews contentView, @NonNull StandardTemplateParams p, @NonNull TemplateBindResult result)5991         private void bindLargeIcon(RemoteViews contentView, @NonNull StandardTemplateParams p,
5992                 @NonNull TemplateBindResult result) {
5993             if (mN.mLargeIcon == null && mN.largeIcon != null) {
5994                 mN.mLargeIcon = Icon.createWithBitmap(mN.largeIcon);
5995             }
5996 
5997             // Determine the left and right icons
5998             Icon leftIcon = p.mHideLeftIcon ? null : mN.mLargeIcon;
5999             Icon rightIcon = p.mHideRightIcon ? null
6000                     : (p.mPromotedPicture != null ? p.mPromotedPicture : mN.mLargeIcon);
6001 
6002             // Apply the left icon (without duplicating the bitmap)
6003             if (leftIcon != rightIcon || leftIcon == null) {
6004                 // If the leftIcon is explicitly hidden or different from the rightIcon, then set it
6005                 // explicitly and make sure it won't take the right_icon drawable.
6006                 contentView.setImageViewIcon(R.id.left_icon, leftIcon);
6007                 contentView.setIntTag(R.id.left_icon, R.id.tag_uses_right_icon_drawable, 0);
6008             } else {
6009                 // If the leftIcon equals the rightIcon, just set the flag to use the right_icon
6010                 // drawable.  This avoids the view having two copies of the same bitmap.
6011                 contentView.setIntTag(R.id.left_icon, R.id.tag_uses_right_icon_drawable, 1);
6012             }
6013 
6014             // Always calculate dimens to populate `result` for the GONE case
6015             boolean isPromotedPicture = p.mPromotedPicture != null;
6016             calculateRightIconDimens(rightIcon, isPromotedPicture, result);
6017 
6018             // Bind the right icon
6019             if (rightIcon != null) {
6020                 contentView.setViewLayoutWidth(R.id.right_icon,
6021                         result.mRightIconWidthDp, TypedValue.COMPLEX_UNIT_DIP);
6022                 contentView.setViewLayoutHeight(R.id.right_icon,
6023                         result.mRightIconHeightDp, TypedValue.COMPLEX_UNIT_DIP);
6024                 contentView.setViewVisibility(R.id.right_icon, View.VISIBLE);
6025                 contentView.setImageViewIcon(R.id.right_icon, rightIcon);
6026                 contentView.setIntTag(R.id.right_icon, R.id.tag_keep_when_showing_left_icon,
6027                         isPromotedPicture ? 1 : 0);
6028                 processLargeLegacyIcon(rightIcon, contentView, p);
6029             } else {
6030                 // The "reset" doesn't clear the drawable, so we do it here.  This clear is
6031                 // important because the presence of a drawable in this view (regardless of the
6032                 // visibility) is used by NotificationGroupingUtil to set the visibility.
6033                 contentView.setImageViewIcon(R.id.right_icon, null);
6034                 contentView.setIntTag(R.id.right_icon, R.id.tag_keep_when_showing_left_icon, 0);
6035             }
6036         }
6037 
bindNotificationHeader(RemoteViews contentView, StandardTemplateParams p)6038         private void bindNotificationHeader(RemoteViews contentView, StandardTemplateParams p) {
6039             bindSmallIcon(contentView, p);
6040             // Populate text left-to-right so that separators are only shown between strings
6041             boolean hasTextToLeft = bindHeaderAppName(contentView, p, false /* force */);
6042             hasTextToLeft |= bindHeaderTextSecondary(contentView, p, hasTextToLeft);
6043             hasTextToLeft |= bindHeaderText(contentView, p, hasTextToLeft);
6044             if (!hasTextToLeft) {
6045                 // If there's still no text, force add the app name so there is some text.
6046                 hasTextToLeft |= bindHeaderAppName(contentView, p, true /* force */);
6047             }
6048             bindHeaderChronometerAndTime(contentView, p, hasTextToLeft);
6049             bindPhishingAlertIcon(contentView, p);
6050             bindProfileBadge(contentView, p);
6051             bindAlertedIcon(contentView, p);
6052             bindExpandButton(contentView, p);
6053             mN.mUsesStandardHeader = true;
6054         }
6055 
bindExpandButton(RemoteViews contentView, StandardTemplateParams p)6056         private void bindExpandButton(RemoteViews contentView, StandardTemplateParams p) {
6057             // set default colors
6058             int bgColor = getBackgroundColor(p);
6059             int pillColor = Colors.flattenAlpha(getColors(p).getProtectionColor(), bgColor);
6060             int textColor = Colors.flattenAlpha(getPrimaryTextColor(p), pillColor);
6061             contentView.setInt(R.id.expand_button, "setDefaultTextColor", textColor);
6062             contentView.setInt(R.id.expand_button, "setDefaultPillColor", pillColor);
6063             // Use different highlighted colors for conversations' unread count
6064             if (p.mHighlightExpander) {
6065                 pillColor = Colors.flattenAlpha(
6066                         getColors(p).getTertiaryFixedDimAccentColor(), bgColor);
6067                 textColor = Colors.flattenAlpha(
6068                         getColors(p).getOnTertiaryFixedAccentTextColor(), pillColor);
6069             }
6070             contentView.setInt(R.id.expand_button, "setHighlightTextColor", textColor);
6071             contentView.setInt(R.id.expand_button, "setHighlightPillColor", pillColor);
6072         }
6073 
bindHeaderChronometerAndTime(RemoteViews contentView, StandardTemplateParams p, boolean hasTextToLeft)6074         private void bindHeaderChronometerAndTime(RemoteViews contentView,
6075                 StandardTemplateParams p, boolean hasTextToLeft) {
6076             if (!p.mHideTime && showsTimeOrChronometer()) {
6077                 if (hasTextToLeft) {
6078                     contentView.setViewVisibility(R.id.time_divider, View.VISIBLE);
6079                     setTextViewColorSecondary(contentView, R.id.time_divider, p);
6080                 }
6081                 if (mN.extras.getBoolean(EXTRA_SHOW_CHRONOMETER)) {
6082                     contentView.setViewVisibility(R.id.chronometer, View.VISIBLE);
6083                     contentView.setLong(R.id.chronometer, "setBase", mN.getWhen()
6084                             + (SystemClock.elapsedRealtime() - System.currentTimeMillis()));
6085                     contentView.setBoolean(R.id.chronometer, "setStarted", true);
6086                     boolean countsDown = mN.extras.getBoolean(EXTRA_CHRONOMETER_COUNT_DOWN);
6087                     contentView.setChronometerCountDown(R.id.chronometer, countsDown);
6088                     setTextViewColorSecondary(contentView, R.id.chronometer, p);
6089                 } else {
6090                     contentView.setViewVisibility(R.id.time, View.VISIBLE);
6091                     contentView.setLong(R.id.time, "setTime", mN.getWhen());
6092                     setTextViewColorSecondary(contentView, R.id.time, p);
6093                 }
6094             } else {
6095                 // We still want a time to be set but gone, such that we can show and hide it
6096                 // on demand in case it's a child notification without anything in the header
6097                 contentView.setLong(R.id.time, "setTime", mN.getWhen() != 0 ? mN.getWhen() :
6098                         mN.creationTime);
6099                 setTextViewColorSecondary(contentView, R.id.time, p);
6100             }
6101         }
6102 
6103         /**
6104          * @return true if the header text will be visible
6105          */
bindHeaderText(RemoteViews contentView, StandardTemplateParams p, boolean hasTextToLeft)6106         private boolean bindHeaderText(RemoteViews contentView, StandardTemplateParams p,
6107                 boolean hasTextToLeft) {
6108             if (p.mHideSubText) {
6109                 return false;
6110             }
6111             CharSequence headerText = p.mSubText;
6112             if (headerText == null && mStyle != null && mStyle.mSummaryTextSet
6113                     && mStyle.hasSummaryInHeader()) {
6114                 headerText = mStyle.mSummaryText;
6115             }
6116             if (headerText == null
6117                     && mContext.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N
6118                     && mN.extras.getCharSequence(EXTRA_INFO_TEXT) != null) {
6119                 headerText = mN.extras.getCharSequence(EXTRA_INFO_TEXT);
6120             }
6121             if (!TextUtils.isEmpty(headerText)) {
6122                 contentView.setTextViewText(R.id.header_text, ensureColorSpanContrastOrStripStyling(
6123                         processLegacyText(headerText), p));
6124                 setTextViewColorSecondary(contentView, R.id.header_text, p);
6125                 contentView.setViewVisibility(R.id.header_text, View.VISIBLE);
6126                 if (hasTextToLeft) {
6127                     contentView.setViewVisibility(R.id.header_text_divider, View.VISIBLE);
6128                     setTextViewColorSecondary(contentView, R.id.header_text_divider, p);
6129                 }
6130                 return true;
6131             }
6132             return false;
6133         }
6134 
6135         /**
6136          * @return true if the secondary header text will be visible
6137          */
bindHeaderTextSecondary(RemoteViews contentView, StandardTemplateParams p, boolean hasTextToLeft)6138         private boolean bindHeaderTextSecondary(RemoteViews contentView, StandardTemplateParams p,
6139                 boolean hasTextToLeft) {
6140             if (p.mHideSubText) {
6141                 return false;
6142             }
6143             if (!TextUtils.isEmpty(p.mHeaderTextSecondary)) {
6144                 contentView.setTextViewText(R.id.header_text_secondary,
6145                         ensureColorSpanContrastOrStripStyling(
6146                                 processLegacyText(p.mHeaderTextSecondary), p));
6147                 setTextViewColorSecondary(contentView, R.id.header_text_secondary, p);
6148                 contentView.setViewVisibility(R.id.header_text_secondary, View.VISIBLE);
6149                 if (hasTextToLeft) {
6150                     contentView.setViewVisibility(R.id.header_text_secondary_divider, View.VISIBLE);
6151                     setTextViewColorSecondary(contentView, R.id.header_text_secondary_divider, p);
6152                 }
6153                 return true;
6154             }
6155             return false;
6156         }
6157 
6158         /**
6159          * @hide
6160          */
6161         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
loadHeaderAppName()6162         public String loadHeaderAppName() {
6163             return mN.loadHeaderAppName(mContext);
6164         }
6165 
6166         /**
6167          * @return true if the app name will be visible
6168          */
bindHeaderAppName(RemoteViews contentView, StandardTemplateParams p, boolean force)6169         private boolean bindHeaderAppName(RemoteViews contentView, StandardTemplateParams p,
6170                 boolean force) {
6171             if (p.mViewType == StandardTemplateParams.VIEW_TYPE_MINIMIZED && !force) {
6172                 // unless the force flag is set, don't show the app name in the minimized state.
6173                 return false;
6174             }
6175             if (p.mHeaderless && p.hasTitle()) {
6176                 // the headerless template will have the TITLE in this position; return true to
6177                 // keep the divider visible between that title and the next text element.
6178                 return true;
6179             }
6180             if (p.mHideAppName) {
6181                 // The app name is being hidden, so we definitely want to return here.
6182                 // Assume that there is a title which will replace it in the header.
6183                 return p.hasTitle();
6184             }
6185             contentView.setViewVisibility(R.id.app_name_text, View.VISIBLE);
6186             contentView.setTextViewText(R.id.app_name_text, loadHeaderAppName());
6187             contentView.setTextColor(R.id.app_name_text, getSecondaryTextColor(p));
6188             return true;
6189         }
6190 
6191         /**
6192          * Determines if the notification should be colorized *for the purposes of applying colors*.
6193          * If this is the minimized view of a colorized notification, this will return false so that
6194          * internal coloring logic can still render the notification normally.
6195          */
isBackgroundColorized(StandardTemplateParams p)6196         private boolean isBackgroundColorized(StandardTemplateParams p) {
6197             return p.allowColorization && mN.isColorized();
6198         }
6199 
isCallActionColorCustomizable()6200         private boolean isCallActionColorCustomizable() {
6201             // NOTE: this doesn't need to check StandardTemplateParams.allowColorization because
6202             //  that is only used for disallowing colorization of headers for the minimized state,
6203             //  and neither of those conditions applies when showing actions.
6204             //  Not requiring StandardTemplateParams as an argument simplifies the creation process.
6205             return mN.isColorized() && mContext.getResources().getBoolean(
6206                     R.bool.config_callNotificationActionColorsRequireColorized);
6207         }
6208 
bindSmallIcon(RemoteViews contentView, StandardTemplateParams p)6209         private void bindSmallIcon(RemoteViews contentView, StandardTemplateParams p) {
6210             if (Flags.notificationsUseAppIcon()) {
6211                 // Override small icon with app icon
6212                 mN.mSmallIcon = Icon.createWithResource(mContext,
6213                         mN.getHeaderAppIconRes(mContext));
6214             } else if (mN.mSmallIcon == null && mN.icon != 0) {
6215                 mN.mSmallIcon = Icon.createWithResource(mContext, mN.icon);
6216             }
6217 
6218             boolean usingAppIcon = false;
6219             if (Flags.notificationsUseAppIconInRow() && !mN.belongsToHeadlessSystemApp(mContext)) {
6220                 // Use the app icon in the view
6221                 int appIconRes = mN.getHeaderAppIconRes(mContext);
6222                 if (appIconRes != 0) {
6223                     mN.mAppIcon = Icon.createWithResource(mContext, appIconRes);
6224                     contentView.setImageViewIcon(R.id.icon, mN.mAppIcon);
6225                     usingAppIcon = true;
6226                 } else {
6227                     Log.w(TAG, "bindSmallIcon: could not get the app icon");
6228                 }
6229             }
6230             if (!usingAppIcon) {
6231                 contentView.setImageViewIcon(R.id.icon, mN.mSmallIcon);
6232             }
6233             contentView.setInt(R.id.icon, "setImageLevel", mN.iconLevel);
6234 
6235             // Don't change color if we're using the app icon.
6236             if (!Flags.notificationsUseAppIcon() && !usingAppIcon) {
6237                 processSmallIconColor(mN.mSmallIcon, contentView, p);
6238             }
6239         }
6240 
6241         /**
6242          * @return true if the built notification will show the time or the chronometer; false
6243          *         otherwise
6244          */
showsTimeOrChronometer()6245         private boolean showsTimeOrChronometer() {
6246             return mN.showsTime() || mN.showsChronometer();
6247         }
6248 
resetStandardTemplateWithActions(RemoteViews big)6249         private void resetStandardTemplateWithActions(RemoteViews big) {
6250             // actions_container is only reset when there are no actions to avoid focus issues with
6251             // remote inputs.
6252             big.setViewVisibility(R.id.actions, View.GONE);
6253             big.removeAllViews(R.id.actions);
6254 
6255             big.setViewVisibility(R.id.notification_material_reply_container, View.GONE);
6256             big.setTextViewText(R.id.notification_material_reply_text_1, null);
6257             big.setViewVisibility(R.id.notification_material_reply_text_1_container, View.GONE);
6258             big.setViewVisibility(R.id.notification_material_reply_progress, View.GONE);
6259 
6260             big.setViewVisibility(R.id.notification_material_reply_text_2, View.GONE);
6261             big.setTextViewText(R.id.notification_material_reply_text_2, null);
6262             big.setViewVisibility(R.id.notification_material_reply_text_3, View.GONE);
6263             big.setTextViewText(R.id.notification_material_reply_text_3, null);
6264 
6265             // This may get erased by bindSnoozeAction
6266             big.setViewLayoutMarginDimen(R.id.notification_action_list_margin_target,
6267                     RemoteViews.MARGIN_BOTTOM, R.dimen.notification_content_margin);
6268         }
6269 
bindSnoozeAction(RemoteViews big, StandardTemplateParams p)6270         private void bindSnoozeAction(RemoteViews big, StandardTemplateParams p) {
6271             boolean hideSnoozeButton = mN.isFgsOrUij()
6272                     || mN.fullScreenIntent != null
6273                     || isBackgroundColorized(p)
6274                     || p.mViewType != StandardTemplateParams.VIEW_TYPE_BIG;
6275             big.setBoolean(R.id.snooze_button, "setEnabled", !hideSnoozeButton);
6276             if (hideSnoozeButton) {
6277                 // Only hide; NotificationContentView will show it when it adds the click listener
6278                 big.setViewVisibility(R.id.snooze_button, View.GONE);
6279             }
6280 
6281             final boolean snoozeEnabled = !hideSnoozeButton
6282                     && mContext.getContentResolver() != null
6283                     && isSnoozeSettingEnabled();
6284             if (snoozeEnabled) {
6285                 big.setViewLayoutMarginDimen(R.id.notification_action_list_margin_target,
6286                         RemoteViews.MARGIN_BOTTOM, 0);
6287             }
6288         }
6289 
isSnoozeSettingEnabled()6290         private boolean isSnoozeSettingEnabled() {
6291             try {
6292                 return Settings.Secure.getIntForUser(mContext.getContentResolver(),
6293                     Settings.Secure.SHOW_NOTIFICATION_SNOOZE, 0, UserHandle.USER_CURRENT) == 1;
6294             } catch (SecurityException ex) {
6295                 // Most 3p apps can't access this snooze setting, so their NotificationListeners
6296                 // would be unable to create notification views if we propagated this exception.
6297                 return false;
6298             }
6299         }
6300 
6301         /**
6302          * Returns the actions that are not contextual.
6303          */
getNonContextualActions()6304         private @NonNull List<Notification.Action> getNonContextualActions() {
6305             if (mActions == null) return Collections.emptyList();
6306             List<Notification.Action> standardActions = new ArrayList<>();
6307             for (Notification.Action action : mActions) {
6308                 if (!action.isContextual()) {
6309                     standardActions.add(action);
6310                 }
6311             }
6312             return standardActions;
6313         }
6314 
applyStandardTemplateWithActions(int layoutId, StandardTemplateParams p, TemplateBindResult result)6315         private RemoteViews applyStandardTemplateWithActions(int layoutId,
6316                 StandardTemplateParams p, TemplateBindResult result) {
6317             RemoteViews big = applyStandardTemplate(layoutId, p, result);
6318 
6319             resetStandardTemplateWithActions(big);
6320             bindSnoozeAction(big, p);
6321             // color the snooze and bubble actions with the theme color
6322             ColorStateList actionColor = ColorStateList.valueOf(getStandardActionColor(p));
6323             big.setColorStateList(R.id.snooze_button, "setImageTintList", actionColor);
6324             big.setColorStateList(R.id.bubble_button, "setImageTintList", actionColor);
6325 
6326             boolean validRemoteInput = false;
6327 
6328             // In the UI, contextual actions appear separately from the standard actions, so we
6329             // filter them out here.
6330             List<Notification.Action> nonContextualActions = getNonContextualActions();
6331 
6332             int numActions = Math.min(nonContextualActions.size(), MAX_ACTION_BUTTONS);
6333             boolean emphasizedMode = mN.fullScreenIntent != null
6334                     || p.mCallStyleActions
6335                     || ((mN.flags & FLAG_FSI_REQUESTED_BUT_DENIED) != 0);
6336 
6337             if (p.mCallStyleActions) {
6338                 // Clear view padding to allow buttons to start on the left edge.
6339                 // This must be done before 'setEmphasizedMode' which sets top/bottom margins.
6340                 big.setViewPadding(R.id.actions, 0, 0, 0, 0);
6341                 // Add an optional indent that will make buttons start at the correct column when
6342                 // there is enough space to do so (and fall back to the left edge if not).
6343                 big.setInt(R.id.actions, "setCollapsibleIndentDimen",
6344                         R.dimen.call_notification_collapsible_indent);
6345                 if (evenlyDividedCallStyleActionLayout()) {
6346                     if (CallStyle.DEBUG_NEW_ACTION_LAYOUT) {
6347                         Log.d(TAG, "setting evenly divided mode on action list");
6348                     }
6349                     big.setBoolean(R.id.actions, "setEvenlyDividedMode", true);
6350                 }
6351             }
6352             big.setBoolean(R.id.actions, "setEmphasizedMode", emphasizedMode);
6353             if (numActions > 0 && !p.mHideActions) {
6354                 big.setViewVisibility(R.id.actions_container, View.VISIBLE);
6355                 big.setViewVisibility(R.id.actions, View.VISIBLE);
6356                 big.setViewLayoutMarginDimen(R.id.notification_action_list_margin_target,
6357                         RemoteViews.MARGIN_BOTTOM, 0);
6358                 for (int i = 0; i < numActions; i++) {
6359                     Action action = nonContextualActions.get(i);
6360 
6361                     boolean actionHasValidInput = hasValidRemoteInput(action);
6362                     validRemoteInput |= actionHasValidInput;
6363 
6364                     final RemoteViews button = generateActionButton(action, emphasizedMode, p);
6365                     if (actionHasValidInput && !emphasizedMode) {
6366                         // Clear the drawable
6367                         button.setInt(R.id.action0, "setBackgroundResource", 0);
6368                     }
6369                     if (emphasizedMode && i > 0) {
6370                         // Clear start margin from non-first buttons to reduce the gap between them.
6371                         //  (8dp remaining gap is from all buttons' standard 4dp inset).
6372                         button.setViewLayoutMarginDimen(R.id.action0, RemoteViews.MARGIN_START, 0);
6373                     }
6374                     big.addView(R.id.actions, button);
6375                 }
6376             } else {
6377                 big.setViewVisibility(R.id.actions_container, View.GONE);
6378             }
6379 
6380             RemoteInputHistoryItem[] replyText = getParcelableArrayFromBundle(
6381                     mN.extras, EXTRA_REMOTE_INPUT_HISTORY_ITEMS, RemoteInputHistoryItem.class);
6382             if (validRemoteInput && replyText != null && replyText.length > 0
6383                     && !TextUtils.isEmpty(replyText[0].getText())
6384                     && p.maxRemoteInputHistory > 0) {
6385                 boolean showSpinner = mN.extras.getBoolean(EXTRA_SHOW_REMOTE_INPUT_SPINNER);
6386                 big.setViewVisibility(R.id.notification_material_reply_container, View.VISIBLE);
6387                 big.setViewVisibility(R.id.notification_material_reply_text_1_container,
6388                         View.VISIBLE);
6389                 big.setTextViewText(R.id.notification_material_reply_text_1,
6390                         ensureColorSpanContrastOrStripStyling(replyText[0].getText(), p));
6391                 setTextViewColorSecondary(big, R.id.notification_material_reply_text_1, p);
6392                 big.setViewVisibility(R.id.notification_material_reply_progress,
6393                         showSpinner ? View.VISIBLE : View.GONE);
6394                 big.setProgressIndeterminateTintList(
6395                         R.id.notification_material_reply_progress,
6396                         ColorStateList.valueOf(getPrimaryAccentColor(p)));
6397 
6398                 if (replyText.length > 1 && !TextUtils.isEmpty(replyText[1].getText())
6399                         && p.maxRemoteInputHistory > 1) {
6400                     big.setViewVisibility(R.id.notification_material_reply_text_2, View.VISIBLE);
6401                     big.setTextViewText(R.id.notification_material_reply_text_2,
6402                             ensureColorSpanContrastOrStripStyling(replyText[1].getText(), p));
6403                     setTextViewColorSecondary(big, R.id.notification_material_reply_text_2, p);
6404 
6405                     if (replyText.length > 2 && !TextUtils.isEmpty(replyText[2].getText())
6406                             && p.maxRemoteInputHistory > 2) {
6407                         big.setViewVisibility(
6408                                 R.id.notification_material_reply_text_3, View.VISIBLE);
6409                         big.setTextViewText(R.id.notification_material_reply_text_3,
6410                                 ensureColorSpanContrastOrStripStyling(replyText[2].getText(), p));
6411                         setTextViewColorSecondary(big, R.id.notification_material_reply_text_3, p);
6412                     }
6413                 }
6414             }
6415 
6416             return big;
6417         }
6418 
hasValidRemoteInput(Action action)6419         private boolean hasValidRemoteInput(Action action) {
6420             if (TextUtils.isEmpty(action.title) || action.actionIntent == null) {
6421                 // Weird actions
6422                 return false;
6423             }
6424 
6425             RemoteInput[] remoteInputs = action.getRemoteInputs();
6426             if (remoteInputs == null) {
6427                 return false;
6428             }
6429 
6430             for (RemoteInput r : remoteInputs) {
6431                 CharSequence[] choices = r.getChoices();
6432                 if (r.getAllowFreeFormInput() || (choices != null && choices.length != 0)) {
6433                     return true;
6434                 }
6435             }
6436             return false;
6437         }
6438 
6439         /**
6440          * Construct a RemoteViews representing the standard notification layout.
6441          *
6442          * @deprecated For performance and system health reasons, this API is no longer required to
6443          *  be used directly by the System UI when rendering Notifications to the user. While the UI
6444          *  returned by this method will still represent the content of the Notification being
6445          *  built, it may differ from the visual style of the system.
6446          *
6447          *  NOTE: this API has always had severe limitations; for example it does not support any
6448          *  interactivity, it ignores the app theme, it hard-codes the colors from the system theme
6449          *  at the time it is called, and it does Bitmap decoding on the main thread which can cause
6450          *  UI jank.
6451          */
6452         @Deprecated
createContentView()6453         public RemoteViews createContentView() {
6454             return createContentView(false /* increasedheight */ );
6455         }
6456 
6457         // This code is executed on behalf of other apps' notifications, sometimes even by 3p apps,
6458         // a use case that is not supported by the Compat Framework library.  Workarounds to resolve
6459         // the change's state in NotificationManagerService were very complex. While it's possible
6460         // apps can detect the change, it's most likely that the changes will simply result in
6461         // visual regressions.
6462         @SuppressWarnings("AndroidFrameworkCompatChange")
fullyCustomViewRequiresDecoration(boolean fromStyle)6463         private boolean fullyCustomViewRequiresDecoration(boolean fromStyle) {
6464             // Custom views which come from a platform style class are safe, and thus do not need to
6465             // be wrapped.  Any subclass of those styles has the opportunity to make arbitrary
6466             // changes to the RemoteViews, and thus can't be trusted as a fully vetted view.
6467             if (fromStyle && PLATFORM_STYLE_CLASSES.contains(mStyle.getClass())) {
6468                 return false;
6469             }
6470             return mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.S;
6471         }
6472 
minimallyDecoratedContentView(@onNull RemoteViews customContent)6473         private RemoteViews minimallyDecoratedContentView(@NonNull RemoteViews customContent) {
6474             StandardTemplateParams p = mParams.reset()
6475                     .viewType(StandardTemplateParams.VIEW_TYPE_NORMAL)
6476                     .decorationType(StandardTemplateParams.DECORATION_MINIMAL)
6477                     .fillTextsFrom(this);
6478             TemplateBindResult result = new TemplateBindResult();
6479             RemoteViews standard = applyStandardTemplate(getBaseLayoutResource(), p, result);
6480             buildCustomContentIntoTemplate(mContext, standard, customContent,
6481                     p, result);
6482             return standard;
6483         }
6484 
minimallyDecoratedBigContentView(@onNull RemoteViews customContent)6485         private RemoteViews minimallyDecoratedBigContentView(@NonNull RemoteViews customContent) {
6486             StandardTemplateParams p = mParams.reset()
6487                     .viewType(StandardTemplateParams.VIEW_TYPE_BIG)
6488                     .decorationType(StandardTemplateParams.DECORATION_MINIMAL)
6489                     .fillTextsFrom(this);
6490             TemplateBindResult result = new TemplateBindResult();
6491             RemoteViews standard = applyStandardTemplateWithActions(getBigBaseLayoutResource(),
6492                     p, result);
6493             buildCustomContentIntoTemplate(mContext, standard, customContent,
6494                     p, result);
6495             makeHeaderExpanded(standard);
6496             return standard;
6497         }
6498 
minimallyDecoratedHeadsUpContentView( @onNull RemoteViews customContent)6499         private RemoteViews minimallyDecoratedHeadsUpContentView(
6500                 @NonNull RemoteViews customContent) {
6501             StandardTemplateParams p = mParams.reset()
6502                     .viewType(StandardTemplateParams.VIEW_TYPE_HEADS_UP)
6503                     .decorationType(StandardTemplateParams.DECORATION_MINIMAL)
6504                     .fillTextsFrom(this);
6505             TemplateBindResult result = new TemplateBindResult();
6506             RemoteViews standard = applyStandardTemplateWithActions(getHeadsUpBaseLayoutResource(),
6507                     p, result);
6508             buildCustomContentIntoTemplate(mContext, standard, customContent,
6509                     p, result);
6510             return standard;
6511         }
6512 
6513         /**
6514          * Construct a RemoteViews for the smaller content view.
6515          *
6516          *   @param increasedHeight true if this layout be created with an increased height. Some
6517          *   styles may support showing more then just that basic 1U size
6518          *   and the system may decide to render important notifications
6519          *   slightly bigger even when collapsed.
6520          *
6521          *   @hide
6522          */
createContentView(boolean increasedHeight)6523         public RemoteViews createContentView(boolean increasedHeight) {
6524             if (useExistingRemoteView(mN.contentView)) {
6525                 return fullyCustomViewRequiresDecoration(false /* fromStyle */)
6526                         ? minimallyDecoratedContentView(mN.contentView) : mN.contentView;
6527             } else if (mStyle != null) {
6528                 final RemoteViews styleView = mStyle.makeContentView(increasedHeight);
6529                 if (styleView != null) {
6530                     return fullyCustomViewRequiresDecoration(true /* fromStyle */)
6531                             ? minimallyDecoratedContentView(styleView) : styleView;
6532                 }
6533             }
6534             StandardTemplateParams p = mParams.reset()
6535                     .viewType(StandardTemplateParams.VIEW_TYPE_NORMAL)
6536                     .fillTextsFrom(this);
6537             return applyStandardTemplate(getBaseLayoutResource(), p, null /* result */);
6538         }
6539 
useExistingRemoteView(RemoteViews customContent)6540         private boolean useExistingRemoteView(RemoteViews customContent) {
6541             if (customContent == null) {
6542                 return false;
6543             }
6544             if (styleDisplaysCustomViewInline()) {
6545                 // the provided custom view is intended to be wrapped by the style.
6546                 return false;
6547             }
6548             if (fullyCustomViewRequiresDecoration(false)
6549                     && STANDARD_LAYOUTS.contains(customContent.getLayoutId())) {
6550                 // If the app's custom views are objects returned from Builder.create*ContentView()
6551                 // then the app is most likely attempting to spoof the user.  Even if they are not,
6552                 // the result would be broken (b/189189308) so we will ignore it.
6553                 Log.w(TAG, "For apps targeting S, a custom content view that is a modified "
6554                         + "version of any standard layout is disallowed.");
6555                 return false;
6556             }
6557             return true;
6558         }
6559 
6560         /**
6561          * Construct a RemoteViews representing the expanded notification layout.
6562          *
6563          * @deprecated For performance and system health reasons, this API is no longer required to
6564          *  be used directly by the System UI when rendering Notifications to the user. While the UI
6565          *  returned by this method will still represent the content of the Notification being
6566          *  built, it may differ from the visual style of the system.
6567          *
6568          *  NOTE: this API has always had severe limitations; for example it does not support any
6569          *  interactivity, it ignores the app theme, it hard-codes the colors from the system theme
6570          *  at the time it is called, and it does Bitmap decoding on the main thread which can cause
6571          *  UI jank.
6572          */
6573         @Deprecated
createBigContentView()6574         public RemoteViews createBigContentView() {
6575             RemoteViews result = null;
6576             if (useExistingRemoteView(mN.bigContentView)) {
6577                 return fullyCustomViewRequiresDecoration(false /* fromStyle */)
6578                         ? minimallyDecoratedBigContentView(mN.bigContentView) : mN.bigContentView;
6579             }
6580             if (mStyle != null) {
6581                 result = mStyle.makeBigContentView();
6582                 if (fullyCustomViewRequiresDecoration(true /* fromStyle */)) {
6583                     result = minimallyDecoratedBigContentView(result);
6584                 }
6585             }
6586             if (result == null) {
6587                 if (bigContentViewRequired()) {
6588                     StandardTemplateParams p = mParams.reset()
6589                             .viewType(StandardTemplateParams.VIEW_TYPE_BIG)
6590                             .allowTextWithProgress(true)
6591                             .fillTextsFrom(this);
6592                     result = applyStandardTemplateWithActions(getBigBaseLayoutResource(), p,
6593                             null /* result */);
6594                 }
6595             }
6596             makeHeaderExpanded(result);
6597             return result;
6598         }
6599 
6600         // This code is executed on behalf of other apps' notifications, sometimes even by 3p apps,
6601         // a use case that is not supported by the Compat Framework library.  Workarounds to resolve
6602         // the change's state in NotificationManagerService were very complex. While it's possible
6603         // apps can detect the change, it's most likely that the changes will simply result in
6604         // visual regressions.
6605         @SuppressWarnings("AndroidFrameworkCompatChange")
bigContentViewRequired()6606         private boolean bigContentViewRequired() {
6607             if (Flags.notificationExpansionOptional()) {
6608                 // Notifications without a bigContentView, style, or actions do not need to expand
6609                 boolean exempt = mN.bigContentView == null
6610                         && mStyle == null && mActions.size() == 0;
6611                 return !exempt;
6612             }
6613             if (mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.S) {
6614                 return true;
6615             }
6616             // Notifications with contentView and without a bigContentView, style, or actions would
6617             // not have an expanded state before S, so showing the standard template expanded state
6618             // usually looks wrong, so we keep it simple and don't show the expanded state.
6619             boolean exempt = mN.contentView != null && mN.bigContentView == null
6620                     && mStyle == null && mActions.size() == 0;
6621             return !exempt;
6622         }
6623 
6624         /**
6625          * Construct a RemoteViews for the final notification header only. This will not be
6626          * colorized.
6627          *
6628          * @hide
6629          */
makeNotificationGroupHeader()6630         public RemoteViews makeNotificationGroupHeader() {
6631             return makeNotificationHeader(mParams.reset()
6632                     .viewType(StandardTemplateParams.VIEW_TYPE_GROUP_HEADER)
6633                     .fillTextsFrom(this));
6634         }
6635 
6636         /**
6637          * Construct a RemoteViews for the final notification header only. This will not be
6638          * colorized.
6639          *
6640          * @param p the template params to inflate this with
6641          */
makeNotificationHeader(StandardTemplateParams p)6642         private RemoteViews makeNotificationHeader(StandardTemplateParams p) {
6643             // Headers on their own are never colorized
6644             p.disallowColorization();
6645             RemoteViews header = new BuilderRemoteViews(mContext.getApplicationInfo(),
6646                     R.layout.notification_template_header);
6647             resetNotificationHeader(header);
6648             bindNotificationHeader(header, p);
6649             return header;
6650         }
6651 
6652         /**
6653          * Construct a RemoteViews for the ambient version of the notification.
6654          *
6655          * @hide
6656          */
makeAmbientNotification()6657         public RemoteViews makeAmbientNotification() {
6658             RemoteViews headsUpContentView = createHeadsUpContentView(false /* increasedHeight */);
6659             if (headsUpContentView != null) {
6660                 return headsUpContentView;
6661             }
6662             return createContentView();
6663         }
6664 
6665         /**
6666          * Adapt the Notification header if this view is used as an expanded view.
6667          *
6668          * @hide
6669          */
makeHeaderExpanded(RemoteViews result)6670         public static void makeHeaderExpanded(RemoteViews result) {
6671             if (result != null) {
6672                 result.setBoolean(R.id.expand_button, "setExpanded", true);
6673             }
6674         }
6675 
6676         /**
6677          * Construct a RemoteViews for the final heads-up notification layout.
6678          *
6679          * @param increasedHeight true if this layout be created with an increased height. Some
6680          * styles may support showing more then just that basic 1U size
6681          * and the system may decide to render important notifications
6682          * slightly bigger even when collapsed.
6683          *
6684          * @hide
6685          */
createHeadsUpContentView(boolean increasedHeight)6686         public RemoteViews createHeadsUpContentView(boolean increasedHeight) {
6687             if (useExistingRemoteView(mN.headsUpContentView)) {
6688                 return fullyCustomViewRequiresDecoration(false /* fromStyle */)
6689                         ? minimallyDecoratedHeadsUpContentView(mN.headsUpContentView)
6690                         : mN.headsUpContentView;
6691             } else if (mStyle != null) {
6692                 final RemoteViews styleView = mStyle.makeHeadsUpContentView(increasedHeight);
6693                 if (styleView != null) {
6694                     return fullyCustomViewRequiresDecoration(true /* fromStyle */)
6695                             ? minimallyDecoratedHeadsUpContentView(styleView) : styleView;
6696                 }
6697             } else if (mActions.size() == 0) {
6698                 return null;
6699             }
6700 
6701             // We only want at most a single remote input history to be shown here, otherwise
6702             // the content would become squished.
6703             StandardTemplateParams p = mParams.reset()
6704                     .viewType(StandardTemplateParams.VIEW_TYPE_HEADS_UP)
6705                     .fillTextsFrom(this)
6706                     .setMaxRemoteInputHistory(1);
6707             return applyStandardTemplateWithActions(getHeadsUpBaseLayoutResource(), p,
6708                     null /* result */);
6709         }
6710 
6711         /**
6712          * Construct a RemoteViews for the final compact heads-up notification layout.
6713          * @hide
6714          */
createCompactHeadsUpContentView()6715         public RemoteViews createCompactHeadsUpContentView() {
6716             // Don't show compact heads up for FSI notifications.
6717             if (mN.fullScreenIntent != null) {
6718                 return createHeadsUpContentView(/* increasedHeight= */ false);
6719             }
6720 
6721             if (mStyle != null) {
6722                 final RemoteViews styleView = mStyle.makeCompactHeadsUpContentView();
6723                 if (styleView != null) {
6724                     return styleView;
6725                 }
6726             }
6727 
6728             final StandardTemplateParams p = mParams.reset()
6729                     .viewType(StandardTemplateParams.VIEW_TYPE_HEADS_UP)
6730                     .fillTextsFrom(this);
6731             // Notification text is shown as secondary header text
6732             // for the minimal hun when it is provided.
6733             // Time(when and chronometer) is not shown for the minimal hun.
6734             p.headerTextSecondary(p.mText).text(null).hideTime(true).summaryText("");
6735 
6736             return applyStandardTemplate(
6737                     getCompactHeadsUpBaseLayoutResource(), p,
6738                     null /* result */);
6739         }
6740 
6741         /**
6742          * Construct a RemoteViews representing the heads up notification layout.
6743          *
6744          * @deprecated For performance and system health reasons, this API is no longer required to
6745          *  be used directly by the System UI when rendering Notifications to the user. While the UI
6746          *  returned by this method will still represent the content of the Notification being
6747          *  built, it may differ from the visual style of the system.
6748          *
6749          *  NOTE: this API has always had severe limitations; for example it does not support any
6750          *  interactivity, it ignores the app theme, it hard-codes the colors from the system theme
6751          *  at the time it is called, and it does Bitmap decoding on the main thread which can cause
6752          *  UI jank.
6753          */
6754         @Deprecated
createHeadsUpContentView()6755         public RemoteViews createHeadsUpContentView() {
6756             return createHeadsUpContentView(false /* useIncreasedHeight */);
6757         }
6758 
6759         /**
6760          * Construct a RemoteViews for the display in public contexts like on the lockscreen.
6761          *
6762          * @param isLowPriority is this notification low priority
6763          * @hide
6764          */
6765         @UnsupportedAppUsage
makePublicContentView(boolean isLowPriority)6766         public RemoteViews makePublicContentView(boolean isLowPriority) {
6767             if (mN.publicVersion != null) {
6768                 final Builder builder = recoverBuilder(mContext, mN.publicVersion);
6769                 return builder.createContentView();
6770             }
6771             Bundle savedBundle = mN.extras;
6772             Style style = mStyle;
6773             mStyle = null;
6774             Icon largeIcon = mN.mLargeIcon;
6775             mN.mLargeIcon = null;
6776             Bitmap largeIconLegacy = mN.largeIcon;
6777             mN.largeIcon = null;
6778             ArrayList<Action> actions = mActions;
6779             mActions = new ArrayList<>();
6780             Bundle publicExtras = new Bundle();
6781             publicExtras.putBoolean(EXTRA_SHOW_WHEN,
6782                     savedBundle.getBoolean(EXTRA_SHOW_WHEN));
6783             publicExtras.putBoolean(EXTRA_SHOW_CHRONOMETER,
6784                     savedBundle.getBoolean(EXTRA_SHOW_CHRONOMETER));
6785             publicExtras.putBoolean(EXTRA_CHRONOMETER_COUNT_DOWN,
6786                     savedBundle.getBoolean(EXTRA_CHRONOMETER_COUNT_DOWN));
6787             String appName = savedBundle.getString(EXTRA_SUBSTITUTE_APP_NAME);
6788             if (appName != null) {
6789                 publicExtras.putString(EXTRA_SUBSTITUTE_APP_NAME, appName);
6790             }
6791             mN.extras = publicExtras;
6792             RemoteViews view;
6793             StandardTemplateParams params = mParams.reset()
6794                     .viewType(StandardTemplateParams.VIEW_TYPE_PUBLIC)
6795                     .fillTextsFrom(this);
6796             if (isLowPriority) {
6797                 params.highlightExpander(false);
6798             }
6799             view = makeNotificationHeader(params);
6800             view.setBoolean(R.id.notification_header, "setExpandOnlyOnButton", true);
6801             mN.extras = savedBundle;
6802             mN.mLargeIcon = largeIcon;
6803             mN.largeIcon = largeIconLegacy;
6804             mActions = actions;
6805             mStyle = style;
6806             return view;
6807         }
6808 
6809         /**
6810          * Construct a content view for the display when low - priority
6811          *
6812          * @param useRegularSubtext uses the normal subtext set if there is one available. Otherwise
6813          *                          a new subtext is created consisting of the content of the
6814          *                          notification.
6815          * @hide
6816          */
makeLowPriorityContentView(boolean useRegularSubtext)6817         public RemoteViews makeLowPriorityContentView(boolean useRegularSubtext) {
6818             StandardTemplateParams p = mParams.reset()
6819                     .viewType(StandardTemplateParams.VIEW_TYPE_MINIMIZED)
6820                     .highlightExpander(false)
6821                     .fillTextsFrom(this);
6822             if (!useRegularSubtext || TextUtils.isEmpty(p.mSubText)) {
6823                 p.summaryText(createSummaryText());
6824             }
6825             RemoteViews header = makeNotificationHeader(p);
6826             header.setBoolean(R.id.notification_header, "setAcceptAllTouches", true);
6827             // The low priority header has no app name and shows the text
6828             header.setBoolean(R.id.notification_header, "styleTextAsTitle", true);
6829             return header;
6830         }
6831 
createSummaryText()6832         private CharSequence createSummaryText() {
6833             CharSequence titleText = mN.extras.getCharSequence(Notification.EXTRA_TITLE);
6834             if (USE_ONLY_TITLE_IN_LOW_PRIORITY_SUMMARY) {
6835                 return titleText;
6836             }
6837             SpannableStringBuilder summary = new SpannableStringBuilder();
6838             if (titleText == null) {
6839                 titleText = mN.extras.getCharSequence(Notification.EXTRA_TITLE_BIG);
6840             }
6841             BidiFormatter bidi = BidiFormatter.getInstance();
6842             if (titleText != null) {
6843                 summary.append(bidi.unicodeWrap(titleText));
6844             }
6845             CharSequence contentText = mN.extras.getCharSequence(Notification.EXTRA_TEXT);
6846             if (titleText != null && contentText != null) {
6847                 summary.append(bidi.unicodeWrap(mContext.getText(
6848                         R.string.notification_header_divider_symbol_with_spaces)));
6849             }
6850             if (contentText != null) {
6851                 summary.append(bidi.unicodeWrap(contentText));
6852             }
6853             return summary;
6854         }
6855 
generateActionButton(Action action, boolean emphasizedMode, StandardTemplateParams p)6856         private RemoteViews generateActionButton(Action action, boolean emphasizedMode,
6857                 StandardTemplateParams p) {
6858             final boolean tombstone = (action.actionIntent == null);
6859             final RemoteViews button = new BuilderRemoteViews(mContext.getApplicationInfo(),
6860                     getActionButtonLayoutResource(emphasizedMode, tombstone));
6861             if (!tombstone) {
6862                 button.setOnClickPendingIntent(R.id.action0, action.actionIntent);
6863             }
6864             button.setContentDescription(R.id.action0, action.title);
6865             if (action.mRemoteInputs != null) {
6866                 button.setRemoteInputs(R.id.action0, action.mRemoteInputs);
6867             }
6868             if (emphasizedMode) {
6869                 // change the background bgColor
6870                 CharSequence title = action.title;
6871                 int buttonFillColor = getColors(p).getSecondaryAccentColor();
6872                 if (tombstone) {
6873                     buttonFillColor = setAlphaComponentByFloatDimen(mContext,
6874                             ContrastColorUtil.resolveSecondaryColor(
6875                                     mContext, getColors(p).getBackgroundColor(), mInNightMode),
6876                             R.dimen.notification_action_disabled_container_alpha);
6877                 }
6878                 if (Flags.cleanUpSpansAndNewLines()) {
6879                     if (!isLegacy()) {
6880                         // Check for a full-length span color to use as the button fill color.
6881                         Integer fullLengthColor = getFullLengthSpanColor(title);
6882                         if (fullLengthColor != null) {
6883                             // Ensure the custom button fill has 1.3:1 contrast w/ notification bg.
6884                             int notifBackgroundColor = getColors(p).getBackgroundColor();
6885                             buttonFillColor = ensureButtonFillContrast(
6886                                     fullLengthColor, notifBackgroundColor);
6887                         }
6888                     }
6889                 } else {
6890                     if (isLegacy()) {
6891                         title = ContrastColorUtil.clearColorSpans(title);
6892                     } else {
6893                         // Check for a full-length span color to use as the button fill color.
6894                         Integer fullLengthColor = getFullLengthSpanColor(title);
6895                         if (fullLengthColor != null) {
6896                             // Ensure the custom button fill has 1.3:1 contrast w/ notification bg.
6897                             int notifBackgroundColor = getColors(p).getBackgroundColor();
6898                             buttonFillColor = ensureButtonFillContrast(
6899                                     fullLengthColor, notifBackgroundColor);
6900                         }
6901                         // Remove full-length color spans
6902                         // and ensure text contrast with the button fill.
6903                         title = ContrastColorUtil.ensureColorSpanContrast(title, buttonFillColor);
6904                     }
6905                 }
6906 
6907 
6908                 final CharSequence label = ensureColorSpanContrastOrStripStyling(title, p);
6909                 if (p.mCallStyleActions && evenlyDividedCallStyleActionLayout()) {
6910                     if (CallStyle.DEBUG_NEW_ACTION_LAYOUT) {
6911                         Log.d(TAG, "new action layout enabled, gluing instead of setting text");
6912                     }
6913                     button.setCharSequence(R.id.action0, "glueLabel", label);
6914                 } else {
6915                     button.setTextViewText(R.id.action0, label);
6916                 }
6917                 int textColor = ContrastColorUtil.resolvePrimaryColor(mContext,
6918                         buttonFillColor, mInNightMode);
6919                 if (tombstone) {
6920                     textColor = setAlphaComponentByFloatDimen(mContext,
6921                             ContrastColorUtil.resolveSecondaryColor(
6922                                     mContext, getColors(p).getBackgroundColor(), mInNightMode),
6923                             R.dimen.notification_action_disabled_content_alpha);
6924                 }
6925                 button.setTextColor(R.id.action0, textColor);
6926                 // We only want about 20% alpha for the ripple
6927                 final int rippleColor = (textColor & 0x00ffffff) | 0x33000000;
6928                 button.setColorStateList(R.id.action0, "setRippleColor",
6929                         ColorStateList.valueOf(rippleColor));
6930                 button.setColorStateList(R.id.action0, "setButtonBackground",
6931                         ColorStateList.valueOf(buttonFillColor));
6932                 if (p.mCallStyleActions) {
6933                     if (evenlyDividedCallStyleActionLayout()) {
6934                         if (CallStyle.DEBUG_NEW_ACTION_LAYOUT) {
6935                             Log.d(TAG, "new action layout enabled, gluing instead of setting icon");
6936                         }
6937                         button.setIcon(R.id.action0, "glueIcon", action.getIcon());
6938                     } else {
6939                         button.setImageViewIcon(R.id.action0, action.getIcon());
6940                     }
6941                     boolean priority = action.getExtras().getBoolean(CallStyle.KEY_ACTION_PRIORITY);
6942                     button.setBoolean(R.id.action0, "setIsPriority", priority);
6943                     int minWidthDimen =
6944                             priority ? R.dimen.call_notification_system_action_min_width : 0;
6945                     button.setIntDimen(R.id.action0, "setMinimumWidth", minWidthDimen);
6946                 }
6947             } else {
6948                 button.setTextViewText(R.id.action0, ensureColorSpanContrastOrStripStyling(
6949                         action.title, p));
6950                 button.setTextColor(R.id.action0, getStandardActionColor(p));
6951             }
6952             // CallStyle notifications add action buttons which don't actually exist in mActions,
6953             //  so we have to omit the index in that case.
6954             int actionIndex = mActions.indexOf(action);
6955             if (actionIndex != -1) {
6956                 button.setIntTag(R.id.action0, R.id.notification_action_index_tag, actionIndex);
6957             }
6958             return button;
6959         }
6960 
getActionButtonLayoutResource(boolean emphasizedMode, boolean tombstone)6961         private int getActionButtonLayoutResource(boolean emphasizedMode, boolean tombstone) {
6962             if (emphasizedMode) {
6963                 return tombstone ? getEmphasizedTombstoneActionLayoutResource()
6964                         : getEmphasizedActionLayoutResource();
6965             } else {
6966                 return tombstone ? getActionTombstoneLayoutResource()
6967                         : getActionLayoutResource();
6968             }
6969         }
6970 
6971         /**
6972          * Set the alpha component of {@code color} to be {@code alphaDimenResId}.
6973          */
setAlphaComponentByFloatDimen(Context context, @ColorInt int color, @DimenRes int alphaDimenResId)6974         private static int setAlphaComponentByFloatDimen(Context context, @ColorInt int color,
6975                 @DimenRes int alphaDimenResId) {
6976             final TypedValue alphaValue = new TypedValue();
6977             context.getResources().getValue(alphaDimenResId, alphaValue, true);
6978             return ColorUtils.setAlphaComponent(color, Math.round(alphaValue.getFloat() * 255));
6979         }
6980 
6981         /**
6982          * Extract the color from a full-length span from the text.
6983          *
6984          * @param charSequence the charSequence containing spans
6985          * @return the raw color of the text's last full-length span containing a color, or null if
6986          * no full-length span sets the text color.
6987          * @hide
6988          */
6989         @VisibleForTesting
6990         @Nullable
getFullLengthSpanColor(CharSequence charSequence)6991         public static Integer getFullLengthSpanColor(CharSequence charSequence) {
6992             // NOTE: this method preserves the functionality that for a CharSequence with multiple
6993             // full-length spans, the color of the last one is used.
6994             Integer result = null;
6995             if (charSequence instanceof Spanned) {
6996                 Spanned ss = (Spanned) charSequence;
6997                 Object[] spans = ss.getSpans(0, ss.length(), Object.class);
6998                 // First read through all full-length spans to get the button fill color, which will
6999                 //  be used as the background color for ensuring contrast of non-full-length spans.
7000                 for (Object span : spans) {
7001                     int spanStart = ss.getSpanStart(span);
7002                     int spanEnd = ss.getSpanEnd(span);
7003                     boolean fullLength = (spanEnd - spanStart) == charSequence.length();
7004                     if (!fullLength) {
7005                         continue;
7006                     }
7007                     if (span instanceof TextAppearanceSpan) {
7008                         TextAppearanceSpan originalSpan = (TextAppearanceSpan) span;
7009                         ColorStateList textColor = originalSpan.getTextColor();
7010                         if (textColor != null) {
7011                             result = textColor.getDefaultColor();
7012                         }
7013                     } else if (span instanceof ForegroundColorSpan) {
7014                         ForegroundColorSpan originalSpan = (ForegroundColorSpan) span;
7015                         result = originalSpan.getForegroundColor();
7016                     }
7017                 }
7018             }
7019             return result;
7020         }
7021 
7022         /**
7023          * @hide
7024          */
ensureColorSpanContrastOrStripStyling(CharSequence cs, StandardTemplateParams p)7025         public CharSequence ensureColorSpanContrastOrStripStyling(CharSequence cs,
7026                 StandardTemplateParams p) {
7027             return ensureColorSpanContrastOrStripStyling(cs, getBackgroundColor(p));
7028         }
7029 
7030         /**
7031          * @hide
7032          */
ensureColorSpanContrastOrStripStyling(CharSequence cs, int buttonFillColor)7033         public CharSequence ensureColorSpanContrastOrStripStyling(CharSequence cs,
7034                 int buttonFillColor) {
7035             if (Flags.cleanUpSpansAndNewLines()) {
7036                 return stripStyling(cs);
7037             }
7038 
7039             return ContrastColorUtil.ensureColorSpanContrast(cs, buttonFillColor);
7040         }
7041 
7042         /**
7043          * Ensures contrast on color spans against a background color.
7044          * Note that any full-length color spans will be removed instead of being contrasted.
7045          *
7046          * @hide
7047          */
7048         @VisibleForTesting
ensureColorSpanContrast(CharSequence charSequence, StandardTemplateParams p)7049         public CharSequence ensureColorSpanContrast(CharSequence charSequence,
7050                 StandardTemplateParams p) {
7051             return ContrastColorUtil.ensureColorSpanContrast(charSequence, getBackgroundColor(p));
7052         }
7053 
7054         /**
7055          * Determines if the color is light or dark.  Specifically, this is using the same metric as
7056          * {@link ContrastColorUtil#resolvePrimaryColor(Context, int, boolean)} and peers so that
7057          * the direction of color shift is consistent.
7058          *
7059          * @param color the color to check
7060          * @return true if the color has higher contrast with white than black
7061          * @hide
7062          */
isColorDark(int color)7063         public static boolean isColorDark(int color) {
7064             // as per ContrastColorUtil.shouldUseDark, this uses the color contrast midpoint.
7065             return ContrastColorUtil.calculateLuminance(color) <= 0.17912878474;
7066         }
7067 
7068         /**
7069          * Finds a button fill color with sufficient contrast over bg (1.3:1) that has the same hue
7070          * as the original color, but is lightened or darkened depending on whether the background
7071          * is dark or light.
7072          *
7073          * @hide
7074          */
7075         @VisibleForTesting
ensureButtonFillContrast(int color, int bg)7076         public static int ensureButtonFillContrast(int color, int bg) {
7077             return isColorDark(bg)
7078                     ? ContrastColorUtil.findContrastColorAgainstDark(color, bg, true, 1.3)
7079                     : ContrastColorUtil.findContrastColor(color, bg, true, 1.3);
7080         }
7081 
7082 
7083         /**
7084          * @return Whether we are currently building a notification from a legacy (an app that
7085          *         doesn't create material notifications by itself) app.
7086          */
isLegacy()7087         private boolean isLegacy() {
7088             if (!mIsLegacyInitialized) {
7089                 mIsLegacy = mContext.getApplicationInfo().targetSdkVersion
7090                         < Build.VERSION_CODES.LOLLIPOP;
7091                 mIsLegacyInitialized = true;
7092             }
7093             return mIsLegacy;
7094         }
7095 
7096         private CharSequence processLegacyText(CharSequence charSequence) {
7097             boolean isAlreadyLightText = isLegacy() || textColorsNeedInversion();
7098             if (isAlreadyLightText) {
7099                 return getColorUtil().invertCharSequenceColors(charSequence);
7100             } else {
7101                 return charSequence;
7102             }
7103         }
7104 
7105         /**
7106          * Apply any necessary colors to the small icon
7107          */
7108         private void processSmallIconColor(Icon smallIcon, RemoteViews contentView,
7109                 StandardTemplateParams p) {
7110             boolean colorable = !isLegacy() || getColorUtil().isGrayscaleIcon(mContext,
7111                     smallIcon);
7112             int color = getSmallIconColor(p);
7113             contentView.setInt(R.id.icon, "setBackgroundColor",
7114                     getBackgroundColor(p));
7115             contentView.setInt(R.id.icon, "setOriginalIconColor",
7116                     colorable ? color : COLOR_INVALID);
7117         }
7118 
7119         /**
7120          * Make the largeIcon dark if it's a fake smallIcon (that is,
7121          * if it's grayscale).
7122          */
7123         // TODO: also check bounds, transparency, that sort of thing.
7124         private void processLargeLegacyIcon(Icon largeIcon, RemoteViews contentView,
7125                 StandardTemplateParams p) {
7126             if (largeIcon != null && isLegacy()
7127                     && getColorUtil().isGrayscaleIcon(mContext, largeIcon)) {
7128                 // resolve color will fall back to the default when legacy
7129                 int color = getSmallIconColor(p);
7130                 contentView.setInt(R.id.icon, "setOriginalIconColor", color);
7131             }
7132         }
7133 
7134         private void sanitizeColor() {
7135             if (mN.color != COLOR_DEFAULT) {
7136                 mN.color |= 0xFF000000; // no alpha for custom colors
7137             }
7138         }
7139 
7140         /**
7141          * Gets the standard action button color
7142          */
7143         private @ColorInt int getStandardActionColor(Notification.StandardTemplateParams p) {
7144             return mTintActionButtons || isBackgroundColorized(p)
7145                     ? getPrimaryAccentColor(p) : getSecondaryTextColor(p);
7146         }
7147 
7148         /**
7149          * Gets the foreground color of the small icon.  If the notification is colorized, this
7150          * is the primary text color, otherwise it's the contrast-adjusted app-provided color.
7151          */
7152         private @ColorInt int getSmallIconColor(StandardTemplateParams p) {
7153             return getColors(p).getContrastColor();
7154         }
7155 
7156         /**
7157          * Gets the foreground color of the small icon.  If the notification is colorized, this
7158          * is the primary text color, otherwise it's the contrast-adjusted app-provided color.
7159          * @hide
7160          */
7161         public @ColorInt int getSmallIconColor(boolean isHeader) {
7162             return getColors(/* isHeader = */ isHeader).getContrastColor();
7163         }
7164 
7165         /**
7166          * Gets the background color of the notification.
7167          * @hide
7168          */
7169         public @ColorInt int getBackgroundColor(boolean isHeader) {
7170             return getColors(/* isHeader = */ isHeader).getBackgroundColor();
7171         }
7172 
7173         /** @return the theme's accent color for colored UI elements. */
7174         private @ColorInt int getPrimaryAccentColor(StandardTemplateParams p) {
7175             return getColors(p).getPrimaryAccentColor();
7176         }
7177 
7178         /**
7179          * Apply the unstyled operations and return a new {@link Notification} object.
7180          * @hide
7181          */
7182         @NonNull
7183         public Notification buildUnstyled() {
7184             if (mActions.size() > 0) {
7185                 mN.actions = new Action[mActions.size()];
7186                 mActions.toArray(mN.actions);
7187             }
7188             if (!mPersonList.isEmpty()) {
7189                 mN.extras.putParcelableArrayList(EXTRA_PEOPLE_LIST, mPersonList);
7190             }
7191             if (mN.bigContentView != null || mN.contentView != null
7192                     || mN.headsUpContentView != null) {
7193                 mN.extras.putBoolean(EXTRA_CONTAINS_CUSTOM_VIEW, true);
7194             }
7195             return mN;
7196         }
7197 
7198         /**
7199          * Creates a Builder from an existing notification so further changes can be made.
7200          * @param context The context for your application / activity.
7201          * @param n The notification to create a Builder from.
7202          */
7203         @NonNull
recoverBuilder(Context context, Notification n)7204         public static Notification.Builder recoverBuilder(Context context, Notification n) {
7205             Trace.beginSection("Notification.Builder#recoverBuilder");
7206 
7207             try {
7208                 // Re-create notification context so we can access app resources.
7209                 ApplicationInfo applicationInfo = n.extras.getParcelable(
7210                         EXTRA_BUILDER_APPLICATION_INFO, ApplicationInfo.class);
7211                 Context builderContext;
7212                 if (applicationInfo != null) {
7213                     try {
7214                         builderContext = context.createApplicationContext(applicationInfo,
7215                                 Context.CONTEXT_RESTRICTED);
7216                     } catch (NameNotFoundException e) {
7217                         Log.e(TAG, "ApplicationInfo " + applicationInfo + " not found");
7218                         builderContext = context;  // try with our context
7219                     }
7220                 } else {
7221                     builderContext = context; // try with given context
7222                 }
7223 
7224                 return new Builder(builderContext, n);
7225             } finally {
7226                 Trace.endSection();
7227             }
7228         }
7229 
7230         /**
7231          * Determines whether the platform can generate contextual actions for a notification.
7232          * By default this is true.
7233          */
7234         @NonNull
setAllowSystemGeneratedContextualActions(boolean allowed)7235         public Builder setAllowSystemGeneratedContextualActions(boolean allowed) {
7236             mN.mAllowSystemGeneratedContextualActions = allowed;
7237             return this;
7238         }
7239 
7240         /**
7241          * @deprecated Use {@link #build()} instead.
7242          */
7243         @Deprecated
getNotification()7244         public Notification getNotification() {
7245             return build();
7246         }
7247 
7248         /**
7249          * Combine all of the options that have been set and return a new {@link Notification}
7250          * object.
7251          *
7252          * If this notification has {@link BubbleMetadata} attached that was created with
7253          * a shortcutId a check will be performed to ensure the shortcutId supplied to bubble
7254          * metadata matches the shortcutId set on the  notification builder, if one was set.
7255          * If the shortcutId's were specified but do not match, an exception is thrown here.
7256          *
7257          * @see BubbleMetadata.Builder#Builder(String)
7258          * @see #setShortcutId(String)
7259          */
7260         @NonNull
build()7261         public Notification build() {
7262             // Check shortcut id matches
7263             if (mN.mShortcutId != null
7264                     && mN.mBubbleMetadata != null
7265                     && mN.mBubbleMetadata.getShortcutId() != null
7266                     && !mN.mShortcutId.equals(mN.mBubbleMetadata.getShortcutId())) {
7267                 throw new IllegalArgumentException(
7268                         "Notification and BubbleMetadata shortcut id's don't match,"
7269                                 + " notification: " + mN.mShortcutId
7270                                 + " vs bubble: " + mN.mBubbleMetadata.getShortcutId());
7271             }
7272 
7273             // Adds any new extras provided by the user.
7274             if (mUserExtras != null) {
7275                 final Bundle saveExtras = (Bundle) mUserExtras.clone();
7276                 mN.extras.putAll(saveExtras);
7277             }
7278 
7279             if (!Flags.sortSectionByTime()) {
7280                 mN.creationTime = System.currentTimeMillis();
7281             }
7282 
7283             // lazy stuff from mContext; see comment in Builder(Context, Notification)
7284             Notification.addFieldsFromContext(mContext, mN);
7285 
7286             buildUnstyled();
7287 
7288             if (mStyle != null) {
7289                 mStyle.reduceImageSizes(mContext);
7290                 mStyle.purgeResources();
7291                 mStyle.validate(mContext);
7292                 mStyle.buildStyled(mN);
7293             }
7294             mN.reduceImageSizes(mContext);
7295 
7296             if (mContext.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N
7297                     && !styleDisplaysCustomViewInline()) {
7298                 RemoteViews newContentView = mN.contentView;
7299                 RemoteViews newBigContentView = mN.bigContentView;
7300                 RemoteViews newHeadsUpContentView = mN.headsUpContentView;
7301                 if (newContentView == null) {
7302                     newContentView = createContentView();
7303                     mN.extras.putInt(EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT,
7304                             newContentView.getSequenceNumber());
7305                 }
7306                 if (newBigContentView == null) {
7307                     newBigContentView = createBigContentView();
7308                     if (newBigContentView != null) {
7309                         mN.extras.putInt(EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT,
7310                                 newBigContentView.getSequenceNumber());
7311                     }
7312                 }
7313                 if (newHeadsUpContentView == null) {
7314                     newHeadsUpContentView = createHeadsUpContentView();
7315                     if (newHeadsUpContentView != null) {
7316                         mN.extras.putInt(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT,
7317                                 newHeadsUpContentView.getSequenceNumber());
7318                     }
7319                 }
7320                 // Don't set any of the content views until after they have all been generated,
7321                 //  to avoid the generated .contentView triggering the logic which skips generating
7322                 //  the .bigContentView.
7323                 mN.contentView = newContentView;
7324                 mN.bigContentView = newBigContentView;
7325                 mN.headsUpContentView = newHeadsUpContentView;
7326             }
7327 
7328             if ((mN.defaults & DEFAULT_LIGHTS) != 0) {
7329                 mN.flags |= FLAG_SHOW_LIGHTS;
7330             }
7331 
7332             mN.allPendingIntents = null;
7333 
7334             return mN;
7335         }
7336 
styleDisplaysCustomViewInline()7337         private boolean styleDisplaysCustomViewInline() {
7338             return mStyle != null && mStyle.displayCustomViewInline();
7339         }
7340 
7341         /**
7342          * Apply this Builder to an existing {@link Notification} object.
7343          *
7344          * @hide
7345          */
7346         @NonNull
buildInto(@onNull Notification n)7347         public Notification buildInto(@NonNull Notification n) {
7348             build().cloneInto(n, true);
7349             return n;
7350         }
7351 
7352         /**
7353          * Removes RemoteViews that were created for compatibility from {@param n}, if they did not
7354          * change.
7355          *
7356          * @return {@param n}, if no stripping is needed, otherwise a stripped clone of {@param n}.
7357          *
7358          * @hide
7359          */
maybeCloneStrippedForDelivery(Notification n)7360         public static Notification maybeCloneStrippedForDelivery(Notification n) {
7361             String templateClass = n.extras.getString(EXTRA_TEMPLATE);
7362 
7363             // Only strip views for known Styles because we won't know how to
7364             // re-create them otherwise.
7365             if (!TextUtils.isEmpty(templateClass)
7366                     && getNotificationStyleClass(templateClass) == null) {
7367                 return n;
7368             }
7369 
7370             // Only strip unmodified BuilderRemoteViews.
7371             boolean stripContentView = n.contentView instanceof BuilderRemoteViews &&
7372                     n.extras.getInt(EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT, -1) ==
7373                             n.contentView.getSequenceNumber();
7374             boolean stripBigContentView = n.bigContentView instanceof BuilderRemoteViews &&
7375                     n.extras.getInt(EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT, -1) ==
7376                             n.bigContentView.getSequenceNumber();
7377             boolean stripHeadsUpContentView = n.headsUpContentView instanceof BuilderRemoteViews &&
7378                     n.extras.getInt(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT, -1) ==
7379                             n.headsUpContentView.getSequenceNumber();
7380 
7381             // Nothing to do here, no need to clone.
7382             if (!stripContentView && !stripBigContentView && !stripHeadsUpContentView) {
7383                 return n;
7384             }
7385 
7386             Notification clone = n.clone();
7387             if (stripContentView) {
7388                 clone.contentView = null;
7389                 clone.extras.remove(EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT);
7390             }
7391             if (stripBigContentView) {
7392                 clone.bigContentView = null;
7393                 clone.extras.remove(EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT);
7394             }
7395             if (stripHeadsUpContentView) {
7396                 clone.headsUpContentView = null;
7397                 clone.extras.remove(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT);
7398             }
7399             return clone;
7400         }
7401 
7402         @UnsupportedAppUsage
getBaseLayoutResource()7403         private int getBaseLayoutResource() {
7404             return R.layout.notification_template_material_base;
7405         }
7406 
getHeadsUpBaseLayoutResource()7407         private int getHeadsUpBaseLayoutResource() {
7408             return R.layout.notification_template_material_heads_up_base;
7409         }
7410 
getCompactHeadsUpBaseLayoutResource()7411         private int getCompactHeadsUpBaseLayoutResource() {
7412             return R.layout.notification_template_material_compact_heads_up_base;
7413         }
7414 
getMessagingCompactHeadsUpLayoutResource()7415         private int getMessagingCompactHeadsUpLayoutResource() {
7416             return R.layout.notification_template_material_messaging_compact_heads_up;
7417         }
7418 
getBigBaseLayoutResource()7419         private int getBigBaseLayoutResource() {
7420             return R.layout.notification_template_material_big_base;
7421         }
7422 
getBigPictureLayoutResource()7423         private int getBigPictureLayoutResource() {
7424             return R.layout.notification_template_material_big_picture;
7425         }
7426 
getBigTextLayoutResource()7427         private int getBigTextLayoutResource() {
7428             return R.layout.notification_template_material_big_text;
7429         }
7430 
getInboxLayoutResource()7431         private int getInboxLayoutResource() {
7432             return R.layout.notification_template_material_inbox;
7433         }
7434 
getMessagingLayoutResource()7435         private int getMessagingLayoutResource() {
7436             return R.layout.notification_template_material_messaging;
7437         }
7438 
getBigMessagingLayoutResource()7439         private int getBigMessagingLayoutResource() {
7440             return R.layout.notification_template_material_big_messaging;
7441         }
7442 
getConversationLayoutResource()7443         private int getConversationLayoutResource() {
7444             return R.layout.notification_template_material_conversation;
7445         }
7446 
getActionLayoutResource()7447         private int getActionLayoutResource() {
7448             return R.layout.notification_material_action;
7449         }
7450 
getEmphasizedActionLayoutResource()7451         private int getEmphasizedActionLayoutResource() {
7452             return R.layout.notification_material_action_emphasized;
7453         }
7454 
getEmphasizedTombstoneActionLayoutResource()7455         private int getEmphasizedTombstoneActionLayoutResource() {
7456             return R.layout.notification_material_action_emphasized_tombstone;
7457         }
7458 
getActionTombstoneLayoutResource()7459         private int getActionTombstoneLayoutResource() {
7460             return R.layout.notification_material_action_tombstone;
7461         }
7462 
getBackgroundColor(StandardTemplateParams p)7463         private @ColorInt int getBackgroundColor(StandardTemplateParams p) {
7464             return getColors(p).getBackgroundColor();
7465         }
7466 
textColorsNeedInversion()7467         private boolean textColorsNeedInversion() {
7468             if (mStyle == null || !MediaStyle.class.equals(mStyle.getClass())) {
7469                 return false;
7470             }
7471             int targetSdkVersion = mContext.getApplicationInfo().targetSdkVersion;
7472             return targetSdkVersion > Build.VERSION_CODES.M
7473                     && targetSdkVersion < Build.VERSION_CODES.O;
7474         }
7475 
7476         /**
7477          * Get the text that should be displayed in the statusBar when heads upped. This is
7478          * usually just the app name, but may be different depending on the style.
7479          *
7480          * @param publicMode If true, return a text that is safe to display in public.
7481          *
7482          * @hide
7483          */
getHeadsUpStatusBarText(boolean publicMode)7484         public CharSequence getHeadsUpStatusBarText(boolean publicMode) {
7485             if (mStyle != null && !publicMode) {
7486                 CharSequence text = mStyle.getHeadsUpStatusBarText();
7487                 if (!TextUtils.isEmpty(text)) {
7488                     return text;
7489                 }
7490             }
7491             return loadHeaderAppName();
7492         }
7493 
7494         /**
7495          * @return if this builder uses a template
7496          *
7497          * @hide
7498          */
usesTemplate()7499         public boolean usesTemplate() {
7500             return (mN.contentView == null && mN.headsUpContentView == null
7501                     && mN.bigContentView == null)
7502                     || styleDisplaysCustomViewInline();
7503         }
7504     }
7505 
7506     /**
7507      * Reduces the image sizes to conform to a maximum allowed size. This also processes all custom
7508      * remote views.
7509      *
7510      * @hide
7511      */
reduceImageSizes(Context context)7512     void reduceImageSizes(Context context) {
7513         if (extras.getBoolean(EXTRA_REDUCED_IMAGES)) {
7514             return;
7515         }
7516         boolean isLowRam = ActivityManager.isLowRamDeviceStatic();
7517 
7518         if (mSmallIcon != null
7519                 // Only bitmap icons can be downscaled.
7520                 && (mSmallIcon.getType() == Icon.TYPE_BITMAP
7521                         || mSmallIcon.getType() == Icon.TYPE_ADAPTIVE_BITMAP)) {
7522             Resources resources = context.getResources();
7523             int maxSize = resources.getDimensionPixelSize(
7524                     isLowRam ? R.dimen.notification_small_icon_size_low_ram
7525                             : R.dimen.notification_small_icon_size);
7526             mSmallIcon.scaleDownIfNecessary(maxSize, maxSize);
7527         }
7528 
7529         if (mLargeIcon != null || largeIcon != null) {
7530             Resources resources = context.getResources();
7531             Class<? extends Style> style = getNotificationStyle();
7532             int maxSize = resources.getDimensionPixelSize(isLowRam
7533                     ? R.dimen.notification_right_icon_size_low_ram
7534                     : R.dimen.notification_right_icon_size);
7535             if (mLargeIcon != null) {
7536                 mLargeIcon.scaleDownIfNecessary(maxSize, maxSize);
7537             }
7538             if (largeIcon != null) {
7539                 largeIcon = Icon.scaleDownIfNecessary(largeIcon, maxSize, maxSize);
7540             }
7541         }
7542         reduceImageSizesForRemoteView(contentView, context, isLowRam);
7543         reduceImageSizesForRemoteView(headsUpContentView, context, isLowRam);
7544         reduceImageSizesForRemoteView(bigContentView, context, isLowRam);
7545         extras.putBoolean(EXTRA_REDUCED_IMAGES, true);
7546     }
7547 
reduceImageSizesForRemoteView(RemoteViews remoteView, Context context, boolean isLowRam)7548     private void reduceImageSizesForRemoteView(RemoteViews remoteView, Context context,
7549             boolean isLowRam) {
7550         if (remoteView != null) {
7551             Resources resources = context.getResources();
7552             int maxWidth = resources.getDimensionPixelSize(isLowRam
7553                     ? R.dimen.notification_custom_view_max_image_width_low_ram
7554                     : R.dimen.notification_custom_view_max_image_width);
7555             int maxHeight = resources.getDimensionPixelSize(isLowRam
7556                     ? R.dimen.notification_custom_view_max_image_height_low_ram
7557                     : R.dimen.notification_custom_view_max_image_height);
7558             remoteView.reduceImageSizes(maxWidth, maxHeight);
7559         }
7560     }
7561 
7562     /**
7563      * @return whether this notification is a foreground service notification
7564      * @hide
7565      */
isForegroundService()7566     public boolean isForegroundService() {
7567         return (flags & Notification.FLAG_FOREGROUND_SERVICE) != 0;
7568     }
7569 
7570     /**
7571      * @return whether this notification is associated with a user initiated job
7572      * @hide
7573      */
7574     @TestApi
isUserInitiatedJob()7575     public boolean isUserInitiatedJob() {
7576         return (flags & Notification.FLAG_USER_INITIATED_JOB) != 0;
7577     }
7578 
7579     /**
7580      * @return whether this notification is associated with either a foreground service or
7581      * a user initiated job
7582      * @hide
7583      */
isFgsOrUij()7584     public boolean isFgsOrUij() {
7585         return isForegroundService() || isUserInitiatedJob();
7586     }
7587 
7588     /**
7589      * Describe whether this notification's content such that it should always display
7590      * immediately when tied to a foreground service, even if the system might generally
7591      * avoid showing the notifications for short-lived foreground service lifetimes.
7592      *
7593      * Immediate visibility of the Notification is indicated when:
7594      * <ul>
7595      *     <li>The app specifically indicated it with
7596      *         {@link Notification.Builder#setForegroundServiceBehavior(int)
7597      *         setForegroundServiceBehavior(BEHAVIOR_IMMEDIATE_DISPLAY)}</li>
7598      *     <li>It is a media notification or has an associated media session</li>
7599      *     <li>It is a call or navigation notification</li>
7600      *     <li>It provides additional action affordances</li>
7601      * </ul>
7602      *
7603      * If the app has specified
7604      * {@code NotificationBuilder.setForegroundServiceBehavior(BEHAVIOR_DEFERRED_DISPLAY)}
7605      * then this method will return {@code false} and notification visibility will be
7606      * deferred following the service's transition to the foreground state even in the
7607      * circumstances described above.
7608      *
7609      * @return whether this notification should be displayed immediately when
7610      * its associated service transitions to the foreground state
7611      * @hide
7612      */
7613     @TestApi
shouldShowForegroundImmediately()7614     public boolean shouldShowForegroundImmediately() {
7615         // Has the app demanded immediate display?
7616         if (mFgsDeferBehavior == FOREGROUND_SERVICE_IMMEDIATE) {
7617             return true;
7618         }
7619 
7620         // Has the app demanded deferred display?
7621         if (mFgsDeferBehavior == FOREGROUND_SERVICE_DEFERRED) {
7622             return false;
7623         }
7624 
7625         // We show these sorts of notifications immediately in the absence of
7626         // any explicit app declaration
7627         if (isMediaNotification()
7628                     || CATEGORY_CALL.equals(category)
7629                     || CATEGORY_NAVIGATION.equals(category)
7630                     || (actions != null && actions.length > 0)) {
7631             return true;
7632         }
7633 
7634         // No extenuating circumstances: defer visibility
7635         return false;
7636     }
7637 
7638     /**
7639      * Has forced deferral for FGS purposes been specified?
7640      * @hide
7641      */
isForegroundDisplayForceDeferred()7642     public boolean isForegroundDisplayForceDeferred() {
7643         return FOREGROUND_SERVICE_DEFERRED == mFgsDeferBehavior;
7644     }
7645 
7646     /**
7647      * @return the style class of this notification
7648      * @hide
7649      */
getNotificationStyle()7650     public Class<? extends Notification.Style> getNotificationStyle() {
7651         String templateClass = extras.getString(Notification.EXTRA_TEMPLATE);
7652 
7653         if (!TextUtils.isEmpty(templateClass)) {
7654             return Notification.getNotificationStyleClass(templateClass);
7655         }
7656         return null;
7657     }
7658 
7659     /**
7660      * @return whether the style of this notification is the one provided
7661      * @hide
7662      */
isStyle(@onNull Class<? extends Style> styleClass)7663     public boolean isStyle(@NonNull Class<? extends Style> styleClass) {
7664         String templateClass = extras.getString(Notification.EXTRA_TEMPLATE);
7665         return Objects.equals(templateClass, styleClass.getName());
7666     }
7667 
7668     /**
7669      * @return true if this notification is colorized *for the purposes of ranking*.  If the
7670      * {@link #color} is {@link #COLOR_DEFAULT} this will be true, even though the actual
7671      * appearance of the notification may not be "colorized".
7672      *
7673      * @hide
7674      */
isColorized()7675     public boolean isColorized() {
7676         return extras.getBoolean(EXTRA_COLORIZED)
7677                 && (hasColorizedPermission() || isFgsOrUij());
7678     }
7679 
7680     /**
7681      * Returns whether an app can colorize due to the android.permission.USE_COLORIZED_NOTIFICATIONS
7682      * permission. The permission is checked when a notification is enqueued.
7683      *
7684      * @hide
7685      */
hasColorizedPermission()7686     public boolean hasColorizedPermission() {
7687         return (flags & Notification.FLAG_CAN_COLORIZE) != 0;
7688     }
7689 
7690     /**
7691      * @return true if this is a media style notification with a media session
7692      *
7693      * @hide
7694      */
isMediaNotification()7695     public boolean isMediaNotification() {
7696         Class<? extends Style> style = getNotificationStyle();
7697         boolean isMediaStyle = (MediaStyle.class.equals(style)
7698                 || DecoratedMediaCustomViewStyle.class.equals(style));
7699 
7700         boolean hasMediaSession = extras.getParcelable(Notification.EXTRA_MEDIA_SESSION,
7701                 MediaSession.Token.class) != null;
7702 
7703         return isMediaStyle && hasMediaSession;
7704     }
7705 
7706     /**
7707      * @return true for custom notifications, including notifications
7708      * with DecoratedCustomViewStyle or DecoratedMediaCustomViewStyle,
7709      * and other notifications with user-provided custom views.
7710      *
7711      * @hide
7712      */
isCustomNotification()7713     public Boolean isCustomNotification() {
7714         if (contentView == null
7715                 && bigContentView == null
7716                 && headsUpContentView == null) {
7717             return false;
7718         }
7719         return true;
7720     }
7721 
7722     /**
7723      * @return true if this notification is showing as a bubble
7724      *
7725      * @hide
7726      */
isBubbleNotification()7727     public boolean isBubbleNotification() {
7728         return (flags & Notification.FLAG_BUBBLE) != 0;
7729     }
7730 
hasLargeIcon()7731     private boolean hasLargeIcon() {
7732         return mLargeIcon != null || largeIcon != null;
7733     }
7734 
7735     /**
7736      * Returns #when, unless it's set to 0, which should be shown as/treated as a 'current'
7737      * notification. 0 is treated as a special value because it was special in an old version of
7738      * android, and some apps are still (incorrectly) using it.
7739      *
7740      * @hide
7741      */
getWhen()7742     public long getWhen() {
7743         if (Flags.sortSectionByTime()) {
7744             if (when == 0) {
7745                 return creationTime;
7746             }
7747         }
7748         return when;
7749     }
7750 
7751     /**
7752      * @return true if the notification will show the time; false otherwise
7753      * @hide
7754      */
showsTime()7755     public boolean showsTime() {
7756         if (Flags.sortSectionByTime()) {
7757             return extras.getBoolean(EXTRA_SHOW_WHEN);
7758         }
7759         return when != 0 && extras.getBoolean(EXTRA_SHOW_WHEN);
7760     }
7761 
7762     /**
7763      * @return true if the notification will show a chronometer; false otherwise
7764      * @hide
7765      */
showsChronometer()7766     public boolean showsChronometer() {
7767         if (Flags.sortSectionByTime()) {
7768             return extras.getBoolean(EXTRA_SHOW_CHRONOMETER);
7769         }
7770         return when != 0 && extras.getBoolean(EXTRA_SHOW_CHRONOMETER);
7771     }
7772 
7773     /**
7774      * @return true if the notification has image
7775      */
hasImage()7776     public boolean hasImage() {
7777         if (isStyle(MessagingStyle.class) && extras != null) {
7778             final Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES,
7779                     Parcelable.class);
7780             if (!ArrayUtils.isEmpty(messages)) {
7781                 for (MessagingStyle.Message m : MessagingStyle.Message
7782                         .getMessagesFromBundleArray(messages)) {
7783                     if (m.getDataUri() != null
7784                             && m.getDataMimeType() != null
7785                             && m.getDataMimeType().startsWith("image/")) {
7786                         return true;
7787                     }
7788                 }
7789             }
7790         } else if (hasLargeIcon()) {
7791             return true;
7792         } else if (extras.containsKey(EXTRA_BACKGROUND_IMAGE_URI)) {
7793             return true;
7794         }
7795         return false;
7796     }
7797 
7798 
7799     /**
7800      * @removed
7801      */
7802     @SystemApi
getNotificationStyleClass(String templateClass)7803     public static Class<? extends Style> getNotificationStyleClass(String templateClass) {
7804         for (Class<? extends Style> innerClass : PLATFORM_STYLE_CLASSES) {
7805             if (templateClass.equals(innerClass.getName())) {
7806                 return innerClass;
7807             }
7808         }
7809         return null;
7810     }
7811 
buildCustomContentIntoTemplate(@onNull Context context, @NonNull RemoteViews template, @Nullable RemoteViews customContent, @NonNull StandardTemplateParams p, @NonNull TemplateBindResult result)7812     private static void buildCustomContentIntoTemplate(@NonNull Context context,
7813             @NonNull RemoteViews template, @Nullable RemoteViews customContent,
7814             @NonNull StandardTemplateParams p, @NonNull TemplateBindResult result) {
7815         int childIndex = -1;
7816         if (customContent != null) {
7817             // Need to clone customContent before adding, because otherwise it can no longer be
7818             // parceled independently of remoteViews.
7819             customContent = customContent.clone();
7820             if (p.mHeaderless) {
7821                 template.removeFromParent(R.id.notification_top_line);
7822                 // We do not know how many lines ar emote view has, so we presume it has 2;  this
7823                 // ensures that we don't under-pad the content, which could lead to abuse, at the
7824                 // cost of making single-line custom content over-padded.
7825                 Builder.setHeaderlessVerticalMargins(template, p, true /* hasSecondLine */);
7826             } else {
7827                 // also update the end margin to account for the large icon or expander
7828                 Resources resources = context.getResources();
7829                 result.mTitleMarginSet.applyToView(template, R.id.notification_main_column,
7830                         resources.getDimension(R.dimen.notification_content_margin_end)
7831                                 / resources.getDisplayMetrics().density);
7832             }
7833             template.removeAllViewsExceptId(R.id.notification_main_column, R.id.progress);
7834             template.addView(R.id.notification_main_column, customContent, 0 /* index */);
7835             template.addFlags(RemoteViews.FLAG_REAPPLY_DISALLOWED);
7836             childIndex = 0;
7837         }
7838         template.setIntTag(R.id.notification_main_column,
7839                 com.android.internal.R.id.notification_custom_view_index_tag,
7840                 childIndex);
7841     }
7842 
7843     /**
7844      * An object that can apply a rich notification style to a {@link Notification.Builder}
7845      * object.
7846      */
7847     public static abstract class Style {
7848 
7849         /**
7850          * @deprecated public access to the constructor of Style() is only useful for creating
7851          * custom subclasses, but that has actually been impossible due to hidden abstract
7852          * methods, so this constructor is now officially deprecated to clarify that this is
7853          * intended to be disallowed.
7854          */
7855         @Deprecated
Style()7856         public Style() {}
7857 
7858         /**
7859          * The number of items allowed simulatanously in the remote input history.
7860          * @hide
7861          */
7862         static final int MAX_REMOTE_INPUT_HISTORY_LINES = 3;
7863         private CharSequence mBigContentTitle;
7864 
7865         /**
7866          * @hide
7867          */
7868         protected CharSequence mSummaryText = null;
7869 
7870         /**
7871          * @hide
7872          */
7873         protected boolean mSummaryTextSet = false;
7874 
7875         protected Builder mBuilder;
7876 
7877         /**
7878          * Overrides ContentTitle in the big form of the template.
7879          * This defaults to the value passed to setContentTitle().
7880          */
internalSetBigContentTitle(CharSequence title)7881         protected void internalSetBigContentTitle(CharSequence title) {
7882             mBigContentTitle = title;
7883         }
7884 
7885         /**
7886          * Set the first line of text after the detail section in the big form of the template.
7887          */
internalSetSummaryText(CharSequence cs)7888         protected void internalSetSummaryText(CharSequence cs) {
7889             mSummaryText = cs;
7890             mSummaryTextSet = true;
7891         }
7892 
setBuilder(Builder builder)7893         public void setBuilder(Builder builder) {
7894             if (mBuilder != builder) {
7895                 mBuilder = builder;
7896                 if (mBuilder != null) {
7897                     mBuilder.setStyle(this);
7898                 }
7899             }
7900         }
7901 
checkBuilder()7902         protected void checkBuilder() {
7903             if (mBuilder == null) {
7904                 throw new IllegalArgumentException("Style requires a valid Builder object");
7905             }
7906         }
7907 
getStandardView(int layoutId)7908         protected RemoteViews getStandardView(int layoutId) {
7909             // TODO(jeffdq): set the view type based on the layout resource?
7910             StandardTemplateParams p = mBuilder.mParams.reset()
7911                     .viewType(StandardTemplateParams.VIEW_TYPE_UNSPECIFIED)
7912                     .fillTextsFrom(mBuilder);
7913             return getStandardView(layoutId, p, null);
7914         }
7915 
7916 
7917         /**
7918          * Get the standard view for this style.
7919          *
7920          * @param layoutId The layout id to use.
7921          * @param p the params for this inflation.
7922          * @param result The result where template bind information is saved.
7923          * @return A remoteView for this style.
7924          * @hide
7925          */
getStandardView(int layoutId, StandardTemplateParams p, TemplateBindResult result)7926         protected RemoteViews getStandardView(int layoutId, StandardTemplateParams p,
7927                 TemplateBindResult result) {
7928             checkBuilder();
7929 
7930             if (mBigContentTitle != null) {
7931                 p.mTitle = mBigContentTitle;
7932             }
7933 
7934             return mBuilder.applyStandardTemplateWithActions(layoutId, p, result);
7935         }
7936 
7937         /**
7938          * Construct a Style-specific RemoteViews for the collapsed notification layout.
7939          * The default implementation has nothing additional to add.
7940          *
7941          * @param increasedHeight true if this layout be created with an increased height.
7942          * @hide
7943          */
makeContentView(boolean increasedHeight)7944         public RemoteViews makeContentView(boolean increasedHeight) {
7945             return null;
7946         }
7947 
7948         /**
7949          * Construct a Style-specific RemoteViews for the final big notification layout.
7950          * @hide
7951          */
makeBigContentView()7952         public RemoteViews makeBigContentView() {
7953             return null;
7954         }
7955 
7956         /**
7957          * Construct a Style-specific RemoteViews for the final HUN layout.
7958          *
7959          * @param increasedHeight true if this layout be created with an increased height.
7960          * @hide
7961          */
makeHeadsUpContentView(boolean increasedHeight)7962         public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
7963             return null;
7964         }
7965 
7966         /**
7967          * Construct a Style-specific RemoteViews for the final compact HUN layout.
7968          * return null to use the standard compact heads up view.
7969          * @hide
7970          */
7971         @Nullable
makeCompactHeadsUpContentView()7972         public RemoteViews makeCompactHeadsUpContentView() {
7973             return null;
7974         }
7975 
7976         /**
7977          * Apply any style-specific extras to this notification before shipping it out.
7978          * @hide
7979          */
addExtras(Bundle extras)7980         public void addExtras(Bundle extras) {
7981             if (mSummaryTextSet) {
7982                 extras.putCharSequence(EXTRA_SUMMARY_TEXT, mSummaryText);
7983             }
7984             if (mBigContentTitle != null) {
7985                 extras.putCharSequence(EXTRA_TITLE_BIG, mBigContentTitle);
7986             }
7987             extras.putString(EXTRA_TEMPLATE, this.getClass().getName());
7988         }
7989 
7990         /**
7991          * Reconstruct the internal state of this Style object from extras.
7992          * @hide
7993          */
restoreFromExtras(Bundle extras)7994         protected void restoreFromExtras(Bundle extras) {
7995             if (extras.containsKey(EXTRA_SUMMARY_TEXT)) {
7996                 mSummaryText = extras.getCharSequence(EXTRA_SUMMARY_TEXT);
7997                 mSummaryTextSet = true;
7998             }
7999             if (extras.containsKey(EXTRA_TITLE_BIG)) {
8000                 mBigContentTitle = extras.getCharSequence(EXTRA_TITLE_BIG);
8001             }
8002         }
8003 
8004 
8005         /**
8006          * @hide
8007          */
buildStyled(Notification wip)8008         public Notification buildStyled(Notification wip) {
8009             addExtras(wip.extras);
8010             return wip;
8011         }
8012 
8013         /**
8014          * @hide
8015          */
purgeResources()8016         public void purgeResources() {}
8017 
8018         /**
8019          * Calls {@link android.app.Notification.Builder#build()} on the Builder this Style is
8020          * attached to.
8021          * <p>
8022          * Note: Calling build() multiple times returns the same Notification instance,
8023          * so reusing a builder to create multiple Notifications is discouraged.
8024          *
8025          * @return the fully constructed Notification.
8026          */
build()8027         public Notification build() {
8028             checkBuilder();
8029             return mBuilder.build();
8030         }
8031 
8032         /**
8033          * @hide
8034          * @return Whether we should put the summary be put into the notification header
8035          */
hasSummaryInHeader()8036         public boolean hasSummaryInHeader() {
8037             return true;
8038         }
8039 
8040         /**
8041          * @hide
8042          * @return Whether custom content views are displayed inline in the style
8043          */
displayCustomViewInline()8044         public boolean displayCustomViewInline() {
8045             return false;
8046         }
8047 
8048         /**
8049          * Reduces the image sizes contained in this style.
8050          *
8051          * @hide
8052          */
reduceImageSizes(Context context)8053         public void reduceImageSizes(Context context) {
8054         }
8055 
8056         /**
8057          * Validate that this style was properly composed. This is called at build time.
8058          * @hide
8059          */
validate(Context context)8060         public void validate(Context context) {
8061         }
8062 
8063         /**
8064          * @hide
8065          */
8066         @SuppressWarnings("HiddenAbstractMethod")
areNotificationsVisiblyDifferent(Style other)8067         public abstract boolean areNotificationsVisiblyDifferent(Style other);
8068 
8069         /**
8070          * @return the text that should be displayed in the statusBar when heads-upped.
8071          * If {@code null} is returned, the default implementation will be used.
8072          *
8073          * @hide
8074          */
getHeadsUpStatusBarText()8075         public CharSequence getHeadsUpStatusBarText() {
8076             return null;
8077         }
8078     }
8079 
8080     /**
8081      * Helper class for generating large-format notifications that include a large image attachment.
8082      *
8083      * Here's how you'd set the <code>BigPictureStyle</code> on a notification:
8084      * <pre class="prettyprint">
8085      * Notification notif = new Notification.Builder(mContext)
8086      *     .setContentTitle(&quot;New photo from &quot; + sender.toString())
8087      *     .setContentText(subject)
8088      *     .setSmallIcon(R.drawable.new_post)
8089      *     .setLargeIcon(aBitmap)
8090      *     .setStyle(new Notification.BigPictureStyle()
8091      *         .bigPicture(aBigBitmap))
8092      *     .build();
8093      * </pre>
8094      *
8095      * @see Notification#bigContentView
8096      */
8097     public static class BigPictureStyle extends Style {
8098         private Icon mPictureIcon;
8099         private Icon mBigLargeIcon;
8100         private boolean mBigLargeIconSet = false;
8101         private CharSequence mPictureContentDescription;
8102         private boolean mShowBigPictureWhenCollapsed;
8103 
BigPictureStyle()8104         public BigPictureStyle() {
8105         }
8106 
8107         /**
8108          * @deprecated use {@code BigPictureStyle()}.
8109          */
8110         @Deprecated
BigPictureStyle(Builder builder)8111         public BigPictureStyle(Builder builder) {
8112             setBuilder(builder);
8113         }
8114 
8115         /**
8116          * Overrides ContentTitle in the big form of the template.
8117          * This defaults to the value passed to setContentTitle().
8118          */
8119         @NonNull
setBigContentTitle(@ullable CharSequence title)8120         public BigPictureStyle setBigContentTitle(@Nullable CharSequence title) {
8121             internalSetBigContentTitle(safeCharSequence(title));
8122             return this;
8123         }
8124 
8125         /**
8126          * Set the first line of text after the detail section in the big form of the template.
8127          */
8128         @NonNull
setSummaryText(@ullable CharSequence cs)8129         public BigPictureStyle setSummaryText(@Nullable CharSequence cs) {
8130             internalSetSummaryText(safeCharSequence(cs));
8131             return this;
8132         }
8133 
8134         /**
8135          * Set the content description of the big picture.
8136          */
8137         @NonNull
setContentDescription( @ullable CharSequence contentDescription)8138         public BigPictureStyle setContentDescription(
8139                 @Nullable CharSequence contentDescription) {
8140             mPictureContentDescription = contentDescription;
8141             return this;
8142         }
8143 
8144         /**
8145          * @hide
8146          */
8147         @Nullable
getBigPicture()8148         public Icon getBigPicture() {
8149             if (mPictureIcon != null) {
8150                 return mPictureIcon;
8151             }
8152             return null;
8153         }
8154 
8155         /**
8156          * Provide the bitmap to be used as the payload for the BigPicture notification.
8157          */
8158         @NonNull
bigPicture(@ullable Bitmap b)8159         public BigPictureStyle bigPicture(@Nullable Bitmap b) {
8160             mPictureIcon = b == null ? null : Icon.createWithBitmap(b);
8161             return this;
8162         }
8163 
8164         /**
8165          * Provide the content Uri to be used as the payload for the BigPicture notification.
8166          */
8167         @NonNull
bigPicture(@ullable Icon icon)8168         public BigPictureStyle bigPicture(@Nullable Icon icon) {
8169             mPictureIcon = icon;
8170             return this;
8171         }
8172 
8173         /**
8174          * When set, the {@link #bigPicture(Bitmap) big picture} of this style will be promoted and
8175          * shown in place of the {@link Builder#setLargeIcon(Icon) large icon} in the collapsed
8176          * state of this notification.
8177          */
8178         @NonNull
showBigPictureWhenCollapsed(boolean show)8179         public BigPictureStyle showBigPictureWhenCollapsed(boolean show) {
8180             mShowBigPictureWhenCollapsed = show;
8181             return this;
8182         }
8183 
8184         /**
8185          * Override the large icon when the big notification is shown.
8186          */
8187         @NonNull
bigLargeIcon(@ullable Bitmap b)8188         public BigPictureStyle bigLargeIcon(@Nullable Bitmap b) {
8189             return bigLargeIcon(b != null ? Icon.createWithBitmap(b) : null);
8190         }
8191 
8192         /**
8193          * Override the large icon when the big notification is shown.
8194          */
8195         @NonNull
bigLargeIcon(@ullable Icon icon)8196         public BigPictureStyle bigLargeIcon(@Nullable Icon icon) {
8197             mBigLargeIconSet = true;
8198             mBigLargeIcon = icon;
8199             return this;
8200         }
8201 
8202         /** @hide */
8203         public static final int MIN_ASHMEM_BITMAP_SIZE = 128 * (1 << 10);
8204 
8205         /**
8206          * @hide
8207          */
8208         @Override
purgeResources()8209         public void purgeResources() {
8210             super.purgeResources();
8211             if (mPictureIcon != null) {
8212                 mPictureIcon.convertToAshmem();
8213             }
8214             if (mBigLargeIcon != null) {
8215                 mBigLargeIcon.convertToAshmem();
8216             }
8217         }
8218 
8219         /**
8220          * @hide
8221          */
8222         @Override
reduceImageSizes(Context context)8223         public void reduceImageSizes(Context context) {
8224             super.reduceImageSizes(context);
8225             Resources resources = context.getResources();
8226             boolean isLowRam = ActivityManager.isLowRamDeviceStatic();
8227             if (mPictureIcon != null) {
8228                 int maxPictureHeight = resources.getDimensionPixelSize(isLowRam
8229                         ? R.dimen.notification_big_picture_max_height_low_ram
8230                         : R.dimen.notification_big_picture_max_height);
8231                 int maxPictureWidth = resources.getDimensionPixelSize(isLowRam
8232                         ? R.dimen.notification_big_picture_max_width_low_ram
8233                         : R.dimen.notification_big_picture_max_width);
8234                 mPictureIcon.scaleDownIfNecessary(maxPictureWidth, maxPictureHeight);
8235             }
8236             if (mBigLargeIcon != null) {
8237                 int rightIconSize = resources.getDimensionPixelSize(isLowRam
8238                         ? R.dimen.notification_right_icon_size_low_ram
8239                         : R.dimen.notification_right_icon_size);
8240                 mBigLargeIcon.scaleDownIfNecessary(rightIconSize, rightIconSize);
8241             }
8242         }
8243 
8244         /**
8245          * @hide
8246          */
8247         @Override
makeContentView(boolean increasedHeight)8248         public RemoteViews makeContentView(boolean increasedHeight) {
8249             if (mPictureIcon == null || !mShowBigPictureWhenCollapsed) {
8250                 return super.makeContentView(increasedHeight);
8251             }
8252 
8253             StandardTemplateParams p = mBuilder.mParams.reset()
8254                     .viewType(StandardTemplateParams.VIEW_TYPE_NORMAL)
8255                     .fillTextsFrom(mBuilder)
8256                     .promotedPicture(mPictureIcon);
8257             return getStandardView(mBuilder.getBaseLayoutResource(), p, null /* result */);
8258         }
8259 
8260         /**
8261          * @hide
8262          */
8263         @Override
makeHeadsUpContentView(boolean increasedHeight)8264         public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
8265             if (mPictureIcon == null || !mShowBigPictureWhenCollapsed) {
8266                 return super.makeHeadsUpContentView(increasedHeight);
8267             }
8268 
8269             StandardTemplateParams p = mBuilder.mParams.reset()
8270                     .viewType(StandardTemplateParams.VIEW_TYPE_HEADS_UP)
8271                     .fillTextsFrom(mBuilder)
8272                     .promotedPicture(mPictureIcon);
8273             return getStandardView(mBuilder.getHeadsUpBaseLayoutResource(), p, null /* result */);
8274         }
8275 
8276         /**
8277          * @hide
8278          */
makeBigContentView()8279         public RemoteViews makeBigContentView() {
8280             // Replace mN.mLargeIcon with mBigLargeIcon if mBigLargeIconSet
8281             // This covers the following cases:
8282             //   1. mBigLargeIconSet -> mBigLargeIcon (null or non-null) applies, overrides
8283             //          mN.mLargeIcon
8284             //   2. !mBigLargeIconSet -> mN.mLargeIcon applies
8285             Icon oldLargeIcon = null;
8286             Bitmap largeIconLegacy = null;
8287             if (mBigLargeIconSet) {
8288                 oldLargeIcon = mBuilder.mN.mLargeIcon;
8289                 mBuilder.mN.mLargeIcon = mBigLargeIcon;
8290                 // The legacy largeIcon might not allow us to clear the image, as it's taken in
8291                 // replacement if the other one is null. Because we're restoring these legacy icons
8292                 // for old listeners, this is in general non-null.
8293                 largeIconLegacy = mBuilder.mN.largeIcon;
8294                 mBuilder.mN.largeIcon = null;
8295             }
8296 
8297             StandardTemplateParams p = mBuilder.mParams.reset()
8298                     .viewType(StandardTemplateParams.VIEW_TYPE_BIG).fillTextsFrom(mBuilder);
8299             RemoteViews contentView = getStandardView(mBuilder.getBigPictureLayoutResource(),
8300                     p, null /* result */);
8301             if (mSummaryTextSet) {
8302                 contentView.setTextViewText(R.id.text,
8303                         mBuilder.ensureColorSpanContrastOrStripStyling(
8304                                 mBuilder.processLegacyText(mSummaryText), p));
8305                 mBuilder.setTextViewColorSecondary(contentView, R.id.text, p);
8306                 contentView.setViewVisibility(R.id.text, View.VISIBLE);
8307             }
8308 
8309             if (mBigLargeIconSet) {
8310                 mBuilder.mN.mLargeIcon = oldLargeIcon;
8311                 mBuilder.mN.largeIcon = largeIconLegacy;
8312             }
8313 
8314             contentView.setImageViewIcon(R.id.big_picture, mPictureIcon);
8315 
8316             if (mPictureContentDescription != null) {
8317                 contentView.setContentDescription(R.id.big_picture, mPictureContentDescription);
8318             }
8319 
8320             return contentView;
8321         }
8322 
8323         /**
8324          * @hide
8325          */
addExtras(Bundle extras)8326         public void addExtras(Bundle extras) {
8327             super.addExtras(extras);
8328 
8329             if (mBigLargeIconSet) {
8330                 extras.putParcelable(EXTRA_LARGE_ICON_BIG, mBigLargeIcon);
8331             }
8332             if (mPictureContentDescription != null) {
8333                 extras.putCharSequence(EXTRA_PICTURE_CONTENT_DESCRIPTION,
8334                         mPictureContentDescription);
8335             }
8336             extras.putBoolean(EXTRA_SHOW_BIG_PICTURE_WHEN_COLLAPSED, mShowBigPictureWhenCollapsed);
8337 
8338             if (mPictureIcon == null) {
8339                 extras.remove(EXTRA_PICTURE_ICON);
8340                 extras.remove(EXTRA_PICTURE);
8341             } else if (mPictureIcon.getType() == Icon.TYPE_BITMAP) {
8342                 // If the icon contains a bitmap, use the old extra so that listeners which look
8343                 // for that extra can still find the picture. Don't include the new extra in
8344                 // that case, to avoid duplicating data. Leave the unused extra set to null to avoid
8345                 // crashing apps that came to expect it to be present but null.
8346                 extras.putParcelable(EXTRA_PICTURE, mPictureIcon.getBitmap());
8347                 extras.putParcelable(EXTRA_PICTURE_ICON, null);
8348             } else {
8349                 extras.putParcelable(EXTRA_PICTURE, null);
8350                 extras.putParcelable(EXTRA_PICTURE_ICON, mPictureIcon);
8351             }
8352         }
8353 
8354         /**
8355          * @hide
8356          */
8357         @Override
restoreFromExtras(Bundle extras)8358         protected void restoreFromExtras(Bundle extras) {
8359             super.restoreFromExtras(extras);
8360 
8361             if (extras.containsKey(EXTRA_LARGE_ICON_BIG)) {
8362                 mBigLargeIconSet = true;
8363                 mBigLargeIcon = extras.getParcelable(EXTRA_LARGE_ICON_BIG, Icon.class);
8364             }
8365 
8366             if (extras.containsKey(EXTRA_PICTURE_CONTENT_DESCRIPTION)) {
8367                 mPictureContentDescription =
8368                         extras.getCharSequence(EXTRA_PICTURE_CONTENT_DESCRIPTION);
8369             }
8370 
8371             mShowBigPictureWhenCollapsed = extras.getBoolean(EXTRA_SHOW_BIG_PICTURE_WHEN_COLLAPSED);
8372 
8373             mPictureIcon = getPictureIcon(extras);
8374         }
8375 
8376         /** @hide */
8377         @Nullable
getPictureIcon(@ullable Bundle extras)8378         public static Icon getPictureIcon(@Nullable Bundle extras) {
8379             if (extras == null) return null;
8380             // When this style adds a picture, we only add one of the keys.  If both were added,
8381             // it would most likely be a legacy app trying to override the picture in some way.
8382             // Because of that case it's better to give precedence to the legacy field.
8383             Bitmap bitmapPicture = extras.getParcelable(EXTRA_PICTURE, Bitmap.class);
8384             if (bitmapPicture != null) {
8385                 return Icon.createWithBitmap(bitmapPicture);
8386             } else {
8387                 return extras.getParcelable(EXTRA_PICTURE_ICON, Icon.class);
8388             }
8389         }
8390 
8391         /**
8392          * @hide
8393          */
8394         @Override
hasSummaryInHeader()8395         public boolean hasSummaryInHeader() {
8396             return false;
8397         }
8398 
8399         /**
8400          * @hide
8401          */
8402         @Override
areNotificationsVisiblyDifferent(Style other)8403         public boolean areNotificationsVisiblyDifferent(Style other) {
8404             if (other == null || getClass() != other.getClass()) {
8405                 return true;
8406             }
8407             BigPictureStyle otherS = (BigPictureStyle) other;
8408             return areIconsMaybeDifferent(getBigPicture(), otherS.getBigPicture());
8409         }
8410     }
8411 
8412     /**
8413      * Helper class for generating large-format notifications that include a lot of text.
8414      *
8415      * Here's how you'd set the <code>BigTextStyle</code> on a notification:
8416      * <pre class="prettyprint">
8417      * Notification notif = new Notification.Builder(mContext)
8418      *     .setContentTitle(&quot;New mail from &quot; + sender.toString())
8419      *     .setContentText(subject)
8420      *     .setSmallIcon(R.drawable.new_mail)
8421      *     .setLargeIcon(aBitmap)
8422      *     .setStyle(new Notification.BigTextStyle()
8423      *         .bigText(aVeryLongString))
8424      *     .build();
8425      * </pre>
8426      *
8427      * @see Notification#bigContentView
8428      */
8429     public static class BigTextStyle extends Style {
8430 
8431         private CharSequence mBigText;
8432 
BigTextStyle()8433         public BigTextStyle() {
8434         }
8435 
8436         /**
8437          * @deprecated use {@code BigTextStyle()}.
8438          */
8439         @Deprecated
BigTextStyle(Builder builder)8440         public BigTextStyle(Builder builder) {
8441             setBuilder(builder);
8442         }
8443 
8444         /**
8445          * Overrides ContentTitle in the big form of the template.
8446          * This defaults to the value passed to setContentTitle().
8447          */
setBigContentTitle(CharSequence title)8448         public BigTextStyle setBigContentTitle(CharSequence title) {
8449             internalSetBigContentTitle(safeCharSequence(title));
8450             return this;
8451         }
8452 
8453         /**
8454          * Set the first line of text after the detail section in the big form of the template.
8455          */
setSummaryText(CharSequence cs)8456         public BigTextStyle setSummaryText(CharSequence cs) {
8457             internalSetSummaryText(safeCharSequence(cs));
8458             return this;
8459         }
8460 
8461         /**
8462          * Provide the longer text to be displayed in the big form of the
8463          * template in place of the content text.
8464          */
bigText(CharSequence cs)8465         public BigTextStyle bigText(CharSequence cs) {
8466             mBigText = safeCharSequence(cs);
8467             return this;
8468         }
8469 
8470         /**
8471          * @hide
8472          */
getBigText()8473         public CharSequence getBigText() {
8474             return mBigText;
8475         }
8476 
8477         /**
8478          * @hide
8479          */
addExtras(Bundle extras)8480         public void addExtras(Bundle extras) {
8481             super.addExtras(extras);
8482 
8483             extras.putCharSequence(EXTRA_BIG_TEXT, mBigText);
8484         }
8485 
8486         /**
8487          * @hide
8488          */
8489         @Override
restoreFromExtras(Bundle extras)8490         protected void restoreFromExtras(Bundle extras) {
8491             super.restoreFromExtras(extras);
8492 
8493             mBigText = extras.getCharSequence(EXTRA_BIG_TEXT);
8494         }
8495 
8496         /**
8497          * @param increasedHeight true if this layout be created with an increased height.
8498          *
8499          * @hide
8500          */
8501         @Override
makeContentView(boolean increasedHeight)8502         public RemoteViews makeContentView(boolean increasedHeight) {
8503             if (increasedHeight) {
8504                 ArrayList<Action> originalActions = mBuilder.mActions;
8505                 mBuilder.mActions = new ArrayList<>();
8506                 RemoteViews remoteViews = makeBigContentView();
8507                 mBuilder.mActions = originalActions;
8508                 return remoteViews;
8509             }
8510             return super.makeContentView(increasedHeight);
8511         }
8512 
8513         /**
8514          * @hide
8515          */
8516         @Override
makeHeadsUpContentView(boolean increasedHeight)8517         public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
8518             if (increasedHeight && mBuilder.mActions.size() > 0) {
8519                 // TODO(b/163626038): pass VIEW_TYPE_HEADS_UP?
8520                 return makeBigContentView();
8521             }
8522             return super.makeHeadsUpContentView(increasedHeight);
8523         }
8524 
8525         /**
8526          * @hide
8527          */
makeBigContentView()8528         public RemoteViews makeBigContentView() {
8529             StandardTemplateParams p = mBuilder.mParams.reset()
8530                     .viewType(StandardTemplateParams.VIEW_TYPE_BIG)
8531                     .allowTextWithProgress(true)
8532                     .textViewId(R.id.big_text)
8533                     .fillTextsFrom(mBuilder);
8534 
8535             // Replace the text with the big text, but only if the big text is not empty.
8536             CharSequence bigTextText = mBuilder.processLegacyText(mBigText);
8537             if (Flags.cleanUpSpansAndNewLines()) {
8538                 bigTextText = normalizeBigText(stripStyling(bigTextText));
8539             }
8540             if (!TextUtils.isEmpty(bigTextText)) {
8541                 p.text(bigTextText);
8542             }
8543 
8544             return getStandardView(mBuilder.getBigTextLayoutResource(), p, null /* result */);
8545         }
8546 
8547         /**
8548          * @hide
8549          * Spans are ignored when comparing text for visual difference.
8550          */
8551         @Override
areNotificationsVisiblyDifferent(Style other)8552         public boolean areNotificationsVisiblyDifferent(Style other) {
8553             if (other == null || getClass() != other.getClass()) {
8554                 return true;
8555             }
8556             BigTextStyle newS = (BigTextStyle) other;
8557             return !Objects.equals(String.valueOf(getBigText()), String.valueOf(newS.getBigText()));
8558         }
8559 
8560     }
8561 
8562     /**
8563      * Helper class for generating large-format notifications that include multiple back-and-forth
8564      * messages of varying types between any number of people.
8565      *
8566      * <p>
8567      * If the platform does not provide large-format notifications, this method has no effect. The
8568      * user will always see the normal notification view.
8569      *
8570      * <p>
8571      * If the app is targeting Android {@link android.os.Build.VERSION_CODES#P} and above, it is
8572      * required to use the {@link Person} class in order to get an optimal rendering of the
8573      * notification and its avatars. For conversations involving multiple people, the app should
8574      * also make sure that it marks the conversation as a group with
8575      * {@link #setGroupConversation(boolean)}.
8576      *
8577      * <p>
8578      * From Android {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, messaging style
8579      * notifications that are associated with a valid conversation shortcut
8580      * (via {@link Notification.Builder#setShortcutId(String)}) are displayed in a dedicated
8581      * conversation section in the shade above non-conversation alerting and silence notifications.
8582      * To be a valid conversation shortcut, the shortcut must be a
8583      * {@link ShortcutInfo.Builder#setLongLived(boolean)} dynamic or cached sharing shortcut.
8584      *
8585      * <p>
8586      * This class is a "rebuilder": It attaches to a Builder object and modifies its behavior.
8587      * Here's an example of how this may be used:
8588      * <pre class="prettyprint">
8589      *
8590      * Person user = new Person.Builder().setIcon(userIcon).setName(userName).build();
8591      * MessagingStyle style = new MessagingStyle(user)
8592      *      .addMessage(messages[1].getText(), messages[1].getTime(), messages[1].getPerson())
8593      *      .addMessage(messages[2].getText(), messages[2].getTime(), messages[2].getPerson())
8594      *      .setGroupConversation(hasMultiplePeople());
8595      *
8596      * Notification noti = new Notification.Builder()
8597      *     .setContentTitle(&quot;2 new messages with &quot; + sender.toString())
8598      *     .setContentText(subject)
8599      *     .setSmallIcon(R.drawable.new_message)
8600      *     .setLargeIcon(aBitmap)
8601      *     .setStyle(style)
8602      *     .build();
8603      * </pre>
8604      */
8605     public static class MessagingStyle extends Style {
8606 
8607         /**
8608          * The maximum number of messages that will be retained in the Notification itself (the
8609          * number displayed is up to the platform).
8610          */
8611         public static final int MAXIMUM_RETAINED_MESSAGES = 25;
8612 
8613 
8614         /** @hide */
8615         public static final int CONVERSATION_TYPE_LEGACY = 0;
8616         /** @hide */
8617         public static final int CONVERSATION_TYPE_NORMAL = 1;
8618         /** @hide */
8619         public static final int CONVERSATION_TYPE_IMPORTANT = 2;
8620 
8621         /** @hide */
8622         @IntDef(prefix = {"CONVERSATION_TYPE_"}, value = {
8623                 CONVERSATION_TYPE_LEGACY, CONVERSATION_TYPE_NORMAL, CONVERSATION_TYPE_IMPORTANT
8624         })
8625         @Retention(RetentionPolicy.SOURCE)
8626         public @interface ConversationType {}
8627 
8628         @NonNull Person mUser;
8629         @Nullable CharSequence mConversationTitle;
8630         @Nullable Icon mShortcutIcon;
8631         List<Message> mMessages = new ArrayList<>();
8632         List<Message> mHistoricMessages = new ArrayList<>();
8633         boolean mIsGroupConversation;
8634         @ConversationType int mConversationType = CONVERSATION_TYPE_LEGACY;
8635         int mUnreadMessageCount;
8636 
MessagingStyle()8637         MessagingStyle() {
8638         }
8639 
8640         /**
8641          * @param userDisplayName Required - the name to be displayed for any replies sent by the
8642          * user before the posting app reposts the notification with those messages after they've
8643          * been actually sent and in previous messages sent by the user added in
8644          * {@link #addMessage(Notification.MessagingStyle.Message)}
8645          *
8646          * @deprecated use {@code MessagingStyle(Person)}
8647          */
MessagingStyle(@onNull CharSequence userDisplayName)8648         public MessagingStyle(@NonNull CharSequence userDisplayName) {
8649             this(new Person.Builder().setName(userDisplayName).build());
8650         }
8651 
8652         /**
8653          * @param user Required - The person displayed for any messages that are sent by the
8654          * user. Any messages added with {@link #addMessage(Notification.MessagingStyle.Message)}
8655          * who don't have a Person associated with it will be displayed as if they were sent
8656          * by this user. The user also needs to have a valid name associated with it, which is
8657          * enforced starting in Android {@link android.os.Build.VERSION_CODES#P}.
8658          */
MessagingStyle(@onNull Person user)8659         public MessagingStyle(@NonNull Person user) {
8660             mUser = user;
8661         }
8662 
8663         /**
8664          * Validate that this style was properly composed. This is called at build time.
8665          * @hide
8666          */
8667         @Override
validate(Context context)8668         public void validate(Context context) {
8669             super.validate(context);
8670             if (context.getApplicationInfo().targetSdkVersion
8671                     >= Build.VERSION_CODES.P && (mUser == null || mUser.getName() == null)) {
8672                 throw new RuntimeException("User must be valid and have a name.");
8673             }
8674         }
8675 
8676         /**
8677          * @return the text that should be displayed in the statusBar when heads upped.
8678          * If {@code null} is returned, the default implementation will be used.
8679          *
8680          * @hide
8681          */
8682         @Override
getHeadsUpStatusBarText()8683         public CharSequence getHeadsUpStatusBarText() {
8684             CharSequence conversationTitle = !TextUtils.isEmpty(super.mBigContentTitle)
8685                     ? super.mBigContentTitle
8686                     : mConversationTitle;
8687             if (mConversationType == CONVERSATION_TYPE_LEGACY
8688                     && !TextUtils.isEmpty(conversationTitle) && !hasOnlyWhiteSpaceSenders()) {
8689                 return conversationTitle;
8690             }
8691             return null;
8692         }
8693 
8694         /**
8695          * @return the user to be displayed for any replies sent by the user
8696          */
8697         @NonNull
getUser()8698         public Person getUser() {
8699             return mUser;
8700         }
8701 
8702         /**
8703          * Returns the name to be displayed for any replies sent by the user
8704          *
8705          * @deprecated use {@link #getUser()} instead
8706          */
getUserDisplayName()8707         public CharSequence getUserDisplayName() {
8708             return mUser.getName();
8709         }
8710 
8711         /**
8712          * Sets the title to be displayed on this conversation. May be set to {@code null}.
8713          *
8714          * <p>Starting in {@link Build.VERSION_CODES#R}, this conversation title will be ignored
8715          * if a valid shortcutId is added via {@link Notification.Builder#setShortcutId(String)}.
8716          * In this case, {@link ShortcutInfo#getLongLabel()} (or, if missing,
8717          * {@link ShortcutInfo#getShortLabel()}) will be shown as the conversation title
8718          * instead.
8719          *
8720          * <p>This API's behavior was changed in SDK version {@link Build.VERSION_CODES#P}. If your
8721          * application's target version is less than {@link Build.VERSION_CODES#P}, setting a
8722          * conversation title to a non-null value will make {@link #isGroupConversation()} return
8723          * {@code true} and passing {@code null} will make it return {@code false}. In
8724          * {@link Build.VERSION_CODES#P} and beyond, use {@link #setGroupConversation(boolean)}
8725          * to set group conversation status.
8726          *
8727          * @param conversationTitle Title displayed for this conversation
8728          * @return this object for method chaining
8729          */
setConversationTitle(@ullable CharSequence conversationTitle)8730         public MessagingStyle setConversationTitle(@Nullable CharSequence conversationTitle) {
8731             mConversationTitle = conversationTitle;
8732             return this;
8733         }
8734 
8735         /**
8736          * Return the title to be displayed on this conversation. May return {@code null}.
8737          */
8738         @Nullable
getConversationTitle()8739         public CharSequence getConversationTitle() {
8740             return mConversationTitle;
8741         }
8742 
8743         /**
8744          * Sets the icon to be displayed on the conversation, derived from the shortcutId.
8745          *
8746          * @hide
8747          */
setShortcutIcon(@ullable Icon conversationIcon)8748         public MessagingStyle setShortcutIcon(@Nullable Icon conversationIcon) {
8749             mShortcutIcon = conversationIcon;
8750             return this;
8751         }
8752 
8753         /**
8754          * Return the icon to be displayed on this conversation, derived from the shortcutId. May
8755          * return {@code null}.
8756          *
8757          * @hide
8758          */
8759         @Nullable
getShortcutIcon()8760         public Icon getShortcutIcon() {
8761             return mShortcutIcon;
8762         }
8763 
8764         /**
8765          * Sets the conversation type of this MessageStyle notification.
8766          * {@link #CONVERSATION_TYPE_LEGACY} will use the "older" layout from pre-R,
8767          * {@link #CONVERSATION_TYPE_NORMAL} will use the new "conversation" layout, and
8768          * {@link #CONVERSATION_TYPE_IMPORTANT} will add additional "important" treatments.
8769          *
8770          * @hide
8771          */
setConversationType(@onversationType int conversationType)8772         public MessagingStyle setConversationType(@ConversationType int conversationType) {
8773             mConversationType = conversationType;
8774             return this;
8775         }
8776 
8777         /** @hide */
8778         @ConversationType
getConversationType()8779         public int getConversationType() {
8780             return mConversationType;
8781         }
8782 
8783         /** @hide */
getUnreadMessageCount()8784         public int getUnreadMessageCount() {
8785             return mUnreadMessageCount;
8786         }
8787 
8788         /** @hide */
setUnreadMessageCount(int unreadMessageCount)8789         public MessagingStyle setUnreadMessageCount(int unreadMessageCount) {
8790             mUnreadMessageCount = unreadMessageCount;
8791             return this;
8792         }
8793 
8794         /**
8795          * Adds a message for display by this notification. Convenience call for a simple
8796          * {@link Message} in {@link #addMessage(Notification.MessagingStyle.Message)}.
8797          * @param text A {@link CharSequence} to be displayed as the message content
8798          * @param timestamp Time in milliseconds at which the message arrived
8799          * @param sender A {@link CharSequence} to be used for displaying the name of the
8800          * sender. Should be <code>null</code> for messages by the current user, in which case
8801          * the platform will insert {@link #getUserDisplayName()}.
8802          * Should be unique amongst all individuals in the conversation, and should be
8803          * consistent during re-posts of the notification.
8804          *
8805          * @see Message#Message(CharSequence, long, CharSequence)
8806          *
8807          * @return this object for method chaining
8808          *
8809          * @deprecated use {@link #addMessage(CharSequence, long, Person)}
8810          */
addMessage(CharSequence text, long timestamp, CharSequence sender)8811         public MessagingStyle addMessage(CharSequence text, long timestamp, CharSequence sender) {
8812             return addMessage(text, timestamp,
8813                     sender == null ? null : new Person.Builder().setName(sender).build());
8814         }
8815 
8816         /**
8817          * Adds a message for display by this notification. Convenience call for a simple
8818          * {@link Message} in {@link #addMessage(Notification.MessagingStyle.Message)}.
8819          * @param text A {@link CharSequence} to be displayed as the message content
8820          * @param timestamp Time in milliseconds at which the message arrived
8821          * @param sender The {@link Person} who sent the message.
8822          * Should be <code>null</code> for messages by the current user, in which case
8823          * the platform will insert the user set in {@code MessagingStyle(Person)}.
8824          *
8825          * @see Message#Message(CharSequence, long, CharSequence)
8826          *
8827          * @return this object for method chaining
8828          */
addMessage(@onNull CharSequence text, long timestamp, @Nullable Person sender)8829         public MessagingStyle addMessage(@NonNull CharSequence text, long timestamp,
8830                 @Nullable Person sender) {
8831             return addMessage(new Message(text, timestamp, sender));
8832         }
8833 
8834         /**
8835          * Adds a {@link Message} for display in this notification.
8836          *
8837          * <p>The messages should be added in chronologic order, i.e. the oldest first,
8838          * the newest last.
8839          *
8840          * <p>Multiple Messages in a row with the same timestamp and sender may be grouped as a
8841          * single message. This means an app should represent a message that has both an image and
8842          * text as two Message objects, one with the image (and fallback text), and the other with
8843          * the message text. For consistency, a text message (if any) should be provided after Uri
8844          * content.
8845          *
8846          * @param message The {@link Message} to be displayed
8847          * @return this object for method chaining
8848          */
addMessage(Message message)8849         public MessagingStyle addMessage(Message message) {
8850             mMessages.add(message);
8851             if (mMessages.size() > MAXIMUM_RETAINED_MESSAGES) {
8852                 mMessages.remove(0);
8853             }
8854             return this;
8855         }
8856 
8857         /**
8858          * Adds a {@link Message} for historic context in this notification.
8859          *
8860          * <p>Messages should be added as historic if they are not the main subject of the
8861          * notification but may give context to a conversation. The system may choose to present
8862          * them only when relevant, e.g. when replying to a message through a {@link RemoteInput}.
8863          *
8864          * <p>The messages should be added in chronologic order, i.e. the oldest first,
8865          * the newest last.
8866          *
8867          * @param message The historic {@link Message} to be added
8868          * @return this object for method chaining
8869          */
addHistoricMessage(Message message)8870         public MessagingStyle addHistoricMessage(Message message) {
8871             mHistoricMessages.add(message);
8872             if (mHistoricMessages.size() > MAXIMUM_RETAINED_MESSAGES) {
8873                 mHistoricMessages.remove(0);
8874             }
8875             return this;
8876         }
8877 
8878         /**
8879          * Gets the list of {@code Message} objects that represent the notification.
8880          */
getMessages()8881         public List<Message> getMessages() {
8882             return mMessages;
8883         }
8884 
8885         /**
8886          * Gets the list of historic {@code Message}s in the notification.
8887          */
getHistoricMessages()8888         public List<Message> getHistoricMessages() {
8889             return mHistoricMessages;
8890         }
8891 
8892         /**
8893          * Sets whether this conversation notification represents a group. If the app is targeting
8894          * Android P, this is required if the app wants to display the largeIcon set with
8895          * {@link Notification.Builder#setLargeIcon(Bitmap)}, otherwise it will be hidden.
8896          *
8897          * @param isGroupConversation {@code true} if the conversation represents a group,
8898          * {@code false} otherwise.
8899          * @return this object for method chaining
8900          */
setGroupConversation(boolean isGroupConversation)8901         public MessagingStyle setGroupConversation(boolean isGroupConversation) {
8902             mIsGroupConversation = isGroupConversation;
8903             return this;
8904         }
8905 
8906         /**
8907          * Returns {@code true} if this notification represents a group conversation, otherwise
8908          * {@code false}.
8909          *
8910          * <p> If the application that generated this {@link MessagingStyle} targets an SDK version
8911          * less than {@link Build.VERSION_CODES#P}, this method becomes dependent on whether or
8912          * not the conversation title is set; returning {@code true} if the conversation title is
8913          * a non-null value, or {@code false} otherwise. From {@link Build.VERSION_CODES#P} forward,
8914          * this method returns what's set by {@link #setGroupConversation(boolean)} allowing for
8915          * named, non-group conversations.
8916          *
8917          * @see #setConversationTitle(CharSequence)
8918          */
isGroupConversation()8919         public boolean isGroupConversation() {
8920             // When target SDK version is < P, a non-null conversation title dictates if this is
8921             // as group conversation.
8922             if (mBuilder != null
8923                     && mBuilder.mContext.getApplicationInfo().targetSdkVersion
8924                             < Build.VERSION_CODES.P) {
8925                 return mConversationTitle != null;
8926             }
8927 
8928             return mIsGroupConversation;
8929         }
8930 
8931         /**
8932          * @hide
8933          */
8934         @Override
addExtras(Bundle extras)8935         public void addExtras(Bundle extras) {
8936             super.addExtras(extras);
8937             addExtras(extras, false, 0);
8938         }
8939 
8940         /**
8941          * @hide
8942          */
addExtras(Bundle extras, boolean ensureContrast, int backgroundColor)8943         public void addExtras(Bundle extras, boolean ensureContrast, int backgroundColor) {
8944             if (mUser != null) {
8945                 // For legacy usages
8946                 extras.putCharSequence(EXTRA_SELF_DISPLAY_NAME, mUser.getName());
8947                 extras.putParcelable(EXTRA_MESSAGING_PERSON, mUser);
8948             }
8949             if (mConversationTitle != null) {
8950                 extras.putCharSequence(EXTRA_CONVERSATION_TITLE, mConversationTitle);
8951             }
8952             if (!mMessages.isEmpty()) {
8953                 extras.putParcelableArray(EXTRA_MESSAGES,
8954                         getBundleArrayForMessages(mMessages, ensureContrast, backgroundColor));
8955             }
8956             if (!mHistoricMessages.isEmpty()) {
8957                 extras.putParcelableArray(EXTRA_HISTORIC_MESSAGES, getBundleArrayForMessages(
8958                         mHistoricMessages, ensureContrast, backgroundColor));
8959             }
8960             if (mShortcutIcon != null) {
8961                 extras.putParcelable(EXTRA_CONVERSATION_ICON, mShortcutIcon);
8962             }
8963             extras.putInt(EXTRA_CONVERSATION_UNREAD_MESSAGE_COUNT, mUnreadMessageCount);
8964 
8965             fixTitleAndTextExtras(extras);
8966             extras.putBoolean(EXTRA_IS_GROUP_CONVERSATION, mIsGroupConversation);
8967         }
8968 
getBundleArrayForMessages(List<Message> messages, boolean ensureContrast, int backgroundColor)8969         private static Bundle[] getBundleArrayForMessages(List<Message> messages,
8970                 boolean ensureContrast, int backgroundColor) {
8971             Bundle[] bundles = new Bundle[messages.size()];
8972             final int N = messages.size();
8973             for (int i = 0; i < N; i++) {
8974                 final Message m = messages.get(i);
8975                 if (ensureContrast) {
8976                     m.ensureColorContrastOrStripStyling(backgroundColor);
8977                 }
8978                 bundles[i] = m.toBundle();
8979             }
8980             return bundles;
8981         }
8982 
fixTitleAndTextExtras(Bundle extras)8983         private void fixTitleAndTextExtras(Bundle extras) {
8984             Message m = findLatestIncomingMessage();
8985             CharSequence text = (m == null) ? null : m.mText;
8986             CharSequence sender = m == null ? null
8987                     : m.mSender == null || TextUtils.isEmpty(m.mSender.getName())
8988                             ? mUser.getName() : m.mSender.getName();
8989             CharSequence title;
8990             if (!TextUtils.isEmpty(mConversationTitle)) {
8991                 if (!TextUtils.isEmpty(sender)) {
8992                     BidiFormatter bidi = BidiFormatter.getInstance();
8993                     title = mBuilder.mContext.getString(
8994                             com.android.internal.R.string.notification_messaging_title_template,
8995                             bidi.unicodeWrap(mConversationTitle), bidi.unicodeWrap(sender));
8996                 } else {
8997                     title = mConversationTitle;
8998                 }
8999             } else {
9000                 title = sender;
9001             }
9002             if (Flags.cleanUpSpansAndNewLines()) {
9003                 title = stripStyling(title);
9004             }
9005             if (title != null) {
9006                 extras.putCharSequence(EXTRA_TITLE, title);
9007             }
9008             if (text != null) {
9009                 extras.putCharSequence(EXTRA_TEXT, text);
9010             }
9011         }
9012 
fixTitleAndTextForCompactMessaging(StandardTemplateParams p)9013         private void fixTitleAndTextForCompactMessaging(StandardTemplateParams p) {
9014             Message m = findLatestIncomingMessage();
9015             final CharSequence text = (m == null) ? null : m.mText;
9016             CharSequence sender = m == null ? null
9017                     : m.mSender == null || TextUtils.isEmpty(m.mSender.getName())
9018                             ? mUser.getName() : m.mSender.getName();
9019 
9020             CharSequence conversationTitle = mIsGroupConversation ? mConversationTitle : null;
9021 
9022             // we want to have colon for possible title for conversation.
9023             final BidiFormatter bidi = BidiFormatter.getInstance();
9024             if (sender != null) {
9025                 sender = mBuilder.mContext.getString(
9026                         com.android.internal.R.string.notification_messaging_title_template,
9027                         bidi.unicodeWrap(sender), "");
9028             } else if (conversationTitle != null) {
9029                 conversationTitle = mBuilder.mContext.getString(
9030                         com.android.internal.R.string.notification_messaging_title_template,
9031                         bidi.unicodeWrap(conversationTitle), "");
9032             }
9033 
9034             if (Flags.cleanUpSpansAndNewLines()) {
9035                 conversationTitle = stripStyling(conversationTitle);
9036                 sender = stripStyling(sender);
9037             }
9038 
9039             final CharSequence title;
9040             final boolean isConversationTitleAvailable = showConversationTitle()
9041                     && conversationTitle != null;
9042             if (isConversationTitleAvailable) {
9043                 title = conversationTitle;
9044             } else {
9045                 title = sender;
9046             }
9047 
9048             p.title(title);
9049             // when the conversation title is available, use headerTextSecondary for sender and
9050             // summaryText for text
9051             if (isConversationTitleAvailable) {
9052                 p.headerTextSecondary(sender);
9053                 p.summaryText(text);
9054             } else {
9055                 // when it is not, use headerTextSecondary for text and don't use summaryText
9056                 p.headerTextSecondary(text);
9057                 p.summaryText(null);
9058             }
9059         }
9060 
9061         /** (b/342370742) Developer settings to show conversation title. */
showConversationTitle()9062         private boolean showConversationTitle() {
9063             return SystemProperties.getBoolean(
9064                     "persist.compact_heads_up_notification.show_conversation_title_for_group",
9065                     false);
9066         }
9067 
9068         /**
9069          * @hide
9070          */
9071         @Override
restoreFromExtras(Bundle extras)9072         protected void restoreFromExtras(Bundle extras) {
9073             super.restoreFromExtras(extras);
9074 
9075             Person user = extras.getParcelable(EXTRA_MESSAGING_PERSON, Person.class);
9076             if (user == null) {
9077                 CharSequence displayName = extras.getCharSequence(EXTRA_SELF_DISPLAY_NAME);
9078                 mUser = new Person.Builder().setName(displayName).build();
9079             } else {
9080                 mUser = user;
9081             }
9082             mConversationTitle = extras.getCharSequence(EXTRA_CONVERSATION_TITLE);
9083             Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES, Parcelable.class);
9084             mMessages = Message.getMessagesFromBundleArray(messages);
9085             Parcelable[] histMessages = extras.getParcelableArray(EXTRA_HISTORIC_MESSAGES,
9086                     Parcelable.class);
9087             mHistoricMessages = Message.getMessagesFromBundleArray(histMessages);
9088             mIsGroupConversation = extras.getBoolean(EXTRA_IS_GROUP_CONVERSATION);
9089             mUnreadMessageCount = extras.getInt(EXTRA_CONVERSATION_UNREAD_MESSAGE_COUNT);
9090             mShortcutIcon = extras.getParcelable(EXTRA_CONVERSATION_ICON, Icon.class);
9091         }
9092 
9093         /**
9094          * @hide
9095          */
9096         @Override
makeContentView(boolean increasedHeight)9097         public RemoteViews makeContentView(boolean increasedHeight) {
9098             // All messaging templates contain the actions
9099             ArrayList<Action> originalActions = mBuilder.mActions;
9100             try {
9101                 mBuilder.mActions = new ArrayList<>();
9102                 return makeMessagingView(StandardTemplateParams.VIEW_TYPE_NORMAL);
9103             } finally {
9104                 mBuilder.mActions = originalActions;
9105             }
9106         }
9107 
9108         /**
9109          * @hide
9110          * Spans are ignored when comparing text for visual difference.
9111          */
9112         @Override
areNotificationsVisiblyDifferent(Style other)9113         public boolean areNotificationsVisiblyDifferent(Style other) {
9114             if (other == null || getClass() != other.getClass()) {
9115                 return true;
9116             }
9117             MessagingStyle newS = (MessagingStyle) other;
9118             List<MessagingStyle.Message> oldMs = getMessages();
9119             List<MessagingStyle.Message> newMs = newS.getMessages();
9120 
9121             if (oldMs == null || newMs == null) {
9122                 newMs = new ArrayList<>();
9123             }
9124 
9125             int n = oldMs.size();
9126             if (n != newMs.size()) {
9127                 return true;
9128             }
9129             for (int i = 0; i < n; i++) {
9130                 MessagingStyle.Message oldM = oldMs.get(i);
9131                 MessagingStyle.Message newM = newMs.get(i);
9132                 if (!Objects.equals(
9133                         String.valueOf(oldM.getText()),
9134                         String.valueOf(newM.getText()))) {
9135                     return true;
9136                 }
9137                 if (!Objects.equals(oldM.getDataUri(), newM.getDataUri())) {
9138                     return true;
9139                 }
9140                 String oldSender = String.valueOf(oldM.getSenderPerson() == null
9141                         ? oldM.getSender()
9142                         : oldM.getSenderPerson().getName());
9143                 String newSender = String.valueOf(newM.getSenderPerson() == null
9144                         ? newM.getSender()
9145                         : newM.getSenderPerson().getName());
9146                 if (!Objects.equals(oldSender, newSender)) {
9147                     return true;
9148                 }
9149 
9150                 String oldKey = oldM.getSenderPerson() == null
9151                         ? null : oldM.getSenderPerson().getKey();
9152                 String newKey = newM.getSenderPerson() == null
9153                         ? null : newM.getSenderPerson().getKey();
9154                 if (!Objects.equals(oldKey, newKey)) {
9155                     return true;
9156                 }
9157                 // Other fields (like timestamp) intentionally excluded
9158             }
9159             return false;
9160         }
9161 
findLatestIncomingMessage()9162         private Message findLatestIncomingMessage() {
9163             return findLatestIncomingMessage(mMessages);
9164         }
9165 
9166         /**
9167          * @hide
9168          */
9169         @Nullable
findLatestIncomingMessage( List<Message> messages)9170         public static Message findLatestIncomingMessage(
9171                 List<Message> messages) {
9172             for (int i = messages.size() - 1; i >= 0; i--) {
9173                 Message m = messages.get(i);
9174                 // Incoming messages have a non-empty sender.
9175                 if (m.mSender != null && !TextUtils.isEmpty(m.mSender.getName())) {
9176                     return m;
9177                 }
9178             }
9179             if (!messages.isEmpty()) {
9180                 // No incoming messages, fall back to outgoing message
9181                 return messages.get(messages.size() - 1);
9182             }
9183             return null;
9184         }
9185 
9186         /**
9187          * @hide
9188          */
9189         @Override
makeBigContentView()9190         public RemoteViews makeBigContentView() {
9191             return makeMessagingView(StandardTemplateParams.VIEW_TYPE_BIG);
9192         }
9193 
9194         /**
9195          * Create a messaging layout.
9196          *
9197          * @param viewType one of StandardTemplateParams.VIEW_TYPE_NORMAL, VIEW_TYPE_BIG,
9198          *                VIEW_TYPE_HEADS_UP
9199          * @return the created remoteView.
9200          */
9201         @NonNull
makeMessagingView(int viewType)9202         private RemoteViews makeMessagingView(int viewType) {
9203             boolean isCollapsed = viewType != StandardTemplateParams.VIEW_TYPE_BIG;
9204             boolean hideRightIcons = viewType != StandardTemplateParams.VIEW_TYPE_NORMAL;
9205             boolean isConversationLayout = mConversationType != CONVERSATION_TYPE_LEGACY;
9206             boolean isImportantConversation = mConversationType == CONVERSATION_TYPE_IMPORTANT;
9207             boolean isHeaderless = !isConversationLayout && isCollapsed;
9208 
9209             //TODO (b/217799515): ensure mConversationTitle always returns the correct
9210             // conversationTitle, probably set mConversationTitle = conversationTitle after this
9211             CharSequence conversationTitle = !TextUtils.isEmpty(super.mBigContentTitle)
9212                     ? super.mBigContentTitle
9213                     : mConversationTitle;
9214             boolean atLeastP = mBuilder.mContext.getApplicationInfo().targetSdkVersion
9215                     >= Build.VERSION_CODES.P;
9216             boolean isOneToOne;
9217             CharSequence nameReplacement = null;
9218             if (!atLeastP) {
9219                 isOneToOne = TextUtils.isEmpty(conversationTitle);
9220                 if (hasOnlyWhiteSpaceSenders()) {
9221                     isOneToOne = true;
9222                     nameReplacement = conversationTitle;
9223                     conversationTitle = null;
9224                 }
9225             } else {
9226                 isOneToOne = !isGroupConversation();
9227             }
9228             if (isHeaderless && isOneToOne && TextUtils.isEmpty(conversationTitle)) {
9229                 conversationTitle = getOtherPersonName();
9230             }
9231 
9232             Icon largeIcon = mBuilder.mN.mLargeIcon;
9233             TemplateBindResult bindResult = new TemplateBindResult();
9234             StandardTemplateParams p = mBuilder.mParams.reset()
9235                     .viewType(viewType)
9236                     .highlightExpander(isConversationLayout)
9237                     .hideProgress(true)
9238                     .title(isHeaderless ? conversationTitle : null)
9239                     .text(null)
9240                     .hideLeftIcon(isOneToOne)
9241                     .hideRightIcon(hideRightIcons || isOneToOne)
9242                     .headerTextSecondary(isHeaderless ? null : conversationTitle);
9243             RemoteViews contentView = mBuilder.applyStandardTemplateWithActions(
9244                     isConversationLayout
9245                             ? mBuilder.getConversationLayoutResource()
9246                             : isCollapsed
9247                                     ? mBuilder.getMessagingLayoutResource()
9248                                     : mBuilder.getBigMessagingLayoutResource(),
9249                     p,
9250                     bindResult);
9251             if (isConversationLayout) {
9252                 mBuilder.setTextViewColorPrimary(contentView, R.id.conversation_text, p);
9253                 mBuilder.setTextViewColorSecondary(contentView, R.id.app_name_divider, p);
9254             }
9255 
9256             addExtras(mBuilder.mN.extras, true, mBuilder.getBackgroundColor(p));
9257             contentView.setInt(R.id.status_bar_latest_event_content, "setLayoutColor",
9258                     mBuilder.getSmallIconColor(p));
9259             contentView.setInt(R.id.status_bar_latest_event_content, "setSenderTextColor",
9260                     mBuilder.getPrimaryTextColor(p));
9261             contentView.setInt(R.id.status_bar_latest_event_content, "setMessageTextColor",
9262                     mBuilder.getSecondaryTextColor(p));
9263             contentView.setInt(R.id.status_bar_latest_event_content,
9264                     "setNotificationBackgroundColor",
9265                     mBuilder.getBackgroundColor(p));
9266             contentView.setBoolean(R.id.status_bar_latest_event_content, "setIsCollapsed",
9267                     isCollapsed);
9268             contentView.setIcon(R.id.status_bar_latest_event_content, "setAvatarReplacement",
9269                     mBuilder.mN.mLargeIcon);
9270             contentView.setCharSequence(R.id.status_bar_latest_event_content, "setNameReplacement",
9271                     nameReplacement);
9272             contentView.setBoolean(R.id.status_bar_latest_event_content, "setIsOneToOne",
9273                     isOneToOne);
9274             contentView.setCharSequence(R.id.status_bar_latest_event_content,
9275                     "setConversationTitle", conversationTitle);
9276             if (isConversationLayout) {
9277                 contentView.setIcon(R.id.status_bar_latest_event_content,
9278                         "setShortcutIcon", mShortcutIcon);
9279                 contentView.setBoolean(R.id.status_bar_latest_event_content,
9280                         "setIsImportantConversation", isImportantConversation);
9281             }
9282             if (isHeaderless) {
9283                 // Collapsed legacy messaging style has a 1-line limit.
9284                 contentView.setInt(R.id.notification_messaging, "setMaxDisplayedLines", 1);
9285             }
9286             contentView.setIcon(R.id.status_bar_latest_event_content, "setLargeIcon",
9287                     largeIcon);
9288             contentView.setBundle(R.id.status_bar_latest_event_content, "setData",
9289                     mBuilder.mN.extras);
9290             return contentView;
9291         }
9292 
getKey(Person person)9293         private CharSequence getKey(Person person) {
9294             return person == null ? null
9295                     : person.getKey() == null ? person.getName() : person.getKey();
9296         }
9297 
getOtherPersonName()9298         private CharSequence getOtherPersonName() {
9299             CharSequence userKey = getKey(mUser);
9300             for (int i = mMessages.size() - 1; i >= 0; i--) {
9301                 Person sender = mMessages.get(i).getSenderPerson();
9302                 if (sender != null && !TextUtils.equals(userKey, getKey(sender))) {
9303                     return sender.getName();
9304                 }
9305             }
9306             return null;
9307         }
9308 
hasOnlyWhiteSpaceSenders()9309         private boolean hasOnlyWhiteSpaceSenders() {
9310             for (int i = 0; i < mMessages.size(); i++) {
9311                 Message m = mMessages.get(i);
9312                 Person sender = m.getSenderPerson();
9313                 if (sender != null && !isWhiteSpace(sender.getName())) {
9314                     return false;
9315                 }
9316             }
9317             return true;
9318         }
9319 
isWhiteSpace(CharSequence sender)9320         private boolean isWhiteSpace(CharSequence sender) {
9321             if (TextUtils.isEmpty(sender)) {
9322                 return true;
9323             }
9324             if (sender.toString().matches("^\\s*$")) {
9325                 return true;
9326             }
9327             // Let's check if we only have 0 whitespace chars. Some apps did this as a workaround
9328             // For the presentation that we had.
9329             for (int i = 0; i < sender.length(); i++) {
9330                 char c = sender.charAt(i);
9331                 if (c != '\u200B') {
9332                     return false;
9333                 }
9334             }
9335             return true;
9336         }
9337 
9338         /**
9339          * @hide
9340          */
9341         @Override
makeHeadsUpContentView(boolean increasedHeight)9342         public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
9343             return makeMessagingView(StandardTemplateParams.VIEW_TYPE_HEADS_UP);
9344         }
9345 
9346         /**
9347          * @hide
9348          */
9349         @Nullable
9350         @Override
makeCompactHeadsUpContentView()9351         public RemoteViews makeCompactHeadsUpContentView() {
9352             final boolean isConversationLayout = mConversationType != CONVERSATION_TYPE_LEGACY;
9353             Icon conversationIcon = null;
9354             Notification.Action remoteInputAction = null;
9355             if (isConversationLayout) {
9356 
9357                 conversationIcon = mShortcutIcon;
9358 
9359                 // conversation icon is m
9360                 // Extract the conversation icon for one to one conversations from
9361                 // the latest incoming message since
9362                 // fixTitleAndTextExtras also uses it as data source for title and text
9363                 if (conversationIcon == null && !mIsGroupConversation) {
9364                     final Message message = findLatestIncomingMessage();
9365                     if (message != null) {
9366                         final Person sender = message.mSender;
9367                         if (sender != null) {
9368                             conversationIcon = sender.getIcon();
9369                         }
9370                     }
9371                 }
9372 
9373                 if (Flags.compactHeadsUpNotificationReply()) {
9374                     // Get the first non-contextual inline reply action.
9375                     final List<Notification.Action> nonContextualActions =
9376                             mBuilder.getNonContextualActions();
9377                     for (int i = 0; i < nonContextualActions.size(); i++) {
9378                         final Notification.Action action = nonContextualActions.get(i);
9379                         if (mBuilder.hasValidRemoteInput(action)) {
9380                             remoteInputAction = action;
9381                             break;
9382                         }
9383                     }
9384                 }
9385             }
9386 
9387             final StandardTemplateParams p = mBuilder.mParams.reset()
9388                     .viewType(StandardTemplateParams.VIEW_TYPE_HEADS_UP)
9389                     .highlightExpander(isConversationLayout)
9390                     .fillTextsFrom(mBuilder)
9391                     .hideTime(true);
9392 
9393             fixTitleAndTextForCompactMessaging(p);
9394             TemplateBindResult bindResult = new TemplateBindResult();
9395 
9396             RemoteViews contentView = mBuilder.applyStandardTemplate(
9397                     mBuilder.getMessagingCompactHeadsUpLayoutResource(), p, bindResult);
9398             contentView.setViewVisibility(R.id.header_text_secondary_divider, View.GONE);
9399             contentView.setViewVisibility(R.id.header_text_divider, View.GONE);
9400             if (conversationIcon != null) {
9401                 contentView.setViewVisibility(R.id.icon, View.GONE);
9402                 contentView.setViewVisibility(R.id.conversation_face_pile, View.GONE);
9403                 contentView.setViewVisibility(R.id.conversation_icon, View.VISIBLE);
9404                 contentView.setBoolean(R.id.conversation_icon, "setApplyCircularCrop", true);
9405                 contentView.setImageViewIcon(R.id.conversation_icon, conversationIcon);
9406             } else if (mIsGroupConversation) {
9407                 contentView.setViewVisibility(R.id.icon, View.GONE);
9408                 contentView.setViewVisibility(R.id.conversation_icon, View.GONE);
9409                 contentView.setInt(R.id.status_bar_latest_event_content,
9410                         "setNotificationBackgroundColor", mBuilder.getBackgroundColor(p));
9411                 contentView.setInt(R.id.status_bar_latest_event_content, "setLayoutColor",
9412                         mBuilder.getSmallIconColor(p));
9413                 contentView.setBundle(R.id.status_bar_latest_event_content, "setGroupFacePile",
9414                         mBuilder.mN.extras);
9415             }
9416 
9417             if (remoteInputAction != null) {
9418                 contentView.setViewVisibility(R.id.reply_action_container, View.VISIBLE);
9419 
9420                 final RemoteViews inlineReplyButton =
9421                         mBuilder.generateActionButton(remoteInputAction, false, p);
9422                 // Clear the drawable
9423                 inlineReplyButton.setInt(R.id.action0, "setBackgroundResource", 0);
9424                 inlineReplyButton.setTextViewText(R.id.action0,
9425                         mBuilder.mContext.getString(R.string.notification_compact_heads_up_reply));
9426                 contentView.addView(R.id.reply_action_container, inlineReplyButton);
9427             } else {
9428                 contentView.setViewVisibility(R.id.reply_action_container, View.GONE);
9429             }
9430             return contentView;
9431         }
9432 
9433 
9434         /**
9435          * @hide
9436          */
9437         @Override
reduceImageSizes(Context context)9438         public void reduceImageSizes(Context context) {
9439             super.reduceImageSizes(context);
9440             Resources resources = context.getResources();
9441             boolean isLowRam = ActivityManager.isLowRamDeviceStatic();
9442             if (mShortcutIcon != null) {
9443                 int maxSize = resources.getDimensionPixelSize(
9444                         isLowRam ? R.dimen.notification_small_icon_size_low_ram
9445                                 : R.dimen.notification_small_icon_size);
9446                 mShortcutIcon.scaleDownIfNecessary(maxSize, maxSize);
9447             }
9448 
9449             int maxAvatarSize = resources.getDimensionPixelSize(
9450                     isLowRam ? R.dimen.notification_person_icon_max_size_low_ram
9451                             : R.dimen.notification_person_icon_max_size);
9452             if (mUser != null && mUser.getIcon() != null) {
9453                 mUser.getIcon().scaleDownIfNecessary(maxAvatarSize, maxAvatarSize);
9454             }
9455 
9456             reduceMessagesIconSizes(mMessages, maxAvatarSize);
9457             reduceMessagesIconSizes(mHistoricMessages, maxAvatarSize);
9458         }
9459 
9460         /**
9461          * @hide
9462          */
reduceMessagesIconSizes(@ullable List<Message> messages, int maxSize)9463         private static void reduceMessagesIconSizes(@Nullable List<Message> messages, int maxSize) {
9464             if (messages == null) {
9465                 return;
9466             }
9467 
9468             for (Message message : messages) {
9469                 Person sender = message.mSender;
9470                 if (sender != null) {
9471                     Icon icon = sender.getIcon();
9472                     if (icon != null) {
9473                         icon.scaleDownIfNecessary(maxSize, maxSize);
9474                     }
9475                 }
9476             }
9477         }
9478 
9479         /*
9480          * An object representing a simple message or piece of media within a mixed-media message.
9481          *
9482          * This object can only represent text or a single binary piece of media. For apps which
9483          * support mixed-media messages (e.g. text + image), multiple Messages should be used, one
9484          * to represent each piece of the message, and they should all be given the same timestamp.
9485          * For consistency, a text message should be added last of all Messages with the same
9486          * timestamp.
9487          */
9488         public static final class Message {
9489             /** @hide */
9490             public static final String KEY_TEXT = "text";
9491             static final String KEY_TIMESTAMP = "time";
9492             static final String KEY_SENDER = "sender";
9493             static final String KEY_SENDER_PERSON = "sender_person";
9494             static final String KEY_DATA_MIME_TYPE = "type";
9495             static final String KEY_DATA_URI= "uri";
9496             static final String KEY_EXTRAS_BUNDLE = "extras";
9497             static final String KEY_REMOTE_INPUT_HISTORY = "remote_input_history";
9498 
9499             private CharSequence mText;
9500             private final long mTimestamp;
9501             @Nullable
9502             private final Person mSender;
9503             /** True if this message was generated from the extra
9504              *  {@link Notification#EXTRA_REMOTE_INPUT_HISTORY_ITEMS}
9505              */
9506             private final boolean mRemoteInputHistory;
9507 
9508             private Bundle mExtras = new Bundle();
9509             private String mDataMimeType;
9510             private Uri mDataUri;
9511 
9512             /**
9513              * Constructor
9514              * @param text A {@link CharSequence} to be displayed as the message content
9515              * @param timestamp Time at which the message arrived
9516              * @param sender A {@link CharSequence} to be used for displaying the name of the
9517              * sender. Should be <code>null</code> for messages by the current user, in which case
9518              * the platform will insert {@link MessagingStyle#getUserDisplayName()}.
9519              * Should be unique amongst all individuals in the conversation, and should be
9520              * consistent during re-posts of the notification.
9521              *
9522              *  @deprecated use {@code Message(CharSequence, long, Person)}
9523              */
Message(CharSequence text, long timestamp, CharSequence sender)9524             public Message(CharSequence text, long timestamp, CharSequence sender){
9525                 this(text, timestamp, sender == null ? null
9526                         : new Person.Builder().setName(sender).build());
9527             }
9528 
9529             /**
9530              * Constructor
9531              * @param text A {@link CharSequence} to be displayed as the message content
9532              * @param timestamp Time at which the message arrived
9533              * @param sender The {@link Person} who sent the message.
9534              * Should be <code>null</code> for messages by the current user, in which case
9535              * the platform will insert the user set in {@code MessagingStyle(Person)}.
9536              * <p>
9537              * The person provided should contain an Icon, set with
9538              * {@link Person.Builder#setIcon(Icon)} and also have a name provided
9539              * with {@link Person.Builder#setName(CharSequence)}. If multiple users have the same
9540              * name, consider providing a key with {@link Person.Builder#setKey(String)} in order
9541              * to differentiate between the different users.
9542              * </p>
9543              */
Message(@onNull CharSequence text, long timestamp, @Nullable Person sender)9544             public Message(@NonNull CharSequence text, long timestamp, @Nullable Person sender) {
9545                 this(text, timestamp, sender, false /* remoteHistory */);
9546             }
9547 
9548             /**
9549              * Constructor
9550              * @param text A {@link CharSequence} to be displayed as the message content
9551              * @param timestamp Time at which the message arrived
9552              * @param sender The {@link Person} who sent the message.
9553              * Should be <code>null</code> for messages by the current user, in which case
9554              * the platform will insert the user set in {@code MessagingStyle(Person)}.
9555              * @param remoteInputHistory True if the messages was generated from the extra
9556              * {@link Notification#EXTRA_REMOTE_INPUT_HISTORY_ITEMS}.
9557              * <p>
9558              * The person provided should contain an Icon, set with
9559              * {@link Person.Builder#setIcon(Icon)} and also have a name provided
9560              * with {@link Person.Builder#setName(CharSequence)}. If multiple users have the same
9561              * name, consider providing a key with {@link Person.Builder#setKey(String)} in order
9562              * to differentiate between the different users.
9563              * </p>
9564              * @hide
9565              */
Message(@onNull CharSequence text, long timestamp, @Nullable Person sender, boolean remoteInputHistory)9566             public Message(@NonNull CharSequence text, long timestamp, @Nullable Person sender,
9567                     boolean remoteInputHistory) {
9568                 mText = safeCharSequence(text);
9569                 mTimestamp = timestamp;
9570                 mSender = sender;
9571                 mRemoteInputHistory = remoteInputHistory;
9572             }
9573 
9574             /**
9575              * Sets a binary blob of data and an associated MIME type for a message. In the case
9576              * where the platform or the UI state doesn't support the MIME type, the original text
9577              * provided in the constructor will be used.  When this data can be presented to the
9578              * user, the original text will only be used as accessibility text.
9579              * @param dataMimeType The MIME type of the content. See
9580              * {@link android.graphics.ImageDecoder#isMimeTypeSupported(String)} for a list of
9581              * supported image MIME types.
9582              * @param dataUri The uri containing the content whose type is given by the MIME type.
9583              * <p class="note">
9584              * Notification Listeners including the System UI need permission to access the
9585              * data the Uri points to. The recommended ways to do this are:
9586              * <ol>
9587              *   <li>Store the data in your own ContentProvider, making sure that other apps have
9588              *       the correct permission to access your provider. The preferred mechanism for
9589              *       providing access is to use per-URI permissions which are temporary and only
9590              *       grant access to the receiving application. An easy way to create a
9591              *       ContentProvider like this is to use the FileProvider helper class.</li>
9592              *   <li>Use the system MediaStore. The MediaStore is primarily aimed at video, audio
9593              *       and image MIME types, however beginning with Android 3.0 (API level 11) it can
9594              *       also store non-media types (see MediaStore.Files for more info). Files can be
9595              *       inserted into the MediaStore using scanFile() after which a content:// style
9596              *       Uri suitable for sharing is passed to the provided onScanCompleted() callback.
9597              *       Note that once added to the system MediaStore the content is accessible to any
9598              *       app on the device.</li>
9599              * </ol>
9600              * @return this object for method chaining
9601              */
setData(String dataMimeType, Uri dataUri)9602             public Message setData(String dataMimeType, Uri dataUri) {
9603                 mDataMimeType = dataMimeType;
9604                 mDataUri = dataUri;
9605                 return this;
9606             }
9607 
9608             /**
9609              * Strip styling or updates TextAppearance spans in message text.
9610              * @hide
9611              */
ensureColorContrastOrStripStyling(int backgroundColor)9612             public void ensureColorContrastOrStripStyling(int backgroundColor) {
9613                 if (Flags.cleanUpSpansAndNewLines()) {
9614                     mText = stripNonStyleSpans(mText);
9615                 } else {
9616                     ensureColorContrast(backgroundColor);
9617                 }
9618             }
9619 
stripNonStyleSpans(CharSequence text)9620             private CharSequence stripNonStyleSpans(CharSequence text) {
9621 
9622                 if (text instanceof Spanned) {
9623                     Spanned ss = (Spanned) text;
9624                     Object[] spans = ss.getSpans(0, ss.length(), Object.class);
9625                     SpannableStringBuilder builder = new SpannableStringBuilder(ss.toString());
9626                     for (Object span : spans) {
9627                         final Object resultSpan;
9628                         if (span instanceof StyleSpan
9629                                 || span instanceof StrikethroughSpan
9630                                 || span instanceof UnderlineSpan) {
9631                             resultSpan = span;
9632                         } else if (span instanceof TextAppearanceSpan) {
9633                             final TextAppearanceSpan originalSpan = (TextAppearanceSpan) span;
9634                             resultSpan = new TextAppearanceSpan(
9635                                     null,
9636                                     originalSpan.getTextStyle(),
9637                                     -1,
9638                                     null,
9639                                     null);
9640                         } else {
9641                             continue;
9642                         }
9643                         builder.setSpan(resultSpan, ss.getSpanStart(span), ss.getSpanEnd(span),
9644                                 ss.getSpanFlags(span));
9645                     }
9646                     return builder;
9647                 }
9648                 return text;
9649             }
9650 
9651             /**
9652              * Updates TextAppearance spans in the message text so it has sufficient contrast
9653              * against its background.
9654              * @hide
9655              */
ensureColorContrast(int backgroundColor)9656             public void ensureColorContrast(int backgroundColor) {
9657                 mText = ContrastColorUtil.ensureColorSpanContrast(mText, backgroundColor);
9658             }
9659 
9660             /**
9661              * Get the text to be used for this message, or the fallback text if a type and content
9662              * Uri have been set
9663              */
getText()9664             public CharSequence getText() {
9665                 return mText;
9666             }
9667 
9668             /**
9669              * Get the time at which this message arrived
9670              */
getTimestamp()9671             public long getTimestamp() {
9672                 return mTimestamp;
9673             }
9674 
9675             /**
9676              * Get the extras Bundle for this message.
9677              */
getExtras()9678             public Bundle getExtras() {
9679                 return mExtras;
9680             }
9681 
9682             /**
9683              * Get the text used to display the contact's name in the messaging experience
9684              *
9685              * @deprecated use {@link #getSenderPerson()}
9686              */
getSender()9687             public CharSequence getSender() {
9688                 return mSender == null ? null : mSender.getName();
9689             }
9690 
9691             /**
9692              * Get the sender associated with this message.
9693              */
9694             @Nullable
getSenderPerson()9695             public Person getSenderPerson() {
9696                 return mSender;
9697             }
9698 
9699             /**
9700              * Get the MIME type of the data pointed to by the Uri
9701              */
getDataMimeType()9702             public String getDataMimeType() {
9703                 return mDataMimeType;
9704             }
9705 
9706             /**
9707              * Get the Uri pointing to the content of the message. Can be null, in which case
9708              * {@see #getText()} is used.
9709              */
getDataUri()9710             public Uri getDataUri() {
9711                 return mDataUri;
9712             }
9713 
9714             /**
9715              * @return True if the message was generated from
9716              * {@link Notification#EXTRA_REMOTE_INPUT_HISTORY_ITEMS}.
9717              * @hide
9718              */
isRemoteInputHistory()9719             public boolean isRemoteInputHistory() {
9720                 return mRemoteInputHistory;
9721             }
9722 
9723             /**
9724              * Converts the message into a {@link Bundle}. To extract the message back,
9725              * check {@link #getMessageFromBundle()}
9726              * @hide
9727              */
9728             @NonNull
toBundle()9729             public Bundle toBundle() {
9730                 Bundle bundle = new Bundle();
9731                 if (mText != null) {
9732                     bundle.putCharSequence(KEY_TEXT, mText);
9733                 }
9734                 bundle.putLong(KEY_TIMESTAMP, mTimestamp);
9735                 if (mSender != null) {
9736                     // Legacy listeners need this
9737                     bundle.putCharSequence(KEY_SENDER, safeCharSequence(mSender.getName()));
9738                     bundle.putParcelable(KEY_SENDER_PERSON, mSender);
9739                 }
9740                 if (mDataMimeType != null) {
9741                     bundle.putString(KEY_DATA_MIME_TYPE, mDataMimeType);
9742                 }
9743                 if (mDataUri != null) {
9744                     bundle.putParcelable(KEY_DATA_URI, mDataUri);
9745                 }
9746                 if (mExtras != null) {
9747                     bundle.putBundle(KEY_EXTRAS_BUNDLE, mExtras);
9748                 }
9749                 if (mRemoteInputHistory) {
9750                     bundle.putBoolean(KEY_REMOTE_INPUT_HISTORY, mRemoteInputHistory);
9751                 }
9752                 return bundle;
9753             }
9754 
9755             /**
9756              * See {@link Notification#visitUris(Consumer)}.
9757              *
9758              * @hide
9759              */
visitUris(@onNull Consumer<Uri> visitor)9760             public void visitUris(@NonNull Consumer<Uri> visitor) {
9761                 visitor.accept(getDataUri());
9762                 if (mSender != null) {
9763                     mSender.visitUris(visitor);
9764                 }
9765             }
9766 
9767             /**
9768              * Returns a list of messages read from the given bundle list, e.g.
9769              * {@link #EXTRA_MESSAGES} or {@link #EXTRA_HISTORIC_MESSAGES}.
9770              */
9771             @NonNull
getMessagesFromBundleArray(@ullable Parcelable[] bundles)9772             public static List<Message> getMessagesFromBundleArray(@Nullable Parcelable[] bundles) {
9773                 if (bundles == null) {
9774                     return new ArrayList<>();
9775                 }
9776                 List<Message> messages = new ArrayList<>(bundles.length);
9777                 for (int i = 0; i < bundles.length; i++) {
9778                     if (bundles[i] instanceof Bundle) {
9779                         Message message = getMessageFromBundle((Bundle)bundles[i]);
9780                         if (message != null) {
9781                             messages.add(message);
9782                         }
9783                     }
9784                 }
9785                 return messages;
9786             }
9787 
9788             /**
9789              * Returns the message that is stored in the bundle (e.g. one of the values in the lists
9790              * in {@link #EXTRA_MESSAGES} or {@link #EXTRA_HISTORIC_MESSAGES}) or null if the
9791              * message couldn't be resolved.
9792              * @hide
9793              */
9794             @Nullable
getMessageFromBundle(@onNull Bundle bundle)9795             public static Message getMessageFromBundle(@NonNull Bundle bundle) {
9796                 try {
9797                     if (!bundle.containsKey(KEY_TEXT) || !bundle.containsKey(KEY_TIMESTAMP)) {
9798                         return null;
9799                     } else {
9800 
9801                         Person senderPerson = bundle.getParcelable(KEY_SENDER_PERSON, Person.class);
9802                         if (senderPerson == null) {
9803                             // Legacy apps that use compat don't actually provide the sender objects
9804                             // We need to fix the compat version to provide people / use
9805                             // the native api instead
9806                             CharSequence senderName = bundle.getCharSequence(KEY_SENDER);
9807                             if (senderName != null) {
9808                                 senderPerson = new Person.Builder().setName(senderName).build();
9809                             }
9810                         }
9811                         Message message = new Message(bundle.getCharSequence(KEY_TEXT),
9812                                 bundle.getLong(KEY_TIMESTAMP),
9813                                 senderPerson,
9814                                 bundle.getBoolean(KEY_REMOTE_INPUT_HISTORY, false));
9815                         if (bundle.containsKey(KEY_DATA_MIME_TYPE) &&
9816                                 bundle.containsKey(KEY_DATA_URI)) {
9817                             message.setData(bundle.getString(KEY_DATA_MIME_TYPE),
9818                                     bundle.getParcelable(KEY_DATA_URI, Uri.class));
9819                         }
9820                         if (bundle.containsKey(KEY_EXTRAS_BUNDLE)) {
9821                             message.getExtras().putAll(bundle.getBundle(KEY_EXTRAS_BUNDLE));
9822                         }
9823                         return message;
9824                     }
9825                 } catch (ClassCastException e) {
9826                     return null;
9827                 }
9828             }
9829         }
9830     }
9831 
9832     /**
9833      * Helper class for generating large-format notifications that include a list of (up to 5) strings.
9834      *
9835      * Here's how you'd set the <code>InboxStyle</code> on a notification:
9836      * <pre class="prettyprint">
9837      * Notification notif = new Notification.Builder(mContext)
9838      *     .setContentTitle(&quot;5 New mails from &quot; + sender.toString())
9839      *     .setContentText(subject)
9840      *     .setSmallIcon(R.drawable.new_mail)
9841      *     .setLargeIcon(aBitmap)
9842      *     .setStyle(new Notification.InboxStyle()
9843      *         .addLine(str1)
9844      *         .addLine(str2)
9845      *         .setContentTitle(&quot;&quot;)
9846      *         .setSummaryText(&quot;+3 more&quot;))
9847      *     .build();
9848      * </pre>
9849      *
9850      * @see Notification#bigContentView
9851      */
9852     public static class InboxStyle extends Style {
9853 
9854         /**
9855          * The number of lines of remote input history allowed until we start reducing lines.
9856          */
9857         private static final int NUMBER_OF_HISTORY_ALLOWED_UNTIL_REDUCTION = 1;
9858         private ArrayList<CharSequence> mTexts = new ArrayList<CharSequence>(5);
9859 
InboxStyle()9860         public InboxStyle() {
9861         }
9862 
9863         /**
9864          * @deprecated use {@code InboxStyle()}.
9865          */
9866         @Deprecated
InboxStyle(Builder builder)9867         public InboxStyle(Builder builder) {
9868             setBuilder(builder);
9869         }
9870 
9871         /**
9872          * Overrides ContentTitle in the big form of the template.
9873          * This defaults to the value passed to setContentTitle().
9874          */
setBigContentTitle(CharSequence title)9875         public InboxStyle setBigContentTitle(CharSequence title) {
9876             internalSetBigContentTitle(safeCharSequence(title));
9877             return this;
9878         }
9879 
9880         /**
9881          * Set the first line of text after the detail section in the big form of the template.
9882          */
setSummaryText(CharSequence cs)9883         public InboxStyle setSummaryText(CharSequence cs) {
9884             internalSetSummaryText(safeCharSequence(cs));
9885             return this;
9886         }
9887 
9888         /**
9889          * Append a line to the digest section of the Inbox notification.
9890          */
addLine(CharSequence cs)9891         public InboxStyle addLine(CharSequence cs) {
9892             mTexts.add(safeCharSequence(cs));
9893             return this;
9894         }
9895 
9896         /**
9897          * @hide
9898          */
getLines()9899         public ArrayList<CharSequence> getLines() {
9900             return mTexts;
9901         }
9902 
9903         /**
9904          * @hide
9905          */
addExtras(Bundle extras)9906         public void addExtras(Bundle extras) {
9907             super.addExtras(extras);
9908 
9909             CharSequence[] a = new CharSequence[mTexts.size()];
9910             extras.putCharSequenceArray(EXTRA_TEXT_LINES, mTexts.toArray(a));
9911         }
9912 
9913         /**
9914          * @hide
9915          */
9916         @Override
restoreFromExtras(Bundle extras)9917         protected void restoreFromExtras(Bundle extras) {
9918             super.restoreFromExtras(extras);
9919 
9920             mTexts.clear();
9921             if (extras.containsKey(EXTRA_TEXT_LINES)) {
9922                 Collections.addAll(mTexts, extras.getCharSequenceArray(EXTRA_TEXT_LINES));
9923             }
9924         }
9925 
9926         /**
9927          * @hide
9928          */
makeBigContentView()9929         public RemoteViews makeBigContentView() {
9930             StandardTemplateParams p = mBuilder.mParams.reset()
9931                     .viewType(StandardTemplateParams.VIEW_TYPE_BIG)
9932                     .fillTextsFrom(mBuilder).text(null);
9933             TemplateBindResult result = new TemplateBindResult();
9934             RemoteViews contentView = getStandardView(mBuilder.getInboxLayoutResource(), p, result);
9935 
9936             int[] rowIds = {R.id.inbox_text0, R.id.inbox_text1, R.id.inbox_text2, R.id.inbox_text3,
9937                     R.id.inbox_text4, R.id.inbox_text5, R.id.inbox_text6};
9938 
9939             // Make sure all rows are gone in case we reuse a view.
9940             for (int rowId : rowIds) {
9941                 contentView.setViewVisibility(rowId, View.GONE);
9942             }
9943 
9944             int i=0;
9945             int topPadding = mBuilder.mContext.getResources().getDimensionPixelSize(
9946                     R.dimen.notification_inbox_item_top_padding);
9947             boolean first = true;
9948             int onlyViewId = 0;
9949             int maxRows = rowIds.length;
9950             if (mBuilder.mActions.size() > 0) {
9951                 maxRows--;
9952             }
9953             RemoteInputHistoryItem[] remoteInputHistory = getParcelableArrayFromBundle(
9954                     mBuilder.mN.extras, EXTRA_REMOTE_INPUT_HISTORY_ITEMS,
9955                     RemoteInputHistoryItem.class);
9956             if (remoteInputHistory != null
9957                     && remoteInputHistory.length > NUMBER_OF_HISTORY_ALLOWED_UNTIL_REDUCTION) {
9958                 // Let's remove some messages to make room for the remote input history.
9959                 // 1 is always able to fit, but let's remove them if they are 2 or 3
9960                 int numRemoteInputs = Math.min(remoteInputHistory.length,
9961                         MAX_REMOTE_INPUT_HISTORY_LINES);
9962                 int totalNumRows = mTexts.size() + numRemoteInputs
9963                         - NUMBER_OF_HISTORY_ALLOWED_UNTIL_REDUCTION;
9964                 if (totalNumRows > maxRows) {
9965                     int overflow = totalNumRows - maxRows;
9966                     if (mTexts.size() > maxRows) {
9967                         // Heuristic: if the Texts don't fit anyway, we'll rather drop the last
9968                         // few messages, even with the remote input
9969                         maxRows -= overflow;
9970                     } else  {
9971                         // otherwise we drop the first messages
9972                         i = overflow;
9973                     }
9974                 }
9975             }
9976             while (i < mTexts.size() && i < maxRows) {
9977                 CharSequence str = mTexts.get(i);
9978                 if (!TextUtils.isEmpty(str)) {
9979                     contentView.setViewVisibility(rowIds[i], View.VISIBLE);
9980                     contentView.setTextViewText(rowIds[i],
9981                             mBuilder.ensureColorSpanContrastOrStripStyling(
9982                                     mBuilder.processLegacyText(str), p));
9983                     mBuilder.setTextViewColorSecondary(contentView, rowIds[i], p);
9984                     contentView.setViewPadding(rowIds[i], 0, topPadding, 0, 0);
9985                     if (first) {
9986                         onlyViewId = rowIds[i];
9987                     } else {
9988                         onlyViewId = 0;
9989                     }
9990                     first = false;
9991                 }
9992                 i++;
9993             }
9994             if (onlyViewId != 0) {
9995                 // We only have 1 entry, lets make it look like the normal Text of a Bigtext
9996                 topPadding = mBuilder.mContext.getResources().getDimensionPixelSize(
9997                         R.dimen.notification_text_margin_top);
9998                 contentView.setViewPadding(onlyViewId, 0, topPadding, 0, 0);
9999             }
10000 
10001             return contentView;
10002         }
10003 
10004         /**
10005          * @hide
10006          */
10007         @Override
areNotificationsVisiblyDifferent(Style other)10008         public boolean areNotificationsVisiblyDifferent(Style other) {
10009             if (other == null || getClass() != other.getClass()) {
10010                 return true;
10011             }
10012             InboxStyle newS = (InboxStyle) other;
10013 
10014             final ArrayList<CharSequence> myLines = getLines();
10015             final ArrayList<CharSequence> newLines = newS.getLines();
10016             final int n = myLines.size();
10017             if (n != newLines.size()) {
10018                 return true;
10019             }
10020 
10021             for (int i = 0; i < n; i++) {
10022                 if (!Objects.equals(
10023                         String.valueOf(myLines.get(i)),
10024                         String.valueOf(newLines.get(i)))) {
10025                     return true;
10026                 }
10027             }
10028             return false;
10029         }
10030     }
10031 
10032     /**
10033      * Notification style for media playback notifications.
10034      *
10035      * In the expanded form, {@link Notification#bigContentView}, up to 5
10036      * {@link Notification.Action}s specified with
10037      * {@link Notification.Builder#addAction(Action) addAction} will be
10038      * shown as icon-only pushbuttons, suitable for transport controls. The Bitmap given to
10039      * {@link Notification.Builder#setLargeIcon(android.graphics.Bitmap) setLargeIcon()} will be
10040      * treated as album artwork.
10041      * <p>
10042      * Unlike the other styles provided here, MediaStyle can also modify the standard-size
10043      * {@link Notification#contentView}; by providing action indices to
10044      * {@link #setShowActionsInCompactView(int...)} you can promote up to 3 actions to be displayed
10045      * in the standard view alongside the usual content.
10046      * <p>
10047      * Notifications created with MediaStyle will have their category set to
10048      * {@link Notification#CATEGORY_TRANSPORT CATEGORY_TRANSPORT} unless you set a different
10049      * category using {@link Notification.Builder#setCategory(String) setCategory()}.
10050      * <p>
10051      * Finally, if you attach a {@link android.media.session.MediaSession.Token} using
10052      * {@link android.app.Notification.MediaStyle#setMediaSession(MediaSession.Token)},
10053      * the System UI can identify this as a notification representing an active media session
10054      * and respond accordingly (by showing album artwork in the lockscreen, for example).
10055      *
10056      * <p>
10057      * Starting at {@link android.os.Build.VERSION_CODES#O Android O} any notification that has a
10058      * media session attached with {@link #setMediaSession(MediaSession.Token)} will be colorized.
10059      * You can opt-out of this behavior by using {@link Notification.Builder#setColorized(boolean)}.
10060      * <p>
10061      *
10062      * <p>
10063      * Starting at {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM Android V} the
10064      * {@link Notification#FLAG_NO_CLEAR NO_CLEAR flag} will be set for valid MediaStyle
10065      * notifications.
10066      * <p>
10067      *
10068      * To use this style with your Notification, feed it to
10069      * {@link Notification.Builder#setStyle(android.app.Notification.Style)} like so:
10070      * <pre class="prettyprint">
10071      * Notification noti = new Notification.Builder()
10072      *     .setSmallIcon(R.drawable.ic_stat_player)
10073      *     .setContentTitle(&quot;Track title&quot;)
10074      *     .setContentText(&quot;Artist - Album&quot;)
10075      *     .setLargeIcon(albumArtBitmap))
10076      *     .setStyle(<b>new Notification.MediaStyle()</b>
10077      *         .setMediaSession(mySession))
10078      *     .build();
10079      * </pre>
10080      *
10081      * @see Notification#bigContentView
10082      * @see Notification.Builder#setColorized(boolean)
10083      */
10084     public static class MediaStyle extends Style {
10085         // Changing max media buttons requires also changing templates
10086         // (notification_template_material_media and notification_template_material_big_media).
10087         static final int MAX_MEDIA_BUTTONS_IN_COMPACT = 3;
10088         static final int MAX_MEDIA_BUTTONS = 5;
10089         @IdRes private static final int[] MEDIA_BUTTON_IDS = {
10090                 R.id.action0,
10091                 R.id.action1,
10092                 R.id.action2,
10093                 R.id.action3,
10094                 R.id.action4,
10095         };
10096 
10097         private int[] mActionsToShowInCompact = null;
10098         private MediaSession.Token mToken;
10099         private CharSequence mDeviceName;
10100         private int mDeviceIcon;
10101         private PendingIntent mDeviceIntent;
10102 
MediaStyle()10103         public MediaStyle() {
10104         }
10105 
10106         /**
10107          * @deprecated use {@code MediaStyle()}.
10108          */
10109         @Deprecated
MediaStyle(Builder builder)10110         public MediaStyle(Builder builder) {
10111             setBuilder(builder);
10112         }
10113 
10114         /**
10115          * Request up to 3 actions (by index in the order of addition) to be shown in the compact
10116          * notification view.
10117          *
10118          * @param actions the indices of the actions to show in the compact notification view
10119          */
setShowActionsInCompactView(int...actions)10120         public MediaStyle setShowActionsInCompactView(int...actions) {
10121             mActionsToShowInCompact = actions;
10122             return this;
10123         }
10124 
10125         /**
10126          * Attach a {@link android.media.session.MediaSession.Token} to this Notification
10127          * to provide additional playback information and control to the SystemUI.
10128          */
setMediaSession(MediaSession.Token token)10129         public MediaStyle setMediaSession(MediaSession.Token token) {
10130             mToken = token;
10131             return this;
10132         }
10133 
10134         /**
10135          * For media notifications associated with playback on a remote device, provide device
10136          * information that will replace the default values for the output switcher chip on the
10137          * media control, as well as an intent to use when the output switcher chip is tapped,
10138          * on devices where this is supported.
10139          * <p>
10140          * This method is intended for system applications to provide information and/or
10141          * functionality that would otherwise be unavailable to the default output switcher because
10142          * the media originated on a remote device.
10143          *
10144          * @param deviceName The name of the remote device to display
10145          * @param iconResource Icon resource representing the device
10146          * @param chipIntent PendingIntent to send when the output switcher is tapped. May be
10147          *                   {@code null}, in which case the output switcher will be disabled.
10148          *                   This intent should open an Activity or it will be ignored.
10149          * @return MediaStyle
10150          */
10151         @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL)
10152         @NonNull
setRemotePlaybackInfo(@onNull CharSequence deviceName, @DrawableRes int iconResource, @Nullable PendingIntent chipIntent)10153         public MediaStyle setRemotePlaybackInfo(@NonNull CharSequence deviceName,
10154                 @DrawableRes int iconResource, @Nullable PendingIntent chipIntent) {
10155             mDeviceName = deviceName;
10156             mDeviceIcon = iconResource;
10157             mDeviceIntent = chipIntent;
10158             return this;
10159         }
10160 
10161         /**
10162          * @hide
10163          */
10164         @Override
10165         @UnsupportedAppUsage
buildStyled(Notification wip)10166         public Notification buildStyled(Notification wip) {
10167             super.buildStyled(wip);
10168             if (wip.category == null) {
10169                 wip.category = Notification.CATEGORY_TRANSPORT;
10170             }
10171             return wip;
10172         }
10173 
10174         /**
10175          * @hide
10176          */
10177         @Override
makeContentView(boolean increasedHeight)10178         public RemoteViews makeContentView(boolean increasedHeight) {
10179             return makeMediaContentView(null /* customContent */);
10180         }
10181 
10182         /**
10183          * @hide
10184          */
10185         @Override
makeBigContentView()10186         public RemoteViews makeBigContentView() {
10187             return makeMediaBigContentView(null /* customContent */);
10188         }
10189 
10190         /**
10191          * @hide
10192          */
10193         @Override
makeHeadsUpContentView(boolean increasedHeight)10194         public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
10195             return makeMediaContentView(null /* customContent */);
10196         }
10197 
10198         /** @hide */
10199         @Override
addExtras(Bundle extras)10200         public void addExtras(Bundle extras) {
10201             super.addExtras(extras);
10202 
10203             if (mToken != null) {
10204                 extras.putParcelable(EXTRA_MEDIA_SESSION, mToken);
10205             }
10206             if (mActionsToShowInCompact != null) {
10207                 extras.putIntArray(EXTRA_COMPACT_ACTIONS, mActionsToShowInCompact);
10208             }
10209             if (mDeviceName != null) {
10210                 extras.putCharSequence(EXTRA_MEDIA_REMOTE_DEVICE, mDeviceName);
10211             }
10212             if (mDeviceIcon > 0) {
10213                 extras.putInt(EXTRA_MEDIA_REMOTE_ICON, mDeviceIcon);
10214             }
10215             if (mDeviceIntent != null) {
10216                 extras.putParcelable(EXTRA_MEDIA_REMOTE_INTENT, mDeviceIntent);
10217             }
10218         }
10219 
10220         /**
10221          * @hide
10222          */
10223         @Override
restoreFromExtras(Bundle extras)10224         protected void restoreFromExtras(Bundle extras) {
10225             super.restoreFromExtras(extras);
10226 
10227             if (extras.containsKey(EXTRA_MEDIA_SESSION)) {
10228                 mToken = extras.getParcelable(EXTRA_MEDIA_SESSION, MediaSession.Token.class);
10229             }
10230             if (extras.containsKey(EXTRA_COMPACT_ACTIONS)) {
10231                 mActionsToShowInCompact = extras.getIntArray(EXTRA_COMPACT_ACTIONS);
10232             }
10233             if (extras.containsKey(EXTRA_MEDIA_REMOTE_DEVICE)) {
10234                 mDeviceName = extras.getCharSequence(EXTRA_MEDIA_REMOTE_DEVICE);
10235             }
10236             if (extras.containsKey(EXTRA_MEDIA_REMOTE_ICON)) {
10237                 mDeviceIcon = extras.getInt(EXTRA_MEDIA_REMOTE_ICON);
10238             }
10239             if (extras.containsKey(EXTRA_MEDIA_REMOTE_INTENT)) {
10240                 mDeviceIntent = extras.getParcelable(
10241                         EXTRA_MEDIA_REMOTE_INTENT, PendingIntent.class);
10242             }
10243         }
10244 
10245         /**
10246          * @hide
10247          */
10248         @Override
areNotificationsVisiblyDifferent(Style other)10249         public boolean areNotificationsVisiblyDifferent(Style other) {
10250             if (other == null || getClass() != other.getClass()) {
10251                 return true;
10252             }
10253             // All fields to compare are on the Notification object
10254             return false;
10255         }
10256 
bindMediaActionButton(RemoteViews container, @IdRes int buttonId, Action action, StandardTemplateParams p)10257         private void bindMediaActionButton(RemoteViews container, @IdRes int buttonId,
10258                 Action action, StandardTemplateParams p) {
10259             final boolean tombstone = (action.actionIntent == null);
10260             container.setViewVisibility(buttonId, View.VISIBLE);
10261             container.setImageViewIcon(buttonId, action.getIcon());
10262 
10263             // If the action buttons should not be tinted, then just use the default
10264             // notification color. Otherwise, just use the passed-in color.
10265             int tintColor = mBuilder.getStandardActionColor(p);
10266 
10267             container.setDrawableTint(buttonId, false, tintColor,
10268                     PorterDuff.Mode.SRC_ATOP);
10269 
10270             int rippleAlpha = mBuilder.getColors(p).getRippleAlpha();
10271             int rippleColor = Color.argb(rippleAlpha, Color.red(tintColor), Color.green(tintColor),
10272                     Color.blue(tintColor));
10273             container.setRippleDrawableColor(buttonId, ColorStateList.valueOf(rippleColor));
10274 
10275             if (!tombstone) {
10276                 container.setOnClickPendingIntent(buttonId, action.actionIntent);
10277             }
10278             container.setContentDescription(buttonId, action.title);
10279         }
10280 
10281         /** @hide */
makeMediaContentView(@ullable RemoteViews customContent)10282         protected RemoteViews makeMediaContentView(@Nullable RemoteViews customContent) {
10283             final int numActions = mBuilder.mActions.size();
10284             final int numActionsToShow = Math.min(mActionsToShowInCompact == null
10285                     ? 0 : mActionsToShowInCompact.length, MAX_MEDIA_BUTTONS_IN_COMPACT);
10286             if (numActionsToShow > numActions) {
10287                 throw new IllegalArgumentException(String.format(
10288                         "setShowActionsInCompactView: action %d out of bounds (max %d)",
10289                         numActions, numActions - 1));
10290             }
10291 
10292             StandardTemplateParams p = mBuilder.mParams.reset()
10293                     .viewType(StandardTemplateParams.VIEW_TYPE_NORMAL)
10294                     .hideTime(numActionsToShow > 1)       // hide if actions wider than a right icon
10295                     .hideSubText(numActionsToShow > 1)    // hide if actions wider than a right icon
10296                     .hideLeftIcon(false)                  // allow large icon on left when grouped
10297                     .hideRightIcon(numActionsToShow > 0)  // right icon or actions; not both
10298                     .hideProgress(true)
10299                     .fillTextsFrom(mBuilder);
10300             TemplateBindResult result = new TemplateBindResult();
10301             RemoteViews template = mBuilder.applyStandardTemplate(
10302                     R.layout.notification_template_material_media, p,
10303                     null /* result */);
10304 
10305             for (int i = 0; i < MAX_MEDIA_BUTTONS_IN_COMPACT; i++) {
10306                 if (i < numActionsToShow) {
10307                     final Action action = mBuilder.mActions.get(mActionsToShowInCompact[i]);
10308                     bindMediaActionButton(template, MEDIA_BUTTON_IDS[i], action, p);
10309                 } else {
10310                     template.setViewVisibility(MEDIA_BUTTON_IDS[i], View.GONE);
10311                 }
10312             }
10313             // Prevent a swooping expand animation when there are no actions
10314             boolean hasActions = numActionsToShow != 0;
10315             template.setViewVisibility(R.id.media_actions, hasActions ? View.VISIBLE : View.GONE);
10316 
10317             // Add custom view if provided by subclass.
10318             buildCustomContentIntoTemplate(mBuilder.mContext, template, customContent, p, result);
10319             return template;
10320         }
10321 
10322         /** @hide */
makeMediaBigContentView(@ullable RemoteViews customContent)10323         protected RemoteViews makeMediaBigContentView(@Nullable RemoteViews customContent) {
10324             final int actionCount = Math.min(mBuilder.mActions.size(), MAX_MEDIA_BUTTONS);
10325             StandardTemplateParams p = mBuilder.mParams.reset()
10326                     .viewType(StandardTemplateParams.VIEW_TYPE_BIG)
10327                     .hideProgress(true)
10328                     .fillTextsFrom(mBuilder);
10329             TemplateBindResult result = new TemplateBindResult();
10330             RemoteViews template = mBuilder.applyStandardTemplate(
10331                     R.layout.notification_template_material_big_media, p , result);
10332 
10333             for (int i = 0; i < MAX_MEDIA_BUTTONS; i++) {
10334                 if (i < actionCount) {
10335                     bindMediaActionButton(template,
10336                             MEDIA_BUTTON_IDS[i], mBuilder.mActions.get(i), p);
10337                 } else {
10338                     template.setViewVisibility(MEDIA_BUTTON_IDS[i], View.GONE);
10339                 }
10340             }
10341             buildCustomContentIntoTemplate(mBuilder.mContext, template, customContent, p, result);
10342             return template;
10343         }
10344     }
10345 
10346     /**
10347      * Helper class for generating large-format notifications that include a large image attachment.
10348      *
10349      * Here's how you'd set the <code>CallStyle</code> on a notification:
10350      * <pre class="prettyprint">
10351      * Notification notif = new Notification.Builder(mContext)
10352      *     .setSmallIcon(R.drawable.new_post)
10353      *     .setStyle(Notification.CallStyle.forIncomingCall(caller, declineIntent, answerIntent))
10354      *     .build();
10355      * </pre>
10356      */
10357     public static class CallStyle extends Style {
10358         /**
10359          * @hide
10360          */
10361         public static final boolean DEBUG_NEW_ACTION_LAYOUT = true;
10362 
10363         /**
10364          * @hide
10365          */
10366         @Retention(RetentionPolicy.SOURCE)
10367         @IntDef({
10368                 CALL_TYPE_UNKNOWN,
10369                 CALL_TYPE_INCOMING,
10370                 CALL_TYPE_ONGOING,
10371                 CALL_TYPE_SCREENING
10372         })
10373         public @interface CallType {};
10374 
10375         /**
10376          * Unknown call type.
10377          *
10378          * See {@link #EXTRA_CALL_TYPE}.
10379          */
10380         public static final int CALL_TYPE_UNKNOWN = 0;
10381 
10382         /**
10383          *  Call type for incoming calls.
10384          *
10385          *  See {@link #EXTRA_CALL_TYPE}.
10386          */
10387         public static final int CALL_TYPE_INCOMING = 1;
10388         /**
10389          * Call type for ongoing calls.
10390          *
10391          * See {@link #EXTRA_CALL_TYPE}.
10392          */
10393         public static final int CALL_TYPE_ONGOING = 2;
10394         /**
10395          * Call type for calls that are being screened.
10396          *
10397          * See {@link #EXTRA_CALL_TYPE}.
10398          */
10399         public static final int CALL_TYPE_SCREENING = 3;
10400 
10401         /**
10402          * This is a key used privately on the action.extras to give spacing priority
10403          * to the required call actions
10404          */
10405         private static final String KEY_ACTION_PRIORITY = "key_action_priority";
10406 
10407         private int mCallType;
10408         private Person mPerson;
10409         private PendingIntent mAnswerIntent;
10410         private PendingIntent mDeclineIntent;
10411         private PendingIntent mHangUpIntent;
10412         private boolean mIsVideo;
10413         private Integer mAnswerButtonColor;
10414         private Integer mDeclineButtonColor;
10415         private Icon mVerificationIcon;
10416         private CharSequence mVerificationText;
10417 
CallStyle()10418         CallStyle() {
10419         }
10420 
10421         /**
10422          * Create a CallStyle for an incoming call.
10423          * This notification will have a decline and an answer action, will allow a single
10424          * custom {@link Builder#addAction(Action) action}, and will have a default
10425          * {@link Builder#setContentText(CharSequence) content text} for an incoming call.
10426          *
10427          * @param person        The person displayed as the caller.
10428          *                      The person also needs to have a non-empty name associated with it.
10429          * @param declineIntent The intent to be sent when the user taps the decline action
10430          * @param answerIntent  The intent to be sent when the user taps the answer action
10431          */
10432         @NonNull
forIncomingCall(@onNull Person person, @NonNull PendingIntent declineIntent, @NonNull PendingIntent answerIntent)10433         public static CallStyle forIncomingCall(@NonNull Person person,
10434                 @NonNull PendingIntent declineIntent, @NonNull PendingIntent answerIntent) {
10435             return new CallStyle(CALL_TYPE_INCOMING, person,
10436                     null /* hangUpIntent */,
10437                     requireNonNull(declineIntent, "declineIntent is required"),
10438                     requireNonNull(answerIntent, "answerIntent is required")
10439             );
10440         }
10441 
10442         /**
10443          * Create a CallStyle for an ongoing call.
10444          * This notification will have a hang up action, will allow up to two
10445          * custom {@link Builder#addAction(Action) actions}, and will have a default
10446          * {@link Builder#setContentText(CharSequence) content text} for an ongoing call.
10447          *
10448          * @param person       The person displayed as being on the other end of the call.
10449          *                     The person also needs to have a non-empty name associated with it.
10450          * @param hangUpIntent The intent to be sent when the user taps the hang up action
10451          */
10452         @NonNull
forOngoingCall(@onNull Person person, @NonNull PendingIntent hangUpIntent)10453         public static CallStyle forOngoingCall(@NonNull Person person,
10454                 @NonNull PendingIntent hangUpIntent) {
10455             return new CallStyle(CALL_TYPE_ONGOING, person,
10456                     requireNonNull(hangUpIntent, "hangUpIntent is required"),
10457                     null /* declineIntent */,
10458                     null /* answerIntent */
10459             );
10460         }
10461 
10462         /**
10463          * Create a CallStyle for a call that is being screened.
10464          * This notification will have a hang up and an answer action, will allow a single
10465          * custom {@link Builder#addAction(Action) action}, and will have a default
10466          * {@link Builder#setContentText(CharSequence) content text} for a call that is being
10467          * screened.
10468          *
10469          * @param person       The person displayed as the caller.
10470          *                     The person also needs to have a non-empty name associated with it.
10471          * @param hangUpIntent The intent to be sent when the user taps the hang up action
10472          * @param answerIntent The intent to be sent when the user taps the answer action
10473          */
10474         @NonNull
forScreeningCall(@onNull Person person, @NonNull PendingIntent hangUpIntent, @NonNull PendingIntent answerIntent)10475         public static CallStyle forScreeningCall(@NonNull Person person,
10476                 @NonNull PendingIntent hangUpIntent, @NonNull PendingIntent answerIntent) {
10477             return new CallStyle(CALL_TYPE_SCREENING, person,
10478                     requireNonNull(hangUpIntent, "hangUpIntent is required"),
10479                     null /* declineIntent */,
10480                     requireNonNull(answerIntent, "answerIntent is required")
10481             );
10482         }
10483 
10484         /**
10485          * @param callType The type of the call
10486          * @param person The person displayed for the incoming call.
10487          *             The user also needs to have a non-empty name associated with it.
10488          * @param hangUpIntent The intent to be sent when the user taps the hang up action
10489          * @param declineIntent The intent to be sent when the user taps the decline action
10490          * @param answerIntent The intent to be sent when the user taps the answer action
10491          */
CallStyle(@allType int callType, @NonNull Person person, @Nullable PendingIntent hangUpIntent, @Nullable PendingIntent declineIntent, @Nullable PendingIntent answerIntent)10492         private CallStyle(@CallType int callType, @NonNull Person person,
10493                 @Nullable PendingIntent hangUpIntent, @Nullable PendingIntent declineIntent,
10494                 @Nullable PendingIntent answerIntent) {
10495             if (person == null || TextUtils.isEmpty(person.getName())) {
10496                 throw new IllegalArgumentException("person must have a non-empty a name");
10497             }
10498             mCallType = callType;
10499             mPerson = person;
10500             mAnswerIntent = answerIntent;
10501             mDeclineIntent = declineIntent;
10502             mHangUpIntent = hangUpIntent;
10503         }
10504 
10505         /**
10506          * Sets whether the call is a video call, which may affect the icons or text used on the
10507          * required action buttons.
10508          */
10509         @NonNull
setIsVideo(boolean isVideo)10510         public CallStyle setIsVideo(boolean isVideo) {
10511             mIsVideo = isVideo;
10512             return this;
10513         }
10514 
10515         /**
10516          * Optional icon to be displayed with {@link #setVerificationText(CharSequence) text}
10517          * as a verification status of the caller.
10518          */
10519         @NonNull
setVerificationIcon(@ullable Icon verificationIcon)10520         public CallStyle setVerificationIcon(@Nullable Icon verificationIcon) {
10521             mVerificationIcon = verificationIcon;
10522             return this;
10523         }
10524 
10525         /**
10526          * Optional text to be displayed with an {@link #setVerificationIcon(Icon) icon}
10527          * as a verification status of the caller.
10528          */
10529         @NonNull
setVerificationText(@ullable CharSequence verificationText)10530         public CallStyle setVerificationText(@Nullable CharSequence verificationText) {
10531             mVerificationText = safeCharSequence(verificationText);
10532             return this;
10533         }
10534 
10535         /**
10536          * Optional color to be used as a hint for the Answer action button's color.
10537          * The system may change this color to ensure sufficient contrast with the background.
10538          * The system may choose to disregard this hint if the notification is not colorized.
10539          */
10540         @NonNull
setAnswerButtonColorHint(@olorInt int color)10541         public CallStyle setAnswerButtonColorHint(@ColorInt int color) {
10542             mAnswerButtonColor = color;
10543             return this;
10544         }
10545 
10546         /**
10547          * Optional color to be used as a hint for the Decline or Hang Up action button's color.
10548          * The system may change this color to ensure sufficient contrast with the background.
10549          * The system may choose to disregard this hint if the notification is not colorized.
10550          */
10551         @NonNull
setDeclineButtonColorHint(@olorInt int color)10552         public CallStyle setDeclineButtonColorHint(@ColorInt int color) {
10553             mDeclineButtonColor = color;
10554             return this;
10555         }
10556 
10557         /** @hide */
10558         @Override
buildStyled(Notification wip)10559         public Notification buildStyled(Notification wip) {
10560             wip = super.buildStyled(wip);
10561             // ensure that the actions in the builder and notification are corrected.
10562             mBuilder.mActions = getActionsListWithSystemActions();
10563             wip.actions = new Action[mBuilder.mActions.size()];
10564             mBuilder.mActions.toArray(wip.actions);
10565             return wip;
10566         }
10567 
10568         /**
10569          * @hide
10570          */
displayCustomViewInline()10571         public boolean displayCustomViewInline() {
10572             // This is a lie; True is returned to make sure that the custom view is not used
10573             // instead of the template, but it will not actually be included.
10574             return true;
10575         }
10576 
10577         /**
10578          * @hide
10579          */
10580         @Override
purgeResources()10581         public void purgeResources() {
10582             super.purgeResources();
10583             if (mVerificationIcon != null) {
10584                 mVerificationIcon.convertToAshmem();
10585             }
10586         }
10587 
10588         /**
10589          * @hide
10590          */
10591         @Override
reduceImageSizes(Context context)10592         public void reduceImageSizes(Context context) {
10593             super.reduceImageSizes(context);
10594             if (mVerificationIcon != null) {
10595                 int rightIconSize = context.getResources().getDimensionPixelSize(
10596                         ActivityManager.isLowRamDeviceStatic()
10597                                 ? R.dimen.notification_right_icon_size_low_ram
10598                                 : R.dimen.notification_right_icon_size);
10599                 mVerificationIcon.scaleDownIfNecessary(rightIconSize, rightIconSize);
10600             }
10601         }
10602 
10603         /**
10604          * @hide
10605          */
10606         @Override
makeContentView(boolean increasedHeight)10607         public RemoteViews makeContentView(boolean increasedHeight) {
10608             return makeCallLayout(StandardTemplateParams.VIEW_TYPE_NORMAL);
10609         }
10610 
10611         /**
10612          * @hide
10613          */
10614         @Override
makeHeadsUpContentView(boolean increasedHeight)10615         public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
10616             return makeCallLayout(StandardTemplateParams.VIEW_TYPE_HEADS_UP);
10617         }
10618 
10619         /**
10620          * @hide
10621          */
10622         @Nullable
10623         @Override
makeCompactHeadsUpContentView()10624         public RemoteViews makeCompactHeadsUpContentView() {
10625             // Use existing heads up for call style.
10626             return makeHeadsUpContentView(false);
10627         }
10628 
10629         /**
10630          * @hide
10631          */
makeBigContentView()10632         public RemoteViews makeBigContentView() {
10633             return makeCallLayout(StandardTemplateParams.VIEW_TYPE_BIG);
10634         }
10635 
10636         @NonNull
makeNegativeAction()10637         private Action makeNegativeAction() {
10638             if (mDeclineIntent == null) {
10639                 return makeAction(R.drawable.ic_call_decline,
10640                         R.string.call_notification_hang_up_action,
10641                         mDeclineButtonColor, R.color.call_notification_decline_color,
10642                         mHangUpIntent);
10643             } else {
10644                 return makeAction(R.drawable.ic_call_decline,
10645                         R.string.call_notification_decline_action,
10646                         mDeclineButtonColor, R.color.call_notification_decline_color,
10647                         mDeclineIntent);
10648             }
10649         }
10650 
10651         @Nullable
makeAnswerAction()10652         private Action makeAnswerAction() {
10653             return mAnswerIntent == null ? null : makeAction(
10654                     mIsVideo ? R.drawable.ic_call_answer_video : R.drawable.ic_call_answer,
10655                     mIsVideo ? R.string.call_notification_answer_video_action
10656                             : R.string.call_notification_answer_action,
10657                     mAnswerButtonColor, R.color.call_notification_answer_color,
10658                     mAnswerIntent);
10659         }
10660 
10661         @NonNull
makeAction(@rawableRes int icon, @StringRes int title, @ColorInt Integer colorInt, @ColorRes int defaultColorRes, PendingIntent intent)10662         private Action makeAction(@DrawableRes int icon, @StringRes int title,
10663                 @ColorInt Integer colorInt, @ColorRes int defaultColorRes, PendingIntent intent) {
10664             if (colorInt == null || !mBuilder.isCallActionColorCustomizable()) {
10665                 colorInt = mBuilder.mContext.getColor(defaultColorRes);
10666             }
10667             Action action = new Action.Builder(Icon.createWithResource("", icon),
10668                     new SpannableStringBuilder().append(mBuilder.mContext.getString(title),
10669                             new ForegroundColorSpan(colorInt),
10670                             SpannableStringBuilder.SPAN_INCLUSIVE_INCLUSIVE),
10671                     intent).build();
10672             action.getExtras().putBoolean(KEY_ACTION_PRIORITY, true);
10673             return action;
10674         }
10675 
isActionAddedByCallStyle(Action action)10676         private boolean isActionAddedByCallStyle(Action action) {
10677             // This is an internal extra added by the style to these actions. If an app were to add
10678             // this extra to the action themselves, the action would be dropped.  :shrug:
10679             return action != null && action.getExtras().getBoolean(KEY_ACTION_PRIORITY);
10680         }
10681 
10682         /**
10683          * Gets the actions list for the call with the answer/decline/hangUp actions inserted in
10684          * the correct place.  This returns the correct result even if the system actions have
10685          * already been added, and even if more actions were added since then.
10686          * @hide
10687          */
10688         @NonNull
getActionsListWithSystemActions()10689         public ArrayList<Action> getActionsListWithSystemActions() {
10690             // Define the system actions we expect to see
10691             final Action firstAction = makeNegativeAction();
10692             final Action lastAction = makeAnswerAction();
10693 
10694             // Start creating the result list.
10695             int nonContextualActionSlotsRemaining = MAX_ACTION_BUTTONS;
10696             ArrayList<Action> resultActions = new ArrayList<>(MAX_ACTION_BUTTONS);
10697 
10698             // Always have a first action.
10699             resultActions.add(firstAction);
10700             --nonContextualActionSlotsRemaining;
10701 
10702             // Copy actions into the new list, correcting system actions.
10703             if (mBuilder.mActions != null) {
10704                 for (Notification.Action action : mBuilder.mActions) {
10705                     if (action.isContextual()) {
10706                         // Always include all contextual actions
10707                         resultActions.add(action);
10708                     } else if (isActionAddedByCallStyle(action)) {
10709                         // Drop any old versions of system actions
10710                     } else {
10711                         // Copy non-contextual actions; decrement the remaining action slots.
10712                         resultActions.add(action);
10713                         --nonContextualActionSlotsRemaining;
10714                     }
10715                     // If there's exactly one action slot left, fill it with the lastAction.
10716                     if (lastAction != null && nonContextualActionSlotsRemaining == 1) {
10717                         resultActions.add(lastAction);
10718                         --nonContextualActionSlotsRemaining;
10719                     }
10720                 }
10721             }
10722             // If there are any action slots left, the lastAction still needs to be added.
10723             if (lastAction != null && nonContextualActionSlotsRemaining >= 1) {
10724                 resultActions.add(lastAction);
10725             }
10726             return resultActions;
10727         }
10728 
makeCallLayout(int viewType)10729         private RemoteViews makeCallLayout(int viewType) {
10730             final boolean isCollapsed = viewType == StandardTemplateParams.VIEW_TYPE_NORMAL;
10731             Bundle extras = mBuilder.mN.extras;
10732             CharSequence title = mPerson != null ? mPerson.getName() : null;
10733             CharSequence text = mBuilder.processLegacyText(extras.getCharSequence(EXTRA_TEXT));
10734             if (text == null) {
10735                 text = getDefaultText();
10736             }
10737 
10738             // Bind standard template
10739             StandardTemplateParams p = mBuilder.mParams.reset()
10740                     .viewType(viewType)
10741                     .callStyleActions(true)
10742                     .allowTextWithProgress(true)
10743                     .hideLeftIcon(true)
10744                     .hideRightIcon(true)
10745                     .hideAppName(isCollapsed)
10746                     .titleViewId(R.id.conversation_text)
10747                     .title(title)
10748                     .text(text)
10749                     .summaryText(mBuilder.processLegacyText(mVerificationText));
10750             mBuilder.mActions = getActionsListWithSystemActions();
10751             final RemoteViews contentView;
10752             if (isCollapsed) {
10753                 contentView = mBuilder.applyStandardTemplate(
10754                         R.layout.notification_template_material_call, p, null /* result */);
10755             } else {
10756                 contentView = mBuilder.applyStandardTemplateWithActions(
10757                         R.layout.notification_template_material_big_call, p, null /* result */);
10758             }
10759 
10760             // Bind some extra conversation-specific header fields.
10761             if (!p.mHideAppName) {
10762                 mBuilder.setTextViewColorSecondary(contentView, R.id.app_name_divider, p);
10763                 contentView.setViewVisibility(R.id.app_name_divider, View.VISIBLE);
10764             }
10765             bindCallerVerification(contentView, p);
10766 
10767             // Bind some custom CallLayout properties
10768             contentView.setInt(R.id.status_bar_latest_event_content, "setLayoutColor",
10769                     mBuilder.getSmallIconColor(p));
10770             contentView.setInt(R.id.status_bar_latest_event_content,
10771                     "setNotificationBackgroundColor", mBuilder.getBackgroundColor(p));
10772             contentView.setIcon(R.id.status_bar_latest_event_content, "setLargeIcon",
10773                     mBuilder.mN.mLargeIcon);
10774             contentView.setBundle(R.id.status_bar_latest_event_content, "setData",
10775                     mBuilder.mN.extras);
10776 
10777             return contentView;
10778         }
10779 
bindCallerVerification(RemoteViews contentView, StandardTemplateParams p)10780         private void bindCallerVerification(RemoteViews contentView, StandardTemplateParams p) {
10781             String iconContentDescription = null;
10782             boolean showDivider = true;
10783             if (mVerificationIcon != null) {
10784                 contentView.setImageViewIcon(R.id.verification_icon, mVerificationIcon);
10785                 contentView.setDrawableTint(R.id.verification_icon, false /* targetBackground */,
10786                         mBuilder.getSecondaryTextColor(p), PorterDuff.Mode.SRC_ATOP);
10787                 contentView.setViewVisibility(R.id.verification_icon, View.VISIBLE);
10788                 iconContentDescription = mBuilder.mContext.getString(
10789                         R.string.notification_verified_content_description);
10790                 showDivider = false;  // the icon replaces the divider
10791             } else {
10792                 contentView.setViewVisibility(R.id.verification_icon, View.GONE);
10793             }
10794             if (!TextUtils.isEmpty(mVerificationText)) {
10795                 contentView.setTextViewText(R.id.verification_text, mVerificationText);
10796                 mBuilder.setTextViewColorSecondary(contentView, R.id.verification_text, p);
10797                 contentView.setViewVisibility(R.id.verification_text, View.VISIBLE);
10798                 iconContentDescription = null;  // let the app's text take precedence
10799             } else {
10800                 contentView.setViewVisibility(R.id.verification_text, View.GONE);
10801                 showDivider = false;  // no divider if no text
10802             }
10803             contentView.setContentDescription(R.id.verification_icon, iconContentDescription);
10804             if (showDivider) {
10805                 contentView.setViewVisibility(R.id.verification_divider, View.VISIBLE);
10806                 mBuilder.setTextViewColorSecondary(contentView, R.id.verification_divider, p);
10807             } else {
10808                 contentView.setViewVisibility(R.id.verification_divider, View.GONE);
10809             }
10810         }
10811 
10812         @Nullable
getDefaultText()10813         private String getDefaultText() {
10814             switch (mCallType) {
10815                 case CALL_TYPE_INCOMING:
10816                     return mBuilder.mContext.getString(R.string.call_notification_incoming_text);
10817                 case CALL_TYPE_ONGOING:
10818                     return mBuilder.mContext.getString(R.string.call_notification_ongoing_text);
10819                 case CALL_TYPE_SCREENING:
10820                     return mBuilder.mContext.getString(R.string.call_notification_screening_text);
10821             }
10822             return null;
10823         }
10824 
10825         /**
10826          * @hide
10827          */
addExtras(Bundle extras)10828         public void addExtras(Bundle extras) {
10829             super.addExtras(extras);
10830             extras.putInt(EXTRA_CALL_TYPE, mCallType);
10831             extras.putBoolean(EXTRA_CALL_IS_VIDEO, mIsVideo);
10832             extras.putParcelable(EXTRA_CALL_PERSON, mPerson);
10833             if (mVerificationIcon != null) {
10834                 extras.putParcelable(EXTRA_VERIFICATION_ICON, mVerificationIcon);
10835             }
10836             if (mVerificationText != null) {
10837                 extras.putCharSequence(EXTRA_VERIFICATION_TEXT, mVerificationText);
10838             }
10839             if (mAnswerIntent != null) {
10840                 extras.putParcelable(EXTRA_ANSWER_INTENT, mAnswerIntent);
10841             }
10842             if (mDeclineIntent != null) {
10843                 extras.putParcelable(EXTRA_DECLINE_INTENT, mDeclineIntent);
10844             }
10845             if (mHangUpIntent != null) {
10846                 extras.putParcelable(EXTRA_HANG_UP_INTENT, mHangUpIntent);
10847             }
10848             if (mAnswerButtonColor != null) {
10849                 extras.putInt(EXTRA_ANSWER_COLOR, mAnswerButtonColor);
10850             }
10851             if (mDeclineButtonColor != null) {
10852                 extras.putInt(EXTRA_DECLINE_COLOR, mDeclineButtonColor);
10853             }
10854             fixTitleAndTextExtras(extras);
10855         }
10856 
fixTitleAndTextExtras(Bundle extras)10857         private void fixTitleAndTextExtras(Bundle extras) {
10858             CharSequence sender = mPerson != null ? mPerson.getName() : null;
10859             if (sender != null) {
10860                 extras.putCharSequence(EXTRA_TITLE, sender);
10861             }
10862             if (extras.getCharSequence(EXTRA_TEXT) == null) {
10863                 extras.putCharSequence(EXTRA_TEXT, getDefaultText());
10864             }
10865         }
10866 
10867         /**
10868          * @hide
10869          */
10870         @Override
restoreFromExtras(Bundle extras)10871         protected void restoreFromExtras(Bundle extras) {
10872             super.restoreFromExtras(extras);
10873             mCallType = extras.getInt(EXTRA_CALL_TYPE);
10874             mIsVideo = extras.getBoolean(EXTRA_CALL_IS_VIDEO);
10875             mPerson = extras.getParcelable(EXTRA_CALL_PERSON, Person.class);
10876             mVerificationIcon = extras.getParcelable(EXTRA_VERIFICATION_ICON, android.graphics.drawable.Icon.class);
10877             mVerificationText = extras.getCharSequence(EXTRA_VERIFICATION_TEXT);
10878             mAnswerIntent = extras.getParcelable(EXTRA_ANSWER_INTENT, PendingIntent.class);
10879             mDeclineIntent = extras.getParcelable(EXTRA_DECLINE_INTENT, PendingIntent.class);
10880             mHangUpIntent = extras.getParcelable(EXTRA_HANG_UP_INTENT, PendingIntent.class);
10881             mAnswerButtonColor = extras.containsKey(EXTRA_ANSWER_COLOR)
10882                     ? extras.getInt(EXTRA_ANSWER_COLOR) : null;
10883             mDeclineButtonColor = extras.containsKey(EXTRA_DECLINE_COLOR)
10884                     ? extras.getInt(EXTRA_DECLINE_COLOR) : null;
10885         }
10886 
10887         /**
10888          * @hide
10889          */
10890         @Override
hasSummaryInHeader()10891         public boolean hasSummaryInHeader() {
10892             return false;
10893         }
10894 
10895         /**
10896          * @hide
10897          */
10898         @Override
areNotificationsVisiblyDifferent(Style other)10899         public boolean areNotificationsVisiblyDifferent(Style other) {
10900             if (other == null || getClass() != other.getClass()) {
10901                 return true;
10902             }
10903             CallStyle otherS = (CallStyle) other;
10904             return !Objects.equals(mCallType, otherS.mCallType)
10905                     || !Objects.equals(mPerson, otherS.mPerson)
10906                     || !Objects.equals(mVerificationText, otherS.mVerificationText);
10907         }
10908     }
10909 
10910     /**
10911      * Notification style for custom views that are decorated by the system
10912      *
10913      * <p>Instead of providing a notification that is completely custom, a developer can set this
10914      * style and still obtain system decorations like the notification header with the expand
10915      * affordance and actions.
10916      *
10917      * <p>Use {@link android.app.Notification.Builder#setCustomContentView(RemoteViews)},
10918      * {@link android.app.Notification.Builder#setCustomBigContentView(RemoteViews)} and
10919      * {@link android.app.Notification.Builder#setCustomHeadsUpContentView(RemoteViews)} to set the
10920      * corresponding custom views to display.
10921      *
10922      * To use this style with your Notification, feed it to
10923      * {@link Notification.Builder#setStyle(android.app.Notification.Style)} like so:
10924      * <pre class="prettyprint">
10925      * Notification noti = new Notification.Builder()
10926      *     .setSmallIcon(R.drawable.ic_stat_player)
10927      *     .setLargeIcon(albumArtBitmap))
10928      *     .setCustomContentView(contentView);
10929      *     .setStyle(<b>new Notification.DecoratedCustomViewStyle()</b>)
10930      *     .build();
10931      * </pre>
10932      */
10933     public static class DecoratedCustomViewStyle extends Style {
10934 
DecoratedCustomViewStyle()10935         public DecoratedCustomViewStyle() {
10936         }
10937 
10938         /**
10939          * @hide
10940          */
displayCustomViewInline()10941         public boolean displayCustomViewInline() {
10942             return true;
10943         }
10944 
10945         /**
10946          * @hide
10947          */
10948         @Override
makeContentView(boolean increasedHeight)10949         public RemoteViews makeContentView(boolean increasedHeight) {
10950             return makeStandardTemplateWithCustomContent(mBuilder.mN.contentView);
10951         }
10952 
10953         /**
10954          * @hide
10955          */
10956         @Override
makeBigContentView()10957         public RemoteViews makeBigContentView() {
10958             return makeDecoratedBigContentView();
10959         }
10960 
10961         /**
10962          * @hide
10963          */
10964         @Override
makeHeadsUpContentView(boolean increasedHeight)10965         public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
10966             return makeDecoratedHeadsUpContentView();
10967         }
10968 
makeDecoratedHeadsUpContentView()10969         private RemoteViews makeDecoratedHeadsUpContentView() {
10970             RemoteViews headsUpContentView = mBuilder.mN.headsUpContentView == null
10971                     ? mBuilder.mN.contentView
10972                     : mBuilder.mN.headsUpContentView;
10973             if (headsUpContentView == null) {
10974                 return null;  // no custom view; use the default behavior
10975             }
10976             if (mBuilder.mActions.size() == 0) {
10977                return makeStandardTemplateWithCustomContent(headsUpContentView);
10978             }
10979             TemplateBindResult result = new TemplateBindResult();
10980             StandardTemplateParams p = mBuilder.mParams.reset()
10981                     .viewType(StandardTemplateParams.VIEW_TYPE_HEADS_UP)
10982                     .decorationType(StandardTemplateParams.DECORATION_PARTIAL)
10983                     .fillTextsFrom(mBuilder);
10984             RemoteViews remoteViews = mBuilder.applyStandardTemplateWithActions(
10985                     mBuilder.getHeadsUpBaseLayoutResource(), p, result);
10986             buildCustomContentIntoTemplate(mBuilder.mContext, remoteViews, headsUpContentView,
10987                     p, result);
10988             return remoteViews;
10989         }
10990 
makeStandardTemplateWithCustomContent(RemoteViews customContent)10991         private RemoteViews makeStandardTemplateWithCustomContent(RemoteViews customContent) {
10992             if (customContent == null) {
10993                 return null;  // no custom view; use the default behavior
10994             }
10995             TemplateBindResult result = new TemplateBindResult();
10996             StandardTemplateParams p = mBuilder.mParams.reset()
10997                     .viewType(StandardTemplateParams.VIEW_TYPE_NORMAL)
10998                     .decorationType(StandardTemplateParams.DECORATION_PARTIAL)
10999                     .fillTextsFrom(mBuilder);
11000             RemoteViews remoteViews = mBuilder.applyStandardTemplate(
11001                     mBuilder.getBaseLayoutResource(), p, result);
11002             buildCustomContentIntoTemplate(mBuilder.mContext, remoteViews, customContent,
11003                     p, result);
11004             return remoteViews;
11005         }
11006 
makeDecoratedBigContentView()11007         private RemoteViews makeDecoratedBigContentView() {
11008             RemoteViews bigContentView = mBuilder.mN.bigContentView == null
11009                     ? mBuilder.mN.contentView
11010                     : mBuilder.mN.bigContentView;
11011             if (bigContentView == null) {
11012                 return null;  // no custom view; use the default behavior
11013             }
11014             TemplateBindResult result = new TemplateBindResult();
11015             StandardTemplateParams p = mBuilder.mParams.reset()
11016                     .viewType(StandardTemplateParams.VIEW_TYPE_BIG)
11017                     .decorationType(StandardTemplateParams.DECORATION_PARTIAL)
11018                     .fillTextsFrom(mBuilder);
11019             RemoteViews remoteViews = mBuilder.applyStandardTemplateWithActions(
11020                     mBuilder.getBigBaseLayoutResource(), p, result);
11021             buildCustomContentIntoTemplate(mBuilder.mContext, remoteViews, bigContentView,
11022                     p, result);
11023             return remoteViews;
11024         }
11025 
11026         /**
11027          * @hide
11028          */
11029         @Override
areNotificationsVisiblyDifferent(Style other)11030         public boolean areNotificationsVisiblyDifferent(Style other) {
11031             if (other == null || getClass() != other.getClass()) {
11032                 return true;
11033             }
11034             // Comparison done for all custom RemoteViews, independent of style
11035             return false;
11036         }
11037     }
11038 
11039     /**
11040      * Notification style for media custom views that are decorated by the system
11041      *
11042      * <p>Instead of providing a media notification that is completely custom, a developer can set
11043      * this style and still obtain system decorations like the notification header with the expand
11044      * affordance and actions.
11045      *
11046      * <p>Use {@link android.app.Notification.Builder#setCustomContentView(RemoteViews)},
11047      * {@link android.app.Notification.Builder#setCustomBigContentView(RemoteViews)} and
11048      * {@link android.app.Notification.Builder#setCustomHeadsUpContentView(RemoteViews)} to set the
11049      * corresponding custom views to display.
11050      * <p>
11051      * Contrary to {@link MediaStyle} a developer has to opt-in to the colorizing of the
11052      * notification by using {@link Notification.Builder#setColorized(boolean)}.
11053      * <p>
11054      * To use this style with your Notification, feed it to
11055      * {@link Notification.Builder#setStyle(android.app.Notification.Style)} like so:
11056      * <pre class="prettyprint">
11057      * Notification noti = new Notification.Builder()
11058      *     .setSmallIcon(R.drawable.ic_stat_player)
11059      *     .setLargeIcon(albumArtBitmap))
11060      *     .setCustomContentView(contentView);
11061      *     .setStyle(<b>new Notification.DecoratedMediaCustomViewStyle()</b>
11062      *          .setMediaSession(mySession))
11063      *     .build();
11064      * </pre>
11065      *
11066      * @see android.app.Notification.DecoratedCustomViewStyle
11067      * @see android.app.Notification.MediaStyle
11068      */
11069     public static class DecoratedMediaCustomViewStyle extends MediaStyle {
11070 
DecoratedMediaCustomViewStyle()11071         public DecoratedMediaCustomViewStyle() {
11072         }
11073 
11074         /**
11075          * @hide
11076          */
displayCustomViewInline()11077         public boolean displayCustomViewInline() {
11078             return true;
11079         }
11080 
11081         /**
11082          * @hide
11083          */
11084         @Override
makeContentView(boolean increasedHeight)11085         public RemoteViews makeContentView(boolean increasedHeight) {
11086             return makeMediaContentView(mBuilder.mN.contentView);
11087         }
11088 
11089         /**
11090          * @hide
11091          */
11092         @Override
makeBigContentView()11093         public RemoteViews makeBigContentView() {
11094             RemoteViews customContent = mBuilder.mN.bigContentView != null
11095                     ? mBuilder.mN.bigContentView
11096                     : mBuilder.mN.contentView;
11097             return makeMediaBigContentView(customContent);
11098         }
11099 
11100         /**
11101          * @hide
11102          */
11103         @Override
makeHeadsUpContentView(boolean increasedHeight)11104         public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
11105             RemoteViews customContent = mBuilder.mN.headsUpContentView != null
11106                     ? mBuilder.mN.headsUpContentView
11107                     : mBuilder.mN.contentView;
11108             return makeMediaBigContentView(customContent);
11109         }
11110 
11111         /**
11112          * @hide
11113          */
11114         @Override
areNotificationsVisiblyDifferent(Style other)11115         public boolean areNotificationsVisiblyDifferent(Style other) {
11116             if (other == null || getClass() != other.getClass()) {
11117                 return true;
11118             }
11119             // Comparison done for all custom RemoteViews, independent of style
11120             return false;
11121         }
11122     }
11123 
11124     /**
11125      * Encapsulates the information needed to display a notification as a bubble.
11126      *
11127      * <p>A bubble is used to display app content in a floating window over the existing
11128      * foreground activity. A bubble has a collapsed state represented by an icon and an
11129      * expanded state that displays an activity. These may be defined via
11130      * {@link Builder#Builder(PendingIntent, Icon)} or they may
11131      * be defined via an existing shortcut using {@link Builder#Builder(String)}.
11132      * </p>
11133      *
11134      * <b>Notifications with a valid and allowed bubble will display in collapsed state
11135      * outside of the notification shade on unlocked devices. When a user interacts with the
11136      * collapsed bubble, the bubble activity will be invoked and displayed.</b>
11137      *
11138      * @see Notification.Builder#setBubbleMetadata(BubbleMetadata)
11139      */
11140     public static final class BubbleMetadata implements Parcelable {
11141 
11142         private PendingIntent mPendingIntent;
11143         private PendingIntent mDeleteIntent;
11144         private Icon mIcon;
11145         private int mDesiredHeight;
11146         @DimenRes private int mDesiredHeightResId;
11147         private int mFlags;
11148         private String mShortcutId;
11149 
11150         /**
11151          * If set and the app creating the bubble is in the foreground, the bubble will be posted
11152          * in its expanded state.
11153          *
11154          * <p>This flag has no effect if the app posting the bubble is not in the foreground.
11155          * The app is considered foreground if it is visible and on the screen, note that
11156          * a foreground service does not qualify.
11157          * </p>
11158          *
11159          * <p>Generally this flag should only be set if the user has performed an action to request
11160          * or create a bubble.</p>
11161          *
11162          * @hide
11163          */
11164         public static final int FLAG_AUTO_EXPAND_BUBBLE = 0x00000001;
11165 
11166         /**
11167          * Indicates whether the notification associated with the bubble is being visually
11168          * suppressed from the notification shade. When <code>true</code> the notification is
11169          * hidden, when <code>false</code> the notification shows as normal.
11170          *
11171          * <p>Apps sending bubbles may set this flag so that the bubble is posted <b>without</b>
11172          * the associated notification in the notification shade.</p>
11173          *
11174          * <p>Generally this flag should only be set by the app if the user has performed an
11175          * action to request or create a bubble, or if the user has seen the content in the
11176          * notification and the notification is no longer relevant. </p>
11177          *
11178          * <p>The system will also update this flag with <code>true</code> to hide the notification
11179          * from the user once the bubble has been expanded. </p>
11180          *
11181          * @hide
11182          */
11183         public static final int FLAG_SUPPRESS_NOTIFICATION = 0x00000002;
11184 
11185         /**
11186          * Indicates whether the bubble should be visually suppressed from the bubble stack if the
11187          * user is viewing the same content outside of the bubble. For example, the user has a
11188          * bubble with Alice and then opens up the main app and navigates to Alice's page.
11189          *
11190          * @hide
11191          */
11192         public static final int FLAG_SUPPRESSABLE_BUBBLE = 0x00000004;
11193 
11194         /**
11195          * Indicates whether the bubble is visually suppressed from the bubble stack.
11196          *
11197          * @hide
11198          */
11199         public static final int FLAG_SUPPRESS_BUBBLE = 0x00000008;
11200 
BubbleMetadata(PendingIntent expandIntent, PendingIntent deleteIntent, Icon icon, int height, @DimenRes int heightResId, String shortcutId)11201         private BubbleMetadata(PendingIntent expandIntent, PendingIntent deleteIntent,
11202                 Icon icon, int height, @DimenRes int heightResId, String shortcutId) {
11203             mPendingIntent = expandIntent;
11204             mIcon = icon;
11205             mDesiredHeight = height;
11206             mDesiredHeightResId = heightResId;
11207             mDeleteIntent = deleteIntent;
11208             mShortcutId = shortcutId;
11209         }
11210 
BubbleMetadata(Parcel in)11211         private BubbleMetadata(Parcel in) {
11212             if (in.readInt() != 0) {
11213                 mPendingIntent = PendingIntent.CREATOR.createFromParcel(in);
11214             }
11215             if (in.readInt() != 0) {
11216                 mIcon = Icon.CREATOR.createFromParcel(in);
11217             }
11218             mDesiredHeight = in.readInt();
11219             mFlags = in.readInt();
11220             if (in.readInt() != 0) {
11221                 mDeleteIntent = PendingIntent.CREATOR.createFromParcel(in);
11222             }
11223             mDesiredHeightResId = in.readInt();
11224             if (in.readInt() != 0) {
11225                 mShortcutId = in.readString8();
11226             }
11227         }
11228 
11229         /**
11230          * @return the shortcut id used for this bubble if created via
11231          * {@link Builder#Builder(String)} or null if created
11232          * via {@link Builder#Builder(PendingIntent, Icon)}.
11233          */
11234         @Nullable
getShortcutId()11235         public String getShortcutId() {
11236             return mShortcutId;
11237         }
11238 
11239         /**
11240          * @return the pending intent used to populate the floating window for this bubble, or
11241          * null if this bubble is created via {@link Builder#Builder(String)}.
11242          */
11243         @SuppressLint("InvalidNullConversion")
11244         @Nullable
getIntent()11245         public PendingIntent getIntent() {
11246             return mPendingIntent;
11247         }
11248 
11249         /**
11250          * @return the pending intent to send when the bubble is dismissed by a user, if one exists.
11251          */
11252         @Nullable
getDeleteIntent()11253         public PendingIntent getDeleteIntent() {
11254             return mDeleteIntent;
11255         }
11256 
11257         /**
11258          * @return the icon that will be displayed for this bubble when it is collapsed, or null
11259          * if the bubble is created via {@link Builder#Builder(String)}.
11260          */
11261         @SuppressLint("InvalidNullConversion")
11262         @Nullable
getIcon()11263         public Icon getIcon() {
11264             return mIcon;
11265         }
11266 
11267         /**
11268          * @return the ideal height, in DPs, for the floating window that app content defined by
11269          * {@link #getIntent()} for this bubble. A value of 0 indicates a desired height has
11270          * not been set.
11271          */
11272         @Dimension(unit = DP)
getDesiredHeight()11273         public int getDesiredHeight() {
11274             return mDesiredHeight;
11275         }
11276 
11277         /**
11278          * @return the resId of ideal height for the floating window that app content defined by
11279          * {@link #getIntent()} for this bubble. A value of 0 indicates a res value has not
11280          * been provided for the desired height.
11281          */
11282         @DimenRes
getDesiredHeightResId()11283         public int getDesiredHeightResId() {
11284             return mDesiredHeightResId;
11285         }
11286 
11287         /**
11288          * @return whether this bubble should auto expand when it is posted.
11289          *
11290          * @see BubbleMetadata.Builder#setAutoExpandBubble(boolean)
11291          */
getAutoExpandBubble()11292         public boolean getAutoExpandBubble() {
11293             return (mFlags & FLAG_AUTO_EXPAND_BUBBLE) != 0;
11294         }
11295 
11296         /**
11297          * Indicates whether the notification associated with the bubble is being visually
11298          * suppressed from the notification shade. When <code>true</code> the notification is
11299          * hidden, when <code>false</code> the notification shows as normal.
11300          *
11301          * <p>Apps sending bubbles may set this flag so that the bubble is posted <b>without</b>
11302          * the associated notification in the notification shade.</p>
11303          *
11304          * <p>Generally the app should only set this flag if the user has performed an
11305          * action to request or create a bubble, or if the user has seen the content in the
11306          * notification and the notification is no longer relevant. </p>
11307          *
11308          * <p>The system will update this flag with <code>true</code> to hide the notification
11309          * from the user once the bubble has been expanded.</p>
11310          *
11311          * @return whether this bubble should suppress the notification when it is posted.
11312          *
11313          * @see BubbleMetadata.Builder#setSuppressNotification(boolean)
11314          */
isNotificationSuppressed()11315         public boolean isNotificationSuppressed() {
11316             return (mFlags & FLAG_SUPPRESS_NOTIFICATION) != 0;
11317         }
11318 
11319         /**
11320          * Indicates whether the bubble should be visually suppressed from the bubble stack if the
11321          * user is viewing the same content outside of the bubble. For example, the user has a
11322          * bubble with Alice and then opens up the main app and navigates to Alice's page.
11323          *
11324          * To match the activity and the bubble notification, the bubble notification should
11325          * have a locus id set that matches a locus id set on the activity.
11326          *
11327          * @return whether this bubble should be suppressed when the same content is visible
11328          * outside of the bubble.
11329          *
11330          * @see BubbleMetadata.Builder#setSuppressableBubble(boolean)
11331          */
isBubbleSuppressable()11332         public boolean isBubbleSuppressable() {
11333             return (mFlags & FLAG_SUPPRESSABLE_BUBBLE) != 0;
11334         }
11335 
11336         /**
11337          * Indicates whether the bubble is currently visually suppressed from the bubble stack.
11338          *
11339          * @see BubbleMetadata.Builder#setSuppressableBubble(boolean)
11340          */
isBubbleSuppressed()11341         public boolean isBubbleSuppressed() {
11342             return (mFlags & FLAG_SUPPRESS_BUBBLE) != 0;
11343         }
11344 
11345         /**
11346          * Sets whether the notification associated with the bubble is being visually
11347          * suppressed from the notification shade. When <code>true</code> the notification is
11348          * hidden, when <code>false</code> the notification shows as normal.
11349          *
11350          * @hide
11351          */
setSuppressNotification(boolean suppressed)11352         public void setSuppressNotification(boolean suppressed) {
11353             if (suppressed) {
11354                 mFlags |= Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION;
11355             } else {
11356                 mFlags &= ~Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION;
11357             }
11358         }
11359 
11360         /**
11361          * Sets whether the bubble should be visually suppressed from the bubble stack if the
11362          * user is viewing the same content outside of the bubble. For example, the user has a
11363          * bubble with Alice and then opens up the main app and navigates to Alice's page.
11364          *
11365          * @hide
11366          */
setSuppressBubble(boolean suppressed)11367         public void setSuppressBubble(boolean suppressed) {
11368             if (suppressed) {
11369                 mFlags |= Notification.BubbleMetadata.FLAG_SUPPRESS_BUBBLE;
11370             } else {
11371                 mFlags &= ~Notification.BubbleMetadata.FLAG_SUPPRESS_BUBBLE;
11372             }
11373         }
11374 
11375         /**
11376          * @hide
11377          */
setFlags(int flags)11378         public void setFlags(int flags) {
11379             mFlags = flags;
11380         }
11381 
11382         /**
11383          * @hide
11384          */
getFlags()11385         public int getFlags() {
11386             return mFlags;
11387         }
11388 
11389         public static final @android.annotation.NonNull Parcelable.Creator<BubbleMetadata> CREATOR =
11390                 new Parcelable.Creator<BubbleMetadata>() {
11391 
11392                     @Override
11393                     public BubbleMetadata createFromParcel(Parcel source) {
11394                         return new BubbleMetadata(source);
11395                     }
11396 
11397                     @Override
11398                     public BubbleMetadata[] newArray(int size) {
11399                         return new BubbleMetadata[size];
11400                     }
11401                 };
11402 
11403         @Override
describeContents()11404         public int describeContents() {
11405             return 0;
11406         }
11407 
11408         @Override
writeToParcel(Parcel out, int flags)11409         public void writeToParcel(Parcel out, int flags) {
11410             out.writeInt(mPendingIntent != null ? 1 : 0);
11411             if (mPendingIntent != null) {
11412                 mPendingIntent.writeToParcel(out, 0);
11413             }
11414             out.writeInt(mIcon != null ? 1 : 0);
11415             if (mIcon != null) {
11416                 mIcon.writeToParcel(out, 0);
11417             }
11418             out.writeInt(mDesiredHeight);
11419             out.writeInt(mFlags);
11420             out.writeInt(mDeleteIntent != null ? 1 : 0);
11421             if (mDeleteIntent != null) {
11422                 mDeleteIntent.writeToParcel(out, 0);
11423             }
11424             out.writeInt(mDesiredHeightResId);
11425             out.writeInt(TextUtils.isEmpty(mShortcutId) ? 0 : 1);
11426             if (!TextUtils.isEmpty(mShortcutId)) {
11427                 out.writeString8(mShortcutId);
11428             }
11429         }
11430 
11431         /**
11432          * Builder to construct a {@link BubbleMetadata} object.
11433          */
11434         public static final class Builder {
11435 
11436             private PendingIntent mPendingIntent;
11437             private Icon mIcon;
11438             private int mDesiredHeight;
11439             @DimenRes private int mDesiredHeightResId;
11440             private int mFlags;
11441             private PendingIntent mDeleteIntent;
11442             private String mShortcutId;
11443 
11444             /**
11445              * @deprecated use {@link Builder#Builder(String)} for a bubble created via a
11446              * {@link ShortcutInfo} or {@link Builder#Builder(PendingIntent, Icon)} for a bubble
11447              * created via a {@link PendingIntent}.
11448              */
11449             @Deprecated
Builder()11450             public Builder() {
11451             }
11452 
11453             /**
11454              * Creates a {@link BubbleMetadata.Builder} based on a {@link ShortcutInfo}. To create
11455              * a shortcut bubble, ensure that the shortcut associated with the provided
11456              * {@param shortcutId} is published as a dynamic shortcut that was built with
11457              * {@link ShortcutInfo.Builder#setLongLived(boolean)} being true, otherwise your
11458              * notification will not be able to bubble.
11459              *
11460              * <p>The shortcut icon will be used to represent the bubble when it is collapsed.</p>
11461              *
11462              * <p>The shortcut activity will be used when the bubble is expanded. This will display
11463              * the shortcut activity in a floating window over the existing foreground activity.</p>
11464              *
11465              * <p>When the activity is launched from a bubble,
11466              * {@link Activity#isLaunchedFromBubble()} will return with {@code true}.
11467              * </p>
11468              *
11469              * <p>If the shortcut has not been published when the bubble notification is sent,
11470              * no bubble will be produced. If the shortcut is deleted while the bubble is active,
11471              * the bubble will be removed.</p>
11472              *
11473              * @throws NullPointerException if shortcutId is null.
11474              *
11475              * @see ShortcutInfo
11476              * @see ShortcutInfo.Builder#setLongLived(boolean)
11477              * @see android.content.pm.ShortcutManager#addDynamicShortcuts(List)
11478              */
Builder(@onNull String shortcutId)11479             public Builder(@NonNull String shortcutId) {
11480                 if (TextUtils.isEmpty(shortcutId)) {
11481                     throw new NullPointerException("Bubble requires a non-null shortcut id");
11482                 }
11483                 mShortcutId = shortcutId;
11484             }
11485 
11486             /**
11487              * Creates a {@link BubbleMetadata.Builder} based on the provided intent and icon.
11488              *
11489              * <p>The icon will be used to represent the bubble when it is collapsed. An icon
11490              * should be representative of the content within the bubble. If your app produces
11491              * multiple bubbles, the icon should be unique for each of them.</p>
11492              *
11493              * <p>The intent that will be used when the bubble is expanded. This will display the
11494              * app content in a floating window over the existing foreground activity. The intent
11495              * should point to a resizable activity. </p>
11496              *
11497              * <p>When the activity is launched from a bubble,
11498              * {@link Activity#isLaunchedFromBubble()} will return with {@code true}.
11499              * </p>
11500              *
11501              * Note that the pending intent used here requires PendingIntent.FLAG_MUTABLE.
11502              *
11503              * @throws NullPointerException if intent is null.
11504              * @throws NullPointerException if icon is null.
11505              */
Builder(@onNull PendingIntent intent, @NonNull Icon icon)11506             public Builder(@NonNull PendingIntent intent, @NonNull Icon icon) {
11507                 if (intent == null) {
11508                     throw new NullPointerException("Bubble requires non-null pending intent");
11509                 }
11510                 if (icon == null) {
11511                     throw new NullPointerException("Bubbles require non-null icon");
11512                 }
11513                 if (icon.getType() != TYPE_URI_ADAPTIVE_BITMAP
11514                         && icon.getType() != TYPE_URI) {
11515                     Log.w(TAG, "Bubbles work best with icons of TYPE_URI or "
11516                             + "TYPE_URI_ADAPTIVE_BITMAP. "
11517                             + "In the future, using an icon of this type will be required.");
11518                 }
11519                 mPendingIntent = intent;
11520                 mIcon = icon;
11521             }
11522 
11523             /**
11524              * Sets the intent for the bubble.
11525              *
11526              * <p>The intent that will be used when the bubble is expanded. This will display the
11527              * app content in a floating window over the existing foreground activity. The intent
11528              * should point to a resizable activity. </p>
11529              *
11530              * @throws NullPointerException  if intent is null.
11531              * @throws IllegalStateException if this builder was created via
11532              *                               {@link Builder#Builder(String)}.
11533              */
11534             @NonNull
setIntent(@onNull PendingIntent intent)11535             public BubbleMetadata.Builder setIntent(@NonNull PendingIntent intent) {
11536                 if (mShortcutId != null) {
11537                     throw new IllegalStateException("Created as a shortcut bubble, cannot set a "
11538                             + "PendingIntent. Consider using "
11539                             + "BubbleMetadata.Builder(PendingIntent,Icon) instead.");
11540                 }
11541                 if (intent == null) {
11542                     throw new NullPointerException("Bubble requires non-null pending intent");
11543                 }
11544                 mPendingIntent = intent;
11545                 return this;
11546             }
11547 
11548             /**
11549              * Sets the icon for the bubble. Can only be used if the bubble was created
11550              * via {@link Builder#Builder(PendingIntent, Icon)}.
11551              *
11552              * <p>The icon will be used to represent the bubble when it is collapsed. An icon
11553              * should be representative of the content within the bubble. If your app produces
11554              * multiple bubbles, the icon should be unique for each of them.</p>
11555              *
11556              * <p>It is recommended to use an {@link Icon} of type {@link Icon#TYPE_URI}
11557              * or {@link Icon#TYPE_URI_ADAPTIVE_BITMAP}</p>
11558              *
11559              * @throws NullPointerException  if icon is null.
11560              * @throws IllegalStateException if this builder was created via
11561              *                               {@link Builder#Builder(String)}.
11562              */
11563             @NonNull
setIcon(@onNull Icon icon)11564             public BubbleMetadata.Builder setIcon(@NonNull Icon icon) {
11565                 if (mShortcutId != null) {
11566                     throw new IllegalStateException("Created as a shortcut bubble, cannot set an "
11567                             + "Icon. Consider using "
11568                             + "BubbleMetadata.Builder(PendingIntent,Icon) instead.");
11569                 }
11570                 if (icon == null) {
11571                     throw new NullPointerException("Bubbles require non-null icon");
11572                 }
11573                 if (icon.getType() != TYPE_URI_ADAPTIVE_BITMAP
11574                         && icon.getType() != TYPE_URI) {
11575                     Log.w(TAG, "Bubbles work best with icons of TYPE_URI or "
11576                             + "TYPE_URI_ADAPTIVE_BITMAP. "
11577                             + "In the future, using an icon of this type will be required.");
11578                 }
11579                 mIcon = icon;
11580                 return this;
11581             }
11582 
11583             /**
11584              * Sets the desired height in DPs for the expanded content of the bubble.
11585              *
11586              * <p>This height may not be respected if there is not enough space on the screen or if
11587              * the provided height is too small to be useful.</p>
11588              *
11589              * <p>If {@link #setDesiredHeightResId(int)} was previously called on this builder, the
11590              * previous value set will be cleared after calling this method, and this value will
11591              * be used instead.</p>
11592              *
11593              * <p>A desired height (in DPs or via resID) is optional.</p>
11594              *
11595              * @see #setDesiredHeightResId(int)
11596              */
11597             @NonNull
setDesiredHeight(@imensionunit = DP) int height)11598             public BubbleMetadata.Builder setDesiredHeight(@Dimension(unit = DP) int height) {
11599                 mDesiredHeight = Math.max(height, 0);
11600                 mDesiredHeightResId = 0;
11601                 return this;
11602             }
11603 
11604 
11605             /**
11606              * Sets the desired height via resId for the expanded content of the bubble.
11607              *
11608              * <p>This height may not be respected if there is not enough space on the screen or if
11609              * the provided height is too small to be useful.</p>
11610              *
11611              * <p>If {@link #setDesiredHeight(int)} was previously called on this builder, the
11612              * previous value set will be cleared after calling this method, and this value will
11613              * be used instead.</p>
11614              *
11615              * <p>A desired height (in DPs or via resID) is optional.</p>
11616              *
11617              * @see #setDesiredHeight(int)
11618              */
11619             @NonNull
setDesiredHeightResId(@imenRes int heightResId)11620             public BubbleMetadata.Builder setDesiredHeightResId(@DimenRes int heightResId) {
11621                 mDesiredHeightResId = heightResId;
11622                 mDesiredHeight = 0;
11623                 return this;
11624             }
11625 
11626             /**
11627              * Sets whether the bubble will be posted in its expanded state.
11628              *
11629              * <p>This flag has no effect if the app posting the bubble is not in the foreground.
11630              * The app is considered foreground if it is visible and on the screen, note that
11631              * a foreground service does not qualify.
11632              * </p>
11633              *
11634              * <p>Generally, this flag should only be set if the user has performed an action to
11635              * request or create a bubble.</p>
11636              *
11637              * <p>Setting this flag is optional; it defaults to false.</p>
11638              */
11639             @NonNull
setAutoExpandBubble(boolean shouldExpand)11640             public BubbleMetadata.Builder setAutoExpandBubble(boolean shouldExpand) {
11641                 setFlag(FLAG_AUTO_EXPAND_BUBBLE, shouldExpand);
11642                 return this;
11643             }
11644 
11645             /**
11646              * Sets whether the bubble will be posted <b>without</b> the associated notification in
11647              * the notification shade.
11648              *
11649              * <p>Generally, this flag should only be set if the user has performed an action to
11650              * request or create a bubble, or if the user has seen the content in the notification
11651              * and the notification is no longer relevant.</p>
11652              *
11653              * <p>Setting this flag is optional; it defaults to false.</p>
11654              */
11655             @NonNull
setSuppressNotification(boolean shouldSuppressNotif)11656             public BubbleMetadata.Builder setSuppressNotification(boolean shouldSuppressNotif) {
11657                 setFlag(FLAG_SUPPRESS_NOTIFICATION, shouldSuppressNotif);
11658                 return this;
11659             }
11660 
11661             /**
11662              * Indicates whether the bubble should be visually suppressed from the bubble stack if
11663              * the user is viewing the same content outside of the bubble. For example, the user has
11664              * a bubble with Alice and then opens up the main app and navigates to Alice's page.
11665              *
11666              * To match the activity and the bubble notification, the bubble notification should
11667              * have a locus id set that matches a locus id set on the activity.
11668              *
11669              * {@link Notification.Builder#setLocusId(LocusId)}
11670              * {@link Activity#setLocusContext(LocusId, Bundle)}
11671              */
11672             @NonNull
setSuppressableBubble(boolean suppressBubble)11673             public BubbleMetadata.Builder setSuppressableBubble(boolean suppressBubble) {
11674                 setFlag(FLAG_SUPPRESSABLE_BUBBLE, suppressBubble);
11675                 return this;
11676             }
11677 
11678             /**
11679              * Sets an intent to send when this bubble is explicitly removed by the user.
11680              *
11681              * <p>Setting a delete intent is optional.</p>
11682              */
11683             @NonNull
setDeleteIntent(@ullable PendingIntent deleteIntent)11684             public BubbleMetadata.Builder setDeleteIntent(@Nullable PendingIntent deleteIntent) {
11685                 mDeleteIntent = deleteIntent;
11686                 return this;
11687             }
11688 
11689             /**
11690              * Creates the {@link BubbleMetadata} defined by this builder.
11691              *
11692              * @throws NullPointerException if required elements have not been set.
11693              */
11694             @NonNull
build()11695             public BubbleMetadata build() {
11696                 if (mShortcutId == null && mPendingIntent == null) {
11697                     throw new NullPointerException(
11698                             "Must supply pending intent or shortcut to bubble");
11699                 }
11700                 if (mShortcutId == null && mIcon == null) {
11701                     throw new NullPointerException(
11702                             "Must supply an icon or shortcut for the bubble");
11703                 }
11704                 BubbleMetadata data = new BubbleMetadata(mPendingIntent, mDeleteIntent,
11705                         mIcon, mDesiredHeight, mDesiredHeightResId, mShortcutId);
11706                 data.setFlags(mFlags);
11707                 return data;
11708             }
11709 
11710             /**
11711              * @hide
11712              */
setFlag(int mask, boolean value)11713             public BubbleMetadata.Builder setFlag(int mask, boolean value) {
11714                 if (value) {
11715                     mFlags |= mask;
11716                 } else {
11717                     mFlags &= ~mask;
11718                 }
11719                 return this;
11720             }
11721         }
11722     }
11723 
11724 
11725     // When adding a new Style subclass here, don't forget to update
11726     // Builder.getNotificationStyleClass.
11727 
11728     /**
11729      * Extender interface for use with {@link Builder#extend}. Extenders may be used to add
11730      * metadata or change options on a notification builder.
11731      */
11732     public interface Extender {
11733         /**
11734          * Apply this extender to a notification builder.
11735          * @param builder the builder to be modified.
11736          * @return the build object for chaining.
11737          */
extend(Builder builder)11738         public Builder extend(Builder builder);
11739     }
11740 
11741     /**
11742      * Helper class to add wearable extensions to notifications.
11743      * <p class="note"> See
11744      * <a href="{@docRoot}wear/notifications/creating.html">Creating Notifications
11745      * for Android Wear</a> for more information on how to use this class.
11746      * <p>
11747      * To create a notification with wearable extensions:
11748      * <ol>
11749      *   <li>Create a {@link android.app.Notification.Builder}, setting any desired
11750      *   properties.
11751      *   <li>Create a {@link android.app.Notification.WearableExtender}.
11752      *   <li>Set wearable-specific properties using the
11753      *   {@code add} and {@code set} methods of {@link android.app.Notification.WearableExtender}.
11754      *   <li>Call {@link android.app.Notification.Builder#extend} to apply the extensions to a
11755      *   notification.
11756      *   <li>Post the notification to the notification system with the
11757      *   {@code NotificationManager.notify(...)} methods.
11758      * </ol>
11759      *
11760      * <pre class="prettyprint">
11761      * Notification notif = new Notification.Builder(mContext)
11762      *         .setContentTitle(&quot;New mail from &quot; + sender.toString())
11763      *         .setContentText(subject)
11764      *         .setSmallIcon(R.drawable.new_mail)
11765      *         .extend(new Notification.WearableExtender()
11766      *                 .setContentIcon(R.drawable.new_mail))
11767      *         .build();
11768      * NotificationManager notificationManger =
11769      *         (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
11770      * notificationManger.notify(0, notif);</pre>
11771      *
11772      * <p>Wearable extensions can be accessed on an existing notification by using the
11773      * {@code WearableExtender(Notification)} constructor,
11774      * and then using the {@code get} methods to access values.
11775      *
11776      * <pre class="prettyprint">
11777      * Notification.WearableExtender wearableExtender = new Notification.WearableExtender(
11778      *         notification);
11779      * List&lt;Notification&gt; pages = wearableExtender.getPages();</pre>
11780      */
11781     public static final class WearableExtender implements Extender {
11782         /**
11783          * Sentinel value for an action index that is unset.
11784          */
11785         public static final int UNSET_ACTION_INDEX = -1;
11786 
11787         /**
11788          * Size value for use with {@link #setCustomSizePreset} to show this notification with
11789          * default sizing.
11790          * <p>For custom display notifications created using {@link #setDisplayIntent},
11791          * the default is {@link #SIZE_MEDIUM}. All other notifications size automatically based
11792          * on their content.
11793          *
11794          * @deprecated Display intents are no longer supported.
11795          */
11796         @Deprecated
11797         public static final int SIZE_DEFAULT = 0;
11798 
11799         /**
11800          * Size value for use with {@link #setCustomSizePreset} to show this notification
11801          * with an extra small size.
11802          * <p>This value is only applicable for custom display notifications created using
11803          * {@link #setDisplayIntent}.
11804          *
11805          * @deprecated Display intents are no longer supported.
11806          */
11807         @Deprecated
11808         public static final int SIZE_XSMALL = 1;
11809 
11810         /**
11811          * Size value for use with {@link #setCustomSizePreset} to show this notification
11812          * with a small size.
11813          * <p>This value is only applicable for custom display notifications created using
11814          * {@link #setDisplayIntent}.
11815          *
11816          * @deprecated Display intents are no longer supported.
11817          */
11818         @Deprecated
11819         public static final int SIZE_SMALL = 2;
11820 
11821         /**
11822          * Size value for use with {@link #setCustomSizePreset} to show this notification
11823          * with a medium size.
11824          * <p>This value is only applicable for custom display notifications created using
11825          * {@link #setDisplayIntent}.
11826          *
11827          * @deprecated Display intents are no longer supported.
11828          */
11829         @Deprecated
11830         public static final int SIZE_MEDIUM = 3;
11831 
11832         /**
11833          * Size value for use with {@link #setCustomSizePreset} to show this notification
11834          * with a large size.
11835          * <p>This value is only applicable for custom display notifications created using
11836          * {@link #setDisplayIntent}.
11837          *
11838          * @deprecated Display intents are no longer supported.
11839          */
11840         @Deprecated
11841         public static final int SIZE_LARGE = 4;
11842 
11843         /**
11844          * Size value for use with {@link #setCustomSizePreset} to show this notification
11845          * full screen.
11846          * <p>This value is only applicable for custom display notifications created using
11847          * {@link #setDisplayIntent}.
11848          *
11849          * @deprecated Display intents are no longer supported.
11850          */
11851         @Deprecated
11852         public static final int SIZE_FULL_SCREEN = 5;
11853 
11854         /**
11855          * Sentinel value for use with {@link #setHintScreenTimeout} to keep the screen on for a
11856          * short amount of time when this notification is displayed on the screen. This
11857          * is the default value.
11858          *
11859          * @deprecated This feature is no longer supported.
11860          */
11861         @Deprecated
11862         public static final int SCREEN_TIMEOUT_SHORT = 0;
11863 
11864         /**
11865          * Sentinel value for use with {@link #setHintScreenTimeout} to keep the screen on
11866          * for a longer amount of time when this notification is displayed on the screen.
11867          *
11868          * @deprecated This feature is no longer supported.
11869          */
11870         @Deprecated
11871         public static final int SCREEN_TIMEOUT_LONG = -1;
11872 
11873         /** Notification extra which contains wearable extensions */
11874         private static final String EXTRA_WEARABLE_EXTENSIONS = "android.wearable.EXTENSIONS";
11875 
11876         // Keys within EXTRA_WEARABLE_EXTENSIONS for wearable options.
11877         private static final String KEY_ACTIONS = "actions";
11878         private static final String KEY_FLAGS = "flags";
11879         static final String KEY_DISPLAY_INTENT = "displayIntent";
11880         private static final String KEY_PAGES = "pages";
11881         static final String KEY_BACKGROUND = "background";
11882         private static final String KEY_CONTENT_ICON = "contentIcon";
11883         private static final String KEY_CONTENT_ICON_GRAVITY = "contentIconGravity";
11884         private static final String KEY_CONTENT_ACTION_INDEX = "contentActionIndex";
11885         private static final String KEY_CUSTOM_SIZE_PRESET = "customSizePreset";
11886         private static final String KEY_CUSTOM_CONTENT_HEIGHT = "customContentHeight";
11887         private static final String KEY_GRAVITY = "gravity";
11888         private static final String KEY_HINT_SCREEN_TIMEOUT = "hintScreenTimeout";
11889         private static final String KEY_DISMISSAL_ID = "dismissalId";
11890         private static final String KEY_BRIDGE_TAG = "bridgeTag";
11891 
11892         // Flags bitwise-ored to mFlags
11893         private static final int FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE = 0x1;
11894         private static final int FLAG_HINT_HIDE_ICON = 1 << 1;
11895         private static final int FLAG_HINT_SHOW_BACKGROUND_ONLY = 1 << 2;
11896         private static final int FLAG_START_SCROLL_BOTTOM = 1 << 3;
11897         private static final int FLAG_HINT_AVOID_BACKGROUND_CLIPPING = 1 << 4;
11898         private static final int FLAG_BIG_PICTURE_AMBIENT = 1 << 5;
11899         private static final int FLAG_HINT_CONTENT_INTENT_LAUNCHES_ACTIVITY = 1 << 6;
11900 
11901         // Default value for flags integer
11902         private static final int DEFAULT_FLAGS = FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE;
11903 
11904         private static final int DEFAULT_CONTENT_ICON_GRAVITY = Gravity.END;
11905         private static final int DEFAULT_GRAVITY = Gravity.BOTTOM;
11906 
11907         private ArrayList<Action> mActions = new ArrayList<Action>();
11908         private int mFlags = DEFAULT_FLAGS;
11909         private PendingIntent mDisplayIntent;
11910         private ArrayList<Notification> mPages = new ArrayList<Notification>();
11911         private Bitmap mBackground;
11912         private int mContentIcon;
11913         private int mContentIconGravity = DEFAULT_CONTENT_ICON_GRAVITY;
11914         private int mContentActionIndex = UNSET_ACTION_INDEX;
11915         private int mCustomSizePreset = SIZE_DEFAULT;
11916         private int mCustomContentHeight;
11917         private int mGravity = DEFAULT_GRAVITY;
11918         private int mHintScreenTimeout;
11919         private String mDismissalId;
11920         private String mBridgeTag;
11921 
11922         /**
11923          * Create a {@link android.app.Notification.WearableExtender} with default
11924          * options.
11925          */
WearableExtender()11926         public WearableExtender() {
11927         }
11928 
WearableExtender(Notification notif)11929         public WearableExtender(Notification notif) {
11930             Bundle wearableBundle = notif.extras.getBundle(EXTRA_WEARABLE_EXTENSIONS);
11931             if (wearableBundle != null) {
11932                 List<Action> actions = wearableBundle.getParcelableArrayList(KEY_ACTIONS, android.app.Notification.Action.class);
11933                 if (actions != null) {
11934                     mActions.addAll(actions);
11935                 }
11936 
11937                 mFlags = wearableBundle.getInt(KEY_FLAGS, DEFAULT_FLAGS);
11938                 mDisplayIntent = wearableBundle.getParcelable(
11939                         KEY_DISPLAY_INTENT, PendingIntent.class);
11940 
11941                 Notification[] pages = getParcelableArrayFromBundle(
11942                         wearableBundle, KEY_PAGES, Notification.class);
11943                 if (pages != null) {
11944                     Collections.addAll(mPages, pages);
11945                 }
11946 
11947                 mBackground = wearableBundle.getParcelable(KEY_BACKGROUND, Bitmap.class);
11948                 mContentIcon = wearableBundle.getInt(KEY_CONTENT_ICON);
11949                 mContentIconGravity = wearableBundle.getInt(KEY_CONTENT_ICON_GRAVITY,
11950                         DEFAULT_CONTENT_ICON_GRAVITY);
11951                 mContentActionIndex = wearableBundle.getInt(KEY_CONTENT_ACTION_INDEX,
11952                         UNSET_ACTION_INDEX);
11953                 mCustomSizePreset = wearableBundle.getInt(KEY_CUSTOM_SIZE_PRESET,
11954                         SIZE_DEFAULT);
11955                 mCustomContentHeight = wearableBundle.getInt(KEY_CUSTOM_CONTENT_HEIGHT);
11956                 mGravity = wearableBundle.getInt(KEY_GRAVITY, DEFAULT_GRAVITY);
11957                 mHintScreenTimeout = wearableBundle.getInt(KEY_HINT_SCREEN_TIMEOUT);
11958                 mDismissalId = wearableBundle.getString(KEY_DISMISSAL_ID);
11959                 mBridgeTag = wearableBundle.getString(KEY_BRIDGE_TAG);
11960             }
11961         }
11962 
11963         /**
11964          * Apply wearable extensions to a notification that is being built. This is typically
11965          * called by the {@link android.app.Notification.Builder#extend} method of
11966          * {@link android.app.Notification.Builder}.
11967          */
11968         @Override
extend(Notification.Builder builder)11969         public Notification.Builder extend(Notification.Builder builder) {
11970             Bundle wearableBundle = new Bundle();
11971 
11972             if (!mActions.isEmpty()) {
11973                 wearableBundle.putParcelableArrayList(KEY_ACTIONS, mActions);
11974             }
11975             if (mFlags != DEFAULT_FLAGS) {
11976                 wearableBundle.putInt(KEY_FLAGS, mFlags);
11977             }
11978             if (mDisplayIntent != null) {
11979                 wearableBundle.putParcelable(KEY_DISPLAY_INTENT, mDisplayIntent);
11980             }
11981             if (!mPages.isEmpty()) {
11982                 wearableBundle.putParcelableArray(KEY_PAGES, mPages.toArray(
11983                         new Notification[mPages.size()]));
11984             }
11985 
11986             if (mBackground != null) {
11987                 // Keeping WearableExtender backgrounds in memory despite them being deprecated has
11988                 // added noticeable increase in system server and system ui memory usage. After
11989                 // target VERSION_CODE#VANILLA_ICE_CREAM the background will not be populated
11990                 // anymore.
11991                 if (CompatChanges.isChangeEnabled(WEARABLE_EXTENDER_BACKGROUND_BLOCKED)) {
11992                     Log.d(TAG, "Use of background in WearableExtenders has been deprecated and "
11993                             + "will not be populated anymore.");
11994                 } else {
11995                     wearableBundle.putParcelable(KEY_BACKGROUND, mBackground);
11996                 }
11997             }
11998 
11999             if (mContentIcon != 0) {
12000                 wearableBundle.putInt(KEY_CONTENT_ICON, mContentIcon);
12001             }
12002             if (mContentIconGravity != DEFAULT_CONTENT_ICON_GRAVITY) {
12003                 wearableBundle.putInt(KEY_CONTENT_ICON_GRAVITY, mContentIconGravity);
12004             }
12005             if (mContentActionIndex != UNSET_ACTION_INDEX) {
12006                 wearableBundle.putInt(KEY_CONTENT_ACTION_INDEX,
12007                         mContentActionIndex);
12008             }
12009             if (mCustomSizePreset != SIZE_DEFAULT) {
12010                 wearableBundle.putInt(KEY_CUSTOM_SIZE_PRESET, mCustomSizePreset);
12011             }
12012             if (mCustomContentHeight != 0) {
12013                 wearableBundle.putInt(KEY_CUSTOM_CONTENT_HEIGHT, mCustomContentHeight);
12014             }
12015             if (mGravity != DEFAULT_GRAVITY) {
12016                 wearableBundle.putInt(KEY_GRAVITY, mGravity);
12017             }
12018             if (mHintScreenTimeout != 0) {
12019                 wearableBundle.putInt(KEY_HINT_SCREEN_TIMEOUT, mHintScreenTimeout);
12020             }
12021             if (mDismissalId != null) {
12022                 wearableBundle.putString(KEY_DISMISSAL_ID, mDismissalId);
12023             }
12024             if (mBridgeTag != null) {
12025                 wearableBundle.putString(KEY_BRIDGE_TAG, mBridgeTag);
12026             }
12027 
12028             builder.getExtras().putBundle(EXTRA_WEARABLE_EXTENSIONS, wearableBundle);
12029             return builder;
12030         }
12031 
12032         @Override
clone()12033         public WearableExtender clone() {
12034             WearableExtender that = new WearableExtender();
12035             that.mActions = new ArrayList<Action>(this.mActions);
12036             that.mFlags = this.mFlags;
12037             that.mDisplayIntent = this.mDisplayIntent;
12038             that.mPages = new ArrayList<Notification>(this.mPages);
12039             that.mBackground = this.mBackground;
12040             that.mContentIcon = this.mContentIcon;
12041             that.mContentIconGravity = this.mContentIconGravity;
12042             that.mContentActionIndex = this.mContentActionIndex;
12043             that.mCustomSizePreset = this.mCustomSizePreset;
12044             that.mCustomContentHeight = this.mCustomContentHeight;
12045             that.mGravity = this.mGravity;
12046             that.mHintScreenTimeout = this.mHintScreenTimeout;
12047             that.mDismissalId = this.mDismissalId;
12048             that.mBridgeTag = this.mBridgeTag;
12049             return that;
12050         }
12051 
12052         /**
12053          * Add a wearable action to this notification.
12054          *
12055          * <p>When wearable actions are added using this method, the set of actions that
12056          * show on a wearable device splits from devices that only show actions added
12057          * using {@link android.app.Notification.Builder#addAction}. This allows for customization
12058          * of which actions display on different devices.
12059          *
12060          * @param action the action to add to this notification
12061          * @return this object for method chaining
12062          * @see android.app.Notification.Action
12063          */
addAction(Action action)12064         public WearableExtender addAction(Action action) {
12065             mActions.add(action);
12066             return this;
12067         }
12068 
12069         /**
12070          * Adds wearable actions to this notification.
12071          *
12072          * <p>When wearable actions are added using this method, the set of actions that
12073          * show on a wearable device splits from devices that only show actions added
12074          * using {@link android.app.Notification.Builder#addAction}. This allows for customization
12075          * of which actions display on different devices.
12076          *
12077          * @param actions the actions to add to this notification
12078          * @return this object for method chaining
12079          * @see android.app.Notification.Action
12080          */
addActions(List<Action> actions)12081         public WearableExtender addActions(List<Action> actions) {
12082             mActions.addAll(actions);
12083             return this;
12084         }
12085 
12086         /**
12087          * Clear all wearable actions present on this builder.
12088          * @return this object for method chaining.
12089          * @see #addAction
12090          */
clearActions()12091         public WearableExtender clearActions() {
12092             mActions.clear();
12093             return this;
12094         }
12095 
12096         /**
12097          * Get the wearable actions present on this notification.
12098          */
getActions()12099         public List<Action> getActions() {
12100             return mActions;
12101         }
12102 
12103         /**
12104          * Set an intent to launch inside of an activity view when displaying
12105          * this notification. The {@link PendingIntent} provided should be for an activity.
12106          *
12107          * <pre class="prettyprint">
12108          * Intent displayIntent = new Intent(context, MyDisplayActivity.class);
12109          * PendingIntent displayPendingIntent = PendingIntent.getActivity(context,
12110          *         0, displayIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
12111          * Notification notif = new Notification.Builder(context)
12112          *         .extend(new Notification.WearableExtender()
12113          *                 .setDisplayIntent(displayPendingIntent)
12114          *                 .setCustomSizePreset(Notification.WearableExtender.SIZE_MEDIUM))
12115          *         .build();</pre>
12116          *
12117          * <p>The activity to launch needs to allow embedding, must be exported, and
12118          * should have an empty task affinity. It is also recommended to use the device
12119          * default light theme.
12120          *
12121          * <p>Example AndroidManifest.xml entry:
12122          * <pre class="prettyprint">
12123          * &lt;activity android:name=&quot;com.example.MyDisplayActivity&quot;
12124          *     android:exported=&quot;true&quot;
12125          *     android:allowEmbedded=&quot;true&quot;
12126          *     android:taskAffinity=&quot;&quot;
12127          *     android:theme=&quot;@android:style/Theme.DeviceDefault.Light&quot; /&gt;</pre>
12128          *
12129          * @param intent the {@link PendingIntent} for an activity
12130          * @return this object for method chaining
12131          * @see android.app.Notification.WearableExtender#getDisplayIntent
12132          * @deprecated Display intents are no longer supported.
12133          */
12134         @Deprecated
setDisplayIntent(PendingIntent intent)12135         public WearableExtender setDisplayIntent(PendingIntent intent) {
12136             mDisplayIntent = intent;
12137             return this;
12138         }
12139 
12140         /**
12141          * Get the intent to launch inside of an activity view when displaying this
12142          * notification. This {@code PendingIntent} should be for an activity.
12143          *
12144          * @deprecated Display intents are no longer supported.
12145          */
12146         @Deprecated
getDisplayIntent()12147         public PendingIntent getDisplayIntent() {
12148             return mDisplayIntent;
12149         }
12150 
12151         /**
12152          * Add an additional page of content to display with this notification. The current
12153          * notification forms the first page, and pages added using this function form
12154          * subsequent pages. This field can be used to separate a notification into multiple
12155          * sections.
12156          *
12157          * @param page the notification to add as another page
12158          * @return this object for method chaining
12159          * @see android.app.Notification.WearableExtender#getPages
12160          * @deprecated Multiple content pages are no longer supported.
12161          */
12162         @Deprecated
addPage(Notification page)12163         public WearableExtender addPage(Notification page) {
12164             mPages.add(page);
12165             return this;
12166         }
12167 
12168         /**
12169          * Add additional pages of content to display with this notification. The current
12170          * notification forms the first page, and pages added using this function form
12171          * subsequent pages. This field can be used to separate a notification into multiple
12172          * sections.
12173          *
12174          * @param pages a list of notifications
12175          * @return this object for method chaining
12176          * @see android.app.Notification.WearableExtender#getPages
12177          * @deprecated Multiple content pages are no longer supported.
12178          */
12179         @Deprecated
addPages(List<Notification> pages)12180         public WearableExtender addPages(List<Notification> pages) {
12181             mPages.addAll(pages);
12182             return this;
12183         }
12184 
12185         /**
12186          * Clear all additional pages present on this builder.
12187          * @return this object for method chaining.
12188          * @see #addPage
12189          * @deprecated Multiple content pages are no longer supported.
12190          */
12191         @Deprecated
clearPages()12192         public WearableExtender clearPages() {
12193             mPages.clear();
12194             return this;
12195         }
12196 
12197         /**
12198          * Get the array of additional pages of content for displaying this notification. The
12199          * current notification forms the first page, and elements within this array form
12200          * subsequent pages. This field can be used to separate a notification into multiple
12201          * sections.
12202          * @return the pages for this notification
12203          * @deprecated Multiple content pages are no longer supported.
12204          */
12205         @Deprecated
getPages()12206         public List<Notification> getPages() {
12207             return mPages;
12208         }
12209 
12210         /**
12211          * Set a background image to be displayed behind the notification content.
12212          * Contrary to the {@link android.app.Notification.BigPictureStyle}, this background
12213          * will work with any notification style.
12214          *
12215          * @param background the background bitmap
12216          * @return this object for method chaining
12217          * @removed Not functional since {@link Build.VERSION_CODES#VANILLA_ICE_CREAM}.
12218          *          The wearable background is not used by wearables anymore and uses up
12219          *          unnecessary memory.
12220          */
12221         @Deprecated
setBackground(Bitmap background)12222         public WearableExtender setBackground(Bitmap background) {
12223             // Keeping WearableExtender backgrounds in memory despite them being deprecated has
12224             // added noticeable increase in system server and system ui memory usage. After
12225             // target VERSION_CODE#VANILLA_ICE_CREAM the background will not be populated anymore.
12226             if (CompatChanges.isChangeEnabled(WEARABLE_EXTENDER_BACKGROUND_BLOCKED)) {
12227                 Log.d(TAG, "Use of background in WearableExtenders has been deprecated and "
12228                         + "will not be populated anymore.");
12229             } else {
12230                 mBackground = background;
12231             }
12232             return this;
12233         }
12234 
12235         /**
12236          * Get a background image to be displayed behind the notification content.
12237          * Contrary to the {@link android.app.Notification.BigPictureStyle}, this background
12238          * will work with any notification style.
12239          *
12240          * @return the background image
12241          * @removed Not functional since {@link Build.VERSION_CODES#VANILLA_ICE_CREAM}. The
12242          *          wearable background is not used by wearables anymore and uses up
12243          *          unnecessary memory.
12244          */
12245         @Deprecated
getBackground()12246         public Bitmap getBackground() {
12247             Log.w(TAG, "Use of background in WearableExtender has been removed, returning null.");
12248             return mBackground;
12249         }
12250 
12251         /**
12252          * Set an icon that goes with the content of this notification.
12253          */
12254         @Deprecated
setContentIcon(int icon)12255         public WearableExtender setContentIcon(int icon) {
12256             mContentIcon = icon;
12257             return this;
12258         }
12259 
12260         /**
12261          * Get an icon that goes with the content of this notification.
12262          */
12263         @Deprecated
getContentIcon()12264         public int getContentIcon() {
12265             return mContentIcon;
12266         }
12267 
12268         /**
12269          * Set the gravity that the content icon should have within the notification display.
12270          * Supported values include {@link android.view.Gravity#START} and
12271          * {@link android.view.Gravity#END}. The default value is {@link android.view.Gravity#END}.
12272          * @see #setContentIcon
12273          */
12274         @Deprecated
setContentIconGravity(int contentIconGravity)12275         public WearableExtender setContentIconGravity(int contentIconGravity) {
12276             mContentIconGravity = contentIconGravity;
12277             return this;
12278         }
12279 
12280         /**
12281          * Get the gravity that the content icon should have within the notification display.
12282          * Supported values include {@link android.view.Gravity#START} and
12283          * {@link android.view.Gravity#END}. The default value is {@link android.view.Gravity#END}.
12284          * @see #getContentIcon
12285          */
12286         @Deprecated
getContentIconGravity()12287         public int getContentIconGravity() {
12288             return mContentIconGravity;
12289         }
12290 
12291         /**
12292          * Set an action from this notification's actions as the primary action. If the action has a
12293          * {@link RemoteInput} associated with it, shortcuts to the options for that input are shown
12294          * directly on the notification.
12295          *
12296          * @param actionIndex The index of the primary action.
12297          *                    If wearable actions were added to the main notification, this index
12298          *                    will apply to that list, otherwise it will apply to the regular
12299          *                    actions list.
12300          */
setContentAction(int actionIndex)12301         public WearableExtender setContentAction(int actionIndex) {
12302             mContentActionIndex = actionIndex;
12303             return this;
12304         }
12305 
12306         /**
12307          * Get the index of the notification action, if any, that was specified as the primary
12308          * action.
12309          *
12310          * <p>If wearable specific actions were added to the main notification, this index will
12311          * apply to that list, otherwise it will apply to the regular actions list.
12312          *
12313          * @return the action index or {@link #UNSET_ACTION_INDEX} if no action was selected.
12314          */
getContentAction()12315         public int getContentAction() {
12316             return mContentActionIndex;
12317         }
12318 
12319         /**
12320          * Set the gravity that this notification should have within the available viewport space.
12321          * Supported values include {@link android.view.Gravity#TOP},
12322          * {@link android.view.Gravity#CENTER_VERTICAL} and {@link android.view.Gravity#BOTTOM}.
12323          * The default value is {@link android.view.Gravity#BOTTOM}.
12324          */
12325         @Deprecated
setGravity(int gravity)12326         public WearableExtender setGravity(int gravity) {
12327             mGravity = gravity;
12328             return this;
12329         }
12330 
12331         /**
12332          * Get the gravity that this notification should have within the available viewport space.
12333          * Supported values include {@link android.view.Gravity#TOP},
12334          * {@link android.view.Gravity#CENTER_VERTICAL} and {@link android.view.Gravity#BOTTOM}.
12335          * The default value is {@link android.view.Gravity#BOTTOM}.
12336          */
12337         @Deprecated
getGravity()12338         public int getGravity() {
12339             return mGravity;
12340         }
12341 
12342         /**
12343          * Set the custom size preset for the display of this notification out of the available
12344          * presets found in {@link android.app.Notification.WearableExtender}, e.g.
12345          * {@link #SIZE_LARGE}.
12346          * <p>Some custom size presets are only applicable for custom display notifications created
12347          * using {@link android.app.Notification.WearableExtender#setDisplayIntent}. Check the
12348          * documentation for the preset in question. See also
12349          * {@link #setCustomContentHeight} and {@link #getCustomSizePreset}.
12350          */
12351         @Deprecated
setCustomSizePreset(int sizePreset)12352         public WearableExtender setCustomSizePreset(int sizePreset) {
12353             mCustomSizePreset = sizePreset;
12354             return this;
12355         }
12356 
12357         /**
12358          * Get the custom size preset for the display of this notification out of the available
12359          * presets found in {@link android.app.Notification.WearableExtender}, e.g.
12360          * {@link #SIZE_LARGE}.
12361          * <p>Some custom size presets are only applicable for custom display notifications created
12362          * using {@link #setDisplayIntent}. Check the documentation for the preset in question.
12363          * See also {@link #setCustomContentHeight} and {@link #setCustomSizePreset}.
12364          */
12365         @Deprecated
getCustomSizePreset()12366         public int getCustomSizePreset() {
12367             return mCustomSizePreset;
12368         }
12369 
12370         /**
12371          * Set the custom height in pixels for the display of this notification's content.
12372          * <p>This option is only available for custom display notifications created
12373          * using {@link android.app.Notification.WearableExtender#setDisplayIntent}. See also
12374          * {@link android.app.Notification.WearableExtender#setCustomSizePreset} and
12375          * {@link #getCustomContentHeight}.
12376          */
12377         @Deprecated
setCustomContentHeight(int height)12378         public WearableExtender setCustomContentHeight(int height) {
12379             mCustomContentHeight = height;
12380             return this;
12381         }
12382 
12383         /**
12384          * Get the custom height in pixels for the display of this notification's content.
12385          * <p>This option is only available for custom display notifications created
12386          * using {@link #setDisplayIntent}. See also {@link #setCustomSizePreset} and
12387          * {@link #setCustomContentHeight}.
12388          */
12389         @Deprecated
getCustomContentHeight()12390         public int getCustomContentHeight() {
12391             return mCustomContentHeight;
12392         }
12393 
12394         /**
12395          * Set whether the scrolling position for the contents of this notification should start
12396          * at the bottom of the contents instead of the top when the contents are too long to
12397          * display within the screen.  Default is false (start scroll at the top).
12398          */
setStartScrollBottom(boolean startScrollBottom)12399         public WearableExtender setStartScrollBottom(boolean startScrollBottom) {
12400             setFlag(FLAG_START_SCROLL_BOTTOM, startScrollBottom);
12401             return this;
12402         }
12403 
12404         /**
12405          * Get whether the scrolling position for the contents of this notification should start
12406          * at the bottom of the contents instead of the top when the contents are too long to
12407          * display within the screen. Default is false (start scroll at the top).
12408          */
getStartScrollBottom()12409         public boolean getStartScrollBottom() {
12410             return (mFlags & FLAG_START_SCROLL_BOTTOM) != 0;
12411         }
12412 
12413         /**
12414          * Set whether the content intent is available when the wearable device is not connected
12415          * to a companion device.  The user can still trigger this intent when the wearable device
12416          * is offline, but a visual hint will indicate that the content intent may not be available.
12417          * Defaults to true.
12418          */
setContentIntentAvailableOffline( boolean contentIntentAvailableOffline)12419         public WearableExtender setContentIntentAvailableOffline(
12420                 boolean contentIntentAvailableOffline) {
12421             setFlag(FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE, contentIntentAvailableOffline);
12422             return this;
12423         }
12424 
12425         /**
12426          * Get whether the content intent is available when the wearable device is not connected
12427          * to a companion device.  The user can still trigger this intent when the wearable device
12428          * is offline, but a visual hint will indicate that the content intent may not be available.
12429          * Defaults to true.
12430          */
getContentIntentAvailableOffline()12431         public boolean getContentIntentAvailableOffline() {
12432             return (mFlags & FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE) != 0;
12433         }
12434 
12435         /**
12436          * Set a hint that this notification's icon should not be displayed.
12437          * @param hintHideIcon {@code true} to hide the icon, {@code false} otherwise.
12438          * @return this object for method chaining
12439          */
12440         @Deprecated
setHintHideIcon(boolean hintHideIcon)12441         public WearableExtender setHintHideIcon(boolean hintHideIcon) {
12442             setFlag(FLAG_HINT_HIDE_ICON, hintHideIcon);
12443             return this;
12444         }
12445 
12446         /**
12447          * Get a hint that this notification's icon should not be displayed.
12448          * @return {@code true} if this icon should not be displayed, false otherwise.
12449          * The default value is {@code false} if this was never set.
12450          */
12451         @Deprecated
getHintHideIcon()12452         public boolean getHintHideIcon() {
12453             return (mFlags & FLAG_HINT_HIDE_ICON) != 0;
12454         }
12455 
12456         /**
12457          * Set a visual hint that only the background image of this notification should be
12458          * displayed, and other semantic content should be hidden. This hint is only applicable
12459          * to sub-pages added using {@link #addPage}.
12460          */
12461         @Deprecated
setHintShowBackgroundOnly(boolean hintShowBackgroundOnly)12462         public WearableExtender setHintShowBackgroundOnly(boolean hintShowBackgroundOnly) {
12463             setFlag(FLAG_HINT_SHOW_BACKGROUND_ONLY, hintShowBackgroundOnly);
12464             return this;
12465         }
12466 
12467         /**
12468          * Get a visual hint that only the background image of this notification should be
12469          * displayed, and other semantic content should be hidden. This hint is only applicable
12470          * to sub-pages added using {@link android.app.Notification.WearableExtender#addPage}.
12471          */
12472         @Deprecated
getHintShowBackgroundOnly()12473         public boolean getHintShowBackgroundOnly() {
12474             return (mFlags & FLAG_HINT_SHOW_BACKGROUND_ONLY) != 0;
12475         }
12476 
12477         /**
12478          * Set a hint that this notification's background should not be clipped if possible,
12479          * and should instead be resized to fully display on the screen, retaining the aspect
12480          * ratio of the image. This can be useful for images like barcodes or qr codes.
12481          * @param hintAvoidBackgroundClipping {@code true} to avoid clipping if possible.
12482          * @return this object for method chaining
12483          */
12484         @Deprecated
setHintAvoidBackgroundClipping( boolean hintAvoidBackgroundClipping)12485         public WearableExtender setHintAvoidBackgroundClipping(
12486                 boolean hintAvoidBackgroundClipping) {
12487             setFlag(FLAG_HINT_AVOID_BACKGROUND_CLIPPING, hintAvoidBackgroundClipping);
12488             return this;
12489         }
12490 
12491         /**
12492          * Get a hint that this notification's background should not be clipped if possible,
12493          * and should instead be resized to fully display on the screen, retaining the aspect
12494          * ratio of the image. This can be useful for images like barcodes or qr codes.
12495          * @return {@code true} if it's ok if the background is clipped on the screen, false
12496          * otherwise. The default value is {@code false} if this was never set.
12497          */
12498         @Deprecated
getHintAvoidBackgroundClipping()12499         public boolean getHintAvoidBackgroundClipping() {
12500             return (mFlags & FLAG_HINT_AVOID_BACKGROUND_CLIPPING) != 0;
12501         }
12502 
12503         /**
12504          * Set a hint that the screen should remain on for at least this duration when
12505          * this notification is displayed on the screen.
12506          * @param timeout The requested screen timeout in milliseconds. Can also be either
12507          *     {@link #SCREEN_TIMEOUT_SHORT} or {@link #SCREEN_TIMEOUT_LONG}.
12508          * @return this object for method chaining
12509          */
12510         @Deprecated
setHintScreenTimeout(int timeout)12511         public WearableExtender setHintScreenTimeout(int timeout) {
12512             mHintScreenTimeout = timeout;
12513             return this;
12514         }
12515 
12516         /**
12517          * Get the duration, in milliseconds, that the screen should remain on for
12518          * when this notification is displayed.
12519          * @return the duration in milliseconds if > 0, or either one of the sentinel values
12520          *     {@link #SCREEN_TIMEOUT_SHORT} or {@link #SCREEN_TIMEOUT_LONG}.
12521          */
12522         @Deprecated
getHintScreenTimeout()12523         public int getHintScreenTimeout() {
12524             return mHintScreenTimeout;
12525         }
12526 
12527         /**
12528          * Set a hint that this notification's {@link BigPictureStyle} (if present) should be
12529          * converted to low-bit and displayed in ambient mode, especially useful for barcodes and
12530          * qr codes, as well as other simple black-and-white tickets.
12531          * @param hintAmbientBigPicture {@code true} to enable converstion and ambient.
12532          * @return this object for method chaining
12533          * @deprecated This feature is no longer supported.
12534          */
12535         @Deprecated
setHintAmbientBigPicture(boolean hintAmbientBigPicture)12536         public WearableExtender setHintAmbientBigPicture(boolean hintAmbientBigPicture) {
12537             setFlag(FLAG_BIG_PICTURE_AMBIENT, hintAmbientBigPicture);
12538             return this;
12539         }
12540 
12541         /**
12542          * Get a hint that this notification's {@link BigPictureStyle} (if present) should be
12543          * converted to low-bit and displayed in ambient mode, especially useful for barcodes and
12544          * qr codes, as well as other simple black-and-white tickets.
12545          * @return {@code true} if it should be displayed in ambient, false otherwise
12546          * otherwise. The default value is {@code false} if this was never set.
12547          * @deprecated This feature is no longer supported.
12548          */
12549         @Deprecated
getHintAmbientBigPicture()12550         public boolean getHintAmbientBigPicture() {
12551             return (mFlags & FLAG_BIG_PICTURE_AMBIENT) != 0;
12552         }
12553 
12554         /**
12555          * Set a hint that this notification's content intent will launch an {@link Activity}
12556          * directly, telling the platform that it can generate the appropriate transitions.
12557          * @param hintContentIntentLaunchesActivity {@code true} if the content intent will launch
12558          * an activity and transitions should be generated, false otherwise.
12559          * @return this object for method chaining
12560          */
setHintContentIntentLaunchesActivity( boolean hintContentIntentLaunchesActivity)12561         public WearableExtender setHintContentIntentLaunchesActivity(
12562                 boolean hintContentIntentLaunchesActivity) {
12563             setFlag(FLAG_HINT_CONTENT_INTENT_LAUNCHES_ACTIVITY, hintContentIntentLaunchesActivity);
12564             return this;
12565         }
12566 
12567         /**
12568          * Get a hint that this notification's content intent will launch an {@link Activity}
12569          * directly, telling the platform that it can generate the appropriate transitions
12570          * @return {@code true} if the content intent will launch an activity and transitions should
12571          * be generated, false otherwise. The default value is {@code false} if this was never set.
12572          */
getHintContentIntentLaunchesActivity()12573         public boolean getHintContentIntentLaunchesActivity() {
12574             return (mFlags & FLAG_HINT_CONTENT_INTENT_LAUNCHES_ACTIVITY) != 0;
12575         }
12576 
12577         /**
12578          * Sets the dismissal id for this notification. If a notification is posted with a
12579          * dismissal id, then when that notification is canceled, notifications on other wearables
12580          * and the paired Android phone having that same dismissal id will also be canceled. See
12581          * <a href="{@docRoot}wear/notifications/index.html">Adding Wearable Features to
12582          * Notifications</a> for more information.
12583          * @param dismissalId the dismissal id of the notification.
12584          * @return this object for method chaining
12585          */
setDismissalId(String dismissalId)12586         public WearableExtender setDismissalId(String dismissalId) {
12587             mDismissalId = dismissalId;
12588             return this;
12589         }
12590 
12591         /**
12592          * Returns the dismissal id of the notification.
12593          * @return the dismissal id of the notification or null if it has not been set.
12594          */
getDismissalId()12595         public String getDismissalId() {
12596             return mDismissalId;
12597         }
12598 
12599         /**
12600          * Sets a bridge tag for this notification. A bridge tag can be set for notifications
12601          * posted from a phone to provide finer-grained control on what notifications are bridged
12602          * to wearables. See <a href="{@docRoot}wear/notifications/index.html">Adding Wearable
12603          * Features to Notifications</a> for more information.
12604          * @param bridgeTag the bridge tag of the notification.
12605          * @return this object for method chaining
12606          */
setBridgeTag(String bridgeTag)12607         public WearableExtender setBridgeTag(String bridgeTag) {
12608             mBridgeTag = bridgeTag;
12609             return this;
12610         }
12611 
12612         /**
12613          * Returns the bridge tag of the notification.
12614          * @return the bridge tag or null if not present.
12615          */
getBridgeTag()12616         public String getBridgeTag() {
12617             return mBridgeTag;
12618         }
12619 
setFlag(int mask, boolean value)12620         private void setFlag(int mask, boolean value) {
12621             if (value) {
12622                 mFlags |= mask;
12623             } else {
12624                 mFlags &= ~mask;
12625             }
12626         }
12627 
visitUris(@onNull Consumer<Uri> visitor)12628         private void visitUris(@NonNull Consumer<Uri> visitor) {
12629             for (Action action : mActions) {
12630                 action.visitUris(visitor);
12631             }
12632         }
12633     }
12634 
12635     /**
12636      * <p>Helper class to add Android Auto extensions to notifications. To create a notification
12637      * with car extensions:
12638      *
12639      * <ol>
12640      *  <li>Create an {@link Notification.Builder}, setting any desired
12641      *  properties.
12642      *  <li>Create a {@link CarExtender}.
12643      *  <li>Set car-specific properties using the {@code add} and {@code set} methods of
12644      *  {@link CarExtender}.
12645      *  <li>Call {@link Notification.Builder#extend(Notification.Extender)}
12646      *  to apply the extensions to a notification.
12647      * </ol>
12648      *
12649      * <pre class="prettyprint">
12650      * Notification notification = new Notification.Builder(context)
12651      *         ...
12652      *         .extend(new CarExtender()
12653      *                 .set*(...))
12654      *         .build();
12655      * </pre>
12656      *
12657      * <p>Car extensions can be accessed on an existing notification by using the
12658      * {@code CarExtender(Notification)} constructor, and then using the {@code get} methods
12659      * to access values.
12660      */
12661     public static final class CarExtender implements Extender {
12662         private static final String TAG = "CarExtender";
12663 
12664         private static final String EXTRA_CAR_EXTENDER = "android.car.EXTENSIONS";
12665         private static final String EXTRA_LARGE_ICON = "large_icon";
12666         private static final String EXTRA_CONVERSATION = "car_conversation";
12667         private static final String EXTRA_COLOR = "app_color";
12668 
12669         private Bitmap mLargeIcon;
12670         private UnreadConversation mUnreadConversation;
12671         private int mColor = Notification.COLOR_DEFAULT;
12672 
12673         /**
12674          * Create a {@link CarExtender} with default options.
12675          */
CarExtender()12676         public CarExtender() {
12677         }
12678 
12679         /**
12680          * Create a {@link CarExtender} from the CarExtender options of an existing Notification.
12681          *
12682          * @param notif The notification from which to copy options.
12683          */
CarExtender(Notification notif)12684         public CarExtender(Notification notif) {
12685             Bundle carBundle = notif.extras == null ?
12686                     null : notif.extras.getBundle(EXTRA_CAR_EXTENDER);
12687             if (carBundle != null) {
12688                 mLargeIcon = carBundle.getParcelable(EXTRA_LARGE_ICON, Bitmap.class);
12689                 mColor = carBundle.getInt(EXTRA_COLOR, Notification.COLOR_DEFAULT);
12690 
12691                 Bundle b = carBundle.getBundle(EXTRA_CONVERSATION);
12692                 mUnreadConversation = UnreadConversation.getUnreadConversationFromBundle(b);
12693             }
12694         }
12695 
12696         /**
12697          * Apply car extensions to a notification that is being built. This is typically called by
12698          * the {@link Notification.Builder#extend(Notification.Extender)}
12699          * method of {@link Notification.Builder}.
12700          */
12701         @Override
extend(Notification.Builder builder)12702         public Notification.Builder extend(Notification.Builder builder) {
12703             Bundle carExtensions = new Bundle();
12704 
12705             if (mLargeIcon != null) {
12706                 carExtensions.putParcelable(EXTRA_LARGE_ICON, mLargeIcon);
12707             }
12708             if (mColor != Notification.COLOR_DEFAULT) {
12709                 carExtensions.putInt(EXTRA_COLOR, mColor);
12710             }
12711 
12712             if (mUnreadConversation != null) {
12713                 Bundle b = mUnreadConversation.getBundleForUnreadConversation();
12714                 carExtensions.putBundle(EXTRA_CONVERSATION, b);
12715             }
12716 
12717             builder.getExtras().putBundle(EXTRA_CAR_EXTENDER, carExtensions);
12718             return builder;
12719         }
12720 
12721         /**
12722          * Sets the accent color to use when Android Auto presents the notification.
12723          *
12724          * Android Auto uses the color set with {@link Notification.Builder#setColor(int)}
12725          * to accent the displayed notification. However, not all colors are acceptable in an
12726          * automotive setting. This method can be used to override the color provided in the
12727          * notification in such a situation.
12728          */
setColor(@olorInt int color)12729         public CarExtender setColor(@ColorInt int color) {
12730             mColor = color;
12731             return this;
12732         }
12733 
12734         /**
12735          * Gets the accent color.
12736          *
12737          * @see #setColor
12738          */
12739         @ColorInt
getColor()12740         public int getColor() {
12741             return mColor;
12742         }
12743 
12744         /**
12745          * Sets the large icon of the car notification.
12746          *
12747          * If no large icon is set in the extender, Android Auto will display the icon
12748          * specified by {@link Notification.Builder#setLargeIcon(android.graphics.Bitmap)}
12749          *
12750          * @param largeIcon The large icon to use in the car notification.
12751          * @return This object for method chaining.
12752          */
setLargeIcon(Bitmap largeIcon)12753         public CarExtender setLargeIcon(Bitmap largeIcon) {
12754             mLargeIcon = largeIcon;
12755             return this;
12756         }
12757 
12758         /**
12759          * Gets the large icon used in this car notification, or null if no icon has been set.
12760          *
12761          * @return The large icon for the car notification.
12762          * @see CarExtender#setLargeIcon
12763          */
getLargeIcon()12764         public Bitmap getLargeIcon() {
12765             return mLargeIcon;
12766         }
12767 
12768         /**
12769          * Sets the unread conversation in a message notification.
12770          *
12771          * @param unreadConversation The unread part of the conversation this notification conveys.
12772          * @return This object for method chaining.
12773          */
setUnreadConversation(UnreadConversation unreadConversation)12774         public CarExtender setUnreadConversation(UnreadConversation unreadConversation) {
12775             mUnreadConversation = unreadConversation;
12776             return this;
12777         }
12778 
12779         /**
12780          * Returns the unread conversation conveyed by this notification.
12781          *
12782          * @see #setUnreadConversation(UnreadConversation)
12783          */
getUnreadConversation()12784         public UnreadConversation getUnreadConversation() {
12785             return mUnreadConversation;
12786         }
12787 
12788         /**
12789          * A class which holds the unread messages from a conversation.
12790          */
12791         public static class UnreadConversation {
12792             private static final String KEY_AUTHOR = "author";
12793             private static final String KEY_TEXT = "text";
12794             private static final String KEY_MESSAGES = "messages";
12795             static final String KEY_REMOTE_INPUT = "remote_input";
12796             static final String KEY_ON_REPLY = "on_reply";
12797             static final String KEY_ON_READ = "on_read";
12798             private static final String KEY_PARTICIPANTS = "participants";
12799             private static final String KEY_TIMESTAMP = "timestamp";
12800 
12801             private final String[] mMessages;
12802             private final RemoteInput mRemoteInput;
12803             private final PendingIntent mReplyPendingIntent;
12804             private final PendingIntent mReadPendingIntent;
12805             private final String[] mParticipants;
12806             private final long mLatestTimestamp;
12807 
UnreadConversation(String[] messages, RemoteInput remoteInput, PendingIntent replyPendingIntent, PendingIntent readPendingIntent, String[] participants, long latestTimestamp)12808             UnreadConversation(String[] messages, RemoteInput remoteInput,
12809                     PendingIntent replyPendingIntent, PendingIntent readPendingIntent,
12810                     String[] participants, long latestTimestamp) {
12811                 mMessages = messages;
12812                 mRemoteInput = remoteInput;
12813                 mReadPendingIntent = readPendingIntent;
12814                 mReplyPendingIntent = replyPendingIntent;
12815                 mParticipants = participants;
12816                 mLatestTimestamp = latestTimestamp;
12817             }
12818 
12819             /**
12820              * Gets the list of messages conveyed by this notification.
12821              */
getMessages()12822             public String[] getMessages() {
12823                 return mMessages;
12824             }
12825 
12826             /**
12827              * Gets the remote input that will be used to convey the response to a message list, or
12828              * null if no such remote input exists.
12829              */
getRemoteInput()12830             public RemoteInput getRemoteInput() {
12831                 return mRemoteInput;
12832             }
12833 
12834             /**
12835              * Gets the pending intent that will be triggered when the user replies to this
12836              * notification.
12837              */
getReplyPendingIntent()12838             public PendingIntent getReplyPendingIntent() {
12839                 return mReplyPendingIntent;
12840             }
12841 
12842             /**
12843              * Gets the pending intent that Android Auto will send after it reads aloud all messages
12844              * in this object's message list.
12845              */
getReadPendingIntent()12846             public PendingIntent getReadPendingIntent() {
12847                 return mReadPendingIntent;
12848             }
12849 
12850             /**
12851              * Gets the participants in the conversation.
12852              */
getParticipants()12853             public String[] getParticipants() {
12854                 return mParticipants;
12855             }
12856 
12857             /**
12858              * Gets the firs participant in the conversation.
12859              */
getParticipant()12860             public String getParticipant() {
12861                 return mParticipants.length > 0 ? mParticipants[0] : null;
12862             }
12863 
12864             /**
12865              * Gets the timestamp of the conversation.
12866              */
getLatestTimestamp()12867             public long getLatestTimestamp() {
12868                 return mLatestTimestamp;
12869             }
12870 
getBundleForUnreadConversation()12871             Bundle getBundleForUnreadConversation() {
12872                 Bundle b = new Bundle();
12873                 String author = null;
12874                 if (mParticipants != null && mParticipants.length > 1) {
12875                     author = mParticipants[0];
12876                 }
12877                 Parcelable[] messages = new Parcelable[mMessages.length];
12878                 for (int i = 0; i < messages.length; i++) {
12879                     Bundle m = new Bundle();
12880                     m.putString(KEY_TEXT, mMessages[i]);
12881                     m.putString(KEY_AUTHOR, author);
12882                     messages[i] = m;
12883                 }
12884                 b.putParcelableArray(KEY_MESSAGES, messages);
12885                 if (mRemoteInput != null) {
12886                     b.putParcelable(KEY_REMOTE_INPUT, mRemoteInput);
12887                 }
12888                 b.putParcelable(KEY_ON_REPLY, mReplyPendingIntent);
12889                 b.putParcelable(KEY_ON_READ, mReadPendingIntent);
12890                 b.putStringArray(KEY_PARTICIPANTS, mParticipants);
12891                 b.putLong(KEY_TIMESTAMP, mLatestTimestamp);
12892                 return b;
12893             }
12894 
getUnreadConversationFromBundle(Bundle b)12895             static UnreadConversation getUnreadConversationFromBundle(Bundle b) {
12896                 if (b == null) {
12897                     return null;
12898                 }
12899                 Parcelable[] parcelableMessages = b.getParcelableArray(KEY_MESSAGES,
12900                         Parcelable.class);
12901                 String[] messages = null;
12902                 if (parcelableMessages != null) {
12903                     String[] tmp = new String[parcelableMessages.length];
12904                     boolean success = true;
12905                     for (int i = 0; i < tmp.length; i++) {
12906                         if (!(parcelableMessages[i] instanceof Bundle)) {
12907                             success = false;
12908                             break;
12909                         }
12910                         tmp[i] = ((Bundle) parcelableMessages[i]).getString(KEY_TEXT);
12911                         if (tmp[i] == null) {
12912                             success = false;
12913                             break;
12914                         }
12915                     }
12916                     if (success) {
12917                         messages = tmp;
12918                     } else {
12919                         return null;
12920                     }
12921                 }
12922 
12923                 PendingIntent onRead = b.getParcelable(KEY_ON_READ, PendingIntent.class);
12924                 PendingIntent onReply = b.getParcelable(KEY_ON_REPLY, PendingIntent.class);
12925 
12926                 RemoteInput remoteInput = b.getParcelable(KEY_REMOTE_INPUT, RemoteInput.class);
12927 
12928                 String[] participants = b.getStringArray(KEY_PARTICIPANTS);
12929                 if (participants == null || participants.length != 1) {
12930                     return null;
12931                 }
12932 
12933                 return new UnreadConversation(messages,
12934                         remoteInput,
12935                         onReply,
12936                         onRead,
12937                         participants, b.getLong(KEY_TIMESTAMP));
12938             }
12939         };
12940 
12941         /**
12942          * Builder class for {@link CarExtender.UnreadConversation} objects.
12943          */
12944         public static class Builder {
12945             private final List<String> mMessages = new ArrayList<String>();
12946             private final String mParticipant;
12947             private RemoteInput mRemoteInput;
12948             private PendingIntent mReadPendingIntent;
12949             private PendingIntent mReplyPendingIntent;
12950             private long mLatestTimestamp;
12951 
12952             /**
12953              * Constructs a new builder for {@link CarExtender.UnreadConversation}.
12954              *
12955              * @param name The name of the other participant in the conversation.
12956              */
Builder(String name)12957             public Builder(String name) {
12958                 mParticipant = name;
12959             }
12960 
12961             /**
12962              * Appends a new unread message to the list of messages for this conversation.
12963              *
12964              * The messages should be added from oldest to newest.
12965              *
12966              * @param message The text of the new unread message.
12967              * @return This object for method chaining.
12968              */
addMessage(String message)12969             public Builder addMessage(String message) {
12970                 mMessages.add(message);
12971                 return this;
12972             }
12973 
12974             /**
12975              * Sets the pending intent and remote input which will convey the reply to this
12976              * notification.
12977              *
12978              * @param pendingIntent The pending intent which will be triggered on a reply.
12979              * @param remoteInput The remote input parcelable which will carry the reply.
12980              * @return This object for method chaining.
12981              *
12982              * @see CarExtender.UnreadConversation#getRemoteInput
12983              * @see CarExtender.UnreadConversation#getReplyPendingIntent
12984              */
setReplyAction( PendingIntent pendingIntent, RemoteInput remoteInput)12985             public Builder setReplyAction(
12986                     PendingIntent pendingIntent, RemoteInput remoteInput) {
12987                 mRemoteInput = remoteInput;
12988                 mReplyPendingIntent = pendingIntent;
12989 
12990                 return this;
12991             }
12992 
12993             /**
12994              * Sets the pending intent that will be sent once the messages in this notification
12995              * are read.
12996              *
12997              * @param pendingIntent The pending intent to use.
12998              * @return This object for method chaining.
12999              */
setReadPendingIntent(PendingIntent pendingIntent)13000             public Builder setReadPendingIntent(PendingIntent pendingIntent) {
13001                 mReadPendingIntent = pendingIntent;
13002                 return this;
13003             }
13004 
13005             /**
13006              * Sets the timestamp of the most recent message in an unread conversation.
13007              *
13008              * If a messaging notification has been posted by your application and has not
13009              * yet been cancelled, posting a later notification with the same id and tag
13010              * but without a newer timestamp may result in Android Auto not displaying a
13011              * heads up notification for the later notification.
13012              *
13013              * @param timestamp The timestamp of the most recent message in the conversation.
13014              * @return This object for method chaining.
13015              */
setLatestTimestamp(long timestamp)13016             public Builder setLatestTimestamp(long timestamp) {
13017                 mLatestTimestamp = timestamp;
13018                 return this;
13019             }
13020 
13021             /**
13022              * Builds a new unread conversation object.
13023              *
13024              * @return The new unread conversation object.
13025              */
build()13026             public UnreadConversation build() {
13027                 String[] messages = mMessages.toArray(new String[mMessages.size()]);
13028                 String[] participants = { mParticipant };
13029                 return new UnreadConversation(messages, mRemoteInput, mReplyPendingIntent,
13030                         mReadPendingIntent, participants, mLatestTimestamp);
13031             }
13032         }
13033     }
13034 
13035     /**
13036      * <p>Helper class to add Android TV extensions to notifications. To create a notification
13037      * with a TV extension:
13038      *
13039      * <ol>
13040      *  <li>Create an {@link Notification.Builder}, setting any desired properties.
13041      *  <li>Create a {@link TvExtender}.
13042      *  <li>Set TV-specific properties using the {@code set} methods of
13043      *  {@link TvExtender}.
13044      *  <li>Call {@link Notification.Builder#extend(Notification.Extender)}
13045      *  to apply the extension to a notification.
13046      * </ol>
13047      *
13048      * <pre class="prettyprint">
13049      * Notification notification = new Notification.Builder(context)
13050      *         ...
13051      *         .extend(new TvExtender()
13052      *                 .set*(...))
13053      *         .build();
13054      * </pre>
13055      *
13056      * <p>TV extensions can be accessed on an existing notification by using the
13057      * {@code TvExtender(Notification)} constructor, and then using the {@code get} methods
13058      * to access values.
13059      */
13060     @FlaggedApi(Flags.FLAG_API_TVEXTENDER)
13061     public static final class TvExtender implements Extender {
13062         private static final String TAG = "TvExtender";
13063 
13064         private static final String EXTRA_TV_EXTENDER = "android.tv.EXTENSIONS";
13065         private static final String EXTRA_FLAGS = "flags";
13066         static final String EXTRA_CONTENT_INTENT = "content_intent";
13067         static final String EXTRA_DELETE_INTENT = "delete_intent";
13068         private static final String EXTRA_CHANNEL_ID = "channel_id";
13069         private static final String EXTRA_SUPPRESS_SHOW_OVER_APPS = "suppressShowOverApps";
13070 
13071         // Flags bitwise-ored to mFlags
13072         private static final int FLAG_AVAILABLE_ON_TV = 0x1;
13073 
13074         private int mFlags;
13075         private String mChannelId;
13076         private PendingIntent mContentIntent;
13077         private PendingIntent mDeleteIntent;
13078         private boolean mSuppressShowOverApps;
13079 
13080         /**
13081          * Create a {@link TvExtender} with default options.
13082          */
TvExtender()13083         public TvExtender() {
13084             mFlags = FLAG_AVAILABLE_ON_TV;
13085         }
13086 
13087         /**
13088          * Create a {@link TvExtender} from the TvExtender options of an existing Notification.
13089          *
13090          * @param notif The notification from which to copy options.
13091          */
TvExtender(@onNull Notification notif)13092         public TvExtender(@NonNull Notification notif) {
13093             Bundle bundle = notif.extras == null ?
13094                 null : notif.extras.getBundle(EXTRA_TV_EXTENDER);
13095             if (bundle != null) {
13096                 mFlags = bundle.getInt(EXTRA_FLAGS);
13097                 mChannelId = bundle.getString(EXTRA_CHANNEL_ID);
13098                 mSuppressShowOverApps = bundle.getBoolean(EXTRA_SUPPRESS_SHOW_OVER_APPS);
13099                 mContentIntent = bundle.getParcelable(EXTRA_CONTENT_INTENT, PendingIntent.class);
13100                 mDeleteIntent = bundle.getParcelable(EXTRA_DELETE_INTENT, PendingIntent.class);
13101             }
13102         }
13103 
13104         /**
13105          * Apply a TV extension to a notification that is being built. This is typically called by
13106          * the {@link Notification.Builder#extend(Notification.Extender)}
13107          * method of {@link Notification.Builder}.
13108          */
13109         @Override
13110         @NonNull
extend(@onNull Notification.Builder builder)13111         public Notification.Builder extend(@NonNull Notification.Builder builder) {
13112             Bundle bundle = new Bundle();
13113 
13114             bundle.putInt(EXTRA_FLAGS, mFlags);
13115             bundle.putString(EXTRA_CHANNEL_ID, mChannelId);
13116             bundle.putBoolean(EXTRA_SUPPRESS_SHOW_OVER_APPS, mSuppressShowOverApps);
13117             if (mContentIntent != null) {
13118                 bundle.putParcelable(EXTRA_CONTENT_INTENT, mContentIntent);
13119             }
13120 
13121             if (mDeleteIntent != null) {
13122                 bundle.putParcelable(EXTRA_DELETE_INTENT, mDeleteIntent);
13123             }
13124 
13125             builder.getExtras().putBundle(EXTRA_TV_EXTENDER, bundle);
13126             return builder;
13127         }
13128 
13129         /**
13130          * Returns true if this notification should be shown on TV. This method returns true
13131          * if the notification was extended with a TvExtender.
13132          */
isAvailableOnTv()13133         public boolean isAvailableOnTv() {
13134             return (mFlags & FLAG_AVAILABLE_ON_TV) != 0;
13135         }
13136 
13137         /**
13138          * Specifies the channel the notification should be delivered on when shown on TV.
13139          * It can be different from the channel that the notification is delivered to when
13140          * posting on a non-TV device. Prefer to use {@link setChannelId(String)}.
13141          *
13142          * @hide
13143          */
13144         @SystemApi
setChannel(String channelId)13145         public TvExtender setChannel(String channelId) {
13146             mChannelId = channelId;
13147             return this;
13148         }
13149 
13150         /**
13151          * Specifies the channel the notification should be delivered on when shown on TV.
13152          * It can be different from the channel that the notification is delivered to when
13153          * posting on a non-TV device.
13154          *
13155          * @return this object for method chaining
13156          */
13157         @NonNull
setChannelId(@ullable String channelId)13158         public TvExtender setChannelId(@Nullable String channelId) {
13159             mChannelId = channelId;
13160             return this;
13161         }
13162 
13163         /**
13164          * @removed
13165          * @hide
13166          */
13167         @Deprecated
13168         @SystemApi
getChannel()13169         public String getChannel() {
13170             return mChannelId;
13171         }
13172 
13173         /**
13174          * Returns the id of the channel this notification posts to on TV.
13175          */
13176         @Nullable
getChannelId()13177         public String getChannelId() {
13178             return mChannelId;
13179         }
13180 
13181         /**
13182          * Supplies a {@link PendingIntent} to be sent when the notification is selected on TV.
13183          * If provided, it is used instead of the content intent specified
13184          * at the level of Notification.
13185          *
13186          * @param intent the {@link PendingIntent} for the associated notification content
13187          * @return this object for method chaining
13188          */
13189         @NonNull
setContentIntent(@ullable PendingIntent intent)13190         public TvExtender setContentIntent(@Nullable PendingIntent intent) {
13191             mContentIntent = intent;
13192             return this;
13193         }
13194 
13195         /**
13196          * Returns the TV-specific content intent.  If this method returns null, the
13197          * main content intent on the notification should be used.
13198          *
13199          * @see Notification#contentIntent
13200          */
13201         @Nullable
getContentIntent()13202         public PendingIntent getContentIntent() {
13203             return mContentIntent;
13204         }
13205 
13206         /**
13207          * Supplies a {@link PendingIntent} to send when the notification is cleared explicitly
13208          * by the user on TV.  If provided, it is used instead of the delete intent specified
13209          * at the level of Notification.
13210          *
13211          * @param intent the {@link PendingIntent} for the associated notification deletion
13212          * @return this object for method chaining
13213          */
13214         @NonNull
setDeleteIntent(@ullable PendingIntent intent)13215         public TvExtender setDeleteIntent(@Nullable PendingIntent intent) {
13216             mDeleteIntent = intent;
13217             return this;
13218         }
13219 
13220         /**
13221          * Returns the TV-specific delete intent.  If this method returns null, the
13222          * main delete intent on the notification should be used.
13223          *
13224          * @see Notification#deleteIntent
13225          */
13226         @Nullable
getDeleteIntent()13227         public PendingIntent getDeleteIntent() {
13228             return mDeleteIntent;
13229         }
13230 
13231         /**
13232          * Specifies whether this notification should suppress showing a message over top of apps
13233          * outside of the launcher.
13234          *
13235          * @param suppress whether the notification should suppress showing over apps.
13236          * @return this object for method chaining
13237          */
13238         @NonNull
setSuppressShowOverApps(boolean suppress)13239         public TvExtender setSuppressShowOverApps(boolean suppress) {
13240             mSuppressShowOverApps = suppress;
13241             return this;
13242         }
13243 
13244         /**
13245          * Returns true if this notification should not show messages over top of apps
13246          * outside of the launcher.
13247          *
13248          * @hide
13249          */
13250         @SystemApi
getSuppressShowOverApps()13251         public boolean getSuppressShowOverApps() {
13252             return mSuppressShowOverApps;
13253         }
13254 
13255         /**
13256          * Returns true if this notification should not show messages over top of apps
13257          * outside of the launcher.
13258          */
isSuppressShowOverApps()13259         public boolean isSuppressShowOverApps() {
13260             return mSuppressShowOverApps;
13261         }
13262     }
13263 
13264     /**
13265      * Get an array of Parcelable objects from a parcelable array bundle field.
13266      * Update the bundle to have a typed array so fetches in the future don't need
13267      * to do an array copy.
13268      */
13269     @Nullable
getParcelableArrayFromBundle( Bundle bundle, String key, Class<T> itemClass)13270     private static <T extends Parcelable> T[] getParcelableArrayFromBundle(
13271             Bundle bundle, String key, Class<T> itemClass) {
13272         final Parcelable[] array = bundle.getParcelableArray(key, Parcelable.class);
13273         final Class<?> arrayClass = Array.newInstance(itemClass, 0).getClass();
13274         if (arrayClass.isInstance(array) || array == null) {
13275             return (T[]) array;
13276         }
13277         final T[] typedArray = (T[]) Array.newInstance(itemClass, array.length);
13278         for (int i = 0; i < array.length; i++) {
13279             typedArray[i] = (T) array[i];
13280         }
13281         bundle.putParcelableArray(key, typedArray);
13282         return typedArray;
13283     }
13284 
13285     private static class BuilderRemoteViews extends RemoteViews {
BuilderRemoteViews(Parcel parcel)13286         public BuilderRemoteViews(Parcel parcel) {
13287             super(parcel);
13288         }
13289 
BuilderRemoteViews(ApplicationInfo appInfo, int layoutId)13290         public BuilderRemoteViews(ApplicationInfo appInfo, int layoutId) {
13291             super(appInfo, layoutId);
13292         }
13293 
13294         @Override
clone()13295         public BuilderRemoteViews clone() {
13296             Parcel p = Parcel.obtain();
13297             writeToParcel(p, 0);
13298             p.setDataPosition(0);
13299             BuilderRemoteViews brv = new BuilderRemoteViews(p);
13300             p.recycle();
13301             return brv;
13302         }
13303 
13304         /**
13305          * Override and return true, since {@link RemoteViews#onLoadClass(Class)} is not overridden.
13306          *
13307          * @see RemoteViews#shouldUseStaticFilter()
13308          */
13309         @Override
shouldUseStaticFilter()13310         protected boolean shouldUseStaticFilter() {
13311             return true;
13312         }
13313     }
13314 
13315     /**
13316      * A result object where information about the template that was created is saved.
13317      */
13318     private static class TemplateBindResult {
13319         boolean mRightIconVisible;
13320         float mRightIconWidthDp;
13321         float mRightIconHeightDp;
13322 
13323         /**
13324          * The margin end that needs to be added to the heading so that it won't overlap
13325          * with the large icon.  This value includes the space required to accommodate the large
13326          * icon, but should be added to the space needed to accommodate the expander. This does
13327          * not include the 16dp content margin that all notification views must have.
13328          */
13329         public final MarginSet mHeadingExtraMarginSet = new MarginSet();
13330 
13331         /**
13332          * The margin end that needs to be added to the heading so that it won't overlap
13333          * with the large icon.  This value includes the space required to accommodate the large
13334          * icon as well as the expander.  This does not include the 16dp content margin that all
13335          * notification views must have.
13336          */
13337         public final MarginSet mHeadingFullMarginSet = new MarginSet();
13338 
13339         /**
13340          * The margin end that needs to be added to the title text of the big state
13341          * so that it won't overlap with the large icon, but assuming the text can run under
13342          * the expander when that icon is not visible.
13343          */
13344         public final MarginSet mTitleMarginSet = new MarginSet();
13345 
setRightIconState(boolean visible, float widthDp, float heightDp, float marginEndDpIfVisible, float expanderSizeDp)13346         public void setRightIconState(boolean visible, float widthDp, float heightDp,
13347                 float marginEndDpIfVisible, float expanderSizeDp) {
13348             mRightIconVisible = visible;
13349             mRightIconWidthDp = widthDp;
13350             mRightIconHeightDp = heightDp;
13351             mHeadingExtraMarginSet.setValues(0, marginEndDpIfVisible);
13352             mHeadingFullMarginSet.setValues(expanderSizeDp, marginEndDpIfVisible + expanderSizeDp);
13353             mTitleMarginSet.setValues(0, marginEndDpIfVisible + expanderSizeDp);
13354         }
13355 
13356         /**
13357          * This contains the end margins for a view when the right icon is visible or not.  These
13358          * values are both needed so that NotificationGroupingUtil can 'move' the right_icon to the
13359          * left_icon and adjust the margins, and to undo that change as well.
13360          */
13361         private class MarginSet {
13362             private float mValueIfGone;
13363             private float mValueIfVisible;
13364 
setValues(float valueIfGone, float valueIfVisible)13365             public void setValues(float valueIfGone, float valueIfVisible) {
13366                 mValueIfGone = valueIfGone;
13367                 mValueIfVisible = valueIfVisible;
13368             }
13369 
applyToView(@onNull RemoteViews views, @IdRes int viewId)13370             public void applyToView(@NonNull RemoteViews views, @IdRes int viewId) {
13371                 applyToView(views, viewId, 0);
13372             }
13373 
applyToView(@onNull RemoteViews views, @IdRes int viewId, float extraMarginDp)13374             public void applyToView(@NonNull RemoteViews views, @IdRes int viewId,
13375                     float extraMarginDp) {
13376                 final float marginEndDp = getDpValue() + extraMarginDp;
13377                 if (viewId == R.id.notification_header) {
13378                     views.setFloat(R.id.notification_header,
13379                             "setTopLineExtraMarginEndDp", marginEndDp);
13380                 } else if (viewId == R.id.text || viewId == R.id.big_text) {
13381                     if (mValueIfGone != 0) {
13382                         throw new RuntimeException("Programming error: `text` and `big_text` use "
13383                                 + "ImageFloatingTextView which can either show a margin or not; "
13384                                 + "thus mValueIfGone must be 0, but it was " + mValueIfGone);
13385                     }
13386                     // Note that the caller must set "setNumIndentLines" to a positive int in order
13387                     //  for this margin to do anything at all.
13388                     views.setFloat(viewId, "setImageEndMarginDp", mValueIfVisible);
13389                     views.setBoolean(viewId, "setHasImage", mRightIconVisible);
13390                     // Apply just the *extra* margin as the view layout margin; this will be
13391                     //  unchanged depending on the visibility of the image, but it means that the
13392                     //  extra margin applies to *every* line of text instead of just indented lines.
13393                     views.setViewLayoutMargin(viewId, RemoteViews.MARGIN_END,
13394                             extraMarginDp, TypedValue.COMPLEX_UNIT_DIP);
13395                 } else {
13396                     views.setViewLayoutMargin(viewId, RemoteViews.MARGIN_END,
13397                                     marginEndDp, TypedValue.COMPLEX_UNIT_DIP);
13398                 }
13399                 if (mRightIconVisible) {
13400                     views.setIntTag(viewId, R.id.tag_margin_end_when_icon_visible,
13401                             TypedValue.createComplexDimension(
13402                                     mValueIfVisible + extraMarginDp, TypedValue.COMPLEX_UNIT_DIP));
13403                     views.setIntTag(viewId, R.id.tag_margin_end_when_icon_gone,
13404                             TypedValue.createComplexDimension(
13405                                     mValueIfGone + extraMarginDp, TypedValue.COMPLEX_UNIT_DIP));
13406                 }
13407             }
13408 
getDpValue()13409             public float getDpValue() {
13410                 return mRightIconVisible ? mValueIfVisible : mValueIfGone;
13411             }
13412         }
13413     }
13414 
13415     private static class StandardTemplateParams {
13416         /**
13417          * Notifications will be minimally decorated with ONLY an icon and expander:
13418          * <li>A large icon is never shown.
13419          * <li>A progress bar is never shown.
13420          * <li>The expanded and heads up states do not show actions, even if provided.
13421          */
13422         public static final int DECORATION_MINIMAL = 1;
13423 
13424         /**
13425          * Notifications will be partially decorated with AT LEAST an icon and expander:
13426          * <li>A large icon is shown if provided.
13427          * <li>A progress bar is shown if provided and enough space remains below the content.
13428          * <li>Actions are shown in the expanded and heads up states.
13429          */
13430         public static final int DECORATION_PARTIAL = 2;
13431 
13432         public static int VIEW_TYPE_UNSPECIFIED = 0;
13433         public static int VIEW_TYPE_NORMAL = 1;
13434         public static int VIEW_TYPE_BIG = 2;
13435         public static int VIEW_TYPE_HEADS_UP = 3;
13436         public static int VIEW_TYPE_MINIMIZED = 4;    // header only for minimized state
13437         public static int VIEW_TYPE_PUBLIC = 5;       // header only for automatic public version
13438         public static int VIEW_TYPE_GROUP_HEADER = 6; // header only for top of group
13439 
13440         int mViewType = VIEW_TYPE_UNSPECIFIED;
13441         boolean mHeaderless;
13442         boolean mHideAppName;
13443         boolean mHideTitle;
13444         boolean mHideSubText;
13445         boolean mHideTime;
13446         boolean mHideActions;
13447         boolean mHideProgress;
13448         boolean mHideSnoozeButton;
13449         boolean mHideLeftIcon;
13450         boolean mHideRightIcon;
13451         Icon mPromotedPicture;
13452         boolean mCallStyleActions;
13453         boolean mAllowTextWithProgress;
13454         int mTitleViewId;
13455         int mTextViewId;
13456         @Nullable CharSequence mTitle;
13457         @Nullable CharSequence mText;
13458         @Nullable CharSequence mHeaderTextSecondary;
13459         @Nullable CharSequence mSubText;
13460         int maxRemoteInputHistory = Style.MAX_REMOTE_INPUT_HISTORY_LINES;
13461         boolean allowColorization  = true;
13462         boolean mHighlightExpander = false;
13463 
reset()13464         final StandardTemplateParams reset() {
13465             mViewType = VIEW_TYPE_UNSPECIFIED;
13466             mHeaderless = false;
13467             mHideAppName = false;
13468             mHideTitle = false;
13469             mHideSubText = false;
13470             mHideTime = false;
13471             mHideActions = false;
13472             mHideProgress = false;
13473             mHideSnoozeButton = false;
13474             mHideLeftIcon = false;
13475             mHideRightIcon = false;
13476             mPromotedPicture = null;
13477             mCallStyleActions = false;
13478             mAllowTextWithProgress = false;
13479             mTitleViewId = R.id.title;
13480             mTextViewId = R.id.text;
13481             mTitle = null;
13482             mText = null;
13483             mSubText = null;
13484             mHeaderTextSecondary = null;
13485             maxRemoteInputHistory = Style.MAX_REMOTE_INPUT_HISTORY_LINES;
13486             allowColorization = true;
13487             mHighlightExpander = false;
13488             return this;
13489         }
13490 
hasTitle()13491         final boolean hasTitle() {
13492             return !TextUtils.isEmpty(mTitle) && !mHideTitle;
13493         }
13494 
viewType(int viewType)13495         final StandardTemplateParams viewType(int viewType) {
13496             mViewType = viewType;
13497             return this;
13498         }
13499 
headerless(boolean headerless)13500         public StandardTemplateParams headerless(boolean headerless) {
13501             mHeaderless = headerless;
13502             return this;
13503         }
13504 
hideAppName(boolean hideAppName)13505         public StandardTemplateParams hideAppName(boolean hideAppName) {
13506             mHideAppName = hideAppName;
13507             return this;
13508         }
13509 
hideSubText(boolean hideSubText)13510         public StandardTemplateParams hideSubText(boolean hideSubText) {
13511             mHideSubText = hideSubText;
13512             return this;
13513         }
13514 
hideTime(boolean hideTime)13515         public StandardTemplateParams hideTime(boolean hideTime) {
13516             mHideTime = hideTime;
13517             return this;
13518         }
13519 
hideActions(boolean hideActions)13520         final StandardTemplateParams hideActions(boolean hideActions) {
13521             this.mHideActions = hideActions;
13522             return this;
13523         }
13524 
hideProgress(boolean hideProgress)13525         final StandardTemplateParams hideProgress(boolean hideProgress) {
13526             this.mHideProgress = hideProgress;
13527             return this;
13528         }
13529 
hideTitle(boolean hideTitle)13530         final StandardTemplateParams hideTitle(boolean hideTitle) {
13531             this.mHideTitle = hideTitle;
13532             return this;
13533         }
13534 
callStyleActions(boolean callStyleActions)13535         final StandardTemplateParams callStyleActions(boolean callStyleActions) {
13536             this.mCallStyleActions = callStyleActions;
13537             return this;
13538         }
13539 
allowTextWithProgress(boolean allowTextWithProgress)13540         final StandardTemplateParams allowTextWithProgress(boolean allowTextWithProgress) {
13541             this.mAllowTextWithProgress = allowTextWithProgress;
13542             return this;
13543         }
13544 
hideSnoozeButton(boolean hideSnoozeButton)13545         final StandardTemplateParams hideSnoozeButton(boolean hideSnoozeButton) {
13546             this.mHideSnoozeButton = hideSnoozeButton;
13547             return this;
13548         }
13549 
promotedPicture(Icon promotedPicture)13550         final StandardTemplateParams promotedPicture(Icon promotedPicture) {
13551             this.mPromotedPicture = promotedPicture;
13552             return this;
13553         }
13554 
titleViewId(int titleViewId)13555         public StandardTemplateParams titleViewId(int titleViewId) {
13556             mTitleViewId = titleViewId;
13557             return this;
13558         }
13559 
textViewId(int textViewId)13560         public StandardTemplateParams textViewId(int textViewId) {
13561             mTextViewId = textViewId;
13562             return this;
13563         }
13564 
title(@ullable CharSequence title)13565         final StandardTemplateParams title(@Nullable CharSequence title) {
13566             this.mTitle = title;
13567             return this;
13568         }
13569 
text(@ullable CharSequence text)13570         final StandardTemplateParams text(@Nullable CharSequence text) {
13571             this.mText = text;
13572             return this;
13573         }
13574 
summaryText(@ullable CharSequence text)13575         final StandardTemplateParams summaryText(@Nullable CharSequence text) {
13576             this.mSubText = text;
13577             return this;
13578         }
13579 
headerTextSecondary(@ullable CharSequence text)13580         final StandardTemplateParams headerTextSecondary(@Nullable CharSequence text) {
13581             this.mHeaderTextSecondary = text;
13582             return this;
13583         }
13584 
13585 
hideLeftIcon(boolean hideLeftIcon)13586         final StandardTemplateParams hideLeftIcon(boolean hideLeftIcon) {
13587             this.mHideLeftIcon = hideLeftIcon;
13588             return this;
13589         }
13590 
hideRightIcon(boolean hideRightIcon)13591         final StandardTemplateParams hideRightIcon(boolean hideRightIcon) {
13592             this.mHideRightIcon = hideRightIcon;
13593             return this;
13594         }
13595 
disallowColorization()13596         final StandardTemplateParams disallowColorization() {
13597             this.allowColorization = false;
13598             return this;
13599         }
13600 
highlightExpander(boolean highlight)13601         final StandardTemplateParams highlightExpander(boolean highlight) {
13602             this.mHighlightExpander = highlight;
13603             return this;
13604         }
13605 
fillTextsFrom(Builder b)13606         final StandardTemplateParams fillTextsFrom(Builder b) {
13607             Bundle extras = b.mN.extras;
13608             this.mTitle = b.processLegacyText(extras.getCharSequence(EXTRA_TITLE));
13609             this.mText = b.processLegacyText(extras.getCharSequence(EXTRA_TEXT));
13610             this.mSubText = extras.getCharSequence(EXTRA_SUB_TEXT);
13611             return this;
13612         }
13613 
13614         /**
13615          * Set the maximum lines of remote input history lines allowed.
13616          * @param maxRemoteInputHistory The number of lines.
13617          * @return The builder for method chaining.
13618          */
setMaxRemoteInputHistory(int maxRemoteInputHistory)13619         public StandardTemplateParams setMaxRemoteInputHistory(int maxRemoteInputHistory) {
13620             this.maxRemoteInputHistory = maxRemoteInputHistory;
13621             return this;
13622         }
13623 
decorationType(int decorationType)13624         public StandardTemplateParams decorationType(int decorationType) {
13625             hideTitle(true);
13626             // Minimally decorated custom views do not show certain pieces of chrome that have
13627             // always been shown when using DecoratedCustomViewStyle.
13628             boolean hideOtherFields = decorationType <= DECORATION_MINIMAL;
13629             hideLeftIcon(false);  // The left icon decoration is better than showing nothing.
13630             hideRightIcon(hideOtherFields);
13631             hideProgress(hideOtherFields);
13632             hideActions(hideOtherFields);
13633             return this;
13634         }
13635     }
13636 
13637     /**
13638      * A utility which stores and calculates the palette of colors used to color notifications.
13639      * @hide
13640      */
13641     @VisibleForTesting
13642     public static class Colors {
13643         private int mPaletteIsForRawColor = COLOR_INVALID;
13644         private boolean mPaletteIsForColorized = false;
13645         private boolean mPaletteIsForNightMode = false;
13646         // The following colors are the palette
13647         private int mBackgroundColor = COLOR_INVALID;
13648         private int mProtectionColor = COLOR_INVALID;
13649         private int mPrimaryTextColor = COLOR_INVALID;
13650         private int mSecondaryTextColor = COLOR_INVALID;
13651         private int mPrimaryAccentColor = COLOR_INVALID;
13652         private int mSecondaryAccentColor = COLOR_INVALID;
13653         private int mTertiaryAccentColor = COLOR_INVALID;
13654         private int mOnTertiaryAccentTextColor = COLOR_INVALID;
13655         private int mTertiaryFixedDimAccentColor = COLOR_INVALID;
13656         private int mOnTertiaryFixedAccentTextColor = COLOR_INVALID;
13657 
13658         private int mErrorColor = COLOR_INVALID;
13659         private int mContrastColor = COLOR_INVALID;
13660         private int mRippleAlpha = 0x33;
13661 
13662         /**
13663          * A utility for obtaining a TypedArray of the given DayNight-styled attributes, which
13664          * returns null when the context is a mock with no theme.
13665          *
13666          * NOTE: Calling this method is expensive, as creating a new ContextThemeWrapper
13667          * instances can allocate as much as 5MB of memory, so its important to call this method
13668          * only when necessary, getting as many attributes as possible from each call.
13669          *
13670          * @see Resources.Theme#obtainStyledAttributes(int[])
13671          */
13672         @Nullable
obtainDayNightAttributes(@onNull Context ctx, @NonNull @StyleableRes int[] attrs)13673         private static TypedArray obtainDayNightAttributes(@NonNull Context ctx,
13674                 @NonNull @StyleableRes int[] attrs) {
13675             // when testing, the mock context may have no theme
13676             if (ctx.getTheme() == null) {
13677                 return null;
13678             }
13679             Resources.Theme theme = new ContextThemeWrapper(ctx,
13680                     R.style.Theme_DeviceDefault_DayNight).getTheme();
13681             return theme.obtainStyledAttributes(attrs);
13682         }
13683 
13684         /** A null-safe wrapper of TypedArray.getColor because mocks return null */
getColor(@ullable TypedArray ta, int index, @ColorInt int defValue)13685         private static @ColorInt int getColor(@Nullable TypedArray ta, int index,
13686                 @ColorInt int defValue) {
13687             return ta == null ? defValue : ta.getColor(index, defValue);
13688         }
13689 
13690         /**
13691          * Resolve the palette.  If the inputs have not changed, this will be a no-op.
13692          * This does not handle invalidating the resolved colors when the context itself changes,
13693          * because that case does not happen in the current notification inflation pipeline; we will
13694          * recreate a new builder (and thus a new palette) when reinflating notifications for a new
13695          * theme (admittedly, we do the same for night mode, but that's easy to check).
13696          *
13697          * @param ctx the builder context.
13698          * @param rawColor the notification's color; may be COLOR_DEFAULT, but may never have alpha.
13699          * @param isColorized whether the notification is colorized.
13700          * @param nightMode whether the UI is in night mode.
13701          */
resolvePalette(Context ctx, int rawColor, boolean isColorized, boolean nightMode)13702         public void resolvePalette(Context ctx, int rawColor,
13703                 boolean isColorized, boolean nightMode) {
13704             if (mPaletteIsForRawColor == rawColor
13705                     && mPaletteIsForColorized == isColorized
13706                     && mPaletteIsForNightMode == nightMode) {
13707                 return;
13708             }
13709             mPaletteIsForRawColor = rawColor;
13710             mPaletteIsForColorized = isColorized;
13711             mPaletteIsForNightMode = nightMode;
13712 
13713             if (isColorized) {
13714                 if (rawColor == COLOR_DEFAULT) {
13715                     int[] attrs = {R.attr.materialColorSecondary};
13716                     try (TypedArray ta = obtainDayNightAttributes(ctx, attrs)) {
13717                         mBackgroundColor = getColor(ta, 0, Color.WHITE);
13718                     }
13719                 } else {
13720                     mBackgroundColor = rawColor;
13721                 }
13722                 mPrimaryTextColor = ContrastColorUtil.findAlphaToMeetContrast(
13723                         ContrastColorUtil.resolvePrimaryColor(ctx, mBackgroundColor, nightMode),
13724                         mBackgroundColor, 4.5);
13725                 mSecondaryTextColor = ContrastColorUtil.findAlphaToMeetContrast(
13726                         ContrastColorUtil.resolveSecondaryColor(ctx, mBackgroundColor, nightMode),
13727                         mBackgroundColor, 4.5);
13728                 mContrastColor = mPrimaryTextColor;
13729                 mPrimaryAccentColor = mPrimaryTextColor;
13730                 mSecondaryAccentColor = mSecondaryTextColor;
13731                 mTertiaryAccentColor = flattenAlpha(mPrimaryTextColor, mBackgroundColor);
13732                 mOnTertiaryAccentTextColor = mBackgroundColor;
13733                 mTertiaryFixedDimAccentColor = mTertiaryAccentColor;
13734                 mOnTertiaryFixedAccentTextColor = mOnTertiaryAccentTextColor;
13735                 mErrorColor = mPrimaryTextColor;
13736                 mRippleAlpha = 0x33;
13737             } else {
13738                 int[] attrs = {
13739                         R.attr.materialColorSurfaceContainerHigh,
13740                         R.attr.materialColorOnSurface,
13741                         R.attr.materialColorOnSurfaceVariant,
13742                         R.attr.materialColorPrimary,
13743                         R.attr.materialColorSecondary,
13744                         R.attr.materialColorTertiary,
13745                         R.attr.materialColorOnTertiary,
13746                         R.attr.materialColorTertiaryFixedDim,
13747                         R.attr.materialColorOnTertiaryFixed,
13748                         R.attr.colorError,
13749                         R.attr.colorControlHighlight
13750                 };
13751                 try (TypedArray ta = obtainDayNightAttributes(ctx, attrs)) {
13752                     mBackgroundColor = getColor(ta, 0, nightMode ? Color.BLACK : Color.WHITE);
13753                     mPrimaryTextColor = getColor(ta, 1, COLOR_INVALID);
13754                     mSecondaryTextColor = getColor(ta, 2, COLOR_INVALID);
13755                     mPrimaryAccentColor = getColor(ta, 3, COLOR_INVALID);
13756                     mSecondaryAccentColor = getColor(ta, 4, COLOR_INVALID);
13757                     mTertiaryAccentColor = getColor(ta, 5, COLOR_INVALID);
13758                     mOnTertiaryAccentTextColor = getColor(ta, 6, COLOR_INVALID);
13759                     mTertiaryFixedDimAccentColor = getColor(ta, 7, COLOR_INVALID);
13760                     mOnTertiaryFixedAccentTextColor = getColor(ta, 8, COLOR_INVALID);
13761                     mErrorColor = getColor(ta, 9, COLOR_INVALID);
13762                     mRippleAlpha = Color.alpha(getColor(ta, 10, 0x33ffffff));
13763                 }
13764                 mContrastColor = calculateContrastColor(ctx, rawColor, mPrimaryAccentColor,
13765                         mBackgroundColor, nightMode);
13766 
13767                 // make sure every color has a valid value
13768                 if (mPrimaryTextColor == COLOR_INVALID) {
13769                     mPrimaryTextColor = ContrastColorUtil.resolvePrimaryColor(
13770                             ctx, mBackgroundColor, nightMode);
13771                 }
13772                 if (mSecondaryTextColor == COLOR_INVALID) {
13773                     mSecondaryTextColor = ContrastColorUtil.resolveSecondaryColor(
13774                             ctx, mBackgroundColor, nightMode);
13775                 }
13776                 if (mPrimaryAccentColor == COLOR_INVALID) {
13777                     mPrimaryAccentColor = mContrastColor;
13778                 }
13779                 if (mSecondaryAccentColor == COLOR_INVALID) {
13780                     mSecondaryAccentColor = mContrastColor;
13781                 }
13782                 if (mTertiaryAccentColor == COLOR_INVALID) {
13783                     mTertiaryAccentColor = mContrastColor;
13784                 }
13785                 if (mOnTertiaryAccentTextColor == COLOR_INVALID) {
13786                     mOnTertiaryAccentTextColor = ColorUtils.setAlphaComponent(
13787                             ContrastColorUtil.resolvePrimaryColor(
13788                                     ctx, mTertiaryAccentColor, nightMode), 0xFF);
13789                 }
13790                 if (mTertiaryFixedDimAccentColor == COLOR_INVALID) {
13791                     mTertiaryFixedDimAccentColor = mContrastColor;
13792                 }
13793                 if (mOnTertiaryFixedAccentTextColor == COLOR_INVALID) {
13794                     mOnTertiaryFixedAccentTextColor = ColorUtils.setAlphaComponent(
13795                             ContrastColorUtil.resolvePrimaryColor(
13796                                     ctx, mTertiaryFixedDimAccentColor, nightMode), 0xFF);
13797                 }
13798                 if (mErrorColor == COLOR_INVALID) {
13799                     mErrorColor = mPrimaryTextColor;
13800                 }
13801             }
13802             // make sure every color has a valid value
13803             mProtectionColor = ColorUtils.blendARGB(mPrimaryTextColor, mBackgroundColor, 0.9f);
13804         }
13805 
13806         /** calculates the contrast color for the non-colorized notifications */
calculateContrastColor(Context ctx, @ColorInt int rawColor, @ColorInt int accentColor, @ColorInt int backgroundColor, boolean nightMode)13807         private static @ColorInt int calculateContrastColor(Context ctx, @ColorInt int rawColor,
13808                 @ColorInt int accentColor, @ColorInt int backgroundColor, boolean nightMode) {
13809             int color;
13810             if (rawColor == COLOR_DEFAULT) {
13811                 color = accentColor;
13812                 if (color == COLOR_INVALID) {
13813                     color = ContrastColorUtil.resolveDefaultColor(ctx, backgroundColor, nightMode);
13814                 }
13815             } else {
13816                 color = ContrastColorUtil.resolveContrastColor(ctx, rawColor, backgroundColor,
13817                         nightMode);
13818             }
13819             return flattenAlpha(color, backgroundColor);
13820         }
13821 
13822         /** remove any alpha by manually blending it with the given background. */
flattenAlpha(@olorInt int color, @ColorInt int background)13823         private static @ColorInt int flattenAlpha(@ColorInt int color, @ColorInt int background) {
13824             return Color.alpha(color) == 0xff ? color
13825                     : ContrastColorUtil.compositeColors(color, background);
13826         }
13827 
13828         /** @return the notification's background color */
getBackgroundColor()13829         public @ColorInt int getBackgroundColor() {
13830             return mBackgroundColor;
13831         }
13832 
13833         /**
13834          * @return the "surface protection" color from the theme,
13835          * or a variant of the normal background color when colorized.
13836          */
getProtectionColor()13837         public @ColorInt int getProtectionColor() {
13838             return mProtectionColor;
13839         }
13840 
13841         /** @return the color for the most prominent text */
getPrimaryTextColor()13842         public @ColorInt int getPrimaryTextColor() {
13843             return mPrimaryTextColor;
13844         }
13845 
13846         /** @return the color for less prominent text */
getSecondaryTextColor()13847         public @ColorInt int getSecondaryTextColor() {
13848             return mSecondaryTextColor;
13849         }
13850 
13851         /** @return the theme's accent color for colored UI elements. */
getPrimaryAccentColor()13852         public @ColorInt int getPrimaryAccentColor() {
13853             return mPrimaryAccentColor;
13854         }
13855 
13856         /** @return the theme's secondary accent color for colored UI elements. */
getSecondaryAccentColor()13857         public @ColorInt int getSecondaryAccentColor() {
13858             return mSecondaryAccentColor;
13859         }
13860 
13861         /** @return the theme's tertiary accent color for colored UI elements. */
getTertiaryAccentColor()13862         public @ColorInt int getTertiaryAccentColor() {
13863             return mTertiaryAccentColor;
13864         }
13865 
13866         /** @return the theme's text color to be used on the tertiary accent color. */
getOnTertiaryAccentTextColor()13867         public @ColorInt int getOnTertiaryAccentTextColor() {
13868             return mOnTertiaryAccentTextColor;
13869         }
13870 
13871         /** @return the theme's tertiary fixed dim accent color for colored UI elements. */
getTertiaryFixedDimAccentColor()13872         public @ColorInt int getTertiaryFixedDimAccentColor() {
13873             return mTertiaryFixedDimAccentColor;
13874         }
13875 
13876         /** @return the theme's text color to be used on the tertiary fixed accent color. */
getOnTertiaryFixedAccentTextColor()13877         public @ColorInt int getOnTertiaryFixedAccentTextColor() {
13878             return mOnTertiaryFixedAccentTextColor;
13879         }
13880 
13881         /**
13882          * @return the contrast-adjusted version of the color provided by the app, or the
13883          * primary text color when colorized.
13884          */
getContrastColor()13885         public @ColorInt int getContrastColor() {
13886             return mContrastColor;
13887         }
13888 
13889         /** @return the theme's error color, or the primary text color when colorized. */
getErrorColor()13890         public @ColorInt int getErrorColor() {
13891             return mErrorColor;
13892         }
13893 
13894         /** @return the alpha component of the current theme's control highlight color. */
getRippleAlpha()13895         public int getRippleAlpha() {
13896             return mRippleAlpha;
13897         }
13898     }
13899 }
13900