• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2007 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.app;
18 
19 import static android.annotation.Dimension.DP;
20 import static android.app.Flags.evenlyDividedCallStyleActionLayout;
21 import static android.app.Flags.notificationsRedesignTemplates;
22 import static android.app.admin.DevicePolicyResources.Drawables.Source.NOTIFICATION;
23 import static android.app.admin.DevicePolicyResources.Drawables.Style.SOLID_COLORED;
24 import static android.app.admin.DevicePolicyResources.Drawables.WORK_PROFILE_ICON;
25 import static android.app.admin.DevicePolicyResources.UNDEFINED;
26 import static android.graphics.drawable.Icon.TYPE_URI;
27 import static android.graphics.drawable.Icon.TYPE_URI_ADAPTIVE_BITMAP;
28 import static android.util.TypedValue.COMPLEX_UNIT_PX;
29 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
30 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
31 
32 import static java.util.Objects.requireNonNull;
33 
34 import android.annotation.ColorInt;
35 import android.annotation.ColorRes;
36 import android.annotation.DimenRes;
37 import android.annotation.Dimension;
38 import android.annotation.DrawableRes;
39 import android.annotation.FlaggedApi;
40 import android.annotation.IdRes;
41 import android.annotation.IntDef;
42 import android.annotation.NonNull;
43 import android.annotation.Nullable;
44 import android.annotation.RequiresPermission;
45 import android.annotation.SdkConstant;
46 import android.annotation.SdkConstant.SdkConstantType;
47 import android.annotation.StringRes;
48 import android.annotation.StyleableRes;
49 import android.annotation.SuppressLint;
50 import android.annotation.SystemApi;
51 import android.annotation.TestApi;
52 import android.app.admin.DevicePolicyManager;
53 import android.app.compat.CompatChanges;
54 import android.compat.annotation.ChangeId;
55 import android.compat.annotation.EnabledSince;
56 import android.compat.annotation.UnsupportedAppUsage;
57 import android.content.Context;
58 import android.content.Intent;
59 import android.content.LocusId;
60 import android.content.pm.ApplicationInfo;
61 import android.content.pm.PackageManager;
62 import android.content.pm.PackageManager.NameNotFoundException;
63 import android.content.pm.ShortcutInfo;
64 import android.content.res.ColorStateList;
65 import android.content.res.Configuration;
66 import android.content.res.Resources;
67 import android.content.res.TypedArray;
68 import android.graphics.Bitmap;
69 import android.graphics.Canvas;
70 import android.graphics.Color;
71 import android.graphics.PorterDuff;
72 import android.graphics.drawable.Drawable;
73 import android.graphics.drawable.Icon;
74 import android.media.AudioAttributes;
75 import android.media.AudioManager;
76 import android.media.PlayerBase;
77 import android.media.session.MediaSession;
78 import android.net.Uri;
79 import android.os.BadParcelableException;
80 import android.os.Build;
81 import android.os.Bundle;
82 import android.os.IBinder;
83 import android.os.Parcel;
84 import android.os.Parcelable;
85 import android.os.SystemClock;
86 import android.os.SystemProperties;
87 import android.os.Trace;
88 import android.os.UserHandle;
89 import android.os.UserManager;
90 import android.provider.Settings;
91 import android.text.BidiFormatter;
92 import android.text.SpannableStringBuilder;
93 import android.text.Spanned;
94 import android.text.TextUtils;
95 import android.text.style.AbsoluteSizeSpan;
96 import android.text.style.CharacterStyle;
97 import android.text.style.ForegroundColorSpan;
98 import android.text.style.RelativeSizeSpan;
99 import android.text.style.StrikethroughSpan;
100 import android.text.style.StyleSpan;
101 import android.text.style.TextAppearanceSpan;
102 import android.text.style.UnderlineSpan;
103 import android.util.ArraySet;
104 import android.util.Log;
105 import android.util.Pair;
106 import android.util.SparseArray;
107 import android.util.TypedValue;
108 import android.util.proto.ProtoOutputStream;
109 import android.view.ContextThemeWrapper;
110 import android.view.Gravity;
111 import android.view.View;
112 import android.view.contentcapture.ContentCaptureContext;
113 import android.widget.ProgressBar;
114 import android.widget.RemoteViews;
115 
116 import com.android.internal.R;
117 import com.android.internal.annotations.VisibleForTesting;
118 import com.android.internal.graphics.ColorUtils;
119 import com.android.internal.util.ArrayUtils;
120 import com.android.internal.util.ContrastColorUtil;
121 import com.android.internal.util.NotificationBigTextNormalizer;
122 import com.android.internal.widget.NotificationProgressModel;
123 
124 import java.lang.annotation.Retention;
125 import java.lang.annotation.RetentionPolicy;
126 import java.lang.reflect.Array;
127 import java.lang.reflect.Constructor;
128 import java.util.ArrayList;
129 import java.util.Collections;
130 import java.util.List;
131 import java.util.Objects;
132 import java.util.Set;
133 import java.util.function.Consumer;
134 
135 /**
136  * A class that represents how a persistent notification is to be presented to
137  * the user using the {@link android.app.NotificationManager}.
138  *
139  * <p>The {@link Notification.Builder Notification.Builder} has been added to make it
140  * easier to construct Notifications.</p>
141  *
142  * <div class="special reference">
143  * <h3>Developer Guides</h3>
144  * <p>For a guide to creating notifications, read the
145  * <a href="{@docRoot}guide/topics/ui/notifiers/notifications.html">Status Bar Notifications</a>
146  * developer guide.</p>
147  * </div>
148  */
149 public class Notification implements Parcelable
150 {
151     private static final String TAG = "Notification";
152 
153     /**
154      * @hide
155      */
156     @Retention(RetentionPolicy.SOURCE)
157     @IntDef({
158             FOREGROUND_SERVICE_DEFAULT,
159             FOREGROUND_SERVICE_IMMEDIATE,
160             FOREGROUND_SERVICE_DEFERRED
161     })
162     public @interface ServiceNotificationPolicy {}
163 
164     /**
165      * If the Notification associated with starting a foreground service has been
166      * built using setForegroundServiceBehavior() with this behavior, display of
167      * the notification will usually be suppressed for a short time to avoid visual
168      * disturbances to the user.
169      * @see Notification.Builder#setForegroundServiceBehavior(int)
170      * @see #FOREGROUND_SERVICE_IMMEDIATE
171      * @see #FOREGROUND_SERVICE_DEFERRED
172      */
173     public static final @ServiceNotificationPolicy int FOREGROUND_SERVICE_DEFAULT = 0;
174 
175     /**
176      * If the Notification associated with starting a foreground service has been
177      * built using setForegroundServiceBehavior() with this behavior, display of
178      * the notification will be immediate even if the default behavior would be
179      * to defer visibility for a short time.
180      * @see Notification.Builder#setForegroundServiceBehavior(int)
181      * @see #FOREGROUND_SERVICE_DEFAULT
182      * @see #FOREGROUND_SERVICE_DEFERRED
183      */
184     public static final @ServiceNotificationPolicy int FOREGROUND_SERVICE_IMMEDIATE = 1;
185 
186     /**
187      * If the Notification associated with starting a foreground service has been
188      * built using setForegroundServiceBehavior() with this behavior, display of
189      * the notification will usually be suppressed for a short time to avoid visual
190      * disturbances to the user.
191      * @see Notification.Builder#setForegroundServiceBehavior(int)
192      * @see #FOREGROUND_SERVICE_DEFAULT
193      * @see #FOREGROUND_SERVICE_IMMEDIATE
194      */
195     public static final @ServiceNotificationPolicy int FOREGROUND_SERVICE_DEFERRED = 2;
196 
197     @ServiceNotificationPolicy
198     private int mFgsDeferBehavior;
199 
200     /**
201      * An activity that provides a user interface for adjusting notification preferences for its
202      * containing application.
203      */
204     @SdkConstant(SdkConstantType.INTENT_CATEGORY)
205     public static final String INTENT_CATEGORY_NOTIFICATION_PREFERENCES
206             = "android.intent.category.NOTIFICATION_PREFERENCES";
207 
208     /**
209      * Optional extra for {@link #INTENT_CATEGORY_NOTIFICATION_PREFERENCES}. If provided, will
210      * contain a {@link NotificationChannel#getId() channel id} that can be used to narrow down
211      * what settings should be shown in the target app.
212      */
213     public static final String EXTRA_CHANNEL_ID = "android.intent.extra.CHANNEL_ID";
214 
215     /**
216      * Optional extra for {@link #INTENT_CATEGORY_NOTIFICATION_PREFERENCES}. If provided, will
217      * contain a {@link NotificationChannelGroup#getId() group id} that can be used to narrow down
218      * what settings should be shown in the target app.
219      */
220     public static final String EXTRA_CHANNEL_GROUP_ID = "android.intent.extra.CHANNEL_GROUP_ID";
221 
222     /**
223      * Optional extra for {@link #INTENT_CATEGORY_NOTIFICATION_PREFERENCES}. If provided, will
224      * contain the tag provided to {@link NotificationManager#notify(String, int, Notification)}
225      * that can be used to narrow down what settings should be shown in the target app.
226      */
227     public static final String EXTRA_NOTIFICATION_TAG = "android.intent.extra.NOTIFICATION_TAG";
228 
229     /**
230      * Optional extra for {@link #INTENT_CATEGORY_NOTIFICATION_PREFERENCES}. If provided, will
231      * contain the id provided to {@link NotificationManager#notify(String, int, Notification)}
232      * that can be used to narrow down what settings should be shown in the target app.
233      */
234     public static final String EXTRA_NOTIFICATION_ID = "android.intent.extra.NOTIFICATION_ID";
235 
236     /**
237      * Use all default values (where applicable).
238      */
239     public static final int DEFAULT_ALL = ~0;
240 
241     /**
242      * Use the default notification sound. This will ignore any given
243      * {@link #sound}.
244      *
245      * <p>
246      * A notification that is noisy is more likely to be presented as a heads-up notification.
247      * </p>
248      *
249      * @see #defaults
250      */
251 
252     public static final int DEFAULT_SOUND = 1;
253 
254     /**
255      * Use the default notification vibrate. This will ignore any given
256      * {@link #vibrate}. Using phone vibration requires the
257      * {@link android.Manifest.permission#VIBRATE VIBRATE} permission.
258      *
259      * <p>
260      * A notification that vibrates is more likely to be presented as a heads-up notification.
261      * </p>
262      *
263      * @see #defaults
264      */
265 
266     public static final int DEFAULT_VIBRATE = 2;
267 
268     /**
269      * Use the default notification lights. This will ignore the
270      * {@link #FLAG_SHOW_LIGHTS} bit, and {@link #ledARGB}, {@link #ledOffMS}, or
271      * {@link #ledOnMS}.
272      *
273      * @see #defaults
274      */
275 
276     public static final int DEFAULT_LIGHTS = 4;
277 
278     /**
279      * Maximum length of CharSequences accepted by Builder and friends.
280      *
281      * <p>
282      * Avoids spamming the system with overly large strings such as full e-mails.
283      */
284     private static final int MAX_CHARSEQUENCE_LENGTH = 1024;
285 
286     /**
287      * Maximum entries of reply text that are accepted by Builder and friends.
288      */
289     private static final int MAX_REPLY_HISTORY = 5;
290 
291     /**
292      * Maximum aspect ratio of the large icon. 16:9
293      */
294     private static final float MAX_LARGE_ICON_ASPECT_RATIO = 16f / 9f;
295 
296     /**
297      * Maximum number of (generic) action buttons in a notification (contextual action buttons are
298      * handled separately).
299      * @hide
300      */
301     public static final int MAX_ACTION_BUTTONS = 3;
302 
303     /**
304      * If the notification contained an unsent draft for a RemoteInput when the user clicked on it,
305      * we're adding the draft as a String extra to the {@link #contentIntent} using this key.
306      *
307      * <p>Apps may use this extra to prepopulate text fields in the app, where the user usually
308      * sends messages.</p>
309      */
310     public static final String EXTRA_REMOTE_INPUT_DRAFT = "android.remoteInputDraft";
311 
312     /**
313      * The call to WearableExtender#setBackground(Bitmap) will have no effect and the passed
314      * Bitmap will not be retained in memory.
315      */
316     @ChangeId
317     @EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
318     @VisibleForTesting
319     static final long WEARABLE_EXTENDER_BACKGROUND_BLOCKED = 270551184L;
320 
321     /**
322      * A timestamp related to this notification, in milliseconds since the epoch.
323      *
324      * Default value: {@link System#currentTimeMillis() Now}.
325      *
326      * Choose a timestamp that will be most relevant to the user. For most finite events, this
327      * corresponds to the time the event happened (or will happen, in the case of events that have
328      * yet to occur but about which the user is being informed). Indefinite events should be
329      * timestamped according to when the activity began.
330      *
331      * Some examples:
332      *
333      * <ul>
334      *   <li>Notification of a new chat message should be stamped when the message was received.</li>
335      *   <li>Notification of an ongoing file download (with a progress bar, for example) should be stamped when the download started.</li>
336      *   <li>Notification of a completed file download should be stamped when the download finished.</li>
337      *   <li>Notification of an upcoming meeting should be stamped with the time the meeting will begin (that is, in the future).</li>
338      *   <li>Notification of an ongoing stopwatch (increasing timer) should be stamped with the watch's start time.
339      *   <li>Notification of an ongoing countdown timer should be stamped with the timer's end time.
340      * </ul>
341      *
342      * For apps targeting {@link android.os.Build.VERSION_CODES#N} and above, this time is not shown
343      * anymore by default and must be opted into by using
344      * {@link android.app.Notification.Builder#setShowWhen(boolean)}
345      */
346     public long when;
347 
348     /**
349      * The creation time of the notification
350      * @hide
351      */
352     public long creationTime;
353 
354     /**
355      * The resource id of a drawable to use as the icon in the status bar.
356      *
357      * @deprecated Use {@link Builder#setSmallIcon(Icon)} instead.
358      */
359     @Deprecated
360     @DrawableRes
361     public int icon;
362 
363     /**
364      * If the icon in the status bar is to have more than one level, you can set this.  Otherwise,
365      * leave it at its default value of 0.
366      *
367      * @see android.widget.ImageView#setImageLevel
368      * @see android.graphics.drawable.Drawable#setLevel
369      */
370     public int iconLevel;
371 
372     /**
373      * The number of events that this notification represents. For example, in a new mail
374      * notification, this could be the number of unread messages.
375      *
376      * The system may or may not use this field to modify the appearance of the notification.
377      * Starting with {@link android.os.Build.VERSION_CODES#O}, the number may be displayed as a
378      * badge icon in Launchers that support badging.
379      */
380     public int number = 0;
381 
382     /**
383      * The intent to execute when the expanded status entry is clicked.  If
384      * this is an activity, it must include the
385      * {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK} flag, which requires
386      * that you take care of task management as described in the
387      * <a href="{@docRoot}guide/topics/fundamentals/tasks-and-back-stack.html">Tasks and Back
388      * Stack</a> document.  In particular, make sure to read the
389      * <a href="{@docRoot}training/notify-user/navigation">Start
390      * an Activity from a Notification</a> page for the correct ways to launch an application from a
391      * notification.
392      */
393     public PendingIntent contentIntent;
394 
395     /**
396      * The intent to execute when the notification is explicitly dismissed by the user, either with
397      * the "Clear All" button or by swiping it away individually.
398      *
399      * This probably shouldn't be launching an activity since several of those will be sent
400      * at the same time.
401      */
402     public PendingIntent deleteIntent;
403 
404     /**
405      * An intent to launch instead of posting the notification to the status bar.
406      *
407      * <p>
408      * The system UI may choose to display a heads-up notification, instead of
409      * launching this intent, while the user is using the device.
410      * </p>
411      *
412      * @see Notification.Builder#setFullScreenIntent
413      */
414     public PendingIntent fullScreenIntent;
415 
416     /**
417      * Text that summarizes this notification for accessibility services.
418      *
419      * As of the L release, this text is no longer shown on screen, but it is still useful to
420      * accessibility services (where it serves as an audible announcement of the notification's
421      * appearance).
422      *
423      * @see #tickerView
424      */
425     public CharSequence tickerText;
426 
427     /**
428      * Formerly, a view showing the {@link #tickerText}.
429      *
430      * No longer displayed in the status bar as of API 21.
431      */
432     @Deprecated
433     public RemoteViews tickerView;
434 
435     /**
436      * The view that will represent this notification in the notification list (which is pulled
437      * down from the status bar).
438      *
439      * As of N, this field may be null. The notification view is determined by the inputs
440      * to {@link Notification.Builder}; a custom RemoteViews can optionally be
441      * supplied with {@link Notification.Builder#setCustomContentView(RemoteViews)}.
442      */
443     @Deprecated
444     public RemoteViews contentView;
445 
446     /**
447      * A large-format version of {@link #contentView}, giving the Notification an
448      * opportunity to show more detail when expanded. The system UI may choose
449      * to show this instead of the normal content view at its discretion.
450      *
451      * As of N, this field may be null. The expanded notification view is determined by the
452      * inputs to {@link Notification.Builder}; a custom RemoteViews can optionally be
453      * supplied with {@link Notification.Builder#setCustomBigContentView(RemoteViews)}.
454      */
455     @Deprecated
456     public RemoteViews bigContentView;
457 
458 
459     /**
460      * A medium-format version of {@link #contentView}, providing the Notification an
461      * opportunity to add action buttons to contentView. At its discretion, the system UI may
462      * choose to show this as a heads-up notification, which will pop up so the user can see
463      * it without leaving their current activity.
464      *
465      * As of N, this field may be null. The heads-up notification view is determined by the
466      * inputs to {@link Notification.Builder}; a custom RemoteViews can optionally be
467      * supplied with {@link Notification.Builder#setCustomHeadsUpContentView(RemoteViews)}.
468      */
469     @Deprecated
470     public RemoteViews headsUpContentView;
471 
472     private boolean mUsesStandardHeader;
473 
474     private static final ArraySet<Integer> STANDARD_LAYOUTS = new ArraySet<>();
475     static {
476         STANDARD_LAYOUTS.add(R.layout.notification_template_material_base);
477         STANDARD_LAYOUTS.add(R.layout.notification_template_material_heads_up_base);
478         STANDARD_LAYOUTS.add(R.layout.notification_template_material_big_base);
479         STANDARD_LAYOUTS.add(R.layout.notification_template_material_big_picture);
480         STANDARD_LAYOUTS.add(R.layout.notification_template_material_big_text);
481         STANDARD_LAYOUTS.add(R.layout.notification_template_material_inbox);
482         STANDARD_LAYOUTS.add(R.layout.notification_template_material_messaging);
483         STANDARD_LAYOUTS.add(R.layout.notification_template_material_big_messaging);
484         STANDARD_LAYOUTS.add(R.layout.notification_template_material_conversation);
485         STANDARD_LAYOUTS.add(R.layout.notification_template_material_media);
486         STANDARD_LAYOUTS.add(R.layout.notification_template_material_big_media);
487         STANDARD_LAYOUTS.add(R.layout.notification_template_material_call);
488         STANDARD_LAYOUTS.add(R.layout.notification_template_material_big_call);
489         STANDARD_LAYOUTS.add(R.layout.notification_template_header);
490     }
491 
492     /**
493      * A large bitmap to be shown in the notification content area.
494      *
495      * @deprecated Use {@link Builder#setLargeIcon(Icon)} instead.
496      */
497     @Deprecated
498     public Bitmap largeIcon;
499 
500     /**
501      * The sound to play.
502      *
503      * <p>
504      * A notification that is noisy is more likely to be presented as a heads-up notification.
505      * </p>
506      *
507      * <p>
508      * To play the default notification sound, see {@link #defaults}.
509      * </p>
510      * @deprecated use {@link NotificationChannel#getSound()}.
511      */
512     @Deprecated
513     public Uri sound;
514 
515     /**
516      * Use this constant as the value for audioStreamType to request that
517      * the default stream type for notifications be used.  Currently the
518      * default stream type is {@link AudioManager#STREAM_NOTIFICATION}.
519      *
520      * @deprecated Use {@link NotificationChannel#getAudioAttributes()} instead.
521      */
522     @Deprecated
523     public static final int STREAM_DEFAULT = -1;
524 
525     /**
526      * The audio stream type to use when playing the sound.
527      * Should be one of the STREAM_ constants from
528      * {@link android.media.AudioManager}.
529      *
530      * @deprecated Use {@link #audioAttributes} instead.
531      */
532     @Deprecated
533     public int audioStreamType = STREAM_DEFAULT;
534 
535     /**
536      * The default value of {@link #audioAttributes}.
537      */
538     public static final AudioAttributes AUDIO_ATTRIBUTES_DEFAULT = new AudioAttributes.Builder()
539             .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
540             .setUsage(AudioAttributes.USAGE_NOTIFICATION)
541             .build();
542 
543     /**
544      * The {@link AudioAttributes audio attributes} to use when playing the sound.
545      *
546      * @deprecated use {@link NotificationChannel#getAudioAttributes()} instead.
547      */
548     @Deprecated
549     public AudioAttributes audioAttributes = AUDIO_ATTRIBUTES_DEFAULT;
550 
551     /**
552      * The pattern with which to vibrate.
553      *
554      * <p>
555      * To vibrate the default pattern, see {@link #defaults}.
556      * </p>
557      *
558      * @see android.os.Vibrator#vibrate(long[],int)
559      * @deprecated use {@link NotificationChannel#getVibrationPattern()}.
560      */
561     @Deprecated
562     public long[] vibrate;
563 
564     /**
565      * The color of the led.  The hardware will do its best approximation.
566      *
567      * @see #FLAG_SHOW_LIGHTS
568      * @see #flags
569      * @deprecated use {@link NotificationChannel#shouldShowLights()}.
570      */
571     @ColorInt
572     @Deprecated
573     public int ledARGB;
574 
575     /**
576      * The number of milliseconds for the LED to be on while it's flashing.
577      * The hardware will do its best approximation.
578      *
579      * @see #FLAG_SHOW_LIGHTS
580      * @see #flags
581      * @deprecated use {@link NotificationChannel#shouldShowLights()}.
582      */
583     @Deprecated
584     public int ledOnMS;
585 
586     /**
587      * The number of milliseconds for the LED to be off while it's flashing.
588      * The hardware will do its best approximation.
589      *
590      * @see #FLAG_SHOW_LIGHTS
591      * @see #flags
592      *
593      * @deprecated use {@link NotificationChannel#shouldShowLights()}.
594      */
595     @Deprecated
596     public int ledOffMS;
597 
598     /**
599      * Specifies which values should be taken from the defaults.
600      * <p>
601      * To set, OR the desired from {@link #DEFAULT_SOUND},
602      * {@link #DEFAULT_VIBRATE}, {@link #DEFAULT_LIGHTS}. For all default
603      * values, use {@link #DEFAULT_ALL}.
604      * </p>
605      *
606      * @deprecated use {@link NotificationChannel#getSound()} and
607      * {@link NotificationChannel#shouldShowLights()} and
608      * {@link NotificationChannel#shouldVibrate()}.
609      */
610     @Deprecated
611     public int defaults;
612 
613     /**
614      * Bit to be bitwise-ored into the {@link #flags} field that should be
615      * set if you want the LED on for this notification.
616      * <ul>
617      * <li>To turn the LED off, pass 0 in the alpha channel for colorARGB
618      *      or 0 for both ledOnMS and ledOffMS.</li>
619      * <li>To turn the LED on, pass 1 for ledOnMS and 0 for ledOffMS.</li>
620      * <li>To flash the LED, pass the number of milliseconds that it should
621      *      be on and off to ledOnMS and ledOffMS.</li>
622      * </ul>
623      * <p>
624      * Since hardware varies, you are not guaranteed that any of the values
625      * you pass are honored exactly.  Use the system defaults if possible
626      * because they will be set to values that work on any given hardware.
627      * <p>
628      * The alpha channel must be set for forward compatibility.
629      *
630      * @deprecated use {@link NotificationChannel#shouldShowLights()}.
631      */
632     @Deprecated
633     public static final int FLAG_SHOW_LIGHTS        = 0x00000001;
634 
635     /**
636      * Bit to be bitwise-ored into the {@link #flags} field that should be
637      * set if this notification is in reference to something that is ongoing,
638      * like a phone call.  It should not be set if this notification is in
639      * reference to something that happened at a particular point in time,
640      * like a missed phone call.
641      */
642     public static final int FLAG_ONGOING_EVENT      = 0x00000002;
643 
644     /**
645      * Bit to be bitwise-ored into the {@link #flags} field that if set,
646      * the audio will be repeated until the notification is
647      * cancelled or the notification window is opened.
648      */
649     public static final int FLAG_INSISTENT          = 0x00000004;
650 
651     /**
652      * Bit to be bitwise-ored into the {@link #flags} field that should be
653      * set if you would only like the sound, vibrate and ticker to be played
654      * if the notification was not already showing.
655      *
656      * Note that using this flag will stop any ongoing alerting behaviour such
657      * as sound, vibration or blinking notification LED.
658      */
659     public static final int FLAG_ONLY_ALERT_ONCE    = 0x00000008;
660 
661     /**
662      * Bit to be bitwise-ored into the {@link #flags} field that should be
663      * set if the notification should be canceled when it is clicked by the
664      * user.
665      */
666     public static final int FLAG_AUTO_CANCEL        = 0x00000010;
667 
668     /**
669      * Bit to be bitwise-ored into the {@link #flags} field that should be
670      * set if the notification should not be canceled when the user clicks
671      * the Clear all button.
672      */
673     public static final int FLAG_NO_CLEAR           = 0x00000020;
674 
675     /**
676      * Bit to be bitwise-ored into the {@link #flags} field that should be
677      * set if this notification represents a currently running service.  This
678      * will normally be set for you by {@link Service#startForeground}.
679      */
680     public static final int FLAG_FOREGROUND_SERVICE = 0x00000040;
681 
682     /**
683      * Obsolete flag indicating high-priority notifications; use the priority field instead.
684      *
685      * @deprecated Use {@link #priority} with a positive value.
686      */
687     @Deprecated
688     public static final int FLAG_HIGH_PRIORITY      = 0x00000080;
689 
690     /**
691      * Bit to be bitswise-ored into the {@link #flags} field that should be
692      * set if this notification is relevant to the current device only
693      * and it is not recommended that it bridge to other devices.
694      */
695     public static final int FLAG_LOCAL_ONLY         = 0x00000100;
696 
697     /**
698      * Bit to be bitswise-ored into the {@link #flags} field that should be
699      * set if this notification is the group summary for a group of notifications.
700      * Grouped notifications may display in a cluster or stack on devices which
701      * support such rendering. Requires a group key also be set using {@link Builder#setGroup}.
702      */
703     public static final int FLAG_GROUP_SUMMARY      = 0x00000200;
704 
705     /**
706      * Bit to be bitswise-ored into the {@link #flags} field that should be
707      * set if this notification is the group summary for an auto-group of notifications.
708      *
709      * @hide
710      */
711     @SystemApi
712     public static final int FLAG_AUTOGROUP_SUMMARY  = 0x00000400;
713 
714     /**
715      * @hide
716      */
717     public static final int FLAG_CAN_COLORIZE = 0x00000800;
718 
719     /**
720      * Bit to be bitswised-ored into the {@link #flags} field that should be
721      * set by the system if this notification is showing as a bubble.
722      *
723      * Applications cannot set this flag directly; they should instead call
724      * {@link Notification.Builder#setBubbleMetadata(BubbleMetadata)} to
725      * request that a notification be displayed as a bubble, and then check
726      * this flag to see whether that request was honored by the system.
727      */
728     public static final int FLAG_BUBBLE = 0x00001000;
729 
730     /**
731      * Bit to be bitswised-ored into the {@link #flags} field that should be
732      * set by the system if this notification is not dismissible.
733      *
734      * This flag is for internal use only; applications cannot set this flag directly.
735      * @hide
736      */
737     public static final int FLAG_NO_DISMISS = 0x00002000;
738 
739     /**
740      * Bit to be bitwise-ORed into the {@link #flags} field that should be
741      * set by the system if the app that sent this notification does not have the permission to send
742      * full screen intents.
743      *
744      * This flag is for internal use only; applications cannot set this flag directly.
745      * @hide
746      */
747     public static final int FLAG_FSI_REQUESTED_BUT_DENIED = 0x00004000;
748 
749     /**
750      * Bit to be bitwise-ored into the {@link #flags} field that should be
751      * set if this notification represents a currently running user-initiated job.
752      *
753      * This flag is for internal use only; applications cannot set this flag directly.
754      * @hide
755      */
756     @TestApi
757     public static final int FLAG_USER_INITIATED_JOB = 0x00008000;
758 
759     /**
760      * Bit to be bitwise-ored into the {@link #flags} field that should be
761      * set if this notification has been lifetime extended due to a direct reply.
762      *
763      * This flag is for internal use only; applications cannot set this flag directly.
764      * @hide
765      */
766     @FlaggedApi(Flags.FLAG_LIFETIME_EXTENSION_REFACTOR)
767     public static final int FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY = 0x00010000;
768 
769     /**
770      * Bit to be bitwise-ored into the {@link #flags} field that should be
771      * set by the system if this notification is silent.
772      *
773      * This flag is for internal use only; applications cannot set this flag directly.
774      * @hide
775      */
776     @FlaggedApi(android.service.notification.Flags.FLAG_NOTIFICATION_SILENT_FLAG)
777     public static final int FLAG_SILENT = 1 << 17;  //0x00020000
778 
779     /**
780      * Bit to be bitwise-ored into the {@link #flags} field that should be
781      * set by the system if this notification is a promoted ongoing notification, both because it
782      * {@link #hasPromotableCharacteristics()} and the user has not disabled the feature for this
783      * app.
784      *
785      * Applications cannot set this flag directly, but the posting app and
786      * {@link android.service.notification.NotificationListenerService} can read it.
787      */
788     @FlaggedApi(Flags.FLAG_API_RICH_ONGOING)
789     public static final int FLAG_PROMOTED_ONGOING = 0x00040000;
790 
791     private static final Set<Class<? extends Style>> PLATFORM_STYLE_CLASSES = Set.of(
792             BigTextStyle.class,
793             BigPictureStyle.class,
794             InboxStyle.class,
795             MediaStyle.class,
796             DecoratedCustomViewStyle.class,
797             DecoratedMediaCustomViewStyle.class,
798             MessagingStyle.class,
799             CallStyle.class
800     );
801 
isPlatformStyle(Style style)802     private static boolean isPlatformStyle(Style style) {
803         if (style == null) {
804             return false;
805         }
806 
807         if (PLATFORM_STYLE_CLASSES.contains(style.getClass())) {
808             return true;
809         }
810 
811         if (Flags.apiRichOngoing()) {
812             return style.getClass() == ProgressStyle.class;
813         }
814 
815         return false;
816     }
817 
isStandardLayout(int layoutId)818     private static boolean isStandardLayout(int layoutId) {
819         if (Flags.notificationsRedesignTemplates()) {
820             return switch (layoutId) {
821                 case R.layout.notification_2025_template_collapsed_base,
822                      R.layout.notification_2025_template_expanded_base,
823                      R.layout.notification_2025_template_heads_up_base,
824                      R.layout.notification_2025_template_header,
825                      R.layout.notification_2025_template_collapsed_conversation,
826                      R.layout.notification_2025_template_expanded_conversation,
827                      R.layout.notification_2025_template_collapsed_call,
828                      R.layout.notification_2025_template_expanded_call,
829                      R.layout.notification_2025_template_collapsed_messaging,
830                      R.layout.notification_2025_template_expanded_messaging,
831                      R.layout.notification_2025_template_collapsed_media,
832                      R.layout.notification_2025_template_expanded_media,
833                      R.layout.notification_2025_template_expanded_big_picture,
834                      R.layout.notification_2025_template_expanded_big_text,
835                      R.layout.notification_2025_template_expanded_inbox -> true;
836                 case R.layout.notification_2025_template_expanded_progress
837                         -> Flags.apiRichOngoing();
838                 default -> false;
839             };
840         }
841         if (Flags.apiRichOngoing()) {
842             if (layoutId == R.layout.notification_template_material_progress) {
843                 return true;
844             }
845         }
846         return STANDARD_LAYOUTS.contains(layoutId);
847     }
848 
849     /** @hide */
850     @IntDef(flag = true, prefix = {"FLAG_"}, value = {
851             FLAG_SHOW_LIGHTS,
852             FLAG_ONGOING_EVENT,
853             FLAG_INSISTENT,
854             FLAG_ONLY_ALERT_ONCE,
855             FLAG_AUTO_CANCEL,
856             FLAG_NO_CLEAR,
857             FLAG_FOREGROUND_SERVICE,
858             FLAG_HIGH_PRIORITY,
859             FLAG_LOCAL_ONLY,
860             FLAG_GROUP_SUMMARY,
861             FLAG_AUTOGROUP_SUMMARY,
862             FLAG_CAN_COLORIZE,
863             FLAG_BUBBLE,
864             FLAG_NO_DISMISS,
865             FLAG_FSI_REQUESTED_BUT_DENIED,
866             FLAG_USER_INITIATED_JOB,
867             FLAG_SILENT
868     })
869     @Retention(RetentionPolicy.SOURCE)
870     public @interface NotificationFlags{};
871 
872     public int flags;
873 
874     /** @hide */
875     @IntDef(prefix = { "PRIORITY_" }, value = {
876             PRIORITY_DEFAULT,
877             PRIORITY_LOW,
878             PRIORITY_MIN,
879             PRIORITY_HIGH,
880             PRIORITY_MAX
881     })
882     @Retention(RetentionPolicy.SOURCE)
883     public @interface Priority {}
884 
885     /**
886      * Default notification {@link #priority}. If your application does not prioritize its own
887      * notifications, use this value for all notifications.
888      *
889      * @deprecated use {@link NotificationManager#IMPORTANCE_DEFAULT} instead.
890      */
891     @Deprecated
892     public static final int PRIORITY_DEFAULT = 0;
893 
894     /**
895      * Lower {@link #priority}, for items that are less important. The UI may choose to show these
896      * items smaller, or at a different position in the list, compared with your app's
897      * {@link #PRIORITY_DEFAULT} items.
898      *
899      * @deprecated use {@link NotificationManager#IMPORTANCE_LOW} instead.
900      */
901     @Deprecated
902     public static final int PRIORITY_LOW = -1;
903 
904     /**
905      * Lowest {@link #priority}; these items might not be shown to the user except under special
906      * circumstances, such as detailed notification logs.
907      *
908      * @deprecated use {@link NotificationManager#IMPORTANCE_MIN} instead.
909      */
910     @Deprecated
911     public static final int PRIORITY_MIN = -2;
912 
913     /**
914      * Higher {@link #priority}, for more important notifications or alerts. The UI may choose to
915      * show these items larger, or at a different position in notification lists, compared with
916      * your app's {@link #PRIORITY_DEFAULT} items.
917      *
918      * @deprecated use {@link NotificationManager#IMPORTANCE_HIGH} instead.
919      */
920     @Deprecated
921     public static final int PRIORITY_HIGH = 1;
922 
923     /**
924      * Highest {@link #priority}, for your application's most important items that require the
925      * user's prompt attention or input.
926      *
927      * @deprecated use {@link NotificationManager#IMPORTANCE_HIGH} instead.
928      */
929     @Deprecated
930     public static final int PRIORITY_MAX = 2;
931 
932     /**
933      * Relative priority for this notification.
934      *
935      * Priority is an indication of how much of the user's valuable attention should be consumed by
936      * this notification. Low-priority notifications may be hidden from the user in certain
937      * situations, while the user might be interrupted for a higher-priority notification. The
938      * system will make a determination about how to interpret this priority when presenting
939      * the notification.
940      *
941      * <p>
942      * A notification that is at least {@link #PRIORITY_HIGH} is more likely to be presented
943      * as a heads-up notification.
944      * </p>
945      *
946      * @deprecated use {@link NotificationChannel#getImportance()} instead.
947      */
948     @Priority
949     @Deprecated
950     public int priority;
951 
952     /**
953      * Accent color (an ARGB integer like the constants in {@link android.graphics.Color})
954      * to be applied by the standard Style templates when presenting this notification.
955      *
956      * The current template design constructs a colorful header image by overlaying the
957      * {@link #icon} image (stenciled in white) atop a field of this color. Alpha components are
958      * ignored.
959      */
960     @ColorInt
961     public int color = COLOR_DEFAULT;
962 
963     /**
964      * Special value of {@link #color} telling the system not to decorate this notification with
965      * any special color but instead use default colors when presenting this notification.
966      */
967     @ColorInt
968     public static final int COLOR_DEFAULT = 0; // AKA Color.TRANSPARENT
969 
970     /**
971      * Special value of {@link #color} used as a place holder for an invalid color.
972      * @hide
973      */
974     @ColorInt
975     public static final int COLOR_INVALID = 1;
976 
977     /**
978      * Sphere of visibility of this notification, which affects how and when the SystemUI reveals
979      * the notification's presence and contents in untrusted situations (namely, on the secure
980      * lockscreen and during screen sharing).
981      *
982      * The default level, {@link #VISIBILITY_PRIVATE}, behaves exactly as notifications have always
983      * done on Android: The notification's {@link #icon} and {@link #tickerText} (if available) are
984      * shown in all situations, but the contents are only available if the device is unlocked for
985      * the appropriate user and there is no active screen sharing session.
986      *
987      * A more permissive policy can be expressed by {@link #VISIBILITY_PUBLIC}; such a notification
988      * can be read even in an "insecure" context (that is, above a secure lockscreen or while
989      * screen sharing with a remote viewer).
990      * To modify the public version of this notification—for example, to redact some portions—see
991      * {@link Builder#setPublicVersion(Notification)}.
992      *
993      * Finally, a notification can be made {@link #VISIBILITY_SECRET}, which will suppress its icon
994      * and ticker until the user has bypassed the lockscreen.
995      */
996     public @Visibility int visibility;
997 
998     /** @hide */
999     @IntDef(prefix = { "VISIBILITY_" }, value = {
1000             VISIBILITY_PUBLIC,
1001             VISIBILITY_PRIVATE,
1002             VISIBILITY_SECRET,
1003     })
1004     @Retention(RetentionPolicy.SOURCE)
1005     public @interface Visibility {}
1006 
1007     /**
1008      * Notification visibility: Show this notification in its entirety on all lockscreens and while
1009      * screen sharing.
1010      *
1011      * {@see #visibility}
1012      */
1013     public static final int VISIBILITY_PUBLIC = 1;
1014 
1015     /**
1016      * Notification visibility: Show this notification on all lockscreens, but conceal sensitive or
1017      * private information on secure lockscreens. Conceal sensitive or private information while
1018      * screen sharing.
1019      *
1020      * {@see #visibility}
1021      */
1022     public static final int VISIBILITY_PRIVATE = 0;
1023 
1024     /**
1025      * Notification visibility: Do not reveal any part of this notification on a secure lockscreen
1026      * or while screen sharing.
1027      *
1028      * {@see #visibility}
1029      */
1030     public static final int VISIBILITY_SECRET = -1;
1031 
1032     /**
1033      * @hide
1034      */
1035     @IntDef(prefix = "VISIBILITY_", value = {
1036             VISIBILITY_PUBLIC,
1037             VISIBILITY_PRIVATE,
1038             VISIBILITY_SECRET,
1039             NotificationManager.VISIBILITY_NO_OVERRIDE
1040     })
1041     @Retention(RetentionPolicy.SOURCE)
1042     public @interface NotificationVisibilityOverride{};
1043 
1044     /**
1045      * Notification category: incoming call (voice or video) or similar synchronous communication request.
1046      */
1047     public static final String CATEGORY_CALL = "call";
1048 
1049     /**
1050      * Notification category: map turn-by-turn navigation.
1051      */
1052     public static final String CATEGORY_NAVIGATION = "navigation";
1053 
1054     /**
1055      * Notification category: incoming direct message (SMS, instant message, etc.).
1056      */
1057     public static final String CATEGORY_MESSAGE = "msg";
1058 
1059     /**
1060      * Notification category: asynchronous bulk message (email).
1061      */
1062     public static final String CATEGORY_EMAIL = "email";
1063 
1064     /**
1065      * Notification category: calendar event.
1066      */
1067     public static final String CATEGORY_EVENT = "event";
1068 
1069     /**
1070      * Notification category: promotion or advertisement.
1071      */
1072     public static final String CATEGORY_PROMO = "promo";
1073 
1074     /**
1075      * Notification category: alarm or timer.
1076      */
1077     public static final String CATEGORY_ALARM = "alarm";
1078 
1079     /**
1080      * Notification category: progress of a long-running background operation.
1081      */
1082     public static final String CATEGORY_PROGRESS = "progress";
1083 
1084     /**
1085      * Notification category: social network or sharing update.
1086      */
1087     public static final String CATEGORY_SOCIAL = "social";
1088 
1089     /**
1090      * Notification category: error in background operation or authentication status.
1091      */
1092     public static final String CATEGORY_ERROR = "err";
1093 
1094     /**
1095      * Notification category: media transport control for playback.
1096      */
1097     public static final String CATEGORY_TRANSPORT = "transport";
1098 
1099     /**
1100      * Notification category: system or device status update.  Reserved for system use.
1101      */
1102     public static final String CATEGORY_SYSTEM = "sys";
1103 
1104     /**
1105      * Notification category: indication of running background service.
1106      */
1107     public static final String CATEGORY_SERVICE = "service";
1108 
1109     /**
1110      * Notification category: a specific, timely recommendation for a single thing.
1111      * For example, a news app might want to recommend a news story it believes the user will
1112      * want to read next.
1113      */
1114     public static final String CATEGORY_RECOMMENDATION = "recommendation";
1115 
1116     /**
1117      * Notification category: ongoing information about device or contextual status.
1118      */
1119     public static final String CATEGORY_STATUS = "status";
1120 
1121     /**
1122      * Notification category: user-scheduled reminder.
1123      */
1124     public static final String CATEGORY_REMINDER = "reminder";
1125 
1126     /**
1127      * Notification category: extreme car emergencies.
1128      * @hide
1129      */
1130     @SystemApi
1131     public static final String CATEGORY_CAR_EMERGENCY = "car_emergency";
1132 
1133     /**
1134      * Notification category: car warnings.
1135      * @hide
1136      */
1137     @SystemApi
1138     public static final String CATEGORY_CAR_WARNING = "car_warning";
1139 
1140     /**
1141      * Notification category: general car system information.
1142      * @hide
1143      */
1144     @SystemApi
1145     public static final String CATEGORY_CAR_INFORMATION = "car_information";
1146 
1147     /**
1148      * Notification category: tracking a user's workout.
1149      */
1150     public static final String CATEGORY_WORKOUT = "workout";
1151 
1152     /**
1153      * Notification category: temporarily sharing location.
1154      */
1155     public static final String CATEGORY_LOCATION_SHARING = "location_sharing";
1156 
1157     /**
1158      * Notification category: running stopwatch.
1159      */
1160     public static final String CATEGORY_STOPWATCH = "stopwatch";
1161 
1162     /**
1163      * Notification category: missed call.
1164      */
1165     public static final String CATEGORY_MISSED_CALL = "missed_call";
1166 
1167     /**
1168      * Notification category: voicemail.
1169      */
1170     @FlaggedApi(Flags.FLAG_CATEGORY_VOICEMAIL)
1171     public static final String CATEGORY_VOICEMAIL = "voicemail";
1172 
1173     /**
1174      * One of the predefined notification categories (see the <code>CATEGORY_*</code> constants)
1175      * that best describes this Notification.  May be used by the system for ranking and filtering.
1176      */
1177     public String category;
1178 
1179     @UnsupportedAppUsage
1180     private String mGroupKey;
1181 
1182     /**
1183      * Get the key used to group this notification into a cluster or stack
1184      * with other notifications on devices which support such rendering.
1185      */
getGroup()1186     public String getGroup() {
1187         return mGroupKey;
1188     }
1189 
1190     private String mSortKey;
1191 
1192     /**
1193      * Get a sort key that orders this notification among other notifications from the
1194      * same package. This can be useful if an external sort was already applied and an app
1195      * would like to preserve this. Notifications will be sorted lexicographically using this
1196      * value, although providing different priorities in addition to providing sort key may
1197      * cause this value to be ignored.
1198      *
1199      * <p>This sort key can also be used to order members of a notification group. See
1200      * {@link Builder#setGroup}.
1201      *
1202      * @see String#compareTo(String)
1203      */
getSortKey()1204     public String getSortKey() {
1205         return mSortKey;
1206     }
1207 
1208     /**
1209      * Additional semantic data to be carried around with this Notification.
1210      * <p>
1211      * The extras keys defined here are intended to capture the original inputs to {@link Builder}
1212      * APIs, and are intended to be used by
1213      * {@link android.service.notification.NotificationListenerService} implementations to extract
1214      * detailed information from notification objects.
1215      */
1216     public Bundle extras = new Bundle();
1217 
1218     /**
1219      * All pending intents in the notification as the system needs to be able to access them but
1220      * touching the extras bundle in the system process is not safe because the bundle may contain
1221      * custom parcelable objects.
1222      *
1223      * @hide
1224      */
1225     @UnsupportedAppUsage
1226     public ArraySet<PendingIntent> allPendingIntents;
1227 
1228     /**
1229      * Token identifying the notification that is applying doze/bgcheck allowlisting to the
1230      * pending intents inside of it, so only those will get the behavior.
1231      *
1232      * @hide
1233      */
1234     private IBinder mAllowlistToken;
1235 
1236     /**
1237      * Must be set by a process to start associating tokens with Notification objects
1238      * coming in to it.  This is set by NotificationManagerService.
1239      *
1240      * @hide
1241      */
1242     static public IBinder processAllowlistToken;
1243 
1244     /**
1245      * {@link #extras} key: this is the title of the notification,
1246      * as supplied to {@link Builder#setContentTitle(CharSequence)}.
1247      */
1248     public static final String EXTRA_TITLE = "android.title";
1249 
1250     /**
1251      * {@link #extras} key: this is the title of the notification when shown in expanded form,
1252      * e.g. as supplied to {@link BigTextStyle#setBigContentTitle(CharSequence)}.
1253      */
1254     public static final String EXTRA_TITLE_BIG = EXTRA_TITLE + ".big";
1255 
1256     /**
1257      * {@link #extras} key: this is the main text payload, as supplied to
1258      * {@link Builder#setContentText(CharSequence)}.
1259      */
1260     public static final String EXTRA_TEXT = "android.text";
1261 
1262     /**
1263      * {@link #extras} key: this is a third line of text, as supplied to
1264      * {@link Builder#setSubText(CharSequence)}.
1265      */
1266     public static final String EXTRA_SUB_TEXT = "android.subText";
1267 
1268     /**
1269      * {@link #extras} key: this is the remote input history, as supplied to
1270      * {@link Builder#setRemoteInputHistory(CharSequence[])}.
1271      *
1272      * Apps can fill this through {@link Builder#setRemoteInputHistory(CharSequence[])}
1273      * with the most recent inputs that have been sent through a {@link RemoteInput} of this
1274      * Notification and are expected to clear it once the it is no longer relevant (e.g. for chat
1275      * notifications once the other party has responded).
1276      *
1277      * The extra with this key is of type CharSequence[] and contains the most recent entry at
1278      * the 0 index, the second most recent at the 1 index, etc.
1279      *
1280      * @see Builder#setRemoteInputHistory(CharSequence[])
1281      */
1282     public static final String EXTRA_REMOTE_INPUT_HISTORY = "android.remoteInputHistory";
1283 
1284 
1285     /**
1286      * {@link #extras} key: this is a remote input history which can include media messages
1287      * in addition to text, as supplied to
1288      * {@link Builder#setRemoteInputHistory(RemoteInputHistoryItem[])} or
1289      * {@link Builder#setRemoteInputHistory(CharSequence[])}.
1290      *
1291      * SystemUI can populate this through
1292      * {@link Builder#setRemoteInputHistory(RemoteInputHistoryItem[])} with the most recent inputs
1293      * that have been sent through a {@link RemoteInput} of this Notification. These items can
1294      * represent either media content (specified by a URI and a MIME type) or a text message
1295      * (described by a CharSequence).
1296      *
1297      * To maintain compatibility, this can also be set by apps with
1298      * {@link Builder#setRemoteInputHistory(CharSequence[])}, which will create a
1299      * {@link RemoteInputHistoryItem} for each of the provided text-only messages.
1300      *
1301      * The extra with this key is of type {@link RemoteInputHistoryItem[]} and contains the most
1302      * recent entry at the 0 index, the second most recent at the 1 index, etc.
1303      *
1304      * @see Builder#setRemoteInputHistory(RemoteInputHistoryItem[])
1305      * @hide
1306      */
1307     public static final String EXTRA_REMOTE_INPUT_HISTORY_ITEMS = "android.remoteInputHistoryItems";
1308 
1309     /**
1310      * {@link #extras} key: boolean as supplied to
1311      * {@link Builder#setShowRemoteInputSpinner(boolean)}.
1312      *
1313      * If set to true, then the view displaying the remote input history from
1314      * {@link Builder#setRemoteInputHistory(CharSequence[])} will have a progress spinner.
1315      *
1316      * @see Builder#setShowRemoteInputSpinner(boolean)
1317      * @hide
1318      */
1319     public static final String EXTRA_SHOW_REMOTE_INPUT_SPINNER = "android.remoteInputSpinner";
1320 
1321     /**
1322      * {@link #extras} key: boolean as supplied to
1323      * {@link Builder#setHideSmartReplies(boolean)}.
1324      *
1325      * If set to true, then any smart reply buttons will be hidden.
1326      *
1327      * @see Builder#setHideSmartReplies(boolean)
1328      * @hide
1329      */
1330     public static final String EXTRA_HIDE_SMART_REPLIES = "android.hideSmartReplies";
1331 
1332     /**
1333      * {@link #extras} key: this is a small piece of additional text as supplied to
1334      * {@link Builder#setContentInfo(CharSequence)}.
1335      */
1336     public static final String EXTRA_INFO_TEXT = "android.infoText";
1337 
1338     /**
1339      * {@link #extras} key: this is a line of summary information intended to be shown
1340      * alongside expanded notifications, as supplied to (e.g.)
1341      * {@link BigTextStyle#setSummaryText(CharSequence)}.
1342      */
1343     public static final String EXTRA_SUMMARY_TEXT = "android.summaryText";
1344 
1345     /**
1346      * {@link #extras} key: this is the longer text shown in the expanded form of a
1347      * {@link BigTextStyle} notification, as supplied to
1348      * {@link BigTextStyle#bigText(CharSequence)}.
1349      */
1350     public static final String EXTRA_BIG_TEXT = "android.bigText";
1351 
1352     /**
1353      * {@link #extras} key: very short text summarizing the most critical information contained in
1354      * the notification.
1355      *
1356      * @hide
1357      */
1358     @FlaggedApi(Flags.FLAG_API_RICH_ONGOING)
1359     public static final String EXTRA_SHORT_CRITICAL_TEXT = "android.shortCriticalText";
1360 
1361     /**
1362      * {@link #extras} key: this is the resource ID of the notification's main small icon, as
1363      * supplied to {@link Builder#setSmallIcon(int)}.
1364      *
1365      * @deprecated Use {@link #getSmallIcon()}, which supports a wider variety of icon sources.
1366      */
1367     @Deprecated
1368     public static final String EXTRA_SMALL_ICON = "android.icon";
1369 
1370     /**
1371      * {@link #extras} key: this is a bitmap to be used instead of the small icon when showing the
1372      * notification payload, as
1373      * supplied to {@link Builder#setLargeIcon(android.graphics.Bitmap)}.
1374      *
1375      * @deprecated Use {@link #getLargeIcon()}, which supports a wider variety of icon sources.
1376      */
1377     @Deprecated
1378     public static final String EXTRA_LARGE_ICON = "android.largeIcon";
1379 
1380     /**
1381      * {@link #extras} key: this is a bitmap to be used instead of the one from
1382      * {@link Builder#setLargeIcon(android.graphics.Bitmap)} when the notification is
1383      * shown in its expanded form, as supplied to
1384      * {@link BigPictureStyle#bigLargeIcon(android.graphics.Bitmap)}.
1385      */
1386     public static final String EXTRA_LARGE_ICON_BIG = EXTRA_LARGE_ICON + ".big";
1387 
1388     /**
1389      * {@link #extras} key: this is the progress value supplied to
1390      * {@link Builder#setProgress(int, int, boolean)}.
1391      */
1392     public static final String EXTRA_PROGRESS = "android.progress";
1393 
1394     /**
1395      * {@link #extras} key: this is the maximum value supplied to
1396      * {@link Builder#setProgress(int, int, boolean)}.
1397      */
1398     public static final String EXTRA_PROGRESS_MAX = "android.progressMax";
1399 
1400     /**
1401      * {@link #extras} key: whether the progress bar is indeterminate, supplied to
1402      * {@link Builder#setProgress(int, int, boolean)}.
1403      */
1404     public static final String EXTRA_PROGRESS_INDETERMINATE = "android.progressIndeterminate";
1405 
1406     /**
1407      * {@link #extras} key: whether {@link #when} should be shown as a count-up timer (specifically
1408      * a {@link android.widget.Chronometer}) instead of a timestamp, as supplied to
1409      * {@link Builder#setUsesChronometer(boolean)}.
1410      */
1411     public static final String EXTRA_SHOW_CHRONOMETER = "android.showChronometer";
1412 
1413     /**
1414      * {@link #extras} key: whether the chronometer set on the notification should count down
1415      * instead of counting up. Is only relevant if key {@link #EXTRA_SHOW_CHRONOMETER} is present.
1416      * This extra is a boolean. The default is false.
1417      */
1418     public static final String EXTRA_CHRONOMETER_COUNT_DOWN = "android.chronometerCountDown";
1419 
1420     /**
1421      * {@link #extras} key: whether {@link #when} should be shown,
1422      * as supplied to {@link Builder#setShowWhen(boolean)}.
1423      */
1424     public static final String EXTRA_SHOW_WHEN = "android.showWhen";
1425 
1426     /**
1427      * {@link #extras} key: this is a bitmap to be shown in {@link BigPictureStyle} expanded
1428      * notifications, supplied to {@link BigPictureStyle#bigPicture(android.graphics.Bitmap)}.
1429      */
1430     public static final String EXTRA_PICTURE = "android.picture";
1431 
1432     /**
1433      * {@link #extras} key: this is an {@link Icon} of an image to be
1434      * shown in {@link BigPictureStyle} expanded notifications, supplied to
1435      * {@link BigPictureStyle#bigPicture(Icon)}.
1436      */
1437     public static final String EXTRA_PICTURE_ICON = "android.pictureIcon";
1438 
1439     /**
1440      * {@link #extras} key: this is a content description of the big picture supplied from
1441      * {@link BigPictureStyle#bigPicture(Bitmap)}, supplied to
1442      * {@link BigPictureStyle#setContentDescription(CharSequence)}.
1443      */
1444     public static final String EXTRA_PICTURE_CONTENT_DESCRIPTION =
1445             "android.pictureContentDescription";
1446 
1447     /**
1448      * {@link #extras} key: this is a boolean to indicate that the
1449      * {@link BigPictureStyle#bigPicture(Bitmap) big picture} is to be shown in the collapsed state
1450      * of a {@link BigPictureStyle} notification.  This will replace a
1451      * {@link Builder#setLargeIcon(Icon) large icon} in that state if one was provided.
1452      */
1453     public static final String EXTRA_SHOW_BIG_PICTURE_WHEN_COLLAPSED =
1454             "android.showBigPictureWhenCollapsed";
1455 
1456     /**
1457      * {@link #extras} key: An array of CharSequences to show in {@link InboxStyle} expanded
1458      * notifications, each of which was supplied to {@link InboxStyle#addLine(CharSequence)}.
1459      */
1460     public static final String EXTRA_TEXT_LINES = "android.textLines";
1461 
1462     /**
1463      * {@link #extras} key: A string representing the name of the specific
1464      * {@link android.app.Notification.Style} used to create this notification.
1465      */
1466     public static final String EXTRA_TEMPLATE = "android.template";
1467 
1468     /**
1469      * {@link #extras} key: A String array containing the people that this notification relates to,
1470      * each of which was supplied to {@link Builder#addPerson(String)}.
1471      *
1472      * @deprecated the actual objects are now in {@link #EXTRA_PEOPLE_LIST}
1473      */
1474     public static final String EXTRA_PEOPLE = "android.people";
1475 
1476     /**
1477      * {@link #extras} key: An arrayList of {@link Person} objects containing the people that
1478      * this notification relates to.
1479      */
1480     public static final String EXTRA_PEOPLE_LIST = "android.people.list";
1481 
1482     /**
1483      * Allow certain system-generated notifications to appear before the device is provisioned.
1484      * Only available to notifications coming from the android package.
1485      * @hide
1486      */
1487     @SystemApi
1488     @RequiresPermission(android.Manifest.permission.NOTIFICATION_DURING_SETUP)
1489     public static final String EXTRA_ALLOW_DURING_SETUP = "android.allowDuringSetup";
1490 
1491     /**
1492      * {@link #extras} key:
1493      * flat {@link String} representation of a {@link android.content.ContentUris content URI}
1494      * pointing to an image that can be displayed in the background when the notification is
1495      * selected. Used on television platforms. The URI must point to an image stream suitable for
1496      * passing into {@link android.graphics.BitmapFactory#decodeStream(java.io.InputStream)
1497      * BitmapFactory.decodeStream}; all other content types will be ignored.
1498      */
1499     public static final String EXTRA_BACKGROUND_IMAGE_URI = "android.backgroundImageUri";
1500 
1501     /**
1502      * {@link #extras} key: A
1503      * {@link android.media.session.MediaSession.Token} associated with a
1504      * {@link android.app.Notification.MediaStyle} notification.
1505      */
1506     public static final String EXTRA_MEDIA_SESSION = "android.mediaSession";
1507 
1508     /**
1509      * {@link #extras} key: A {@code CharSequence} name of a remote device used for a media session
1510      * associated with a {@link Notification.MediaStyle} notification. This will show in the media
1511      * controls output switcher instead of the local device name.
1512      * @hide
1513      */
1514     @TestApi
1515     public static final String EXTRA_MEDIA_REMOTE_DEVICE = "android.mediaRemoteDevice";
1516 
1517     /**
1518      * {@link #extras} key: A {@code int} resource ID for an icon that should show in the output
1519      * switcher of the media controls for a {@link Notification.MediaStyle} notification.
1520      * @hide
1521      */
1522     @TestApi
1523     public static final String EXTRA_MEDIA_REMOTE_ICON = "android.mediaRemoteIcon";
1524 
1525     /**
1526      * {@link #extras} key: A {@code PendingIntent} that will replace the default action for the
1527      * media controls output switcher chip, associated with a {@link Notification.MediaStyle}
1528      * notification. This should launch an activity.
1529      * @hide
1530      */
1531     @TestApi
1532     public static final String EXTRA_MEDIA_REMOTE_INTENT = "android.mediaRemoteIntent";
1533 
1534     /**
1535      * {@link #extras} key: the indices of actions to be shown in the compact view,
1536      * as supplied to (e.g.) {@link MediaStyle#setShowActionsInCompactView(int...)}.
1537      */
1538     public static final String EXTRA_COMPACT_ACTIONS = "android.compactActions";
1539 
1540     /**
1541      * {@link #extras} key: the username to be displayed for all messages sent by the user including
1542      * direct replies
1543      * {@link android.app.Notification.MessagingStyle} notification. This extra is a
1544      * {@link CharSequence}
1545      *
1546      * @deprecated use {@link #EXTRA_MESSAGING_PERSON}
1547      */
1548     public static final String EXTRA_SELF_DISPLAY_NAME = "android.selfDisplayName";
1549 
1550     /**
1551      * {@link #extras} key: the person to be displayed for all messages sent by the user including
1552      * direct replies
1553      * {@link android.app.Notification.MessagingStyle} notification. This extra is a
1554      * {@link Person}
1555      */
1556     public static final String EXTRA_MESSAGING_PERSON = "android.messagingUser";
1557 
1558     /**
1559      * {@link #extras} key: a {@link CharSequence} to be displayed as the title to a conversation
1560      * represented by a {@link android.app.Notification.MessagingStyle}
1561      */
1562     public static final String EXTRA_CONVERSATION_TITLE = "android.conversationTitle";
1563 
1564     /** @hide */
1565     public static final String EXTRA_CONVERSATION_ICON = "android.conversationIcon";
1566 
1567     /** @hide */
1568     public static final String EXTRA_CONVERSATION_UNREAD_MESSAGE_COUNT =
1569             "android.conversationUnreadMessageCount";
1570 
1571     /**
1572      * {@link #extras} key: an array of {@link android.app.Notification.MessagingStyle.Message}
1573      * bundles provided by a
1574      * {@link android.app.Notification.MessagingStyle} notification. This extra is a parcelable
1575      * array of bundles.
1576      */
1577     public static final String EXTRA_MESSAGES = "android.messages";
1578 
1579     /**
1580      * {@link #extras} key: an array of
1581      * {@link android.app.Notification.MessagingStyle#addHistoricMessage historic}
1582      * {@link android.app.Notification.MessagingStyle.Message} bundles provided by a
1583      * {@link android.app.Notification.MessagingStyle} notification. This extra is a parcelable
1584      * array of bundles.
1585      */
1586     public static final String EXTRA_HISTORIC_MESSAGES = "android.messages.historic";
1587 
1588     /**
1589      * {@link #extras} key: whether the {@link android.app.Notification.MessagingStyle} notification
1590      * represents a group conversation.
1591      */
1592     public static final String EXTRA_IS_GROUP_CONVERSATION = "android.isGroupConversation";
1593 
1594     /**
1595      * {@link #extras} key: the type of call represented by the
1596      * {@link android.app.Notification.CallStyle} notification. This extra is an int.
1597      */
1598     public static final String EXTRA_CALL_TYPE = "android.callType";
1599 
1600     /**
1601      * {@link #extras} key: whether the  {@link android.app.Notification.CallStyle} notification
1602      * is for a call that will activate video when answered. This extra is a boolean.
1603      */
1604     public static final String EXTRA_CALL_IS_VIDEO = "android.callIsVideo";
1605 
1606     /**
1607      * {@link #extras} key: the person to be displayed as calling for the
1608      * {@link android.app.Notification.CallStyle} notification. This extra is a {@link Person}.
1609      */
1610     public static final String EXTRA_CALL_PERSON = "android.callPerson";
1611 
1612     /**
1613      * {@link #extras} key: the icon to be displayed as a verification status of the caller on a
1614      * {@link android.app.Notification.CallStyle} notification. This extra is an {@link Icon}.
1615      */
1616     public static final String EXTRA_VERIFICATION_ICON = "android.verificationIcon";
1617 
1618     /**
1619      * {@link #extras} key: the text to be displayed as a verification status of the caller on a
1620      * {@link android.app.Notification.CallStyle} notification. This extra is a
1621      * {@link CharSequence}.
1622      */
1623     public static final String EXTRA_VERIFICATION_TEXT = "android.verificationText";
1624 
1625     /**
1626      * {@link #extras} key: the intent to be sent when the users answers a
1627      * {@link android.app.Notification.CallStyle} notification. This extra is a
1628      * {@link PendingIntent}.
1629      */
1630     public static final String EXTRA_ANSWER_INTENT = "android.answerIntent";
1631 
1632     /**
1633      * {@link #extras} key: the intent to be sent when the users declines a
1634      * {@link android.app.Notification.CallStyle} notification. This extra is a
1635      * {@link PendingIntent}.
1636      */
1637     public static final String EXTRA_DECLINE_INTENT = "android.declineIntent";
1638 
1639     /**
1640      * {@link #extras} key: the intent to be sent when the users hangs up a
1641      * {@link android.app.Notification.CallStyle} notification. This extra is a
1642      * {@link PendingIntent}.
1643      */
1644     public static final String EXTRA_HANG_UP_INTENT = "android.hangUpIntent";
1645 
1646     /**
1647      * {@link #extras} key: the color used as a hint for the Answer action button of a
1648      * {@link android.app.Notification.CallStyle} notification. This extra is a {@code ColorInt}.
1649      */
1650     public static final String EXTRA_ANSWER_COLOR = "android.answerColor";
1651 
1652     /**
1653      * {@link #extras} key: the color used as a hint for the Decline or Hang Up action button of a
1654      * {@link android.app.Notification.CallStyle} notification. This extra is a {@code ColorInt}.
1655      */
1656     public static final String EXTRA_DECLINE_COLOR = "android.declineColor";
1657 
1658     /**
1659      * {@link #extras} key: whether the notification should be colorized as
1660      * supplied to {@link Builder#setColorized(boolean)}.
1661      */
1662     public static final String EXTRA_COLORIZED = "android.colorized";
1663 
1664     /**
1665      * {@link #extras} key: an arraylist of {@link android.app.Notification.ProgressStyle.Segment}
1666      * bundles provided by a
1667      * {@link android.app.Notification.ProgressStyle} notification as supplied to
1668      * {@link ProgressStyle#setProgressSegments}
1669      * or {@link ProgressStyle#addProgressSegment(ProgressStyle.Segment)}.
1670      * This extra is a parcelable array list of bundles.
1671      * @hide
1672      */
1673     @FlaggedApi(Flags.FLAG_API_RICH_ONGOING)
1674     public static final String EXTRA_PROGRESS_SEGMENTS = "android.progressSegments";
1675 
1676     /**
1677      * {@link #extras} key: an arraylist of {@link ProgressStyle.Point}
1678      * bundles provided by a
1679      * {@link android.app.Notification.ProgressStyle} notification as supplied to
1680      * {@link ProgressStyle#setProgressPoints}
1681      * or {@link ProgressStyle#addProgressPoint(ProgressStyle.Point)}.
1682      * This extra is a parcelable array list of bundles.
1683      * @hide
1684      */
1685     @FlaggedApi(Flags.FLAG_API_RICH_ONGOING)
1686     public static final String EXTRA_PROGRESS_POINTS = "android.progressPoints";
1687 
1688     /**
1689      * {@link #extras} key: whether the progress bar should be styled by its progress as
1690      * supplied to {@link ProgressStyle#setStyledByProgress}.
1691      * This extra is a boolean.
1692      * @hide
1693      */
1694     @FlaggedApi(Flags.FLAG_API_RICH_ONGOING)
1695     public static final String EXTRA_STYLED_BY_PROGRESS = "android.styledByProgress";
1696 
1697     /**
1698      * {@link #extras} key: this is an {@link Icon} of an image to be
1699      * shown as progress bar progress tracker icon in {@link ProgressStyle}, supplied to
1700      *{@link ProgressStyle#setProgressTrackerIcon(Icon)}.
1701      * @hide
1702      */
1703     @FlaggedApi(Flags.FLAG_API_RICH_ONGOING)
1704     public static final String EXTRA_PROGRESS_TRACKER_ICON = "android.progressTrackerIcon";
1705 
1706     /**
1707      * {@link #extras} key: this is an {@link Icon} of an image to be
1708      * shown at the beginning of the progress bar in {@link ProgressStyle}, supplied to
1709      *{@link ProgressStyle#setProgressStartIcon(Icon)}.
1710      * @hide
1711      */
1712     @FlaggedApi(Flags.FLAG_API_RICH_ONGOING)
1713     public static final String EXTRA_PROGRESS_START_ICON = "android.progressStartIcon";
1714 
1715     /**
1716      * {@link #extras} key: this is an {@link Icon} of an image to be
1717      * shown at the end of the progress bar in {@link ProgressStyle}, supplied to
1718      *{@link ProgressStyle#setProgressEndIcon(Icon)}.
1719      * @hide
1720      */
1721     @FlaggedApi(Flags.FLAG_API_RICH_ONGOING)
1722     public static final String EXTRA_PROGRESS_END_ICON = "android.progressEndIcon";
1723 
1724     /**
1725      * @hide
1726      */
1727     public static final String EXTRA_BUILDER_APPLICATION_INFO = "android.appInfo";
1728 
1729     /**
1730      * @hide
1731      */
1732     public static final String EXTRA_CONTAINS_CUSTOM_VIEW = "android.contains.customView";
1733 
1734     /**
1735      * @hide
1736      */
1737     public static final String EXTRA_REDUCED_IMAGES = "android.reduced.images";
1738 
1739     /**
1740      * {@link #extras} key: the audio contents of this notification.
1741      *
1742      * This is for use when rendering the notification on an audio-focused interface;
1743      * the audio contents are a complete sound sample that contains the contents/body of the
1744      * notification. This may be used in substitute of a Text-to-Speech reading of the
1745      * notification. For example if the notification represents a voice message this should point
1746      * to the audio of that message.
1747      *
1748      * The data stored under this key should be a String representation of a Uri that contains the
1749      * audio contents in one of the following formats: WAV, PCM 16-bit, AMR-WB.
1750      *
1751      * This extra is unnecessary if you are using {@code MessagingStyle} since each {@code Message}
1752      * has a field for holding data URI. That field can be used for audio.
1753      * See {@code Message#setData}.
1754      *
1755      * Example usage:
1756      * <pre>
1757      * {@code
1758      * Notification.Builder myBuilder = (build your Notification as normal);
1759      * myBuilder.getExtras().putString(EXTRA_AUDIO_CONTENTS_URI, myAudioUri.toString());
1760      * }
1761      * </pre>
1762      */
1763     public static final String EXTRA_AUDIO_CONTENTS_URI = "android.audioContents";
1764 
1765     /** @hide */
1766     @SystemApi
1767     @RequiresPermission(android.Manifest.permission.SUBSTITUTE_NOTIFICATION_APP_NAME)
1768     public static final String EXTRA_SUBSTITUTE_APP_NAME = "android.substName";
1769 
1770     /**
1771      * This is set on the notifications shown by system_server about apps running foreground
1772      * services. It indicates that the notification should be shown
1773      * only if any of the given apps do not already have a properly tagged
1774      * {@link #FLAG_FOREGROUND_SERVICE} notification currently visible to the user.
1775      * This is a string array of all package names of the apps.
1776      * @hide
1777      */
1778     public static final String EXTRA_FOREGROUND_APPS = "android.foregroundApps";
1779 
1780     /**
1781      * @hide
1782      */
1783     public static final String EXTRA_SUMMARIZED_CONTENT = "android.summarization";
1784 
1785     @UnsupportedAppUsage
1786     private Icon mSmallIcon;
1787     @UnsupportedAppUsage
1788     private Icon mLargeIcon;
1789 
1790     @UnsupportedAppUsage
1791     private String mChannelId;
1792     private long mTimeout;
1793 
1794     private String mShortcutId;
1795     private LocusId mLocusId;
1796     private CharSequence mSettingsText;
1797 
1798     private BubbleMetadata mBubbleMetadata;
1799 
1800     /** @hide */
1801     @IntDef(prefix = { "GROUP_ALERT_" }, value = {
1802             GROUP_ALERT_ALL, GROUP_ALERT_CHILDREN, GROUP_ALERT_SUMMARY
1803     })
1804     @Retention(RetentionPolicy.SOURCE)
1805     public @interface GroupAlertBehavior {}
1806 
1807     /**
1808      * Constant for {@link Builder#setGroupAlertBehavior(int)}, meaning that all notifications in a
1809      * group with sound or vibration ought to make sound or vibrate (respectively), so this
1810      * notification will not be muted when it is in a group.
1811      */
1812     public static final int GROUP_ALERT_ALL = 0;
1813 
1814     /**
1815      * Constant for {@link Builder#setGroupAlertBehavior(int)}, meaning that all children
1816      * notification in a group should be silenced (no sound or vibration) even if they are posted
1817      * to a {@link NotificationChannel} that has sound and/or vibration. Use this constant to
1818      * mute this notification if this notification is a group child. This must be applied to all
1819      * children notifications you want to mute.
1820      *
1821      * <p> For example, you might want to use this constant if you post a number of children
1822      * notifications at once (say, after a periodic sync), and only need to notify the user
1823      * audibly once.
1824      */
1825     public static final int GROUP_ALERT_SUMMARY = 1;
1826 
1827     /**
1828      * Constant for {@link Builder#setGroupAlertBehavior(int)}, meaning that the summary
1829      * notification in a group should be silenced (no sound or vibration) even if they are
1830      * posted to a {@link NotificationChannel} that has sound and/or vibration. Use this constant
1831      * to mute this notification if this notification is a group summary.
1832      *
1833      * <p>For example, you might want to use this constant if only the children notifications
1834      * in your group have content and the summary is only used to visually group notifications
1835      * rather than to alert the user that new information is available.
1836      */
1837     public static final int GROUP_ALERT_CHILDREN = 2;
1838 
1839     /**
1840      * Constant for the {@link Builder#setGroup(String) group key} that is added to notifications
1841      * that are not already grouped when {@link Builder#setSilent()} is used.
1842      *
1843      * @hide
1844      */
1845     @Deprecated
1846     public static final String GROUP_KEY_SILENT = "silent";
1847 
1848     private int mGroupAlertBehavior = GROUP_ALERT_ALL;
1849 
1850     /**
1851      * If this notification is being shown as a badge, always show as a number.
1852      */
1853     public static final int BADGE_ICON_NONE = 0;
1854 
1855     /**
1856      * If this notification is being shown as a badge, use the {@link #getSmallIcon()} to
1857      * represent this notification.
1858      */
1859     public static final int BADGE_ICON_SMALL = 1;
1860 
1861     /**
1862      * If this notification is being shown as a badge, use the {@link #getLargeIcon()} to
1863      * represent this notification.
1864      */
1865     public static final int BADGE_ICON_LARGE = 2;
1866     private int mBadgeIcon = BADGE_ICON_NONE;
1867 
1868     /**
1869      * Determines whether the platform can generate contextual actions for a notification.
1870      */
1871     private boolean mAllowSystemGeneratedContextualActions = true;
1872 
1873     /**
1874      * Structure to encapsulate a named action that can be shown as part of this notification.
1875      * It must include an icon, a label, and a {@link PendingIntent} to be fired when the action is
1876      * selected by the user.
1877      * <p>
1878      * Apps should use {@link Notification.Builder#addAction(int, CharSequence, PendingIntent)}
1879      * or {@link Notification.Builder#addAction(Notification.Action)}
1880      * to attach actions.
1881      * <p>
1882      * As of Android {@link android.os.Build.VERSION_CODES#S}, apps targeting API level {@link
1883      * android.os.Build.VERSION_CODES#S} or higher won't be able to start activities while
1884      * processing broadcast receivers or services in response to notification action clicks. To
1885      * launch an activity in those cases, provide a {@link PendingIntent} for the activity itself.
1886      */
1887     public static class Action implements Parcelable {
1888         /**
1889          * {@link #extras} key: Keys to a {@link Parcelable} {@link ArrayList} of
1890          * {@link RemoteInput}s.
1891          *
1892          * This is intended for {@link RemoteInput}s that only accept data, meaning
1893          * {@link RemoteInput#getAllowFreeFormInput} is false, {@link RemoteInput#getChoices}
1894          * is null or empty, and {@link RemoteInput#getAllowedDataTypes} is non-null and not
1895          * empty. These {@link RemoteInput}s will be ignored by devices that do not
1896          * support non-text-based {@link RemoteInput}s. See {@link Builder#build}.
1897          *
1898          * You can test if a RemoteInput matches these constraints using
1899          * {@link RemoteInput#isDataOnly}.
1900          */
1901         private static final String EXTRA_DATA_ONLY_INPUTS = "android.extra.DATA_ONLY_INPUTS";
1902 
1903         /**
1904          * No semantic action defined.
1905          */
1906         public static final int SEMANTIC_ACTION_NONE = 0;
1907 
1908         /**
1909          * {@code SemanticAction}: Reply to a conversation, chat, group, or wherever replies
1910          * may be appropriate.
1911          */
1912         public static final int SEMANTIC_ACTION_REPLY = 1;
1913 
1914         /**
1915          * {@code SemanticAction}: Mark content as read.
1916          */
1917         public static final int SEMANTIC_ACTION_MARK_AS_READ = 2;
1918 
1919         /**
1920          * {@code SemanticAction}: Mark content as unread.
1921          */
1922         public static final int SEMANTIC_ACTION_MARK_AS_UNREAD = 3;
1923 
1924         /**
1925          * {@code SemanticAction}: Delete the content associated with the notification. This
1926          * could mean deleting an email, message, etc.
1927          */
1928         public static final int SEMANTIC_ACTION_DELETE = 4;
1929 
1930         /**
1931          * {@code SemanticAction}: Archive the content associated with the notification. This
1932          * could mean archiving an email, message, etc.
1933          */
1934         public static final int SEMANTIC_ACTION_ARCHIVE = 5;
1935 
1936         /**
1937          * {@code SemanticAction}: Mute the content associated with the notification. This could
1938          * mean silencing a conversation or currently playing media.
1939          */
1940         public static final int SEMANTIC_ACTION_MUTE = 6;
1941 
1942         /**
1943          * {@code SemanticAction}: Unmute the content associated with the notification. This could
1944          * mean un-silencing a conversation or currently playing media.
1945          */
1946         public static final int SEMANTIC_ACTION_UNMUTE = 7;
1947 
1948         /**
1949          * {@code SemanticAction}: Mark content with a thumbs up.
1950          */
1951         public static final int SEMANTIC_ACTION_THUMBS_UP = 8;
1952 
1953         /**
1954          * {@code SemanticAction}: Mark content with a thumbs down.
1955          */
1956         public static final int SEMANTIC_ACTION_THUMBS_DOWN = 9;
1957 
1958         /**
1959          * {@code SemanticAction}: Call a contact, group, etc.
1960          */
1961         public static final int SEMANTIC_ACTION_CALL = 10;
1962 
1963         /**
1964          * {@code SemanticAction}: Mark the conversation associated with the notification as a
1965          * priority. Note that this is only for use by the notification assistant services. The
1966          * type will be ignored for actions an app adds to its own notifications.
1967          * @hide
1968          */
1969         @SystemApi
1970         public static final int SEMANTIC_ACTION_MARK_CONVERSATION_AS_PRIORITY = 11;
1971 
1972         /**
1973          * {@code SemanticAction}: Mark content as a potential phishing attempt.
1974          * Note that this is only for use by the notification assistant services. The type will
1975          * be ignored for actions an app adds to its own notifications.
1976          * @hide
1977          */
1978         @SystemApi
1979         public static final int SEMANTIC_ACTION_CONVERSATION_IS_PHISHING = 12;
1980 
1981         /**
1982          * {@link #extras} key to a boolean defining if this action requires special visual
1983          * treatment.
1984          * @hide
1985          */
1986         public static final String EXTRA_IS_ANIMATED = "android.extra.IS_ANIMATED";
1987 
1988         private final Bundle mExtras;
1989         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
1990         private Icon mIcon;
1991         private final RemoteInput[] mRemoteInputs;
1992         private boolean mAllowGeneratedReplies = true;
1993         private final @SemanticAction int mSemanticAction;
1994         private final boolean mIsContextual;
1995         private boolean mAuthenticationRequired;
1996 
1997         /**
1998          * Small icon representing the action.
1999          *
2000          * @deprecated Use {@link Action#getIcon()} instead.
2001          */
2002         @Deprecated
2003         public int icon;
2004 
2005         /**
2006          * Title of the action.
2007          */
2008         public CharSequence title;
2009 
2010         /**
2011          * Intent to send when the user invokes this action. May be null, in which case the action
2012          * may be rendered in a disabled presentation by the system UI.
2013          */
2014         public PendingIntent actionIntent;
2015 
Action(Parcel in)2016         private Action(Parcel in) {
2017             if (in.readInt() != 0) {
2018                 mIcon = Icon.CREATOR.createFromParcel(in);
2019                 if (mIcon.getType() == Icon.TYPE_RESOURCE) {
2020                     icon = mIcon.getResId();
2021                 }
2022             }
2023             title = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
2024             if (in.readInt() == 1) {
2025                 actionIntent = PendingIntent.CREATOR.createFromParcel(in);
2026             }
2027             mExtras = Bundle.setDefusable(in.readBundle(), true);
2028             mRemoteInputs = in.createTypedArray(RemoteInput.CREATOR);
2029             mAllowGeneratedReplies = in.readInt() == 1;
2030             mSemanticAction = in.readInt();
2031             mIsContextual = in.readInt() == 1;
2032             mAuthenticationRequired = in.readInt() == 1;
2033         }
2034 
2035         /**
2036          * @deprecated Use {@link android.app.Notification.Action.Builder}.
2037          */
2038         @Deprecated
Action(int icon, CharSequence title, @Nullable PendingIntent intent)2039         public Action(int icon, CharSequence title, @Nullable PendingIntent intent) {
2040             this(Icon.createWithResource("", icon), title, intent, new Bundle(), null, true,
2041                     SEMANTIC_ACTION_NONE, false /* isContextual */, false /* requireAuth */);
2042         }
2043 
2044         /** 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)2045         private Action(Icon icon, CharSequence title, PendingIntent intent, Bundle extras,
2046                 RemoteInput[] remoteInputs, boolean allowGeneratedReplies,
2047                 @SemanticAction int semanticAction, boolean isContextual,
2048                 boolean requireAuth) {
2049             this.mIcon = icon;
2050             if (icon != null && icon.getType() == Icon.TYPE_RESOURCE) {
2051                 this.icon = icon.getResId();
2052             }
2053             this.title = title;
2054             this.actionIntent = intent;
2055             this.mExtras = extras != null ? extras : new Bundle();
2056             this.mRemoteInputs = remoteInputs;
2057             this.mAllowGeneratedReplies = allowGeneratedReplies;
2058             this.mSemanticAction = semanticAction;
2059             this.mIsContextual = isContextual;
2060             this.mAuthenticationRequired = requireAuth;
2061         }
2062 
2063         /**
2064          * Return an icon representing the action.
2065          */
getIcon()2066         public Icon getIcon() {
2067             if (mIcon == null && icon != 0) {
2068                 // you snuck an icon in here without using the builder; let's try to keep it
2069                 mIcon = Icon.createWithResource("", icon);
2070             }
2071             return mIcon;
2072         }
2073 
2074         /**
2075          * Get additional metadata carried around with this Action.
2076          */
getExtras()2077         public Bundle getExtras() {
2078             return mExtras;
2079         }
2080 
2081         /**
2082          * Return whether the platform should automatically generate possible replies for this
2083          * {@link Action}
2084          */
getAllowGeneratedReplies()2085         public boolean getAllowGeneratedReplies() {
2086             return mAllowGeneratedReplies;
2087         }
2088 
2089         /**
2090          * Get the list of inputs to be collected from the user when this action is sent.
2091          * May return null if no remote inputs were added. Only returns inputs which accept
2092          * a text input. For inputs which only accept data use {@link #getDataOnlyRemoteInputs}.
2093          */
getRemoteInputs()2094         public RemoteInput[] getRemoteInputs() {
2095             return mRemoteInputs;
2096         }
2097 
2098         /**
2099          * Returns the {@code SemanticAction} associated with this {@link Action}. A
2100          * {@code SemanticAction} denotes what an {@link Action}'s {@link PendingIntent} will do
2101          * (eg. reply, mark as read, delete, etc).
2102          */
getSemanticAction()2103         public @SemanticAction int getSemanticAction() {
2104             return mSemanticAction;
2105         }
2106 
2107         /**
2108          * Returns whether this is a contextual Action, i.e. whether the action is dependent on the
2109          * notification message body. An example of a contextual action could be an action opening a
2110          * map application with an address shown in the notification.
2111          */
isContextual()2112         public boolean isContextual() {
2113             return mIsContextual;
2114         }
2115 
2116         /**
2117          * Get the list of inputs to be collected from the user that ONLY accept data when this
2118          * action is sent. These remote inputs are guaranteed to return true on a call to
2119          * {@link RemoteInput#isDataOnly}.
2120          *
2121          * Returns null if there are no data-only remote inputs.
2122          *
2123          * This method exists so that legacy RemoteInput collectors that pre-date the addition
2124          * of non-textual RemoteInputs do not access these remote inputs.
2125          */
getDataOnlyRemoteInputs()2126         public RemoteInput[] getDataOnlyRemoteInputs() {
2127             return getParcelableArrayFromBundle(mExtras, EXTRA_DATA_ONLY_INPUTS, RemoteInput.class);
2128         }
2129 
2130         /**
2131          * Returns whether the OS should only send this action's {@link PendingIntent} on an
2132          * unlocked device.
2133          *
2134          * If the device is locked when the action is invoked, the OS should show the keyguard and
2135          * require successful authentication before invoking the intent.
2136          */
isAuthenticationRequired()2137         public boolean isAuthenticationRequired() {
2138             return mAuthenticationRequired;
2139         }
2140 
2141         /**
2142          * Builder class for {@link Action} objects.
2143          */
2144         public static final class Builder {
2145             @Nullable private final Icon mIcon;
2146             @Nullable private final CharSequence mTitle;
2147             @Nullable private final PendingIntent mIntent;
2148             private boolean mAllowGeneratedReplies = true;
2149             @NonNull private final Bundle mExtras;
2150             @Nullable private ArrayList<RemoteInput> mRemoteInputs;
2151             private @SemanticAction int mSemanticAction;
2152             private boolean mIsContextual;
2153             private boolean mAuthenticationRequired;
2154 
2155             /**
2156              * Construct a new builder for {@link Action} object.
2157              * <p>As of Android {@link android.os.Build.VERSION_CODES#N},
2158              * action button icons will not be displayed on action buttons, but are still required
2159              * and are available to
2160              * {@link android.service.notification.NotificationListenerService notification listeners},
2161              * which may display them in other contexts, for example on a wearable device.
2162              * @param icon icon to show for this action
2163              * @param title the title of the action
2164              * @param intent the {@link PendingIntent} to fire when users trigger this action. May
2165              * be null, in which case the action may be rendered in a disabled presentation by the
2166              * system UI.
2167              */
2168             @Deprecated
Builder(int icon, CharSequence title, @Nullable PendingIntent intent)2169             public Builder(int icon, CharSequence title, @Nullable PendingIntent intent) {
2170                 this(Icon.createWithResource("", icon), title, intent);
2171             }
2172 
2173             /**
2174              * Construct a new builder for {@link Action} object.
2175              *
2176              * <p>As of Android {@link android.os.Build.VERSION_CODES#S}, apps targeting API level
2177              * {@link android.os.Build.VERSION_CODES#S} or higher won't be able to start activities
2178              * while processing broadcast receivers or services in response to notification action
2179              * clicks. To launch an activity in those cases, provide a {@link PendingIntent} for the
2180              * activity itself.
2181              *
2182              * <p>How an Action is displayed, including whether the {@code icon}, {@code text}, or
2183              * both are displayed or required, depends on where and how the action is used, and the
2184              * {@link Style} applied to the Notification.
2185              *
2186              * <p>As of Android {@link android.os.Build.VERSION_CODES#N}, action button icons
2187              * will not be displayed on action buttons, but are still required and are available
2188              * to {@link android.service.notification.NotificationListenerService notification
2189              * listeners}, which may display them in other contexts, for example on a wearable
2190              * device.
2191              *
2192              * <p>When the {@code title} is a {@link android.text.Spanned}, any colors set by a
2193              * {@link ForegroundColorSpan} or {@link TextAppearanceSpan} may be removed or displayed
2194              * with an altered in luminance to ensure proper contrast within the Notification.
2195              *
2196              * @param icon icon to show for this action
2197              * @param title the title of the action
2198              * @param intent the {@link PendingIntent} to fire when users trigger this action. May
2199              * be null, in which case the action may be rendered in a disabled presentation by the
2200              * system UI.
2201              */
Builder(Icon icon, CharSequence title, @Nullable PendingIntent intent)2202             public Builder(Icon icon, CharSequence title, @Nullable PendingIntent intent) {
2203                 this(icon, title, intent, new Bundle(), null, true, SEMANTIC_ACTION_NONE, false);
2204             }
2205 
2206             /**
2207              * Construct a new builder for {@link Action} object using the fields from an
2208              * {@link Action}.
2209              * @param action the action to read fields from.
2210              */
Builder(Action action)2211             public Builder(Action action) {
2212                 this(action.getIcon(), action.title, action.actionIntent,
2213                         new Bundle(action.mExtras), action.getRemoteInputs(),
2214                         action.getAllowGeneratedReplies(), action.getSemanticAction(),
2215                         action.isAuthenticationRequired());
2216             }
2217 
Builder(@ullable Icon icon, @Nullable CharSequence title, @Nullable PendingIntent intent, @NonNull Bundle extras, @Nullable RemoteInput[] remoteInputs, boolean allowGeneratedReplies, @SemanticAction int semanticAction, boolean authRequired)2218             private Builder(@Nullable Icon icon, @Nullable CharSequence title,
2219                     @Nullable PendingIntent intent, @NonNull Bundle extras,
2220                     @Nullable RemoteInput[] remoteInputs, boolean allowGeneratedReplies,
2221                     @SemanticAction int semanticAction, boolean authRequired) {
2222                 mIcon = icon;
2223                 mTitle = title;
2224                 mIntent = intent;
2225                 mExtras = extras;
2226                 if (remoteInputs != null) {
2227                     mRemoteInputs = new ArrayList<>(remoteInputs.length);
2228                     Collections.addAll(mRemoteInputs, remoteInputs);
2229                 }
2230                 mAllowGeneratedReplies = allowGeneratedReplies;
2231                 mSemanticAction = semanticAction;
2232                 mAuthenticationRequired = authRequired;
2233             }
2234 
2235             /**
2236              * Merge additional metadata into this builder.
2237              *
2238              * <p>Values within the Bundle will replace existing extras values in this Builder.
2239              *
2240              * @see Notification.Action#extras
2241              */
2242             @NonNull
addExtras(Bundle extras)2243             public Builder addExtras(Bundle extras) {
2244                 if (extras != null) {
2245                     mExtras.putAll(extras);
2246                 }
2247                 return this;
2248             }
2249 
2250             /**
2251              * Get the metadata Bundle used by this Builder.
2252              *
2253              * <p>The returned Bundle is shared with this Builder.
2254              */
2255             @NonNull
getExtras()2256             public Bundle getExtras() {
2257                 return mExtras;
2258             }
2259 
2260             /**
2261              * Add an input to be collected from the user when this action is sent.
2262              * Response values can be retrieved from the fired intent by using the
2263              * {@link RemoteInput#getResultsFromIntent} function.
2264              * @param remoteInput a {@link RemoteInput} to add to the action
2265              * @return this object for method chaining
2266              */
2267             @NonNull
addRemoteInput(RemoteInput remoteInput)2268             public Builder addRemoteInput(RemoteInput remoteInput) {
2269                 if (mRemoteInputs == null) {
2270                     mRemoteInputs = new ArrayList<RemoteInput>();
2271                 }
2272                 mRemoteInputs.add(remoteInput);
2273                 return this;
2274             }
2275 
2276             /**
2277              * Set whether the platform should automatically generate possible replies to add to
2278              * {@link RemoteInput#getChoices()}. If the {@link Action} doesn't have a
2279              * {@link RemoteInput}, this has no effect.
2280              * @param allowGeneratedReplies {@code true} to allow generated replies, {@code false}
2281              * otherwise
2282              * @return this object for method chaining
2283              * The default value is {@code true}
2284              */
2285             @NonNull
setAllowGeneratedReplies(boolean allowGeneratedReplies)2286             public Builder setAllowGeneratedReplies(boolean allowGeneratedReplies) {
2287                 mAllowGeneratedReplies = allowGeneratedReplies;
2288                 return this;
2289             }
2290 
2291             /**
2292              * Sets the {@code SemanticAction} for this {@link Action}. A
2293              * {@code SemanticAction} denotes what an {@link Action}'s
2294              * {@link PendingIntent} will do (eg. reply, mark as read, delete, etc).
2295              * @param semanticAction a SemanticAction defined within {@link Action} with
2296              * {@code SEMANTIC_ACTION_} prefixes
2297              * @return this object for method chaining
2298              */
2299             @NonNull
setSemanticAction(@emanticAction int semanticAction)2300             public Builder setSemanticAction(@SemanticAction int semanticAction) {
2301                 mSemanticAction = semanticAction;
2302                 return this;
2303             }
2304 
2305             /**
2306              * Sets whether this {@link Action} is a contextual action, i.e. whether the action is
2307              * dependent on the notification message body. An example of a contextual action could
2308              * be an action opening a map application with an address shown in the notification.
2309              */
2310             @NonNull
setContextual(boolean isContextual)2311             public Builder setContextual(boolean isContextual) {
2312                 mIsContextual = isContextual;
2313                 return this;
2314             }
2315 
2316             /**
2317              * Apply an extender to this action builder. Extenders may be used to add
2318              * metadata or change options on this builder.
2319              */
2320             @NonNull
extend(Extender extender)2321             public Builder extend(Extender extender) {
2322                 extender.extend(this);
2323                 return this;
2324             }
2325 
2326             /**
2327              * Sets whether the OS should only send this action's {@link PendingIntent} on an
2328              * unlocked device.
2329              *
2330              * If this is true and the device is locked when the action is invoked, the OS will
2331              * show the keyguard and require successful authentication before invoking the intent.
2332              * If this is false and the device is locked, the OS will decide whether authentication
2333              * should be required.
2334              */
2335             @NonNull
setAuthenticationRequired(boolean authenticationRequired)2336             public Builder setAuthenticationRequired(boolean authenticationRequired) {
2337                 mAuthenticationRequired = authenticationRequired;
2338                 return this;
2339             }
2340 
2341             /**
2342              * Throws an NPE if we are building a contextual action missing one of the fields
2343              * necessary to display the action.
2344              */
checkContextualActionNullFields()2345             private void checkContextualActionNullFields() {
2346                 if (!mIsContextual) return;
2347 
2348                 if (mIcon == null) {
2349                     throw new NullPointerException("Contextual Actions must contain a valid icon");
2350                 }
2351 
2352                 if (mIntent == null) {
2353                     throw new NullPointerException(
2354                             "Contextual Actions must contain a valid PendingIntent");
2355                 }
2356             }
2357 
2358             /**
2359              * Combine all of the options that have been set and return a new {@link Action}
2360              * object.
2361              * @return the built action
2362              */
2363             @NonNull
build()2364             public Action build() {
2365                 checkContextualActionNullFields();
2366 
2367                 ArrayList<RemoteInput> dataOnlyInputs = new ArrayList<>();
2368                 RemoteInput[] previousDataInputs = getParcelableArrayFromBundle(
2369                         mExtras, EXTRA_DATA_ONLY_INPUTS, RemoteInput.class);
2370                 if (previousDataInputs != null) {
2371                     for (RemoteInput input : previousDataInputs) {
2372                         dataOnlyInputs.add(input);
2373                     }
2374                 }
2375                 List<RemoteInput> textInputs = new ArrayList<>();
2376                 if (mRemoteInputs != null) {
2377                     for (RemoteInput input : mRemoteInputs) {
2378                         if (input.isDataOnly()) {
2379                             dataOnlyInputs.add(input);
2380                         } else {
2381                             textInputs.add(input);
2382                         }
2383                     }
2384                 }
2385                 if (!dataOnlyInputs.isEmpty()) {
2386                     RemoteInput[] dataInputsArr =
2387                             dataOnlyInputs.toArray(new RemoteInput[dataOnlyInputs.size()]);
2388                     mExtras.putParcelableArray(EXTRA_DATA_ONLY_INPUTS, dataInputsArr);
2389                 }
2390                 RemoteInput[] textInputsArr = textInputs.isEmpty()
2391                         ? null : textInputs.toArray(new RemoteInput[textInputs.size()]);
2392                 return new Action(mIcon, mTitle, mIntent, mExtras, textInputsArr,
2393                         mAllowGeneratedReplies, mSemanticAction, mIsContextual,
2394                         mAuthenticationRequired);
2395             }
2396         }
2397 
visitUris(@onNull Consumer<Uri> visitor)2398         private void visitUris(@NonNull Consumer<Uri> visitor) {
2399             visitIconUri(visitor, getIcon());
2400         }
2401 
2402         @Override
clone()2403         public Action clone() {
2404             return new Action(
2405                     getIcon(),
2406                     title,
2407                     actionIntent, // safe to alias
2408                     mExtras == null ? new Bundle() : new Bundle(mExtras),
2409                     getRemoteInputs(),
2410                     getAllowGeneratedReplies(),
2411                     getSemanticAction(),
2412                     isContextual(),
2413                     isAuthenticationRequired());
2414         }
2415 
2416         @Override
describeContents()2417         public int describeContents() {
2418             return 0;
2419         }
2420 
2421         @Override
writeToParcel(Parcel out, int flags)2422         public void writeToParcel(Parcel out, int flags) {
2423             final Icon ic = getIcon();
2424             if (ic != null) {
2425                 out.writeInt(1);
2426                 ic.writeToParcel(out, 0);
2427             } else {
2428                 out.writeInt(0);
2429             }
2430             TextUtils.writeToParcel(title, out, flags);
2431             if (actionIntent != null) {
2432                 out.writeInt(1);
2433                 actionIntent.writeToParcel(out, flags);
2434             } else {
2435                 out.writeInt(0);
2436             }
2437             out.writeBundle(mExtras);
2438             out.writeTypedArray(mRemoteInputs, flags);
2439             out.writeInt(mAllowGeneratedReplies ? 1 : 0);
2440             out.writeInt(mSemanticAction);
2441             out.writeInt(mIsContextual ? 1 : 0);
2442             out.writeInt(mAuthenticationRequired ? 1 : 0);
2443         }
2444 
2445         public static final @android.annotation.NonNull Parcelable.Creator<Action> CREATOR =
2446                 new Parcelable.Creator<Action>() {
2447             public Action createFromParcel(Parcel in) {
2448                 return new Action(in);
2449             }
2450             public Action[] newArray(int size) {
2451                 return new Action[size];
2452             }
2453         };
2454 
2455         /**
2456          * Extender interface for use with {@link Builder#extend}. Extenders may be used to add
2457          * metadata or change options on an action builder.
2458          */
2459         public interface Extender {
2460             /**
2461              * Apply this extender to a notification action builder.
2462              * @param builder the builder to be modified.
2463              * @return the build object for chaining.
2464              */
extend(Builder builder)2465             public Builder extend(Builder builder);
2466         }
2467 
2468         /**
2469          * Wearable extender for notification actions. To add extensions to an action,
2470          * create a new {@link android.app.Notification.Action.WearableExtender} object using
2471          * the {@code WearableExtender()} constructor and apply it to a
2472          * {@link android.app.Notification.Action.Builder} using
2473          * {@link android.app.Notification.Action.Builder#extend}.
2474          *
2475          * <pre class="prettyprint">
2476          * Notification.Action action = new Notification.Action.Builder(
2477          *         R.drawable.archive_all, "Archive all", actionIntent)
2478          *         .extend(new Notification.Action.WearableExtender()
2479          *                 .setAvailableOffline(false))
2480          *         .build();</pre>
2481          */
2482         public static final class WearableExtender implements Extender {
2483             /** Notification action extra which contains wearable extensions */
2484             private static final String EXTRA_WEARABLE_EXTENSIONS = "android.wearable.EXTENSIONS";
2485 
2486             // Keys within EXTRA_WEARABLE_EXTENSIONS for wearable options.
2487             private static final String KEY_FLAGS = "flags";
2488             private static final String KEY_IN_PROGRESS_LABEL = "inProgressLabel";
2489             private static final String KEY_CONFIRM_LABEL = "confirmLabel";
2490             private static final String KEY_CANCEL_LABEL = "cancelLabel";
2491 
2492             // Flags bitwise-ored to mFlags
2493             private static final int FLAG_AVAILABLE_OFFLINE = 0x1;
2494             private static final int FLAG_HINT_LAUNCHES_ACTIVITY = 1 << 1;
2495             private static final int FLAG_HINT_DISPLAY_INLINE = 1 << 2;
2496 
2497             // Default value for flags integer
2498             private static final int DEFAULT_FLAGS = FLAG_AVAILABLE_OFFLINE;
2499 
2500             private int mFlags = DEFAULT_FLAGS;
2501 
2502             private CharSequence mInProgressLabel;
2503             private CharSequence mConfirmLabel;
2504             private CharSequence mCancelLabel;
2505 
2506             /**
2507              * Create a {@link android.app.Notification.Action.WearableExtender} with default
2508              * options.
2509              */
WearableExtender()2510             public WearableExtender() {
2511             }
2512 
2513             /**
2514              * Create a {@link android.app.Notification.Action.WearableExtender} by reading
2515              * wearable options present in an existing notification action.
2516              * @param action the notification action to inspect.
2517              */
WearableExtender(Action action)2518             public WearableExtender(Action action) {
2519                 Bundle wearableBundle = action.getExtras().getBundle(EXTRA_WEARABLE_EXTENSIONS);
2520                 if (wearableBundle != null) {
2521                     mFlags = wearableBundle.getInt(KEY_FLAGS, DEFAULT_FLAGS);
2522                     mInProgressLabel = wearableBundle.getCharSequence(KEY_IN_PROGRESS_LABEL);
2523                     mConfirmLabel = wearableBundle.getCharSequence(KEY_CONFIRM_LABEL);
2524                     mCancelLabel = wearableBundle.getCharSequence(KEY_CANCEL_LABEL);
2525                 }
2526             }
2527 
2528             /**
2529              * Apply wearable extensions to a notification action that is being built. This is
2530              * typically called by the {@link android.app.Notification.Action.Builder#extend}
2531              * method of {@link android.app.Notification.Action.Builder}.
2532              */
2533             @Override
extend(Action.Builder builder)2534             public Action.Builder extend(Action.Builder builder) {
2535                 Bundle wearableBundle = new Bundle();
2536 
2537                 if (mFlags != DEFAULT_FLAGS) {
2538                     wearableBundle.putInt(KEY_FLAGS, mFlags);
2539                 }
2540                 if (mInProgressLabel != null) {
2541                     wearableBundle.putCharSequence(KEY_IN_PROGRESS_LABEL, mInProgressLabel);
2542                 }
2543                 if (mConfirmLabel != null) {
2544                     wearableBundle.putCharSequence(KEY_CONFIRM_LABEL, mConfirmLabel);
2545                 }
2546                 if (mCancelLabel != null) {
2547                     wearableBundle.putCharSequence(KEY_CANCEL_LABEL, mCancelLabel);
2548                 }
2549 
2550                 builder.getExtras().putBundle(EXTRA_WEARABLE_EXTENSIONS, wearableBundle);
2551                 return builder;
2552             }
2553 
2554             @Override
clone()2555             public WearableExtender clone() {
2556                 WearableExtender that = new WearableExtender();
2557                 that.mFlags = this.mFlags;
2558                 that.mInProgressLabel = this.mInProgressLabel;
2559                 that.mConfirmLabel = this.mConfirmLabel;
2560                 that.mCancelLabel = this.mCancelLabel;
2561                 return that;
2562             }
2563 
2564             /**
2565              * Set whether this action is available when the wearable device is not connected to
2566              * a companion device. The user can still trigger this action when the wearable device is
2567              * offline, but a visual hint will indicate that the action may not be available.
2568              * Defaults to true.
2569              */
setAvailableOffline(boolean availableOffline)2570             public WearableExtender setAvailableOffline(boolean availableOffline) {
2571                 setFlag(FLAG_AVAILABLE_OFFLINE, availableOffline);
2572                 return this;
2573             }
2574 
2575             /**
2576              * Get whether this action is available when the wearable device is not connected to
2577              * a companion device. The user can still trigger this action when the wearable device is
2578              * offline, but a visual hint will indicate that the action may not be available.
2579              * Defaults to true.
2580              */
isAvailableOffline()2581             public boolean isAvailableOffline() {
2582                 return (mFlags & FLAG_AVAILABLE_OFFLINE) != 0;
2583             }
2584 
setFlag(int mask, boolean value)2585             private void setFlag(int mask, boolean value) {
2586                 if (value) {
2587                     mFlags |= mask;
2588                 } else {
2589                     mFlags &= ~mask;
2590                 }
2591             }
2592 
2593             /**
2594              * Set a label to display while the wearable is preparing to automatically execute the
2595              * action. This is usually a 'ing' verb ending in ellipsis like "Sending..."
2596              *
2597              * @param label the label to display while the action is being prepared to execute
2598              * @return this object for method chaining
2599              */
2600             @Deprecated
setInProgressLabel(CharSequence label)2601             public WearableExtender setInProgressLabel(CharSequence label) {
2602                 mInProgressLabel = label;
2603                 return this;
2604             }
2605 
2606             /**
2607              * Get the label to display while the wearable is preparing to automatically execute
2608              * the action. This is usually a 'ing' verb ending in ellipsis like "Sending..."
2609              *
2610              * @return the label to display while the action is being prepared to execute
2611              */
2612             @Deprecated
getInProgressLabel()2613             public CharSequence getInProgressLabel() {
2614                 return mInProgressLabel;
2615             }
2616 
2617             /**
2618              * Set a label to display to confirm that the action should be executed.
2619              * This is usually an imperative verb like "Send".
2620              *
2621              * @param label the label to confirm the action should be executed
2622              * @return this object for method chaining
2623              */
2624             @Deprecated
setConfirmLabel(CharSequence label)2625             public WearableExtender setConfirmLabel(CharSequence label) {
2626                 mConfirmLabel = label;
2627                 return this;
2628             }
2629 
2630             /**
2631              * Get the label to display to confirm that the action should be executed.
2632              * This is usually an imperative verb like "Send".
2633              *
2634              * @return the label to confirm the action should be executed
2635              */
2636             @Deprecated
getConfirmLabel()2637             public CharSequence getConfirmLabel() {
2638                 return mConfirmLabel;
2639             }
2640 
2641             /**
2642              * Set a label to display to cancel the action.
2643              * This is usually an imperative verb, like "Cancel".
2644              *
2645              * @param label the label to display to cancel the action
2646              * @return this object for method chaining
2647              */
2648             @Deprecated
setCancelLabel(CharSequence label)2649             public WearableExtender setCancelLabel(CharSequence label) {
2650                 mCancelLabel = label;
2651                 return this;
2652             }
2653 
2654             /**
2655              * Get the label to display to cancel the action.
2656              * This is usually an imperative verb like "Cancel".
2657              *
2658              * @return the label to display to cancel the action
2659              */
2660             @Deprecated
getCancelLabel()2661             public CharSequence getCancelLabel() {
2662                 return mCancelLabel;
2663             }
2664 
2665             /**
2666              * Set a hint that this Action will launch an {@link Activity} directly, telling the
2667              * platform that it can generate the appropriate transitions.
2668              * @param hintLaunchesActivity {@code true} if the content intent will launch
2669              * an activity and transitions should be generated, false otherwise.
2670              * @return this object for method chaining
2671              */
setHintLaunchesActivity( boolean hintLaunchesActivity)2672             public WearableExtender setHintLaunchesActivity(
2673                     boolean hintLaunchesActivity) {
2674                 setFlag(FLAG_HINT_LAUNCHES_ACTIVITY, hintLaunchesActivity);
2675                 return this;
2676             }
2677 
2678             /**
2679              * Get a hint that this Action will launch an {@link Activity} directly, telling the
2680              * platform that it can generate the appropriate transitions
2681              * @return {@code true} if the content intent will launch an activity and transitions
2682              * should be generated, false otherwise. The default value is {@code false} if this was
2683              * never set.
2684              */
getHintLaunchesActivity()2685             public boolean getHintLaunchesActivity() {
2686                 return (mFlags & FLAG_HINT_LAUNCHES_ACTIVITY) != 0;
2687             }
2688 
2689             /**
2690              * Set a hint that this Action should be displayed inline.
2691              *
2692              * @param hintDisplayInline {@code true} if action should be displayed inline, false
2693              *        otherwise
2694              * @return this object for method chaining
2695              */
setHintDisplayActionInline( boolean hintDisplayInline)2696             public WearableExtender setHintDisplayActionInline(
2697                     boolean hintDisplayInline) {
2698                 setFlag(FLAG_HINT_DISPLAY_INLINE, hintDisplayInline);
2699                 return this;
2700             }
2701 
2702             /**
2703              * Get a hint that this Action should be displayed inline.
2704              *
2705              * @return {@code true} if the Action should be displayed inline, {@code false}
2706              *         otherwise. The default value is {@code false} if this was never set.
2707              */
getHintDisplayActionInline()2708             public boolean getHintDisplayActionInline() {
2709                 return (mFlags & FLAG_HINT_DISPLAY_INLINE) != 0;
2710             }
2711         }
2712 
2713         /**
2714          * Provides meaning to an {@link Action} that hints at what the associated
2715          * {@link PendingIntent} will do. For example, an {@link Action} with a
2716          * {@link PendingIntent} that replies to a text message notification may have the
2717          * {@link #SEMANTIC_ACTION_REPLY} {@code SemanticAction} set within it.
2718          *
2719          * @hide
2720          */
2721         @IntDef(prefix = { "SEMANTIC_ACTION_" }, value = {
2722                 SEMANTIC_ACTION_NONE,
2723                 SEMANTIC_ACTION_REPLY,
2724                 SEMANTIC_ACTION_MARK_AS_READ,
2725                 SEMANTIC_ACTION_MARK_AS_UNREAD,
2726                 SEMANTIC_ACTION_DELETE,
2727                 SEMANTIC_ACTION_ARCHIVE,
2728                 SEMANTIC_ACTION_MUTE,
2729                 SEMANTIC_ACTION_UNMUTE,
2730                 SEMANTIC_ACTION_THUMBS_UP,
2731                 SEMANTIC_ACTION_THUMBS_DOWN,
2732                 SEMANTIC_ACTION_CALL,
2733                 SEMANTIC_ACTION_MARK_CONVERSATION_AS_PRIORITY,
2734                 SEMANTIC_ACTION_CONVERSATION_IS_PHISHING
2735         })
2736         @Retention(RetentionPolicy.SOURCE)
2737         public @interface SemanticAction {}
2738     }
2739 
2740     /**
2741      * Array of all {@link Action} structures attached to this notification by
2742      * {@link Builder#addAction(int, CharSequence, PendingIntent)}. Mostly useful for instances of
2743      * {@link android.service.notification.NotificationListenerService} that provide an alternative
2744      * interface for invoking actions.
2745      */
2746     public Action[] actions;
2747 
2748     /**
2749      * Replacement version of this notification whose content will be shown
2750      * in an insecure context such as atop a secure keyguard. See {@link #visibility}
2751      * and {@link #VISIBILITY_PUBLIC}.
2752      */
2753     public Notification publicVersion;
2754 
2755     /**
2756      * Constructs a Notification object with default values.
2757      * You might want to consider using {@link Builder} instead.
2758      */
Notification()2759     public Notification()
2760     {
2761         this.when = System.currentTimeMillis();
2762         if (Flags.sortSectionByTime()) {
2763             creationTime = when;
2764             extras.putBoolean(EXTRA_SHOW_WHEN, true);
2765         } else {
2766             this.creationTime = System.currentTimeMillis();
2767         }
2768         this.priority = PRIORITY_DEFAULT;
2769     }
2770 
2771     /**
2772      * @hide
2773      */
2774     @UnsupportedAppUsage
Notification(Context context, int icon, CharSequence tickerText, long when, CharSequence contentTitle, CharSequence contentText, Intent contentIntent)2775     public Notification(Context context, int icon, CharSequence tickerText, long when,
2776             CharSequence contentTitle, CharSequence contentText, Intent contentIntent)
2777     {
2778         if (Flags.sortSectionByTime()) {
2779             creationTime = when;
2780             extras.putBoolean(EXTRA_SHOW_WHEN, true);
2781         }
2782         new Builder(context)
2783                 .setWhen(when)
2784                 .setSmallIcon(icon)
2785                 .setTicker(tickerText)
2786                 .setContentTitle(contentTitle)
2787                 .setContentText(contentText)
2788                 .setContentIntent(PendingIntent.getActivity(
2789                         context, 0, contentIntent, PendingIntent.FLAG_MUTABLE))
2790                 .buildInto(this);
2791     }
2792 
2793     /**
2794      * Constructs a Notification object with the information needed to
2795      * have a status bar icon without the standard expanded view.
2796      *
2797      * @param icon          The resource id of the icon to put in the status bar.
2798      * @param tickerText    The text that flows by in the status bar when the notification first
2799      *                      activates.
2800      * @param when          The time to show in the time field.  In the System.currentTimeMillis
2801      *                      timebase.
2802      *
2803      * @deprecated Use {@link Builder} instead.
2804      */
2805     @Deprecated
Notification(int icon, CharSequence tickerText, long when)2806     public Notification(int icon, CharSequence tickerText, long when)
2807     {
2808         this.icon = icon;
2809         this.tickerText = tickerText;
2810         this.when = when;
2811         if (Flags.sortSectionByTime()) {
2812             creationTime = when;
2813             extras.putBoolean(EXTRA_SHOW_WHEN, true);
2814         } else {
2815             this.creationTime = System.currentTimeMillis();
2816         }
2817     }
2818 
2819     /**
2820      * Unflatten the notification from a parcel.
2821      */
2822     @SuppressWarnings("unchecked")
Notification(Parcel parcel)2823     public Notification(Parcel parcel) {
2824         // IMPORTANT: Add unmarshaling code in readFromParcel as the pending
2825         // intents in extras are always written as the last entry.
2826         readFromParcelImpl(parcel);
2827         // Must be read last!
2828         allPendingIntents = (ArraySet<PendingIntent>) parcel.readArraySet(null);
2829     }
2830 
readFromParcelImpl(Parcel parcel)2831     private void readFromParcelImpl(Parcel parcel)
2832     {
2833         int version = parcel.readInt();
2834 
2835         mAllowlistToken = parcel.readStrongBinder();
2836         if (mAllowlistToken == null) {
2837             mAllowlistToken = processAllowlistToken;
2838         }
2839         // Propagate this token to all pending intents that are unmarshalled from the parcel,
2840         // or keep the one we're already propagating, if that's the case.
2841         if (!parcel.hasClassCookie(PendingIntent.class)) {
2842             parcel.setClassCookie(PendingIntent.class, mAllowlistToken);
2843         }
2844 
2845         when = parcel.readLong();
2846         creationTime = parcel.readLong();
2847         if (parcel.readInt() != 0) {
2848             mSmallIcon = Icon.CREATOR.createFromParcel(parcel);
2849             if (mSmallIcon.getType() == Icon.TYPE_RESOURCE) {
2850                 icon = mSmallIcon.getResId();
2851             }
2852         }
2853         number = parcel.readInt();
2854         if (parcel.readInt() != 0) {
2855             contentIntent = PendingIntent.CREATOR.createFromParcel(parcel);
2856         }
2857         if (parcel.readInt() != 0) {
2858             deleteIntent = PendingIntent.CREATOR.createFromParcel(parcel);
2859         }
2860         if (parcel.readInt() != 0) {
2861             tickerText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
2862         }
2863         if (parcel.readInt() != 0) {
2864             tickerView = RemoteViews.CREATOR.createFromParcel(parcel);
2865         }
2866         if (parcel.readInt() != 0) {
2867             contentView = RemoteViews.CREATOR.createFromParcel(parcel);
2868         }
2869         if (parcel.readInt() != 0) {
2870             mLargeIcon = Icon.CREATOR.createFromParcel(parcel);
2871         }
2872         defaults = parcel.readInt();
2873         flags = parcel.readInt();
2874         if (parcel.readInt() != 0) {
2875             sound = Uri.CREATOR.createFromParcel(parcel);
2876         }
2877 
2878         audioStreamType = parcel.readInt();
2879         if (parcel.readInt() != 0) {
2880             audioAttributes = AudioAttributes.CREATOR.createFromParcel(parcel);
2881         }
2882         vibrate = parcel.createLongArray();
2883         ledARGB = parcel.readInt();
2884         ledOnMS = parcel.readInt();
2885         ledOffMS = parcel.readInt();
2886         iconLevel = parcel.readInt();
2887 
2888         if (parcel.readInt() != 0) {
2889             fullScreenIntent = PendingIntent.CREATOR.createFromParcel(parcel);
2890         }
2891 
2892         priority = parcel.readInt();
2893 
2894         category = parcel.readString8();
2895 
2896         mGroupKey = parcel.readString8();
2897 
2898         mSortKey = parcel.readString8();
2899 
2900         extras = Bundle.setDefusable(parcel.readBundle(), true); // may be null
2901         fixDuplicateExtras();
2902 
2903         actions = parcel.createTypedArray(Action.CREATOR); // may be null
2904 
2905         if (parcel.readInt() != 0) {
2906             bigContentView = RemoteViews.CREATOR.createFromParcel(parcel);
2907         }
2908 
2909         if (parcel.readInt() != 0) {
2910             headsUpContentView = RemoteViews.CREATOR.createFromParcel(parcel);
2911         }
2912 
2913         visibility = parcel.readInt();
2914 
2915         if (parcel.readInt() != 0) {
2916             publicVersion = Notification.CREATOR.createFromParcel(parcel);
2917         }
2918 
2919         color = parcel.readInt();
2920 
2921         if (parcel.readInt() != 0) {
2922             mChannelId = parcel.readString8();
2923         }
2924         mTimeout = parcel.readLong();
2925 
2926         if (parcel.readInt() != 0) {
2927             mShortcutId = parcel.readString8();
2928         }
2929 
2930         if (parcel.readInt() != 0) {
2931             mLocusId = LocusId.CREATOR.createFromParcel(parcel);
2932         }
2933 
2934         mBadgeIcon = parcel.readInt();
2935 
2936         if (parcel.readInt() != 0) {
2937             mSettingsText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
2938         }
2939 
2940         mGroupAlertBehavior = parcel.readInt();
2941         if (parcel.readInt() != 0) {
2942             mBubbleMetadata = BubbleMetadata.CREATOR.createFromParcel(parcel);
2943         }
2944 
2945         mAllowSystemGeneratedContextualActions = parcel.readBoolean();
2946 
2947         mFgsDeferBehavior = parcel.readInt();
2948     }
2949 
2950     @Override
clone()2951     public Notification clone() {
2952         Notification that = new Notification();
2953         cloneInto(that, true);
2954         return that;
2955     }
2956 
2957     /**
2958      * Copy all (or if heavy is false, all except Bitmaps and RemoteViews) members
2959      * of this into that.
2960      * @hide
2961      */
cloneInto(Notification that, boolean heavy)2962     public void cloneInto(Notification that, boolean heavy) {
2963         that.mAllowlistToken = this.mAllowlistToken;
2964         that.when = this.when;
2965         that.creationTime = this.creationTime;
2966         that.mSmallIcon = this.mSmallIcon;
2967         that.number = this.number;
2968 
2969         // PendingIntents are global, so there's no reason (or way) to clone them.
2970         that.contentIntent = this.contentIntent;
2971         that.deleteIntent = this.deleteIntent;
2972         that.fullScreenIntent = this.fullScreenIntent;
2973 
2974         if (this.tickerText != null) {
2975             that.tickerText = this.tickerText.toString();
2976         }
2977         if (heavy && this.tickerView != null) {
2978             that.tickerView = this.tickerView.clone();
2979         }
2980         if (heavy && this.contentView != null) {
2981             that.contentView = this.contentView.clone();
2982         }
2983         if (heavy && this.mLargeIcon != null) {
2984             that.mLargeIcon = this.mLargeIcon;
2985         }
2986         that.iconLevel = this.iconLevel;
2987         that.sound = this.sound; // android.net.Uri is immutable
2988         that.audioStreamType = this.audioStreamType;
2989         if (this.audioAttributes != null) {
2990             that.audioAttributes = new AudioAttributes.Builder(this.audioAttributes).build();
2991         }
2992 
2993         final long[] vibrate = this.vibrate;
2994         if (vibrate != null) {
2995             final int N = vibrate.length;
2996             final long[] vib = that.vibrate = new long[N];
2997             System.arraycopy(vibrate, 0, vib, 0, N);
2998         }
2999 
3000         that.ledARGB = this.ledARGB;
3001         that.ledOnMS = this.ledOnMS;
3002         that.ledOffMS = this.ledOffMS;
3003         that.defaults = this.defaults;
3004 
3005         that.flags = this.flags;
3006 
3007         that.priority = this.priority;
3008 
3009         that.category = this.category;
3010 
3011         that.mGroupKey = this.mGroupKey;
3012 
3013         that.mSortKey = this.mSortKey;
3014 
3015         if (this.extras != null) {
3016             try {
3017                 that.extras = new Bundle(this.extras);
3018                 // will unparcel
3019                 that.extras.size();
3020             } catch (BadParcelableException e) {
3021                 Log.e(TAG, "could not unparcel extras from notification: " + this, e);
3022                 that.extras = null;
3023             }
3024         }
3025 
3026         if (!ArrayUtils.isEmpty(allPendingIntents)) {
3027             that.allPendingIntents = new ArraySet<>(allPendingIntents);
3028         }
3029 
3030         if (this.actions != null) {
3031             that.actions = new Action[this.actions.length];
3032             for(int i=0; i<this.actions.length; i++) {
3033                 if ( this.actions[i] != null) {
3034                     that.actions[i] = this.actions[i].clone();
3035                 }
3036             }
3037         }
3038 
3039         if (heavy && this.bigContentView != null) {
3040             that.bigContentView = this.bigContentView.clone();
3041         }
3042 
3043         if (heavy && this.headsUpContentView != null) {
3044             that.headsUpContentView = this.headsUpContentView.clone();
3045         }
3046 
3047         that.visibility = this.visibility;
3048 
3049         if (this.publicVersion != null) {
3050             that.publicVersion = new Notification();
3051             this.publicVersion.cloneInto(that.publicVersion, heavy);
3052         }
3053 
3054         that.color = this.color;
3055 
3056         that.mChannelId = this.mChannelId;
3057         that.mTimeout = this.mTimeout;
3058         that.mShortcutId = this.mShortcutId;
3059         that.mLocusId = this.mLocusId;
3060         that.mBadgeIcon = this.mBadgeIcon;
3061         that.mSettingsText = this.mSettingsText;
3062         that.mGroupAlertBehavior = this.mGroupAlertBehavior;
3063         that.mFgsDeferBehavior = this.mFgsDeferBehavior;
3064         that.mBubbleMetadata = this.mBubbleMetadata;
3065         that.mAllowSystemGeneratedContextualActions = this.mAllowSystemGeneratedContextualActions;
3066 
3067         if (!heavy) {
3068             that.lightenPayload(); // will clean out extras
3069         }
3070     }
3071 
visitIconUri(@onNull Consumer<Uri> visitor, @Nullable Icon icon)3072     private static void visitIconUri(@NonNull Consumer<Uri> visitor, @Nullable Icon icon) {
3073         if (icon == null) return;
3074         final int iconType = icon.getType();
3075         if (iconType == TYPE_URI || iconType == TYPE_URI_ADAPTIVE_BITMAP) {
3076             visitor.accept(icon.getUri());
3077         }
3078     }
3079 
3080     /**
3081     * Note all {@link Uri} that are referenced internally, with the expectation that Uri permission
3082     * grants will need to be issued to ensure the recipient of this object is able to render its
3083     * contents.
3084     * See b/281044385 for more context and examples about what happens when this isn't done
3085     * correctly.
3086     *
3087     * @hide
3088     */
visitUris(@onNull Consumer<Uri> visitor)3089     public void visitUris(@NonNull Consumer<Uri> visitor) {
3090         if (publicVersion != null) {
3091             publicVersion.visitUris(visitor);
3092         }
3093 
3094         visitor.accept(sound);
3095 
3096         if (tickerView != null) tickerView.visitUris(visitor);
3097         if (contentView != null) contentView.visitUris(visitor);
3098         if (bigContentView != null) bigContentView.visitUris(visitor);
3099         if (headsUpContentView != null) headsUpContentView.visitUris(visitor);
3100 
3101         visitIconUri(visitor, mSmallIcon);
3102         visitIconUri(visitor, mLargeIcon);
3103 
3104         if (actions != null) {
3105             for (Action action : actions) {
3106                 action.visitUris(visitor);
3107             }
3108         }
3109 
3110         if (extras != null) {
3111             visitIconUri(visitor, extras.getParcelable(EXTRA_LARGE_ICON_BIG, Icon.class));
3112             visitIconUri(visitor, extras.getParcelable(EXTRA_PICTURE_ICON, Icon.class));
3113 
3114             // NOTE: The documentation of EXTRA_AUDIO_CONTENTS_URI explicitly says that it is a
3115             // String representation of a Uri, but the previous implementation (and unit test) of
3116             // this method has always treated it as a Uri object. Given the inconsistency,
3117             // supporting both going forward is the safest choice.
3118             Object audioContentsUri = extras.get(EXTRA_AUDIO_CONTENTS_URI);
3119             if (audioContentsUri instanceof Uri) {
3120                 visitor.accept((Uri) audioContentsUri);
3121             } else if (audioContentsUri instanceof String) {
3122                 visitor.accept(Uri.parse((String) audioContentsUri));
3123             }
3124 
3125             if (extras.containsKey(EXTRA_BACKGROUND_IMAGE_URI)) {
3126                 visitor.accept(Uri.parse(extras.getString(EXTRA_BACKGROUND_IMAGE_URI)));
3127             }
3128 
3129             ArrayList<Person> people = extras.getParcelableArrayList(EXTRA_PEOPLE_LIST, android.app.Person.class);
3130             if (people != null && !people.isEmpty()) {
3131                 for (Person p : people) {
3132                     p.visitUris(visitor);
3133                 }
3134             }
3135 
3136             final RemoteInputHistoryItem[] history = extras.getParcelableArray(
3137                     Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS,
3138                     RemoteInputHistoryItem.class);
3139             if (history != null) {
3140                 for (int i = 0; i < history.length; i++) {
3141                     RemoteInputHistoryItem item = history[i];
3142                     if (item.getUri() != null) {
3143                         visitor.accept(item.getUri());
3144                     }
3145                 }
3146             }
3147 
3148             // Extras for MessagingStyle. We visit them even if not isStyle(MessagingStyle), since
3149             // Notification Listeners might use directly (without the isStyle check).
3150             final Person person = extras.getParcelable(EXTRA_MESSAGING_PERSON, Person.class);
3151             if (person != null) {
3152                 person.visitUris(visitor);
3153             }
3154 
3155             final Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES,
3156                     Parcelable.class);
3157             if (!ArrayUtils.isEmpty(messages)) {
3158                 for (MessagingStyle.Message message : MessagingStyle.Message
3159                         .getMessagesFromBundleArray(messages)) {
3160                     message.visitUris(visitor);
3161                 }
3162             }
3163 
3164             final Parcelable[] historic = extras.getParcelableArray(EXTRA_HISTORIC_MESSAGES,
3165                     Parcelable.class);
3166             if (!ArrayUtils.isEmpty(historic)) {
3167                 for (MessagingStyle.Message message : MessagingStyle.Message
3168                         .getMessagesFromBundleArray(historic)) {
3169                     message.visitUris(visitor);
3170                 }
3171             }
3172 
3173             visitIconUri(visitor, extras.getParcelable(EXTRA_CONVERSATION_ICON, Icon.class));
3174 
3175             // Extras for CallStyle (same reason for visiting without checking isStyle).
3176             Person callPerson = extras.getParcelable(EXTRA_CALL_PERSON, Person.class);
3177             if (callPerson != null) {
3178                 callPerson.visitUris(visitor);
3179             }
3180             visitIconUri(visitor, extras.getParcelable(EXTRA_VERIFICATION_ICON, Icon.class));
3181 
3182 
3183             if (Flags.apiRichOngoing()) {
3184                 visitIconUri(visitor, extras.getParcelable(EXTRA_PROGRESS_TRACKER_ICON,
3185                     Icon.class));
3186                 visitIconUri(visitor, extras.getParcelable(EXTRA_PROGRESS_START_ICON,
3187                     Icon.class));
3188                 visitIconUri(visitor, extras.getParcelable(EXTRA_PROGRESS_END_ICON,
3189                     Icon.class));
3190             }
3191         }
3192 
3193         if (mBubbleMetadata != null) {
3194             visitIconUri(visitor, mBubbleMetadata.getIcon());
3195         }
3196 
3197         if (extras != null && extras.containsKey(WearableExtender.EXTRA_WEARABLE_EXTENSIONS)) {
3198             WearableExtender extender = new WearableExtender(this);
3199             extender.visitUris(visitor);
3200         }
3201     }
3202 
3203     /**
3204      * @hide
3205      */
loadHeaderAppName(Context context)3206     public String loadHeaderAppName(Context context) {
3207         Trace.beginSection("Notification#loadHeaderAppName");
3208 
3209         try {
3210             CharSequence name = null;
3211             // Check if there is a non-empty substitute app name and return that.
3212             if (extras.containsKey(EXTRA_SUBSTITUTE_APP_NAME)) {
3213                 name = extras.getString(EXTRA_SUBSTITUTE_APP_NAME);
3214                 if (!TextUtils.isEmpty(name)) {
3215                     return name.toString();
3216                 }
3217             }
3218             // If not, try getting the name from the app info.
3219             if (context == null) {
3220                 return null;
3221             }
3222             if (TextUtils.isEmpty(name)) {
3223                 ApplicationInfo info = getApplicationInfo(context);
3224                 if (info != null) {
3225                     final PackageManager pm = context.getPackageManager();
3226                     name = pm.getApplicationLabel(getApplicationInfo(context));
3227                 }
3228             }
3229             // If there's still nothing, ¯\_(ツ)_/¯
3230             if (TextUtils.isEmpty(name)) {
3231                 return null;
3232             }
3233             return name.toString();
3234         } finally {
3235             Trace.endSection();
3236         }
3237     }
3238 
3239     /**
3240      * @hide
3241      */
containsCustomViews()3242     public boolean containsCustomViews() {
3243         return contentView != null
3244                 || bigContentView != null
3245                 || headsUpContentView != null
3246                 || (publicVersion != null
3247                 && (publicVersion.contentView != null
3248                 || publicVersion.bigContentView != null
3249                 || publicVersion.headsUpContentView != null));
3250     }
3251 
3252     /**
3253      * @hide
3254      */
hasTitle()3255     public boolean hasTitle() {
3256         if (extras == null) {
3257             return false;
3258         }
3259         // CallStyle notifications only use the other person's name as the title.
3260         if (isStyle(CallStyle.class)) {
3261             Person person = extras.getParcelable(EXTRA_CALL_PERSON, Person.class);
3262             return person != null && !TextUtils.isEmpty(person.getName());
3263         }
3264         // non-CallStyle notifications can use EXTRA_TITLE
3265         if (!TextUtils.isEmpty(extras.getCharSequence(EXTRA_TITLE))) {
3266             return true;
3267         }
3268         // BigTextStyle notifications first use EXTRA_TITLE_BIG
3269         if (isStyle(BigTextStyle.class)) {
3270             return !TextUtils.isEmpty(extras.getCharSequence(EXTRA_TITLE_BIG));
3271         } else {
3272             return false;
3273         }
3274     }
3275 
3276     /**
3277      * @hide
3278      */
3279     @FlaggedApi(Flags.FLAG_API_RICH_ONGOING)
hasPromotableStyle()3280     public boolean hasPromotableStyle() {
3281         final Class<? extends Style> notificationStyle = getNotificationStyle();
3282 
3283         return notificationStyle == null
3284                 || BigTextStyle.class.equals(notificationStyle)
3285                 || CallStyle.class.equals(notificationStyle)
3286                 || ProgressStyle.class.equals(notificationStyle);
3287     }
3288 
3289     /**
3290      * Returns whether the notification has all the characteristics that make it eligible for
3291      * {@link #FLAG_PROMOTED_ONGOING}. This method does not factor in other criteria such user
3292      * preferences for the app or channel. If this returns true, it does not guarantee that the
3293      * notification will be assigned FLAG_PROMOTED_ONGOING by the system, but if this returns false,
3294      * it will not.
3295      */
3296     @FlaggedApi(Flags.FLAG_API_RICH_ONGOING)
hasPromotableCharacteristics()3297     public boolean hasPromotableCharacteristics() {
3298         if (!isOngoingEvent() || isGroupSummary() || containsCustomViews() || !hasTitle()) {
3299             return false;
3300         }
3301         // Only "Ongoing CallStyle" notifications are promotable without EXTRA_COLORIZED
3302         if (isOngoingCallStyle()) {
3303             return true;
3304         }
3305         return isColorizedRequested() && hasPromotableStyle();
3306     }
3307 
3308     /** Returns whether the notification is CallStyle.forOngoingCall(). */
isOngoingCallStyle()3309     private boolean isOngoingCallStyle() {
3310         if (!isStyle(CallStyle.class)) {
3311             return false;
3312         }
3313         int callType = extras.getInt(EXTRA_CALL_TYPE, CallStyle.CALL_TYPE_UNKNOWN);
3314         return callType == CallStyle.CALL_TYPE_ONGOING;
3315     }
3316 
3317     /**
3318      * Fetch the application info from the notification, or the context if that isn't available.
3319      */
getApplicationInfo(Context context)3320     private ApplicationInfo getApplicationInfo(Context context) {
3321         ApplicationInfo info = null;
3322         if (extras.containsKey(EXTRA_BUILDER_APPLICATION_INFO)) {
3323             info = extras.getParcelable(
3324                     EXTRA_BUILDER_APPLICATION_INFO,
3325                     ApplicationInfo.class);
3326         }
3327         if (info == null) {
3328             if (context == null) {
3329                 return null;
3330             }
3331             info = context.getApplicationInfo();
3332         }
3333         return info;
3334     }
3335 
3336     /**
3337      * Removes heavyweight parts of the Notification object for archival or for sending to
3338      * listeners when the full contents are not necessary.
3339      * @hide
3340      */
lightenPayload()3341     public final void lightenPayload() {
3342         tickerView = null;
3343         contentView = null;
3344         bigContentView = null;
3345         headsUpContentView = null;
3346         mLargeIcon = null;
3347         if (extras != null && !extras.isEmpty()) {
3348             final Set<String> keyset = extras.keySet();
3349             final int N = keyset.size();
3350             final String[] keys = keyset.toArray(new String[N]);
3351             for (int i=0; i<N; i++) {
3352                 final String key = keys[i];
3353                 if (TvExtender.EXTRA_TV_EXTENDER.equals(key)) {
3354                     continue;
3355                 }
3356                 final Object obj = extras.get(key);
3357                 if (obj != null &&
3358                     (  obj instanceof Parcelable
3359                     || obj instanceof Parcelable[]
3360                     || obj instanceof SparseArray
3361                     || obj instanceof ArrayList)) {
3362                     extras.remove(key);
3363                 }
3364             }
3365         }
3366     }
3367 
3368     /**
3369      * Make sure this String is safe to put into a bundle.
3370      * @hide
3371      */
safeString(String str)3372     public static String safeString(String str) {
3373         if (str == null) return str;
3374         if (str.length() > MAX_CHARSEQUENCE_LENGTH) {
3375             str = str.substring(0, MAX_CHARSEQUENCE_LENGTH);
3376         }
3377         return str;
3378     }
3379 
3380     /**
3381      * Make sure this CharSequence is safe to put into a bundle, which basically
3382      * means it had better not be some custom Parcelable implementation.
3383      * @hide
3384      */
safeCharSequence(CharSequence cs)3385     public static CharSequence safeCharSequence(CharSequence cs) {
3386         if (cs == null) return cs;
3387         if (cs.length() > MAX_CHARSEQUENCE_LENGTH) {
3388             cs = cs.subSequence(0, MAX_CHARSEQUENCE_LENGTH);
3389         }
3390         if (cs instanceof Parcelable) {
3391             Log.e(TAG, "warning: " + cs.getClass().getCanonicalName()
3392                     + " instance is a custom Parcelable and not allowed in Notification");
3393             return cs.toString();
3394         }
3395 
3396         return removeTextSizeSpans(cs);
3397     }
3398 
stripStyling(@ullable CharSequence cs)3399     private static CharSequence stripStyling(@Nullable CharSequence cs) {
3400         if (cs == null) {
3401             return cs;
3402         }
3403 
3404         return cs.toString();
3405     }
3406 
normalizeBigText(@ullable CharSequence charSequence)3407     private static CharSequence normalizeBigText(@Nullable CharSequence charSequence) {
3408         if (charSequence == null) {
3409             return charSequence;
3410         }
3411 
3412         return NotificationBigTextNormalizer.normalizeBigText(charSequence.toString());
3413     }
3414 
removeTextSizeSpans(CharSequence charSequence)3415     private static CharSequence removeTextSizeSpans(CharSequence charSequence) {
3416         if (charSequence instanceof Spanned) {
3417             Spanned ss = (Spanned) charSequence;
3418             Object[] spans = ss.getSpans(0, ss.length(), Object.class);
3419             SpannableStringBuilder builder = new SpannableStringBuilder(ss.toString());
3420             for (Object span : spans) {
3421                 Object resultSpan = span;
3422                 if (resultSpan instanceof CharacterStyle) {
3423                     resultSpan = ((CharacterStyle) span).getUnderlying();
3424                 }
3425                 if (resultSpan instanceof TextAppearanceSpan) {
3426                     TextAppearanceSpan originalSpan = (TextAppearanceSpan) resultSpan;
3427                     resultSpan = new TextAppearanceSpan(
3428                             originalSpan.getFamily(),
3429                             originalSpan.getTextStyle(),
3430                             -1,
3431                             originalSpan.getTextColor(),
3432                             originalSpan.getLinkTextColor());
3433                 } else if (resultSpan instanceof RelativeSizeSpan
3434                         || resultSpan instanceof AbsoluteSizeSpan) {
3435                     continue;
3436                 } else {
3437                     resultSpan = span;
3438                 }
3439                 builder.setSpan(resultSpan, ss.getSpanStart(span), ss.getSpanEnd(span),
3440                         ss.getSpanFlags(span));
3441             }
3442             return builder;
3443         }
3444         return charSequence;
3445     }
3446 
describeContents()3447     public int describeContents() {
3448         return 0;
3449     }
3450 
3451     /**
3452      * Flatten this notification into a parcel.
3453      */
writeToParcel(Parcel parcel, int flags)3454     public void writeToParcel(Parcel parcel, int flags) {
3455         // We need to mark all pending intents getting into the notification
3456         // system as being put there to later allow the notification ranker
3457         // to launch them and by doing so add the app to the battery saver white
3458         // list for a short period of time. The problem is that the system
3459         // cannot look into the extras as there may be parcelables there that
3460         // the platform does not know how to handle. To go around that we have
3461         // an explicit list of the pending intents in the extras bundle.
3462         PendingIntent.OnMarshaledListener addedListener = null;
3463         if (allPendingIntents == null) {
3464             addedListener = (PendingIntent intent, Parcel out, int outFlags) -> {
3465                 if (parcel == out) {
3466                     synchronized (this) {
3467                         if (allPendingIntents == null) {
3468                             allPendingIntents = new ArraySet<>();
3469                         }
3470                         allPendingIntents.add(intent);
3471                     }
3472                 }
3473             };
3474             PendingIntent.addOnMarshaledListener(addedListener);
3475         }
3476         try {
3477             boolean mustClearCookie = false;
3478             if (!parcel.hasClassCookie(Notification.class)) {
3479                 // This is the "root" notification, and not an "inner" notification (including
3480                 // publicVersion or anything else that might be embedded in extras). So we want
3481                 // to use its token for every inner notification (might be null).
3482                 parcel.setClassCookie(Notification.class, mAllowlistToken);
3483                 mustClearCookie = true;
3484             }
3485             try {
3486                 // IMPORTANT: Add marshaling code in writeToParcelImpl as we
3487                 // want to intercept all pending events written to the parcel.
3488                 writeToParcelImpl(parcel, flags);
3489             } finally {
3490                 if (mustClearCookie) {
3491                     parcel.removeClassCookie(Notification.class, mAllowlistToken);
3492                 }
3493             }
3494 
3495             synchronized (this) {
3496                 // Must be written last!
3497                 parcel.writeArraySet(allPendingIntents);
3498             }
3499         } finally {
3500             if (addedListener != null) {
3501                 PendingIntent.removeOnMarshaledListener(addedListener);
3502             }
3503         }
3504     }
3505 
writeToParcelImpl(Parcel parcel, int flags)3506     private void writeToParcelImpl(Parcel parcel, int flags) {
3507         parcel.writeInt(1);
3508 
3509         // Always use the same token as the root notification (might be null).
3510         IBinder rootNotificationToken = (IBinder) parcel.getClassCookie(Notification.class);
3511         parcel.writeStrongBinder(rootNotificationToken);
3512 
3513         parcel.writeLong(when);
3514         parcel.writeLong(creationTime);
3515         if (mSmallIcon == null && icon != 0) {
3516             // you snuck an icon in here without using the builder; let's try to keep it
3517             mSmallIcon = Icon.createWithResource("", icon);
3518         }
3519         if (mSmallIcon != null) {
3520             parcel.writeInt(1);
3521             mSmallIcon.writeToParcel(parcel, 0);
3522         } else {
3523             parcel.writeInt(0);
3524         }
3525         parcel.writeInt(number);
3526         if (contentIntent != null) {
3527             parcel.writeInt(1);
3528             contentIntent.writeToParcel(parcel, 0);
3529         } else {
3530             parcel.writeInt(0);
3531         }
3532         if (deleteIntent != null) {
3533             parcel.writeInt(1);
3534             deleteIntent.writeToParcel(parcel, 0);
3535         } else {
3536             parcel.writeInt(0);
3537         }
3538         if (tickerText != null) {
3539             parcel.writeInt(1);
3540             TextUtils.writeToParcel(tickerText, parcel, flags);
3541         } else {
3542             parcel.writeInt(0);
3543         }
3544         if (tickerView != null) {
3545             parcel.writeInt(1);
3546             tickerView.writeToParcel(parcel, 0);
3547         } else {
3548             parcel.writeInt(0);
3549         }
3550         if (contentView != null) {
3551             parcel.writeInt(1);
3552             contentView.writeToParcel(parcel, 0);
3553         } else {
3554             parcel.writeInt(0);
3555         }
3556         if (mLargeIcon == null && largeIcon != null) {
3557             // you snuck an icon in here without using the builder; let's try to keep it
3558             mLargeIcon = Icon.createWithBitmap(largeIcon);
3559         }
3560         if (mLargeIcon != null) {
3561             parcel.writeInt(1);
3562             mLargeIcon.writeToParcel(parcel, 0);
3563         } else {
3564             parcel.writeInt(0);
3565         }
3566 
3567         parcel.writeInt(defaults);
3568         parcel.writeInt(this.flags);
3569 
3570         if (sound != null) {
3571             parcel.writeInt(1);
3572             sound.writeToParcel(parcel, 0);
3573         } else {
3574             parcel.writeInt(0);
3575         }
3576         parcel.writeInt(audioStreamType);
3577 
3578         if (audioAttributes != null) {
3579             parcel.writeInt(1);
3580             audioAttributes.writeToParcel(parcel, 0);
3581         } else {
3582             parcel.writeInt(0);
3583         }
3584 
3585         parcel.writeLongArray(vibrate);
3586         parcel.writeInt(ledARGB);
3587         parcel.writeInt(ledOnMS);
3588         parcel.writeInt(ledOffMS);
3589         parcel.writeInt(iconLevel);
3590 
3591         if (fullScreenIntent != null) {
3592             parcel.writeInt(1);
3593             fullScreenIntent.writeToParcel(parcel, 0);
3594         } else {
3595             parcel.writeInt(0);
3596         }
3597 
3598         parcel.writeInt(priority);
3599 
3600         parcel.writeString8(category);
3601 
3602         parcel.writeString8(mGroupKey);
3603 
3604         parcel.writeString8(mSortKey);
3605 
3606         parcel.writeBundle(extras); // null ok
3607 
3608         parcel.writeTypedArray(actions, 0); // null ok
3609 
3610         if (bigContentView != null) {
3611             parcel.writeInt(1);
3612             bigContentView.writeToParcel(parcel, 0);
3613         } else {
3614             parcel.writeInt(0);
3615         }
3616 
3617         if (headsUpContentView != null) {
3618             parcel.writeInt(1);
3619             headsUpContentView.writeToParcel(parcel, 0);
3620         } else {
3621             parcel.writeInt(0);
3622         }
3623 
3624         parcel.writeInt(visibility);
3625 
3626         if (publicVersion != null) {
3627             parcel.writeInt(1);
3628             publicVersion.writeToParcel(parcel, 0);
3629         } else {
3630             parcel.writeInt(0);
3631         }
3632 
3633         parcel.writeInt(color);
3634 
3635         if (mChannelId != null) {
3636             parcel.writeInt(1);
3637             parcel.writeString8(mChannelId);
3638         } else {
3639             parcel.writeInt(0);
3640         }
3641         parcel.writeLong(mTimeout);
3642 
3643         if (mShortcutId != null) {
3644             parcel.writeInt(1);
3645             parcel.writeString8(mShortcutId);
3646         } else {
3647             parcel.writeInt(0);
3648         }
3649 
3650         if (mLocusId != null) {
3651             parcel.writeInt(1);
3652             mLocusId.writeToParcel(parcel, 0);
3653         } else {
3654             parcel.writeInt(0);
3655         }
3656 
3657         parcel.writeInt(mBadgeIcon);
3658 
3659         if (mSettingsText != null) {
3660             parcel.writeInt(1);
3661             TextUtils.writeToParcel(mSettingsText, parcel, flags);
3662         } else {
3663             parcel.writeInt(0);
3664         }
3665 
3666         parcel.writeInt(mGroupAlertBehavior);
3667 
3668         if (mBubbleMetadata != null) {
3669             parcel.writeInt(1);
3670             mBubbleMetadata.writeToParcel(parcel, 0);
3671         } else {
3672             parcel.writeInt(0);
3673         }
3674 
3675         parcel.writeBoolean(mAllowSystemGeneratedContextualActions);
3676 
3677         parcel.writeInt(mFgsDeferBehavior);
3678 
3679         // mUsesStandardHeader is not written because it should be recomputed in listeners
3680     }
3681 
3682     /**
3683      * Parcelable.Creator that instantiates Notification objects
3684      */
3685     public static final @android.annotation.NonNull Parcelable.Creator<Notification> CREATOR
3686             = new Parcelable.Creator<Notification>()
3687     {
3688         public Notification createFromParcel(Parcel parcel)
3689         {
3690             return new Notification(parcel);
3691         }
3692 
3693         public Notification[] newArray(int size)
3694         {
3695             return new Notification[size];
3696         }
3697     };
3698 
3699     /**
3700      * @hide
3701      */
areActionsVisiblyDifferent(Notification first, Notification second)3702     public static boolean areActionsVisiblyDifferent(Notification first, Notification second) {
3703         Notification.Action[] firstAs = first.actions;
3704         Notification.Action[] secondAs = second.actions;
3705         if (firstAs == null && secondAs != null || firstAs != null && secondAs == null) {
3706             return true;
3707         }
3708         if (firstAs != null && secondAs != null) {
3709             if (firstAs.length != secondAs.length) {
3710                 return true;
3711             }
3712             for (int i = 0; i < firstAs.length; i++) {
3713                 if (!Objects.equals(String.valueOf(firstAs[i].title),
3714                         String.valueOf(secondAs[i].title))) {
3715                     return true;
3716                 }
3717                 RemoteInput[] firstRs = firstAs[i].getRemoteInputs();
3718                 RemoteInput[] secondRs = secondAs[i].getRemoteInputs();
3719                 if (firstRs == null) {
3720                     firstRs = new RemoteInput[0];
3721                 }
3722                 if (secondRs == null) {
3723                     secondRs = new RemoteInput[0];
3724                 }
3725                 if (firstRs.length != secondRs.length) {
3726                     return true;
3727                 }
3728                 for (int j = 0; j < firstRs.length; j++) {
3729                     if (!Objects.equals(String.valueOf(firstRs[j].getLabel()),
3730                             String.valueOf(secondRs[j].getLabel()))) {
3731                         return true;
3732                     }
3733                 }
3734             }
3735         }
3736         return false;
3737     }
3738 
3739     /**
3740      * @hide
3741      */
areIconsDifferent(Notification first, Notification second)3742     public static boolean areIconsDifferent(Notification first, Notification second) {
3743         return areIconsMaybeDifferent(first.getSmallIcon(), second.getSmallIcon())
3744                 || areIconsMaybeDifferent(first.getLargeIcon(), second.getLargeIcon());
3745     }
3746 
3747     /**
3748      * Note that we aren't actually comparing the contents of the bitmaps here; this is only a
3749      * cursory inspection. We will not return false negatives, but false positives are likely.
3750      */
areIconsMaybeDifferent(Icon a, Icon b)3751     private static boolean areIconsMaybeDifferent(Icon a, Icon b) {
3752         if (a == b) {
3753             return false;
3754         }
3755         if (a == null || b == null) {
3756             return true;
3757         }
3758         if (a.sameAs(b)) {
3759             return false;
3760         }
3761         final int aType = a.getType();
3762         if (aType != b.getType()) {
3763             return true;
3764         }
3765         if (aType == Icon.TYPE_BITMAP || aType == Icon.TYPE_ADAPTIVE_BITMAP) {
3766             final Bitmap aBitmap = a.getBitmap();
3767             final Bitmap bBitmap = b.getBitmap();
3768             return aBitmap.getWidth() != bBitmap.getWidth()
3769                     || aBitmap.getHeight() != bBitmap.getHeight()
3770                     || aBitmap.getConfig() != bBitmap.getConfig()
3771                     || aBitmap.getGenerationId() != bBitmap.getGenerationId();
3772         }
3773         return true;
3774     }
3775 
3776     /**
3777      * @hide
3778      */
areStyledNotificationsVisiblyDifferent(Builder first, Builder second)3779     public static boolean areStyledNotificationsVisiblyDifferent(Builder first, Builder second) {
3780         if (first.getStyle() == null) {
3781             return second.getStyle() != null;
3782         }
3783         if (second.getStyle() == null) {
3784             return true;
3785         }
3786         return first.getStyle().areNotificationsVisiblyDifferent(second.getStyle());
3787     }
3788 
3789     /**
3790      * @hide
3791      */
areRemoteViewsChanged(Builder first, Builder second)3792     public static boolean areRemoteViewsChanged(Builder first, Builder second) {
3793         if (!Objects.equals(first.usesStandardHeader(), second.usesStandardHeader())) {
3794             return true;
3795         }
3796 
3797         if (areRemoteViewsChanged(first.mN.contentView, second.mN.contentView)) {
3798             return true;
3799         }
3800         if (areRemoteViewsChanged(first.mN.bigContentView, second.mN.bigContentView)) {
3801             return true;
3802         }
3803         if (areRemoteViewsChanged(first.mN.headsUpContentView, second.mN.headsUpContentView)) {
3804             return true;
3805         }
3806 
3807         return false;
3808     }
3809 
areRemoteViewsChanged(RemoteViews first, RemoteViews second)3810     private static boolean areRemoteViewsChanged(RemoteViews first, RemoteViews second) {
3811         if (first == null && second == null) {
3812             return false;
3813         }
3814         if (first == null && second != null || first != null && second == null) {
3815             return true;
3816         }
3817 
3818         if (!Objects.equals(first.getLayoutId(), second.getLayoutId())) {
3819             return true;
3820         }
3821 
3822         if (!Objects.equals(first.getSequenceNumber(), second.getSequenceNumber())) {
3823             return true;
3824         }
3825 
3826         return false;
3827     }
3828 
3829     /**
3830      * Parcelling creates multiple copies of objects in {@code extras}. Fix them.
3831      * <p>
3832      * For backwards compatibility {@code extras} holds some references to "real" member data such
3833      * as {@link getLargeIcon()} which is mirrored by {@link #EXTRA_LARGE_ICON}. This is mostly
3834      * fine as long as the object stays in one process.
3835      * <p>
3836      * However, once the notification goes into a parcel each reference gets marshalled separately,
3837      * wasting memory. Especially with large images on Auto and TV, this is worth fixing.
3838      */
fixDuplicateExtras()3839     private void fixDuplicateExtras() {
3840         if (extras != null) {
3841             fixDuplicateExtra(mLargeIcon, EXTRA_LARGE_ICON);
3842         }
3843     }
3844 
3845     /**
3846      * If we find an extra that's exactly the same as one of the "real" fields but refers to a
3847      * separate object, replace it with the field's version to avoid holding duplicate copies.
3848      */
fixDuplicateExtra(@ullable Parcelable original, @NonNull String extraName)3849     private void fixDuplicateExtra(@Nullable Parcelable original, @NonNull String extraName) {
3850         if (original != null && extras.getParcelable(extraName, Parcelable.class) != null) {
3851             extras.putParcelable(extraName, original);
3852         }
3853     }
3854 
3855     /**
3856      * Sets the {@link #contentView} field to be a view with the standard "Latest Event"
3857      * layout.
3858      *
3859      * <p>Uses the {@link #icon} and {@link #when} fields to set the icon and time fields
3860      * in the view.</p>
3861      * @param context       The context for your application / activity.
3862      * @param contentTitle The title that goes in the expanded entry.
3863      * @param contentText  The text that goes in the expanded entry.
3864      * @param contentIntent The intent to launch when the user clicks the expanded notification.
3865      * If this is an activity, it must include the
3866      * {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK} flag, which requires
3867      * that you take care of task management as described in the
3868      * <a href="{@docRoot}guide/topics/fundamentals/tasks-and-back-stack.html">Tasks and Back
3869      * Stack</a> document.
3870      *
3871      * @deprecated Use {@link Builder} instead.
3872      * @removed
3873      */
3874     @Deprecated
setLatestEventInfo(Context context, CharSequence contentTitle, CharSequence contentText, PendingIntent contentIntent)3875     public void setLatestEventInfo(Context context,
3876             CharSequence contentTitle, CharSequence contentText, PendingIntent contentIntent) {
3877         if (context.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1){
3878             Log.e(TAG, "setLatestEventInfo() is deprecated and you should feel deprecated.",
3879                     new Throwable());
3880         }
3881 
3882         if (context.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N) {
3883             extras.putBoolean(EXTRA_SHOW_WHEN, true);
3884         }
3885 
3886         // ensure that any information already set directly is preserved
3887         final Notification.Builder builder = new Notification.Builder(context, this);
3888 
3889         // now apply the latestEventInfo fields
3890         if (contentTitle != null) {
3891             builder.setContentTitle(contentTitle);
3892         }
3893         if (contentText != null) {
3894             builder.setContentText(contentText);
3895         }
3896         builder.setContentIntent(contentIntent);
3897 
3898         builder.build(); // callers expect this notification to be ready to use
3899     }
3900 
3901     /**
3902      * Sets the token used for background operations for the pending intents associated with this
3903      * notification.
3904      *
3905      * Note: Should <em>only</em> be invoked by NotificationManagerService, since this is normally
3906      * populated by unparceling (and also used there). Any other usage is suspect.
3907      *
3908      * @hide
3909      */
overrideAllowlistToken(IBinder token)3910     public void overrideAllowlistToken(IBinder token) {
3911         mAllowlistToken = token;
3912         if (publicVersion != null) {
3913             publicVersion.overrideAllowlistToken(token);
3914         }
3915     }
3916 
3917     /** @hide */
getAllowlistToken()3918     public IBinder getAllowlistToken() {
3919         return mAllowlistToken;
3920     }
3921 
3922     /**
3923      * @hide
3924      */
addFieldsFromContext(Context context, Notification notification)3925     public static void addFieldsFromContext(Context context, Notification notification) {
3926         addFieldsFromContext(context.getApplicationInfo(), notification);
3927     }
3928 
3929     /**
3930      * @hide
3931      */
addFieldsFromContext(ApplicationInfo ai, Notification notification)3932     public static void addFieldsFromContext(ApplicationInfo ai, Notification notification) {
3933         notification.extras.putParcelable(EXTRA_BUILDER_APPLICATION_INFO, ai);
3934     }
3935 
3936     /**
3937      * @hide
3938      */
dumpDebug(ProtoOutputStream proto, long fieldId)3939     public void dumpDebug(ProtoOutputStream proto, long fieldId) {
3940         long token = proto.start(fieldId);
3941         proto.write(NotificationProto.CHANNEL_ID, getChannelId());
3942         proto.write(NotificationProto.HAS_TICKER_TEXT, this.tickerText != null);
3943         proto.write(NotificationProto.FLAGS, this.flags);
3944         proto.write(NotificationProto.COLOR, this.color);
3945         proto.write(NotificationProto.CATEGORY, this.category);
3946         proto.write(NotificationProto.GROUP_KEY, this.mGroupKey);
3947         proto.write(NotificationProto.SORT_KEY, this.mSortKey);
3948         if (this.actions != null) {
3949             proto.write(NotificationProto.ACTION_LENGTH, this.actions.length);
3950         }
3951         if (this.visibility >= VISIBILITY_SECRET && this.visibility <= VISIBILITY_PUBLIC) {
3952             proto.write(NotificationProto.VISIBILITY, this.visibility);
3953         }
3954         if (publicVersion != null) {
3955             publicVersion.dumpDebug(proto, NotificationProto.PUBLIC_VERSION);
3956         }
3957         proto.end(token);
3958     }
3959 
3960     @Override
toString()3961     public String toString() {
3962         StringBuilder sb = new StringBuilder();
3963         sb.append("Notification(channel=");
3964         sb.append(getChannelId());
3965         sb.append(" shortcut=");
3966         sb.append(getShortcutId());
3967         sb.append(" contentView=");
3968         if (contentView != null) {
3969             sb.append(contentView.getPackage());
3970             sb.append("/0x");
3971             sb.append(Integer.toHexString(contentView.getLayoutId()));
3972         } else {
3973             sb.append("null");
3974         }
3975         sb.append(" vibrate=");
3976         if ((this.defaults & DEFAULT_VIBRATE) != 0) {
3977             sb.append("default");
3978         } else if (this.vibrate != null) {
3979             int N = this.vibrate.length-1;
3980             sb.append("[");
3981             for (int i=0; i<N; i++) {
3982                 sb.append(this.vibrate[i]);
3983                 sb.append(',');
3984             }
3985             if (N != -1) {
3986                 sb.append(this.vibrate[N]);
3987             }
3988             sb.append("]");
3989         } else {
3990             sb.append("null");
3991         }
3992         sb.append(" sound=");
3993         if ((this.defaults & DEFAULT_SOUND) != 0) {
3994             sb.append("default");
3995         } else if (this.sound != null) {
3996             sb.append(this.sound.toString());
3997         } else {
3998             sb.append("null");
3999         }
4000         if (this.tickerText != null) {
4001             sb.append(" tick");
4002         }
4003         sb.append(" defaults=");
4004         sb.append(defaultsToString(this.defaults));
4005         sb.append(" flags=");
4006         sb.append(flagsToString(this.flags));
4007         sb.append(String.format(" color=0x%08x", this.color));
4008         if (this.category != null) {
4009             sb.append(" category=");
4010             sb.append(this.category);
4011         }
4012         if (this.mGroupKey != null) {
4013             sb.append(" groupKey=");
4014             sb.append(this.mGroupKey);
4015         }
4016         if (this.mSortKey != null) {
4017             sb.append(" sortKey=");
4018             sb.append(this.mSortKey);
4019         }
4020         if (actions != null) {
4021             sb.append(" actions=");
4022             sb.append(actions.length);
4023         }
4024         sb.append(" vis=");
4025         sb.append(visibilityToString(this.visibility));
4026         if (this.publicVersion != null) {
4027             sb.append(" publicVersion=");
4028             sb.append(publicVersion.toString());
4029         }
4030         if (this.mLocusId != null) {
4031             sb.append(" locusId=");
4032             sb.append(this.mLocusId); // LocusId.toString() is PII safe.
4033         }
4034         sb.append(")");
4035         return sb.toString();
4036     }
4037 
4038     /**
4039      * {@hide}
4040      */
visibilityToString(int vis)4041     public static String visibilityToString(int vis) {
4042         switch (vis) {
4043             case VISIBILITY_PRIVATE:
4044                 return "PRIVATE";
4045             case VISIBILITY_PUBLIC:
4046                 return "PUBLIC";
4047             case VISIBILITY_SECRET:
4048                 return "SECRET";
4049             default:
4050                 return "UNKNOWN(" + String.valueOf(vis) + ")";
4051         }
4052     }
4053 
4054     /**
4055      * {@hide}
4056      */
priorityToString(@riority int pri)4057     public static String priorityToString(@Priority int pri) {
4058         switch (pri) {
4059             case PRIORITY_MIN:
4060                 return "MIN";
4061             case PRIORITY_LOW:
4062                 return "LOW";
4063             case PRIORITY_DEFAULT:
4064                 return "DEFAULT";
4065             case PRIORITY_HIGH:
4066                 return "HIGH";
4067             case PRIORITY_MAX:
4068                 return "MAX";
4069             default:
4070                 return "UNKNOWN(" + String.valueOf(pri) + ")";
4071         }
4072     }
4073 
4074     /**
4075      * {@hide}
4076      */
flagsToString(@otificationFlags int flags)4077     public static String flagsToString(@NotificationFlags int flags) {
4078         final List<String> flagStrings = new ArrayList<String>();
4079         if ((flags & FLAG_SHOW_LIGHTS) != 0) {
4080             flagStrings.add("SHOW_LIGHTS");
4081             flags &= ~FLAG_SHOW_LIGHTS;
4082         }
4083         if ((flags & FLAG_ONGOING_EVENT) != 0) {
4084             flagStrings.add("ONGOING_EVENT");
4085             flags &= ~FLAG_ONGOING_EVENT;
4086         }
4087         if ((flags & FLAG_INSISTENT) != 0) {
4088             flagStrings.add("INSISTENT");
4089             flags &= ~FLAG_INSISTENT;
4090         }
4091         if ((flags & FLAG_ONLY_ALERT_ONCE) != 0) {
4092             flagStrings.add("ONLY_ALERT_ONCE");
4093             flags &= ~FLAG_ONLY_ALERT_ONCE;
4094         }
4095         if ((flags & FLAG_AUTO_CANCEL) != 0) {
4096             flagStrings.add("AUTO_CANCEL");
4097             flags &= ~FLAG_AUTO_CANCEL;
4098         }
4099         if ((flags & FLAG_NO_CLEAR) != 0) {
4100             flagStrings.add("NO_CLEAR");
4101             flags &= ~FLAG_NO_CLEAR;
4102         }
4103         if ((flags & FLAG_FOREGROUND_SERVICE) != 0) {
4104             flagStrings.add("FOREGROUND_SERVICE");
4105             flags &= ~FLAG_FOREGROUND_SERVICE;
4106         }
4107         if ((flags & FLAG_HIGH_PRIORITY) != 0) {
4108             flagStrings.add("HIGH_PRIORITY");
4109             flags &= ~FLAG_HIGH_PRIORITY;
4110         }
4111         if ((flags & FLAG_LOCAL_ONLY) != 0) {
4112             flagStrings.add("LOCAL_ONLY");
4113             flags &= ~FLAG_LOCAL_ONLY;
4114         }
4115         if ((flags & FLAG_GROUP_SUMMARY) != 0) {
4116             flagStrings.add("GROUP_SUMMARY");
4117             flags &= ~FLAG_GROUP_SUMMARY;
4118         }
4119         if ((flags & FLAG_AUTOGROUP_SUMMARY) != 0) {
4120             flagStrings.add("AUTOGROUP_SUMMARY");
4121             flags &= ~FLAG_AUTOGROUP_SUMMARY;
4122         }
4123         if ((flags & FLAG_CAN_COLORIZE) != 0) {
4124             flagStrings.add("CAN_COLORIZE");
4125             flags &= ~FLAG_CAN_COLORIZE;
4126         }
4127         if ((flags & FLAG_BUBBLE) != 0) {
4128             flagStrings.add("BUBBLE");
4129             flags &= ~FLAG_BUBBLE;
4130         }
4131         if ((flags & FLAG_NO_DISMISS) != 0) {
4132             flagStrings.add("NO_DISMISS");
4133             flags &= ~FLAG_NO_DISMISS;
4134         }
4135         if ((flags & FLAG_FSI_REQUESTED_BUT_DENIED) != 0) {
4136             flagStrings.add("FSI_REQUESTED_BUT_DENIED");
4137             flags &= ~FLAG_FSI_REQUESTED_BUT_DENIED;
4138         }
4139         if ((flags & FLAG_USER_INITIATED_JOB) != 0) {
4140             flagStrings.add("USER_INITIATED_JOB");
4141             flags &= ~FLAG_USER_INITIATED_JOB;
4142         }
4143         if (Flags.lifetimeExtensionRefactor()) {
4144             if ((flags & FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY) != 0) {
4145                 flagStrings.add("LIFETIME_EXTENDED_BY_DIRECT_REPLY");
4146                 flags &= ~FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY;
4147             }
4148         }
4149         if (Flags.apiRichOngoing()) {
4150             if ((flags & FLAG_PROMOTED_ONGOING) != 0) {
4151                 flagStrings.add("PROMOTED_ONGOING");
4152                 flags &= ~FLAG_PROMOTED_ONGOING;
4153             }
4154         }
4155 
4156         if (android.service.notification.Flags.notificationSilentFlag()) {
4157             if ((flags & FLAG_SILENT) != 0) {
4158                 flagStrings.add("SILENT");
4159                 flags &= ~FLAG_SILENT;
4160             }
4161         }
4162 
4163         if (flagStrings.isEmpty()) {
4164             return "0";
4165         }
4166 
4167         if (flags != 0) {
4168             flagStrings.add(String.format("UNKNOWN(0x%08x)", flags));
4169         }
4170 
4171         return String.join("|", flagStrings);
4172     }
4173 
4174     /** @hide */
defaultsToString(int defaults)4175     public static String defaultsToString(int defaults) {
4176         final List<String> defaultStrings = new ArrayList<String>();
4177         if ((defaults & DEFAULT_ALL) == DEFAULT_ALL) {
4178             defaultStrings.add("ALL");
4179             defaults &= ~DEFAULT_ALL;
4180         }
4181         if ((defaults & DEFAULT_SOUND) != 0) {
4182             defaultStrings.add("SOUND");
4183             defaults &= ~DEFAULT_SOUND;
4184         }
4185         if ((defaults & DEFAULT_VIBRATE) != 0) {
4186             defaultStrings.add("VIBRATE");
4187             defaults &= ~DEFAULT_VIBRATE;
4188         }
4189         if ((defaults & DEFAULT_LIGHTS) != 0) {
4190             defaultStrings.add("LIGHTS");
4191             defaults &= ~DEFAULT_LIGHTS;
4192         }
4193 
4194         if (defaultStrings.isEmpty()) {
4195             return "0";
4196         }
4197 
4198         if (defaults != 0) {
4199             defaultStrings.add(String.format("UNKNOWN(0x%08x)", defaults));
4200         }
4201 
4202         return String.join("|", defaultStrings);
4203     }
4204 
4205 
4206     /**
4207      * Returns the very short text summarizing the most critical information contained in the
4208      * notification, or null if this field was not set.
4209      */
4210     @Nullable
4211     @FlaggedApi(Flags.FLAG_API_RICH_ONGOING)
getShortCriticalText()4212     public String getShortCriticalText() {
4213         return extras.getString(EXTRA_SHORT_CRITICAL_TEXT);
4214     }
4215 
4216     /**
4217      * @hide
4218      */
isOngoingEvent()4219     public boolean isOngoingEvent() {
4220         return (flags & FLAG_ONGOING_EVENT) != 0;
4221     }
4222 
4223     /**
4224      * @hide
4225      */
hasCompletedProgress()4226     public boolean hasCompletedProgress() {
4227         // not a progress notification; can't be complete
4228         if (!extras.containsKey(EXTRA_PROGRESS)
4229                 || !extras.containsKey(EXTRA_PROGRESS_MAX)) {
4230             return false;
4231         }
4232         // many apps use max 0 for 'indeterminate'; not complete
4233         if (extras.getInt(EXTRA_PROGRESS_MAX) == 0) {
4234             return false;
4235         }
4236         return extras.getInt(EXTRA_PROGRESS) == extras.getInt(EXTRA_PROGRESS_MAX);
4237     }
4238 
4239     /** @removed */
4240     @Deprecated
getChannel()4241     public String getChannel() {
4242         return mChannelId;
4243     }
4244 
4245     /**
4246      * Returns the id of the channel this notification posts to.
4247      */
getChannelId()4248     public String getChannelId() {
4249         return mChannelId;
4250     }
4251 
4252     /** @removed */
4253     @Deprecated
getTimeout()4254     public long getTimeout() {
4255         return mTimeout;
4256     }
4257 
4258     /**
4259      * Returns the duration from posting after which this notification should be canceled by the
4260      * system, if it's not canceled already.
4261      */
getTimeoutAfter()4262     public long getTimeoutAfter() {
4263         return mTimeout;
4264     }
4265 
4266     /**
4267      * @hide
4268      */
setTimeoutAfter(long timeout)4269     public void setTimeoutAfter(long timeout) {
4270         mTimeout = timeout;
4271     }
4272 
4273     /**
4274      * Returns what icon should be shown for this notification if it is being displayed in a
4275      * Launcher that supports badging. Will be one of {@link #BADGE_ICON_NONE},
4276      * {@link #BADGE_ICON_SMALL}, or {@link #BADGE_ICON_LARGE}.
4277      */
getBadgeIconType()4278     public int getBadgeIconType() {
4279         return mBadgeIcon;
4280     }
4281 
4282     /**
4283      * Returns the {@link ShortcutInfo#getId() id} that this notification supersedes, if any.
4284      *
4285      * <p>Used by some Launchers that display notification content to hide shortcuts that duplicate
4286      * notifications.
4287      */
getShortcutId()4288     public String getShortcutId() {
4289         return mShortcutId;
4290     }
4291 
4292     /**
4293      * Gets the {@link LocusId} associated with this notification.
4294      *
4295      * <p>Used by the device's intelligence services to correlate objects (such as
4296      * {@link ShortcutInfo} and {@link ContentCaptureContext}) that are correlated.
4297      */
4298     @Nullable
getLocusId()4299     public LocusId getLocusId() {
4300         return mLocusId;
4301     }
4302 
4303     /**
4304      * Returns the settings text provided to {@link Builder#setSettingsText(CharSequence)}.
4305      */
getSettingsText()4306     public CharSequence getSettingsText() {
4307         return mSettingsText;
4308     }
4309 
4310     /**
4311      * Returns which type of notifications in a group are responsible for audibly alerting the
4312      * user. See {@link #GROUP_ALERT_ALL}, {@link #GROUP_ALERT_CHILDREN},
4313      * {@link #GROUP_ALERT_SUMMARY}.
4314      */
getGroupAlertBehavior()4315     public @GroupAlertBehavior int getGroupAlertBehavior() {
4316         return mGroupAlertBehavior;
4317     }
4318 
4319     /**
4320      * Sets which type of notifications in a group are responsible for audibly alerting the
4321      * user. See {@link #GROUP_ALERT_ALL}, {@link #GROUP_ALERT_CHILDREN},
4322      * {@link #GROUP_ALERT_SUMMARY}.
4323      * @param groupAlertBehavior
4324      * @hide
4325      */
setGroupAlertBehavior(@roupAlertBehavior int groupAlertBehavior)4326     public void setGroupAlertBehavior(@GroupAlertBehavior int groupAlertBehavior) {
4327         mGroupAlertBehavior = groupAlertBehavior;
4328     }
4329 
4330     /**
4331      * Returns the bubble metadata that will be used to display app content in a floating window
4332      * over the existing foreground activity.
4333      */
4334     @Nullable
getBubbleMetadata()4335     public BubbleMetadata getBubbleMetadata() {
4336         return mBubbleMetadata;
4337     }
4338 
4339     /**
4340      * Sets the {@link BubbleMetadata} for this notification.
4341      * @hide
4342      */
setBubbleMetadata(BubbleMetadata data)4343     public void setBubbleMetadata(BubbleMetadata data) {
4344         mBubbleMetadata = data;
4345     }
4346 
4347     /**
4348      * Returns whether the platform is allowed (by the app developer) to generate contextual actions
4349      * for this notification.
4350      */
getAllowSystemGeneratedContextualActions()4351     public boolean getAllowSystemGeneratedContextualActions() {
4352         return mAllowSystemGeneratedContextualActions;
4353     }
4354 
4355     /**
4356      * The small icon representing this notification in the status bar and content view.
4357      *
4358      * @return the small icon representing this notification.
4359      *
4360      * @see Builder#getSmallIcon()
4361      * @see Builder#setSmallIcon(Icon)
4362      */
getSmallIcon()4363     public Icon getSmallIcon() {
4364         return mSmallIcon;
4365     }
4366 
4367     /**
4368      * Used when notifying to clean up legacy small icons.
4369      * @hide
4370      */
4371     @UnsupportedAppUsage
setSmallIcon(Icon icon)4372     public void setSmallIcon(Icon icon) {
4373         mSmallIcon = icon;
4374     }
4375 
4376     /**
4377      * The large icon shown in this notification's content view.
4378      * @see Builder#getLargeIcon()
4379      * @see Builder#setLargeIcon(Icon)
4380      */
getLargeIcon()4381     public Icon getLargeIcon() {
4382         return mLargeIcon;
4383     }
4384 
4385     /**
4386      * @hide
4387      */
hasAppProvidedWhen()4388     public boolean hasAppProvidedWhen() {
4389         return when != 0 && when != creationTime;
4390     }
4391 
4392     /**
4393      * @hide
4394      */
4395     @UnsupportedAppUsage
isGroupSummary()4396     public boolean isGroupSummary() {
4397         return mGroupKey != null && (flags & FLAG_GROUP_SUMMARY) != 0;
4398     }
4399 
4400     /**
4401      * @hide
4402      */
4403     @UnsupportedAppUsage
isGroupChild()4404     public boolean isGroupChild() {
4405         return mGroupKey != null && (flags & FLAG_GROUP_SUMMARY) == 0;
4406     }
4407 
4408     /**
4409      * @hide
4410      */
suppressAlertingDueToGrouping()4411     public boolean suppressAlertingDueToGrouping() {
4412         if (isGroupSummary()
4413                 && getGroupAlertBehavior() == Notification.GROUP_ALERT_CHILDREN) {
4414             return true;
4415         } else if (isGroupChild()
4416                 && getGroupAlertBehavior() == Notification.GROUP_ALERT_SUMMARY) {
4417             return true;
4418         }
4419         return false;
4420     }
4421 
4422 
4423     /**
4424      * Finds and returns a remote input and its corresponding action.
4425      *
4426      * @param requiresFreeform requires the remoteinput to allow freeform or not.
4427      * @return the result pair, {@code null} if no result is found.
4428      */
4429     @Nullable
findRemoteInputActionPair(boolean requiresFreeform)4430     public Pair<RemoteInput, Action> findRemoteInputActionPair(boolean requiresFreeform) {
4431         if (isPromotedOngoing()) {
4432             return null;
4433         }
4434         if (actions == null) {
4435             return null;
4436         }
4437         for (Notification.Action action : actions) {
4438             if (action.getRemoteInputs() == null) {
4439                 continue;
4440             }
4441             RemoteInput resultRemoteInput = null;
4442             for (RemoteInput remoteInput : action.getRemoteInputs()) {
4443                 if (remoteInput.getAllowFreeFormInput() || !requiresFreeform) {
4444                     resultRemoteInput = remoteInput;
4445                 }
4446             }
4447             if (resultRemoteInput != null) {
4448                 return Pair.create(resultRemoteInput, action);
4449             }
4450         }
4451         return null;
4452     }
4453 
4454     /**
4455      * Returns the actions that are contextual (that is, suggested because of the content of the
4456      * notification) out of the actions in this notification.
4457      */
getContextualActions()4458     public @NonNull List<Notification.Action> getContextualActions() {
4459         if (actions == null || isPromotedOngoing()) return Collections.emptyList();
4460         List<Notification.Action> contextualActions = new ArrayList<>();
4461         for (Notification.Action action : actions) {
4462             if (action.isContextual()) {
4463                 contextualActions.add(action);
4464             }
4465         }
4466         return contextualActions;
4467     }
4468 
4469     /**
4470      * Sets the FLAG_SILENT flag to mark the notification as silent and clears the group key.
4471      * @hide
4472      */
fixSilentGroup()4473     public void fixSilentGroup() {
4474         if (android.service.notification.Flags.notificationSilentFlag()) {
4475             if (GROUP_KEY_SILENT.equals(mGroupKey)) {
4476                 mGroupKey = null;
4477                 flags |= FLAG_SILENT;
4478             }
4479         }
4480     }
4481 
4482     /**
4483      * @return whether this notification is silent. See {@link Builder#setSilent()}
4484      * @hide
4485      */
isSilent()4486     public boolean isSilent() {
4487         if (android.service.notification.Flags.notificationSilentFlag()) {
4488             return (flags & Notification.FLAG_SILENT) != 0;
4489         } else {
4490             return GROUP_KEY_SILENT.equals(getGroup()) && suppressAlertingDueToGrouping();
4491         }
4492     }
4493 
4494     /**
4495      * Builder class for {@link Notification} objects.
4496      *
4497      * Provides a convenient way to set the various fields of a {@link Notification} and generate
4498      * content views using the platform's notification layout template. If your app supports
4499      * versions of Android as old as API level 4, you can instead use
4500      * {@link androidx.core.app.NotificationCompat.Builder NotificationCompat.Builder},
4501      * available in the <a href="{@docRoot}tools/extras/support-library.html">Android Support
4502      * library</a>.
4503      *
4504      * <p>Example:
4505      *
4506      * <pre class="prettyprint">
4507      * Notification noti = new Notification.Builder(mContext)
4508      *         .setContentTitle(&quot;New mail from &quot; + sender.toString())
4509      *         .setContentText(subject)
4510      *         .setSmallIcon(R.drawable.new_mail)
4511      *         .setLargeIcon(aBitmap)
4512      *         .build();
4513      * </pre>
4514      */
4515     public static class Builder {
4516         /**
4517          * @hide
4518          */
4519         public static final String EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT =
4520                 "android.rebuild.contentViewActionCount";
4521         /**
4522          * @hide
4523          */
4524         public static final String EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT
4525                 = "android.rebuild.bigViewActionCount";
4526         /**
4527          * @hide
4528          */
4529         public static final String EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT
4530                 = "android.rebuild.hudViewActionCount";
4531 
4532         private static final boolean USE_ONLY_TITLE_IN_LOW_PRIORITY_SUMMARY =
4533                 SystemProperties.getBoolean("notifications.only_title", true);
4534 
4535         private Context mContext;
4536         private Notification mN;
4537         private Bundle mUserExtras = new Bundle();
4538         private Style mStyle;
4539         @UnsupportedAppUsage
4540         private ArrayList<Action> mActions = new ArrayList<>(MAX_ACTION_BUTTONS);
4541         private ArrayList<Person> mPersonList = new ArrayList<>();
4542         private ContrastColorUtil mColorUtil;
4543         private boolean mIsLegacy;
4544         private boolean mIsLegacyInitialized;
4545 
4546         /**
4547          * Caches an instance of StandardTemplateParams. Note that this may have been used before,
4548          * so make sure to call {@link StandardTemplateParams#reset()} before using it.
4549          */
4550         StandardTemplateParams mParams = new StandardTemplateParams();
4551         Colors mColors = new Colors();
4552 
4553         private boolean mTintActionButtons;
4554         private boolean mInNightMode;
4555 
4556         /**
4557          * Constructs a new Builder with the defaults:
4558          *
4559          * @param context
4560          *            A {@link Context} that will be used by the Builder to construct the
4561          *            RemoteViews. The Context will not be held past the lifetime of this Builder
4562          *            object.
4563          * @param channelId
4564          *            The constructed Notification will be posted on this
4565          *            {@link NotificationChannel}. To use a NotificationChannel, it must first be
4566          *            created using {@link NotificationManager#createNotificationChannel}.
4567          */
Builder(Context context, String channelId)4568         public Builder(Context context, String channelId) {
4569             this(context, (Notification) null);
4570             mN.mChannelId = channelId;
4571         }
4572 
4573         /**
4574          * @deprecated use {@link #Builder(Context, String)}
4575          * instead. All posted Notifications must specify a NotificationChannel Id.
4576          */
4577         @Deprecated
Builder(Context context)4578         public Builder(Context context) {
4579             this(context, (Notification) null);
4580         }
4581 
4582         /**
4583          * @hide
4584          */
Builder(Context context, Notification toAdopt)4585         public Builder(Context context, Notification toAdopt) {
4586             mContext = context;
4587             Resources res = mContext.getResources();
4588             mTintActionButtons = res.getBoolean(R.bool.config_tintNotificationActionButtons);
4589 
4590             if (res.getBoolean(R.bool.config_enableNightMode)) {
4591                 Configuration currentConfig = res.getConfiguration();
4592                 mInNightMode = (currentConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK)
4593                         == Configuration.UI_MODE_NIGHT_YES;
4594             }
4595 
4596             if (toAdopt == null) {
4597                 mN = new Notification();
4598                 if (context.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N) {
4599                     mN.extras.putBoolean(EXTRA_SHOW_WHEN, true);
4600                 }
4601                 mN.priority = PRIORITY_DEFAULT;
4602                 mN.visibility = VISIBILITY_PRIVATE;
4603             } else {
4604                 mN = toAdopt;
4605                 if (mN.actions != null) {
4606                     Collections.addAll(mActions, mN.actions);
4607                 }
4608 
4609                 if (mN.extras.containsKey(EXTRA_PEOPLE_LIST)) {
4610                     ArrayList<Person> people = mN.extras.getParcelableArrayList(EXTRA_PEOPLE_LIST,
4611                             android.app.Person.class);
4612                     if (people != null && !people.isEmpty()) {
4613                         mPersonList.addAll(people);
4614                     }
4615                 }
4616 
4617                 if (mN.getSmallIcon() == null && mN.icon != 0) {
4618                     setSmallIcon(mN.icon);
4619                 }
4620 
4621                 if (mN.getLargeIcon() == null && mN.largeIcon != null) {
4622                     setLargeIcon(mN.largeIcon);
4623                 }
4624 
4625                 String templateClass = mN.extras.getString(EXTRA_TEMPLATE);
4626                 if (!TextUtils.isEmpty(templateClass)) {
4627                     final Class<? extends Style> styleClass
4628                             = getNotificationStyleClass(templateClass);
4629                     if (styleClass == null) {
4630                         Log.d(TAG, "Unknown style class: " + templateClass);
4631                     } else {
4632                         try {
4633                             final Constructor<? extends Style> ctor =
4634                                     styleClass.getDeclaredConstructor();
4635                             ctor.setAccessible(true);
4636                             final Style style = ctor.newInstance();
4637                             style.restoreFromExtras(mN.extras);
4638 
4639                             if (style != null) {
4640                                 setStyle(style);
4641                             }
4642                         } catch (Throwable t) {
4643                             Log.e(TAG, "Could not create Style", t);
4644                         }
4645                     }
4646                 }
4647             }
4648         }
4649 
getColorUtil()4650         private ContrastColorUtil getColorUtil() {
4651             if (mColorUtil == null) {
4652                 mColorUtil = ContrastColorUtil.getInstance(mContext);
4653             }
4654             return mColorUtil;
4655         }
4656 
4657         /**
4658          * From Android 11, messaging notifications (those that use {@link MessagingStyle}) that
4659          * use this method to link to a published long-lived sharing shortcut may appear in a
4660          * dedicated Conversation section of the shade and may show configuration options that
4661          * are unique to conversations. This behavior should be reserved for person to person(s)
4662          * conversations where there is a likely social obligation for an individual to respond.
4663          * <p>
4664          * For example, the following are some examples of notifications that belong in the
4665          * conversation space:
4666          * <ul>
4667          * <li>1:1 conversations between two individuals</li>
4668          * <li>Group conversations between individuals where everyone can contribute</li>
4669          * </ul>
4670          * And the following are some examples of notifications that do not belong in the
4671          * conversation space:
4672          * <ul>
4673          * <li>Advertisements from a bot (even if personal and contextualized)</li>
4674          * <li>Engagement notifications from a bot</li>
4675          * <li>Directional conversations where there is an active speaker and many passive
4676          * individuals</li>
4677          * <li>Stream / posting updates from other individuals</li>
4678          * <li>Email, document comments, or other conversation types that are not real-time</li>
4679          * </ul>
4680          * </p>
4681          *
4682          * <p>
4683          * Additionally, this method can be used for all types of notifications to mark this
4684          * notification as duplicative of a Launcher shortcut. Launchers that show badges or
4685          * notification content may then suppress the shortcut in favor of the content of this
4686          * notification.
4687          * <p>
4688          * If this notification has {@link BubbleMetadata} attached that was created with
4689          * a shortcutId a check will be performed to ensure the shortcutId supplied to bubble
4690          * metadata matches the shortcutId set here, if one was set. If the shortcutId's were
4691          * specified but do not match, an exception is thrown.
4692          *
4693          * @param shortcutId the {@link ShortcutInfo#getId() id} of the shortcut this notification
4694          *                   is linked to
4695          *
4696          * @see BubbleMetadata.Builder#Builder(String)
4697          */
4698         @NonNull
setShortcutId(String shortcutId)4699         public Builder setShortcutId(String shortcutId) {
4700             mN.mShortcutId = shortcutId;
4701             return this;
4702         }
4703 
4704         /**
4705          * Sets the {@link LocusId} associated with this notification.
4706          *
4707          * <p>This method should be called when the {@link LocusId} is used in other places (such
4708          * as {@link ShortcutInfo} and {@link ContentCaptureContext}) so the device's intelligence
4709          * services can correlate them.
4710          */
4711         @NonNull
setLocusId(@ullable LocusId locusId)4712         public Builder setLocusId(@Nullable LocusId locusId) {
4713             mN.mLocusId = locusId;
4714             return this;
4715         }
4716 
4717         /**
4718          * Sets which icon to display as a badge for this notification.
4719          *
4720          * Must be one of {@link #BADGE_ICON_NONE}, {@link #BADGE_ICON_SMALL},
4721          * {@link #BADGE_ICON_LARGE}.
4722          *
4723          * Note: This value might be ignored, for launchers that don't support badge icons.
4724          */
4725         @NonNull
setBadgeIconType(int icon)4726         public Builder setBadgeIconType(int icon) {
4727             mN.mBadgeIcon = icon;
4728             return this;
4729         }
4730 
4731         /**
4732          * Sets the group alert behavior for this notification. Use this method to mute this
4733          * notification if alerts for this notification's group should be handled by a different
4734          * notification. This is only applicable for notifications that belong to a
4735          * {@link #setGroup(String) group}. This must be called on all notifications you want to
4736          * mute. For example, if you want only the summary of your group to make noise and/or peek
4737          * on screen, all children in the group should have the group alert behavior
4738          * {@link #GROUP_ALERT_SUMMARY}.
4739          *
4740          * <p> The default value is {@link #GROUP_ALERT_ALL}.</p>
4741          */
4742         @NonNull
setGroupAlertBehavior(@roupAlertBehavior int groupAlertBehavior)4743         public Builder setGroupAlertBehavior(@GroupAlertBehavior int groupAlertBehavior) {
4744             mN.mGroupAlertBehavior = groupAlertBehavior;
4745             return this;
4746         }
4747 
4748         /**
4749          * Sets the {@link BubbleMetadata} that will be used to display app content in a floating
4750          * window over the existing foreground activity.
4751          *
4752          * <p>This data will be ignored unless the notification is posted to a channel that
4753          * allows {@link NotificationChannel#canBubble() bubbles}.</p>
4754          *
4755          * <p>Notifications allowed to bubble that have valid bubble metadata will display in
4756          * collapsed state outside of the notification shade on unlocked devices. When a user
4757          * interacts with the collapsed state, the bubble intent will be invoked and displayed.</p>
4758          */
4759         @NonNull
setBubbleMetadata(@ullable BubbleMetadata data)4760         public Builder setBubbleMetadata(@Nullable BubbleMetadata data) {
4761             mN.mBubbleMetadata = data;
4762             return this;
4763         }
4764 
4765         /** @removed */
4766         @Deprecated
setChannel(String channelId)4767         public Builder setChannel(String channelId) {
4768             mN.mChannelId = channelId;
4769             return this;
4770         }
4771 
4772         /**
4773          * Specifies the channel the notification should be delivered on.
4774          */
4775         @NonNull
setChannelId(String channelId)4776         public Builder setChannelId(String channelId) {
4777             mN.mChannelId = channelId;
4778             return this;
4779         }
4780 
4781         /** @removed */
4782         @Deprecated
setTimeout(long durationMs)4783         public Builder setTimeout(long durationMs) {
4784             mN.mTimeout = durationMs;
4785             return this;
4786         }
4787 
4788         /**
4789          * Specifies a duration in milliseconds after which this notification should be canceled,
4790          * if it is not already canceled.
4791          */
4792         @NonNull
setTimeoutAfter(long durationMs)4793         public Builder setTimeoutAfter(long durationMs) {
4794             mN.mTimeout = durationMs;
4795             return this;
4796         }
4797 
4798         /**
4799          * Add a timestamp pertaining to the notification (usually the time the event occurred).
4800          *
4801          * @see Notification#when
4802          */
4803         @NonNull
setWhen(long when)4804         public Builder setWhen(long when) {
4805             mN.when = when;
4806             return this;
4807         }
4808 
4809         /**
4810          * Control whether the timestamp set with {@link #setWhen(long) setWhen} is shown
4811          * in the content view.
4812          */
4813         @NonNull
setShowWhen(boolean show)4814         public Builder setShowWhen(boolean show) {
4815             mN.extras.putBoolean(EXTRA_SHOW_WHEN, show);
4816             return this;
4817         }
4818 
4819         /**
4820          * Show the {@link Notification#when} field as a stopwatch.
4821          *
4822          * Instead of presenting <code>when</code> as a timestamp, the notification will show an
4823          * automatically updating display of the minutes and seconds since <code>when</code>.
4824          *
4825          * Useful when showing an elapsed time (like an ongoing phone call).
4826          *
4827          * The counter can also be set to count down to <code>when</code> when using
4828          * {@link #setChronometerCountDown(boolean)}.
4829          *
4830          * @see android.widget.Chronometer
4831          * @see Notification#when
4832          * @see #setChronometerCountDown(boolean)
4833          */
4834         @NonNull
setUsesChronometer(boolean b)4835         public Builder setUsesChronometer(boolean b) {
4836             mN.extras.putBoolean(EXTRA_SHOW_CHRONOMETER, b);
4837             return this;
4838         }
4839 
4840         /**
4841          * Sets the Chronometer to count down instead of counting up.
4842          *
4843          * <p>This is only relevant if {@link #setUsesChronometer(boolean)} has been set to true.
4844          * If it isn't set the chronometer will count up.
4845          *
4846          * @see #setUsesChronometer(boolean)
4847          */
4848         @NonNull
setChronometerCountDown(boolean countDown)4849         public Builder setChronometerCountDown(boolean countDown) {
4850             mN.extras.putBoolean(EXTRA_CHRONOMETER_COUNT_DOWN, countDown);
4851             return this;
4852         }
4853 
4854         /**
4855          * Set the small icon resource, which will be used to represent the notification in the
4856          * status bar.
4857          *
4858 
4859          * The platform template for the expanded view will draw this icon in the left, unless a
4860          * {@link #setLargeIcon(Bitmap) large icon} has also been specified, in which case the small
4861          * icon will be moved to the right-hand side.
4862          *
4863 
4864          * @param icon
4865          *            A resource ID in the application's package of the drawable to use.
4866          * @see Notification#icon
4867          */
4868         @NonNull
setSmallIcon(@rawableRes int icon)4869         public Builder setSmallIcon(@DrawableRes int icon) {
4870             return setSmallIcon(icon != 0
4871                     ? Icon.createWithResource(mContext, icon)
4872                     : null);
4873         }
4874 
4875         /**
4876          * A variant of {@link #setSmallIcon(int) setSmallIcon(int)} that takes an additional
4877          * level parameter for when the icon is a {@link android.graphics.drawable.LevelListDrawable
4878          * LevelListDrawable}.
4879          *
4880          * @param icon A resource ID in the application's package of the drawable to use.
4881          * @param level The level to use for the icon.
4882          *
4883          * @see Notification#icon
4884          * @see Notification#iconLevel
4885          */
4886         @NonNull
setSmallIcon(@rawableRes int icon, int level)4887         public Builder setSmallIcon(@DrawableRes int icon, int level) {
4888             mN.iconLevel = level;
4889             return setSmallIcon(icon);
4890         }
4891 
4892         /**
4893          * Set the small icon, which will be used to represent the notification in the
4894          * status bar and content view (unless overridden there by a
4895          * {@link #setLargeIcon(Bitmap) large icon}).
4896          *
4897          * @param icon An Icon object to use.
4898          * @see Notification#icon
4899          */
4900         @NonNull
setSmallIcon(Icon icon)4901         public Builder setSmallIcon(Icon icon) {
4902             mN.setSmallIcon(icon);
4903             if (icon != null && icon.getType() == Icon.TYPE_RESOURCE) {
4904                 mN.icon = icon.getResId();
4905             }
4906             return this;
4907         }
4908 
4909         /**
4910          * If {@code true}, silences this instance of the notification, regardless of the sounds or
4911          * vibrations set on the notification or notification channel. If {@code false}, then the
4912          * normal sound and vibration logic applies.
4913          *
4914          * @hide
4915          */
setSilent(boolean silent)4916         public @NonNull Builder setSilent(boolean silent) {
4917             if (!silent) {
4918                 return this;
4919             }
4920             if (mN.isGroupSummary()) {
4921                 setGroupAlertBehavior(GROUP_ALERT_CHILDREN);
4922             } else {
4923                 setGroupAlertBehavior(GROUP_ALERT_SUMMARY);
4924             }
4925 
4926             setVibrate(null);
4927             setSound(null);
4928             mN.defaults &= ~DEFAULT_SOUND;
4929             mN.defaults &= ~DEFAULT_VIBRATE;
4930             setDefaults(mN.defaults);
4931 
4932             if (android.service.notification.Flags.notificationSilentFlag()) {
4933                 mN.flags |= FLAG_SILENT;
4934             } else {
4935                 if (TextUtils.isEmpty(mN.mGroupKey)) {
4936                     setGroup(GROUP_KEY_SILENT);
4937                 }
4938             }
4939             return this;
4940         }
4941 
4942         /**
4943          * Set the first line of text in the platform notification template.
4944          */
4945         @NonNull
setContentTitle(CharSequence title)4946         public Builder setContentTitle(CharSequence title) {
4947             mN.extras.putCharSequence(EXTRA_TITLE, safeCharSequence(title));
4948             return this;
4949         }
4950 
4951         /**
4952          * Set the second line of text in the platform notification template.
4953          */
4954         @NonNull
setContentText(CharSequence text)4955         public Builder setContentText(CharSequence text) {
4956             mN.extras.putCharSequence(EXTRA_TEXT, safeCharSequence(text));
4957             return this;
4958         }
4959 
4960         /**
4961          * This provides some additional information that is displayed in the notification. No
4962          * guarantees are given where exactly it is displayed.
4963          *
4964          * <p>This information should only be provided if it provides an essential
4965          * benefit to the understanding of the notification. The more text you provide the
4966          * less readable it becomes. For example, an email client should only provide the account
4967          * name here if more than one email account has been added.</p>
4968          *
4969          * <p>As of {@link android.os.Build.VERSION_CODES#N} this information is displayed in the
4970          * notification header area.
4971          *
4972          * On Android versions before {@link android.os.Build.VERSION_CODES#N}
4973          * this will be shown in the third line of text in the platform notification template.
4974          * You should not be using {@link #setProgress(int, int, boolean)} at the
4975          * same time on those versions; they occupy the same place.
4976          * </p>
4977          */
4978         @NonNull
setSubText(CharSequence text)4979         public Builder setSubText(CharSequence text) {
4980             mN.extras.putCharSequence(EXTRA_SUB_TEXT, safeCharSequence(text));
4981             return this;
4982         }
4983 
4984         /**
4985          * Provides text that will appear as a link to your application's settings.
4986          *
4987          * <p>This text does not appear within notification {@link Style templates} but may
4988          * appear when the user uses an affordance to learn more about the notification.
4989          * Additionally, this text will not appear unless you provide a valid link target by
4990          * handling {@link #INTENT_CATEGORY_NOTIFICATION_PREFERENCES}.
4991          *
4992          * <p>This text is meant to be concise description about what the user can customize
4993          * when they click on this link. The recommended maximum length is 40 characters.
4994          * @param text
4995          * @return
4996          */
4997         @NonNull
setSettingsText(CharSequence text)4998         public Builder setSettingsText(CharSequence text) {
4999             mN.mSettingsText = safeCharSequence(text);
5000             return this;
5001         }
5002 
5003         /**
5004          * Set the remote input history.
5005          *
5006          * This should be set to the most recent inputs that have been sent
5007          * through a {@link RemoteInput} of this Notification and cleared once the it is no
5008          * longer relevant (e.g. for chat notifications once the other party has responded).
5009          *
5010          * The most recent input must be stored at the 0 index, the second most recent at the
5011          * 1 index, etc. Note that the system will limit both how far back the inputs will be shown
5012          * and how much of each individual input is shown.
5013          *
5014          * <p>Note: The reply text will only be shown on notifications that have least one action
5015          * with a {@code RemoteInput}.</p>
5016          */
5017         @NonNull
setRemoteInputHistory(CharSequence[] text)5018         public Builder setRemoteInputHistory(CharSequence[] text) {
5019             if (text == null) {
5020                 mN.extras.putCharSequenceArray(EXTRA_REMOTE_INPUT_HISTORY, null);
5021             } else {
5022                 final int itemCount = Math.min(MAX_REPLY_HISTORY, text.length);
5023                 CharSequence[] safe = new CharSequence[itemCount];
5024                 RemoteInputHistoryItem[] items = new RemoteInputHistoryItem[itemCount];
5025                 for (int i = 0; i < itemCount; i++) {
5026                     safe[i] = safeCharSequence(text[i]);
5027                     items[i] = new RemoteInputHistoryItem(text[i]);
5028                 }
5029                 mN.extras.putCharSequenceArray(EXTRA_REMOTE_INPUT_HISTORY, safe);
5030 
5031                 // Also add these messages as structured history items.
5032                 mN.extras.putParcelableArray(EXTRA_REMOTE_INPUT_HISTORY_ITEMS, items);
5033             }
5034             return this;
5035         }
5036 
5037         /**
5038          * Set the remote input history, with support for embedding URIs and mime types for
5039          * images and other media.
5040          * @hide
5041          */
5042         @NonNull
setRemoteInputHistory(RemoteInputHistoryItem[] items)5043         public Builder setRemoteInputHistory(RemoteInputHistoryItem[] items) {
5044             if (items == null) {
5045                 mN.extras.putParcelableArray(EXTRA_REMOTE_INPUT_HISTORY_ITEMS, null);
5046             } else {
5047                 final int itemCount = Math.min(MAX_REPLY_HISTORY, items.length);
5048                 RemoteInputHistoryItem[] history = new RemoteInputHistoryItem[itemCount];
5049                 for (int i = 0; i < itemCount; i++) {
5050                     history[i] = items[i];
5051                 }
5052                 mN.extras.putParcelableArray(EXTRA_REMOTE_INPUT_HISTORY_ITEMS, history);
5053             }
5054             return this;
5055         }
5056 
5057         /**
5058          * Sets whether remote history entries view should have a spinner.
5059          * @hide
5060          */
5061         @NonNull
setShowRemoteInputSpinner(boolean showSpinner)5062         public Builder setShowRemoteInputSpinner(boolean showSpinner) {
5063             mN.extras.putBoolean(EXTRA_SHOW_REMOTE_INPUT_SPINNER, showSpinner);
5064             return this;
5065         }
5066 
5067         /**
5068          * Sets whether smart reply buttons should be hidden.
5069          * @hide
5070          */
5071         @NonNull
setHideSmartReplies(boolean hideSmartReplies)5072         public Builder setHideSmartReplies(boolean hideSmartReplies) {
5073             mN.extras.putBoolean(EXTRA_HIDE_SMART_REPLIES, hideSmartReplies);
5074             return this;
5075         }
5076 
5077         /**
5078          * Sets the number of items this notification represents. May be displayed as a badge count
5079          * for Launchers that support badging.
5080          */
5081         @NonNull
setNumber(int number)5082         public Builder setNumber(int number) {
5083             mN.number = number;
5084             return this;
5085         }
5086 
5087         /**
5088          * A small piece of additional information pertaining to this notification.
5089          *
5090          * The platform template will draw this on the last line of the notification, at the far
5091          * right (to the right of a smallIcon if it has been placed there).
5092          *
5093          * @deprecated use {@link #setSubText(CharSequence)} instead to set a text in the header.
5094          * For legacy apps targeting a version below {@link android.os.Build.VERSION_CODES#N} this
5095          * field will still show up, but the subtext will take precedence.
5096          */
5097         @Deprecated
setContentInfo(CharSequence info)5098         public Builder setContentInfo(CharSequence info) {
5099             mN.extras.putCharSequence(EXTRA_INFO_TEXT, safeCharSequence(info));
5100             return this;
5101         }
5102 
5103         /**
5104          * Sets a very short string summarizing the most critical information contained in the
5105          * notification. Suggested max length is 7 characters, and there is no guarantee how much or
5106          * how little of this text will be shown.
5107          */
5108         @FlaggedApi(Flags.FLAG_API_RICH_ONGOING)
5109         @NonNull
setShortCriticalText(@ullable String shortCriticalText)5110         public Builder setShortCriticalText(@Nullable String shortCriticalText) {
5111             mN.extras.putString(EXTRA_SHORT_CRITICAL_TEXT, safeString(shortCriticalText));
5112             return this;
5113         }
5114 
5115         /**
5116          * Set the progress this notification represents.
5117          *
5118          * The platform template will represent this using a {@link ProgressBar}.
5119          */
5120         @NonNull
setProgress(int max, int progress, boolean indeterminate)5121         public Builder setProgress(int max, int progress, boolean indeterminate) {
5122             mN.extras.putInt(EXTRA_PROGRESS, progress);
5123             mN.extras.putInt(EXTRA_PROGRESS_MAX, max);
5124             mN.extras.putBoolean(EXTRA_PROGRESS_INDETERMINATE, indeterminate);
5125             return this;
5126         }
5127 
5128         /**
5129          * Supply a custom RemoteViews to use instead of the platform template.
5130          *
5131          * Use {@link #setCustomContentView(RemoteViews)} instead.
5132          */
5133         @Deprecated
setContent(RemoteViews views)5134         public Builder setContent(RemoteViews views) {
5135             return setCustomContentView(views);
5136         }
5137 
5138         /**
5139          * Supply custom RemoteViews to use instead of the platform template.
5140          *
5141          * This will override the layout that would otherwise be constructed by this Builder
5142          * object.
5143          */
5144         @NonNull
setCustomContentView(RemoteViews contentView)5145         public Builder setCustomContentView(RemoteViews contentView) {
5146             mN.contentView = contentView;
5147             return this;
5148         }
5149 
5150         /**
5151          * Supply custom RemoteViews to use instead of the platform template in the expanded form.
5152          *
5153          * This will override the expanded layout that would otherwise be constructed by this
5154          * Builder object.
5155          */
5156         @NonNull
setCustomBigContentView(RemoteViews contentView)5157         public Builder setCustomBigContentView(RemoteViews contentView) {
5158             mN.bigContentView = contentView;
5159             return this;
5160         }
5161 
5162         /**
5163          * Supply custom RemoteViews to use instead of the platform template in the heads up dialog.
5164          *
5165          * This will override the heads-up layout that would otherwise be constructed by this
5166          * Builder object.
5167          */
5168         @NonNull
setCustomHeadsUpContentView(RemoteViews contentView)5169         public Builder setCustomHeadsUpContentView(RemoteViews contentView) {
5170             mN.headsUpContentView = contentView;
5171             return this;
5172         }
5173 
5174         /**
5175          * Supply a {@link PendingIntent} to be sent when the notification is clicked.
5176          *
5177          * <p>As of Android {@link android.os.Build.VERSION_CODES#S}, apps targeting API level
5178          * {@link android.os.Build.VERSION_CODES#S} or higher won't be able to start activities
5179          * while processing broadcast receivers or services in response to notification clicks. To
5180          * launch an activity in those cases, provide a {@link PendingIntent} for the activity
5181          * itself.
5182          *
5183          * <p>As of {@link android.os.Build.VERSION_CODES#HONEYCOMB}, if this field is unset and you
5184          * have specified a custom RemoteViews with {@link #setContent(RemoteViews)}, you can use
5185          * {@link RemoteViews#setOnClickPendingIntent RemoteViews.setOnClickPendingIntent(int,PendingIntent)}
5186          * to assign PendingIntents to individual views in that custom layout (i.e., to create
5187          * clickable buttons inside the notification view).
5188          *
5189          * @see Notification#contentIntent Notification.contentIntent
5190          */
5191         @NonNull
setContentIntent(PendingIntent intent)5192         public Builder setContentIntent(PendingIntent intent) {
5193             mN.contentIntent = intent;
5194             return this;
5195         }
5196 
5197         /**
5198          * Supply a {@link PendingIntent} to send when the notification is cleared explicitly by the user.
5199          *
5200          * @see Notification#deleteIntent
5201          */
5202         @NonNull
setDeleteIntent(PendingIntent intent)5203         public Builder setDeleteIntent(PendingIntent intent) {
5204             mN.deleteIntent = intent;
5205             return this;
5206         }
5207 
5208         /**
5209          * An intent to launch instead of posting the notification to the status bar.
5210          * Only for use with extremely high-priority notifications demanding the user's
5211          * <strong>immediate</strong> attention, such as an incoming phone call or
5212          * alarm clock that the user has explicitly set to a particular time.
5213          * If this facility is used for something else, please give the user an option
5214          * to turn it off and use a normal notification, as this can be extremely
5215          * disruptive.
5216          *
5217          * <p>Apps targeting {@link Build.VERSION_CODES#Q} and above will have to request
5218          * a permission ({@link android.Manifest.permission#USE_FULL_SCREEN_INTENT}) in order to
5219          * use full screen intents. </p>
5220          * <p>
5221          * Prior to {@link Build.VERSION_CODES#TIRAMISU}, the system may display a
5222          * heads up notification (which may display on screen longer than other heads up
5223          * notifications), instead of launching the intent, while the user is using the device.
5224          * From {@link Build.VERSION_CODES#TIRAMISU},
5225          * the system UI will display a heads up notification, instead of launching this intent,
5226          * while the user is using the device. This notification will display with emphasized
5227          * action buttons. If the posting app holds
5228          * {@link android.Manifest.permission#USE_FULL_SCREEN_INTENT}, then the heads
5229          * up notification will appear persistently until the user dismisses or snoozes it, or
5230          * the app cancels it. If the posting app does not hold
5231          * {@link android.Manifest.permission#USE_FULL_SCREEN_INTENT}, then the notification will
5232          * appear as heads up notification even when the screen is locked or turned off, and this
5233          * notification will only be persistent for 60 seconds.
5234          * </p>
5235          * <p>
5236          * To be launched as a full screen intent, the notification must also be posted to a
5237          * channel with importance level set to IMPORTANCE_HIGH or higher.
5238          * </p>
5239          *
5240          * @param intent The pending intent to launch.
5241          * @param highPriority Passing true will cause this notification to be sent
5242          *          even if other notifications are suppressed.
5243          *
5244          * @see Notification#fullScreenIntent
5245          */
5246         @NonNull
5247         @RequiresPermission(android.Manifest.permission.USE_FULL_SCREEN_INTENT)
setFullScreenIntent(PendingIntent intent, boolean highPriority)5248         public Builder setFullScreenIntent(PendingIntent intent, boolean highPriority) {
5249             mN.fullScreenIntent = intent;
5250             setFlag(FLAG_HIGH_PRIORITY, highPriority);
5251             return this;
5252         }
5253 
5254         /**
5255          * Set the "ticker" text which is sent to accessibility services.
5256          *
5257          * @see Notification#tickerText
5258          */
5259         @NonNull
setTicker(CharSequence tickerText)5260         public Builder setTicker(CharSequence tickerText) {
5261             mN.tickerText = safeCharSequence(tickerText);
5262             return this;
5263         }
5264 
5265         /**
5266          * Obsolete version of {@link #setTicker(CharSequence)}.
5267          *
5268          */
5269         @Deprecated
setTicker(CharSequence tickerText, RemoteViews views)5270         public Builder setTicker(CharSequence tickerText, RemoteViews views) {
5271             setTicker(tickerText);
5272             // views is ignored
5273             return this;
5274         }
5275 
5276         /**
5277          * Add a large icon to the notification content view.
5278          *
5279          * In the platform template, this image will be shown either on the right of the
5280          * notification, with an aspect ratio of up to 16:9, or (when the notification is grouped)
5281          * on the left in place of the {@link #setSmallIcon(Icon) small icon}.
5282          */
5283         @NonNull
setLargeIcon(Bitmap b)5284         public Builder setLargeIcon(Bitmap b) {
5285             return setLargeIcon(b != null ? Icon.createWithBitmap(b) : null);
5286         }
5287 
5288         /**
5289          * Add a large icon to the notification content view.
5290          *
5291          * In the platform template, this image will be shown either on the right of the
5292          * notification, with an aspect ratio of up to 16:9, or (when the notification is grouped)
5293          * on the left in place of the {@link #setSmallIcon(Icon) small icon}.
5294          */
5295         @NonNull
setLargeIcon(Icon icon)5296         public Builder setLargeIcon(Icon icon) {
5297             mN.mLargeIcon = icon;
5298             mN.extras.putParcelable(EXTRA_LARGE_ICON, icon);
5299             return this;
5300         }
5301 
5302         /**
5303          * Set the sound to play.
5304          *
5305          * It will be played using the {@link #AUDIO_ATTRIBUTES_DEFAULT default audio attributes}
5306          * for notifications.
5307          *
5308          * @deprecated use {@link NotificationChannel#setSound(Uri, AudioAttributes)} instead.
5309          */
5310         @Deprecated
setSound(Uri sound)5311         public Builder setSound(Uri sound) {
5312             mN.sound = sound;
5313             mN.audioAttributes = AUDIO_ATTRIBUTES_DEFAULT;
5314             return this;
5315         }
5316 
5317         /**
5318          * Set the sound to play, along with a specific stream on which to play it.
5319          *
5320          * See {@link android.media.AudioManager} for the <code>STREAM_</code> constants.
5321          *
5322          * @deprecated use {@link NotificationChannel#setSound(Uri, AudioAttributes)}.
5323          */
5324         @Deprecated
setSound(Uri sound, int streamType)5325         public Builder setSound(Uri sound, int streamType) {
5326             PlayerBase.deprecateStreamTypeForPlayback(streamType, "Notification", "setSound()");
5327             mN.sound = sound;
5328             mN.audioStreamType = streamType;
5329             return this;
5330         }
5331 
5332         /**
5333          * Set the sound to play, along with specific {@link AudioAttributes audio attributes} to
5334          * use during playback.
5335          *
5336          * @deprecated use {@link NotificationChannel#setSound(Uri, AudioAttributes)} instead.
5337          * @see Notification#sound
5338          */
5339         @Deprecated
setSound(Uri sound, AudioAttributes audioAttributes)5340         public Builder setSound(Uri sound, AudioAttributes audioAttributes) {
5341             mN.sound = sound;
5342             mN.audioAttributes = audioAttributes;
5343             return this;
5344         }
5345 
5346         /**
5347          * Set the vibration pattern to use.
5348          *
5349          * See {@link android.os.Vibrator#vibrate(long[], int)} for a discussion of the
5350          * <code>pattern</code> parameter.
5351          *
5352          * <p>
5353          * A notification that vibrates is more likely to be presented as a heads-up notification.
5354          * </p>
5355          *
5356          * @deprecated use {@link NotificationChannel#setVibrationPattern(long[])} instead.
5357          * @see Notification#vibrate
5358          */
5359         @Deprecated
setVibrate(long[] pattern)5360         public Builder setVibrate(long[] pattern) {
5361             mN.vibrate = pattern;
5362             return this;
5363         }
5364 
5365         /**
5366          * Set the desired color for the indicator LED on the device, as well as the
5367          * blink duty cycle (specified in milliseconds).
5368          *
5369 
5370          * Not all devices will honor all (or even any) of these values.
5371          *
5372          * @deprecated use {@link NotificationChannel#enableLights(boolean)} instead.
5373          * @see Notification#ledARGB
5374          * @see Notification#ledOnMS
5375          * @see Notification#ledOffMS
5376          */
5377         @Deprecated
setLights(@olorInt int argb, int onMs, int offMs)5378         public Builder setLights(@ColorInt int argb, int onMs, int offMs) {
5379             mN.ledARGB = argb;
5380             mN.ledOnMS = onMs;
5381             mN.ledOffMS = offMs;
5382             if (onMs != 0 || offMs != 0) {
5383                 mN.flags |= FLAG_SHOW_LIGHTS;
5384             }
5385             return this;
5386         }
5387 
5388         /**
5389          * Set whether this is an "ongoing" notification.
5390          *
5391          * Ongoing notifications cannot be dismissed by the user on locked devices, or by
5392          * notification listeners, and some notifications (call, device management, media) cannot
5393          * be dismissed on unlocked devices, so your application or service must take care of
5394          * canceling them.
5395          *
5396          * They are typically used to indicate a background task that the user is actively engaged
5397          * with (e.g., playing music) or is pending in some way and therefore occupying the device
5398          * (e.g., a file download, sync operation, active network connection).
5399          *
5400          * @see Notification#FLAG_ONGOING_EVENT
5401          */
5402         @NonNull
setOngoing(boolean ongoing)5403         public Builder setOngoing(boolean ongoing) {
5404             setFlag(FLAG_ONGOING_EVENT, ongoing);
5405             return this;
5406         }
5407 
5408         /**
5409          * Set whether this notification should be colorized. When set, the color set with
5410          * {@link #setColor(int)} will be used as the background color of this notification.
5411          * <p>
5412          * This should only be used for high priority ongoing tasks like navigation, an ongoing
5413          * call, or other similarly high-priority events for the user.
5414          * <p>
5415          * For most styles, the coloring will only be applied if the notification is for a
5416          * foreground service notification.
5417          * However, for {@link MediaStyle} and {@link DecoratedMediaCustomViewStyle} notifications
5418          * that have a media session attached there is no such requirement.
5419          *
5420          * @see #setColor(int)
5421          * @see MediaStyle#setMediaSession(MediaSession.Token)
5422          */
5423         @NonNull
setColorized(boolean colorize)5424         public Builder setColorized(boolean colorize) {
5425             mN.extras.putBoolean(EXTRA_COLORIZED, colorize);
5426             return this;
5427         }
5428 
5429         /**
5430          * Set this flag if you would only like the sound, vibrate
5431          * and ticker to be played if the notification is not already showing.
5432          *
5433          * Note that using this flag will stop any ongoing alerting behaviour such
5434          * as sound, vibration or blinking notification LED.
5435          *
5436          * @see Notification#FLAG_ONLY_ALERT_ONCE
5437          */
5438         @NonNull
setOnlyAlertOnce(boolean onlyAlertOnce)5439         public Builder setOnlyAlertOnce(boolean onlyAlertOnce) {
5440             setFlag(FLAG_ONLY_ALERT_ONCE, onlyAlertOnce);
5441             return this;
5442         }
5443 
5444         /**
5445          * Specify a desired visibility policy for a Notification associated with a
5446          * foreground service.  By default, the system can choose to defer
5447          * visibility of the notification for a short time after the service is
5448          * started.  Pass
5449          * {@link Notification#FOREGROUND_SERVICE_IMMEDIATE FOREGROUND_SERVICE_IMMEDIATE}
5450          * to this method in order to guarantee that visibility is never deferred.  Pass
5451          * {@link Notification#FOREGROUND_SERVICE_DEFERRED FOREGROUND_SERVICE_DEFERRED}
5452          * to request that visibility is deferred whenever possible.
5453          *
5454          * <p class="note">Note that deferred visibility is not guaranteed.  There
5455          * may be some circumstances under which the system will show the foreground
5456          * service's associated Notification immediately even when the app has used
5457          * this method to explicitly request deferred display.</p>
5458          * @param behavior One of
5459          * {@link Notification#FOREGROUND_SERVICE_DEFAULT FOREGROUND_SERVICE_DEFAULT},
5460          * {@link Notification#FOREGROUND_SERVICE_IMMEDIATE FOREGROUND_SERVICE_IMMEDIATE},
5461          * or {@link Notification#FOREGROUND_SERVICE_DEFERRED FOREGROUND_SERVICE_DEFERRED}
5462          * @return
5463          */
5464         @NonNull
setForegroundServiceBehavior(@erviceNotificationPolicy int behavior)5465         public Builder setForegroundServiceBehavior(@ServiceNotificationPolicy int behavior) {
5466             mN.mFgsDeferBehavior = behavior;
5467             return this;
5468         }
5469 
5470         /**
5471          * Make this notification automatically dismissed when the user touches it.
5472          *
5473          * @see Notification#FLAG_AUTO_CANCEL
5474          */
5475         @NonNull
setAutoCancel(boolean autoCancel)5476         public Builder setAutoCancel(boolean autoCancel) {
5477             setFlag(FLAG_AUTO_CANCEL, autoCancel);
5478             return this;
5479         }
5480 
5481         /**
5482          * Set whether or not this notification should not bridge to other devices.
5483          *
5484          * <p>Some notifications can be bridged to other devices for remote display.
5485          * This hint can be set to recommend this notification not be bridged.
5486          */
5487         @NonNull
setLocalOnly(boolean localOnly)5488         public Builder setLocalOnly(boolean localOnly) {
5489             setFlag(FLAG_LOCAL_ONLY, localOnly);
5490             return this;
5491         }
5492 
5493         /**
5494          * Set which notification properties will be inherited from system defaults.
5495          * <p>
5496          * The value should be one or more of the following fields combined with
5497          * bitwise-or:
5498          * {@link #DEFAULT_SOUND}, {@link #DEFAULT_VIBRATE}, {@link #DEFAULT_LIGHTS}.
5499          * <p>
5500          * For all default values, use {@link #DEFAULT_ALL}.
5501          *
5502          * @deprecated use {@link NotificationChannel#enableVibration(boolean)} and
5503          * {@link NotificationChannel#enableLights(boolean)} and
5504          * {@link NotificationChannel#setSound(Uri, AudioAttributes)} instead.
5505          */
5506         @Deprecated
setDefaults(int defaults)5507         public Builder setDefaults(int defaults) {
5508             mN.defaults = defaults;
5509             return this;
5510         }
5511 
5512         /**
5513          * Set the priority of this notification.
5514          *
5515          * @see Notification#priority
5516          * @deprecated use {@link NotificationChannel#setImportance(int)} instead.
5517          */
5518         @Deprecated
setPriority(@riority int pri)5519         public Builder setPriority(@Priority int pri) {
5520             mN.priority = pri;
5521             return this;
5522         }
5523 
5524         /**
5525          * Set the notification category.
5526          *
5527          * @see Notification#category
5528          */
5529         @NonNull
setCategory(String category)5530         public Builder setCategory(String category) {
5531             mN.category = category;
5532             return this;
5533         }
5534 
5535         /**
5536          * Add a person that is relevant to this notification.
5537          *
5538          * <P>
5539          * Depending on user preferences, this annotation may allow the notification to pass
5540          * through interruption filters, if this notification is of category {@link #CATEGORY_CALL}
5541          * or {@link #CATEGORY_MESSAGE}. The addition of people may also cause this notification to
5542          * appear more prominently in the user interface.
5543          * </P>
5544          *
5545          * <P>
5546          * The person should be specified by the {@code String} representation of a
5547          * {@link android.provider.ContactsContract.Contacts#CONTENT_LOOKUP_URI}.
5548          * </P>
5549          *
5550          * <P>The system will also attempt to resolve {@code mailto:} and {@code tel:} schema
5551          * URIs.  The path part of these URIs must exist in the contacts database, in the
5552          * appropriate column, or the reference will be discarded as invalid. Telephone schema
5553          * URIs will be resolved by {@link android.provider.ContactsContract.PhoneLookup}.
5554          * It is also possible to provide a URI with the schema {@code name:} in order to uniquely
5555          * identify a person without an entry in the contacts database.
5556          * </P>
5557          *
5558          * @param uri A URI for the person.
5559          * @see Notification#EXTRA_PEOPLE
5560          * @deprecated use {@link #addPerson(Person)}
5561          */
addPerson(String uri)5562         public Builder addPerson(String uri) {
5563             addPerson(new Person.Builder().setUri(uri).build());
5564             return this;
5565         }
5566 
5567         /**
5568          * Add a person that is relevant to this notification.
5569          *
5570          * <P>
5571          * Depending on user preferences, this annotation may allow the notification to pass
5572          * through interruption filters, if this notification is of category {@link #CATEGORY_CALL}
5573          * or {@link #CATEGORY_MESSAGE}. The addition of people may also cause this notification to
5574          * appear more prominently in the user interface.
5575          * </P>
5576          *
5577          * <P>
5578          * A person should usually contain a uri in order to benefit from the ranking boost.
5579          * However, even if no uri is provided, it's beneficial to provide other people in the
5580          * notification, such that listeners and voice only devices can announce and handle them
5581          * properly.
5582          * </P>
5583          *
5584          * @param person the person to add.
5585          * @see Notification#EXTRA_PEOPLE_LIST
5586          */
5587         @NonNull
addPerson(Person person)5588         public Builder addPerson(Person person) {
5589             mPersonList.add(person);
5590             return this;
5591         }
5592 
5593         /**
5594          * Set this notification to be part of a group of notifications sharing the same key.
5595          * Grouped notifications may display in a cluster or stack on devices which
5596          * support such rendering.
5597          *
5598          * <p>To make this notification the summary for its group, also call
5599          * {@link #setGroupSummary}. A sort order can be specified for group members by using
5600          * {@link #setSortKey}.
5601          * @param groupKey The group key of the group.
5602          * @return this object for method chaining
5603          */
5604         @NonNull
setGroup(String groupKey)5605         public Builder setGroup(String groupKey) {
5606             mN.mGroupKey = groupKey;
5607             return this;
5608         }
5609 
5610         /**
5611          * Set this notification to be the group summary for a group of notifications.
5612          * Grouped notifications may display in a cluster or stack on devices which
5613          * support such rendering. If thereRequires a group key also be set using {@link #setGroup}.
5614          * The group summary may be suppressed if too few notifications are included in the group.
5615          * @param isGroupSummary Whether this notification should be a group summary.
5616          * @return this object for method chaining
5617          */
5618         @NonNull
setGroupSummary(boolean isGroupSummary)5619         public Builder setGroupSummary(boolean isGroupSummary) {
5620             setFlag(FLAG_GROUP_SUMMARY, isGroupSummary);
5621             return this;
5622         }
5623 
5624         /**
5625          * Set a sort key that orders this notification among other notifications from the
5626          * same package. This can be useful if an external sort was already applied and an app
5627          * would like to preserve this. Notifications will be sorted lexicographically using this
5628          * value, although providing different priorities in addition to providing sort key may
5629          * cause this value to be ignored.
5630          *
5631          * <p>This sort key can also be used to order members of a notification group. See
5632          * {@link #setGroup}.
5633          *
5634          * @see String#compareTo(String)
5635          */
5636         @NonNull
setSortKey(String sortKey)5637         public Builder setSortKey(String sortKey) {
5638             mN.mSortKey = sortKey;
5639             return this;
5640         }
5641 
5642         /**
5643          * Merge additional metadata into this notification.
5644          *
5645          * <p>Values within the Bundle will replace existing extras values in this Builder.
5646          *
5647          * @see Notification#extras
5648          */
5649         @NonNull
addExtras(Bundle extras)5650         public Builder addExtras(Bundle extras) {
5651             if (extras != null) {
5652                 mUserExtras.putAll(extras);
5653             }
5654             return this;
5655         }
5656 
5657         /**
5658          * Set metadata for this notification.
5659          *
5660          * <p>A reference to the Bundle is held for the lifetime of this Builder, and the Bundle's
5661          * current contents are copied into the Notification each time {@link #build()} is
5662          * called.
5663          *
5664          * <p>Replaces any existing extras values with those from the provided Bundle.
5665          * Use {@link #addExtras} to merge in metadata instead.
5666          *
5667          * @see Notification#extras
5668          */
5669         @NonNull
setExtras(Bundle extras)5670         public Builder setExtras(Bundle extras) {
5671             if (extras != null) {
5672                 mUserExtras = extras;
5673             }
5674             return this;
5675         }
5676 
5677         /**
5678          * Get the current metadata Bundle used by this notification Builder.
5679          *
5680          * <p>The returned Bundle is shared with this Builder.
5681          *
5682          * <p>The current contents of this Bundle are copied into the Notification each time
5683          * {@link #build()} is called.
5684          *
5685          * @see Notification#extras
5686          */
getExtras()5687         public Bundle getExtras() {
5688             return mUserExtras;
5689         }
5690 
5691         /**
5692          * Add an action to this notification. Actions are typically displayed by
5693          * the system as a button adjacent to the notification content.
5694          * <p>
5695          * Every action must have an icon (32dp square and matching the
5696          * <a href="{@docRoot}design/style/iconography.html#action-bar">Holo
5697          * Dark action bar</a> visual style), a textual label, and a {@link PendingIntent}.
5698          * <p>
5699          * A notification in its expanded form can display up to 3 actions, from left to right in
5700          * the order they were added. Actions will not be displayed when the notification is
5701          * collapsed, however, so be sure that any essential functions may be accessed by the user
5702          * in some other way (for example, in the Activity pointed to by {@link #contentIntent}).
5703          * <p>
5704          * As of Android {@link android.os.Build.VERSION_CODES#S}, apps targeting API level
5705          * {@link android.os.Build.VERSION_CODES#S} or higher won't be able to start activities
5706          * while processing broadcast receivers or services in response to notification action
5707          * clicks. To launch an activity in those cases, provide a {@link PendingIntent} to the
5708          * activity itself.
5709          * <p>
5710          * As of Android {@link android.os.Build.VERSION_CODES#N},
5711          * action button icons will not be displayed on action buttons, but are still required
5712          * and are available to
5713          * {@link android.service.notification.NotificationListenerService notification listeners},
5714          * which may display them in other contexts, for example on a wearable device.
5715          *
5716          * @param icon Resource ID of a drawable that represents the action.
5717          * @param title Text describing the action.
5718          * @param intent PendingIntent to be fired when the action is invoked.
5719          *
5720          * @deprecated Use {@link #addAction(Action)} instead.
5721          */
5722         @Deprecated
addAction(int icon, CharSequence title, PendingIntent intent)5723         public Builder addAction(int icon, CharSequence title, PendingIntent intent) {
5724             mActions.add(new Action(icon, safeCharSequence(title), intent));
5725             return this;
5726         }
5727 
5728         /**
5729          * Add an action to this notification. Actions are typically displayed by
5730          * the system as a button adjacent to the notification content.
5731          * <p>
5732          * Every action must have an icon (32dp square and matching the
5733          * <a href="{@docRoot}design/style/iconography.html#action-bar">Holo
5734          * Dark action bar</a> visual style), a textual label, and a {@link PendingIntent}.
5735          * <p>
5736          * A notification in its expanded form can display up to 3 actions, from left to right in
5737          * the order they were added. Actions will not be displayed when the notification is
5738          * collapsed, however, so be sure that any essential functions may be accessed by the user
5739          * in some other way (for example, in the Activity pointed to by {@link #contentIntent}).
5740          *
5741          * @param action The action to add.
5742          */
5743         @NonNull
addAction(Action action)5744         public Builder addAction(Action action) {
5745             if (action != null) {
5746                 mActions.add(action);
5747             }
5748             return this;
5749         }
5750 
5751         /**
5752          * Alter the complete list of actions attached to this notification.
5753          * @see #addAction(Action).
5754          *
5755          * @param actions
5756          * @return
5757          */
5758         @NonNull
setActions(Action... actions)5759         public Builder setActions(Action... actions) {
5760             mActions.clear();
5761             for (int i = 0; i < actions.length; i++) {
5762                 if (actions[i] != null) {
5763                     mActions.add(actions[i]);
5764                 }
5765             }
5766             return this;
5767         }
5768 
5769         /**
5770          * Add a rich notification style to be applied at build time.
5771          *
5772          * @param style Object responsible for modifying the notification style.
5773          */
5774         @NonNull
setStyle(Style style)5775         public Builder setStyle(Style style) {
5776             if (mStyle != style) {
5777                 mStyle = style;
5778                 if (mStyle != null) {
5779                     mStyle.setBuilder(this);
5780                     mN.extras.putString(EXTRA_TEMPLATE, style.getClass().getName());
5781                 }  else {
5782                     mN.extras.remove(EXTRA_TEMPLATE);
5783                 }
5784             }
5785             return this;
5786         }
5787 
5788         /**
5789          * Returns the style set by {@link #setStyle(Style)}.
5790          */
getStyle()5791         public Style getStyle() {
5792             return mStyle;
5793         }
5794 
5795         /**
5796          * Specify the value of {@link #visibility}.
5797          *
5798          * @return The same Builder.
5799          */
5800         @NonNull
setVisibility(@isibility int visibility)5801         public Builder setVisibility(@Visibility int visibility) {
5802             mN.visibility = visibility;
5803             return this;
5804         }
5805 
5806         /**
5807          * Supply a replacement Notification whose contents should be shown in insecure contexts
5808          * (i.e. atop the secure lockscreen). See {@link #visibility} and {@link #VISIBILITY_PUBLIC}.
5809          * @param n A replacement notification, presumably with some or all info redacted.
5810          * @return The same Builder.
5811          */
5812         @NonNull
setPublicVersion(Notification n)5813         public Builder setPublicVersion(Notification n) {
5814             if (n != null) {
5815                 mN.publicVersion = new Notification();
5816                 n.cloneInto(mN.publicVersion, /*heavy=*/ true);
5817             } else {
5818                 mN.publicVersion = null;
5819             }
5820             return this;
5821         }
5822 
5823         /**
5824          * Apply an extender to this notification builder. Extenders may be used to add
5825          * metadata or change options on this builder.
5826          */
5827         @NonNull
extend(Extender extender)5828         public Builder extend(Extender extender) {
5829             extender.extend(this);
5830             return this;
5831         }
5832 
5833         /**
5834          * Set the value for a notification flag
5835          *
5836          * @param mask Bit mask of the flag
5837          * @param value Status (on/off) of the flag
5838          *
5839          * @return The same Builder.
5840          */
5841         @NonNull
setFlag(@otificationFlags int mask, boolean value)5842         public Builder setFlag(@NotificationFlags int mask, boolean value) {
5843             if (value) {
5844                 mN.flags |= mask;
5845             } else {
5846                 mN.flags &= ~mask;
5847             }
5848             return this;
5849         }
5850 
5851         /**
5852          * Sets {@link Notification#color}.
5853          *
5854          * @param argb The accent color to use
5855          *
5856          * @return The same Builder.
5857          */
5858         @NonNull
setColor(@olorInt int argb)5859         public Builder setColor(@ColorInt int argb) {
5860             mN.color = argb;
5861             sanitizeColor();
5862             return this;
5863         }
5864 
bindPhishingAlertIcon(RemoteViews contentView, StandardTemplateParams p)5865         private void bindPhishingAlertIcon(RemoteViews contentView, StandardTemplateParams p) {
5866             contentView.setDrawableTint(
5867                     R.id.phishing_alert,
5868                     false /* targetBackground */,
5869                     getColors(p).getErrorColor(),
5870                     PorterDuff.Mode.SRC_ATOP);
5871         }
5872 
getProfileBadgeDrawable()5873         private Drawable getProfileBadgeDrawable() {
5874             if (mContext.getUserId() == UserHandle.USER_SYSTEM) {
5875                 // This user can never be a badged profile,
5876                 // and also includes USER_ALL system notifications.
5877                 return null;
5878             }
5879             // Note: This assumes that the current user can read the profile badge of the
5880             // originating user.
5881             DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
5882             return dpm.getResources().getDrawable(
5883                     getUpdatableProfileBadgeId(), SOLID_COLORED, NOTIFICATION,
5884                     this::getDefaultProfileBadgeDrawable);
5885         }
5886 
getUpdatableProfileBadgeId()5887         private String getUpdatableProfileBadgeId() {
5888             return mContext.getSystemService(UserManager.class).isManagedProfile()
5889                     ? WORK_PROFILE_ICON : UNDEFINED;
5890         }
5891 
getDefaultProfileBadgeDrawable()5892         private Drawable getDefaultProfileBadgeDrawable() {
5893             return mContext.getPackageManager().getUserBadgeForDensityNoBackground(
5894                     new UserHandle(mContext.getUserId()), 0);
5895         }
5896 
getProfileBadge()5897         private Bitmap getProfileBadge() {
5898             Drawable badge = getProfileBadgeDrawable();
5899             if (badge == null) {
5900                 return null;
5901             }
5902             final int size = mContext.getResources().getDimensionPixelSize(
5903                     Flags.notificationsRedesignTemplates()
5904                             ? R.dimen.notification_2025_badge_size
5905                             : R.dimen.notification_badge_size);
5906             Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
5907             Canvas canvas = new Canvas(bitmap);
5908             badge.setBounds(0, 0, size, size);
5909             badge.draw(canvas);
5910             return bitmap;
5911         }
5912 
bindProfileBadge(RemoteViews contentView, StandardTemplateParams p)5913         private void bindProfileBadge(RemoteViews contentView, StandardTemplateParams p) {
5914             Bitmap profileBadge = getProfileBadge();
5915 
5916             if (profileBadge != null) {
5917                 contentView.setImageViewBitmap(R.id.profile_badge, profileBadge);
5918                 contentView.setViewVisibility(R.id.profile_badge, View.VISIBLE);
5919                 if (isBackgroundColorized(p)) {
5920                     contentView.setDrawableTint(R.id.profile_badge, false,
5921                             getPrimaryTextColor(p), PorterDuff.Mode.SRC_ATOP);
5922                 }
5923                 contentView.setContentDescription(
5924                         R.id.profile_badge,
5925                         mContext.getSystemService(UserManager.class)
5926                                 .getProfileAccessibilityString(mContext.getUserId()));
5927             }
5928         }
5929 
bindAlertedIcon(RemoteViews contentView, StandardTemplateParams p)5930         private void bindAlertedIcon(RemoteViews contentView, StandardTemplateParams p) {
5931             contentView.setDrawableTint(
5932                     R.id.alerted_icon,
5933                     false /* targetBackground */,
5934                     getColors(p).getSecondaryTextColor(),
5935                     PorterDuff.Mode.SRC_IN);
5936         }
5937 
5938         /**
5939          * @hide
5940          */
usesStandardHeader()5941         public boolean usesStandardHeader() {
5942             if (mN.mUsesStandardHeader) {
5943                 return true;
5944             }
5945             if (mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.N) {
5946                 if (mN.contentView == null && mN.bigContentView == null) {
5947                     return true;
5948                 }
5949             }
5950             boolean contentViewUsesHeader = mN.contentView == null
5951                     || isStandardLayout(mN.contentView.getLayoutId());
5952             boolean bigContentViewUsesHeader = mN.bigContentView == null
5953                     || isStandardLayout(mN.bigContentView.getLayoutId());
5954             return contentViewUsesHeader && bigContentViewUsesHeader;
5955         }
5956 
resetStandardTemplate(RemoteViews contentView)5957         private void resetStandardTemplate(RemoteViews contentView) {
5958             resetNotificationHeader(contentView);
5959             contentView.setViewVisibility(R.id.right_icon, View.GONE);
5960             contentView.setViewVisibility(R.id.title, View.GONE);
5961             contentView.setTextViewText(R.id.title, null);
5962             contentView.setViewVisibility(R.id.text, View.GONE);
5963             contentView.setTextViewText(R.id.text, null);
5964         }
5965 
5966         /**
5967          * Resets the notification header to its original state
5968          */
resetNotificationHeader(RemoteViews contentView)5969         private void resetNotificationHeader(RemoteViews contentView) {
5970             // Small icon doesn't need to be reset, as it's always set. Resetting would prevent
5971             // re-using the drawable when the notification is updated.
5972             contentView.setBoolean(R.id.expand_button, "setExpanded", false);
5973             contentView.setViewVisibility(R.id.app_name_text, View.GONE);
5974             contentView.setTextViewText(R.id.app_name_text, null);
5975             contentView.setViewVisibility(R.id.chronometer, View.GONE);
5976             contentView.setViewVisibility(R.id.header_text, View.GONE);
5977             contentView.setTextViewText(R.id.header_text, null);
5978             contentView.setViewVisibility(R.id.header_text_secondary, View.GONE);
5979             contentView.setTextViewText(R.id.header_text_secondary, null);
5980             contentView.setViewVisibility(R.id.header_text_divider, View.GONE);
5981             contentView.setViewVisibility(R.id.header_text_secondary_divider, View.GONE);
5982             contentView.setViewVisibility(R.id.time_divider, View.GONE);
5983             contentView.setViewVisibility(R.id.time, View.GONE);
5984             contentView.setImageViewIcon(R.id.profile_badge, null);
5985             contentView.setViewVisibility(R.id.profile_badge, View.GONE);
5986             mN.mUsesStandardHeader = false;
5987         }
5988 
applyStandardTemplate(int resId, StandardTemplateParams p, TemplateBindResult result)5989         private RemoteViews applyStandardTemplate(int resId, StandardTemplateParams p,
5990                 TemplateBindResult result) {
5991             p.headerless(resId == getCollapsedBaseLayoutResource()
5992                     || resId == getHeadsUpBaseLayoutResource()
5993                     || resId == getCompactHeadsUpBaseLayoutResource()
5994                     || resId == getMessagingCompactHeadsUpLayoutResource()
5995                     || resId == getCollapsedMessagingLayoutResource()
5996                     || resId == getCollapsedMediaLayoutResource()
5997                     || resId == getCollapsedConversationLayoutResource()
5998                     || (notificationsRedesignTemplates()
5999                     && resId == getCollapsedCallLayoutResource()));
6000             RemoteViews contentView = new BuilderRemoteViews(mContext.getApplicationInfo(), resId);
6001 
6002             resetStandardTemplate(contentView);
6003 
6004             final Bundle ex = mN.extras;
6005             updateBackgroundColor(contentView, p);
6006             bindNotificationHeader(contentView, p);
6007             bindLargeIconAndApplyMargin(contentView, p, result);
6008             boolean showProgress = handleProgressBar(contentView, ex, p);
6009             boolean hasSecondLine = showProgress;
6010             if (p.hasTitle()) {
6011                 contentView.setViewVisibility(p.mTitleViewId, View.VISIBLE);
6012                 contentView.setTextViewText(p.mTitleViewId,
6013                         ensureColorSpanContrastOrStripStyling(p.mTitle, p));
6014                 setTextViewColorPrimary(contentView, p.mTitleViewId, p);
6015             } else if (p.mTitleViewId != R.id.title) {
6016                 // This alternate title view ID is not cleared by resetStandardTemplate
6017                 contentView.setViewVisibility(p.mTitleViewId, View.GONE);
6018                 contentView.setTextViewText(p.mTitleViewId, null);
6019             }
6020             if (p.mText != null && p.mText.length() != 0
6021                     && (!showProgress || p.mAllowTextWithProgress)) {
6022                 contentView.setViewVisibility(p.mTextViewId, View.VISIBLE);
6023                 contentView.setTextViewText(p.mTextViewId,
6024                         ensureColorSpanContrastOrStripStyling(p.mText, p));
6025                 setTextViewColorSecondary(contentView, p.mTextViewId, p);
6026                 hasSecondLine = true;
6027             } else if (p.mTextViewId != R.id.text) {
6028                 // This alternate text view ID is not cleared by resetStandardTemplate
6029                 contentView.setViewVisibility(p.mTextViewId, View.GONE);
6030                 contentView.setTextViewText(p.mTextViewId, null);
6031             }
6032 
6033             updateExpanderAlignment(contentView, p, hasSecondLine);
6034             setHeaderlessVerticalMargins(contentView, p, hasSecondLine);
6035 
6036             // Update margins to leave space for the top line (but not for headerless views like
6037             // HUNS, which use a different layout that already accounts for that). Templates that
6038             // have content that will be displayed under the small icon also use a different margin.
6039             if (Flags.notificationsRedesignTemplates() && !p.mHeaderless) {
6040                 int margin = getContentMarginTop(mContext,
6041                         R.dimen.notification_2025_content_margin_top);
6042                 contentView.setViewLayoutMargin(R.id.notification_main_column,
6043                         RemoteViews.MARGIN_TOP, margin, COMPLEX_UNIT_PX);
6044             }
6045 
6046             return contentView;
6047         }
6048 
updateExpanderAlignment(RemoteViews contentView, StandardTemplateParams p, boolean hasSecondLine)6049         private static void updateExpanderAlignment(RemoteViews contentView,
6050                 StandardTemplateParams p, boolean hasSecondLine) {
6051             if (notificationsRedesignTemplates() && p.mHeaderless) {
6052                 if (!hasSecondLine) {
6053                     // If there's no text, let's center the expand button vertically to align things
6054                     // more nicely. This is handled separately for notifications that use a
6055                     // NotificationHeaderView, see NotificationHeaderView#centerTopLine.
6056                     contentView.setViewLayoutHeight(R.id.expand_button, MATCH_PARENT,
6057                             COMPLEX_UNIT_PX);
6058                 } else {
6059                     // Otherwise, just use the default height for the button to keep it top-aligned.
6060                     contentView.setViewLayoutHeight(R.id.expand_button, WRAP_CONTENT,
6061                             COMPLEX_UNIT_PX);
6062                 }
6063             }
6064         }
6065 
setHeaderlessVerticalMargins(RemoteViews contentView, StandardTemplateParams p, boolean hasSecondLine)6066         private static void setHeaderlessVerticalMargins(RemoteViews contentView,
6067                 StandardTemplateParams p, boolean hasSecondLine) {
6068             if (Flags.notificationsRedesignTemplates() || !p.mHeaderless) {
6069                 return;
6070             }
6071             int marginDimen = hasSecondLine
6072                     ? R.dimen.notification_headerless_margin_twoline
6073                     : R.dimen.notification_headerless_margin_oneline;
6074             contentView.setViewLayoutMarginDimen(R.id.notification_headerless_view_column,
6075                     RemoteViews.MARGIN_TOP, marginDimen);
6076             contentView.setViewLayoutMarginDimen(R.id.notification_headerless_view_column,
6077                     RemoteViews.MARGIN_BOTTOM, marginDimen);
6078         }
6079 
setTextViewColorPrimary(RemoteViews contentView, @IdRes int id, StandardTemplateParams p)6080         private void setTextViewColorPrimary(RemoteViews contentView, @IdRes int id,
6081                 StandardTemplateParams p) {
6082             contentView.setTextColor(id, getPrimaryTextColor(p));
6083         }
6084 
6085         /**
6086          * @param p the template params to inflate this with
6087          * @return the primary text color
6088          * @hide
6089          */
6090         @VisibleForTesting
getPrimaryTextColor(StandardTemplateParams p)6091         public @ColorInt int getPrimaryTextColor(StandardTemplateParams p) {
6092             return getColors(p).getPrimaryTextColor();
6093         }
6094 
6095         /**
6096          * @param p the template params to inflate this with
6097          * @return the secondary text color
6098          * @hide
6099          */
6100         @VisibleForTesting
getSecondaryTextColor(StandardTemplateParams p)6101         public @ColorInt int getSecondaryTextColor(StandardTemplateParams p) {
6102             return getColors(p).getSecondaryTextColor();
6103         }
6104 
setTextViewColorSecondary(RemoteViews contentView, @IdRes int id, StandardTemplateParams p)6105         private void setTextViewColorSecondary(RemoteViews contentView, @IdRes int id,
6106                 StandardTemplateParams p) {
6107             contentView.setTextColor(id, getSecondaryTextColor(p));
6108         }
6109 
getColors(StandardTemplateParams p)6110         private Colors getColors(StandardTemplateParams p) {
6111             mColors.resolvePalette(mContext, mN.color, isBackgroundColorized(p), mInNightMode);
6112             return mColors;
6113         }
6114 
6115         /**
6116          * @param isHeader If the notification is a notification header
6117          * @return An instance of mColors after resolving the palette
6118          * @hide
6119          */
getColors(boolean isHeader)6120         public Colors getColors(boolean isHeader) {
6121             mColors.resolvePalette(mContext, mN.color, !isHeader && mN.isColorized(), mInNightMode);
6122             return mColors;
6123         }
6124 
updateHeaderBackgroundColor(RemoteViews contentView, StandardTemplateParams p)6125         private void updateHeaderBackgroundColor(RemoteViews contentView,
6126                 StandardTemplateParams p) {
6127             if (!Flags.uiRichOngoing()) {
6128                 return;
6129             }
6130             if (isBackgroundColorized(p)) {
6131                 contentView.setInt(R.id.notification_header, "setBackgroundColor",
6132                         getBackgroundColor(p));
6133             } else {
6134                 // Clear it!
6135                 contentView.setInt(R.id.notification_header, "setBackgroundResource",
6136                         0);
6137             }
6138         }
6139 
updateBackgroundColor(RemoteViews contentView, StandardTemplateParams p)6140         private void updateBackgroundColor(RemoteViews contentView,
6141                 StandardTemplateParams p) {
6142             if (isBackgroundColorized(p)) {
6143                 contentView.setInt(R.id.status_bar_latest_event_content, "setBackgroundColor",
6144                         getBackgroundColor(p));
6145             } else {
6146                 // Clear it!
6147                 contentView.setInt(R.id.status_bar_latest_event_content, "setBackgroundResource",
6148                         0);
6149             }
6150         }
6151 
handleProgressBar(RemoteViews contentView, Bundle ex, StandardTemplateParams p)6152         private boolean handleProgressBar(RemoteViews contentView, Bundle ex,
6153                 StandardTemplateParams p) {
6154             final int max = ex.getInt(EXTRA_PROGRESS_MAX, 0);
6155             final int progress = ex.getInt(EXTRA_PROGRESS, 0);
6156             final boolean ind = ex.getBoolean(EXTRA_PROGRESS_INDETERMINATE);
6157             if (!p.mHideProgress && (max != 0 || ind)) {
6158                 contentView.setViewVisibility(com.android.internal.R.id.progress, View.VISIBLE);
6159                 contentView.setProgressBar(R.id.progress, max, progress, ind);
6160                 contentView.setProgressBackgroundTintList(R.id.progress,
6161                         mContext.getColorStateList(R.color.notification_progress_background_color));
6162                 ColorStateList progressTint = ColorStateList.valueOf(getPrimaryAccentColor(p));
6163                 contentView.setProgressTintList(R.id.progress, progressTint);
6164                 contentView.setProgressIndeterminateTintList(R.id.progress, progressTint);
6165                 return true;
6166             } else {
6167                 contentView.setViewVisibility(R.id.progress, View.GONE);
6168                 return false;
6169             }
6170         }
6171 
bindLargeIconAndApplyMargin(RemoteViews contentView, @NonNull StandardTemplateParams p, @Nullable TemplateBindResult result)6172         private void bindLargeIconAndApplyMargin(RemoteViews contentView,
6173                 @NonNull StandardTemplateParams p,
6174                 @Nullable TemplateBindResult result) {
6175             if (result == null) {
6176                 result = new TemplateBindResult();
6177             }
6178             bindLargeIcon(contentView, p, result);
6179             if (!p.mHeaderless) {
6180                 // views in states with a header (big states)
6181                 result.mHeadingExtraMarginSet.applyToView(contentView, R.id.notification_header);
6182                 result.mTitleMarginSet.applyToView(contentView, R.id.title);
6183                 // If there is no title, the text (or big_text) needs to wrap around the image
6184                 result.mTitleMarginSet.applyToView(contentView, p.mTextViewId);
6185                 contentView.setInt(p.mTextViewId, "setNumIndentLines", p.hasTitle() ? 0 : 1);
6186             }
6187             // The expand button uses paddings rather than margins, so we'll adjust it
6188             // separately.
6189             adjustExpandButtonPadding(contentView, result.mRightIconVisible);
6190         }
6191 
adjustExpandButtonPadding(RemoteViews contentView, boolean rightIconVisible)6192         private void adjustExpandButtonPadding(RemoteViews contentView, boolean rightIconVisible) {
6193             if (notificationsRedesignTemplates()) {
6194                 final Resources res = mContext.getResources();
6195                 int normalPadding = res.getDimensionPixelSize(R.dimen.notification_2025_margin);
6196                 int iconSpacing = res.getDimensionPixelSize(
6197                         R.dimen.notification_2025_expand_button_right_icon_spacing);
6198                 contentView.setInt(R.id.expand_button, "setStartPadding",
6199                         rightIconVisible ? iconSpacing : normalPadding);
6200             }
6201         }
6202 
6203         // This code is executed on behalf of other apps' notifications, sometimes even by 3p apps,
6204         // a use case that is not supported by the Compat Framework library.  Workarounds to resolve
6205         // the change's state in NotificationManagerService were very complex. These behavior
6206         // changes are entirely visual, and should otherwise be undetectable by apps.
6207         @SuppressWarnings("AndroidFrameworkCompatChange")
calculateRightIconDimens(Icon rightIcon, boolean isPromotedPicture, @NonNull TemplateBindResult result)6208         private void calculateRightIconDimens(Icon rightIcon, boolean isPromotedPicture,
6209                 @NonNull TemplateBindResult result) {
6210             final Resources resources = mContext.getResources();
6211             final float density = resources.getDisplayMetrics().density;
6212             int iconMarginId = notificationsRedesignTemplates()
6213                     ? R.dimen.notification_2025_right_icon_content_margin
6214                     : R.dimen.notification_right_icon_content_margin;
6215             final float iconMarginDp = resources.getDimension(iconMarginId) / density;
6216             final float contentMarginDp = resources.getDimension(
6217                     R.dimen.notification_content_margin_end) / density;
6218             float spaceForExpanderDp;
6219             if (notificationsRedesignTemplates()) {
6220                 spaceForExpanderDp = resources.getDimension(
6221                         R.dimen.notification_2025_right_icon_expanded_margin_end) / density
6222                         - contentMarginDp;
6223             } else {
6224                 spaceForExpanderDp = resources.getDimension(
6225                         R.dimen.notification_header_expand_icon_size) / density - contentMarginDp;
6226             }
6227             final float viewHeightDp = resources.getDimension(
6228                     R.dimen.notification_right_icon_size) / density;
6229             float viewWidthDp = viewHeightDp;  // icons are 1:1 by default
6230             if (rightIcon != null && (isPromotedPicture
6231                     || mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.S)) {
6232                 Drawable drawable = rightIcon.loadDrawable(mContext);
6233                 if (drawable != null) {
6234                     int iconWidth = drawable.getIntrinsicWidth();
6235                     int iconHeight = drawable.getIntrinsicHeight();
6236                     if (iconWidth > iconHeight && iconHeight > 0) {
6237                         final float maxViewWidthDp = viewHeightDp * MAX_LARGE_ICON_ASPECT_RATIO;
6238                         viewWidthDp = Math.min(viewHeightDp * iconWidth / iconHeight,
6239                                 maxViewWidthDp);
6240                     }
6241                 }
6242             }
6243             // Margin needed for the header to accommodate the icon when shown
6244             final float extraMarginEndDpIfVisible = viewWidthDp + iconMarginDp;
6245             result.setRightIconState(rightIcon != null /* visible */, viewWidthDp,
6246                     viewHeightDp, extraMarginEndDpIfVisible, spaceForExpanderDp);
6247         }
6248 
6249         /**
6250          * Bind the large icon.
6251          */
bindLargeIcon(RemoteViews contentView, @NonNull StandardTemplateParams p, @NonNull TemplateBindResult result)6252         private void bindLargeIcon(RemoteViews contentView, @NonNull StandardTemplateParams p,
6253                 @NonNull TemplateBindResult result) {
6254             if (mN.mLargeIcon == null && mN.largeIcon != null) {
6255                 mN.mLargeIcon = Icon.createWithBitmap(mN.largeIcon);
6256             }
6257 
6258             // Determine the left and right icons
6259             Icon leftIcon = p.mHideLeftIcon ? null : mN.mLargeIcon;
6260             Icon rightIcon = p.mHideRightIcon ? null
6261                     : (p.mPromotedPicture != null ? p.mPromotedPicture : mN.mLargeIcon);
6262 
6263             // Apply the left icon (without duplicating the bitmap)
6264             if (leftIcon != rightIcon || leftIcon == null) {
6265                 // If the leftIcon is explicitly hidden or different from the rightIcon, then set it
6266                 // explicitly and make sure it won't take the right_icon drawable.
6267                 contentView.setImageViewIcon(R.id.left_icon, leftIcon);
6268                 contentView.setIntTag(R.id.left_icon, R.id.tag_uses_right_icon_drawable, 0);
6269             } else {
6270                 // If the leftIcon equals the rightIcon, just set the flag to use the right_icon
6271                 // drawable.  This avoids the view having two copies of the same bitmap.
6272                 contentView.setIntTag(R.id.left_icon, R.id.tag_uses_right_icon_drawable, 1);
6273             }
6274 
6275             // Always calculate dimens to populate `result` for the GONE case
6276             boolean isPromotedPicture = p.mPromotedPicture != null;
6277             calculateRightIconDimens(rightIcon, isPromotedPicture, result);
6278 
6279             // Bind the right icon
6280             if (rightIcon != null) {
6281                 contentView.setViewLayoutWidth(R.id.right_icon,
6282                         result.mRightIconWidthDp, TypedValue.COMPLEX_UNIT_DIP);
6283                 contentView.setViewLayoutHeight(R.id.right_icon,
6284                         result.mRightIconHeightDp, TypedValue.COMPLEX_UNIT_DIP);
6285                 contentView.setViewVisibility(R.id.right_icon, View.VISIBLE);
6286                 contentView.setImageViewIcon(R.id.right_icon, rightIcon);
6287                 contentView.setIntTag(R.id.right_icon, R.id.tag_keep_when_showing_left_icon,
6288                         isPromotedPicture ? 1 : 0);
6289                 processLargeLegacyIcon(rightIcon, contentView, p);
6290             } else {
6291                 // The "reset" doesn't clear the drawable, so we do it here.  This clear is
6292                 // important because the presence of a drawable in this view (regardless of the
6293                 // visibility) is used by NotificationGroupingUtil to set the visibility.
6294                 contentView.setImageViewIcon(R.id.right_icon, null);
6295                 contentView.setIntTag(R.id.right_icon, R.id.tag_keep_when_showing_left_icon, 0);
6296             }
6297         }
6298 
bindNotificationHeader(RemoteViews contentView, StandardTemplateParams p)6299         private void bindNotificationHeader(RemoteViews contentView, StandardTemplateParams p) {
6300             bindSmallIcon(contentView, p);
6301             // Populate text left-to-right so that separators are only shown between strings
6302             boolean hasTextToLeft = bindHeaderAppName(contentView, p, false /* force */);
6303             hasTextToLeft |= bindHeaderTextSecondary(contentView, p, hasTextToLeft);
6304             hasTextToLeft |= bindHeaderText(contentView, p, hasTextToLeft);
6305             if (!hasTextToLeft) {
6306                 // If there's still no text, force add the app name so there is some text.
6307                 hasTextToLeft |= bindHeaderAppName(contentView, p, true /* force */);
6308             }
6309             bindHeaderChronometerAndTime(contentView, p, hasTextToLeft);
6310             bindPhishingAlertIcon(contentView, p);
6311             bindProfileBadge(contentView, p);
6312             bindAlertedIcon(contentView, p);
6313             bindExpandButton(contentView, p);
6314             bindCloseButton(contentView, p);
6315             mN.mUsesStandardHeader = true;
6316         }
6317 
bindExpandButton(RemoteViews contentView, StandardTemplateParams p)6318         private void bindExpandButton(RemoteViews contentView, StandardTemplateParams p) {
6319             // set default colors
6320             int bgColor = getBackgroundColor(p);
6321             int pillColor = Colors.flattenAlpha(getColors(p).getProtectionColor(), bgColor);
6322             int textColor = Colors.flattenAlpha(getPrimaryTextColor(p), pillColor);
6323             contentView.setInt(R.id.expand_button, "setDefaultTextColor", textColor);
6324             contentView.setInt(R.id.expand_button, "setDefaultPillColor", pillColor);
6325             // Use different highlighted colors for conversations' unread count
6326             if (p.mHighlightExpander) {
6327                 pillColor = Colors.flattenAlpha(
6328                         getColors(p).getTertiaryFixedDimAccentColor(), bgColor);
6329                 textColor = Colors.flattenAlpha(
6330                         getColors(p).getOnTertiaryFixedAccentTextColor(), pillColor);
6331             }
6332             contentView.setInt(R.id.expand_button, "setHighlightTextColor", textColor);
6333             contentView.setInt(R.id.expand_button, "setHighlightPillColor", pillColor);
6334         }
6335 
bindCloseButton(RemoteViews contentView, StandardTemplateParams p)6336         private void bindCloseButton(RemoteViews contentView, StandardTemplateParams p) {
6337             // set default colors
6338             int bgColor = getBackgroundColor(p);
6339             int backgroundColor = Colors.flattenAlpha(getColors(p).getProtectionColor(), bgColor);
6340             int foregroundColor = Colors.flattenAlpha(getPrimaryTextColor(p), backgroundColor);
6341             contentView.setInt(R.id.close_button, "setForegroundColor", foregroundColor);
6342             contentView.setInt(R.id.close_button, "setBackgroundColor", backgroundColor);
6343         }
6344 
bindHeaderChronometerAndTime(RemoteViews contentView, StandardTemplateParams p, boolean hasTextToLeft)6345         private void bindHeaderChronometerAndTime(RemoteViews contentView,
6346                 StandardTemplateParams p, boolean hasTextToLeft) {
6347             if (!p.mHideTime && showsTimeOrChronometer()) {
6348                 if (hasTextToLeft) {
6349                     contentView.setViewVisibility(R.id.time_divider, View.VISIBLE);
6350                     setTextViewColorSecondary(contentView, R.id.time_divider, p);
6351                 }
6352                 if (mN.extras.getBoolean(EXTRA_SHOW_CHRONOMETER)) {
6353                     contentView.setViewVisibility(R.id.chronometer, View.VISIBLE);
6354                     contentView.setLong(R.id.chronometer, "setBase", mN.getWhen()
6355                             + (SystemClock.elapsedRealtime() - System.currentTimeMillis()));
6356                     contentView.setBoolean(R.id.chronometer, "setStarted", true);
6357                     boolean countsDown = mN.extras.getBoolean(EXTRA_CHRONOMETER_COUNT_DOWN);
6358                     contentView.setChronometerCountDown(R.id.chronometer, countsDown);
6359                     setTextViewColorSecondary(contentView, R.id.chronometer, p);
6360                 } else {
6361                     contentView.setViewVisibility(R.id.time, View.VISIBLE);
6362                     contentView.setLong(R.id.time, "setTime", mN.getWhen());
6363                     setTextViewColorSecondary(contentView, R.id.time, p);
6364                 }
6365             } else {
6366                 // We still want a time to be set but gone, such that we can show and hide it
6367                 // on demand in case it's a child notification without anything in the header
6368                 contentView.setLong(R.id.time, "setTime", mN.getWhen() != 0 ? mN.getWhen() :
6369                         mN.creationTime);
6370                 setTextViewColorSecondary(contentView, R.id.time, p);
6371             }
6372         }
6373 
6374         /**
6375          * @return true if the header text will be visible
6376          */
bindHeaderText(RemoteViews contentView, StandardTemplateParams p, boolean hasTextToLeft)6377         private boolean bindHeaderText(RemoteViews contentView, StandardTemplateParams p,
6378                 boolean hasTextToLeft) {
6379             if (p.mHideSubText) {
6380                 return false;
6381             }
6382             CharSequence headerText = p.mSubText;
6383             if (headerText == null && mStyle != null && mStyle.mSummaryTextSet
6384                     && mStyle.hasSummaryInHeader()) {
6385                 headerText = mStyle.mSummaryText;
6386             }
6387             if (headerText == null
6388                     && mContext.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N
6389                     && mN.extras.getCharSequence(EXTRA_INFO_TEXT) != null) {
6390                 headerText = mN.extras.getCharSequence(EXTRA_INFO_TEXT);
6391             }
6392             if (!TextUtils.isEmpty(headerText)) {
6393                 contentView.setTextViewText(R.id.header_text, ensureColorSpanContrastOrStripStyling(
6394                         processLegacyText(headerText), p));
6395                 setTextViewColorSecondary(contentView, R.id.header_text, p);
6396                 contentView.setViewVisibility(R.id.header_text, View.VISIBLE);
6397                 if (hasTextToLeft) {
6398                     contentView.setViewVisibility(R.id.header_text_divider, View.VISIBLE);
6399                     setTextViewColorSecondary(contentView, R.id.header_text_divider, p);
6400                 }
6401                 return true;
6402             }
6403             return false;
6404         }
6405 
6406         /**
6407          * @return true if the secondary header text will be visible
6408          */
bindHeaderTextSecondary(RemoteViews contentView, StandardTemplateParams p, boolean hasTextToLeft)6409         private boolean bindHeaderTextSecondary(RemoteViews contentView, StandardTemplateParams p,
6410                 boolean hasTextToLeft) {
6411             if (p.mHideSubText) {
6412                 return false;
6413             }
6414             if (!TextUtils.isEmpty(p.mHeaderTextSecondary)) {
6415                 contentView.setTextViewText(R.id.header_text_secondary,
6416                         ensureColorSpanContrastOrStripStyling(
6417                                 processLegacyText(p.mHeaderTextSecondary), p));
6418                 setTextViewColorSecondary(contentView, R.id.header_text_secondary, p);
6419                 contentView.setViewVisibility(R.id.header_text_secondary, View.VISIBLE);
6420                 if (hasTextToLeft) {
6421                     contentView.setViewVisibility(R.id.header_text_secondary_divider, View.VISIBLE);
6422                     setTextViewColorSecondary(contentView, R.id.header_text_secondary_divider, p);
6423                 }
6424                 return true;
6425             }
6426             return false;
6427         }
6428 
6429         /**
6430          * @hide
6431          */
6432         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
loadHeaderAppName()6433         public String loadHeaderAppName() {
6434             return mN.loadHeaderAppName(mContext);
6435         }
6436 
6437         /**
6438          * @return true if the app name will be visible
6439          */
bindHeaderAppName(RemoteViews contentView, StandardTemplateParams p, boolean force)6440         private boolean bindHeaderAppName(RemoteViews contentView, StandardTemplateParams p,
6441                 boolean force) {
6442             if (p.mViewType == StandardTemplateParams.VIEW_TYPE_MINIMIZED && !force) {
6443                 // unless the force flag is set, don't show the app name in the minimized state.
6444                 return false;
6445             }
6446             if (p.mHeaderless && p.hasTitle()) {
6447                 // the headerless template will have the TITLE in this position; return true to
6448                 // keep the divider visible between that title and the next text element.
6449                 return true;
6450             }
6451             if (p.mHideAppName) {
6452                 // The app name is being hidden, so we definitely want to return here.
6453                 // Assume that there is a title which will replace it in the header.
6454                 return p.hasTitle();
6455             }
6456             contentView.setViewVisibility(R.id.app_name_text, View.VISIBLE);
6457             contentView.setTextViewText(R.id.app_name_text, loadHeaderAppName());
6458             contentView.setTextColor(R.id.app_name_text, getSecondaryTextColor(p));
6459             return true;
6460         }
6461 
6462         /**
6463          * Determines if the notification should be colorized *for the purposes of applying colors*.
6464          * If this is the minimized view of a colorized notification, this will return false so that
6465          * internal coloring logic can still render the notification normally.
6466          */
isBackgroundColorized(StandardTemplateParams p)6467         private boolean isBackgroundColorized(StandardTemplateParams p) {
6468             return p.allowColorization && mN.isColorized();
6469         }
6470 
isCallActionColorCustomizable()6471         private boolean isCallActionColorCustomizable() {
6472             // NOTE: this doesn't need to check StandardTemplateParams.allowColorization because
6473             //  that is only used for disallowing colorization of headers for the minimized state,
6474             //  and neither of those conditions applies when showing actions.
6475             //  Not requiring StandardTemplateParams as an argument simplifies the creation process.
6476             return mN.isColorized() && mContext.getResources().getBoolean(
6477                     R.bool.config_callNotificationActionColorsRequireColorized);
6478         }
6479 
bindSmallIcon(RemoteViews contentView, StandardTemplateParams p)6480         private void bindSmallIcon(RemoteViews contentView, StandardTemplateParams p) {
6481             if (mN.mSmallIcon == null && mN.icon != 0) {
6482                 mN.mSmallIcon = Icon.createWithResource(mContext, mN.icon);
6483             }
6484             contentView.setImageViewIcon(R.id.icon, mN.mSmallIcon);
6485             contentView.setInt(R.id.icon, "setImageLevel", mN.iconLevel);
6486             processSmallIconColor(mN.mSmallIcon, contentView, p);
6487         }
6488 
6489         /**
6490          * @return true if the built notification will show the time or the chronometer; false
6491          *         otherwise
6492          */
showsTimeOrChronometer()6493         private boolean showsTimeOrChronometer() {
6494             return mN.showsTime() || mN.showsChronometer();
6495         }
6496 
resetStandardTemplateWithActions(RemoteViews contentView)6497         private void resetStandardTemplateWithActions(RemoteViews contentView) {
6498             // actions_container is only reset when there are no actions to avoid focus issues with
6499             // remote inputs.
6500             contentView.setViewVisibility(R.id.actions, View.GONE);
6501             contentView.removeAllViews(R.id.actions);
6502 
6503             contentView.setViewVisibility(R.id.notification_material_reply_container, View.GONE);
6504             contentView.setTextViewText(R.id.notification_material_reply_text_1, null);
6505             contentView.setViewVisibility(R.id.notification_material_reply_text_1_container,
6506                     View.GONE);
6507             contentView.setViewVisibility(R.id.notification_material_reply_progress, View.GONE);
6508 
6509             contentView.setViewVisibility(R.id.notification_material_reply_text_2, View.GONE);
6510             contentView.setTextViewText(R.id.notification_material_reply_text_2, null);
6511             contentView.setViewVisibility(R.id.notification_material_reply_text_3, View.GONE);
6512             contentView.setTextViewText(R.id.notification_material_reply_text_3, null);
6513 
6514             if (!notificationsRedesignTemplates()) {
6515                 // This may get erased by bindSnoozeAction, or if we're showing the bubble icon
6516                 contentView.setViewLayoutMarginDimen(R.id.notification_action_list_margin_target,
6517                         RemoteViews.MARGIN_BOTTOM, R.dimen.notification_content_margin);
6518             }
6519         }
6520 
bindSnoozeAction(RemoteViews contentView, StandardTemplateParams p)6521         private boolean bindSnoozeAction(RemoteViews contentView, StandardTemplateParams p) {
6522             boolean hideSnoozeButton = mN.isFgsOrUij()
6523                     || mN.fullScreenIntent != null
6524                     || isBackgroundColorized(p)
6525                     || p.mViewType != StandardTemplateParams.VIEW_TYPE_EXPANDED;
6526             contentView.setBoolean(R.id.snooze_button, "setEnabled", !hideSnoozeButton);
6527             if (hideSnoozeButton) {
6528                 // Only hide; NotificationContentView will show it when it adds the click listener
6529                 contentView.setViewVisibility(R.id.snooze_button, View.GONE);
6530             }
6531 
6532             final boolean snoozeEnabled = !hideSnoozeButton
6533                     && mContext.getContentResolver() != null
6534                     && isSnoozeSettingEnabled();
6535             if (!notificationsRedesignTemplates() && snoozeEnabled) {
6536                 contentView.setViewLayoutMarginDimen(R.id.notification_action_list_margin_target,
6537                         RemoteViews.MARGIN_BOTTOM, 0);
6538             }
6539             return snoozeEnabled;
6540         }
6541 
isSnoozeSettingEnabled()6542         private boolean isSnoozeSettingEnabled() {
6543             try {
6544                 return Settings.Secure.getIntForUser(mContext.getContentResolver(),
6545                     Settings.Secure.SHOW_NOTIFICATION_SNOOZE, 0, UserHandle.USER_CURRENT) == 1;
6546             } catch (SecurityException ex) {
6547                 // Most 3p apps can't access this snooze setting, so their NotificationListeners
6548                 // would be unable to create notification views if we propagated this exception.
6549                 return false;
6550             }
6551         }
6552 
6553         /**
6554          * Returns the actions that are not contextual.
6555          */
getNonContextualActions()6556         private @NonNull List<Notification.Action> getNonContextualActions() {
6557             if (mActions == null) return Collections.emptyList();
6558             List<Notification.Action> standardActions = new ArrayList<>();
6559             for (Notification.Action action : mActions) {
6560                 // Actions with RemoteInput are ignored for RONs.
6561                 if (mN.isPromotedOngoing()
6562                         && hasValidRemoteInput(action)) {
6563                     continue;
6564                 }
6565                 if (!action.isContextual()) {
6566                     standardActions.add(action);
6567                 }
6568             }
6569             return standardActions;
6570         }
6571 
applyStandardTemplateWithActions(int layoutId, StandardTemplateParams p, TemplateBindResult result)6572         private RemoteViews applyStandardTemplateWithActions(int layoutId,
6573                 StandardTemplateParams p, TemplateBindResult result) {
6574             RemoteViews contentView = applyStandardTemplate(layoutId, p, result);
6575 
6576             resetStandardTemplateWithActions(contentView);
6577             boolean snoozeEnabled = bindSnoozeAction(contentView, p);
6578             // color the snooze and bubble actions with the theme color
6579             ColorStateList actionColor = ColorStateList.valueOf(getStandardActionColor(p));
6580             contentView.setColorStateList(R.id.snooze_button, "setImageTintList", actionColor);
6581             contentView.setColorStateList(R.id.bubble_button, "setImageTintList", actionColor);
6582 
6583             // In the UI, contextual actions appear separately from the standard actions, so we
6584             // filter them out here.
6585             List<Notification.Action> nonContextualActions = getNonContextualActions();
6586 
6587             int numActions = Math.min(nonContextualActions.size(), MAX_ACTION_BUTTONS);
6588             boolean emphasizedMode = mN.fullScreenIntent != null
6589                     || p.mCallStyleActions
6590                     || ((mN.flags & FLAG_FSI_REQUESTED_BUT_DENIED) != 0);
6591 
6592             if (p.mCallStyleActions) {
6593                 // Clear view padding to allow buttons to start on the left edge.
6594                 // This must be done before 'setEmphasizedMode' which sets top/bottom margins.
6595                 contentView.setViewPadding(R.id.actions, 0, 0, 0, 0);
6596                 if (!Flags.notificationsRedesignTemplates()) {
6597                     // Add an optional indent that will make buttons start at the correct column
6598                     // when there is enough space to do so (and fall back to the left edge if not).
6599                     // This is handled directly in NotificationActionListLayout in the new design.
6600                     contentView.setInt(R.id.actions, "setCollapsibleIndentDimen",
6601                             R.dimen.call_notification_collapsible_indent);
6602                 }
6603                 if (evenlyDividedCallStyleActionLayout()) {
6604                     if (CallStyle.DEBUG_NEW_ACTION_LAYOUT) {
6605                         Log.d(TAG, "setting evenly divided mode on action list");
6606                     }
6607                     contentView.setBoolean(R.id.actions, "setEvenlyDividedMode", true);
6608                 }
6609             }
6610             if (!notificationsRedesignTemplates()) {
6611                 contentView.setBoolean(R.id.actions, "setEmphasizedMode", emphasizedMode);
6612             }
6613 
6614             boolean validRemoteInput = false;
6615             // With the new design, the actions_container should always be visible to act as padding
6616             // when there are no actions. We're making its child GONE instead.
6617             int actionsContainerForVisibilityChange = notificationsRedesignTemplates()
6618                     ? R.id.actions_container_layout : R.id.actions_container;
6619             if (numActions > 0 && !p.mHideActions) {
6620                 contentView.setViewVisibility(actionsContainerForVisibilityChange, View.VISIBLE);
6621                 contentView.setViewVisibility(R.id.actions, View.VISIBLE);
6622                 updateMarginsForActions(contentView, emphasizedMode);
6623                 validRemoteInput = populateActionsContainer(contentView, p, nonContextualActions,
6624                         numActions, emphasizedMode);
6625             } else {
6626                 contentView.setViewVisibility(actionsContainerForVisibilityChange, View.GONE);
6627             }
6628 
6629             RemoteInputHistoryItem[] replyText = getParcelableArrayFromBundle(
6630                     mN.extras, EXTRA_REMOTE_INPUT_HISTORY_ITEMS, RemoteInputHistoryItem.class);
6631             if (validRemoteInput && replyText != null && replyText.length > 0
6632                     && !TextUtils.isEmpty(replyText[0].getText())
6633                     && p.maxRemoteInputHistory > 0) {
6634                 boolean showSpinner = mN.extras.getBoolean(EXTRA_SHOW_REMOTE_INPUT_SPINNER);
6635                 contentView.setViewVisibility(R.id.notification_material_reply_container,
6636                         View.VISIBLE);
6637                 contentView.setViewVisibility(R.id.notification_material_reply_text_1_container,
6638                         View.VISIBLE);
6639                 contentView.setTextViewText(R.id.notification_material_reply_text_1,
6640                         ensureColorSpanContrastOrStripStyling(replyText[0].getText(), p));
6641                 setTextViewColorSecondary(contentView, R.id.notification_material_reply_text_1, p);
6642                 contentView.setViewVisibility(R.id.notification_material_reply_progress,
6643                         showSpinner ? View.VISIBLE : View.GONE);
6644                 contentView.setProgressIndeterminateTintList(
6645                         R.id.notification_material_reply_progress,
6646                         ColorStateList.valueOf(getPrimaryAccentColor(p)));
6647 
6648                 if (replyText.length > 1 && !TextUtils.isEmpty(replyText[1].getText())
6649                         && p.maxRemoteInputHistory > 1) {
6650                     contentView.setViewVisibility(R.id.notification_material_reply_text_2,
6651                             View.VISIBLE);
6652                     contentView.setTextViewText(R.id.notification_material_reply_text_2,
6653                             ensureColorSpanContrastOrStripStyling(replyText[1].getText(), p));
6654                     setTextViewColorSecondary(contentView, R.id.notification_material_reply_text_2,
6655                             p);
6656 
6657                     if (replyText.length > 2 && !TextUtils.isEmpty(replyText[2].getText())
6658                             && p.maxRemoteInputHistory > 2) {
6659                         contentView.setViewVisibility(
6660                                 R.id.notification_material_reply_text_3, View.VISIBLE);
6661                         contentView.setTextViewText(R.id.notification_material_reply_text_3,
6662                                 ensureColorSpanContrastOrStripStyling(replyText[2].getText(), p));
6663                         setTextViewColorSecondary(contentView,
6664                                 R.id.notification_material_reply_text_3, p);
6665                     }
6666                 }
6667             }
6668 
6669             return contentView;
6670         }
6671 
updateMarginsForActions(RemoteViews contentView, boolean emphasizedMode)6672         private void updateMarginsForActions(RemoteViews contentView, boolean emphasizedMode) {
6673             if (notificationsRedesignTemplates()) {
6674                 if (emphasizedMode) {
6675                     // Emphasized actions look similar to smart replies, so let's use the same
6676                     // margins.
6677                     contentView.setViewLayoutMarginDimen(R.id.actions_container,
6678                             RemoteViews.MARGIN_TOP,
6679                             R.dimen.notification_2025_smart_reply_container_margin);
6680                     contentView.setViewLayoutMarginDimen(R.id.actions_container,
6681                             RemoteViews.MARGIN_BOTTOM,
6682                             R.dimen.notification_2025_smart_reply_container_margin);
6683                 } else {
6684                     contentView.setViewLayoutMarginDimen(R.id.actions_container,
6685                             RemoteViews.MARGIN_TOP, 0);
6686                     contentView.setViewLayoutMarginDimen(R.id.actions_container,
6687                             RemoteViews.MARGIN_BOTTOM,
6688                             R.dimen.notification_2025_action_list_margin_bottom);
6689                 }
6690             } else {
6691                 contentView.setViewLayoutMarginDimen(R.id.notification_action_list_margin_target,
6692                         RemoteViews.MARGIN_BOTTOM, 0);
6693             }
6694         }
6695 
populateActionsContainer(RemoteViews contentView, StandardTemplateParams p, List<Action> nonContextualActions, int numActions, boolean emphasizedMode)6696         private boolean populateActionsContainer(RemoteViews contentView, StandardTemplateParams p,
6697                 List<Action> nonContextualActions, int numActions, boolean emphasizedMode) {
6698             boolean validRemoteInput = false;
6699             for (int i = 0; i < numActions; i++) {
6700                 Action action = nonContextualActions.get(i);
6701 
6702                 boolean actionHasValidInput = hasValidRemoteInput(action);
6703                 validRemoteInput |= actionHasValidInput;
6704 
6705                 final RemoteViews button = generateActionButton(action, emphasizedMode, p);
6706                 if (actionHasValidInput && !emphasizedMode) {
6707                     // Clear the drawable
6708                     button.setInt(R.id.action0, "setBackgroundResource", 0);
6709                 }
6710                 if (emphasizedMode && i > 0) {
6711                     // Clear start margin from non-first buttons to reduce the gap between them.
6712                     //  (8dp remaining gap is from all buttons' standard 4dp inset).
6713                     button.setViewLayoutMarginDimen(R.id.action0, RemoteViews.MARGIN_START, 0);
6714                 }
6715                 contentView.addView(R.id.actions, button);
6716             }
6717             return validRemoteInput;
6718         }
6719 
6720         /**
6721          * Calculate the top margin for the content in px, to allow enough space for the top line
6722          * above, using the given resource ID for the desired spacing.
6723          *
6724          * @hide
6725          */
getContentMarginTop(Context context, @DimenRes int spacingRes)6726         public static int getContentMarginTop(Context context, @DimenRes int spacingRes) {
6727             final Resources resources = context.getResources();
6728             // The margin above the text, at the top of the notification (originally in dp)
6729             int notifMargin = resources.getDimensionPixelSize(R.dimen.notification_2025_margin);
6730             // Spacing between the text lines, scaling with the font size (originally in sp)
6731             int spacing = resources.getDimensionPixelSize(spacingRes);
6732             // Size of the text in the notification top line (originally in sp)
6733             int textSize = resources.getDimensionPixelSize(R.dimen.notification_subtext_size);
6734 
6735             // Adding up all the values as pixels
6736             return notifMargin + spacing + textSize;
6737         }
6738 
hasValidRemoteInput(Action action)6739         private boolean hasValidRemoteInput(Action action) {
6740             if (TextUtils.isEmpty(action.title) || action.actionIntent == null) {
6741                 // Weird actions
6742                 return false;
6743             }
6744 
6745             RemoteInput[] remoteInputs = action.getRemoteInputs();
6746             if (remoteInputs == null) {
6747                 return false;
6748             }
6749 
6750             for (RemoteInput r : remoteInputs) {
6751                 CharSequence[] choices = r.getChoices();
6752                 if (r.getAllowFreeFormInput() || (choices != null && choices.length != 0)) {
6753                     return true;
6754                 }
6755             }
6756             return false;
6757         }
6758 
6759         /**
6760          * Construct a RemoteViews representing the standard notification layout.
6761          *
6762          * @deprecated For performance and system health reasons, this API is no longer required to
6763          *  be used directly by the System UI when rendering Notifications to the user. While the UI
6764          *  returned by this method will still represent the content of the Notification being
6765          *  built, it may differ from the visual style of the system.
6766          *
6767          *  NOTE: this API has always had severe limitations; for example it does not support any
6768          *  interactivity, it ignores the app theme, it hard-codes the colors from the system theme
6769          *  at the time it is called, and it does Bitmap decoding on the main thread which can cause
6770          *  UI jank.
6771          */
6772         @Deprecated
createContentView()6773         public RemoteViews createContentView() {
6774             if (useExistingRemoteView(mN.contentView)) {
6775                 return fullyCustomViewRequiresDecoration(false /* fromStyle */)
6776                         ? minimallyDecoratedContentView(mN.contentView) : mN.contentView;
6777             } else if (mStyle != null) {
6778                 final RemoteViews styleView = mStyle.makeContentView();
6779                 if (styleView != null) {
6780                     return fullyCustomViewRequiresDecoration(true /* fromStyle */)
6781                             ? minimallyDecoratedContentView(styleView) : styleView;
6782                 }
6783             }
6784             StandardTemplateParams p = mParams.reset()
6785                     .viewType(StandardTemplateParams.VIEW_TYPE_NORMAL)
6786                     .fillTextsFrom(this);
6787             return applyStandardTemplate(getCollapsedBaseLayoutResource(), p, null /* result */);
6788         }
6789 
6790         // This code is executed on behalf of other apps' notifications, sometimes even by 3p apps,
6791         // a use case that is not supported by the Compat Framework library.  Workarounds to resolve
6792         // the change's state in NotificationManagerService were very complex. While it's possible
6793         // apps can detect the change, it's most likely that the changes will simply result in
6794         // visual regressions.
6795         @SuppressWarnings("AndroidFrameworkCompatChange")
fullyCustomViewRequiresDecoration(boolean fromStyle)6796         private boolean fullyCustomViewRequiresDecoration(boolean fromStyle) {
6797             // Custom views which come from a platform style class are safe, and thus do not need to
6798             // be wrapped.  Any subclass of those styles has the opportunity to make arbitrary
6799             // changes to the RemoteViews, and thus can't be trusted as a fully vetted view.
6800             if (fromStyle && isPlatformStyle(mStyle)) {
6801                 return false;
6802             }
6803             return mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.S;
6804         }
6805 
minimallyDecoratedContentView(@onNull RemoteViews customContent)6806         private RemoteViews minimallyDecoratedContentView(@NonNull RemoteViews customContent) {
6807             StandardTemplateParams p = mParams.reset()
6808                     .viewType(StandardTemplateParams.VIEW_TYPE_NORMAL)
6809                     .decorationType(StandardTemplateParams.DECORATION_MINIMAL)
6810                     .fillTextsFrom(this);
6811             TemplateBindResult result = new TemplateBindResult();
6812             RemoteViews standard = applyStandardTemplate(getCollapsedBaseLayoutResource(),
6813                     p, result);
6814             buildCustomContentIntoTemplate(mContext, standard, customContent,
6815                     p, result);
6816             return standard;
6817         }
6818 
minimallyDecoratedExpandedContentView( @onNull RemoteViews customContent)6819         private RemoteViews minimallyDecoratedExpandedContentView(
6820                 @NonNull RemoteViews customContent) {
6821             StandardTemplateParams p = mParams.reset()
6822                     .viewType(StandardTemplateParams.VIEW_TYPE_EXPANDED)
6823                     .decorationType(StandardTemplateParams.DECORATION_MINIMAL)
6824                     .fillTextsFrom(this);
6825             TemplateBindResult result = new TemplateBindResult();
6826             RemoteViews standard = applyStandardTemplateWithActions(getExpandedBaseLayoutResource(),
6827                     p, result);
6828             buildCustomContentIntoTemplate(mContext, standard, customContent,
6829                     p, result);
6830             makeHeaderExpanded(standard);
6831             return standard;
6832         }
6833 
minimallyDecoratedHeadsUpContentView( @onNull RemoteViews customContent)6834         private RemoteViews minimallyDecoratedHeadsUpContentView(
6835                 @NonNull RemoteViews customContent) {
6836             StandardTemplateParams p = mParams.reset()
6837                     .viewType(StandardTemplateParams.VIEW_TYPE_HEADS_UP)
6838                     .decorationType(StandardTemplateParams.DECORATION_MINIMAL)
6839                     .fillTextsFrom(this);
6840             TemplateBindResult result = new TemplateBindResult();
6841             RemoteViews standard = applyStandardTemplateWithActions(getHeadsUpBaseLayoutResource(),
6842                     p, result);
6843             buildCustomContentIntoTemplate(mContext, standard, customContent,
6844                     p, result);
6845             return standard;
6846         }
6847 
useExistingRemoteView(RemoteViews customContent)6848         private boolean useExistingRemoteView(RemoteViews customContent) {
6849             if (customContent == null) {
6850                 return false;
6851             }
6852             if (styleDisplaysCustomViewInline()) {
6853                 // the provided custom view is intended to be wrapped by the style.
6854                 return false;
6855             }
6856             if (fullyCustomViewRequiresDecoration(false)
6857                     && isStandardLayout(customContent.getLayoutId())) {
6858                 // If the app's custom views are objects returned from Builder.create*ContentView()
6859                 // then the app is most likely attempting to spoof the user.  Even if they are not,
6860                 // the result would be broken (b/189189308) so we will ignore it.
6861                 Log.w(TAG, "For apps targeting S, a custom content view that is a modified "
6862                         + "version of any standard layout is disallowed.");
6863                 return false;
6864             }
6865             return true;
6866         }
6867 
6868         /**
6869          * Construct a RemoteViews representing the expanded notification layout.
6870          *
6871          * @deprecated For performance and system health reasons, this API is no longer required to
6872          *  be used directly by the System UI when rendering Notifications to the user. While the UI
6873          *  returned by this method will still represent the content of the Notification being
6874          *  built, it may differ from the visual style of the system.
6875          *
6876          *  NOTE: this API has always had severe limitations; for example it does not support any
6877          *  interactivity, it ignores the app theme, it hard-codes the colors from the system theme
6878          *  at the time it is called, and it does Bitmap decoding on the main thread which can cause
6879          *  UI jank.
6880          */
6881         @Deprecated
createBigContentView()6882         public RemoteViews createBigContentView() {
6883             return createExpandedContentView();
6884         }
6885 
createExpandedContentView()6886         private RemoteViews createExpandedContentView() {
6887             RemoteViews result = null;
6888             if (useExistingRemoteView(mN.bigContentView)) {
6889                 return fullyCustomViewRequiresDecoration(false /* fromStyle */)
6890                         ? minimallyDecoratedExpandedContentView(mN.bigContentView)
6891                         : mN.bigContentView;
6892             }
6893             if (mStyle != null) {
6894                 result = mStyle.makeExpandedContentView();
6895                 if (fullyCustomViewRequiresDecoration(true /* fromStyle */)) {
6896                     result = minimallyDecoratedExpandedContentView(result);
6897                 }
6898             }
6899             if (result == null) {
6900                 if (expandedContentViewRequired()) {
6901                     StandardTemplateParams p = mParams.reset()
6902                             .viewType(StandardTemplateParams.VIEW_TYPE_EXPANDED)
6903                             .allowTextWithProgress(true)
6904                             .fillTextsFrom(this);
6905                     result = applyStandardTemplateWithActions(getExpandedBaseLayoutResource(), p,
6906                             null /* result */);
6907                 }
6908             }
6909             makeHeaderExpanded(result);
6910             return result;
6911         }
6912 
6913         // This code is executed on behalf of other apps' notifications, sometimes even by 3p apps,
6914         // a use case that is not supported by the Compat Framework library.  Workarounds to resolve
6915         // the change's state in NotificationManagerService were very complex. While it's possible
6916         // apps can detect the change, it's most likely that the changes will simply result in
6917         // visual regressions.
6918         @SuppressWarnings("AndroidFrameworkCompatChange")
expandedContentViewRequired()6919         private boolean expandedContentViewRequired() {
6920             if (Flags.notificationExpansionOptional()) {
6921                 // Notifications without a bigContentView, style, or actions do not need to expand
6922                 boolean exempt = mN.bigContentView == null
6923                         && mStyle == null && mActions.size() == 0;
6924                 return !exempt;
6925             }
6926             if (mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.S) {
6927                 return true;
6928             }
6929             // Notifications with contentView and without a bigContentView, style, or actions would
6930             // not have an expanded state before S, so showing the standard template expanded state
6931             // usually looks wrong, so we keep it simple and don't show the expanded state.
6932             boolean exempt = mN.contentView != null && mN.bigContentView == null
6933                     && mStyle == null && mActions.size() == 0;
6934             return !exempt;
6935         }
6936 
6937         /**
6938          * Construct a RemoteViews for the final notification header only. This will not be
6939          * colorized.
6940          *
6941          * @hide
6942          */
makeNotificationGroupHeader()6943         public RemoteViews makeNotificationGroupHeader() {
6944             return makeNotificationHeader(mParams.reset().disallowColorization()
6945                     .viewType(StandardTemplateParams.VIEW_TYPE_GROUP_HEADER)
6946                     .fillTextsFrom(this));
6947         }
6948 
6949         /**
6950          * Construct a RemoteViews for the final notification header only. This will not be
6951          * colorized.
6952          *
6953          * @param p the template params to inflate this with
6954          */
makeNotificationHeader(StandardTemplateParams p)6955         private RemoteViews makeNotificationHeader(StandardTemplateParams p) {
6956             RemoteViews header = new BuilderRemoteViews(mContext.getApplicationInfo(),
6957                     getHeaderLayoutResource());
6958             resetNotificationHeader(header);
6959             bindNotificationHeader(header, p);
6960             updateHeaderBackgroundColor(header, p);
6961             if (Flags.notificationsRedesignTemplates()
6962                     && (p.mViewType == StandardTemplateParams.VIEW_TYPE_MINIMIZED
6963                     || p.mViewType == StandardTemplateParams.VIEW_TYPE_PUBLIC)) {
6964                 // Center top line vertically in minimized and public header-only views
6965                 header.setBoolean(R.id.notification_header, "centerTopLine", true);
6966             }
6967             return header;
6968         }
6969 
6970         /**
6971          * Adapt the Notification header if this view is used as an expanded view.
6972          *
6973          * @hide
6974          */
makeHeaderExpanded(RemoteViews result)6975         public static void makeHeaderExpanded(RemoteViews result) {
6976             if (result != null) {
6977                 result.setBoolean(R.id.expand_button, "setExpanded", true);
6978             }
6979         }
6980 
6981         /**
6982          * Construct a RemoteViews for the final compact heads-up notification layout.
6983          * @hide
6984          */
createCompactHeadsUpContentView()6985         public RemoteViews createCompactHeadsUpContentView() {
6986             // Don't show compact heads up for FSI notifications.
6987             if (mN.fullScreenIntent != null) {
6988                 return createHeadsUpContentView();
6989             }
6990 
6991             if (mStyle != null) {
6992                 final RemoteViews styleView = mStyle.makeCompactHeadsUpContentView();
6993                 if (styleView != null) {
6994                     return styleView;
6995                 }
6996             }
6997 
6998             final StandardTemplateParams p = mParams.reset()
6999                     .viewType(StandardTemplateParams.VIEW_TYPE_HEADS_UP)
7000                     .fillTextsFrom(this);
7001             // Notification text is shown as secondary header text
7002             // for the minimal hun when it is provided.
7003             // Time(when and chronometer) is not shown for the minimal hun.
7004             p.headerTextSecondary(p.mText).text(null).hideTime(true).summaryText("");
7005 
7006             return applyStandardTemplate(
7007                     getCompactHeadsUpBaseLayoutResource(), p,
7008                     null /* result */);
7009         }
7010 
7011         /**
7012          * Construct a RemoteViews representing the heads up notification layout.
7013          *
7014          * @deprecated For performance and system health reasons, this API is no longer required to
7015          *  be used directly by the System UI when rendering Notifications to the user. While the UI
7016          *  returned by this method will still represent the content of the Notification being
7017          *  built, it may differ from the visual style of the system.
7018          *
7019          *  NOTE: this API has always had severe limitations; for example it does not support any
7020          *  interactivity, it ignores the app theme, it hard-codes the colors from the system theme
7021          *  at the time it is called, and it does Bitmap decoding on the main thread which can cause
7022          *  UI jank.
7023          */
7024         @Deprecated
createHeadsUpContentView()7025         public RemoteViews createHeadsUpContentView() {
7026             if (useExistingRemoteView(mN.headsUpContentView)) {
7027                 return fullyCustomViewRequiresDecoration(false /* fromStyle */)
7028                         ? minimallyDecoratedHeadsUpContentView(mN.headsUpContentView)
7029                         : mN.headsUpContentView;
7030             } else if (mStyle != null) {
7031                 final RemoteViews styleView = mStyle.makeHeadsUpContentView();
7032                 if (styleView != null) {
7033                     return fullyCustomViewRequiresDecoration(true /* fromStyle */)
7034                             ? minimallyDecoratedHeadsUpContentView(styleView) : styleView;
7035                 }
7036             } else if (mActions.size() == 0) {
7037                 return null;
7038             }
7039 
7040             // We only want at most a single remote input history to be shown here, otherwise
7041             // the content would become squished.
7042             StandardTemplateParams p = mParams.reset()
7043                     .viewType(StandardTemplateParams.VIEW_TYPE_HEADS_UP)
7044                     .fillTextsFrom(this)
7045                     .setMaxRemoteInputHistory(1);
7046             return applyStandardTemplateWithActions(getHeadsUpBaseLayoutResource(), p,
7047                     null /* result */);
7048         }
7049 
7050         /**
7051          * Construct a RemoteViews for the display in public contexts like on the lockscreen.
7052          *
7053          * @param isLowPriority is this notification low priority
7054          * @hide
7055          */
7056         @UnsupportedAppUsage
makePublicContentView(boolean isLowPriority)7057         public RemoteViews makePublicContentView(boolean isLowPriority) {
7058             if (mN.publicVersion != null) {
7059                 final Builder builder = recoverBuilder(mContext, mN.publicVersion);
7060                 // copy non-sensitive style fields to the public style
7061                 if (mStyle instanceof Notification.MessagingStyle privateStyle) {
7062                     if (builder.mStyle instanceof Notification.MessagingStyle publicStyle) {
7063                         publicStyle.mConversationType = privateStyle.mConversationType;
7064                     }
7065                 }
7066                 return builder.createContentView();
7067             }
7068             Bundle savedBundle = mN.extras;
7069             Style style = mStyle;
7070             mStyle = null;
7071             Icon largeIcon = mN.mLargeIcon;
7072             mN.mLargeIcon = null;
7073             Bitmap largeIconLegacy = mN.largeIcon;
7074             mN.largeIcon = null;
7075             ArrayList<Action> actions = mActions;
7076             mActions = new ArrayList<>();
7077             Bundle publicExtras = new Bundle();
7078             publicExtras.putBoolean(EXTRA_SHOW_WHEN,
7079                     savedBundle.getBoolean(EXTRA_SHOW_WHEN));
7080             publicExtras.putBoolean(EXTRA_SHOW_CHRONOMETER,
7081                     savedBundle.getBoolean(EXTRA_SHOW_CHRONOMETER));
7082             publicExtras.putBoolean(EXTRA_CHRONOMETER_COUNT_DOWN,
7083                     savedBundle.getBoolean(EXTRA_CHRONOMETER_COUNT_DOWN));
7084             if (mN.isPromotedOngoing()) {
7085                 publicExtras.putBoolean(EXTRA_COLORIZED,
7086                         savedBundle.getBoolean(EXTRA_COLORIZED));
7087             }
7088             String appName = savedBundle.getString(EXTRA_SUBSTITUTE_APP_NAME);
7089             if (appName != null) {
7090                 publicExtras.putString(EXTRA_SUBSTITUTE_APP_NAME, appName);
7091             }
7092             mN.extras = publicExtras;
7093             RemoteViews view;
7094             StandardTemplateParams params = mParams.reset()
7095                     .viewType(StandardTemplateParams.VIEW_TYPE_PUBLIC)
7096                     .fillTextsFrom(this);
7097             if (isLowPriority) {
7098                 params.highlightExpander(false);
7099             }
7100             if (!mN.isPromotedOngoing()) {
7101                 params.disallowColorization();
7102             }
7103             view = makeNotificationHeader(params);
7104             view.setBoolean(R.id.notification_header, "setExpandOnlyOnButton", true);
7105             mN.extras = savedBundle;
7106             mN.mLargeIcon = largeIcon;
7107             mN.largeIcon = largeIconLegacy;
7108             mActions = actions;
7109             mStyle = style;
7110             return view;
7111         }
7112 
7113         /**
7114          * Construct a content view for the display when low - priority
7115          *
7116          * @param useRegularSubtext uses the normal subtext set if there is one available. Otherwise
7117          *                          a new subtext is created consisting of the content of the
7118          *                          notification.
7119          * @hide
7120          */
makeLowPriorityContentView(boolean useRegularSubtext)7121         public RemoteViews makeLowPriorityContentView(boolean useRegularSubtext) {
7122             StandardTemplateParams p = mParams.reset().disallowColorization()
7123                     .viewType(StandardTemplateParams.VIEW_TYPE_MINIMIZED)
7124                     .highlightExpander(false)
7125                     .fillTextsFrom(this);
7126             if (!useRegularSubtext || TextUtils.isEmpty(p.mSubText)) {
7127                 p.summaryText(createSummaryText());
7128             }
7129             RemoteViews header = makeNotificationHeader(p);
7130             header.setBoolean(R.id.notification_header, "setAcceptAllTouches", true);
7131             // The low priority header has no app name and shows the text
7132             header.setBoolean(R.id.notification_header, "styleTextAsTitle", true);
7133             return header;
7134         }
7135 
createSummaryText()7136         private CharSequence createSummaryText() {
7137             CharSequence titleText = mN.extras.getCharSequence(Notification.EXTRA_TITLE);
7138             if (USE_ONLY_TITLE_IN_LOW_PRIORITY_SUMMARY) {
7139                 return titleText;
7140             }
7141             SpannableStringBuilder summary = new SpannableStringBuilder();
7142             if (titleText == null) {
7143                 titleText = mN.extras.getCharSequence(Notification.EXTRA_TITLE_BIG);
7144             }
7145             BidiFormatter bidi = BidiFormatter.getInstance();
7146             if (titleText != null) {
7147                 summary.append(bidi.unicodeWrap(titleText));
7148             }
7149             CharSequence contentText = mN.extras.getCharSequence(Notification.EXTRA_TEXT);
7150             if (titleText != null && contentText != null) {
7151                 summary.append(bidi.unicodeWrap(mContext.getText(
7152                         R.string.notification_header_divider_symbol_with_spaces)));
7153             }
7154             if (contentText != null) {
7155                 summary.append(bidi.unicodeWrap(contentText));
7156             }
7157             return summary;
7158         }
7159 
generateActionButton(Action action, boolean emphasizedMode, StandardTemplateParams p)7160         private RemoteViews generateActionButton(Action action, boolean emphasizedMode,
7161                 StandardTemplateParams p) {
7162             final boolean tombstone = (action.actionIntent == null);
7163             final RemoteViews button = new BuilderRemoteViews(mContext.getApplicationInfo(),
7164                     getActionButtonLayoutResource(emphasizedMode, tombstone));
7165             if (!tombstone) {
7166                 button.setOnClickPendingIntent(R.id.action0, action.actionIntent);
7167             }
7168             button.setContentDescription(R.id.action0, action.title);
7169             if (action.mRemoteInputs != null) {
7170                 button.setRemoteInputs(R.id.action0, action.mRemoteInputs);
7171             }
7172             if (emphasizedMode) {
7173                 // change the background bgColor
7174                 CharSequence title = action.title;
7175                 int buttonFillColor = getColors(p).getSecondaryAccentColor();
7176                 if (tombstone) {
7177                     buttonFillColor = setAlphaComponentByFloatDimen(mContext,
7178                             ContrastColorUtil.resolveSecondaryColor(
7179                                     mContext, getColors(p).getBackgroundColor(), mInNightMode),
7180                             R.dimen.notification_action_disabled_container_alpha);
7181                 }
7182                 if (Flags.cleanUpSpansAndNewLines()) {
7183                     if (!isLegacy()) {
7184                         // Check for a full-length span color to use as the button fill color.
7185                         Integer fullLengthColor = getFullLengthSpanColor(title);
7186                         if (fullLengthColor != null) {
7187                             // Ensure the custom button fill has 1.3:1 contrast w/ notification bg.
7188                             int notifBackgroundColor = getColors(p).getBackgroundColor();
7189                             buttonFillColor = ensureButtonFillContrast(
7190                                     fullLengthColor, notifBackgroundColor);
7191                         }
7192                     }
7193                 } else {
7194                     if (isLegacy()) {
7195                         title = ContrastColorUtil.clearColorSpans(title);
7196                     } else {
7197                         // Check for a full-length span color to use as the button fill color.
7198                         Integer fullLengthColor = getFullLengthSpanColor(title);
7199                         if (fullLengthColor != null) {
7200                             // Ensure the custom button fill has 1.3:1 contrast w/ notification bg.
7201                             int notifBackgroundColor = getColors(p).getBackgroundColor();
7202                             buttonFillColor = ensureButtonFillContrast(
7203                                     fullLengthColor, notifBackgroundColor);
7204                         }
7205                         // Remove full-length color spans
7206                         // and ensure text contrast with the button fill.
7207                         title = ContrastColorUtil.ensureColorSpanContrast(title, buttonFillColor);
7208                     }
7209                 }
7210 
7211 
7212                 final CharSequence label = ensureColorSpanContrastOrStripStyling(title, p);
7213                 if (p.mCallStyleActions && evenlyDividedCallStyleActionLayout()) {
7214                     if (CallStyle.DEBUG_NEW_ACTION_LAYOUT) {
7215                         Log.d(TAG, "new action layout enabled, gluing instead of setting text");
7216                     }
7217                     button.setCharSequence(R.id.action0, "glueLabel", label);
7218                 } else {
7219                     button.setTextViewText(R.id.action0, label);
7220                 }
7221                 int textColor = ContrastColorUtil.resolvePrimaryColor(mContext,
7222                         buttonFillColor, mInNightMode);
7223                 if (tombstone) {
7224                     textColor = setAlphaComponentByFloatDimen(mContext,
7225                             ContrastColorUtil.resolveSecondaryColor(
7226                                     mContext, getColors(p).getBackgroundColor(), mInNightMode),
7227                             R.dimen.notification_action_disabled_content_alpha);
7228                 }
7229                 button.setTextColor(R.id.action0, textColor);
7230                 // We only want about 20% alpha for the ripple
7231                 final int rippleColor = (textColor & 0x00ffffff) | 0x33000000;
7232                 button.setColorStateList(R.id.action0, "setRippleColor",
7233                         ColorStateList.valueOf(rippleColor));
7234                 button.setColorStateList(R.id.action0, "setButtonBackground",
7235                         ColorStateList.valueOf(buttonFillColor));
7236                 if (p.mCallStyleActions) {
7237                     if (evenlyDividedCallStyleActionLayout()) {
7238                         if (CallStyle.DEBUG_NEW_ACTION_LAYOUT) {
7239                             Log.d(TAG, "new action layout enabled, gluing instead of setting icon");
7240                         }
7241                         button.setIcon(R.id.action0, "glueIcon", action.getIcon());
7242                     } else {
7243                         button.setImageViewIcon(R.id.action0, action.getIcon());
7244                     }
7245                     boolean priority = action.getExtras().getBoolean(CallStyle.KEY_ACTION_PRIORITY);
7246                     button.setBoolean(R.id.action0, "setIsPriority", priority);
7247                     int minWidthDimen =
7248                             priority ? R.dimen.call_notification_system_action_min_width : 0;
7249                     button.setIntDimen(R.id.action0, "setMinimumWidth", minWidthDimen);
7250                 }
7251             } else {
7252                 button.setTextViewText(R.id.action0, ensureColorSpanContrastOrStripStyling(
7253                         action.title, p));
7254                 button.setTextColor(R.id.action0, getStandardActionColor(p));
7255             }
7256             // CallStyle notifications add action buttons which don't actually exist in mActions,
7257             //  so we have to omit the index in that case.
7258             int actionIndex = mActions.indexOf(action);
7259             if (actionIndex != -1) {
7260                 button.setIntTag(R.id.action0, R.id.notification_action_index_tag, actionIndex);
7261             }
7262             return button;
7263         }
7264 
getActionButtonLayoutResource(boolean emphasizedMode, boolean tombstone)7265         private int getActionButtonLayoutResource(boolean emphasizedMode, boolean tombstone) {
7266             if (emphasizedMode) {
7267                 return tombstone ? getEmphasizedTombstoneActionLayoutResource()
7268                         : getEmphasizedActionLayoutResource();
7269             } else {
7270                 return tombstone ? getActionTombstoneLayoutResource()
7271                         : getActionLayoutResource();
7272             }
7273         }
7274 
7275         /**
7276          * Set the alpha component of {@code color} to be {@code alphaDimenResId}.
7277          */
setAlphaComponentByFloatDimen(Context context, @ColorInt int color, @DimenRes int alphaDimenResId)7278         private static int setAlphaComponentByFloatDimen(Context context, @ColorInt int color,
7279                 @DimenRes int alphaDimenResId) {
7280             final TypedValue alphaValue = new TypedValue();
7281             context.getResources().getValue(alphaDimenResId, alphaValue, true);
7282             return ColorUtils.setAlphaComponent(color, Math.round(alphaValue.getFloat() * 255));
7283         }
7284 
7285         /**
7286          * Extract the color from a full-length span from the text.
7287          *
7288          * @param charSequence the charSequence containing spans
7289          * @return the raw color of the text's last full-length span containing a color, or null if
7290          * no full-length span sets the text color.
7291          * @hide
7292          */
7293         @VisibleForTesting
7294         @Nullable
getFullLengthSpanColor(CharSequence charSequence)7295         public static Integer getFullLengthSpanColor(CharSequence charSequence) {
7296             // NOTE: this method preserves the functionality that for a CharSequence with multiple
7297             // full-length spans, the color of the last one is used.
7298             Integer result = null;
7299             if (charSequence instanceof Spanned) {
7300                 Spanned ss = (Spanned) charSequence;
7301                 Object[] spans = ss.getSpans(0, ss.length(), Object.class);
7302                 // First read through all full-length spans to get the button fill color, which will
7303                 //  be used as the background color for ensuring contrast of non-full-length spans.
7304                 for (Object span : spans) {
7305                     int spanStart = ss.getSpanStart(span);
7306                     int spanEnd = ss.getSpanEnd(span);
7307                     boolean fullLength = (spanEnd - spanStart) == charSequence.length();
7308                     if (!fullLength) {
7309                         continue;
7310                     }
7311                     if (span instanceof TextAppearanceSpan) {
7312                         TextAppearanceSpan originalSpan = (TextAppearanceSpan) span;
7313                         ColorStateList textColor = originalSpan.getTextColor();
7314                         if (textColor != null) {
7315                             result = textColor.getDefaultColor();
7316                         }
7317                     } else if (span instanceof ForegroundColorSpan) {
7318                         ForegroundColorSpan originalSpan = (ForegroundColorSpan) span;
7319                         result = originalSpan.getForegroundColor();
7320                     }
7321                 }
7322             }
7323             return result;
7324         }
7325 
7326         /**
7327          * @hide
7328          */
ensureColorSpanContrastOrStripStyling(CharSequence cs, StandardTemplateParams p)7329         public CharSequence ensureColorSpanContrastOrStripStyling(CharSequence cs,
7330                 StandardTemplateParams p) {
7331             return ensureColorSpanContrastOrStripStyling(cs, getBackgroundColor(p));
7332         }
7333 
7334         /**
7335          * @hide
7336          */
ensureColorSpanContrastOrStripStyling(CharSequence cs, int buttonFillColor)7337         public CharSequence ensureColorSpanContrastOrStripStyling(CharSequence cs,
7338                 int buttonFillColor) {
7339             // Ongoing promoted notifications are allowed to have styling.
7340             final boolean isPromotedOngoing = mN.isPromotedOngoing();
7341             if (!isPromotedOngoing && Flags.cleanUpSpansAndNewLines()) {
7342                 return stripStyling(cs);
7343             }
7344 
7345             return ContrastColorUtil.ensureColorSpanContrast(cs, buttonFillColor);
7346         }
7347 
7348         /**
7349          * Ensures contrast on color spans against a background color.
7350          * Note that any full-length color spans will be removed instead of being contrasted.
7351          *
7352          * @hide
7353          */
7354         @VisibleForTesting
ensureColorSpanContrast(CharSequence charSequence, StandardTemplateParams p)7355         public CharSequence ensureColorSpanContrast(CharSequence charSequence,
7356                 StandardTemplateParams p) {
7357             return ContrastColorUtil.ensureColorSpanContrast(charSequence, getBackgroundColor(p));
7358         }
7359 
7360         /**
7361          * Determines if the color is light or dark.  Specifically, this is using the same metric as
7362          * {@link ContrastColorUtil#resolvePrimaryColor(Context, int, boolean)} and peers so that
7363          * the direction of color shift is consistent.
7364          *
7365          * @param color the color to check
7366          * @return true if the color has higher contrast with white than black
7367          * @hide
7368          */
isColorDark(int color)7369         public static boolean isColorDark(int color) {
7370             // as per ContrastColorUtil.shouldUseDark, this uses the color contrast midpoint.
7371             return ContrastColorUtil.calculateLuminance(color) <= 0.17912878474;
7372         }
7373 
7374         /**
7375          * Finds a button fill color with sufficient contrast over bg (1.3:1) that has the same hue
7376          * as the original color, but is lightened or darkened depending on whether the background
7377          * is dark or light.
7378          *
7379          * @hide
7380          */
7381         @VisibleForTesting
ensureButtonFillContrast(int color, int bg)7382         public static int ensureButtonFillContrast(int color, int bg) {
7383             return ensureColorContrast(color, bg, 1.3);
7384         }
7385 
7386 
ensureColorContrast(int color, int bg, double contrastRatio)7387         private static int ensureColorContrast(int color, int bg, double contrastRatio) {
7388             return isColorDark(bg)
7389                     ? ContrastColorUtil.findContrastColorAgainstDark(color, bg, true, contrastRatio)
7390                     : ContrastColorUtil.findContrastColor(color, bg, true, contrastRatio);
7391         }
7392 
7393         /**
7394          * @return Whether we are currently building a notification from a legacy (an app that
7395          *         doesn't create material notifications by itself) app.
7396          */
isLegacy()7397         private boolean isLegacy() {
7398             if (!mIsLegacyInitialized) {
7399                 mIsLegacy = mContext.getApplicationInfo().targetSdkVersion
7400                         < Build.VERSION_CODES.LOLLIPOP;
7401                 mIsLegacyInitialized = true;
7402             }
7403             return mIsLegacy;
7404         }
7405 
7406         private CharSequence processLegacyText(CharSequence charSequence) {
7407             boolean isAlreadyLightText = isLegacy() || textColorsNeedInversion();
7408             if (isAlreadyLightText) {
7409                 return getColorUtil().invertCharSequenceColors(charSequence);
7410             } else {
7411                 return charSequence;
7412             }
7413         }
7414 
7415         /**
7416          * Apply any necessary colors to the small icon
7417          */
7418         private void processSmallIconColor(Icon smallIcon, RemoteViews contentView,
7419                 StandardTemplateParams p) {
7420             boolean colorable = !isLegacy() || getColorUtil().isGrayscaleIcon(mContext,
7421                     smallIcon);
7422             int color = getSmallIconColor(p);
7423             contentView.setInt(R.id.icon, "setBackgroundColor",
7424                     getBackgroundColor(p));
7425             contentView.setInt(R.id.icon, "setOriginalIconColor",
7426                     colorable ? color : COLOR_INVALID);
7427         }
7428 
7429         /**
7430          * Make the largeIcon dark if it's a fake smallIcon (that is,
7431          * if it's grayscale).
7432          */
7433         // TODO: also check bounds, transparency, that sort of thing.
7434         private void processLargeLegacyIcon(Icon largeIcon, RemoteViews contentView,
7435                 StandardTemplateParams p) {
7436             if (largeIcon != null && isLegacy()
7437                     && getColorUtil().isGrayscaleIcon(mContext, largeIcon)) {
7438                 // resolve color will fall back to the default when legacy
7439                 int color = getSmallIconColor(p);
7440                 contentView.setInt(R.id.icon, "setOriginalIconColor", color);
7441             }
7442         }
7443 
7444         private void sanitizeColor() {
7445             if (mN.color != COLOR_DEFAULT) {
7446                 mN.color |= 0xFF000000; // no alpha for custom colors
7447             }
7448         }
7449 
7450         /**
7451          * Gets the standard action button color
7452          */
7453         private @ColorInt int getStandardActionColor(Notification.StandardTemplateParams p) {
7454             return mTintActionButtons || isBackgroundColorized(p)
7455                     ? getPrimaryAccentColor(p) : getSecondaryTextColor(p);
7456         }
7457 
7458         /**
7459          * Gets the foreground color of the small icon.  If the notification is colorized, this
7460          * is the primary text color, otherwise it's the contrast-adjusted app-provided color.
7461          */
7462         private @ColorInt int getSmallIconColor(StandardTemplateParams p) {
7463             return getColors(p).getContrastColor();
7464         }
7465 
7466         /**
7467          * Gets the foreground color of the small icon.  If the notification is colorized, this
7468          * is the primary text color, otherwise it's the contrast-adjusted app-provided color.
7469          * @hide
7470          */
7471         public @ColorInt int getSmallIconColor(boolean isHeader) {
7472             return getColors(/* isHeader = */ isHeader).getContrastColor();
7473         }
7474 
7475         /**
7476          * Gets the background color of the notification.
7477          * @hide
7478          */
7479         public @ColorInt int getBackgroundColor(boolean isHeader) {
7480             return getColors(/* isHeader = */ isHeader).getBackgroundColor();
7481         }
7482 
7483         /** @return the theme's accent color for colored UI elements. */
7484         private @ColorInt int getPrimaryAccentColor(StandardTemplateParams p) {
7485             return getColors(p).getPrimaryAccentColor();
7486         }
7487 
7488         /**
7489          * Apply the unstyled operations and return a new {@link Notification} object.
7490          * @hide
7491          */
7492         @NonNull
7493         public Notification buildUnstyled() {
7494             if (mActions.size() > 0) {
7495                 mN.actions = new Action[mActions.size()];
7496                 mActions.toArray(mN.actions);
7497             }
7498             if (!mPersonList.isEmpty()) {
7499                 mN.extras.putParcelableArrayList(EXTRA_PEOPLE_LIST, mPersonList);
7500             }
7501             if (mN.bigContentView != null || mN.contentView != null
7502                     || mN.headsUpContentView != null) {
7503                 mN.extras.putBoolean(EXTRA_CONTAINS_CUSTOM_VIEW, true);
7504             }
7505             return mN;
7506         }
7507 
7508         /**
7509          * Creates a Builder from an existing notification so further changes can be made.
7510          * @param context The context for your application / activity.
7511          * @param n The notification to create a Builder from.
7512          */
7513         @NonNull
recoverBuilder(Context context, Notification n)7514         public static Notification.Builder recoverBuilder(Context context, Notification n) {
7515             Trace.beginSection("Notification.Builder#recoverBuilder");
7516 
7517             try {
7518                 // Re-create notification context so we can access app resources.
7519                 ApplicationInfo applicationInfo = n.extras.getParcelable(
7520                         EXTRA_BUILDER_APPLICATION_INFO, ApplicationInfo.class);
7521                 Context builderContext;
7522                 if (applicationInfo != null) {
7523                     try {
7524                         builderContext = context.createApplicationContext(applicationInfo,
7525                                 Context.CONTEXT_RESTRICTED);
7526                     } catch (NameNotFoundException e) {
7527                         Log.e(TAG, "ApplicationInfo " + applicationInfo + " not found");
7528                         builderContext = context;  // try with our context
7529                     }
7530                 } else {
7531                     builderContext = context; // try with given context
7532                 }
7533 
7534                 return new Builder(builderContext, n);
7535             } finally {
7536                 Trace.endSection();
7537             }
7538         }
7539 
7540         /**
7541          * Determines whether the platform can generate contextual actions for a notification.
7542          * By default this is true.
7543          */
7544         @NonNull
setAllowSystemGeneratedContextualActions(boolean allowed)7545         public Builder setAllowSystemGeneratedContextualActions(boolean allowed) {
7546             mN.mAllowSystemGeneratedContextualActions = allowed;
7547             return this;
7548         }
7549 
7550         /**
7551          * @deprecated Use {@link #build()} instead.
7552          */
7553         @Deprecated
getNotification()7554         public Notification getNotification() {
7555             return build();
7556         }
7557 
7558         /**
7559          * Combine all of the options that have been set and return a new {@link Notification}
7560          * object.
7561          *
7562          * If this notification has {@link BubbleMetadata} attached that was created with
7563          * a shortcutId a check will be performed to ensure the shortcutId supplied to bubble
7564          * metadata matches the shortcutId set on the  notification builder, if one was set.
7565          * If the shortcutId's were specified but do not match, an exception is thrown here.
7566          *
7567          * @see BubbleMetadata.Builder#Builder(String)
7568          * @see #setShortcutId(String)
7569          */
7570         @NonNull
build()7571         public Notification build() {
7572             // Check shortcut id matches
7573             if (mN.mShortcutId != null
7574                     && mN.mBubbleMetadata != null
7575                     && mN.mBubbleMetadata.getShortcutId() != null
7576                     && !mN.mShortcutId.equals(mN.mBubbleMetadata.getShortcutId())) {
7577                 throw new IllegalArgumentException(
7578                         "Notification and BubbleMetadata shortcut id's don't match,"
7579                                 + " notification: " + mN.mShortcutId
7580                                 + " vs bubble: " + mN.mBubbleMetadata.getShortcutId());
7581             }
7582 
7583             // Adds any new extras provided by the user.
7584             if (mUserExtras != null) {
7585                 final Bundle saveExtras = (Bundle) mUserExtras.clone();
7586                 mN.extras.putAll(saveExtras);
7587             }
7588 
7589             if (!Flags.sortSectionByTime()) {
7590                 mN.creationTime = System.currentTimeMillis();
7591             }
7592 
7593             // lazy stuff from mContext; see comment in Builder(Context, Notification)
7594             Notification.addFieldsFromContext(mContext, mN);
7595 
7596             buildUnstyled();
7597 
7598             if (mStyle != null) {
7599                 mStyle.reduceImageSizes(mContext);
7600                 mStyle.purgeResources();
7601                 mStyle.validate(mContext);
7602                 mStyle.buildStyled(mN);
7603             }
7604             mN.reduceImageSizes(mContext);
7605 
7606             if (mContext.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N
7607                     && !styleDisplaysCustomViewInline()) {
7608                 RemoteViews newContentView = mN.contentView;
7609                 RemoteViews newBigContentView = mN.bigContentView;
7610                 RemoteViews newHeadsUpContentView = mN.headsUpContentView;
7611                 if (newContentView == null) {
7612                     newContentView = createContentView();
7613                     mN.extras.putInt(EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT,
7614                             newContentView.getSequenceNumber());
7615                 }
7616                 if (newBigContentView == null) {
7617                     newBigContentView = createBigContentView();
7618                     if (newBigContentView != null) {
7619                         mN.extras.putInt(EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT,
7620                                 newBigContentView.getSequenceNumber());
7621                     }
7622                 }
7623                 if (newHeadsUpContentView == null) {
7624                     newHeadsUpContentView = createHeadsUpContentView();
7625                     if (newHeadsUpContentView != null) {
7626                         mN.extras.putInt(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT,
7627                                 newHeadsUpContentView.getSequenceNumber());
7628                     }
7629                 }
7630                 // Don't set any of the content views until after they have all been generated,
7631                 //  to avoid the generated .contentView triggering the logic which skips generating
7632                 //  the .bigContentView.
7633                 mN.contentView = newContentView;
7634                 mN.bigContentView = newBigContentView;
7635                 mN.headsUpContentView = newHeadsUpContentView;
7636             }
7637 
7638             if ((mN.defaults & DEFAULT_LIGHTS) != 0) {
7639                 mN.flags |= FLAG_SHOW_LIGHTS;
7640             }
7641 
7642             mN.allPendingIntents = null;
7643 
7644             return mN;
7645         }
7646 
styleDisplaysCustomViewInline()7647         private boolean styleDisplaysCustomViewInline() {
7648             return mStyle != null && mStyle.displayCustomViewInline();
7649         }
7650 
7651         /**
7652          * Apply this Builder to an existing {@link Notification} object.
7653          *
7654          * @hide
7655          */
7656         @NonNull
buildInto(@onNull Notification n)7657         public Notification buildInto(@NonNull Notification n) {
7658             build().cloneInto(n, true);
7659             return n;
7660         }
7661 
7662         /**
7663          * Removes RemoteViews that were created for compatibility from {@param n}, if they did not
7664          * change.
7665          *
7666          * @return {@param n}, if no stripping is needed, otherwise a stripped clone of {@param n}.
7667          *
7668          * @hide
7669          */
maybeCloneStrippedForDelivery(Notification n)7670         public static Notification maybeCloneStrippedForDelivery(Notification n) {
7671             String templateClass = n.extras.getString(EXTRA_TEMPLATE);
7672 
7673             // Only strip views for known Styles because we won't know how to
7674             // re-create them otherwise.
7675             if (!TextUtils.isEmpty(templateClass)
7676                     && getNotificationStyleClass(templateClass) == null) {
7677                 return n;
7678             }
7679 
7680             // Only strip unmodified BuilderRemoteViews.
7681             boolean stripContentView = n.contentView instanceof BuilderRemoteViews &&
7682                     n.extras.getInt(EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT, -1) ==
7683                             n.contentView.getSequenceNumber();
7684             boolean stripBigContentView = n.bigContentView instanceof BuilderRemoteViews &&
7685                     n.extras.getInt(EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT, -1) ==
7686                             n.bigContentView.getSequenceNumber();
7687             boolean stripHeadsUpContentView = n.headsUpContentView instanceof BuilderRemoteViews &&
7688                     n.extras.getInt(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT, -1) ==
7689                             n.headsUpContentView.getSequenceNumber();
7690 
7691             // Nothing to do here, no need to clone.
7692             if (!stripContentView && !stripBigContentView && !stripHeadsUpContentView) {
7693                 return n;
7694             }
7695 
7696             Notification clone = n.clone();
7697             if (stripContentView) {
7698                 clone.contentView = null;
7699                 clone.extras.remove(EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT);
7700             }
7701             if (stripBigContentView) {
7702                 clone.bigContentView = null;
7703                 clone.extras.remove(EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT);
7704             }
7705             if (stripHeadsUpContentView) {
7706                 clone.headsUpContentView = null;
7707                 clone.extras.remove(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT);
7708             }
7709             return clone;
7710         }
7711 
getHeaderLayoutResource()7712         private int getHeaderLayoutResource() {
7713             if (Flags.notificationsRedesignTemplates()) {
7714                 return R.layout.notification_2025_template_header;
7715             } else {
7716                 return R.layout.notification_template_header;
7717             }
7718         }
7719 
7720         @UnsupportedAppUsage
getCollapsedBaseLayoutResource()7721         private int getCollapsedBaseLayoutResource() {
7722             if (Flags.notificationsRedesignTemplates()) {
7723                 return R.layout.notification_2025_template_collapsed_base;
7724             } else {
7725                 return R.layout.notification_template_material_base;
7726             }
7727         }
7728 
getHeadsUpBaseLayoutResource()7729         private int getHeadsUpBaseLayoutResource() {
7730             if (Flags.notificationsRedesignTemplates()) {
7731                 return R.layout.notification_2025_template_heads_up_base;
7732             } else {
7733                 return R.layout.notification_template_material_heads_up_base;
7734             }
7735         }
7736 
getCompactHeadsUpBaseLayoutResource()7737         private int getCompactHeadsUpBaseLayoutResource() {
7738             if (Flags.notificationsRedesignTemplates()) {
7739                 return R.layout.notification_2025_template_compact_heads_up_base;
7740             } else {
7741                 return R.layout.notification_template_material_compact_heads_up_base;
7742             }
7743         }
7744 
getMessagingCompactHeadsUpLayoutResource()7745         private int getMessagingCompactHeadsUpLayoutResource() {
7746             if (Flags.notificationsRedesignTemplates()) {
7747                 return R.layout.notification_2025_template_compact_heads_up_messaging;
7748             } else {
7749                 return R.layout.notification_template_material_messaging_compact_heads_up;
7750             }
7751         }
7752 
getExpandedBaseLayoutResource()7753         private int getExpandedBaseLayoutResource() {
7754             if (Flags.notificationsRedesignTemplates()) {
7755                 return R.layout.notification_2025_template_expanded_base;
7756             } else {
7757                 return R.layout.notification_template_material_big_base;
7758             }
7759         }
7760 
getBigPictureLayoutResource()7761         private int getBigPictureLayoutResource() {
7762             if (Flags.notificationsRedesignTemplates()) {
7763                 return R.layout.notification_2025_template_expanded_big_picture;
7764             } else {
7765                 return R.layout.notification_template_material_big_picture;
7766             }
7767         }
7768 
getBigTextLayoutResource()7769         private int getBigTextLayoutResource() {
7770             if (Flags.notificationsRedesignTemplates()) {
7771                 return R.layout.notification_2025_template_expanded_big_text;
7772             } else {
7773                 return R.layout.notification_template_material_big_text;
7774             }
7775         }
7776 
getInboxLayoutResource()7777         private int getInboxLayoutResource() {
7778             if (Flags.notificationsRedesignTemplates()) {
7779                 return R.layout.notification_2025_template_expanded_inbox;
7780             } else {
7781                 return R.layout.notification_template_material_inbox;
7782             }
7783         }
7784 
getCollapsedMessagingLayoutResource()7785         private int getCollapsedMessagingLayoutResource() {
7786             if (Flags.notificationsRedesignTemplates()) {
7787                 return R.layout.notification_2025_template_collapsed_messaging;
7788             } else {
7789                 return R.layout.notification_template_material_messaging;
7790             }
7791         }
7792 
getExpandedMessagingLayoutResource()7793         private int getExpandedMessagingLayoutResource() {
7794             if (Flags.notificationsRedesignTemplates()) {
7795                 return R.layout.notification_2025_template_expanded_messaging;
7796             } else {
7797                 return R.layout.notification_template_material_big_messaging;
7798             }
7799         }
7800 
getCollapsedMediaLayoutResource()7801         private int getCollapsedMediaLayoutResource() {
7802             if (Flags.notificationsRedesignTemplates()) {
7803                 return R.layout.notification_2025_template_collapsed_media;
7804             } else {
7805                 return R.layout.notification_template_material_media;
7806             }
7807         }
7808 
getExpandedMediaLayoutResource()7809         private int getExpandedMediaLayoutResource() {
7810             if (Flags.notificationsRedesignTemplates()) {
7811                 return R.layout.notification_2025_template_expanded_media;
7812             } else {
7813                 return R.layout.notification_template_material_big_media;
7814             }
7815         }
7816 
7817         // Note: In the 2025 redesign, we use two separate layouts for the collapsed and expanded
7818         //  version of conversations. See below.
getConversationLayoutResource()7819         private int getConversationLayoutResource() {
7820             return R.layout.notification_template_material_conversation;
7821         }
7822 
getCollapsedConversationLayoutResource()7823         private int getCollapsedConversationLayoutResource() {
7824             return R.layout.notification_2025_template_collapsed_conversation;
7825         }
7826 
getExpandedConversationLayoutResource()7827         private int getExpandedConversationLayoutResource() {
7828             return R.layout.notification_2025_template_expanded_conversation;
7829         }
7830 
getCollapsedCallLayoutResource()7831         private int getCollapsedCallLayoutResource() {
7832             if (Flags.notificationsRedesignTemplates()) {
7833                 return R.layout.notification_2025_template_collapsed_call;
7834             } else {
7835                 return R.layout.notification_template_material_call;
7836             }
7837         }
7838 
getExpandedCallLayoutResource()7839         private int getExpandedCallLayoutResource() {
7840             if (Flags.notificationsRedesignTemplates()) {
7841                 return R.layout.notification_2025_template_expanded_call;
7842             } else {
7843                 return R.layout.notification_template_material_big_call;
7844             }
7845         }
7846 
getProgressLayoutResource()7847         private int getProgressLayoutResource() {
7848             if (Flags.notificationsRedesignTemplates()) {
7849                 return R.layout.notification_2025_template_expanded_progress;
7850             } else {
7851                 return R.layout.notification_template_material_progress;
7852             }
7853         }
7854 
getActionLayoutResource()7855         private int getActionLayoutResource() {
7856             return R.layout.notification_material_action;
7857         }
7858 
getEmphasizedActionLayoutResource()7859         private int getEmphasizedActionLayoutResource() {
7860             return R.layout.notification_material_action_emphasized;
7861         }
7862 
getEmphasizedTombstoneActionLayoutResource()7863         private int getEmphasizedTombstoneActionLayoutResource() {
7864             return R.layout.notification_material_action_emphasized_tombstone;
7865         }
7866 
getActionTombstoneLayoutResource()7867         private int getActionTombstoneLayoutResource() {
7868             return R.layout.notification_material_action_tombstone;
7869         }
7870 
getBackgroundColor(StandardTemplateParams p)7871         private @ColorInt int getBackgroundColor(StandardTemplateParams p) {
7872             return getColors(p).getBackgroundColor();
7873         }
7874 
textColorsNeedInversion()7875         private boolean textColorsNeedInversion() {
7876             if (mStyle == null || !MediaStyle.class.equals(mStyle.getClass())) {
7877                 return false;
7878             }
7879             int targetSdkVersion = mContext.getApplicationInfo().targetSdkVersion;
7880             return targetSdkVersion > Build.VERSION_CODES.M
7881                     && targetSdkVersion < Build.VERSION_CODES.O;
7882         }
7883 
7884         /**
7885          * Get the text that should be displayed in the statusBar when heads upped. This is
7886          * usually just the app name, but may be different depending on the style.
7887          *
7888          * @param publicMode If true, return a text that is safe to display in public.
7889          *
7890          * @hide
7891          */
getHeadsUpStatusBarText(boolean publicMode)7892         public CharSequence getHeadsUpStatusBarText(boolean publicMode) {
7893             if (mStyle != null && !publicMode) {
7894                 CharSequence text = mStyle.getHeadsUpStatusBarText();
7895                 if (!TextUtils.isEmpty(text)) {
7896                     return text;
7897                 }
7898             }
7899             return loadHeaderAppName();
7900         }
7901 
7902         /**
7903          * @return if this builder uses a template
7904          *
7905          * @hide
7906          */
usesTemplate()7907         public boolean usesTemplate() {
7908             return (mN.contentView == null && mN.headsUpContentView == null
7909                     && mN.bigContentView == null)
7910                     || styleDisplaysCustomViewInline();
7911         }
7912     }
7913 
7914     /**
7915      * Reduces the image sizes to conform to a maximum allowed size. This also processes all custom
7916      * remote views.
7917      *
7918      * @hide
7919      */
reduceImageSizes(Context context)7920     void reduceImageSizes(Context context) {
7921         if (extras.getBoolean(EXTRA_REDUCED_IMAGES)) {
7922             return;
7923         }
7924         boolean isLowRam = ActivityManager.isLowRamDeviceStatic();
7925 
7926         if (mSmallIcon != null
7927                 // Only bitmap icons can be downscaled.
7928                 && (mSmallIcon.getType() == Icon.TYPE_BITMAP
7929                         || mSmallIcon.getType() == Icon.TYPE_ADAPTIVE_BITMAP)) {
7930             Resources resources = context.getResources();
7931             int maxSize = resources.getDimensionPixelSize(
7932                     isLowRam ? R.dimen.notification_small_icon_size_low_ram
7933                             : R.dimen.notification_small_icon_size);
7934             mSmallIcon.scaleDownIfNecessary(maxSize, maxSize);
7935         }
7936 
7937         if (mLargeIcon != null || largeIcon != null) {
7938             Resources resources = context.getResources();
7939             int maxSize = resources.getDimensionPixelSize(isLowRam
7940                     ? R.dimen.notification_right_icon_size_low_ram
7941                     : R.dimen.notification_right_icon_size);
7942             if (mLargeIcon != null) {
7943                 mLargeIcon.scaleDownIfNecessary(maxSize, maxSize);
7944             }
7945             if (largeIcon != null) {
7946                 largeIcon = Icon.scaleDownIfNecessary(largeIcon, maxSize, maxSize);
7947             }
7948         }
7949         reduceImageSizesForRemoteView(contentView, context, isLowRam);
7950         reduceImageSizesForRemoteView(headsUpContentView, context, isLowRam);
7951         reduceImageSizesForRemoteView(bigContentView, context, isLowRam);
7952         extras.putBoolean(EXTRA_REDUCED_IMAGES, true);
7953     }
7954 
reduceImageSizesForRemoteView(RemoteViews remoteView, Context context, boolean isLowRam)7955     private void reduceImageSizesForRemoteView(RemoteViews remoteView, Context context,
7956             boolean isLowRam) {
7957         if (remoteView != null) {
7958             Resources resources = context.getResources();
7959             int maxWidth = resources.getDimensionPixelSize(isLowRam
7960                     ? R.dimen.notification_custom_view_max_image_width_low_ram
7961                     : R.dimen.notification_custom_view_max_image_width);
7962             int maxHeight = resources.getDimensionPixelSize(isLowRam
7963                     ? R.dimen.notification_custom_view_max_image_height_low_ram
7964                     : R.dimen.notification_custom_view_max_image_height);
7965             remoteView.reduceImageSizes(maxWidth, maxHeight);
7966         }
7967     }
7968 
7969     /**
7970      * @return whether this notification is a foreground service notification
7971      * @hide
7972      */
isForegroundService()7973     public boolean isForegroundService() {
7974         return (flags & Notification.FLAG_FOREGROUND_SERVICE) != 0;
7975     }
7976 
7977     /**
7978      * @return whether this notification is associated with a user initiated job
7979      * @hide
7980      */
7981     @TestApi
isUserInitiatedJob()7982     public boolean isUserInitiatedJob() {
7983         return (flags & Notification.FLAG_USER_INITIATED_JOB) != 0;
7984     }
7985 
7986     /**
7987      * @return whether this notification is associated with either a foreground service or
7988      * a user initiated job
7989      * @hide
7990      */
isFgsOrUij()7991     public boolean isFgsOrUij() {
7992         return isForegroundService() || isUserInitiatedJob();
7993     }
7994 
7995     /**
7996      * Describe whether this notification's content such that it should always display
7997      * immediately when tied to a foreground service, even if the system might generally
7998      * avoid showing the notifications for short-lived foreground service lifetimes.
7999      *
8000      * Immediate visibility of the Notification is indicated when:
8001      * <ul>
8002      *     <li>The app specifically indicated it with
8003      *         {@link Notification.Builder#setForegroundServiceBehavior(int)
8004      *         setForegroundServiceBehavior(BEHAVIOR_IMMEDIATE_DISPLAY)}</li>
8005      *     <li>It is a media notification or has an associated media session</li>
8006      *     <li>It is a call or navigation notification</li>
8007      *     <li>It provides additional action affordances</li>
8008      * </ul>
8009      *
8010      * If the app has specified
8011      * {@code NotificationBuilder.setForegroundServiceBehavior(BEHAVIOR_DEFERRED_DISPLAY)}
8012      * then this method will return {@code false} and notification visibility will be
8013      * deferred following the service's transition to the foreground state even in the
8014      * circumstances described above.
8015      *
8016      * @return whether this notification should be displayed immediately when
8017      * its associated service transitions to the foreground state
8018      * @hide
8019      */
8020     @TestApi
shouldShowForegroundImmediately()8021     public boolean shouldShowForegroundImmediately() {
8022         // Has the app demanded immediate display?
8023         if (mFgsDeferBehavior == FOREGROUND_SERVICE_IMMEDIATE) {
8024             return true;
8025         }
8026 
8027         // Has the app demanded deferred display?
8028         if (mFgsDeferBehavior == FOREGROUND_SERVICE_DEFERRED) {
8029             return false;
8030         }
8031 
8032         // We show these sorts of notifications immediately in the absence of
8033         // any explicit app declaration
8034         if (isMediaNotification()
8035                     || CATEGORY_CALL.equals(category)
8036                     || CATEGORY_NAVIGATION.equals(category)
8037                     || (actions != null && actions.length > 0)) {
8038             return true;
8039         }
8040 
8041         // No extenuating circumstances: defer visibility
8042         return false;
8043     }
8044 
8045     /**
8046      * Has forced deferral for FGS purposes been specified?
8047      * @hide
8048      */
isForegroundDisplayForceDeferred()8049     public boolean isForegroundDisplayForceDeferred() {
8050         return FOREGROUND_SERVICE_DEFERRED == mFgsDeferBehavior;
8051     }
8052 
8053     /**
8054      * @return the style class of this notification
8055      * @hide
8056      */
getNotificationStyle()8057     public Class<? extends Notification.Style> getNotificationStyle() {
8058         String templateClass = extras.getString(Notification.EXTRA_TEMPLATE);
8059 
8060         if (!TextUtils.isEmpty(templateClass)) {
8061             return Notification.getNotificationStyleClass(templateClass);
8062         }
8063         return null;
8064     }
8065 
8066     /**
8067      * @return whether the style of this notification is the one provided
8068      * @hide
8069      */
isStyle(@onNull Class<? extends Style> styleClass)8070     public boolean isStyle(@NonNull Class<? extends Style> styleClass) {
8071         String templateClass = extras.getString(Notification.EXTRA_TEMPLATE);
8072         return Objects.equals(templateClass, styleClass.getName());
8073     }
8074 
8075     /**
8076      * @return true if this notification is colorized *for the purposes of ranking*.  If the
8077      * {@link #color} is {@link #COLOR_DEFAULT} this will be true, even though the actual
8078      * appearance of the notification may not be "colorized".
8079      *
8080      * @hide
8081      */
isColorized()8082     public boolean isColorized() {
8083         return isColorizedRequested()
8084                 && (hasColorizedPermission() || isFgsOrUij() || isPromotedOngoing());
8085     }
8086 
8087     /**
8088      * @return true if this notification has requested to be colorized, regardless of whether it
8089      * meets the requirements to be displayed that way.
8090      */
isColorizedRequested()8091     private boolean isColorizedRequested() {
8092         return extras.getBoolean(EXTRA_COLORIZED);
8093     }
8094 
8095     /**
8096      * Returns whether an app can colorize due to the android.permission.USE_COLORIZED_NOTIFICATIONS
8097      * permission. The permission is checked when a notification is enqueued.
8098      *
8099      * @hide
8100      */
hasColorizedPermission()8101     public boolean hasColorizedPermission() {
8102         return (flags & Notification.FLAG_CAN_COLORIZE) != 0;
8103     }
8104 
8105     /**
8106      * Returns whether this notification is a promoted ongoing notification.
8107      *
8108      * This requires the Notification.FLAG_PROMOTED_ONGOING flag to be set
8109      * (which may be true once the api_rich_ongoing feature flag is enabled),
8110      * and requires that the ui_rich_ongoing feature flag is enabled.
8111      *
8112      * @hide
8113      */
isPromotedOngoing()8114     public boolean isPromotedOngoing() {
8115         return Flags.uiRichOngoing() && (flags & Notification.FLAG_PROMOTED_ONGOING) != 0;
8116     }
8117 
8118     /**
8119      * @return true if this is a media style notification with a media session
8120      *
8121      * @hide
8122      */
isMediaNotification()8123     public boolean isMediaNotification() {
8124         Class<? extends Style> style = getNotificationStyle();
8125         boolean isMediaStyle = (MediaStyle.class.equals(style)
8126                 || DecoratedMediaCustomViewStyle.class.equals(style));
8127 
8128         boolean hasMediaSession = extras.getParcelable(Notification.EXTRA_MEDIA_SESSION,
8129                 MediaSession.Token.class) != null;
8130 
8131         return isMediaStyle && hasMediaSession;
8132     }
8133 
8134     /**
8135      * @return true for custom notifications, including notifications
8136      * with DecoratedCustomViewStyle or DecoratedMediaCustomViewStyle,
8137      * and other notifications with user-provided custom views.
8138      *
8139      * @hide
8140      */
isCustomNotification()8141     public Boolean isCustomNotification() {
8142         if (contentView == null
8143                 && bigContentView == null
8144                 && headsUpContentView == null) {
8145             return false;
8146         }
8147         return true;
8148     }
8149 
8150     /**
8151      * @return true if this notification is showing as a bubble
8152      *
8153      * @hide
8154      */
isBubbleNotification()8155     public boolean isBubbleNotification() {
8156         return (flags & Notification.FLAG_BUBBLE) != 0;
8157     }
8158 
hasLargeIcon()8159     private boolean hasLargeIcon() {
8160         return mLargeIcon != null || largeIcon != null;
8161     }
8162 
8163     /**
8164      * Returns #when, unless it's set to 0, which should be shown as/treated as a 'current'
8165      * notification. 0 is treated as a special value because it was special in an old version of
8166      * android, and some apps are still (incorrectly) using it.
8167      *
8168      * @hide
8169      */
getWhen()8170     public long getWhen() {
8171         if (Flags.sortSectionByTime()) {
8172             if (when == 0) {
8173                 return creationTime;
8174             }
8175         }
8176         return when;
8177     }
8178 
8179     /**
8180      * @return true if the notification will show the time; false otherwise
8181      * @hide
8182      */
showsTime()8183     public boolean showsTime() {
8184         if (Flags.sortSectionByTime()) {
8185             return extras.getBoolean(EXTRA_SHOW_WHEN);
8186         }
8187         return when != 0 && extras.getBoolean(EXTRA_SHOW_WHEN);
8188     }
8189 
8190     /**
8191      * @return true if the notification will show a chronometer; false otherwise
8192      * @hide
8193      */
showsChronometer()8194     public boolean showsChronometer() {
8195         if (Flags.sortSectionByTime()) {
8196             return extras.getBoolean(EXTRA_SHOW_CHRONOMETER);
8197         }
8198         return when != 0 && extras.getBoolean(EXTRA_SHOW_CHRONOMETER);
8199     }
8200 
8201     /**
8202      * @return true if the notification has image
8203      */
hasImage()8204     public boolean hasImage() {
8205         if (isStyle(MessagingStyle.class) && extras != null) {
8206             final Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES,
8207                     Parcelable.class);
8208             if (!ArrayUtils.isEmpty(messages)) {
8209                 for (MessagingStyle.Message m : MessagingStyle.Message
8210                         .getMessagesFromBundleArray(messages)) {
8211                     if (m.getDataUri() != null
8212                             && m.getDataMimeType() != null
8213                             && m.getDataMimeType().startsWith("image/")) {
8214                         return true;
8215                     }
8216                 }
8217             }
8218         } else if (hasLargeIcon()) {
8219             return true;
8220         } else if (extras.containsKey(EXTRA_BACKGROUND_IMAGE_URI)) {
8221             return true;
8222         }
8223         return false;
8224     }
8225 
8226 
8227     /**
8228      * @removed
8229      */
8230     @SystemApi
getNotificationStyleClass(String templateClass)8231     public static Class<? extends Style> getNotificationStyleClass(String templateClass) {
8232         for (Class<? extends Style> innerClass : PLATFORM_STYLE_CLASSES) {
8233             if (templateClass.equals(innerClass.getName())) {
8234                 return innerClass;
8235             }
8236         }
8237 
8238         if (Flags.apiRichOngoing()) {
8239             if (templateClass.equals(ProgressStyle.class.getName())) {
8240                 return ProgressStyle.class;
8241             }
8242         }
8243         return null;
8244     }
8245 
buildCustomContentIntoTemplate(@onNull Context context, @NonNull RemoteViews template, @Nullable RemoteViews customContent, @NonNull StandardTemplateParams p, @NonNull TemplateBindResult result)8246     private static void buildCustomContentIntoTemplate(@NonNull Context context,
8247             @NonNull RemoteViews template, @Nullable RemoteViews customContent,
8248             @NonNull StandardTemplateParams p, @NonNull TemplateBindResult result) {
8249         int childIndex = -1;
8250         if (customContent != null) {
8251             // Need to clone customContent before adding, because otherwise it can no longer be
8252             // parceled independently of remoteViews.
8253             customContent = customContent.clone();
8254             if (p.mHeaderless) {
8255                 template.removeFromParent(R.id.notification_top_line);
8256                 // We do not know how many lines ar emote view has, so we presume it has 2;  this
8257                 // ensures that we don't under-pad the content, which could lead to abuse, at the
8258                 // cost of making single-line custom content over-padded.
8259                 Builder.setHeaderlessVerticalMargins(template, p, true /* hasSecondLine */);
8260             } else {
8261                 // also update the end margin to account for the large icon or expander
8262                 Resources resources = context.getResources();
8263                 result.mTitleMarginSet.applyToView(template, R.id.notification_main_column,
8264                         resources.getDimension(R.dimen.notification_content_margin_end)
8265                                 / resources.getDisplayMetrics().density);
8266             }
8267             template.removeAllViewsExceptId(R.id.notification_main_column, R.id.progress);
8268             template.addView(R.id.notification_main_column, customContent, 0 /* index */);
8269             template.addFlags(RemoteViews.FLAG_REAPPLY_DISALLOWED);
8270             childIndex = 0;
8271         }
8272         template.setIntTag(R.id.notification_main_column,
8273                 com.android.internal.R.id.notification_custom_view_index_tag,
8274                 childIndex);
8275     }
8276 
8277     /**
8278      * An object that can apply a rich notification style to a {@link Notification.Builder}
8279      * object.
8280      */
8281     public static abstract class Style {
8282 
8283         /**
8284          * @deprecated public access to the constructor of Style() is only useful for creating
8285          * custom subclasses, but that has actually been impossible due to hidden abstract
8286          * methods, so this constructor is now officially deprecated to clarify that this is
8287          * intended to be disallowed.
8288          */
8289         @Deprecated
Style()8290         public Style() {}
8291 
8292         /**
8293          * The number of items allowed simulatanously in the remote input history.
8294          * @hide
8295          */
8296         static final int MAX_REMOTE_INPUT_HISTORY_LINES = 3;
8297         private CharSequence mBigContentTitle;
8298 
8299         /**
8300          * @hide
8301          */
8302         protected CharSequence mSummaryText = null;
8303 
8304         /**
8305          * @hide
8306          */
8307         protected boolean mSummaryTextSet = false;
8308 
8309         protected Builder mBuilder;
8310 
8311         /**
8312          * Overrides ContentTitle in the expanded form of the template.
8313          * This defaults to the value passed to setContentTitle().
8314          */
internalSetBigContentTitle(CharSequence title)8315         protected void internalSetBigContentTitle(CharSequence title) {
8316             mBigContentTitle = title;
8317         }
8318 
8319         /**
8320          * Set the first line of text after the detail section in the expanded form of the template.
8321          */
internalSetSummaryText(CharSequence cs)8322         protected void internalSetSummaryText(CharSequence cs) {
8323             mSummaryText = cs;
8324             mSummaryTextSet = true;
8325         }
8326 
setBuilder(Builder builder)8327         public void setBuilder(Builder builder) {
8328             if (mBuilder != builder) {
8329                 mBuilder = builder;
8330                 if (mBuilder != null) {
8331                     mBuilder.setStyle(this);
8332                 }
8333             }
8334         }
8335 
checkBuilder()8336         protected void checkBuilder() {
8337             if (mBuilder == null) {
8338                 throw new IllegalArgumentException("Style requires a valid Builder object");
8339             }
8340         }
8341 
getStandardView(int layoutId)8342         protected RemoteViews getStandardView(int layoutId) {
8343             // TODO(jeffdq): set the view type based on the layout resource?
8344             StandardTemplateParams p = mBuilder.mParams.reset()
8345                     .viewType(StandardTemplateParams.VIEW_TYPE_UNSPECIFIED)
8346                     .fillTextsFrom(mBuilder);
8347             return getStandardView(layoutId, p, null);
8348         }
8349 
8350 
8351         /**
8352          * Get the standard view for this style.
8353          *
8354          * @param layoutId The layout id to use.
8355          * @param p the params for this inflation.
8356          * @param result The result where template bind information is saved.
8357          * @return A remoteView for this style.
8358          * @hide
8359          */
getStandardView(int layoutId, StandardTemplateParams p, TemplateBindResult result)8360         protected RemoteViews getStandardView(int layoutId, StandardTemplateParams p,
8361                 TemplateBindResult result) {
8362             checkBuilder();
8363 
8364             if (mBigContentTitle != null) {
8365                 p.mTitle = mBigContentTitle;
8366             }
8367 
8368             return mBuilder.applyStandardTemplateWithActions(layoutId, p, result);
8369         }
8370 
8371         /**
8372          * Construct a Style-specific RemoteViews for the collapsed notification layout.
8373          * The default implementation has nothing additional to add.
8374          *
8375          * @hide
8376          */
makeContentView()8377         public RemoteViews makeContentView() {
8378             return null;
8379         }
8380 
8381         /**
8382          * Construct a Style-specific RemoteViews for the final expanded notification layout.
8383          * @hide
8384          */
makeExpandedContentView()8385         public RemoteViews makeExpandedContentView() {
8386             return null;
8387         }
8388 
8389         /**
8390          * Construct a Style-specific RemoteViews for the final HUN layout.
8391          *
8392          * @hide
8393          */
makeHeadsUpContentView()8394         public RemoteViews makeHeadsUpContentView() {
8395             return null;
8396         }
8397 
8398         /**
8399          * Construct a Style-specific RemoteViews for the final compact HUN layout.
8400          * return null to use the standard compact heads up view.
8401          * @hide
8402          */
8403         @Nullable
makeCompactHeadsUpContentView()8404         public RemoteViews makeCompactHeadsUpContentView() {
8405             return null;
8406         }
8407 
8408         /**
8409          * Apply any style-specific extras to this notification before shipping it out.
8410          * @hide
8411          */
addExtras(Bundle extras)8412         public void addExtras(Bundle extras) {
8413             if (mSummaryTextSet) {
8414                 extras.putCharSequence(EXTRA_SUMMARY_TEXT, mSummaryText);
8415             }
8416             if (mBigContentTitle != null) {
8417                 extras.putCharSequence(EXTRA_TITLE_BIG, mBigContentTitle);
8418             }
8419             extras.putString(EXTRA_TEMPLATE, this.getClass().getName());
8420         }
8421 
8422         /**
8423          * Reconstruct the internal state of this Style object from extras.
8424          * @hide
8425          */
restoreFromExtras(Bundle extras)8426         protected void restoreFromExtras(Bundle extras) {
8427             if (extras.containsKey(EXTRA_SUMMARY_TEXT)) {
8428                 mSummaryText = extras.getCharSequence(EXTRA_SUMMARY_TEXT);
8429                 mSummaryTextSet = true;
8430             }
8431             if (extras.containsKey(EXTRA_TITLE_BIG)) {
8432                 mBigContentTitle = extras.getCharSequence(EXTRA_TITLE_BIG);
8433             }
8434         }
8435 
8436 
8437         /**
8438          * @hide
8439          */
buildStyled(Notification wip)8440         public Notification buildStyled(Notification wip) {
8441             addExtras(wip.extras);
8442             return wip;
8443         }
8444 
8445         /**
8446          * @hide
8447          */
purgeResources()8448         public void purgeResources() {}
8449 
8450         /**
8451          * Calls {@link android.app.Notification.Builder#build()} on the Builder this Style is
8452          * attached to.
8453          * <p>
8454          * Note: Calling build() multiple times returns the same Notification instance,
8455          * so reusing a builder to create multiple Notifications is discouraged.
8456          *
8457          * @return the fully constructed Notification.
8458          */
build()8459         public Notification build() {
8460             checkBuilder();
8461             return mBuilder.build();
8462         }
8463 
8464         /**
8465          * @hide
8466          * @return Whether we should put the summary be put into the notification header
8467          */
hasSummaryInHeader()8468         public boolean hasSummaryInHeader() {
8469             return true;
8470         }
8471 
8472         /**
8473          * @hide
8474          * @return Whether custom content views are displayed inline in the style
8475          */
displayCustomViewInline()8476         public boolean displayCustomViewInline() {
8477             return false;
8478         }
8479 
8480         /**
8481          * Reduces the image sizes contained in this style.
8482          *
8483          * @hide
8484          */
reduceImageSizes(Context context)8485         public void reduceImageSizes(Context context) {
8486         }
8487 
8488         /**
8489          * Validate that this style was properly composed. This is called at build time.
8490          * @hide
8491          */
validate(Context context)8492         public void validate(Context context) {
8493         }
8494 
8495         /**
8496          * @hide
8497          */
8498         @SuppressWarnings("HiddenAbstractMethod")
areNotificationsVisiblyDifferent(Style other)8499         public abstract boolean areNotificationsVisiblyDifferent(Style other);
8500 
8501         /**
8502          * @return the text that should be displayed in the statusBar when heads-upped.
8503          * If {@code null} is returned, the default implementation will be used.
8504          *
8505          * @hide
8506          */
getHeadsUpStatusBarText()8507         public CharSequence getHeadsUpStatusBarText() {
8508             return null;
8509         }
8510     }
8511 
8512     /**
8513      * Helper class for generating large-format notifications that include a large image attachment.
8514      *
8515      * Here's how you'd set the <code>BigPictureStyle</code> on a notification:
8516      * <pre class="prettyprint">
8517      * Notification notif = new Notification.Builder(mContext)
8518      *     .setContentTitle(&quot;New photo from &quot; + sender.toString())
8519      *     .setContentText(subject)
8520      *     .setSmallIcon(R.drawable.new_post)
8521      *     .setLargeIcon(aBitmap)
8522      *     .setStyle(new Notification.BigPictureStyle()
8523      *         .bigPicture(aBigBitmap))
8524      *     .build();
8525      * </pre>
8526      *
8527      * @see Notification#bigContentView
8528      */
8529     public static class BigPictureStyle extends Style {
8530         private Icon mPictureIcon;
8531         private Icon mBigLargeIcon;
8532         private boolean mBigLargeIconSet = false;
8533         private CharSequence mPictureContentDescription;
8534         private boolean mShowBigPictureWhenCollapsed;
8535 
BigPictureStyle()8536         public BigPictureStyle() {
8537         }
8538 
8539         /**
8540          * @deprecated use {@code BigPictureStyle()}.
8541          */
8542         @Deprecated
BigPictureStyle(Builder builder)8543         public BigPictureStyle(Builder builder) {
8544             setBuilder(builder);
8545         }
8546 
8547         /**
8548          * Overrides ContentTitle in the expanded form of the template.
8549          * This defaults to the value passed to setContentTitle().
8550          */
8551         @NonNull
setBigContentTitle(@ullable CharSequence title)8552         public BigPictureStyle setBigContentTitle(@Nullable CharSequence title) {
8553             internalSetBigContentTitle(safeCharSequence(title));
8554             return this;
8555         }
8556 
8557         /**
8558          * Set the first line of text after the detail section in the expanded form of the template.
8559          */
8560         @NonNull
setSummaryText(@ullable CharSequence cs)8561         public BigPictureStyle setSummaryText(@Nullable CharSequence cs) {
8562             internalSetSummaryText(safeCharSequence(cs));
8563             return this;
8564         }
8565 
8566         /**
8567          * Set the content description of the big picture.
8568          */
8569         @NonNull
setContentDescription( @ullable CharSequence contentDescription)8570         public BigPictureStyle setContentDescription(
8571                 @Nullable CharSequence contentDescription) {
8572             mPictureContentDescription = contentDescription;
8573             return this;
8574         }
8575 
8576         /**
8577          * @hide
8578          */
8579         @Nullable
getBigPicture()8580         public Icon getBigPicture() {
8581             if (mPictureIcon != null) {
8582                 return mPictureIcon;
8583             }
8584             return null;
8585         }
8586 
8587         /**
8588          * Provide the bitmap to be used as the payload for the BigPicture notification.
8589          */
8590         @NonNull
bigPicture(@ullable Bitmap b)8591         public BigPictureStyle bigPicture(@Nullable Bitmap b) {
8592             mPictureIcon = b == null ? null : Icon.createWithBitmap(b);
8593             return this;
8594         }
8595 
8596         /**
8597          * Provide the content Uri to be used as the payload for the BigPicture notification.
8598          */
8599         @NonNull
bigPicture(@ullable Icon icon)8600         public BigPictureStyle bigPicture(@Nullable Icon icon) {
8601             mPictureIcon = icon;
8602             return this;
8603         }
8604 
8605         /**
8606          * When set, the {@link #bigPicture(Bitmap) big picture} of this style will be promoted and
8607          * shown in place of the {@link Builder#setLargeIcon(Icon) large icon} in the collapsed
8608          * state of this notification.
8609          */
8610         @NonNull
showBigPictureWhenCollapsed(boolean show)8611         public BigPictureStyle showBigPictureWhenCollapsed(boolean show) {
8612             mShowBigPictureWhenCollapsed = show;
8613             return this;
8614         }
8615 
8616         /**
8617          * Override the large icon when the expanded notification is shown.
8618          */
8619         @NonNull
bigLargeIcon(@ullable Bitmap b)8620         public BigPictureStyle bigLargeIcon(@Nullable Bitmap b) {
8621             return bigLargeIcon(b != null ? Icon.createWithBitmap(b) : null);
8622         }
8623 
8624         /**
8625          * Override the large icon when the expanded notification is shown.
8626          */
8627         @NonNull
bigLargeIcon(@ullable Icon icon)8628         public BigPictureStyle bigLargeIcon(@Nullable Icon icon) {
8629             mBigLargeIconSet = true;
8630             mBigLargeIcon = icon;
8631             return this;
8632         }
8633 
8634         /** @hide */
8635         public static final int MIN_ASHMEM_BITMAP_SIZE = 128 * (1 << 10);
8636 
8637         /**
8638          * @hide
8639          */
8640         @Override
purgeResources()8641         public void purgeResources() {
8642             super.purgeResources();
8643             if (mPictureIcon != null) {
8644                 mPictureIcon.convertToAshmem();
8645             }
8646             if (mBigLargeIcon != null) {
8647                 mBigLargeIcon.convertToAshmem();
8648             }
8649         }
8650 
8651         /**
8652          * @hide
8653          */
8654         @Override
reduceImageSizes(Context context)8655         public void reduceImageSizes(Context context) {
8656             super.reduceImageSizes(context);
8657             Resources resources = context.getResources();
8658             boolean isLowRam = ActivityManager.isLowRamDeviceStatic();
8659             if (mPictureIcon != null) {
8660                 int maxPictureHeight = resources.getDimensionPixelSize(isLowRam
8661                         ? R.dimen.notification_big_picture_max_height_low_ram
8662                         : R.dimen.notification_big_picture_max_height);
8663                 int maxPictureWidth = resources.getDimensionPixelSize(isLowRam
8664                         ? R.dimen.notification_big_picture_max_width_low_ram
8665                         : R.dimen.notification_big_picture_max_width);
8666                 mPictureIcon.scaleDownIfNecessary(maxPictureWidth, maxPictureHeight);
8667             }
8668             if (mBigLargeIcon != null) {
8669                 int rightIconSize = resources.getDimensionPixelSize(isLowRam
8670                         ? R.dimen.notification_right_icon_size_low_ram
8671                         : R.dimen.notification_right_icon_size);
8672                 mBigLargeIcon.scaleDownIfNecessary(rightIconSize, rightIconSize);
8673             }
8674         }
8675 
8676         /**
8677          * @hide
8678          */
8679         @Override
makeContentView()8680         public RemoteViews makeContentView() {
8681             if (mPictureIcon == null || !mShowBigPictureWhenCollapsed) {
8682                 return super.makeContentView();
8683             }
8684 
8685             StandardTemplateParams p = mBuilder.mParams.reset()
8686                     .viewType(StandardTemplateParams.VIEW_TYPE_NORMAL)
8687                     .fillTextsFrom(mBuilder)
8688                     .promotedPicture(mPictureIcon);
8689             return getStandardView(mBuilder.getCollapsedBaseLayoutResource(), p, null /* result */);
8690         }
8691 
8692         /**
8693          * @hide
8694          */
8695         @Override
makeHeadsUpContentView()8696         public RemoteViews makeHeadsUpContentView() {
8697             if (mPictureIcon == null || !mShowBigPictureWhenCollapsed) {
8698                 return super.makeHeadsUpContentView();
8699             }
8700 
8701             StandardTemplateParams p = mBuilder.mParams.reset()
8702                     .viewType(StandardTemplateParams.VIEW_TYPE_HEADS_UP)
8703                     .fillTextsFrom(mBuilder)
8704                     .promotedPicture(mPictureIcon);
8705             return getStandardView(mBuilder.getHeadsUpBaseLayoutResource(), p, null /* result */);
8706         }
8707 
8708         /**
8709          * @hide
8710          */
makeExpandedContentView()8711         public RemoteViews makeExpandedContentView() {
8712             // Replace mN.mLargeIcon with mBigLargeIcon if mBigLargeIconSet
8713             // This covers the following cases:
8714             //   1. mBigLargeIconSet -> mBigLargeIcon (null or non-null) applies, overrides
8715             //          mN.mLargeIcon
8716             //   2. !mBigLargeIconSet -> mN.mLargeIcon applies
8717             Icon oldLargeIcon = null;
8718             Bitmap largeIconLegacy = null;
8719             if (mBigLargeIconSet) {
8720                 oldLargeIcon = mBuilder.mN.mLargeIcon;
8721                 mBuilder.mN.mLargeIcon = mBigLargeIcon;
8722                 // The legacy largeIcon might not allow us to clear the image, as it's taken in
8723                 // replacement if the other one is null. Because we're restoring these legacy icons
8724                 // for old listeners, this is in general non-null.
8725                 largeIconLegacy = mBuilder.mN.largeIcon;
8726                 mBuilder.mN.largeIcon = null;
8727             }
8728 
8729             StandardTemplateParams p = mBuilder.mParams.reset()
8730                     .viewType(StandardTemplateParams.VIEW_TYPE_EXPANDED).fillTextsFrom(mBuilder);
8731             RemoteViews contentView = getStandardView(mBuilder.getBigPictureLayoutResource(),
8732                     p, null /* result */);
8733             if (mSummaryTextSet) {
8734                 contentView.setTextViewText(R.id.text,
8735                         mBuilder.ensureColorSpanContrastOrStripStyling(
8736                                 mBuilder.processLegacyText(mSummaryText), p));
8737                 mBuilder.setTextViewColorSecondary(contentView, R.id.text, p);
8738                 contentView.setViewVisibility(R.id.text, View.VISIBLE);
8739             }
8740 
8741             if (mBigLargeIconSet) {
8742                 mBuilder.mN.mLargeIcon = oldLargeIcon;
8743                 mBuilder.mN.largeIcon = largeIconLegacy;
8744             }
8745 
8746             contentView.setImageViewIcon(R.id.big_picture, mPictureIcon);
8747 
8748             if (mPictureContentDescription != null) {
8749                 contentView.setContentDescription(R.id.big_picture, mPictureContentDescription);
8750             }
8751 
8752             return contentView;
8753         }
8754 
8755         /**
8756          * @hide
8757          */
addExtras(Bundle extras)8758         public void addExtras(Bundle extras) {
8759             super.addExtras(extras);
8760 
8761             if (mBigLargeIconSet) {
8762                 extras.putParcelable(EXTRA_LARGE_ICON_BIG, mBigLargeIcon);
8763             }
8764             if (mPictureContentDescription != null) {
8765                 extras.putCharSequence(EXTRA_PICTURE_CONTENT_DESCRIPTION,
8766                         mPictureContentDescription);
8767             }
8768             extras.putBoolean(EXTRA_SHOW_BIG_PICTURE_WHEN_COLLAPSED, mShowBigPictureWhenCollapsed);
8769 
8770             if (mPictureIcon == null) {
8771                 extras.remove(EXTRA_PICTURE_ICON);
8772                 extras.remove(EXTRA_PICTURE);
8773             } else if (mPictureIcon.getType() == Icon.TYPE_BITMAP) {
8774                 // If the icon contains a bitmap, use the old extra so that listeners which look
8775                 // for that extra can still find the picture. Don't include the new extra in
8776                 // that case, to avoid duplicating data. Leave the unused extra set to null to avoid
8777                 // crashing apps that came to expect it to be present but null.
8778                 extras.putParcelable(EXTRA_PICTURE, mPictureIcon.getBitmap());
8779                 extras.putParcelable(EXTRA_PICTURE_ICON, null);
8780             } else {
8781                 extras.putParcelable(EXTRA_PICTURE, null);
8782                 extras.putParcelable(EXTRA_PICTURE_ICON, mPictureIcon);
8783             }
8784         }
8785 
8786         /**
8787          * @hide
8788          */
8789         @Override
restoreFromExtras(Bundle extras)8790         protected void restoreFromExtras(Bundle extras) {
8791             super.restoreFromExtras(extras);
8792 
8793             if (extras.containsKey(EXTRA_LARGE_ICON_BIG)) {
8794                 mBigLargeIconSet = true;
8795                 mBigLargeIcon = extras.getParcelable(EXTRA_LARGE_ICON_BIG, Icon.class);
8796             }
8797 
8798             if (extras.containsKey(EXTRA_PICTURE_CONTENT_DESCRIPTION)) {
8799                 mPictureContentDescription =
8800                         extras.getCharSequence(EXTRA_PICTURE_CONTENT_DESCRIPTION);
8801             }
8802 
8803             mShowBigPictureWhenCollapsed = extras.getBoolean(EXTRA_SHOW_BIG_PICTURE_WHEN_COLLAPSED);
8804 
8805             mPictureIcon = getPictureIcon(extras);
8806         }
8807 
8808         /** @hide */
8809         @Nullable
getPictureIcon(@ullable Bundle extras)8810         public static Icon getPictureIcon(@Nullable Bundle extras) {
8811             if (extras == null) return null;
8812             // When this style adds a picture, we only add one of the keys.  If both were added,
8813             // it would most likely be a legacy app trying to override the picture in some way.
8814             // Because of that case it's better to give precedence to the legacy field.
8815             Bitmap bitmapPicture = extras.getParcelable(EXTRA_PICTURE, Bitmap.class);
8816             if (bitmapPicture != null) {
8817                 return Icon.createWithBitmap(bitmapPicture);
8818             } else {
8819                 return extras.getParcelable(EXTRA_PICTURE_ICON, Icon.class);
8820             }
8821         }
8822 
8823         /**
8824          * @hide
8825          */
8826         @Override
hasSummaryInHeader()8827         public boolean hasSummaryInHeader() {
8828             return false;
8829         }
8830 
8831         /**
8832          * @hide
8833          */
8834         @Override
areNotificationsVisiblyDifferent(Style other)8835         public boolean areNotificationsVisiblyDifferent(Style other) {
8836             if (other == null || getClass() != other.getClass()) {
8837                 return true;
8838             }
8839             BigPictureStyle otherS = (BigPictureStyle) other;
8840             return areIconsMaybeDifferent(getBigPicture(), otherS.getBigPicture());
8841         }
8842     }
8843 
8844     /**
8845      * Helper class for generating large-format notifications that include a lot of text.
8846      *
8847      * Here's how you'd set the <code>BigTextStyle</code> on a notification:
8848      * <pre class="prettyprint">
8849      * Notification notif = new Notification.Builder(mContext)
8850      *     .setContentTitle(&quot;New mail from &quot; + sender.toString())
8851      *     .setContentText(subject)
8852      *     .setSmallIcon(R.drawable.new_mail)
8853      *     .setLargeIcon(aBitmap)
8854      *     .setStyle(new Notification.BigTextStyle()
8855      *         .bigText(aVeryLongString))
8856      *     .build();
8857      * </pre>
8858      *
8859      * @see Notification#bigContentView
8860      */
8861     public static class BigTextStyle extends Style {
8862 
8863         private CharSequence mBigText;
8864 
BigTextStyle()8865         public BigTextStyle() {
8866         }
8867 
8868         /**
8869          * @deprecated use {@code BigTextStyle()}.
8870          */
8871         @Deprecated
BigTextStyle(Builder builder)8872         public BigTextStyle(Builder builder) {
8873             setBuilder(builder);
8874         }
8875 
8876         /**
8877          * Overrides ContentTitle in the expanded form of the template.
8878          * This defaults to the value passed to setContentTitle().
8879          */
setBigContentTitle(CharSequence title)8880         public BigTextStyle setBigContentTitle(CharSequence title) {
8881             internalSetBigContentTitle(safeCharSequence(title));
8882             return this;
8883         }
8884 
8885         /**
8886          * Set the first line of text after the detail section in the expanded form of the template.
8887          */
setSummaryText(CharSequence cs)8888         public BigTextStyle setSummaryText(CharSequence cs) {
8889             internalSetSummaryText(safeCharSequence(cs));
8890             return this;
8891         }
8892 
8893         /**
8894          * Provide the longer text to be displayed in the expanded form of the
8895          * template in place of the content text.
8896          */
bigText(CharSequence cs)8897         public BigTextStyle bigText(CharSequence cs) {
8898             mBigText = safeCharSequence(cs);
8899             return this;
8900         }
8901 
8902         /**
8903          * @hide
8904          */
getBigText()8905         public CharSequence getBigText() {
8906             return mBigText;
8907         }
8908 
8909         /**
8910          * @hide
8911          */
addExtras(Bundle extras)8912         public void addExtras(Bundle extras) {
8913             super.addExtras(extras);
8914 
8915             extras.putCharSequence(EXTRA_BIG_TEXT, mBigText);
8916         }
8917 
8918         /**
8919          * @hide
8920          */
8921         @Override
restoreFromExtras(Bundle extras)8922         protected void restoreFromExtras(Bundle extras) {
8923             super.restoreFromExtras(extras);
8924 
8925             mBigText = extras.getCharSequence(EXTRA_BIG_TEXT);
8926         }
8927 
8928         /**
8929          * @hide
8930          */
makeExpandedContentView()8931         public RemoteViews makeExpandedContentView() {
8932             StandardTemplateParams p = mBuilder.mParams.reset()
8933                     .viewType(StandardTemplateParams.VIEW_TYPE_EXPANDED)
8934                     .allowTextWithProgress(true)
8935                     .textViewId(R.id.big_text)
8936                     .fillTextsFrom(mBuilder);
8937 
8938             // Replace the text with the big text, but only if the big text is not empty.
8939             CharSequence bigTextText = mBuilder.processLegacyText(mBigText);
8940             // Ongoing promoted notifications are allowed to have styling.
8941             final boolean isPromotedOngoing = mBuilder.mN.isPromotedOngoing();
8942             if (!isPromotedOngoing && Flags.cleanUpSpansAndNewLines()) {
8943                 bigTextText = normalizeBigText(stripStyling(bigTextText));
8944             }
8945             if (!TextUtils.isEmpty(bigTextText)) {
8946                 p.text(bigTextText);
8947             }
8948 
8949             return getStandardView(mBuilder.getBigTextLayoutResource(), p, null /* result */);
8950         }
8951 
8952         /**
8953          * @hide
8954          * Spans are ignored when comparing text for visual difference.
8955          */
8956         @Override
areNotificationsVisiblyDifferent(Style other)8957         public boolean areNotificationsVisiblyDifferent(Style other) {
8958             if (other == null || getClass() != other.getClass()) {
8959                 return true;
8960             }
8961             BigTextStyle newS = (BigTextStyle) other;
8962             return !Objects.equals(String.valueOf(getBigText()), String.valueOf(newS.getBigText()));
8963         }
8964 
8965     }
8966 
8967     /**
8968      * Helper class for generating large-format notifications that include multiple back-and-forth
8969      * messages of varying types between any number of people.
8970      *
8971      * <p>
8972      * If the platform does not provide large-format notifications, this method has no effect. The
8973      * user will always see the normal notification view.
8974      *
8975      * <p>
8976      * If the app is targeting Android {@link android.os.Build.VERSION_CODES#P} and above, it is
8977      * required to use the {@link Person} class in order to get an optimal rendering of the
8978      * notification and its avatars. For conversations involving multiple people, the app should
8979      * also make sure that it marks the conversation as a group with
8980      * {@link #setGroupConversation(boolean)}.
8981      *
8982      * <p>
8983      * From Android {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, messaging style
8984      * notifications that are associated with a valid conversation shortcut
8985      * (via {@link Notification.Builder#setShortcutId(String)}) are displayed in a dedicated
8986      * conversation section in the shade above non-conversation alerting and silence notifications.
8987      * To be a valid conversation shortcut, the shortcut must be a
8988      * {@link ShortcutInfo.Builder#setLongLived(boolean)} dynamic or cached sharing shortcut.
8989      *
8990      * <p>
8991      * This class is a "rebuilder": It attaches to a Builder object and modifies its behavior.
8992      * Here's an example of how this may be used:
8993      * <pre class="prettyprint">
8994      *
8995      * Person user = new Person.Builder().setIcon(userIcon).setName(userName).build();
8996      * MessagingStyle style = new MessagingStyle(user)
8997      *      .addMessage(messages[1].getText(), messages[1].getTime(), messages[1].getPerson())
8998      *      .addMessage(messages[2].getText(), messages[2].getTime(), messages[2].getPerson())
8999      *      .setGroupConversation(hasMultiplePeople());
9000      *
9001      * Notification noti = new Notification.Builder()
9002      *     .setContentTitle(&quot;2 new messages with &quot; + sender.toString())
9003      *     .setContentText(subject)
9004      *     .setSmallIcon(R.drawable.new_message)
9005      *     .setLargeIcon(aBitmap)
9006      *     .setStyle(style)
9007      *     .build();
9008      * </pre>
9009      */
9010     public static class MessagingStyle extends Style {
9011 
9012         /**
9013          * The maximum number of messages that will be retained in the Notification itself (the
9014          * number displayed is up to the platform).
9015          */
9016         public static final int MAXIMUM_RETAINED_MESSAGES = 25;
9017 
9018 
9019         /** @hide */
9020         public static final int CONVERSATION_TYPE_LEGACY = 0;
9021         /** @hide */
9022         public static final int CONVERSATION_TYPE_NORMAL = 1;
9023         /** @hide */
9024         public static final int CONVERSATION_TYPE_IMPORTANT = 2;
9025 
9026         /** @hide */
9027         @IntDef(prefix = {"CONVERSATION_TYPE_"}, value = {
9028                 CONVERSATION_TYPE_LEGACY, CONVERSATION_TYPE_NORMAL, CONVERSATION_TYPE_IMPORTANT
9029         })
9030         @Retention(RetentionPolicy.SOURCE)
9031         public @interface ConversationType {}
9032 
9033         @NonNull Person mUser;
9034         @Nullable CharSequence mConversationTitle;
9035         @Nullable Icon mShortcutIcon;
9036         List<Message> mMessages = new ArrayList<>();
9037         List<Message> mHistoricMessages = new ArrayList<>();
9038         boolean mIsGroupConversation;
9039         @ConversationType int mConversationType = CONVERSATION_TYPE_LEGACY;
9040         int mUnreadMessageCount;
9041 
MessagingStyle()9042         MessagingStyle() {
9043         }
9044 
9045         /**
9046          * @param userDisplayName Required - the name to be displayed for any replies sent by the
9047          * user before the posting app reposts the notification with those messages after they've
9048          * been actually sent and in previous messages sent by the user added in
9049          * {@link #addMessage(Notification.MessagingStyle.Message)}
9050          *
9051          * @deprecated use {@code MessagingStyle(Person)}
9052          */
MessagingStyle(@onNull CharSequence userDisplayName)9053         public MessagingStyle(@NonNull CharSequence userDisplayName) {
9054             this(new Person.Builder().setName(userDisplayName).build());
9055         }
9056 
9057         /**
9058          * @param user Required - The person displayed for any messages that are sent by the
9059          * user. Any messages added with {@link #addMessage(Notification.MessagingStyle.Message)}
9060          * who don't have a Person associated with it will be displayed as if they were sent
9061          * by this user. The user also needs to have a valid name associated with it, which is
9062          * enforced starting in Android {@link android.os.Build.VERSION_CODES#P}.
9063          */
MessagingStyle(@onNull Person user)9064         public MessagingStyle(@NonNull Person user) {
9065             mUser = user;
9066         }
9067 
9068         /**
9069          * Validate that this style was properly composed. This is called at build time.
9070          * @hide
9071          */
9072         @Override
validate(Context context)9073         public void validate(Context context) {
9074             super.validate(context);
9075             if (context.getApplicationInfo().targetSdkVersion
9076                     >= Build.VERSION_CODES.P && (mUser == null || mUser.getName() == null)) {
9077                 throw new RuntimeException("User must be valid and have a name.");
9078             }
9079         }
9080 
9081         /**
9082          * @return the text that should be displayed in the statusBar when heads upped.
9083          * If {@code null} is returned, the default implementation will be used.
9084          *
9085          * @hide
9086          */
9087         @Override
getHeadsUpStatusBarText()9088         public CharSequence getHeadsUpStatusBarText() {
9089             CharSequence conversationTitle = !TextUtils.isEmpty(super.mBigContentTitle)
9090                     ? super.mBigContentTitle
9091                     : mConversationTitle;
9092             if (mConversationType == CONVERSATION_TYPE_LEGACY
9093                     && !TextUtils.isEmpty(conversationTitle) && !hasOnlyWhiteSpaceSenders()) {
9094                 return conversationTitle;
9095             }
9096             return null;
9097         }
9098 
9099         /**
9100          * @return the user to be displayed for any replies sent by the user
9101          */
9102         @NonNull
getUser()9103         public Person getUser() {
9104             return mUser;
9105         }
9106 
9107         /**
9108          * Returns the name to be displayed for any replies sent by the user
9109          *
9110          * @deprecated use {@link #getUser()} instead
9111          */
getUserDisplayName()9112         public CharSequence getUserDisplayName() {
9113             return mUser.getName();
9114         }
9115 
9116         /**
9117          * Sets the title to be displayed on this conversation. May be set to {@code null}.
9118          *
9119          * <p>Starting in {@link Build.VERSION_CODES#R}, this conversation title will be ignored
9120          * if a valid shortcutId is added via {@link Notification.Builder#setShortcutId(String)}.
9121          * In this case, {@link ShortcutInfo#getLongLabel()} (or, if missing,
9122          * {@link ShortcutInfo#getShortLabel()}) will be shown as the conversation title
9123          * instead.
9124          *
9125          * <p>This API's behavior was changed in SDK version {@link Build.VERSION_CODES#P}. If your
9126          * application's target version is less than {@link Build.VERSION_CODES#P}, setting a
9127          * conversation title to a non-null value will make {@link #isGroupConversation()} return
9128          * {@code true} and passing {@code null} will make it return {@code false}. In
9129          * {@link Build.VERSION_CODES#P} and beyond, use {@link #setGroupConversation(boolean)}
9130          * to set group conversation status.
9131          *
9132          * @param conversationTitle Title displayed for this conversation
9133          * @return this object for method chaining
9134          */
setConversationTitle(@ullable CharSequence conversationTitle)9135         public MessagingStyle setConversationTitle(@Nullable CharSequence conversationTitle) {
9136             mConversationTitle = conversationTitle;
9137             return this;
9138         }
9139 
9140         /**
9141          * Return the title to be displayed on this conversation. May return {@code null}.
9142          */
9143         @Nullable
getConversationTitle()9144         public CharSequence getConversationTitle() {
9145             return mConversationTitle;
9146         }
9147 
9148         /**
9149          * Sets the icon to be displayed on the conversation, derived from the shortcutId.
9150          *
9151          * @hide
9152          */
setShortcutIcon(@ullable Icon conversationIcon)9153         public MessagingStyle setShortcutIcon(@Nullable Icon conversationIcon) {
9154             mShortcutIcon = conversationIcon;
9155             return this;
9156         }
9157 
9158         /**
9159          * Return the icon to be displayed on this conversation, derived from the shortcutId. May
9160          * return {@code null}.
9161          *
9162          * @hide
9163          */
9164         @Nullable
getShortcutIcon()9165         public Icon getShortcutIcon() {
9166             return mShortcutIcon;
9167         }
9168 
9169         /**
9170          * Sets the conversation type of this MessageStyle notification.
9171          * {@link #CONVERSATION_TYPE_LEGACY} will use the "older" layout from pre-R,
9172          * {@link #CONVERSATION_TYPE_NORMAL} will use the new "conversation" layout, and
9173          * {@link #CONVERSATION_TYPE_IMPORTANT} will add additional "important" treatments.
9174          *
9175          * @hide
9176          */
setConversationType(@onversationType int conversationType)9177         public MessagingStyle setConversationType(@ConversationType int conversationType) {
9178             mConversationType = conversationType;
9179             return this;
9180         }
9181 
9182         /** @hide */
9183         @ConversationType
getConversationType()9184         public int getConversationType() {
9185             return mConversationType;
9186         }
9187 
9188         /** @hide */
getUnreadMessageCount()9189         public int getUnreadMessageCount() {
9190             return mUnreadMessageCount;
9191         }
9192 
9193         /** @hide */
setUnreadMessageCount(int unreadMessageCount)9194         public MessagingStyle setUnreadMessageCount(int unreadMessageCount) {
9195             mUnreadMessageCount = unreadMessageCount;
9196             return this;
9197         }
9198 
9199         /**
9200          * Adds a message for display by this notification. Convenience call for a simple
9201          * {@link Message} in {@link #addMessage(Notification.MessagingStyle.Message)}.
9202          * @param text A {@link CharSequence} to be displayed as the message content
9203          * @param timestamp Time in milliseconds at which the message arrived
9204          * @param sender A {@link CharSequence} to be used for displaying the name of the
9205          * sender. Should be <code>null</code> for messages by the current user, in which case
9206          * the platform will insert {@link #getUserDisplayName()}.
9207          * Should be unique amongst all individuals in the conversation, and should be
9208          * consistent during re-posts of the notification.
9209          *
9210          * @see Message#Message(CharSequence, long, CharSequence)
9211          *
9212          * @return this object for method chaining
9213          *
9214          * @deprecated use {@link #addMessage(CharSequence, long, Person)}
9215          */
addMessage(CharSequence text, long timestamp, CharSequence sender)9216         public MessagingStyle addMessage(CharSequence text, long timestamp, CharSequence sender) {
9217             return addMessage(text, timestamp,
9218                     sender == null ? null : new Person.Builder().setName(sender).build());
9219         }
9220 
9221         /**
9222          * Adds a message for display by this notification. Convenience call for a simple
9223          * {@link Message} in {@link #addMessage(Notification.MessagingStyle.Message)}.
9224          * @param text A {@link CharSequence} to be displayed as the message content
9225          * @param timestamp Time in milliseconds at which the message arrived
9226          * @param sender The {@link Person} who sent the message.
9227          * Should be <code>null</code> for messages by the current user, in which case
9228          * the platform will insert the user set in {@code MessagingStyle(Person)}.
9229          *
9230          * @see Message#Message(CharSequence, long, CharSequence)
9231          *
9232          * @return this object for method chaining
9233          */
addMessage(@onNull CharSequence text, long timestamp, @Nullable Person sender)9234         public MessagingStyle addMessage(@NonNull CharSequence text, long timestamp,
9235                 @Nullable Person sender) {
9236             return addMessage(new Message(text, timestamp, sender));
9237         }
9238 
9239         /**
9240          * Adds a {@link Message} for display in this notification.
9241          *
9242          * <p>The messages should be added in chronologic order, i.e. the oldest first,
9243          * the newest last.
9244          *
9245          * <p>Multiple Messages in a row with the same timestamp and sender may be grouped as a
9246          * single message. This means an app should represent a message that has both an image and
9247          * text as two Message objects, one with the image (and fallback text), and the other with
9248          * the message text. For consistency, a text message (if any) should be provided after Uri
9249          * content.
9250          *
9251          * @param message The {@link Message} to be displayed
9252          * @return this object for method chaining
9253          */
addMessage(Message message)9254         public MessagingStyle addMessage(Message message) {
9255             mMessages.add(message);
9256             if (mMessages.size() > MAXIMUM_RETAINED_MESSAGES) {
9257                 mMessages.remove(0);
9258             }
9259             return this;
9260         }
9261 
9262         /**
9263          * Adds a {@link Message} for historic context in this notification.
9264          *
9265          * <p>Messages should be added as historic if they are not the main subject of the
9266          * notification but may give context to a conversation. The system may choose to present
9267          * them only when relevant, e.g. when replying to a message through a {@link RemoteInput}.
9268          *
9269          * <p>The messages should be added in chronologic order, i.e. the oldest first,
9270          * the newest last.
9271          *
9272          * @param message The historic {@link Message} to be added
9273          * @return this object for method chaining
9274          */
addHistoricMessage(Message message)9275         public MessagingStyle addHistoricMessage(Message message) {
9276             mHistoricMessages.add(message);
9277             if (mHistoricMessages.size() > MAXIMUM_RETAINED_MESSAGES) {
9278                 mHistoricMessages.remove(0);
9279             }
9280             return this;
9281         }
9282 
9283         /**
9284          * Gets the list of {@code Message} objects that represent the notification.
9285          */
getMessages()9286         public List<Message> getMessages() {
9287             return mMessages;
9288         }
9289 
9290         /**
9291          * Gets the list of historic {@code Message}s in the notification.
9292          */
getHistoricMessages()9293         public List<Message> getHistoricMessages() {
9294             return mHistoricMessages;
9295         }
9296 
9297         /**
9298          * Sets whether this conversation notification represents a group. If the app is targeting
9299          * Android P, this is required if the app wants to display the largeIcon set with
9300          * {@link Notification.Builder#setLargeIcon(Bitmap)}, otherwise it will be hidden.
9301          *
9302          * @param isGroupConversation {@code true} if the conversation represents a group,
9303          * {@code false} otherwise.
9304          * @return this object for method chaining
9305          */
setGroupConversation(boolean isGroupConversation)9306         public MessagingStyle setGroupConversation(boolean isGroupConversation) {
9307             mIsGroupConversation = isGroupConversation;
9308             return this;
9309         }
9310 
9311         /**
9312          * Returns {@code true} if this notification represents a group conversation, otherwise
9313          * {@code false}.
9314          *
9315          * <p> If the application that generated this {@link MessagingStyle} targets an SDK version
9316          * less than {@link Build.VERSION_CODES#P}, this method becomes dependent on whether or
9317          * not the conversation title is set; returning {@code true} if the conversation title is
9318          * a non-null value, or {@code false} otherwise. From {@link Build.VERSION_CODES#P} forward,
9319          * this method returns what's set by {@link #setGroupConversation(boolean)} allowing for
9320          * named, non-group conversations.
9321          *
9322          * @see #setConversationTitle(CharSequence)
9323          */
isGroupConversation()9324         public boolean isGroupConversation() {
9325             // When target SDK version is < P, a non-null conversation title dictates if this is
9326             // as group conversation.
9327             if (mBuilder != null
9328                     && mBuilder.mContext.getApplicationInfo().targetSdkVersion
9329                             < Build.VERSION_CODES.P) {
9330                 return mConversationTitle != null;
9331             }
9332 
9333             return mIsGroupConversation;
9334         }
9335 
9336         /**
9337          * @hide
9338          */
9339         @Override
addExtras(Bundle extras)9340         public void addExtras(Bundle extras) {
9341             super.addExtras(extras);
9342             addExtras(extras, false, 0);
9343         }
9344 
9345         /**
9346          * @hide
9347          */
addExtras(Bundle extras, boolean ensureContrast, int backgroundColor)9348         public void addExtras(Bundle extras, boolean ensureContrast, int backgroundColor) {
9349             if (mUser != null) {
9350                 // For legacy usages
9351                 extras.putCharSequence(EXTRA_SELF_DISPLAY_NAME, mUser.getName());
9352                 extras.putParcelable(EXTRA_MESSAGING_PERSON, mUser);
9353             }
9354             if (mConversationTitle != null) {
9355                 extras.putCharSequence(EXTRA_CONVERSATION_TITLE, mConversationTitle);
9356             }
9357             if (!mMessages.isEmpty()) {
9358                 extras.putParcelableArray(EXTRA_MESSAGES,
9359                         getBundleArrayForMessages(mMessages, ensureContrast, backgroundColor));
9360             }
9361             if (!mHistoricMessages.isEmpty()) {
9362                 extras.putParcelableArray(EXTRA_HISTORIC_MESSAGES, getBundleArrayForMessages(
9363                         mHistoricMessages, ensureContrast, backgroundColor));
9364             }
9365             if (mShortcutIcon != null) {
9366                 extras.putParcelable(EXTRA_CONVERSATION_ICON, mShortcutIcon);
9367             }
9368             extras.putInt(EXTRA_CONVERSATION_UNREAD_MESSAGE_COUNT, mUnreadMessageCount);
9369 
9370             fixTitleAndTextExtras(extras);
9371             extras.putBoolean(EXTRA_IS_GROUP_CONVERSATION, mIsGroupConversation);
9372         }
9373 
getBundleArrayForMessages(List<Message> messages, boolean ensureContrast, int backgroundColor)9374         private static Bundle[] getBundleArrayForMessages(List<Message> messages,
9375                 boolean ensureContrast, int backgroundColor) {
9376             Bundle[] bundles = new Bundle[messages.size()];
9377             final int N = messages.size();
9378             for (int i = 0; i < N; i++) {
9379                 final Message m = messages.get(i);
9380                 if (ensureContrast) {
9381                     m.ensureColorContrastOrStripStyling(backgroundColor);
9382                 }
9383                 bundles[i] = m.toBundle();
9384             }
9385             return bundles;
9386         }
9387 
fixTitleAndTextExtras(Bundle extras)9388         private void fixTitleAndTextExtras(Bundle extras) {
9389             Message m = findLatestIncomingMessage();
9390             CharSequence text = (m == null) ? null : m.mText;
9391             CharSequence sender = m == null ? null
9392                     : m.mSender == null || TextUtils.isEmpty(m.mSender.getName())
9393                             ? mUser.getName() : m.mSender.getName();
9394             CharSequence title;
9395             if (!TextUtils.isEmpty(mConversationTitle)) {
9396                 if (!TextUtils.isEmpty(sender)) {
9397                     BidiFormatter bidi = BidiFormatter.getInstance();
9398                     title = mBuilder.mContext.getString(
9399                             com.android.internal.R.string.notification_messaging_title_template,
9400                             bidi.unicodeWrap(mConversationTitle), bidi.unicodeWrap(sender));
9401                 } else {
9402                     title = mConversationTitle;
9403                 }
9404             } else {
9405                 title = sender;
9406             }
9407             if (Flags.cleanUpSpansAndNewLines()) {
9408                 title = stripStyling(title);
9409             }
9410             if (title != null) {
9411                 extras.putCharSequence(EXTRA_TITLE, title);
9412             }
9413             if (text != null) {
9414                 extras.putCharSequence(EXTRA_TEXT, text);
9415             }
9416         }
9417 
fixTitleAndTextForCompactMessaging(StandardTemplateParams p)9418         private void fixTitleAndTextForCompactMessaging(StandardTemplateParams p) {
9419             Message m = findLatestIncomingMessage();
9420             final CharSequence text = (m == null) ? null : m.mText;
9421             CharSequence sender = m == null ? null
9422                     : m.mSender == null || TextUtils.isEmpty(m.mSender.getName())
9423                             ? mUser.getName() : m.mSender.getName();
9424 
9425             CharSequence conversationTitle = mIsGroupConversation ? mConversationTitle : null;
9426 
9427             // we want to have colon for possible title for conversation.
9428             final BidiFormatter bidi = BidiFormatter.getInstance();
9429             if (sender != null) {
9430                 sender = mBuilder.mContext.getString(
9431                         com.android.internal.R.string.notification_messaging_title_template,
9432                         bidi.unicodeWrap(sender), "");
9433             } else if (conversationTitle != null) {
9434                 conversationTitle = mBuilder.mContext.getString(
9435                         com.android.internal.R.string.notification_messaging_title_template,
9436                         bidi.unicodeWrap(conversationTitle), "");
9437             }
9438 
9439             if (Flags.cleanUpSpansAndNewLines()) {
9440                 conversationTitle = stripStyling(conversationTitle);
9441                 sender = stripStyling(sender);
9442             }
9443 
9444             final CharSequence title;
9445             final boolean isConversationTitleAvailable = showConversationTitle()
9446                     && conversationTitle != null;
9447             if (isConversationTitleAvailable) {
9448                 title = conversationTitle;
9449             } else {
9450                 title = sender;
9451             }
9452 
9453             p.title(title);
9454             // when the conversation title is available, use headerTextSecondary for sender and
9455             // summaryText for text
9456             if (isConversationTitleAvailable) {
9457                 p.headerTextSecondary(sender);
9458                 p.summaryText(text);
9459             } else {
9460                 // when it is not, use headerTextSecondary for text and don't use summaryText
9461                 p.headerTextSecondary(text);
9462                 p.summaryText(null);
9463             }
9464         }
9465 
9466         /** (b/342370742) Developer settings to show conversation title. */
showConversationTitle()9467         private boolean showConversationTitle() {
9468             return SystemProperties.getBoolean(
9469                     "persist.compact_heads_up_notification.show_conversation_title_for_group",
9470                     false);
9471         }
9472 
9473         /**
9474          * @hide
9475          */
9476         @Override
restoreFromExtras(Bundle extras)9477         protected void restoreFromExtras(Bundle extras) {
9478             super.restoreFromExtras(extras);
9479 
9480             Person user = extras.getParcelable(EXTRA_MESSAGING_PERSON, Person.class);
9481             if (user == null) {
9482                 CharSequence displayName = extras.getCharSequence(EXTRA_SELF_DISPLAY_NAME);
9483                 mUser = new Person.Builder().setName(displayName).build();
9484             } else {
9485                 mUser = user;
9486             }
9487             mConversationTitle = extras.getCharSequence(EXTRA_CONVERSATION_TITLE);
9488             Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES, Parcelable.class);
9489             mMessages = Message.getMessagesFromBundleArray(messages);
9490             Parcelable[] histMessages = extras.getParcelableArray(EXTRA_HISTORIC_MESSAGES,
9491                     Parcelable.class);
9492             mHistoricMessages = Message.getMessagesFromBundleArray(histMessages);
9493             mIsGroupConversation = extras.getBoolean(EXTRA_IS_GROUP_CONVERSATION);
9494             mUnreadMessageCount = extras.getInt(EXTRA_CONVERSATION_UNREAD_MESSAGE_COUNT);
9495             mShortcutIcon = extras.getParcelable(EXTRA_CONVERSATION_ICON, Icon.class);
9496         }
9497 
9498         /**
9499          * @hide
9500          */
9501         @Override
makeContentView()9502         public RemoteViews makeContentView() {
9503             // All messaging templates contain the actions
9504             ArrayList<Action> originalActions = mBuilder.mActions;
9505             try {
9506                 mBuilder.mActions = new ArrayList<>();
9507                 return makeMessagingView(StandardTemplateParams.VIEW_TYPE_NORMAL);
9508             } finally {
9509                 mBuilder.mActions = originalActions;
9510             }
9511         }
9512 
9513         /**
9514          * @hide
9515          * Spans are ignored when comparing text for visual difference.
9516          */
9517         @Override
areNotificationsVisiblyDifferent(Style other)9518         public boolean areNotificationsVisiblyDifferent(Style other) {
9519             if (other == null || getClass() != other.getClass()) {
9520                 return true;
9521             }
9522             MessagingStyle newS = (MessagingStyle) other;
9523             List<MessagingStyle.Message> oldMs = getMessages();
9524             List<MessagingStyle.Message> newMs = newS.getMessages();
9525 
9526             if (oldMs == null || newMs == null) {
9527                 newMs = new ArrayList<>();
9528             }
9529 
9530             int n = oldMs.size();
9531             if (n != newMs.size()) {
9532                 return true;
9533             }
9534             for (int i = 0; i < n; i++) {
9535                 MessagingStyle.Message oldM = oldMs.get(i);
9536                 MessagingStyle.Message newM = newMs.get(i);
9537                 if (!Objects.equals(
9538                         String.valueOf(oldM.getText()),
9539                         String.valueOf(newM.getText()))) {
9540                     return true;
9541                 }
9542                 if (!Objects.equals(oldM.getDataUri(), newM.getDataUri())) {
9543                     return true;
9544                 }
9545                 String oldSender = String.valueOf(oldM.getSenderPerson() == null
9546                         ? oldM.getSender()
9547                         : oldM.getSenderPerson().getName());
9548                 String newSender = String.valueOf(newM.getSenderPerson() == null
9549                         ? newM.getSender()
9550                         : newM.getSenderPerson().getName());
9551                 if (!Objects.equals(oldSender, newSender)) {
9552                     return true;
9553                 }
9554 
9555                 String oldKey = oldM.getSenderPerson() == null
9556                         ? null : oldM.getSenderPerson().getKey();
9557                 String newKey = newM.getSenderPerson() == null
9558                         ? null : newM.getSenderPerson().getKey();
9559                 if (!Objects.equals(oldKey, newKey)) {
9560                     return true;
9561                 }
9562                 // Other fields (like timestamp) intentionally excluded
9563             }
9564             return false;
9565         }
9566 
findLatestIncomingMessage()9567         private Message findLatestIncomingMessage() {
9568             return findLatestIncomingMessage(mMessages);
9569         }
9570 
9571         /**
9572          * @hide
9573          */
9574         @Nullable
findLatestIncomingMessage( List<Message> messages)9575         public static Message findLatestIncomingMessage(
9576                 List<Message> messages) {
9577             for (int i = messages.size() - 1; i >= 0; i--) {
9578                 Message m = messages.get(i);
9579                 // Incoming messages have a non-empty sender.
9580                 if (m.mSender != null && !TextUtils.isEmpty(m.mSender.getName())) {
9581                     return m;
9582                 }
9583             }
9584             if (!messages.isEmpty()) {
9585                 // No incoming messages, fall back to outgoing message
9586                 return messages.get(messages.size() - 1);
9587             }
9588             return null;
9589         }
9590 
9591         /**
9592          * @hide
9593          */
9594         @Override
makeExpandedContentView()9595         public RemoteViews makeExpandedContentView() {
9596             return makeMessagingView(StandardTemplateParams.VIEW_TYPE_EXPANDED);
9597         }
9598 
9599         /**
9600          * Create a messaging layout.
9601          *
9602          * @param viewType one of StandardTemplateParams.VIEW_TYPE_NORMAL, VIEW_TYPE_EXPANDEDIG,
9603          *                VIEW_TYPE_HEADS_UP
9604          * @return the created remoteView.
9605          */
9606         @NonNull
makeMessagingView(int viewType)9607         private RemoteViews makeMessagingView(int viewType) {
9608             boolean isCollapsed = viewType != StandardTemplateParams.VIEW_TYPE_EXPANDED;
9609             boolean hideRightIcons = viewType != StandardTemplateParams.VIEW_TYPE_NORMAL;
9610             boolean isConversationLayout = mConversationType != CONVERSATION_TYPE_LEGACY;
9611             boolean isImportantConversation = mConversationType == CONVERSATION_TYPE_IMPORTANT;
9612             boolean isLegacyHeaderless = !isConversationLayout && isCollapsed;
9613 
9614             //TODO (b/217799515): ensure mConversationTitle always returns the correct
9615             // conversationTitle, probably set mConversationTitle = conversationTitle after this
9616             CharSequence conversationTitle = !TextUtils.isEmpty(super.mBigContentTitle)
9617                     ? super.mBigContentTitle
9618                     : mConversationTitle;
9619             boolean atLeastP = mBuilder.mContext.getApplicationInfo().targetSdkVersion
9620                     >= Build.VERSION_CODES.P;
9621             boolean isOneToOne;
9622             CharSequence nameReplacement = null;
9623             if (!atLeastP) {
9624                 isOneToOne = TextUtils.isEmpty(conversationTitle);
9625                 if (hasOnlyWhiteSpaceSenders()) {
9626                     isOneToOne = true;
9627                     nameReplacement = conversationTitle;
9628                     conversationTitle = null;
9629                 }
9630             } else {
9631                 isOneToOne = !isGroupConversation();
9632             }
9633             if ((isLegacyHeaderless || notificationsRedesignTemplates())
9634                     && isOneToOne && TextUtils.isEmpty(conversationTitle)) {
9635                 conversationTitle = getOtherPersonName();
9636             }
9637 
9638             Icon largeIcon = mBuilder.mN.mLargeIcon;
9639             TemplateBindResult bindResult = new TemplateBindResult();
9640             StandardTemplateParams p = mBuilder.mParams.reset()
9641                     .viewType(viewType)
9642                     .highlightExpander(isConversationLayout)
9643                     .hideProgress(true)
9644                     .hideLeftIcon(isOneToOne)
9645                     .hideRightIcon(hideRightIcons || isOneToOne);
9646             if (notificationsRedesignTemplates()) {
9647                 String lastMessage = !mMessages.isEmpty()
9648                         ? mMessages.getLast().mText.toString() : null;
9649 
9650                 p.title(conversationTitle)
9651                         // The text is not actually displayed like this (since we're using a
9652                         // MessagingLinearLayout instead of the regular text), but we're using it to
9653                         // know whether the notification will have a second line in practice.
9654                         .text(lastMessage)
9655                         .hideAppName(isCollapsed);
9656             } else {
9657                 p.title(isLegacyHeaderless ? conversationTitle : null)
9658                         .text(null)
9659                         .headerTextSecondary(isLegacyHeaderless ? null : conversationTitle);
9660             }
9661             RemoteViews contentView = mBuilder.applyStandardTemplateWithActions(
9662                     getMessagingLayoutResource(isConversationLayout, isCollapsed),
9663                     p,
9664                     bindResult);
9665             if (isConversationLayout && !notificationsRedesignTemplates()) {
9666                 // Redesign note: This view is replaced by the `title`, which is handled normally.
9667                 mBuilder.setTextViewColorPrimary(contentView, R.id.conversation_text, p);
9668                 // Redesign note: This special divider is no longer needed.
9669                 mBuilder.setTextViewColorSecondary(contentView, R.id.app_name_divider, p);
9670             }
9671 
9672             addExtras(mBuilder.mN.extras, true, mBuilder.getBackgroundColor(p));
9673             contentView.setInt(R.id.status_bar_latest_event_content, "setLayoutColor",
9674                     mBuilder.getSmallIconColor(p));
9675             contentView.setInt(R.id.status_bar_latest_event_content, "setSenderTextColor",
9676                     mBuilder.getPrimaryTextColor(p));
9677             contentView.setInt(R.id.status_bar_latest_event_content, "setMessageTextColor",
9678                     mBuilder.getSecondaryTextColor(p));
9679             contentView.setInt(R.id.status_bar_latest_event_content,
9680                     "setNotificationBackgroundColor",
9681                     mBuilder.getBackgroundColor(p));
9682             contentView.setBoolean(R.id.status_bar_latest_event_content, "setIsCollapsed",
9683                     isCollapsed);
9684             contentView.setIcon(R.id.status_bar_latest_event_content, "setAvatarReplacement",
9685                     mBuilder.mN.mLargeIcon);
9686             contentView.setCharSequence(R.id.status_bar_latest_event_content, "setNameReplacement",
9687                     nameReplacement);
9688             contentView.setBoolean(R.id.status_bar_latest_event_content, "setIsOneToOne",
9689                     isOneToOne);
9690             contentView.setCharSequence(R.id.status_bar_latest_event_content,
9691                     "setConversationTitle", conversationTitle);
9692             if (isConversationLayout) {
9693                 contentView.setIcon(R.id.status_bar_latest_event_content,
9694                         "setShortcutIcon", mShortcutIcon);
9695                 contentView.setBoolean(R.id.status_bar_latest_event_content,
9696                         "setIsImportantConversation", isImportantConversation);
9697             }
9698             if (notificationsRedesignTemplates() && !isCollapsed) {
9699                 // Align the title to the app/small icon in the expanded form. In other layouts,
9700                 // this margin is added directly to the notification_main_column parent, but for
9701                 // messages we don't want the margin to be applied to the actual messaging
9702                 // content since it can contain icons that are displayed below the app icon.
9703                 Resources res = mBuilder.mContext.getResources();
9704                 int marginStart = res.getDimensionPixelSize(
9705                         R.dimen.notification_2025_content_margin_start);
9706                 contentView.setViewLayoutMargin(R.id.title,
9707                         RemoteViews.MARGIN_START, marginStart, COMPLEX_UNIT_PX);
9708             }
9709             if (isLegacyHeaderless) {
9710                 // Collapsed legacy messaging style has a 1-line limit.
9711                 contentView.setInt(R.id.notification_messaging, "setMaxDisplayedLines", 1);
9712             }
9713             contentView.setIcon(R.id.status_bar_latest_event_content, "setLargeIcon",
9714                     largeIcon);
9715             contentView.setBundle(R.id.status_bar_latest_event_content, "setData",
9716                     mBuilder.mN.extras);
9717             return contentView;
9718         }
9719 
getMessagingLayoutResource(boolean isConversationLayout, boolean isCollapsed)9720         private int getMessagingLayoutResource(boolean isConversationLayout, boolean isCollapsed) {
9721             if (notificationsRedesignTemplates()) {
9722                 // Note: We eventually would like to use the same layouts for both conversations and
9723                 //  regular messaging notifications.
9724                 if (isConversationLayout) {
9725                     if (isCollapsed) {
9726                         return mBuilder.getCollapsedConversationLayoutResource();
9727                     } else {
9728                         return mBuilder.getExpandedConversationLayoutResource();
9729                     }
9730                 } else {
9731                     if (isCollapsed) {
9732                         return mBuilder.getCollapsedMessagingLayoutResource();
9733                     } else {
9734                         return mBuilder.getExpandedMessagingLayoutResource();
9735                     }
9736                 }
9737 
9738             } else {
9739                 return isConversationLayout
9740                         ? mBuilder.getConversationLayoutResource()
9741                         : isCollapsed
9742                                 ? mBuilder.getCollapsedMessagingLayoutResource()
9743                                 : mBuilder.getExpandedMessagingLayoutResource();
9744             }
9745         }
9746 
getKey(Person person)9747         private CharSequence getKey(Person person) {
9748             return person == null ? null
9749                     : person.getKey() == null ? person.getName() : person.getKey();
9750         }
9751 
getOtherPersonName()9752         private CharSequence getOtherPersonName() {
9753             CharSequence userKey = getKey(mUser);
9754             for (int i = mMessages.size() - 1; i >= 0; i--) {
9755                 Person sender = mMessages.get(i).getSenderPerson();
9756                 if (sender != null && !TextUtils.equals(userKey, getKey(sender))) {
9757                     return sender.getName();
9758                 }
9759             }
9760             // If we've reached this point without finding a sender that doesn't match the user, it
9761             // likely points to an incorrect use of our API, where the user isn't being set
9762             // correctly. It's either that, or perhaps the user actually is having a conversation
9763             // with themselves ¯\_(ツ)_/¯ so let's not leave the name empty.
9764             return notificationsRedesignTemplates() ? mUser.getName() : null;
9765         }
9766 
hasOnlyWhiteSpaceSenders()9767         private boolean hasOnlyWhiteSpaceSenders() {
9768             for (int i = 0; i < mMessages.size(); i++) {
9769                 Message m = mMessages.get(i);
9770                 Person sender = m.getSenderPerson();
9771                 if (sender != null && !isWhiteSpace(sender.getName())) {
9772                     return false;
9773                 }
9774             }
9775             return true;
9776         }
9777 
isWhiteSpace(CharSequence sender)9778         private boolean isWhiteSpace(CharSequence sender) {
9779             if (TextUtils.isEmpty(sender)) {
9780                 return true;
9781             }
9782             if (sender.toString().matches("^\\s*$")) {
9783                 return true;
9784             }
9785             // Let's check if we only have 0 whitespace chars. Some apps did this as a workaround
9786             // For the presentation that we had.
9787             for (int i = 0; i < sender.length(); i++) {
9788                 char c = sender.charAt(i);
9789                 if (c != '\u200B') {
9790                     return false;
9791                 }
9792             }
9793             return true;
9794         }
9795 
9796         /**
9797          * @hide
9798          */
9799         @Override
makeHeadsUpContentView()9800         public RemoteViews makeHeadsUpContentView() {
9801             return makeMessagingView(StandardTemplateParams.VIEW_TYPE_HEADS_UP);
9802         }
9803 
9804         /**
9805          * @hide
9806          */
9807         @Nullable
9808         @Override
makeCompactHeadsUpContentView()9809         public RemoteViews makeCompactHeadsUpContentView() {
9810             final boolean isConversationLayout = mConversationType != CONVERSATION_TYPE_LEGACY;
9811             Icon conversationIcon = null;
9812             Notification.Action remoteInputAction = null;
9813             if (isConversationLayout) {
9814 
9815                 conversationIcon = mShortcutIcon;
9816 
9817                 // conversation icon is m
9818                 // Extract the conversation icon for one to one conversations from
9819                 // the latest incoming message since
9820                 // fixTitleAndTextExtras also uses it as data source for title and text
9821                 if (conversationIcon == null && !mIsGroupConversation) {
9822                     final Message message = findLatestIncomingMessage();
9823                     if (message != null) {
9824                         final Person sender = message.mSender;
9825                         if (sender != null) {
9826                             conversationIcon = sender.getIcon();
9827                         }
9828                     }
9829                 }
9830 
9831                 if (Flags.compactHeadsUpNotificationReply()) {
9832                     // Get the first non-contextual inline reply action.
9833                     final List<Notification.Action> nonContextualActions =
9834                             mBuilder.getNonContextualActions();
9835                     for (int i = 0; i < nonContextualActions.size(); i++) {
9836                         final Notification.Action action = nonContextualActions.get(i);
9837                         if (mBuilder.hasValidRemoteInput(action)) {
9838                             remoteInputAction = action;
9839                             break;
9840                         }
9841                     }
9842                 }
9843             }
9844 
9845             final StandardTemplateParams p = mBuilder.mParams.reset()
9846                     .viewType(StandardTemplateParams.VIEW_TYPE_HEADS_UP)
9847                     .highlightExpander(isConversationLayout)
9848                     .fillTextsFrom(mBuilder)
9849                     .hideTime(true);
9850 
9851             fixTitleAndTextForCompactMessaging(p);
9852             TemplateBindResult bindResult = new TemplateBindResult();
9853 
9854             RemoteViews contentView = mBuilder.applyStandardTemplate(
9855                     mBuilder.getMessagingCompactHeadsUpLayoutResource(), p, bindResult);
9856             contentView.setViewVisibility(R.id.header_text_secondary_divider, View.GONE);
9857             contentView.setViewVisibility(R.id.header_text_divider, View.GONE);
9858             if (conversationIcon != null) {
9859                 contentView.setViewVisibility(R.id.icon, View.GONE);
9860                 contentView.setViewVisibility(R.id.conversation_face_pile, View.GONE);
9861                 contentView.setViewVisibility(R.id.conversation_icon, View.VISIBLE);
9862                 contentView.setImageViewIcon(R.id.conversation_icon, conversationIcon);
9863             } else if (mIsGroupConversation) {
9864                 contentView.setViewVisibility(R.id.icon, View.GONE);
9865                 contentView.setViewVisibility(R.id.conversation_icon, View.GONE);
9866                 contentView.setInt(R.id.status_bar_latest_event_content,
9867                         "setNotificationBackgroundColor", mBuilder.getBackgroundColor(p));
9868                 contentView.setInt(R.id.status_bar_latest_event_content, "setLayoutColor",
9869                         mBuilder.getSmallIconColor(p));
9870                 contentView.setBundle(R.id.status_bar_latest_event_content, "setGroupFacePile",
9871                         mBuilder.mN.extras);
9872             }
9873 
9874             if (remoteInputAction != null) {
9875                 contentView.setViewVisibility(R.id.reply_action_container, View.VISIBLE);
9876 
9877                 final RemoteViews inlineReplyButton =
9878                         mBuilder.generateActionButton(remoteInputAction, false, p);
9879                 // Clear the drawable
9880                 inlineReplyButton.setInt(R.id.action0, "setBackgroundResource", 0);
9881                 inlineReplyButton.setTextViewText(R.id.action0,
9882                         mBuilder.mContext.getString(R.string.notification_compact_heads_up_reply));
9883                 contentView.addView(R.id.reply_action_container, inlineReplyButton);
9884             } else {
9885                 contentView.setViewVisibility(R.id.reply_action_container, View.GONE);
9886             }
9887             return contentView;
9888         }
9889 
9890 
9891         /**
9892          * @hide
9893          */
9894         @Override
reduceImageSizes(Context context)9895         public void reduceImageSizes(Context context) {
9896             super.reduceImageSizes(context);
9897             Resources resources = context.getResources();
9898             boolean isLowRam = ActivityManager.isLowRamDeviceStatic();
9899             if (mShortcutIcon != null) {
9900                 int maxSize = resources.getDimensionPixelSize(
9901                         isLowRam ? R.dimen.notification_small_icon_size_low_ram
9902                                 : R.dimen.notification_small_icon_size);
9903                 mShortcutIcon.scaleDownIfNecessary(maxSize, maxSize);
9904             }
9905 
9906             int maxAvatarSize = resources.getDimensionPixelSize(
9907                     isLowRam ? R.dimen.notification_person_icon_max_size_low_ram
9908                             : R.dimen.notification_person_icon_max_size);
9909             if (mUser != null && mUser.getIcon() != null) {
9910                 mUser.getIcon().scaleDownIfNecessary(maxAvatarSize, maxAvatarSize);
9911             }
9912 
9913             reduceMessagesIconSizes(mMessages, maxAvatarSize);
9914             reduceMessagesIconSizes(mHistoricMessages, maxAvatarSize);
9915         }
9916 
9917         /**
9918          * @hide
9919          */
reduceMessagesIconSizes(@ullable List<Message> messages, int maxSize)9920         private static void reduceMessagesIconSizes(@Nullable List<Message> messages, int maxSize) {
9921             if (messages == null) {
9922                 return;
9923             }
9924 
9925             for (Message message : messages) {
9926                 Person sender = message.mSender;
9927                 if (sender != null) {
9928                     Icon icon = sender.getIcon();
9929                     if (icon != null) {
9930                         icon.scaleDownIfNecessary(maxSize, maxSize);
9931                     }
9932                 }
9933             }
9934         }
9935 
9936         /*
9937          * An object representing a simple message or piece of media within a mixed-media message.
9938          *
9939          * This object can only represent text or a single binary piece of media. For apps which
9940          * support mixed-media messages (e.g. text + image), multiple Messages should be used, one
9941          * to represent each piece of the message, and they should all be given the same timestamp.
9942          * For consistency, a text message should be added last of all Messages with the same
9943          * timestamp.
9944          */
9945         public static final class Message {
9946             /** @hide */
9947             public static final String KEY_TEXT = "text";
9948             static final String KEY_TIMESTAMP = "time";
9949             static final String KEY_SENDER = "sender";
9950             static final String KEY_SENDER_PERSON = "sender_person";
9951             static final String KEY_DATA_MIME_TYPE = "type";
9952             static final String KEY_DATA_URI= "uri";
9953             static final String KEY_EXTRAS_BUNDLE = "extras";
9954             static final String KEY_REMOTE_INPUT_HISTORY = "remote_input_history";
9955 
9956             private CharSequence mText;
9957             private final long mTimestamp;
9958             @Nullable
9959             private final Person mSender;
9960             /** True if this message was generated from the extra
9961              *  {@link Notification#EXTRA_REMOTE_INPUT_HISTORY_ITEMS}
9962              */
9963             private final boolean mRemoteInputHistory;
9964 
9965             private Bundle mExtras = new Bundle();
9966             private String mDataMimeType;
9967             private Uri mDataUri;
9968 
9969             /**
9970              * Constructor
9971              * @param text A {@link CharSequence} to be displayed as the message content
9972              * @param timestamp Time at which the message arrived
9973              * @param sender A {@link CharSequence} to be used for displaying the name of the
9974              * sender. Should be <code>null</code> for messages by the current user, in which case
9975              * the platform will insert {@link MessagingStyle#getUserDisplayName()}.
9976              * Should be unique amongst all individuals in the conversation, and should be
9977              * consistent during re-posts of the notification.
9978              *
9979              *  @deprecated use {@code Message(CharSequence, long, Person)}
9980              */
Message(CharSequence text, long timestamp, CharSequence sender)9981             public Message(CharSequence text, long timestamp, CharSequence sender){
9982                 this(text, timestamp, sender == null ? null
9983                         : new Person.Builder().setName(sender).build());
9984             }
9985 
9986             /**
9987              * Constructor
9988              * @param text A {@link CharSequence} to be displayed as the message content
9989              * @param timestamp Time at which the message arrived
9990              * @param sender The {@link Person} who sent the message.
9991              * Should be <code>null</code> for messages by the current user, in which case
9992              * the platform will insert the user set in {@code MessagingStyle(Person)}.
9993              * <p>
9994              * The person provided should contain an Icon, set with
9995              * {@link Person.Builder#setIcon(Icon)} and also have a name provided
9996              * with {@link Person.Builder#setName(CharSequence)}. If multiple users have the same
9997              * name, consider providing a key with {@link Person.Builder#setKey(String)} in order
9998              * to differentiate between the different users.
9999              * </p>
10000              */
Message(@onNull CharSequence text, long timestamp, @Nullable Person sender)10001             public Message(@NonNull CharSequence text, long timestamp, @Nullable Person sender) {
10002                 this(text, timestamp, sender, false /* remoteHistory */);
10003             }
10004 
10005             /**
10006              * Constructor
10007              * @param text A {@link CharSequence} to be displayed as the message content
10008              * @param timestamp Time at which the message arrived
10009              * @param sender The {@link Person} who sent the message.
10010              * Should be <code>null</code> for messages by the current user, in which case
10011              * the platform will insert the user set in {@code MessagingStyle(Person)}.
10012              * @param remoteInputHistory True if the messages was generated from the extra
10013              * {@link Notification#EXTRA_REMOTE_INPUT_HISTORY_ITEMS}.
10014              * <p>
10015              * The person provided should contain an Icon, set with
10016              * {@link Person.Builder#setIcon(Icon)} and also have a name provided
10017              * with {@link Person.Builder#setName(CharSequence)}. If multiple users have the same
10018              * name, consider providing a key with {@link Person.Builder#setKey(String)} in order
10019              * to differentiate between the different users.
10020              * </p>
10021              * @hide
10022              */
Message(@onNull CharSequence text, long timestamp, @Nullable Person sender, boolean remoteInputHistory)10023             public Message(@NonNull CharSequence text, long timestamp, @Nullable Person sender,
10024                     boolean remoteInputHistory) {
10025                 mText = safeCharSequence(text);
10026                 mTimestamp = timestamp;
10027                 mSender = sender;
10028                 mRemoteInputHistory = remoteInputHistory;
10029             }
10030 
10031             /**
10032              * Sets a binary blob of data and an associated MIME type for a message. In the case
10033              * where the platform or the UI state doesn't support the MIME type, the original text
10034              * provided in the constructor will be used.  When this data can be presented to the
10035              * user, the original text will only be used as accessibility text.
10036              * @param dataMimeType The MIME type of the content. See
10037              * {@link android.graphics.ImageDecoder#isMimeTypeSupported(String)} for a list of
10038              * supported image MIME types.
10039              * @param dataUri The uri containing the content whose type is given by the MIME type.
10040              * <p class="note">
10041              * Notification Listeners including the System UI need permission to access the
10042              * data the Uri points to. The recommended ways to do this are:
10043              * <ol>
10044              *   <li>Store the data in your own ContentProvider, making sure that other apps have
10045              *       the correct permission to access your provider. The preferred mechanism for
10046              *       providing access is to use per-URI permissions which are temporary and only
10047              *       grant access to the receiving application. An easy way to create a
10048              *       ContentProvider like this is to use the FileProvider helper class.</li>
10049              *   <li>Use the system MediaStore. The MediaStore is primarily aimed at video, audio
10050              *       and image MIME types, however beginning with Android 3.0 (API level 11) it can
10051              *       also store non-media types (see MediaStore.Files for more info). Files can be
10052              *       inserted into the MediaStore using scanFile() after which a content:// style
10053              *       Uri suitable for sharing is passed to the provided onScanCompleted() callback.
10054              *       Note that once added to the system MediaStore the content is accessible to any
10055              *       app on the device.</li>
10056              * </ol>
10057              * @return this object for method chaining
10058              */
setData(String dataMimeType, Uri dataUri)10059             public Message setData(String dataMimeType, Uri dataUri) {
10060                 mDataMimeType = dataMimeType;
10061                 mDataUri = dataUri;
10062                 return this;
10063             }
10064 
10065             /**
10066              * Strip styling or updates TextAppearance spans in message text.
10067              * @hide
10068              */
ensureColorContrastOrStripStyling(int backgroundColor)10069             public void ensureColorContrastOrStripStyling(int backgroundColor) {
10070                 if (Flags.cleanUpSpansAndNewLines()) {
10071                     mText = stripNonStyleSpans(mText);
10072                 } else {
10073                     ensureColorContrast(backgroundColor);
10074                 }
10075             }
10076 
stripNonStyleSpans(CharSequence text)10077             private CharSequence stripNonStyleSpans(CharSequence text) {
10078 
10079                 if (text instanceof Spanned) {
10080                     Spanned ss = (Spanned) text;
10081                     Object[] spans = ss.getSpans(0, ss.length(), Object.class);
10082                     SpannableStringBuilder builder = new SpannableStringBuilder(ss.toString());
10083                     for (Object span : spans) {
10084                         final Object resultSpan;
10085                         if (span instanceof StyleSpan
10086                                 || span instanceof StrikethroughSpan
10087                                 || span instanceof UnderlineSpan) {
10088                             resultSpan = span;
10089                         } else if (span instanceof TextAppearanceSpan) {
10090                             final TextAppearanceSpan originalSpan = (TextAppearanceSpan) span;
10091                             resultSpan = new TextAppearanceSpan(
10092                                     null,
10093                                     originalSpan.getTextStyle(),
10094                                     -1,
10095                                     null,
10096                                     null);
10097                         } else {
10098                             continue;
10099                         }
10100                         builder.setSpan(resultSpan, ss.getSpanStart(span), ss.getSpanEnd(span),
10101                                 ss.getSpanFlags(span));
10102                     }
10103                     return builder;
10104                 }
10105                 return text;
10106             }
10107 
10108             /**
10109              * Updates TextAppearance spans in the message text so it has sufficient contrast
10110              * against its background.
10111              * @hide
10112              */
ensureColorContrast(int backgroundColor)10113             public void ensureColorContrast(int backgroundColor) {
10114                 mText = ContrastColorUtil.ensureColorSpanContrast(mText, backgroundColor);
10115             }
10116 
10117             /**
10118              * Get the text to be used for this message, or the fallback text if a type and content
10119              * Uri have been set
10120              */
getText()10121             public CharSequence getText() {
10122                 return mText;
10123             }
10124 
10125             /**
10126              * Get the time at which this message arrived
10127              */
getTimestamp()10128             public long getTimestamp() {
10129                 return mTimestamp;
10130             }
10131 
10132             /**
10133              * Get the extras Bundle for this message.
10134              */
getExtras()10135             public Bundle getExtras() {
10136                 return mExtras;
10137             }
10138 
10139             /**
10140              * Get the text used to display the contact's name in the messaging experience
10141              *
10142              * @deprecated use {@link #getSenderPerson()}
10143              */
getSender()10144             public CharSequence getSender() {
10145                 return mSender == null ? null : mSender.getName();
10146             }
10147 
10148             /**
10149              * Get the sender associated with this message.
10150              */
10151             @Nullable
getSenderPerson()10152             public Person getSenderPerson() {
10153                 return mSender;
10154             }
10155 
10156             /**
10157              * Get the MIME type of the data pointed to by the Uri
10158              */
getDataMimeType()10159             public String getDataMimeType() {
10160                 return mDataMimeType;
10161             }
10162 
10163             /**
10164              * Get the Uri pointing to the content of the message. Can be null, in which case
10165              * {@see #getText()} is used.
10166              */
getDataUri()10167             public Uri getDataUri() {
10168                 return mDataUri;
10169             }
10170 
10171             /**
10172              * @return True if the message was generated from
10173              * {@link Notification#EXTRA_REMOTE_INPUT_HISTORY_ITEMS}.
10174              * @hide
10175              */
isRemoteInputHistory()10176             public boolean isRemoteInputHistory() {
10177                 return mRemoteInputHistory;
10178             }
10179 
10180             /**
10181              * Converts the message into a {@link Bundle}. To extract the message back,
10182              * check {@link #getMessageFromBundle()}
10183              * @hide
10184              */
10185             @NonNull
toBundle()10186             public Bundle toBundle() {
10187                 Bundle bundle = new Bundle();
10188                 if (mText != null) {
10189                     bundle.putCharSequence(KEY_TEXT, mText);
10190                 }
10191                 bundle.putLong(KEY_TIMESTAMP, mTimestamp);
10192                 if (mSender != null) {
10193                     // Legacy listeners need this
10194                     bundle.putCharSequence(KEY_SENDER, safeCharSequence(mSender.getName()));
10195                     bundle.putParcelable(KEY_SENDER_PERSON, mSender);
10196                 }
10197                 if (mDataMimeType != null) {
10198                     bundle.putString(KEY_DATA_MIME_TYPE, mDataMimeType);
10199                 }
10200                 if (mDataUri != null) {
10201                     bundle.putParcelable(KEY_DATA_URI, mDataUri);
10202                 }
10203                 if (mExtras != null) {
10204                     bundle.putBundle(KEY_EXTRAS_BUNDLE, mExtras);
10205                 }
10206                 if (mRemoteInputHistory) {
10207                     bundle.putBoolean(KEY_REMOTE_INPUT_HISTORY, mRemoteInputHistory);
10208                 }
10209                 return bundle;
10210             }
10211 
10212             /**
10213              * See {@link Notification#visitUris(Consumer)}.
10214              *
10215              * @hide
10216              */
visitUris(@onNull Consumer<Uri> visitor)10217             public void visitUris(@NonNull Consumer<Uri> visitor) {
10218                 visitor.accept(getDataUri());
10219                 if (mSender != null) {
10220                     mSender.visitUris(visitor);
10221                 }
10222             }
10223 
10224             /**
10225              * Returns a list of messages read from the given bundle list, e.g.
10226              * {@link #EXTRA_MESSAGES} or {@link #EXTRA_HISTORIC_MESSAGES}.
10227              */
10228             @NonNull
getMessagesFromBundleArray(@ullable Parcelable[] bundles)10229             public static List<Message> getMessagesFromBundleArray(@Nullable Parcelable[] bundles) {
10230                 if (bundles == null) {
10231                     return new ArrayList<>();
10232                 }
10233                 List<Message> messages = new ArrayList<>(bundles.length);
10234                 for (int i = 0; i < bundles.length; i++) {
10235                     if (bundles[i] instanceof Bundle) {
10236                         Message message = getMessageFromBundle((Bundle)bundles[i]);
10237                         if (message != null) {
10238                             messages.add(message);
10239                         }
10240                     }
10241                 }
10242                 return messages;
10243             }
10244 
10245             /**
10246              * Returns the message that is stored in the bundle (e.g. one of the values in the lists
10247              * in {@link #EXTRA_MESSAGES} or {@link #EXTRA_HISTORIC_MESSAGES}) or null if the
10248              * message couldn't be resolved.
10249              * @hide
10250              */
10251             @Nullable
getMessageFromBundle(@onNull Bundle bundle)10252             public static Message getMessageFromBundle(@NonNull Bundle bundle) {
10253                 try {
10254                     if (!bundle.containsKey(KEY_TEXT) || !bundle.containsKey(KEY_TIMESTAMP)) {
10255                         return null;
10256                     } else {
10257 
10258                         Person senderPerson = bundle.getParcelable(KEY_SENDER_PERSON, Person.class);
10259                         if (senderPerson == null) {
10260                             // Legacy apps that use compat don't actually provide the sender objects
10261                             // We need to fix the compat version to provide people / use
10262                             // the native api instead
10263                             CharSequence senderName = bundle.getCharSequence(KEY_SENDER);
10264                             if (senderName != null) {
10265                                 senderPerson = new Person.Builder().setName(senderName).build();
10266                             }
10267                         }
10268                         Message message = new Message(bundle.getCharSequence(KEY_TEXT),
10269                                 bundle.getLong(KEY_TIMESTAMP),
10270                                 senderPerson,
10271                                 bundle.getBoolean(KEY_REMOTE_INPUT_HISTORY, false));
10272                         if (bundle.containsKey(KEY_DATA_MIME_TYPE) &&
10273                                 bundle.containsKey(KEY_DATA_URI)) {
10274                             message.setData(bundle.getString(KEY_DATA_MIME_TYPE),
10275                                     bundle.getParcelable(KEY_DATA_URI, Uri.class));
10276                         }
10277                         if (bundle.containsKey(KEY_EXTRAS_BUNDLE)) {
10278                             message.getExtras().putAll(bundle.getBundle(KEY_EXTRAS_BUNDLE));
10279                         }
10280                         return message;
10281                     }
10282                 } catch (ClassCastException e) {
10283                     return null;
10284                 }
10285             }
10286         }
10287     }
10288 
10289     /**
10290      * Helper class for generating large-format notifications that include a list of (up to 5) strings.
10291      *
10292      * Here's how you'd set the <code>InboxStyle</code> on a notification:
10293      * <pre class="prettyprint">
10294      * Notification notif = new Notification.Builder(mContext)
10295      *     .setContentTitle(&quot;5 New mails from &quot; + sender.toString())
10296      *     .setContentText(subject)
10297      *     .setSmallIcon(R.drawable.new_mail)
10298      *     .setLargeIcon(aBitmap)
10299      *     .setStyle(new Notification.InboxStyle()
10300      *         .addLine(str1)
10301      *         .addLine(str2)
10302      *         .setContentTitle(&quot;&quot;)
10303      *         .setSummaryText(&quot;+3 more&quot;))
10304      *     .build();
10305      * </pre>
10306      *
10307      * @see Notification#bigContentView
10308      */
10309     public static class InboxStyle extends Style {
10310 
10311         /**
10312          * The number of lines of remote input history allowed until we start reducing lines.
10313          */
10314         private static final int NUMBER_OF_HISTORY_ALLOWED_UNTIL_REDUCTION = 1;
10315         private ArrayList<CharSequence> mTexts = new ArrayList<CharSequence>(5);
10316 
InboxStyle()10317         public InboxStyle() {
10318         }
10319 
10320         /**
10321          * @deprecated use {@code InboxStyle()}.
10322          */
10323         @Deprecated
InboxStyle(Builder builder)10324         public InboxStyle(Builder builder) {
10325             setBuilder(builder);
10326         }
10327 
10328         /**
10329          * Overrides ContentTitle in the expanded form of the template.
10330          * This defaults to the value passed to setContentTitle().
10331          */
setBigContentTitle(CharSequence title)10332         public InboxStyle setBigContentTitle(CharSequence title) {
10333             internalSetBigContentTitle(safeCharSequence(title));
10334             return this;
10335         }
10336 
10337         /**
10338          * Set the first line of text after the detail section in the expanded form of the template.
10339          */
setSummaryText(CharSequence cs)10340         public InboxStyle setSummaryText(CharSequence cs) {
10341             internalSetSummaryText(safeCharSequence(cs));
10342             return this;
10343         }
10344 
10345         /**
10346          * Append a line to the digest section of the Inbox notification.
10347          */
addLine(CharSequence cs)10348         public InboxStyle addLine(CharSequence cs) {
10349             mTexts.add(safeCharSequence(cs));
10350             return this;
10351         }
10352 
10353         /**
10354          * @hide
10355          */
getLines()10356         public ArrayList<CharSequence> getLines() {
10357             return mTexts;
10358         }
10359 
10360         /**
10361          * @hide
10362          */
addExtras(Bundle extras)10363         public void addExtras(Bundle extras) {
10364             super.addExtras(extras);
10365 
10366             CharSequence[] a = new CharSequence[mTexts.size()];
10367             extras.putCharSequenceArray(EXTRA_TEXT_LINES, mTexts.toArray(a));
10368         }
10369 
10370         /**
10371          * @hide
10372          */
10373         @Override
restoreFromExtras(Bundle extras)10374         protected void restoreFromExtras(Bundle extras) {
10375             super.restoreFromExtras(extras);
10376 
10377             mTexts.clear();
10378             if (extras.containsKey(EXTRA_TEXT_LINES)) {
10379                 Collections.addAll(mTexts, extras.getCharSequenceArray(EXTRA_TEXT_LINES));
10380             }
10381         }
10382 
10383         /**
10384          * @hide
10385          */
makeExpandedContentView()10386         public RemoteViews makeExpandedContentView() {
10387             StandardTemplateParams p = mBuilder.mParams.reset()
10388                     .viewType(StandardTemplateParams.VIEW_TYPE_EXPANDED)
10389                     .fillTextsFrom(mBuilder).text(null);
10390             TemplateBindResult result = new TemplateBindResult();
10391             RemoteViews contentView = getStandardView(mBuilder.getInboxLayoutResource(), p, result);
10392 
10393             int[] rowIds = {R.id.inbox_text0, R.id.inbox_text1, R.id.inbox_text2, R.id.inbox_text3,
10394                     R.id.inbox_text4, R.id.inbox_text5, R.id.inbox_text6};
10395 
10396             // Make sure all rows are gone in case we reuse a view.
10397             for (int rowId : rowIds) {
10398                 contentView.setViewVisibility(rowId, View.GONE);
10399             }
10400 
10401             int i=0;
10402             int topPadding = mBuilder.mContext.getResources().getDimensionPixelSize(
10403                     R.dimen.notification_inbox_item_top_padding);
10404             boolean first = true;
10405             int onlyViewId = 0;
10406             int maxRows = rowIds.length;
10407             if (mBuilder.mActions.size() > 0) {
10408                 maxRows--;
10409             }
10410             RemoteInputHistoryItem[] remoteInputHistory = getParcelableArrayFromBundle(
10411                     mBuilder.mN.extras, EXTRA_REMOTE_INPUT_HISTORY_ITEMS,
10412                     RemoteInputHistoryItem.class);
10413             if (remoteInputHistory != null
10414                     && remoteInputHistory.length > NUMBER_OF_HISTORY_ALLOWED_UNTIL_REDUCTION) {
10415                 // Let's remove some messages to make room for the remote input history.
10416                 // 1 is always able to fit, but let's remove them if they are 2 or 3
10417                 int numRemoteInputs = Math.min(remoteInputHistory.length,
10418                         MAX_REMOTE_INPUT_HISTORY_LINES);
10419                 int totalNumRows = mTexts.size() + numRemoteInputs
10420                         - NUMBER_OF_HISTORY_ALLOWED_UNTIL_REDUCTION;
10421                 if (totalNumRows > maxRows) {
10422                     int overflow = totalNumRows - maxRows;
10423                     if (mTexts.size() > maxRows) {
10424                         // Heuristic: if the Texts don't fit anyway, we'll rather drop the last
10425                         // few messages, even with the remote input
10426                         maxRows -= overflow;
10427                     } else  {
10428                         // otherwise we drop the first messages
10429                         i = overflow;
10430                     }
10431                 }
10432             }
10433             while (i < mTexts.size() && i < maxRows) {
10434                 CharSequence str = mTexts.get(i);
10435                 if (!TextUtils.isEmpty(str)) {
10436                     contentView.setViewVisibility(rowIds[i], View.VISIBLE);
10437                     contentView.setTextViewText(rowIds[i],
10438                             mBuilder.ensureColorSpanContrastOrStripStyling(
10439                                     mBuilder.processLegacyText(str), p));
10440                     mBuilder.setTextViewColorSecondary(contentView, rowIds[i], p);
10441                     contentView.setViewPadding(rowIds[i], 0, topPadding, 0, 0);
10442                     if (first) {
10443                         onlyViewId = rowIds[i];
10444                     } else {
10445                         onlyViewId = 0;
10446                     }
10447                     first = false;
10448                 }
10449                 i++;
10450             }
10451             if (onlyViewId != 0) {
10452                 // We only have 1 entry, lets make it look like the normal Text of a Bigtext
10453                 topPadding = mBuilder.mContext.getResources().getDimensionPixelSize(
10454                         R.dimen.notification_text_margin_top);
10455                 contentView.setViewPadding(onlyViewId, 0, topPadding, 0, 0);
10456             }
10457 
10458             return contentView;
10459         }
10460 
10461         /**
10462          * @hide
10463          */
10464         @Override
areNotificationsVisiblyDifferent(Style other)10465         public boolean areNotificationsVisiblyDifferent(Style other) {
10466             if (other == null || getClass() != other.getClass()) {
10467                 return true;
10468             }
10469             InboxStyle newS = (InboxStyle) other;
10470 
10471             final ArrayList<CharSequence> myLines = getLines();
10472             final ArrayList<CharSequence> newLines = newS.getLines();
10473             final int n = myLines.size();
10474             if (n != newLines.size()) {
10475                 return true;
10476             }
10477 
10478             for (int i = 0; i < n; i++) {
10479                 if (!Objects.equals(
10480                         String.valueOf(myLines.get(i)),
10481                         String.valueOf(newLines.get(i)))) {
10482                     return true;
10483                 }
10484             }
10485             return false;
10486         }
10487     }
10488 
10489     /**
10490      * Notification style for media playback notifications.
10491      *
10492      * In the expanded form, {@link Notification#bigContentView}, up to 5
10493      * {@link Notification.Action}s specified with
10494      * {@link Notification.Builder#addAction(Action) addAction} will be
10495      * shown as icon-only pushbuttons, suitable for transport controls. The Bitmap given to
10496      * {@link Notification.Builder#setLargeIcon(android.graphics.Bitmap) setLargeIcon()} will be
10497      * treated as album artwork.
10498      * <p>
10499      * Unlike the other styles provided here, MediaStyle can also modify the standard-size
10500      * {@link Notification#contentView}; by providing action indices to
10501      * {@link #setShowActionsInCompactView(int...)} you can promote up to 3 actions to be displayed
10502      * in the standard view alongside the usual content.
10503      * <p>
10504      * Notifications created with MediaStyle will have their category set to
10505      * {@link Notification#CATEGORY_TRANSPORT CATEGORY_TRANSPORT} unless you set a different
10506      * category using {@link Notification.Builder#setCategory(String) setCategory()}.
10507      * <p>
10508      * Finally, if you attach a {@link android.media.session.MediaSession.Token} using
10509      * {@link android.app.Notification.MediaStyle#setMediaSession(MediaSession.Token)},
10510      * the System UI can identify this as a notification representing an active media session
10511      * and respond accordingly (by showing album artwork in the lockscreen, for example).
10512      *
10513      * <p>
10514      * Starting at {@link android.os.Build.VERSION_CODES#O Android O} any notification that has a
10515      * media session attached with {@link #setMediaSession(MediaSession.Token)} will be colorized.
10516      * You can opt-out of this behavior by using {@link Notification.Builder#setColorized(boolean)}.
10517      * <p>
10518      *
10519      * <p>
10520      * Starting at {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM Android V} the
10521      * {@link Notification#FLAG_NO_CLEAR NO_CLEAR flag} will be set for valid MediaStyle
10522      * notifications.
10523      * <p>
10524      *
10525      * To use this style with your Notification, feed it to
10526      * {@link Notification.Builder#setStyle(android.app.Notification.Style)} like so:
10527      * <pre class="prettyprint">
10528      * Notification noti = new Notification.Builder()
10529      *     .setSmallIcon(R.drawable.ic_stat_player)
10530      *     .setContentTitle(&quot;Track title&quot;)
10531      *     .setContentText(&quot;Artist - Album&quot;)
10532      *     .setLargeIcon(albumArtBitmap))
10533      *     .setStyle(<b>new Notification.MediaStyle()</b>
10534      *         .setMediaSession(mySession))
10535      *     .build();
10536      * </pre>
10537      *
10538      * @see Notification#bigContentView
10539      * @see Notification.Builder#setColorized(boolean)
10540      */
10541     public static class MediaStyle extends Style {
10542         // Changing max media buttons requires also changing templates
10543         // (notification_template_material_media and notification_template_material_big_media).
10544         static final int MAX_MEDIA_BUTTONS_IN_COMPACT = 3;
10545         static final int MAX_MEDIA_BUTTONS = 5;
10546         @IdRes private static final int[] MEDIA_BUTTON_IDS = {
10547                 R.id.action0,
10548                 R.id.action1,
10549                 R.id.action2,
10550                 R.id.action3,
10551                 R.id.action4,
10552         };
10553 
10554         private int[] mActionsToShowInCompact = null;
10555         private MediaSession.Token mToken;
10556         private CharSequence mDeviceName;
10557         private int mDeviceIcon;
10558         private PendingIntent mDeviceIntent;
10559 
MediaStyle()10560         public MediaStyle() {
10561         }
10562 
10563         /**
10564          * @deprecated use {@code MediaStyle()}.
10565          */
10566         @Deprecated
MediaStyle(Builder builder)10567         public MediaStyle(Builder builder) {
10568             setBuilder(builder);
10569         }
10570 
10571         /**
10572          * Request up to 3 actions (by index in the order of addition) to be shown in the compact
10573          * notification view.
10574          *
10575          * @param actions the indices of the actions to show in the compact notification view
10576          */
setShowActionsInCompactView(int...actions)10577         public MediaStyle setShowActionsInCompactView(int...actions) {
10578             mActionsToShowInCompact = actions;
10579             return this;
10580         }
10581 
10582         /**
10583          * Attach a {@link android.media.session.MediaSession.Token} to this Notification
10584          * to provide additional playback information and control to the SystemUI.
10585          */
setMediaSession(MediaSession.Token token)10586         public MediaStyle setMediaSession(MediaSession.Token token) {
10587             mToken = token;
10588             return this;
10589         }
10590 
10591         /**
10592          * For media notifications associated with playback on a remote device, provide device
10593          * information that will replace the default values for the output switcher chip on the
10594          * media control, as well as an intent to use when the output switcher chip is tapped,
10595          * on devices where this is supported.
10596          * <p>
10597          * This method is intended for system applications to provide information and/or
10598          * functionality that would otherwise be unavailable to the default output switcher because
10599          * the media originated on a remote device.
10600          *
10601          * @param deviceName The name of the remote device to display
10602          * @param iconResource Icon resource representing the device
10603          * @param chipIntent PendingIntent to send when the output switcher is tapped. May be
10604          *                   {@code null}, in which case the output switcher will be disabled.
10605          *                   This intent should open an Activity or it will be ignored.
10606          * @return MediaStyle
10607          */
10608         @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL)
10609         @NonNull
setRemotePlaybackInfo(@onNull CharSequence deviceName, @DrawableRes int iconResource, @Nullable PendingIntent chipIntent)10610         public MediaStyle setRemotePlaybackInfo(@NonNull CharSequence deviceName,
10611                 @DrawableRes int iconResource, @Nullable PendingIntent chipIntent) {
10612             mDeviceName = deviceName;
10613             mDeviceIcon = iconResource;
10614             mDeviceIntent = chipIntent;
10615             return this;
10616         }
10617 
10618         /**
10619          * @hide
10620          */
10621         @Override
10622         @UnsupportedAppUsage
buildStyled(Notification wip)10623         public Notification buildStyled(Notification wip) {
10624             super.buildStyled(wip);
10625             if (wip.category == null) {
10626                 wip.category = Notification.CATEGORY_TRANSPORT;
10627             }
10628             return wip;
10629         }
10630 
10631         /**
10632          * @hide
10633          */
10634         @Override
makeContentView()10635         public RemoteViews makeContentView() {
10636             return makeMediaContentView(null /* customContent */);
10637         }
10638 
10639         /**
10640          * @hide
10641          */
10642         @Override
makeExpandedContentView()10643         public RemoteViews makeExpandedContentView() {
10644             return makeMediaExpandedContentView(null /* customContent */);
10645         }
10646 
10647         /**
10648          * @hide
10649          */
10650         @Override
makeHeadsUpContentView()10651         public RemoteViews makeHeadsUpContentView() {
10652             return makeMediaContentView(null /* customContent */);
10653         }
10654 
10655         /** @hide */
10656         @Override
addExtras(Bundle extras)10657         public void addExtras(Bundle extras) {
10658             super.addExtras(extras);
10659 
10660             if (mToken != null) {
10661                 extras.putParcelable(EXTRA_MEDIA_SESSION, mToken);
10662             }
10663             if (mActionsToShowInCompact != null) {
10664                 extras.putIntArray(EXTRA_COMPACT_ACTIONS, mActionsToShowInCompact);
10665             }
10666             if (mDeviceName != null) {
10667                 extras.putCharSequence(EXTRA_MEDIA_REMOTE_DEVICE, mDeviceName);
10668             }
10669             if (mDeviceIcon > 0) {
10670                 extras.putInt(EXTRA_MEDIA_REMOTE_ICON, mDeviceIcon);
10671             }
10672             if (mDeviceIntent != null) {
10673                 extras.putParcelable(EXTRA_MEDIA_REMOTE_INTENT, mDeviceIntent);
10674             }
10675         }
10676 
10677         /**
10678          * @hide
10679          */
10680         @Override
restoreFromExtras(Bundle extras)10681         protected void restoreFromExtras(Bundle extras) {
10682             super.restoreFromExtras(extras);
10683 
10684             if (extras.containsKey(EXTRA_MEDIA_SESSION)) {
10685                 mToken = extras.getParcelable(EXTRA_MEDIA_SESSION, MediaSession.Token.class);
10686             }
10687             if (extras.containsKey(EXTRA_COMPACT_ACTIONS)) {
10688                 mActionsToShowInCompact = extras.getIntArray(EXTRA_COMPACT_ACTIONS);
10689             }
10690             if (extras.containsKey(EXTRA_MEDIA_REMOTE_DEVICE)) {
10691                 mDeviceName = extras.getCharSequence(EXTRA_MEDIA_REMOTE_DEVICE);
10692             }
10693             if (extras.containsKey(EXTRA_MEDIA_REMOTE_ICON)) {
10694                 mDeviceIcon = extras.getInt(EXTRA_MEDIA_REMOTE_ICON);
10695             }
10696             if (extras.containsKey(EXTRA_MEDIA_REMOTE_INTENT)) {
10697                 mDeviceIntent = extras.getParcelable(
10698                         EXTRA_MEDIA_REMOTE_INTENT, PendingIntent.class);
10699             }
10700         }
10701 
10702         /**
10703          * @hide
10704          */
10705         @Override
areNotificationsVisiblyDifferent(Style other)10706         public boolean areNotificationsVisiblyDifferent(Style other) {
10707             if (other == null || getClass() != other.getClass()) {
10708                 return true;
10709             }
10710             // All fields to compare are on the Notification object
10711             return false;
10712         }
10713 
bindMediaActionButton(RemoteViews container, @IdRes int buttonId, Action action, StandardTemplateParams p)10714         private void bindMediaActionButton(RemoteViews container, @IdRes int buttonId,
10715                 Action action, StandardTemplateParams p) {
10716             final boolean tombstone = (action.actionIntent == null);
10717             container.setViewVisibility(buttonId, View.VISIBLE);
10718             container.setImageViewIcon(buttonId, action.getIcon());
10719 
10720             // If the action buttons should not be tinted, then just use the default
10721             // notification color. Otherwise, just use the passed-in color.
10722             int tintColor = mBuilder.getStandardActionColor(p);
10723 
10724             container.setDrawableTint(buttonId, false, tintColor,
10725                     PorterDuff.Mode.SRC_ATOP);
10726 
10727             int rippleAlpha = mBuilder.getColors(p).getRippleAlpha();
10728             int rippleColor = Color.argb(rippleAlpha, Color.red(tintColor), Color.green(tintColor),
10729                     Color.blue(tintColor));
10730             container.setRippleDrawableColor(buttonId, ColorStateList.valueOf(rippleColor));
10731 
10732             if (!tombstone) {
10733                 container.setOnClickPendingIntent(buttonId, action.actionIntent);
10734             }
10735             container.setContentDescription(buttonId, action.title);
10736         }
10737 
10738         /** @hide */
makeMediaContentView(@ullable RemoteViews customContent)10739         protected RemoteViews makeMediaContentView(@Nullable RemoteViews customContent) {
10740             final int numActions = mBuilder.mActions.size();
10741             final int numActionsToShow = Math.min(mActionsToShowInCompact == null
10742                     ? 0 : mActionsToShowInCompact.length, MAX_MEDIA_BUTTONS_IN_COMPACT);
10743             if (numActionsToShow > numActions) {
10744                 throw new IllegalArgumentException(String.format(
10745                         "setShowActionsInCompactView: action %d out of bounds (max %d)",
10746                         numActions, numActions - 1));
10747             }
10748 
10749             StandardTemplateParams p = mBuilder.mParams.reset()
10750                     .viewType(StandardTemplateParams.VIEW_TYPE_NORMAL)
10751                     .hideTime(numActionsToShow > 1)       // hide if actions wider than a right icon
10752                     .hideSubText(numActionsToShow > 1)    // hide if actions wider than a right icon
10753                     .hideLeftIcon(false)                  // allow large icon on left when grouped
10754                     .hideRightIcon(numActionsToShow > 0)  // right icon or actions; not both
10755                     .hideProgress(true)
10756                     .fillTextsFrom(mBuilder);
10757             TemplateBindResult result = new TemplateBindResult();
10758             RemoteViews template = mBuilder.applyStandardTemplate(
10759                     mBuilder.getCollapsedMediaLayoutResource(), p,
10760                     null /* result */);
10761 
10762             for (int i = 0; i < MAX_MEDIA_BUTTONS_IN_COMPACT; i++) {
10763                 if (i < numActionsToShow) {
10764                     final Action action = mBuilder.mActions.get(mActionsToShowInCompact[i]);
10765                     bindMediaActionButton(template, MEDIA_BUTTON_IDS[i], action, p);
10766                 } else {
10767                     template.setViewVisibility(MEDIA_BUTTON_IDS[i], View.GONE);
10768                 }
10769             }
10770             // Prevent a swooping expand animation when there are no actions
10771             boolean hasActions = numActionsToShow != 0;
10772             template.setViewVisibility(R.id.media_actions, hasActions ? View.VISIBLE : View.GONE);
10773 
10774             // Add custom view if provided by subclass.
10775             buildCustomContentIntoTemplate(mBuilder.mContext, template, customContent, p, result);
10776             return template;
10777         }
10778 
10779         /** @hide */
makeMediaExpandedContentView(@ullable RemoteViews customContent)10780         protected RemoteViews makeMediaExpandedContentView(@Nullable RemoteViews customContent) {
10781             final int actionCount = Math.min(mBuilder.mActions.size(), MAX_MEDIA_BUTTONS);
10782             StandardTemplateParams p = mBuilder.mParams.reset()
10783                     .viewType(StandardTemplateParams.VIEW_TYPE_EXPANDED)
10784                     .hideProgress(true)
10785                     .fillTextsFrom(mBuilder);
10786             TemplateBindResult result = new TemplateBindResult();
10787             RemoteViews template = mBuilder.applyStandardTemplate(
10788                     mBuilder.getExpandedMediaLayoutResource(), p , result);
10789 
10790             for (int i = 0; i < MAX_MEDIA_BUTTONS; i++) {
10791                 if (i < actionCount) {
10792                     bindMediaActionButton(template,
10793                             MEDIA_BUTTON_IDS[i], mBuilder.mActions.get(i), p);
10794                 } else {
10795                     template.setViewVisibility(MEDIA_BUTTON_IDS[i], View.GONE);
10796                 }
10797             }
10798             buildCustomContentIntoTemplate(mBuilder.mContext, template, customContent, p, result);
10799             return template;
10800         }
10801     }
10802 
10803     /**
10804      * Helper class for generating large-format notifications that include a large image attachment.
10805      *
10806      * Here's how you'd set the <code>CallStyle</code> on a notification:
10807      * <pre class="prettyprint">
10808      * Notification notif = new Notification.Builder(mContext)
10809      *     .setSmallIcon(R.drawable.new_post)
10810      *     .setStyle(Notification.CallStyle.forIncomingCall(caller, declineIntent, answerIntent))
10811      *     .build();
10812      * </pre>
10813      */
10814     public static class CallStyle extends Style {
10815         /**
10816          * @hide
10817          */
10818         public static final boolean DEBUG_NEW_ACTION_LAYOUT = true;
10819 
10820         /**
10821          * @hide
10822          */
10823         @Retention(RetentionPolicy.SOURCE)
10824         @IntDef({
10825                 CALL_TYPE_UNKNOWN,
10826                 CALL_TYPE_INCOMING,
10827                 CALL_TYPE_ONGOING,
10828                 CALL_TYPE_SCREENING
10829         })
10830         public @interface CallType {};
10831 
10832         /**
10833          * Unknown call type.
10834          *
10835          * See {@link #EXTRA_CALL_TYPE}.
10836          */
10837         public static final int CALL_TYPE_UNKNOWN = 0;
10838 
10839         /**
10840          *  Call type for incoming calls.
10841          *
10842          *  See {@link #EXTRA_CALL_TYPE}.
10843          */
10844         public static final int CALL_TYPE_INCOMING = 1;
10845         /**
10846          * Call type for ongoing calls.
10847          *
10848          * See {@link #EXTRA_CALL_TYPE}.
10849          */
10850         public static final int CALL_TYPE_ONGOING = 2;
10851         /**
10852          * Call type for calls that are being screened.
10853          *
10854          * See {@link #EXTRA_CALL_TYPE}.
10855          */
10856         public static final int CALL_TYPE_SCREENING = 3;
10857 
10858         /**
10859          * This is a key used privately on the action.extras to give spacing priority
10860          * to the required call actions
10861          */
10862         private static final String KEY_ACTION_PRIORITY = "key_action_priority";
10863 
10864         private int mCallType;
10865         private Person mPerson;
10866         private PendingIntent mAnswerIntent;
10867         private PendingIntent mDeclineIntent;
10868         private PendingIntent mHangUpIntent;
10869         private boolean mIsVideo;
10870         private Integer mAnswerButtonColor;
10871         private Integer mDeclineButtonColor;
10872         private Icon mVerificationIcon;
10873         private CharSequence mVerificationText;
10874 
CallStyle()10875         CallStyle() {
10876         }
10877 
10878         /**
10879          * Create a CallStyle for an incoming call.
10880          * This notification will have a decline and an answer action, will allow a single
10881          * custom {@link Builder#addAction(Action) action}, and will have a default
10882          * {@link Builder#setContentText(CharSequence) content text} for an incoming call.
10883          *
10884          * @param person        The person displayed as the caller.
10885          *                      The person also needs to have a non-empty name associated with it.
10886          * @param declineIntent The intent to be sent when the user taps the decline action
10887          * @param answerIntent  The intent to be sent when the user taps the answer action
10888          */
10889         @NonNull
forIncomingCall(@onNull Person person, @NonNull PendingIntent declineIntent, @NonNull PendingIntent answerIntent)10890         public static CallStyle forIncomingCall(@NonNull Person person,
10891                 @NonNull PendingIntent declineIntent, @NonNull PendingIntent answerIntent) {
10892             return new CallStyle(CALL_TYPE_INCOMING, person,
10893                     null /* hangUpIntent */,
10894                     requireNonNull(declineIntent, "declineIntent is required"),
10895                     requireNonNull(answerIntent, "answerIntent is required")
10896             );
10897         }
10898 
10899         /**
10900          * Create a CallStyle for an ongoing call.
10901          * This notification will have a hang up action, will allow up to two
10902          * custom {@link Builder#addAction(Action) actions}, and will have a default
10903          * {@link Builder#setContentText(CharSequence) content text} for an ongoing call.
10904          *
10905          * @param person       The person displayed as being on the other end of the call.
10906          *                     The person also needs to have a non-empty name associated with it.
10907          * @param hangUpIntent The intent to be sent when the user taps the hang up action
10908          */
10909         @NonNull
forOngoingCall(@onNull Person person, @NonNull PendingIntent hangUpIntent)10910         public static CallStyle forOngoingCall(@NonNull Person person,
10911                 @NonNull PendingIntent hangUpIntent) {
10912             return new CallStyle(CALL_TYPE_ONGOING, person,
10913                     requireNonNull(hangUpIntent, "hangUpIntent is required"),
10914                     null /* declineIntent */,
10915                     null /* answerIntent */
10916             );
10917         }
10918 
10919         /**
10920          * Create a CallStyle for a call that is being screened.
10921          * This notification will have a hang up and an answer action, will allow a single
10922          * custom {@link Builder#addAction(Action) action}, and will have a default
10923          * {@link Builder#setContentText(CharSequence) content text} for a call that is being
10924          * screened.
10925          *
10926          * @param person       The person displayed as the caller.
10927          *                     The person also needs to have a non-empty name associated with it.
10928          * @param hangUpIntent The intent to be sent when the user taps the hang up action
10929          * @param answerIntent The intent to be sent when the user taps the answer action
10930          */
10931         @NonNull
forScreeningCall(@onNull Person person, @NonNull PendingIntent hangUpIntent, @NonNull PendingIntent answerIntent)10932         public static CallStyle forScreeningCall(@NonNull Person person,
10933                 @NonNull PendingIntent hangUpIntent, @NonNull PendingIntent answerIntent) {
10934             return new CallStyle(CALL_TYPE_SCREENING, person,
10935                     requireNonNull(hangUpIntent, "hangUpIntent is required"),
10936                     null /* declineIntent */,
10937                     requireNonNull(answerIntent, "answerIntent is required")
10938             );
10939         }
10940 
10941         /**
10942          * @param callType The type of the call
10943          * @param person The person displayed for the incoming call.
10944          *             The user also needs to have a non-empty name associated with it.
10945          * @param hangUpIntent The intent to be sent when the user taps the hang up action
10946          * @param declineIntent The intent to be sent when the user taps the decline action
10947          * @param answerIntent The intent to be sent when the user taps the answer action
10948          */
CallStyle(@allType int callType, @NonNull Person person, @Nullable PendingIntent hangUpIntent, @Nullable PendingIntent declineIntent, @Nullable PendingIntent answerIntent)10949         private CallStyle(@CallType int callType, @NonNull Person person,
10950                 @Nullable PendingIntent hangUpIntent, @Nullable PendingIntent declineIntent,
10951                 @Nullable PendingIntent answerIntent) {
10952             if (person == null || TextUtils.isEmpty(person.getName())) {
10953                 throw new IllegalArgumentException("person must have a non-empty a name");
10954             }
10955             mCallType = callType;
10956             mPerson = person;
10957             mAnswerIntent = answerIntent;
10958             mDeclineIntent = declineIntent;
10959             mHangUpIntent = hangUpIntent;
10960         }
10961 
10962         /**
10963          * Sets whether the call is a video call, which may affect the icons or text used on the
10964          * required action buttons.
10965          */
10966         @NonNull
setIsVideo(boolean isVideo)10967         public CallStyle setIsVideo(boolean isVideo) {
10968             mIsVideo = isVideo;
10969             return this;
10970         }
10971 
10972         /**
10973          * Optional icon to be displayed with {@link #setVerificationText(CharSequence) text}
10974          * as a verification status of the caller.
10975          */
10976         @NonNull
setVerificationIcon(@ullable Icon verificationIcon)10977         public CallStyle setVerificationIcon(@Nullable Icon verificationIcon) {
10978             mVerificationIcon = verificationIcon;
10979             return this;
10980         }
10981 
10982         /**
10983          * Optional text to be displayed with an {@link #setVerificationIcon(Icon) icon}
10984          * as a verification status of the caller.
10985          */
10986         @NonNull
setVerificationText(@ullable CharSequence verificationText)10987         public CallStyle setVerificationText(@Nullable CharSequence verificationText) {
10988             mVerificationText = safeCharSequence(verificationText);
10989             return this;
10990         }
10991 
10992         /**
10993          * Optional color to be used as a hint for the Answer action button's color.
10994          * The system may change this color to ensure sufficient contrast with the background.
10995          * The system may choose to disregard this hint if the notification is not colorized.
10996          */
10997         @NonNull
setAnswerButtonColorHint(@olorInt int color)10998         public CallStyle setAnswerButtonColorHint(@ColorInt int color) {
10999             mAnswerButtonColor = color;
11000             return this;
11001         }
11002 
11003         /**
11004          * Optional color to be used as a hint for the Decline or Hang Up action button's color.
11005          * The system may change this color to ensure sufficient contrast with the background.
11006          * The system may choose to disregard this hint if the notification is not colorized.
11007          */
11008         @NonNull
setDeclineButtonColorHint(@olorInt int color)11009         public CallStyle setDeclineButtonColorHint(@ColorInt int color) {
11010             mDeclineButtonColor = color;
11011             return this;
11012         }
11013 
11014         /** @hide */
11015         @Override
buildStyled(Notification wip)11016         public Notification buildStyled(Notification wip) {
11017             wip = super.buildStyled(wip);
11018             // ensure that the actions in the builder and notification are corrected.
11019             mBuilder.mActions = getActionsListWithSystemActions();
11020             wip.actions = new Action[mBuilder.mActions.size()];
11021             mBuilder.mActions.toArray(wip.actions);
11022             return wip;
11023         }
11024 
11025         /**
11026          * @hide
11027          */
displayCustomViewInline()11028         public boolean displayCustomViewInline() {
11029             // This is a lie; True is returned to make sure that the custom view is not used
11030             // instead of the template, but it will not actually be included.
11031             return true;
11032         }
11033 
11034         /**
11035          * @hide
11036          */
11037         @Override
purgeResources()11038         public void purgeResources() {
11039             super.purgeResources();
11040             if (mVerificationIcon != null) {
11041                 mVerificationIcon.convertToAshmem();
11042             }
11043         }
11044 
11045         /**
11046          * @hide
11047          */
11048         @Override
reduceImageSizes(Context context)11049         public void reduceImageSizes(Context context) {
11050             super.reduceImageSizes(context);
11051             if (mVerificationIcon != null) {
11052                 int rightIconSize = context.getResources().getDimensionPixelSize(
11053                         ActivityManager.isLowRamDeviceStatic()
11054                                 ? R.dimen.notification_right_icon_size_low_ram
11055                                 : R.dimen.notification_right_icon_size);
11056                 mVerificationIcon.scaleDownIfNecessary(rightIconSize, rightIconSize);
11057             }
11058         }
11059 
11060         /**
11061          * @hide
11062          */
11063         @Override
makeContentView()11064         public RemoteViews makeContentView() {
11065             return makeCallLayout(StandardTemplateParams.VIEW_TYPE_NORMAL);
11066         }
11067 
11068         /**
11069          * @hide
11070          */
11071         @Override
makeHeadsUpContentView()11072         public RemoteViews makeHeadsUpContentView() {
11073             return makeCallLayout(StandardTemplateParams.VIEW_TYPE_HEADS_UP);
11074         }
11075 
11076         /**
11077          * @hide
11078          */
11079         @Nullable
11080         @Override
makeCompactHeadsUpContentView()11081         public RemoteViews makeCompactHeadsUpContentView() {
11082             // Use existing heads up for call style.
11083             return makeHeadsUpContentView();
11084         }
11085 
11086         /**
11087          * @hide
11088          */
makeExpandedContentView()11089         public RemoteViews makeExpandedContentView() {
11090             return makeCallLayout(StandardTemplateParams.VIEW_TYPE_EXPANDED);
11091         }
11092 
11093         @NonNull
makeNegativeAction()11094         private Action makeNegativeAction() {
11095             if (mDeclineIntent == null) {
11096                 return makeAction(R.drawable.ic_call_decline,
11097                         R.string.call_notification_hang_up_action,
11098                         mDeclineButtonColor, R.color.call_notification_decline_color,
11099                         mHangUpIntent);
11100             } else {
11101                 return makeAction(R.drawable.ic_call_decline,
11102                         R.string.call_notification_decline_action,
11103                         mDeclineButtonColor, R.color.call_notification_decline_color,
11104                         mDeclineIntent);
11105             }
11106         }
11107 
11108         @Nullable
makeAnswerAction()11109         private Action makeAnswerAction() {
11110             return mAnswerIntent == null ? null : makeAction(
11111                     mIsVideo ? R.drawable.ic_call_answer_video : R.drawable.ic_call_answer,
11112                     mIsVideo ? R.string.call_notification_answer_video_action
11113                             : R.string.call_notification_answer_action,
11114                     mAnswerButtonColor, R.color.call_notification_answer_color,
11115                     mAnswerIntent);
11116         }
11117 
11118         @NonNull
makeAction(@rawableRes int icon, @StringRes int title, @ColorInt Integer colorInt, @ColorRes int defaultColorRes, PendingIntent intent)11119         private Action makeAction(@DrawableRes int icon, @StringRes int title,
11120                 @ColorInt Integer colorInt, @ColorRes int defaultColorRes, PendingIntent intent) {
11121             if (colorInt == null || !mBuilder.isCallActionColorCustomizable()) {
11122                 colorInt = mBuilder.mContext.getColor(defaultColorRes);
11123             }
11124             Action action = new Action.Builder(Icon.createWithResource("", icon),
11125                     new SpannableStringBuilder().append(mBuilder.mContext.getString(title),
11126                             new ForegroundColorSpan(colorInt),
11127                             SpannableStringBuilder.SPAN_INCLUSIVE_INCLUSIVE),
11128                     intent).build();
11129             action.getExtras().putBoolean(KEY_ACTION_PRIORITY, true);
11130             return action;
11131         }
11132 
isActionAddedByCallStyle(Action action)11133         private boolean isActionAddedByCallStyle(Action action) {
11134             // This is an internal extra added by the style to these actions. If an app were to add
11135             // this extra to the action themselves, the action would be dropped.  :shrug:
11136             return action != null && action.getExtras().getBoolean(KEY_ACTION_PRIORITY);
11137         }
11138 
11139         /**
11140          * Gets the actions list for the call with the answer/decline/hangUp actions inserted in
11141          * the correct place.  This returns the correct result even if the system actions have
11142          * already been added, and even if more actions were added since then.
11143          * @hide
11144          */
11145         @NonNull
getActionsListWithSystemActions()11146         public ArrayList<Action> getActionsListWithSystemActions() {
11147             // Define the system actions we expect to see
11148             final Action firstAction = makeNegativeAction();
11149             final Action lastAction = makeAnswerAction();
11150 
11151             // Start creating the result list.
11152             int nonContextualActionSlotsRemaining = MAX_ACTION_BUTTONS;
11153             ArrayList<Action> resultActions = new ArrayList<>(MAX_ACTION_BUTTONS);
11154 
11155             // Always have a first action.
11156             resultActions.add(firstAction);
11157             --nonContextualActionSlotsRemaining;
11158 
11159             // Copy actions into the new list, correcting system actions.
11160             if (mBuilder.mActions != null) {
11161                 for (Notification.Action action : mBuilder.mActions) {
11162                     if (action.isContextual()) {
11163                         // Always include all contextual actions
11164                         resultActions.add(action);
11165                     } else if (isActionAddedByCallStyle(action)) {
11166                         // Drop any old versions of system actions
11167                     } else {
11168                         // Copy non-contextual actions; decrement the remaining action slots.
11169                         resultActions.add(action);
11170                         --nonContextualActionSlotsRemaining;
11171                     }
11172                     // If there's exactly one action slot left, fill it with the lastAction.
11173                     if (lastAction != null && nonContextualActionSlotsRemaining == 1) {
11174                         resultActions.add(lastAction);
11175                         --nonContextualActionSlotsRemaining;
11176                     }
11177                 }
11178             }
11179             // If there are any action slots left, the lastAction still needs to be added.
11180             if (lastAction != null && nonContextualActionSlotsRemaining >= 1) {
11181                 resultActions.add(lastAction);
11182             }
11183             return resultActions;
11184         }
11185 
makeCallLayout(int viewType)11186         private RemoteViews makeCallLayout(int viewType) {
11187             final boolean isCollapsed = viewType == StandardTemplateParams.VIEW_TYPE_NORMAL;
11188             final boolean isHeadsUp = viewType == StandardTemplateParams.VIEW_TYPE_HEADS_UP;
11189             Bundle extras = mBuilder.mN.extras;
11190             CharSequence title = mPerson != null ? mPerson.getName() : null;
11191             CharSequence text = mBuilder.processLegacyText(extras.getCharSequence(EXTRA_TEXT));
11192             if (text == null) {
11193                 text = getDefaultText();
11194             }
11195 
11196             // Bind standard template
11197             StandardTemplateParams p = mBuilder.mParams.reset()
11198                     .viewType(viewType)
11199                     .callStyleActions(true)
11200                     .allowTextWithProgress(true)
11201                     .hideLeftIcon(true)
11202                     .hideRightIcon(true)
11203                     .hideAppName(isCollapsed)
11204                     .title(title)
11205                     .text(text);
11206             if (!notificationsRedesignTemplates()) {
11207                 // We're using the normal title in the redesign, not a special text.
11208                 p.titleViewId(R.id.conversation_text)
11209                         // The verification text is now part of the top line views, so this is no
11210                         // longer necessary.
11211                         .summaryText(mBuilder.processLegacyText(mVerificationText));
11212             }
11213             mBuilder.mActions = getActionsListWithSystemActions();
11214             final RemoteViews contentView;
11215             if (isCollapsed) {
11216                 contentView = mBuilder.applyStandardTemplate(
11217                         mBuilder.getCollapsedCallLayoutResource(), p, null /* result */);
11218             } else if (notificationsRedesignTemplates() && isHeadsUp) {
11219                 contentView = mBuilder.applyStandardTemplateWithActions(
11220                         mBuilder.getCollapsedCallLayoutResource(), p, null /* result */);
11221             } else {
11222                 contentView = mBuilder.applyStandardTemplateWithActions(
11223                     mBuilder.getExpandedCallLayoutResource(), p, null /* result */);
11224             }
11225 
11226             // Bind some extra conversation-specific header fields.
11227             if (!notificationsRedesignTemplates() && !p.mHideAppName) {
11228                 // Redesign note: This special divider is no longer needed.
11229                 mBuilder.setTextViewColorSecondary(contentView, R.id.app_name_divider, p);
11230                 contentView.setViewVisibility(R.id.app_name_divider, View.VISIBLE);
11231             }
11232             bindCallerVerification(contentView, p);
11233 
11234             // Bind some custom CallLayout properties
11235             contentView.setInt(R.id.status_bar_latest_event_content, "setLayoutColor",
11236                     mBuilder.getSmallIconColor(p));
11237             contentView.setInt(R.id.status_bar_latest_event_content,
11238                     "setNotificationBackgroundColor", mBuilder.getBackgroundColor(p));
11239             contentView.setIcon(R.id.status_bar_latest_event_content, "setLargeIcon",
11240                     mBuilder.mN.mLargeIcon);
11241             contentView.setBundle(R.id.status_bar_latest_event_content, "setData",
11242                     mBuilder.mN.extras);
11243 
11244             return contentView;
11245         }
11246 
bindCallerVerification(RemoteViews contentView, StandardTemplateParams p)11247         private void bindCallerVerification(RemoteViews contentView, StandardTemplateParams p) {
11248             String iconContentDescription = null;
11249             boolean showDivider = true;
11250             if (mVerificationIcon != null) {
11251                 contentView.setImageViewIcon(R.id.verification_icon, mVerificationIcon);
11252                 contentView.setDrawableTint(R.id.verification_icon, false /* targetBackground */,
11253                         mBuilder.getSecondaryTextColor(p), PorterDuff.Mode.SRC_ATOP);
11254                 contentView.setViewVisibility(R.id.verification_icon, View.VISIBLE);
11255                 iconContentDescription = mBuilder.mContext.getString(
11256                         R.string.notification_verified_content_description);
11257                 showDivider = false;  // the icon replaces the divider
11258             } else {
11259                 contentView.setViewVisibility(R.id.verification_icon, View.GONE);
11260             }
11261             if (!TextUtils.isEmpty(mVerificationText)) {
11262                 contentView.setTextViewText(R.id.verification_text, mVerificationText);
11263                 mBuilder.setTextViewColorSecondary(contentView, R.id.verification_text, p);
11264                 contentView.setViewVisibility(R.id.verification_text, View.VISIBLE);
11265                 iconContentDescription = null;  // let the app's text take precedence
11266             } else {
11267                 contentView.setViewVisibility(R.id.verification_text, View.GONE);
11268                 showDivider = false;  // no divider if no text
11269             }
11270             contentView.setContentDescription(R.id.verification_icon, iconContentDescription);
11271             if (showDivider) {
11272                 contentView.setViewVisibility(R.id.verification_divider, View.VISIBLE);
11273                 mBuilder.setTextViewColorSecondary(contentView, R.id.verification_divider, p);
11274             } else {
11275                 contentView.setViewVisibility(R.id.verification_divider, View.GONE);
11276             }
11277         }
11278 
11279         @Nullable
getDefaultText()11280         private String getDefaultText() {
11281             switch (mCallType) {
11282                 case CALL_TYPE_INCOMING:
11283                     return mBuilder.mContext.getString(R.string.call_notification_incoming_text);
11284                 case CALL_TYPE_ONGOING:
11285                     return mBuilder.mContext.getString(R.string.call_notification_ongoing_text);
11286                 case CALL_TYPE_SCREENING:
11287                     return mBuilder.mContext.getString(R.string.call_notification_screening_text);
11288             }
11289             return null;
11290         }
11291 
11292         /**
11293          * @hide
11294          */
addExtras(Bundle extras)11295         public void addExtras(Bundle extras) {
11296             super.addExtras(extras);
11297             extras.putInt(EXTRA_CALL_TYPE, mCallType);
11298             extras.putBoolean(EXTRA_CALL_IS_VIDEO, mIsVideo);
11299             extras.putParcelable(EXTRA_CALL_PERSON, mPerson);
11300             if (mVerificationIcon != null) {
11301                 extras.putParcelable(EXTRA_VERIFICATION_ICON, mVerificationIcon);
11302             }
11303             if (mVerificationText != null) {
11304                 extras.putCharSequence(EXTRA_VERIFICATION_TEXT, mVerificationText);
11305             }
11306             if (mAnswerIntent != null) {
11307                 extras.putParcelable(EXTRA_ANSWER_INTENT, mAnswerIntent);
11308             }
11309             if (mDeclineIntent != null) {
11310                 extras.putParcelable(EXTRA_DECLINE_INTENT, mDeclineIntent);
11311             }
11312             if (mHangUpIntent != null) {
11313                 extras.putParcelable(EXTRA_HANG_UP_INTENT, mHangUpIntent);
11314             }
11315             if (mAnswerButtonColor != null) {
11316                 extras.putInt(EXTRA_ANSWER_COLOR, mAnswerButtonColor);
11317             }
11318             if (mDeclineButtonColor != null) {
11319                 extras.putInt(EXTRA_DECLINE_COLOR, mDeclineButtonColor);
11320             }
11321             fixTitleAndTextExtras(extras);
11322         }
11323 
fixTitleAndTextExtras(Bundle extras)11324         private void fixTitleAndTextExtras(Bundle extras) {
11325             CharSequence sender = mPerson != null ? mPerson.getName() : null;
11326             if (sender != null) {
11327                 extras.putCharSequence(EXTRA_TITLE, sender);
11328             }
11329             if (extras.getCharSequence(EXTRA_TEXT) == null) {
11330                 extras.putCharSequence(EXTRA_TEXT, getDefaultText());
11331             }
11332         }
11333 
11334         /**
11335          * @hide
11336          */
11337         @Override
restoreFromExtras(Bundle extras)11338         protected void restoreFromExtras(Bundle extras) {
11339             super.restoreFromExtras(extras);
11340             mCallType = extras.getInt(EXTRA_CALL_TYPE);
11341             mIsVideo = extras.getBoolean(EXTRA_CALL_IS_VIDEO);
11342             mPerson = extras.getParcelable(EXTRA_CALL_PERSON, Person.class);
11343             mVerificationIcon = extras.getParcelable(EXTRA_VERIFICATION_ICON, android.graphics.drawable.Icon.class);
11344             mVerificationText = extras.getCharSequence(EXTRA_VERIFICATION_TEXT);
11345             mAnswerIntent = extras.getParcelable(EXTRA_ANSWER_INTENT, PendingIntent.class);
11346             mDeclineIntent = extras.getParcelable(EXTRA_DECLINE_INTENT, PendingIntent.class);
11347             mHangUpIntent = extras.getParcelable(EXTRA_HANG_UP_INTENT, PendingIntent.class);
11348             mAnswerButtonColor = extras.containsKey(EXTRA_ANSWER_COLOR)
11349                     ? extras.getInt(EXTRA_ANSWER_COLOR) : null;
11350             mDeclineButtonColor = extras.containsKey(EXTRA_DECLINE_COLOR)
11351                     ? extras.getInt(EXTRA_DECLINE_COLOR) : null;
11352         }
11353 
11354         /**
11355          * @hide
11356          */
11357         @Override
hasSummaryInHeader()11358         public boolean hasSummaryInHeader() {
11359             return false;
11360         }
11361 
11362         /**
11363          * @hide
11364          */
11365         @Override
areNotificationsVisiblyDifferent(Style other)11366         public boolean areNotificationsVisiblyDifferent(Style other) {
11367             if (other == null || getClass() != other.getClass()) {
11368                 return true;
11369             }
11370             CallStyle otherS = (CallStyle) other;
11371             return !Objects.equals(mCallType, otherS.mCallType)
11372                     || !Objects.equals(mPerson, otherS.mPerson)
11373                     || !Objects.equals(mVerificationText, otherS.mVerificationText);
11374         }
11375     }
11376 
11377     /**
11378      * A Notification Style used to define a notification whose expanded state includes
11379      * a highly customizable progress bar with segments, points, a custom tracker icon,
11380      * and custom icons at the start and end of the progress bar.
11381      *
11382      * This style is suggested for use cases where the app is showing a tracker to the
11383      * user of a thing they are interested in: the location of a car on its way
11384      * to pick them up, food being delivered, or their own progress in a navigation
11385      * journey.
11386      *
11387      * To use this style with your Notification, feed it to
11388      * {@link Notification.Builder#setStyle(android.app.Notification.Style)} like so:
11389      * <pre class="prettyprint">
11390      * new Notification.Builder(context)
11391      *   .setSmallIcon(R.drawable.ic_notification)
11392      *   .setColor(Color.GREEN)
11393      *   .setColorized(true)
11394      *   .setContentTitle("Arrive 10:08 AM").
11395      *   .setContentText("Dominique Ansel Bakery Soho")
11396      *   .addAction(new Notification.Action("Exit navigation",...))
11397      *   .setStyle(new Notification.ProgressStyle()
11398      *       .setStyledByProgress(false)
11399      *       .setProgress(456)
11400      *       .setProgressTrackerIcon(Icon.createWithResource(R.drawable.ic_driving_tracker))
11401      *       .addProgressSegment(new Segment(41).setColor(Color.BLACK))
11402      *       .addProgressSegment(new Segment(552).setColor(Color.YELLOW))
11403      *       .addProgressSegment(new Segment(253).setColor(Color.YELLOW))
11404      *       .addProgressSegment(new Segment(94).setColor(Color.BLUE))
11405      *       .addProgressPoint(new Point(60).setColor(Color.RED))
11406      *       .addProgressPoint(new Point(560).setColor(Color.YELLOW))
11407      *   )
11408      * </pre>
11409      *
11410      *
11411      * <p>
11412      * NOTE: The progress bar layout will be mirrored for RTL layout.
11413      * </p>
11414      *
11415      * <p>
11416      * NOTE: The extras set by {@link Notification.Builder#setProgress} will be overridden by
11417      * the values set on this style object when the notification is built.  Therefore, that method
11418      * is not used with this style.
11419      * </p>
11420      *
11421      */
11422     @FlaggedApi(Flags.FLAG_API_RICH_ONGOING)
11423     public static class ProgressStyle extends Notification.Style {
11424         private static final String KEY_ELEMENT_ID = "id";
11425         private static final String KEY_ELEMENT_COLOR = "colorInt";
11426         private static final String KEY_SEGMENT_LENGTH = "length";
11427         private static final String KEY_POINT_POSITION = "position";
11428 
11429         private static final int MAX_PROGRESS_SEGMENT_LIMIT = 10;
11430         private static final int MAX_PROGRESS_POINT_LIMIT = 4;
11431         private static final int DEFAULT_PROGRESS_MAX = 100;
11432 
11433         private List<Segment> mProgressSegments = new ArrayList<>();
11434         private List<Point> mProgressPoints = new ArrayList<>();
11435 
11436         private int mProgress = 0;
11437 
11438         private boolean mIndeterminate;
11439 
11440         private boolean mIsStyledByProgress = true;
11441 
11442         @Nullable
11443         private Icon mTrackerIcon;
11444         @Nullable
11445         private Icon mStartIcon;
11446         @Nullable
11447         private Icon mEndIcon;
11448 
11449         /**
11450          * @hide
11451          */
11452         @Override
areNotificationsVisiblyDifferent(Style other)11453         public boolean areNotificationsVisiblyDifferent(Style other) {
11454             if (other == null || getClass() != other.getClass()) {
11455                 return true;
11456             }
11457 
11458             final ProgressStyle progressStyle = (ProgressStyle) other;
11459 
11460             /**
11461              * @see #setProgressIndeterminate
11462              */
11463             if (!Objects.equals(mIndeterminate, progressStyle.mIndeterminate)) {
11464                 return true;
11465             }
11466             boolean nonIndeterminateCheckResult = false;
11467             if (!mIndeterminate) {
11468                 nonIndeterminateCheckResult = !Objects.equals(mProgress, progressStyle.mProgress)
11469                         || !Objects.equals(mIsStyledByProgress, progressStyle.mIsStyledByProgress)
11470                         || !Objects.equals(mProgressSegments, progressStyle.mProgressSegments)
11471                         || !Objects.equals(mProgressPoints, progressStyle.mProgressPoints)
11472                         || !Objects.equals(mTrackerIcon, progressStyle.mTrackerIcon);
11473             }
11474 
11475             return !Objects.equals(mStartIcon, progressStyle.mStartIcon)
11476                     || !Objects.equals(mEndIcon, progressStyle.mEndIcon)
11477                     || nonIndeterminateCheckResult;
11478         }
11479 
11480         /**
11481          * Gets the segments that define the background layer of the progress bar.
11482          *
11483          * If no segments are provided, the progress bar will be rendered with a single segment
11484          * with length 100 and default color.
11485          *
11486          * @see #setProgressSegments
11487          * @see #addProgressSegment
11488          * @see Segment
11489          */
getProgressSegments()11490         public @NonNull List<Segment> getProgressSegments() {
11491             return mProgressSegments;
11492         }
11493 
11494         /**
11495          * Sets or replaces the segments of the progress bar.
11496          *
11497          * Segments allow for creating progress bars with multiple colors or sections
11498          * to represent different stages or categories of progress.
11499          * For example, Traffic conditions along a navigation journey.
11500          * @see Segment
11501          */
setProgressSegments(@onNull List<Segment> progressSegments)11502         public @NonNull ProgressStyle setProgressSegments(@NonNull List<Segment> progressSegments) {
11503             if (mProgressSegments == null) {
11504                 mProgressSegments = new ArrayList<>();
11505             }
11506             mProgressSegments.clear();
11507             for (Segment segment : progressSegments) {
11508                 addProgressSegment(segment);
11509             }
11510             return this;
11511         }
11512 
11513         /**
11514          * Appends a segment to the end of the progress bar.
11515          *
11516          * Segments allow for creating progress bars with multiple colors or sections
11517          * to represent different stages or categories of progress.
11518          * For example, Traffic conditions along a navigation journey.
11519          * @see Segment
11520          */
addProgressSegment(@onNull Segment segment)11521         public @NonNull ProgressStyle addProgressSegment(@NonNull Segment segment) {
11522             if (mProgressSegments == null) {
11523                 mProgressSegments = new ArrayList<>();
11524             }
11525             if (segment.getLength() > 0) {
11526                 mProgressSegments.add(segment);
11527             } else {
11528                 Log.w(TAG, "Dropped the segment. The length is not a positive integer.");
11529             }
11530 
11531             return this;
11532         }
11533 
11534         /**
11535          * Gets the points that are displayed on the progress bar.
11536          *.
11537          * @see #setProgressPoints
11538          * @see #addProgressPoint
11539          * @see Point
11540          */
getProgressPoints()11541         public @NonNull List<Point> getProgressPoints() {
11542             return mProgressPoints;
11543         }
11544 
11545         /**
11546          * Replaces all the progress points.
11547          *
11548          * Points within a progress bar are used to visualize distinct stages or milestones.
11549          * For example, you might use points to mark stops in a multi-stop
11550          * navigation journey, where each point represents a destination.
11551          * @see Point
11552          */
setProgressPoints(@onNull List<Point> points)11553         public @NonNull ProgressStyle setProgressPoints(@NonNull List<Point> points) {
11554             if (mProgressPoints == null) {
11555                 mProgressPoints = new ArrayList<>();
11556             }
11557             mProgressPoints.clear();
11558 
11559             for (Point point: points) {
11560                 addProgressPoint(point);
11561             }
11562             return this;
11563         }
11564 
11565         /**
11566          * Adds another point.
11567          *
11568          * Points within a progress bar are used to visualize distinct stages or milestones.
11569          *
11570          * For example, you might use points to mark stops in a multi-stop
11571          * navigation journey, where each point represents a destination.
11572          *
11573          * Points can be added in any order, as their
11574          * position within the progress bar is determined by their individual
11575          * {@link Point#getPosition()}.
11576          * @see Point
11577          */
addProgressPoint(@onNull Point point)11578         public @NonNull ProgressStyle addProgressPoint(@NonNull Point point) {
11579             if (mProgressPoints == null) {
11580                 mProgressPoints = new ArrayList<>();
11581             }
11582             if (point.getPosition() > 0) {
11583                 mProgressPoints.add(point);
11584 
11585                 if (mProgressPoints.size() > MAX_PROGRESS_POINT_LIMIT) {
11586                     Log.w(TAG, "Progress points limit is reached. First"
11587                             + MAX_PROGRESS_POINT_LIMIT + " points will be rendered.");
11588                 }
11589 
11590             } else {
11591                 Log.w(TAG, "Dropped the point. The position is a negative or zero integer.");
11592             }
11593 
11594             return this;
11595         }
11596 
11597         /**
11598          * Gets the progress value of the progress bar.
11599          * @see #setProgress
11600          */
getProgress()11601         public int getProgress() {
11602             return mProgress;
11603         }
11604 
11605         /**
11606         * Specifies the progress (in the same units as {@link Segment#getLength()})
11607         * of the tracker along the length of the bar.
11608         *
11609         * The max progress value is the sum of all Segment lengths.
11610         * The default value is 0.
11611         */
setProgress(int progress)11612         public @NonNull ProgressStyle setProgress(int progress) {
11613             mProgress = progress;
11614             return this;
11615         }
11616 
11617         /**
11618          * Gets the sum of the lengths of all Segments in the style, which
11619          * defines the maximum progress. Defaults to 100 when segments are omitted.
11620          */
getProgressMax()11621         public int getProgressMax() {
11622             final List<Segment> progressSegment = mProgressSegments;
11623             if (progressSegment == null || progressSegment.isEmpty()) {
11624                 return DEFAULT_PROGRESS_MAX;
11625             } else {
11626                 int progressMax = 0;
11627                 int validSegmentCount = 0;
11628                 for (int i = 0; i < progressSegment.size(); i++) {
11629                     int segmentLength = progressSegment.get(i).getLength();
11630                     if (segmentLength > 0) {
11631                         try {
11632                             progressMax = Math.addExact(progressMax, segmentLength);
11633                             validSegmentCount++;
11634                         } catch (ArithmeticException e) {
11635                             Log.e(TAG,
11636                                     "Notification.ProgressStyle segment total overflowed.", e);
11637                             return DEFAULT_PROGRESS_MAX;
11638                         }
11639                     }
11640                 }
11641 
11642                 if (validSegmentCount == 0) {
11643                     return DEFAULT_PROGRESS_MAX;
11644                 }
11645 
11646                 return progressMax;
11647             }
11648 
11649         }
11650 
11651         /**
11652          * Get indeterminate value of the progress bar.
11653          * @see #setProgressIndeterminate
11654          */
isProgressIndeterminate()11655         public boolean isProgressIndeterminate() {
11656             return mIndeterminate;
11657         }
11658 
11659         /**
11660          * Used to indicate an initialization state without a known progress amount.
11661          * When specified, the following fields are ignored:
11662          * @see #setProgress
11663          * @see #setProgressSegments
11664          * @see #setProgressPoints
11665          * @see #setProgressTrackerIcon
11666          * @see #setStyledByProgress
11667          *
11668          * If the app provides exactly one Segment, that segment's color will be
11669          * used to style the indeterminate bar.
11670          */
setProgressIndeterminate(boolean indeterminate)11671         public @NonNull ProgressStyle setProgressIndeterminate(boolean indeterminate) {
11672             mIndeterminate = indeterminate;
11673             return this;
11674         }
11675 
11676         /**
11677          * Gets whether the progress bar's style is based on its progress.
11678          * @see #setStyledByProgress
11679          */
isStyledByProgress()11680         public boolean isStyledByProgress() {
11681             return mIsStyledByProgress;
11682         }
11683 
11684         /**
11685          * Indicates whether the segments and points will be styled differently
11686          * based on whether they are behind or ahead of the current progress.
11687          * When true, segments appearing ahead of the current progress will be given a
11688          * slightly different appearance to indicate that it is part of the progress bar
11689          * that is not "filled".
11690          * When false, all segments will be given the filled appearance, and it will be
11691          * the app's responsibility to use #setProgressTrackerIcon or segment colors
11692          * to make the current progress clear to the user.
11693          * the default value is true.
11694          */
setStyledByProgress(boolean enabled)11695         public @NonNull ProgressStyle setStyledByProgress(boolean enabled) {
11696             mIsStyledByProgress = enabled;
11697             return this;
11698         }
11699 
11700 
11701         /**
11702          * Gets the progress tracker icon for the progress bar.
11703          * @see #setProgressTrackerIcon
11704          */
getProgressTrackerIcon()11705         public @Nullable Icon getProgressTrackerIcon() {
11706             return mTrackerIcon;
11707         }
11708 
11709         /**
11710          * An optional icon that can appear as an overlay on the bar at the point of
11711          * current progress.
11712          * Aspect ratio may be anywhere from 2:1 to 1:2; content outside that
11713          * aspect ratio range will be cropped.
11714          * This icon will be mirrored in RTL.
11715          */
setProgressTrackerIcon(@ullable Icon trackerIcon)11716         public @NonNull ProgressStyle setProgressTrackerIcon(@Nullable Icon trackerIcon) {
11717             mTrackerIcon = trackerIcon;
11718             return this;
11719         }
11720 
11721         /**
11722          * Gets the progress bar start icon.
11723          * @see #setProgressStartIcon
11724          */
getProgressStartIcon()11725         public @Nullable Icon getProgressStartIcon() {
11726             return mStartIcon;
11727         }
11728 
11729         /**
11730          * An optional square icon that appears at the start of the progress bar.
11731          * This icon will be cropped to its central square.
11732          * This icon will NOT be mirrored in RTL layouts.
11733          */
setProgressStartIcon(@ullable Icon startIcon)11734         public @NonNull ProgressStyle setProgressStartIcon(@Nullable Icon startIcon) {
11735             mStartIcon = startIcon;
11736             return this;
11737         }
11738 
11739         /**
11740          * Gets the progress bar end icon.
11741          * @see #setProgressEndIcon(Icon)
11742          */
getProgressEndIcon()11743         public @Nullable Icon getProgressEndIcon() {
11744             return mEndIcon;
11745         }
11746 
11747         /**
11748          * An optional square icon that appears at the end of the progress bar.
11749          * This icon will be cropped to its central square.
11750          * This icon will NOT be mirrored in RTL layouts.
11751          */
setProgressEndIcon(@ullable Icon endIcon)11752         public @NonNull ProgressStyle setProgressEndIcon(@Nullable Icon endIcon) {
11753             mEndIcon = endIcon;
11754             return this;
11755         }
11756 
11757         /**
11758          * @hide
11759          */
11760         @Override
purgeResources()11761         public void purgeResources() {
11762             super.purgeResources();
11763             if (mTrackerIcon != null) {
11764                 mTrackerIcon.convertToAshmem();
11765             }
11766             if (mStartIcon != null) {
11767                 mStartIcon.convertToAshmem();
11768             }
11769             if (mEndIcon != null) {
11770                 mEndIcon.convertToAshmem();
11771             }
11772         }
11773 
11774         /**
11775          * @hide
11776          */
11777         @Override
reduceImageSizes(Context context)11778         public void reduceImageSizes(Context context) {
11779             super.reduceImageSizes(context);
11780 
11781             final Resources resources = context.getResources();
11782 
11783             int progressIconSize =
11784                     resources.getDimensionPixelSize(R.dimen.notification_progress_icon_size);
11785             if (mStartIcon != null) {
11786                 mStartIcon.scaleDownIfNecessary(progressIconSize, progressIconSize);
11787             }
11788             if (mEndIcon != null) {
11789                 mEndIcon.scaleDownIfNecessary(progressIconSize, progressIconSize);
11790             }
11791             if (mTrackerIcon != null) {
11792                 int progressTrackerWidth = resources.getDimensionPixelSize(
11793                         R.dimen.notification_progress_tracker_width);
11794                 int progressTrackerHeight = resources.getDimensionPixelSize(
11795                         R.dimen.notification_progress_tracker_height);
11796                 mTrackerIcon.scaleDownIfNecessary(progressTrackerWidth, progressTrackerHeight);
11797             }
11798         }
11799 
11800         /**
11801          * @hide
11802          */
11803         @Override
addExtras(Bundle extras)11804         public void addExtras(Bundle extras) {
11805             super.addExtras(extras);
11806             extras.putParcelableArrayList(EXTRA_PROGRESS_SEGMENTS,
11807                     getProgressSegmentsAsBundleList(mProgressSegments));
11808             extras.putParcelableArrayList(EXTRA_PROGRESS_POINTS,
11809                     getProgressPointsAsBundleList(mProgressPoints));
11810 
11811             extras.putInt(EXTRA_PROGRESS, mProgress);
11812             extras.putBoolean(EXTRA_PROGRESS_INDETERMINATE, mIndeterminate);
11813             extras.putInt(EXTRA_PROGRESS_MAX, getProgressMax());
11814             extras.putBoolean(EXTRA_STYLED_BY_PROGRESS, mIsStyledByProgress);
11815 
11816             if (mTrackerIcon != null) {
11817                 extras.putParcelable(EXTRA_PROGRESS_TRACKER_ICON, mTrackerIcon);
11818             } else {
11819                 extras.remove(EXTRA_PROGRESS_TRACKER_ICON);
11820             }
11821 
11822             if (mStartIcon != null) {
11823                 extras.putParcelable(EXTRA_PROGRESS_START_ICON, mStartIcon);
11824             } else {
11825                 extras.remove(EXTRA_PROGRESS_START_ICON);
11826             }
11827 
11828             if (mEndIcon != null) {
11829                 extras.putParcelable(EXTRA_PROGRESS_END_ICON, mEndIcon);
11830             } else {
11831                 extras.remove(EXTRA_PROGRESS_END_ICON);
11832             }
11833         }
11834 
11835         /**
11836          * @hide
11837          */
11838         @Override
restoreFromExtras(Bundle extras)11839         protected void restoreFromExtras(Bundle extras) {
11840             super.restoreFromExtras(extras);
11841             mProgressSegments = getProgressSegmentsFromBundleList(
11842                     extras.getParcelableArrayList(EXTRA_PROGRESS_SEGMENTS, Bundle.class));
11843             mProgress = extras.getInt(EXTRA_PROGRESS, 0);
11844             mIndeterminate = extras.getBoolean(EXTRA_PROGRESS_INDETERMINATE, false);
11845             mIsStyledByProgress = extras.getBoolean(EXTRA_STYLED_BY_PROGRESS, true);
11846             mTrackerIcon = extras.getParcelable(EXTRA_PROGRESS_TRACKER_ICON, Icon.class);
11847             mStartIcon = extras.getParcelable(EXTRA_PROGRESS_START_ICON, Icon.class);
11848             mEndIcon = extras.getParcelable(EXTRA_PROGRESS_END_ICON, Icon.class);
11849             mProgressPoints = getProgressPointsFromBundleList(
11850                     extras.getParcelableArrayList(EXTRA_PROGRESS_POINTS, Bundle.class));
11851         }
11852 
11853         /**
11854          * @hide
11855          */
11856         @Override
displayCustomViewInline()11857         public boolean displayCustomViewInline() {
11858             // This is a lie; True is returned for progress notifications to make sure
11859             // that the custom view is not used instead of the template, but it will not
11860             // actually be included.
11861             return true;
11862         }
11863         /**
11864          * @hide
11865          */
11866         @Override
makeContentView()11867         public RemoteViews makeContentView() {
11868             final StandardTemplateParams p = mBuilder.mParams.reset()
11869                     .viewType(StandardTemplateParams.VIEW_TYPE_NORMAL)
11870                     .hideProgress(true)
11871                     .fillTextsFrom(mBuilder);
11872 
11873             return getStandardView(mBuilder.getCollapsedBaseLayoutResource(), p, null /* result */);
11874         }
11875         /**
11876          * @hide
11877          */
11878         @Override
makeHeadsUpContentView()11879         public RemoteViews makeHeadsUpContentView() {
11880             final StandardTemplateParams p = mBuilder.mParams.reset()
11881                     .viewType(StandardTemplateParams.VIEW_TYPE_HEADS_UP)
11882                     .hideProgress(true)
11883                     .fillTextsFrom(mBuilder);
11884 
11885             return getStandardView(mBuilder.getHeadsUpBaseLayoutResource(), p, null /* result */);
11886         }
11887         /**
11888          * @hide
11889          */
11890         @Override
makeExpandedContentView()11891         public RemoteViews makeExpandedContentView() {
11892             StandardTemplateParams p = mBuilder.mParams.reset()
11893                     .viewType(StandardTemplateParams.VIEW_TYPE_EXPANDED)
11894                     .allowTextWithProgress(true)
11895                     .hideProgress(true)
11896                     .fillTextsFrom(mBuilder);
11897 
11898             // Replace the text with the big text, but only if the big text is not empty.
11899             RemoteViews contentView = getStandardView(mBuilder.getProgressLayoutResource(), p,
11900                     null /* result */);
11901 
11902             // Bind progress start and end icons.
11903             if (mStartIcon != null) {
11904                 contentView.setViewVisibility(R.id.notification_progress_start_icon, View.VISIBLE);
11905                 contentView.setImageViewIcon(R.id.notification_progress_start_icon, mStartIcon);
11906             } else {
11907                 contentView.setViewVisibility(R.id.notification_progress_start_icon, View.GONE);
11908             }
11909 
11910             if (mEndIcon != null) {
11911                 contentView.setViewVisibility(R.id.notification_progress_end_icon, View.VISIBLE);
11912                 contentView.setImageViewIcon(R.id.notification_progress_end_icon, mEndIcon);
11913             } else {
11914                 contentView.setViewVisibility(R.id.notification_progress_end_icon, View.GONE);
11915             }
11916 
11917             contentView.setViewVisibility(R.id.progress, View.VISIBLE);
11918 
11919             final int backgroundColor = mBuilder.getColors(p).getBackgroundColor();
11920             final int defaultProgressColor = mBuilder.getPrimaryAccentColor(p);
11921             final NotificationProgressModel model = createProgressModel(
11922                     defaultProgressColor, backgroundColor);
11923             contentView.setBundle(R.id.progress,
11924                     "setProgressModel", model.toBundle());
11925 
11926             contentView.setIcon(R.id.progress,
11927                     "setProgressTrackerIcon",
11928                     mTrackerIcon);
11929 
11930             return contentView;
11931         }
11932 
11933         /**
11934          * @hide
11935          */
getProgressSegmentsAsBundleList( @ullable List<Segment> progressSegments)11936         public static @NonNull ArrayList<Bundle> getProgressSegmentsAsBundleList(
11937                 @Nullable List<Segment> progressSegments) {
11938             final ArrayList<Bundle> segments = new ArrayList<>();
11939             if (progressSegments != null && !progressSegments.isEmpty()) {
11940                 for (int i = 0; i < progressSegments.size(); i++) {
11941                     final Segment segment = progressSegments.get(i);
11942                     if (segment.getLength() <= 0) {
11943                         continue;
11944                     }
11945 
11946                     final Bundle bundle = new Bundle();
11947                     bundle.putInt(KEY_SEGMENT_LENGTH, segment.getLength());
11948                     bundle.putInt(KEY_ELEMENT_ID, segment.getId());
11949                     bundle.putInt(KEY_ELEMENT_COLOR, segment.getColor());
11950 
11951                     segments.add(bundle);
11952                 }
11953             }
11954 
11955             return segments;
11956         }
11957 
11958         /**
11959          * @hide
11960          */
getProgressSegmentsFromBundleList( @ullable List<Bundle> segmentBundleList)11961         public  static @NonNull List<Segment> getProgressSegmentsFromBundleList(
11962                 @Nullable List<Bundle> segmentBundleList) {
11963             final ArrayList<Segment> segments = new ArrayList<>();
11964             if (segmentBundleList != null && !segmentBundleList.isEmpty()) {
11965                 for (int i = 0; i < segmentBundleList.size(); i++) {
11966                     final Bundle segmentBundle = segmentBundleList.get(i);
11967                     final int length = segmentBundle.getInt(KEY_SEGMENT_LENGTH);
11968                     if (length <= 0) {
11969                         continue;
11970                     }
11971 
11972                     final int id = segmentBundle.getInt(KEY_ELEMENT_ID);
11973                     final int color = segmentBundle.getInt(KEY_ELEMENT_COLOR,
11974                             Notification.COLOR_DEFAULT);
11975                     final Segment segment = new Segment(length)
11976                             .setId(id).setColor(color);
11977 
11978                     segments.add(segment);
11979                 }
11980             }
11981 
11982             return segments;
11983         }
11984         /**
11985          * @hide
11986          */
getProgressPointsAsBundleList( @ullable List<Point> progressPoints)11987         public static @NonNull ArrayList<Bundle> getProgressPointsAsBundleList(
11988                 @Nullable List<Point> progressPoints) {
11989             final ArrayList<Bundle> points = new ArrayList<>();
11990             if (progressPoints != null && !progressPoints.isEmpty()) {
11991                 for (int i = 0; i < progressPoints.size(); i++) {
11992                     final Point point = progressPoints.get(i);
11993                     if (point.getPosition() < 0) {
11994                         continue;
11995                     }
11996 
11997                     final Bundle bundle = new Bundle();
11998                     bundle.putInt(KEY_POINT_POSITION, point.getPosition());
11999                     bundle.putInt(KEY_ELEMENT_ID, point.getId());
12000                     bundle.putInt(KEY_ELEMENT_COLOR, point.getColor());
12001 
12002                     points.add(bundle);
12003                 }
12004             }
12005 
12006             return points;
12007         }
12008 
12009         /**
12010          * @hide
12011          */
getProgressPointsFromBundleList( @ullable List<Bundle> pointBundleList)12012         public static @NonNull List<Point> getProgressPointsFromBundleList(
12013                 @Nullable List<Bundle> pointBundleList) {
12014             final ArrayList<Point> points = new ArrayList<>();
12015 
12016             if (pointBundleList != null && !pointBundleList.isEmpty()) {
12017                 for (int i = 0; i < pointBundleList.size(); i++) {
12018                     final Bundle pointBundle = pointBundleList.get(i);
12019                     final int position = pointBundle.getInt(KEY_POINT_POSITION);
12020                     if (position < 0) {
12021                         continue;
12022                     }
12023                     final int id = pointBundle.getInt(KEY_ELEMENT_ID);
12024                     final int color = pointBundle.getInt(KEY_ELEMENT_COLOR,
12025                             Notification.COLOR_DEFAULT);
12026                     final Point point = new Point(position).setId(id).setColor(color);
12027                     points.add(point);
12028                 }
12029             }
12030 
12031             return points;
12032         }
12033 
12034         /**
12035          * @hide
12036          */
createProgressModel(int defaultProgressColor, int backgroundColor)12037         public @NonNull NotificationProgressModel createProgressModel(int defaultProgressColor,
12038                 int backgroundColor) {
12039             final NotificationProgressModel model;
12040             if (mIndeterminate) {
12041                 final int indeterminateColor;
12042                 if (!mProgressSegments.isEmpty()) {
12043                     indeterminateColor = mProgressSegments.get(0).mColor;
12044                 } else {
12045                     indeterminateColor = defaultProgressColor;
12046                 }
12047 
12048                 model = new NotificationProgressModel(
12049                         sanitizeProgressColor(indeterminateColor,
12050                                 backgroundColor, defaultProgressColor));
12051             } else {
12052                 // Ensure segment color contrasts.
12053                 final List<Segment> segments = new ArrayList<>();
12054                 int totalLength = 0;
12055                 for (Segment segment : mProgressSegments) {
12056                     final int length = segment.getLength();
12057                     if (length <= 0) continue;
12058 
12059                     try {
12060                         totalLength = Math.addExact(totalLength, length);
12061                         segments.add(sanitizeSegment(segment, backgroundColor,
12062                                 defaultProgressColor));
12063                     } catch (ArithmeticException e) {
12064                         totalLength = DEFAULT_PROGRESS_MAX;
12065                         segments.clear();
12066                         break;
12067                     }
12068                 }
12069 
12070                 // Create default segment when no segments are provided.
12071                 if (segments.isEmpty()) {
12072                     totalLength = DEFAULT_PROGRESS_MAX;
12073                     segments.add(sanitizeSegment(new Segment(totalLength), backgroundColor,
12074                             defaultProgressColor));
12075                 } else if (segments.size() > MAX_PROGRESS_SEGMENT_LIMIT) {
12076                     // If segment limit is exceeded. All segments will be replaced
12077                     // with a single segment
12078                     boolean allSameColor = true;
12079                     int firstSegmentColor = segments.getFirst().getColor();
12080 
12081                     for (int i = 1; i < segments.size(); i++) {
12082                         if (segments.get(i).getColor() != firstSegmentColor) {
12083                             allSameColor = false;
12084                             break;
12085                         }
12086                     }
12087 
12088                     // This single segment length has same max as total.
12089                     final Segment singleSegment = new Segment(totalLength);
12090                     // Single segment color: if all segments have the same color,
12091                     // use that color. Otherwise, use 0 / default.
12092                     singleSegment.setColor(allSameColor ? firstSegmentColor
12093                             : Notification.COLOR_DEFAULT);
12094 
12095                     segments.clear();
12096                     segments.add(sanitizeSegment(singleSegment,
12097                             backgroundColor,
12098                             defaultProgressColor));
12099                 }
12100 
12101                 // Ensure point color contrasts.
12102                 final List<Point> points = new ArrayList<>();
12103                 for (Point point : mProgressPoints) {
12104                     final int position = point.getPosition();
12105                     // The points at start/end aren't supposed to show in the progress bar.
12106                     // Therefore those are also dropped here.
12107                     if (position <= 0 || position >= totalLength) continue;
12108                     points.add(sanitizePoint(point, backgroundColor, defaultProgressColor));
12109                     if (points.size() == MAX_PROGRESS_POINT_LIMIT) {
12110                         break;
12111                     }
12112                 }
12113 
12114                 // If the segments and points can't all fit inside the progress drawable, the
12115                 // view will replace all segments with a single segment.
12116                 final int segmentsFallbackColor;
12117                 if (segments.size() <= 1) {
12118                     segmentsFallbackColor = NotificationProgressModel.INVALID_COLOR;
12119                 } else {
12120 
12121                     boolean allSameColor = true;
12122                     int firstSegmentColor = segments.getFirst().getColor();
12123                     for (int i = 1; i < segments.size(); i++) {
12124                         if (segments.get(i).getColor() != firstSegmentColor) {
12125                             allSameColor = false;
12126                             break;
12127                         }
12128                     }
12129                     // If the segments are of the same color, the view can just use that color.
12130                     // In that case there is no need to send the fallback color.
12131                     segmentsFallbackColor = allSameColor ? NotificationProgressModel.INVALID_COLOR
12132                             : sanitizeProgressColor(Notification.COLOR_DEFAULT, backgroundColor,
12133                                     defaultProgressColor);
12134                 }
12135 
12136                 model = new NotificationProgressModel(segments, points,
12137                         Math.clamp(mProgress, 0, totalLength), mIsStyledByProgress,
12138                         segmentsFallbackColor);
12139             }
12140             return model;
12141         }
12142 
sanitizeSegment(@onNull Segment segment, @ColorInt int bg, @ColorInt int defaultColor)12143         private Segment sanitizeSegment(@NonNull Segment segment,
12144                 @ColorInt int bg,
12145                 @ColorInt int defaultColor) {
12146             return new Segment(segment.getLength())
12147                     .setId(segment.getId())
12148                     .setColor(sanitizeProgressColor(segment.getColor(), bg, defaultColor));
12149         }
12150 
sanitizePoint(@onNull Point point, @ColorInt int bg, @ColorInt int defaultColor)12151         private Point sanitizePoint(@NonNull Point point,
12152                 @ColorInt int bg,
12153                 @ColorInt int defaultColor) {
12154             return new Point(point.getPosition()).setId(point.getId())
12155                     .setColor(sanitizeProgressColor(point.getColor(), bg, defaultColor));
12156         }
12157 
12158         /**
12159          * Finds steps and points fill color with sufficient contrast over bg (3:1) that
12160          * has the same hue as the original color, but is lightened or darkened depending on
12161          * whether the background is dark or light.
12162          *
12163          * @hide
12164          */
12165         @VisibleForTesting
sanitizeProgressColor(@olorInt int color, @ColorInt int bg, @ColorInt int defaultColor)12166         public static int sanitizeProgressColor(@ColorInt int color,
12167                 @ColorInt int bg,
12168                 @ColorInt int defaultColor) {
12169             return Builder.ensureColorContrast(
12170                     Color.alpha(color) == 0 ? defaultColor : color,
12171                     bg,
12172                     3);
12173         }
12174 
12175         /**
12176          * A segment of the progress bar, which defines its length and color.
12177          * Segments allow for creating progress bars with multiple colors or sections
12178          * to represent different stages or categories of progress.
12179          * For example, Traffic conditions along a navigation journey.
12180          */
12181         public static final class Segment {
12182             private int mLength;
12183             private int mId = 0;
12184             @ColorInt
12185             private int mColor = Notification.COLOR_DEFAULT;
12186 
12187             /**
12188              * Create a segment with a non-zero length.
12189              * @param length
12190              * See {@link #getLength}
12191              */
Segment(int length)12192             public Segment(int length) {
12193                 mLength = length;
12194             }
12195 
12196             /**
12197              * The length of this Segment within the progress bar.
12198              * This value has no units, it is just relative to the length of other segments,
12199              * and the value provided to {@link ProgressStyle#setProgress}.
12200              */
getLength()12201             public int getLength() {
12202                 return mLength;
12203             }
12204 
12205             /**
12206              * Gets the id of this Segment.
12207              *
12208              * @see #setId
12209              */
getId()12210             public int getId() {
12211                 return mId;
12212             }
12213 
12214             /**
12215              * Optional ID used to uniquely identify the element across updates.
12216              */
setId(int id)12217             public @NonNull Segment setId(int id) {
12218                 mId = id;
12219                 return this;
12220             }
12221 
12222             /**
12223              * Returns the color of this Segment.
12224              *
12225              * @see #setColor
12226              */
12227             @ColorInt
getColor()12228             public int getColor() {
12229                 return mColor;
12230             }
12231 
12232             /**
12233              * Optional color of this Segment
12234              */
setColor(@olorInt int color)12235             public @NonNull Segment setColor(@ColorInt int color) {
12236                 mColor = color;
12237                 return this;
12238             }
12239 
12240             /**
12241              * Needed for {@link Notification.Style#areNotificationsVisiblyDifferent}
12242              */
12243             @Override
equals(Object o)12244             public boolean equals(Object o) {
12245                 if (this == o) return true;
12246                 if (o == null || getClass() != o.getClass()) return false;
12247                 final Segment segment = (Segment) o;
12248                 return mLength == segment.mLength && mId == segment.mId
12249                         && mColor == segment.mColor;
12250             }
12251 
12252             @Override
hashCode()12253             public int hashCode() {
12254                 return Objects.hash(mLength, mId, mColor);
12255             }
12256         }
12257 
12258         /**
12259          * A point within the progress bar, defining its position and color.
12260          * Points within a progress bar are used to visualize distinct stages or milestones.
12261          * For example, you might use points to mark stops in a multi-stop
12262          * navigation journey, where each point represents a destination.
12263          */
12264         public static final class Point {
12265 
12266             private int mPosition;
12267             private int mId;
12268             @ColorInt
12269             private int mColor = Notification.COLOR_DEFAULT;
12270 
12271             /**
12272              * Create a point element.
12273              * The position of this point on the progress bar
12274              * relative to {@link ProgressStyle#getProgressMax}
12275              * @param position
12276              * See {@link #getPosition}
12277              */
Point(int position)12278             public Point(int position) {
12279                 mPosition = position;
12280             }
12281 
12282             /**
12283              * Gets the position of this Point.
12284              * The position of this point on the progress bar
12285              * relative to {@link ProgressStyle#getProgressMax}.
12286              */
getPosition()12287             public int getPosition() {
12288                 return mPosition;
12289             }
12290 
12291 
12292             /**
12293              * Optional ID used to uniquely identify the element across updates.
12294              */
getId()12295             public int getId() {
12296                 return mId;
12297             }
12298 
12299             /**
12300              * Optional ID used to uniquely identify the element across updates.
12301              */
setId(int id)12302             public @NonNull Point setId(int id) {
12303                 mId = id;
12304                 return this;
12305             }
12306 
12307             /**
12308              * Returns the color of this Segment.
12309              *
12310              * @see #setColor
12311              */
12312             @ColorInt
getColor()12313             public int getColor() {
12314                 return mColor;
12315             }
12316 
12317             /**
12318              * Optional color of this Segment
12319              */
setColor(@olorInt int color)12320             public @NonNull Point setColor(@ColorInt int color) {
12321                 mColor = color;
12322                 return this;
12323             }
12324 
12325             /**
12326              * Needed for {@link Notification.Style#areNotificationsVisiblyDifferent}
12327              */
12328             @Override
equals(Object o)12329             public boolean equals(Object o) {
12330                 if (this == o) return true;
12331                 if (o == null || getClass() != o.getClass()) return false;
12332                 final Point point = (Point) o;
12333                 return mPosition == point.mPosition && mId == point.mId
12334                         && mColor == point.mColor;
12335             }
12336 
12337             @Override
hashCode()12338             public int hashCode() {
12339                 return Objects.hash(mPosition, mId, mColor);
12340             }
12341         }
12342     }
12343 
12344     /**
12345      * Notification style for custom views that are decorated by the system
12346      *
12347      * <p>Instead of providing a notification that is completely custom, a developer can set this
12348      * style and still obtain system decorations like the notification header with the expand
12349      * affordance and actions.
12350      *
12351      * <p>Use {@link android.app.Notification.Builder#setCustomContentView(RemoteViews)},
12352      * {@link android.app.Notification.Builder#setCustomBigContentView(RemoteViews)} and
12353      * {@link android.app.Notification.Builder#setCustomHeadsUpContentView(RemoteViews)} to set the
12354      * corresponding custom views to display.
12355      *
12356      * To use this style with your Notification, feed it to
12357      * {@link Notification.Builder#setStyle(android.app.Notification.Style)} like so:
12358      * <pre class="prettyprint">
12359      * Notification noti = new Notification.Builder()
12360      *     .setSmallIcon(R.drawable.ic_stat_player)
12361      *     .setLargeIcon(albumArtBitmap))
12362      *     .setCustomContentView(contentView);
12363      *     .setStyle(<b>new Notification.DecoratedCustomViewStyle()</b>)
12364      *     .build();
12365      * </pre>
12366      */
12367     public static class DecoratedCustomViewStyle extends Style {
12368 
DecoratedCustomViewStyle()12369         public DecoratedCustomViewStyle() {
12370         }
12371 
12372         /**
12373          * @hide
12374          */
displayCustomViewInline()12375         public boolean displayCustomViewInline() {
12376             return true;
12377         }
12378 
12379         /**
12380          * @hide
12381          */
12382         @Override
makeContentView()12383         public RemoteViews makeContentView() {
12384             return makeStandardTemplateWithCustomContent(mBuilder.mN.contentView);
12385         }
12386 
12387         /**
12388          * @hide
12389          */
12390         @Override
makeExpandedContentView()12391         public RemoteViews makeExpandedContentView() {
12392             return makeDecoratedExpandedContentView();
12393         }
12394 
12395         /**
12396          * @hide
12397          */
12398         @Override
makeHeadsUpContentView()12399         public RemoteViews makeHeadsUpContentView() {
12400             return makeDecoratedHeadsUpContentView();
12401         }
12402 
makeDecoratedHeadsUpContentView()12403         private RemoteViews makeDecoratedHeadsUpContentView() {
12404             RemoteViews headsUpContentView = mBuilder.mN.headsUpContentView == null
12405                     ? mBuilder.mN.contentView
12406                     : mBuilder.mN.headsUpContentView;
12407             if (headsUpContentView == null) {
12408                 return null;  // no custom view; use the default behavior
12409             }
12410             if (mBuilder.mActions.size() == 0) {
12411                return makeStandardTemplateWithCustomContent(headsUpContentView);
12412             }
12413             TemplateBindResult result = new TemplateBindResult();
12414             StandardTemplateParams p = mBuilder.mParams.reset()
12415                     .viewType(StandardTemplateParams.VIEW_TYPE_HEADS_UP)
12416                     .decorationType(StandardTemplateParams.DECORATION_PARTIAL)
12417                     .fillTextsFrom(mBuilder);
12418             RemoteViews remoteViews = mBuilder.applyStandardTemplateWithActions(
12419                     mBuilder.getHeadsUpBaseLayoutResource(), p, result);
12420             buildCustomContentIntoTemplate(mBuilder.mContext, remoteViews, headsUpContentView,
12421                     p, result);
12422             return remoteViews;
12423         }
12424 
makeStandardTemplateWithCustomContent(RemoteViews customContent)12425         private RemoteViews makeStandardTemplateWithCustomContent(RemoteViews customContent) {
12426             if (customContent == null) {
12427                 return null;  // no custom view; use the default behavior
12428             }
12429             TemplateBindResult result = new TemplateBindResult();
12430             StandardTemplateParams p = mBuilder.mParams.reset()
12431                     .viewType(StandardTemplateParams.VIEW_TYPE_NORMAL)
12432                     .decorationType(StandardTemplateParams.DECORATION_PARTIAL)
12433                     .fillTextsFrom(mBuilder);
12434             RemoteViews remoteViews = mBuilder.applyStandardTemplate(
12435                     mBuilder.getCollapsedBaseLayoutResource(), p, result);
12436             buildCustomContentIntoTemplate(mBuilder.mContext, remoteViews, customContent,
12437                     p, result);
12438             return remoteViews;
12439         }
12440 
makeDecoratedExpandedContentView()12441         private RemoteViews makeDecoratedExpandedContentView() {
12442             RemoteViews bigContentView = mBuilder.mN.bigContentView == null
12443                     ? mBuilder.mN.contentView
12444                     : mBuilder.mN.bigContentView;
12445             if (bigContentView == null) {
12446                 return null;  // no custom view; use the default behavior
12447             }
12448             TemplateBindResult result = new TemplateBindResult();
12449             StandardTemplateParams p = mBuilder.mParams.reset()
12450                     .viewType(StandardTemplateParams.VIEW_TYPE_EXPANDED)
12451                     .decorationType(StandardTemplateParams.DECORATION_PARTIAL)
12452                     .fillTextsFrom(mBuilder);
12453             RemoteViews remoteViews = mBuilder.applyStandardTemplateWithActions(
12454                     mBuilder.getExpandedBaseLayoutResource(), p, result);
12455             buildCustomContentIntoTemplate(mBuilder.mContext, remoteViews, bigContentView,
12456                     p, result);
12457             return remoteViews;
12458         }
12459 
12460         /**
12461          * @hide
12462          */
12463         @Override
areNotificationsVisiblyDifferent(Style other)12464         public boolean areNotificationsVisiblyDifferent(Style other) {
12465             if (other == null || getClass() != other.getClass()) {
12466                 return true;
12467             }
12468             // Comparison done for all custom RemoteViews, independent of style
12469             return false;
12470         }
12471     }
12472 
12473     /**
12474      * Notification style for media custom views that are decorated by the system
12475      *
12476      * <p>Instead of providing a media notification that is completely custom, a developer can set
12477      * this style and still obtain system decorations like the notification header with the expand
12478      * affordance and actions.
12479      *
12480      * <p>Use {@link android.app.Notification.Builder#setCustomContentView(RemoteViews)},
12481      * {@link android.app.Notification.Builder#setCustomBigContentView(RemoteViews)} and
12482      * {@link android.app.Notification.Builder#setCustomHeadsUpContentView(RemoteViews)} to set the
12483      * corresponding custom views to display.
12484      * <p>
12485      * Contrary to {@link MediaStyle} a developer has to opt-in to the colorizing of the
12486      * notification by using {@link Notification.Builder#setColorized(boolean)}.
12487      * <p>
12488      * To use this style with your Notification, feed it to
12489      * {@link Notification.Builder#setStyle(android.app.Notification.Style)} like so:
12490      * <pre class="prettyprint">
12491      * Notification noti = new Notification.Builder()
12492      *     .setSmallIcon(R.drawable.ic_stat_player)
12493      *     .setLargeIcon(albumArtBitmap))
12494      *     .setCustomContentView(contentView);
12495      *     .setStyle(<b>new Notification.DecoratedMediaCustomViewStyle()</b>
12496      *          .setMediaSession(mySession))
12497      *     .build();
12498      * </pre>
12499      *
12500      * @see android.app.Notification.DecoratedCustomViewStyle
12501      * @see android.app.Notification.MediaStyle
12502      */
12503     public static class DecoratedMediaCustomViewStyle extends MediaStyle {
12504 
DecoratedMediaCustomViewStyle()12505         public DecoratedMediaCustomViewStyle() {
12506         }
12507 
12508         /**
12509          * @hide
12510          */
displayCustomViewInline()12511         public boolean displayCustomViewInline() {
12512             return true;
12513         }
12514 
12515         /**
12516          * @hide
12517          */
12518         @Override
makeContentView()12519         public RemoteViews makeContentView() {
12520             return makeMediaContentView(mBuilder.mN.contentView);
12521         }
12522 
12523         /**
12524          * @hide
12525          */
12526         @Override
makeExpandedContentView()12527         public RemoteViews makeExpandedContentView() {
12528             RemoteViews customContent = mBuilder.mN.bigContentView != null
12529                     ? mBuilder.mN.bigContentView
12530                     : mBuilder.mN.contentView;
12531             return makeMediaExpandedContentView(customContent);
12532         }
12533 
12534         /**
12535          * @hide
12536          */
12537         @Override
makeHeadsUpContentView()12538         public RemoteViews makeHeadsUpContentView() {
12539             RemoteViews customContent = mBuilder.mN.headsUpContentView != null
12540                     ? mBuilder.mN.headsUpContentView
12541                     : mBuilder.mN.contentView;
12542             return makeMediaExpandedContentView(customContent);
12543         }
12544 
12545         /**
12546          * @hide
12547          */
12548         @Override
areNotificationsVisiblyDifferent(Style other)12549         public boolean areNotificationsVisiblyDifferent(Style other) {
12550             if (other == null || getClass() != other.getClass()) {
12551                 return true;
12552             }
12553             // Comparison done for all custom RemoteViews, independent of style
12554             return false;
12555         }
12556     }
12557 
12558     /**
12559      * Encapsulates the information needed to display a notification as a bubble.
12560      *
12561      * <p>A bubble is used to display app content in a floating window over the existing
12562      * foreground activity. A bubble has a collapsed state represented by an icon and an
12563      * expanded state that displays an activity. These may be defined via
12564      * {@link Builder#Builder(PendingIntent, Icon)} or they may
12565      * be defined via an existing shortcut using {@link Builder#Builder(String)}.
12566      * </p>
12567      *
12568      * <b>Notifications with a valid and allowed bubble will display in collapsed state
12569      * outside of the notification shade on unlocked devices. When a user interacts with the
12570      * collapsed bubble, the bubble activity will be invoked and displayed.</b>
12571      *
12572      * @see Notification.Builder#setBubbleMetadata(BubbleMetadata)
12573      */
12574     public static final class BubbleMetadata implements Parcelable {
12575 
12576         private PendingIntent mPendingIntent;
12577         private PendingIntent mDeleteIntent;
12578         private Icon mIcon;
12579         private int mDesiredHeight;
12580         @DimenRes private int mDesiredHeightResId;
12581         private int mFlags;
12582         private String mShortcutId;
12583 
12584         /**
12585          * If set and the app creating the bubble is in the foreground, the bubble will be posted
12586          * in its expanded state.
12587          *
12588          * <p>This flag has no effect if the app posting the bubble is not in the foreground.
12589          * The app is considered foreground if it is visible and on the screen, note that
12590          * a foreground service does not qualify.
12591          * </p>
12592          *
12593          * <p>Generally this flag should only be set if the user has performed an action to request
12594          * or create a bubble.</p>
12595          *
12596          * @hide
12597          */
12598         public static final int FLAG_AUTO_EXPAND_BUBBLE = 0x00000001;
12599 
12600         /**
12601          * Indicates whether the notification associated with the bubble is being visually
12602          * suppressed from the notification shade. When <code>true</code> the notification is
12603          * hidden, when <code>false</code> the notification shows as normal.
12604          *
12605          * <p>Apps sending bubbles may set this flag so that the bubble is posted <b>without</b>
12606          * the associated notification in the notification shade.</p>
12607          *
12608          * <p>Generally this flag should only be set by the app if the user has performed an
12609          * action to request or create a bubble, or if the user has seen the content in the
12610          * notification and the notification is no longer relevant. </p>
12611          *
12612          * <p>The system will also update this flag with <code>true</code> to hide the notification
12613          * from the user once the bubble has been expanded. </p>
12614          *
12615          * @hide
12616          */
12617         public static final int FLAG_SUPPRESS_NOTIFICATION = 0x00000002;
12618 
12619         /**
12620          * Indicates whether the bubble should be visually suppressed from the bubble stack if the
12621          * user is viewing the same content outside of the bubble. For example, the user has a
12622          * bubble with Alice and then opens up the main app and navigates to Alice's page.
12623          *
12624          * @hide
12625          */
12626         public static final int FLAG_SUPPRESSABLE_BUBBLE = 0x00000004;
12627 
12628         /**
12629          * Indicates whether the bubble is visually suppressed from the bubble stack.
12630          *
12631          * @hide
12632          */
12633         public static final int FLAG_SUPPRESS_BUBBLE = 0x00000008;
12634 
BubbleMetadata(PendingIntent expandIntent, PendingIntent deleteIntent, Icon icon, int height, @DimenRes int heightResId, String shortcutId)12635         private BubbleMetadata(PendingIntent expandIntent, PendingIntent deleteIntent,
12636                 Icon icon, int height, @DimenRes int heightResId, String shortcutId) {
12637             mPendingIntent = expandIntent;
12638             mIcon = icon;
12639             mDesiredHeight = height;
12640             mDesiredHeightResId = heightResId;
12641             mDeleteIntent = deleteIntent;
12642             mShortcutId = shortcutId;
12643         }
12644 
BubbleMetadata(Parcel in)12645         private BubbleMetadata(Parcel in) {
12646             if (in.readInt() != 0) {
12647                 mPendingIntent = PendingIntent.CREATOR.createFromParcel(in);
12648             }
12649             if (in.readInt() != 0) {
12650                 mIcon = Icon.CREATOR.createFromParcel(in);
12651             }
12652             mDesiredHeight = in.readInt();
12653             mFlags = in.readInt();
12654             if (in.readInt() != 0) {
12655                 mDeleteIntent = PendingIntent.CREATOR.createFromParcel(in);
12656             }
12657             mDesiredHeightResId = in.readInt();
12658             if (in.readInt() != 0) {
12659                 mShortcutId = in.readString8();
12660             }
12661         }
12662 
12663         /**
12664          * @return the shortcut id used for this bubble if created via
12665          * {@link Builder#Builder(String)} or null if created
12666          * via {@link Builder#Builder(PendingIntent, Icon)}.
12667          */
12668         @Nullable
getShortcutId()12669         public String getShortcutId() {
12670             return mShortcutId;
12671         }
12672 
12673         /**
12674          * @return the pending intent used to populate the floating window for this bubble, or
12675          * null if this bubble is created via {@link Builder#Builder(String)}.
12676          */
12677         @SuppressLint("InvalidNullConversion")
12678         @Nullable
getIntent()12679         public PendingIntent getIntent() {
12680             return mPendingIntent;
12681         }
12682 
12683         /**
12684          * @return the pending intent to send when the bubble is dismissed by a user, if one exists.
12685          */
12686         @Nullable
getDeleteIntent()12687         public PendingIntent getDeleteIntent() {
12688             return mDeleteIntent;
12689         }
12690 
12691         /**
12692          * @return the icon that will be displayed for this bubble when it is collapsed, or null
12693          * if the bubble is created via {@link Builder#Builder(String)}.
12694          */
12695         @SuppressLint("InvalidNullConversion")
12696         @Nullable
getIcon()12697         public Icon getIcon() {
12698             return mIcon;
12699         }
12700 
12701         /**
12702          * @return the ideal height, in DPs, for the floating window that app content defined by
12703          * {@link #getIntent()} for this bubble. A value of 0 indicates a desired height has
12704          * not been set.
12705          */
12706         @Dimension(unit = DP)
getDesiredHeight()12707         public int getDesiredHeight() {
12708             return mDesiredHeight;
12709         }
12710 
12711         /**
12712          * @return the resId of ideal height for the floating window that app content defined by
12713          * {@link #getIntent()} for this bubble. A value of 0 indicates a res value has not
12714          * been provided for the desired height.
12715          */
12716         @DimenRes
getDesiredHeightResId()12717         public int getDesiredHeightResId() {
12718             return mDesiredHeightResId;
12719         }
12720 
12721         /**
12722          * @return whether this bubble should auto expand when it is posted.
12723          *
12724          * @see BubbleMetadata.Builder#setAutoExpandBubble(boolean)
12725          */
getAutoExpandBubble()12726         public boolean getAutoExpandBubble() {
12727             return (mFlags & FLAG_AUTO_EXPAND_BUBBLE) != 0;
12728         }
12729 
12730         /**
12731          * Indicates whether the notification associated with the bubble is being visually
12732          * suppressed from the notification shade. When <code>true</code> the notification is
12733          * hidden, when <code>false</code> the notification shows as normal.
12734          *
12735          * <p>Apps sending bubbles may set this flag so that the bubble is posted <b>without</b>
12736          * the associated notification in the notification shade.</p>
12737          *
12738          * <p>Generally the app should only set this flag if the user has performed an
12739          * action to request or create a bubble, or if the user has seen the content in the
12740          * notification and the notification is no longer relevant. </p>
12741          *
12742          * <p>The system will update this flag with <code>true</code> to hide the notification
12743          * from the user once the bubble has been expanded.</p>
12744          *
12745          * @return whether this bubble should suppress the notification when it is posted.
12746          *
12747          * @see BubbleMetadata.Builder#setSuppressNotification(boolean)
12748          */
isNotificationSuppressed()12749         public boolean isNotificationSuppressed() {
12750             return (mFlags & FLAG_SUPPRESS_NOTIFICATION) != 0;
12751         }
12752 
12753         /**
12754          * Indicates whether the bubble should be visually suppressed from the bubble stack if the
12755          * user is viewing the same content outside of the bubble. For example, the user has a
12756          * bubble with Alice and then opens up the main app and navigates to Alice's page.
12757          *
12758          * To match the activity and the bubble notification, the bubble notification should
12759          * have a locus id set that matches a locus id set on the activity.
12760          *
12761          * @return whether this bubble should be suppressed when the same content is visible
12762          * outside of the bubble.
12763          *
12764          * @see BubbleMetadata.Builder#setSuppressableBubble(boolean)
12765          */
isBubbleSuppressable()12766         public boolean isBubbleSuppressable() {
12767             return (mFlags & FLAG_SUPPRESSABLE_BUBBLE) != 0;
12768         }
12769 
12770         /**
12771          * Indicates whether the bubble is currently visually suppressed from the bubble stack.
12772          *
12773          * @see BubbleMetadata.Builder#setSuppressableBubble(boolean)
12774          */
isBubbleSuppressed()12775         public boolean isBubbleSuppressed() {
12776             return (mFlags & FLAG_SUPPRESS_BUBBLE) != 0;
12777         }
12778 
12779         /**
12780          * Sets whether the notification associated with the bubble is being visually
12781          * suppressed from the notification shade. When <code>true</code> the notification is
12782          * hidden, when <code>false</code> the notification shows as normal.
12783          *
12784          * @hide
12785          */
setSuppressNotification(boolean suppressed)12786         public void setSuppressNotification(boolean suppressed) {
12787             if (suppressed) {
12788                 mFlags |= Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION;
12789             } else {
12790                 mFlags &= ~Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION;
12791             }
12792         }
12793 
12794         /**
12795          * Sets whether the bubble should be visually suppressed from the bubble stack if the
12796          * user is viewing the same content outside of the bubble. For example, the user has a
12797          * bubble with Alice and then opens up the main app and navigates to Alice's page.
12798          *
12799          * @hide
12800          */
setSuppressBubble(boolean suppressed)12801         public void setSuppressBubble(boolean suppressed) {
12802             if (suppressed) {
12803                 mFlags |= Notification.BubbleMetadata.FLAG_SUPPRESS_BUBBLE;
12804             } else {
12805                 mFlags &= ~Notification.BubbleMetadata.FLAG_SUPPRESS_BUBBLE;
12806             }
12807         }
12808 
12809         /**
12810          * @hide
12811          */
setFlags(int flags)12812         public void setFlags(int flags) {
12813             mFlags = flags;
12814         }
12815 
12816         /**
12817          * @hide
12818          */
getFlags()12819         public int getFlags() {
12820             return mFlags;
12821         }
12822 
12823         public static final @android.annotation.NonNull Parcelable.Creator<BubbleMetadata> CREATOR =
12824                 new Parcelable.Creator<BubbleMetadata>() {
12825 
12826                     @Override
12827                     public BubbleMetadata createFromParcel(Parcel source) {
12828                         return new BubbleMetadata(source);
12829                     }
12830 
12831                     @Override
12832                     public BubbleMetadata[] newArray(int size) {
12833                         return new BubbleMetadata[size];
12834                     }
12835                 };
12836 
12837         @Override
describeContents()12838         public int describeContents() {
12839             return 0;
12840         }
12841 
12842         @Override
writeToParcel(Parcel out, int flags)12843         public void writeToParcel(Parcel out, int flags) {
12844             out.writeInt(mPendingIntent != null ? 1 : 0);
12845             if (mPendingIntent != null) {
12846                 mPendingIntent.writeToParcel(out, 0);
12847             }
12848             out.writeInt(mIcon != null ? 1 : 0);
12849             if (mIcon != null) {
12850                 mIcon.writeToParcel(out, 0);
12851             }
12852             out.writeInt(mDesiredHeight);
12853             out.writeInt(mFlags);
12854             out.writeInt(mDeleteIntent != null ? 1 : 0);
12855             if (mDeleteIntent != null) {
12856                 mDeleteIntent.writeToParcel(out, 0);
12857             }
12858             out.writeInt(mDesiredHeightResId);
12859             out.writeInt(TextUtils.isEmpty(mShortcutId) ? 0 : 1);
12860             if (!TextUtils.isEmpty(mShortcutId)) {
12861                 out.writeString8(mShortcutId);
12862             }
12863         }
12864 
12865         /**
12866          * Builder to construct a {@link BubbleMetadata} object.
12867          */
12868         public static final class Builder {
12869 
12870             private PendingIntent mPendingIntent;
12871             private Icon mIcon;
12872             private int mDesiredHeight;
12873             @DimenRes private int mDesiredHeightResId;
12874             private int mFlags;
12875             private PendingIntent mDeleteIntent;
12876             private String mShortcutId;
12877 
12878             /**
12879              * @deprecated use {@link Builder#Builder(String)} for a bubble created via a
12880              * {@link ShortcutInfo} or {@link Builder#Builder(PendingIntent, Icon)} for a bubble
12881              * created via a {@link PendingIntent}.
12882              */
12883             @Deprecated
Builder()12884             public Builder() {
12885             }
12886 
12887             /**
12888              * Creates a {@link BubbleMetadata.Builder} based on a {@link ShortcutInfo}. To create
12889              * a shortcut bubble, ensure that the shortcut associated with the provided
12890              * {@param shortcutId} is published as a dynamic shortcut that was built with
12891              * {@link ShortcutInfo.Builder#setLongLived(boolean)} being true, otherwise your
12892              * notification will not be able to bubble.
12893              *
12894              * <p>The shortcut icon will be used to represent the bubble when it is collapsed.</p>
12895              *
12896              * <p>The shortcut activity will be used when the bubble is expanded. This will display
12897              * the shortcut activity in a floating window over the existing foreground activity.</p>
12898              *
12899              * <p>When the activity is launched from a bubble,
12900              * {@link Activity#isLaunchedFromBubble()} will return with {@code true}.
12901              * </p>
12902              *
12903              * <p>If the shortcut has not been published when the bubble notification is sent,
12904              * no bubble will be produced. If the shortcut is deleted while the bubble is active,
12905              * the bubble will be removed.</p>
12906              *
12907              * @throws NullPointerException if shortcutId is null.
12908              *
12909              * @see ShortcutInfo
12910              * @see ShortcutInfo.Builder#setLongLived(boolean)
12911              * @see android.content.pm.ShortcutManager#addDynamicShortcuts(List)
12912              */
Builder(@onNull String shortcutId)12913             public Builder(@NonNull String shortcutId) {
12914                 if (TextUtils.isEmpty(shortcutId)) {
12915                     throw new NullPointerException("Bubble requires a non-null shortcut id");
12916                 }
12917                 mShortcutId = shortcutId;
12918             }
12919 
12920             /**
12921              * Creates a {@link BubbleMetadata.Builder} based on the provided intent and icon.
12922              *
12923              * <p>The icon will be used to represent the bubble when it is collapsed. An icon
12924              * should be representative of the content within the bubble. If your app produces
12925              * multiple bubbles, the icon should be unique for each of them.</p>
12926              *
12927              * <p>The intent that will be used when the bubble is expanded. This will display the
12928              * app content in a floating window over the existing foreground activity. The intent
12929              * should point to a resizable activity. </p>
12930              *
12931              * <p>When the activity is launched from a bubble,
12932              * {@link Activity#isLaunchedFromBubble()} will return with {@code true}.
12933              * </p>
12934              *
12935              * Note that the pending intent used here requires PendingIntent.FLAG_MUTABLE.
12936              *
12937              * @throws NullPointerException if intent is null.
12938              * @throws NullPointerException if icon is null.
12939              */
Builder(@onNull PendingIntent intent, @NonNull Icon icon)12940             public Builder(@NonNull PendingIntent intent, @NonNull Icon icon) {
12941                 if (intent == null) {
12942                     throw new NullPointerException("Bubble requires non-null pending intent");
12943                 }
12944                 if (icon == null) {
12945                     throw new NullPointerException("Bubbles require non-null icon");
12946                 }
12947                 if (icon.getType() != TYPE_URI_ADAPTIVE_BITMAP
12948                         && icon.getType() != TYPE_URI) {
12949                     Log.w(TAG, "Bubbles work best with icons of TYPE_URI or "
12950                             + "TYPE_URI_ADAPTIVE_BITMAP. "
12951                             + "In the future, using an icon of this type will be required.");
12952                 }
12953                 mPendingIntent = intent;
12954                 mIcon = icon;
12955             }
12956 
12957             /**
12958              * Sets the intent for the bubble.
12959              *
12960              * <p>The intent that will be used when the bubble is expanded. This will display the
12961              * app content in a floating window over the existing foreground activity. The intent
12962              * should point to a resizable activity. </p>
12963              *
12964              * @throws NullPointerException  if intent is null.
12965              * @throws IllegalStateException if this builder was created via
12966              *                               {@link Builder#Builder(String)}.
12967              */
12968             @NonNull
setIntent(@onNull PendingIntent intent)12969             public BubbleMetadata.Builder setIntent(@NonNull PendingIntent intent) {
12970                 if (mShortcutId != null) {
12971                     throw new IllegalStateException("Created as a shortcut bubble, cannot set a "
12972                             + "PendingIntent. Consider using "
12973                             + "BubbleMetadata.Builder(PendingIntent,Icon) instead.");
12974                 }
12975                 if (intent == null) {
12976                     throw new NullPointerException("Bubble requires non-null pending intent");
12977                 }
12978                 mPendingIntent = intent;
12979                 return this;
12980             }
12981 
12982             /**
12983              * Sets the icon for the bubble. Can only be used if the bubble was created
12984              * via {@link Builder#Builder(PendingIntent, Icon)}.
12985              *
12986              * <p>The icon will be used to represent the bubble when it is collapsed. An icon
12987              * should be representative of the content within the bubble. If your app produces
12988              * multiple bubbles, the icon should be unique for each of them.</p>
12989              *
12990              * <p>It is recommended to use an {@link Icon} of type {@link Icon#TYPE_URI}
12991              * or {@link Icon#TYPE_URI_ADAPTIVE_BITMAP}</p>
12992              *
12993              * @throws NullPointerException  if icon is null.
12994              * @throws IllegalStateException if this builder was created via
12995              *                               {@link Builder#Builder(String)}.
12996              */
12997             @NonNull
setIcon(@onNull Icon icon)12998             public BubbleMetadata.Builder setIcon(@NonNull Icon icon) {
12999                 if (mShortcutId != null) {
13000                     throw new IllegalStateException("Created as a shortcut bubble, cannot set an "
13001                             + "Icon. Consider using "
13002                             + "BubbleMetadata.Builder(PendingIntent,Icon) instead.");
13003                 }
13004                 if (icon == null) {
13005                     throw new NullPointerException("Bubbles require non-null icon");
13006                 }
13007                 if (icon.getType() != TYPE_URI_ADAPTIVE_BITMAP
13008                         && icon.getType() != TYPE_URI) {
13009                     Log.w(TAG, "Bubbles work best with icons of TYPE_URI or "
13010                             + "TYPE_URI_ADAPTIVE_BITMAP. "
13011                             + "In the future, using an icon of this type will be required.");
13012                 }
13013                 mIcon = icon;
13014                 return this;
13015             }
13016 
13017             /**
13018              * Sets the desired height in DPs for the expanded content of the bubble.
13019              *
13020              * <p>This height may not be respected if there is not enough space on the screen or if
13021              * the provided height is too small to be useful.</p>
13022              *
13023              * <p>If {@link #setDesiredHeightResId(int)} was previously called on this builder, the
13024              * previous value set will be cleared after calling this method, and this value will
13025              * be used instead.</p>
13026              *
13027              * <p>A desired height (in DPs or via resID) is optional.</p>
13028              *
13029              * @see #setDesiredHeightResId(int)
13030              */
13031             @NonNull
setDesiredHeight(@imensionunit = DP) int height)13032             public BubbleMetadata.Builder setDesiredHeight(@Dimension(unit = DP) int height) {
13033                 mDesiredHeight = Math.max(height, 0);
13034                 mDesiredHeightResId = 0;
13035                 return this;
13036             }
13037 
13038 
13039             /**
13040              * Sets the desired height via resId for the expanded content of the bubble.
13041              *
13042              * <p>This height may not be respected if there is not enough space on the screen or if
13043              * the provided height is too small to be useful.</p>
13044              *
13045              * <p>If {@link #setDesiredHeight(int)} was previously called on this builder, the
13046              * previous value set will be cleared after calling this method, and this value will
13047              * be used instead.</p>
13048              *
13049              * <p>A desired height (in DPs or via resID) is optional.</p>
13050              *
13051              * @see #setDesiredHeight(int)
13052              */
13053             @NonNull
setDesiredHeightResId(@imenRes int heightResId)13054             public BubbleMetadata.Builder setDesiredHeightResId(@DimenRes int heightResId) {
13055                 mDesiredHeightResId = heightResId;
13056                 mDesiredHeight = 0;
13057                 return this;
13058             }
13059 
13060             /**
13061              * Sets whether the bubble will be posted in its expanded state.
13062              *
13063              * <p>This flag has no effect if the app posting the bubble is not in the foreground.
13064              * The app is considered foreground if it is visible and on the screen, note that
13065              * a foreground service does not qualify.
13066              * </p>
13067              *
13068              * <p>Generally, this flag should only be set if the user has performed an action to
13069              * request or create a bubble.</p>
13070              *
13071              * <p>Setting this flag is optional; it defaults to false.</p>
13072              */
13073             @NonNull
setAutoExpandBubble(boolean shouldExpand)13074             public BubbleMetadata.Builder setAutoExpandBubble(boolean shouldExpand) {
13075                 setFlag(FLAG_AUTO_EXPAND_BUBBLE, shouldExpand);
13076                 return this;
13077             }
13078 
13079             /**
13080              * Sets whether the bubble will be posted <b>without</b> the associated notification in
13081              * the notification shade.
13082              *
13083              * <p>Generally, this flag should only be set if the user has performed an action to
13084              * request or create a bubble, or if the user has seen the content in the notification
13085              * and the notification is no longer relevant.</p>
13086              *
13087              * <p>Setting this flag is optional; it defaults to false.</p>
13088              */
13089             @NonNull
setSuppressNotification(boolean shouldSuppressNotif)13090             public BubbleMetadata.Builder setSuppressNotification(boolean shouldSuppressNotif) {
13091                 setFlag(FLAG_SUPPRESS_NOTIFICATION, shouldSuppressNotif);
13092                 return this;
13093             }
13094 
13095             /**
13096              * Indicates whether the bubble should be visually suppressed from the bubble stack if
13097              * the user is viewing the same content outside of the bubble. For example, the user has
13098              * a bubble with Alice and then opens up the main app and navigates to Alice's page.
13099              *
13100              * To match the activity and the bubble notification, the bubble notification should
13101              * have a locus id set that matches a locus id set on the activity.
13102              *
13103              * {@link Notification.Builder#setLocusId(LocusId)}
13104              * {@link Activity#setLocusContext(LocusId, Bundle)}
13105              */
13106             @NonNull
setSuppressableBubble(boolean suppressBubble)13107             public BubbleMetadata.Builder setSuppressableBubble(boolean suppressBubble) {
13108                 setFlag(FLAG_SUPPRESSABLE_BUBBLE, suppressBubble);
13109                 return this;
13110             }
13111 
13112             /**
13113              * Sets an intent to send when this bubble is explicitly removed by the user.
13114              *
13115              * <p>Setting a delete intent is optional.</p>
13116              */
13117             @NonNull
setDeleteIntent(@ullable PendingIntent deleteIntent)13118             public BubbleMetadata.Builder setDeleteIntent(@Nullable PendingIntent deleteIntent) {
13119                 mDeleteIntent = deleteIntent;
13120                 return this;
13121             }
13122 
13123             /**
13124              * Creates the {@link BubbleMetadata} defined by this builder.
13125              *
13126              * @throws NullPointerException if required elements have not been set.
13127              */
13128             @NonNull
build()13129             public BubbleMetadata build() {
13130                 if (mShortcutId == null && mPendingIntent == null) {
13131                     throw new NullPointerException(
13132                             "Must supply pending intent or shortcut to bubble");
13133                 }
13134                 if (mShortcutId == null && mIcon == null) {
13135                     throw new NullPointerException(
13136                             "Must supply an icon or shortcut for the bubble");
13137                 }
13138                 BubbleMetadata data = new BubbleMetadata(mPendingIntent, mDeleteIntent,
13139                         mIcon, mDesiredHeight, mDesiredHeightResId, mShortcutId);
13140                 data.setFlags(mFlags);
13141                 return data;
13142             }
13143 
13144             /**
13145              * @hide
13146              */
setFlag(int mask, boolean value)13147             public BubbleMetadata.Builder setFlag(int mask, boolean value) {
13148                 if (value) {
13149                     mFlags |= mask;
13150                 } else {
13151                     mFlags &= ~mask;
13152                 }
13153                 return this;
13154             }
13155         }
13156     }
13157 
13158 
13159     // When adding a new Style subclass here, don't forget to update
13160     // Builder.getNotificationStyleClass.
13161 
13162     /**
13163      * Extender interface for use with {@link Builder#extend}. Extenders may be used to add
13164      * metadata or change options on a notification builder.
13165      */
13166     public interface Extender {
13167         /**
13168          * Apply this extender to a notification builder.
13169          * @param builder the builder to be modified.
13170          * @return the build object for chaining.
13171          */
extend(Builder builder)13172         public Builder extend(Builder builder);
13173     }
13174 
13175     /**
13176      * Helper class to add wearable extensions to notifications.
13177      * <p class="note"> See
13178      * <a href="{@docRoot}wear/notifications/creating.html">Creating Notifications
13179      * for Android Wear</a> for more information on how to use this class.
13180      * <p>
13181      * To create a notification with wearable extensions:
13182      * <ol>
13183      *   <li>Create a {@link android.app.Notification.Builder}, setting any desired
13184      *   properties.
13185      *   <li>Create a {@link android.app.Notification.WearableExtender}.
13186      *   <li>Set wearable-specific properties using the
13187      *   {@code add} and {@code set} methods of {@link android.app.Notification.WearableExtender}.
13188      *   <li>Call {@link android.app.Notification.Builder#extend} to apply the extensions to a
13189      *   notification.
13190      *   <li>Post the notification to the notification system with the
13191      *   {@code NotificationManager.notify(...)} methods.
13192      * </ol>
13193      *
13194      * <pre class="prettyprint">
13195      * Notification notif = new Notification.Builder(mContext)
13196      *         .setContentTitle(&quot;New mail from &quot; + sender.toString())
13197      *         .setContentText(subject)
13198      *         .setSmallIcon(R.drawable.new_mail)
13199      *         .extend(new Notification.WearableExtender()
13200      *                 .setContentIcon(R.drawable.new_mail))
13201      *         .build();
13202      * NotificationManager notificationManger =
13203      *         (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
13204      * notificationManger.notify(0, notif);</pre>
13205      *
13206      * <p>Wearable extensions can be accessed on an existing notification by using the
13207      * {@code WearableExtender(Notification)} constructor,
13208      * and then using the {@code get} methods to access values.
13209      *
13210      * <pre class="prettyprint">
13211      * Notification.WearableExtender wearableExtender = new Notification.WearableExtender(
13212      *         notification);
13213      * List&lt;Notification&gt; pages = wearableExtender.getPages();</pre>
13214      */
13215     public static final class WearableExtender implements Extender {
13216         /**
13217          * Sentinel value for an action index that is unset.
13218          */
13219         public static final int UNSET_ACTION_INDEX = -1;
13220 
13221         /**
13222          * Size value for use with {@link #setCustomSizePreset} to show this notification with
13223          * default sizing.
13224          * <p>For custom display notifications created using {@link #setDisplayIntent},
13225          * the default is {@link #SIZE_MEDIUM}. All other notifications size automatically based
13226          * on their content.
13227          *
13228          * @deprecated Display intents are no longer supported.
13229          */
13230         @Deprecated
13231         public static final int SIZE_DEFAULT = 0;
13232 
13233         /**
13234          * Size value for use with {@link #setCustomSizePreset} to show this notification
13235          * with an extra small size.
13236          * <p>This value is only applicable for custom display notifications created using
13237          * {@link #setDisplayIntent}.
13238          *
13239          * @deprecated Display intents are no longer supported.
13240          */
13241         @Deprecated
13242         public static final int SIZE_XSMALL = 1;
13243 
13244         /**
13245          * Size value for use with {@link #setCustomSizePreset} to show this notification
13246          * with a small size.
13247          * <p>This value is only applicable for custom display notifications created using
13248          * {@link #setDisplayIntent}.
13249          *
13250          * @deprecated Display intents are no longer supported.
13251          */
13252         @Deprecated
13253         public static final int SIZE_SMALL = 2;
13254 
13255         /**
13256          * Size value for use with {@link #setCustomSizePreset} to show this notification
13257          * with a medium size.
13258          * <p>This value is only applicable for custom display notifications created using
13259          * {@link #setDisplayIntent}.
13260          *
13261          * @deprecated Display intents are no longer supported.
13262          */
13263         @Deprecated
13264         public static final int SIZE_MEDIUM = 3;
13265 
13266         /**
13267          * Size value for use with {@link #setCustomSizePreset} to show this notification
13268          * with a large size.
13269          * <p>This value is only applicable for custom display notifications created using
13270          * {@link #setDisplayIntent}.
13271          *
13272          * @deprecated Display intents are no longer supported.
13273          */
13274         @Deprecated
13275         public static final int SIZE_LARGE = 4;
13276 
13277         /**
13278          * Size value for use with {@link #setCustomSizePreset} to show this notification
13279          * full screen.
13280          * <p>This value is only applicable for custom display notifications created using
13281          * {@link #setDisplayIntent}.
13282          *
13283          * @deprecated Display intents are no longer supported.
13284          */
13285         @Deprecated
13286         public static final int SIZE_FULL_SCREEN = 5;
13287 
13288         /**
13289          * Sentinel value for use with {@link #setHintScreenTimeout} to keep the screen on for a
13290          * short amount of time when this notification is displayed on the screen. This
13291          * is the default value.
13292          *
13293          * @deprecated This feature is no longer supported.
13294          */
13295         @Deprecated
13296         public static final int SCREEN_TIMEOUT_SHORT = 0;
13297 
13298         /**
13299          * Sentinel value for use with {@link #setHintScreenTimeout} to keep the screen on
13300          * for a longer amount of time when this notification is displayed on the screen.
13301          *
13302          * @deprecated This feature is no longer supported.
13303          */
13304         @Deprecated
13305         public static final int SCREEN_TIMEOUT_LONG = -1;
13306 
13307         /** Notification extra which contains wearable extensions */
13308         private static final String EXTRA_WEARABLE_EXTENSIONS = "android.wearable.EXTENSIONS";
13309 
13310         // Keys within EXTRA_WEARABLE_EXTENSIONS for wearable options.
13311         private static final String KEY_ACTIONS = "actions";
13312         private static final String KEY_FLAGS = "flags";
13313         static final String KEY_DISPLAY_INTENT = "displayIntent";
13314         private static final String KEY_PAGES = "pages";
13315         static final String KEY_BACKGROUND = "background";
13316         private static final String KEY_CONTENT_ICON = "contentIcon";
13317         private static final String KEY_CONTENT_ICON_GRAVITY = "contentIconGravity";
13318         private static final String KEY_CONTENT_ACTION_INDEX = "contentActionIndex";
13319         private static final String KEY_CUSTOM_SIZE_PRESET = "customSizePreset";
13320         private static final String KEY_CUSTOM_CONTENT_HEIGHT = "customContentHeight";
13321         private static final String KEY_GRAVITY = "gravity";
13322         private static final String KEY_HINT_SCREEN_TIMEOUT = "hintScreenTimeout";
13323         private static final String KEY_DISMISSAL_ID = "dismissalId";
13324         private static final String KEY_BRIDGE_TAG = "bridgeTag";
13325 
13326         // Flags bitwise-ored to mFlags
13327         private static final int FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE = 0x1;
13328         private static final int FLAG_HINT_HIDE_ICON = 1 << 1;
13329         private static final int FLAG_HINT_SHOW_BACKGROUND_ONLY = 1 << 2;
13330         private static final int FLAG_START_SCROLL_BOTTOM = 1 << 3;
13331         private static final int FLAG_HINT_AVOID_BACKGROUND_CLIPPING = 1 << 4;
13332         private static final int FLAG_BIG_PICTURE_AMBIENT = 1 << 5;
13333         private static final int FLAG_HINT_CONTENT_INTENT_LAUNCHES_ACTIVITY = 1 << 6;
13334 
13335         // Default value for flags integer
13336         private static final int DEFAULT_FLAGS = FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE;
13337 
13338         private static final int DEFAULT_CONTENT_ICON_GRAVITY = Gravity.END;
13339         private static final int DEFAULT_GRAVITY = Gravity.BOTTOM;
13340 
13341         private ArrayList<Action> mActions = new ArrayList<Action>();
13342         private int mFlags = DEFAULT_FLAGS;
13343         private PendingIntent mDisplayIntent;
13344         private ArrayList<Notification> mPages = new ArrayList<Notification>();
13345         private Bitmap mBackground;
13346         private int mContentIcon;
13347         private int mContentIconGravity = DEFAULT_CONTENT_ICON_GRAVITY;
13348         private int mContentActionIndex = UNSET_ACTION_INDEX;
13349         private int mCustomSizePreset = SIZE_DEFAULT;
13350         private int mCustomContentHeight;
13351         private int mGravity = DEFAULT_GRAVITY;
13352         private int mHintScreenTimeout;
13353         private String mDismissalId;
13354         private String mBridgeTag;
13355 
13356         /**
13357          * Create a {@link android.app.Notification.WearableExtender} with default
13358          * options.
13359          */
WearableExtender()13360         public WearableExtender() {
13361         }
13362 
WearableExtender(Notification notif)13363         public WearableExtender(Notification notif) {
13364             Bundle wearableBundle = notif.extras.getBundle(EXTRA_WEARABLE_EXTENSIONS);
13365             if (wearableBundle != null) {
13366                 List<Action> actions = wearableBundle.getParcelableArrayList(KEY_ACTIONS, android.app.Notification.Action.class);
13367                 if (actions != null) {
13368                     mActions.addAll(actions);
13369                 }
13370 
13371                 mFlags = wearableBundle.getInt(KEY_FLAGS, DEFAULT_FLAGS);
13372                 mDisplayIntent = wearableBundle.getParcelable(
13373                         KEY_DISPLAY_INTENT, PendingIntent.class);
13374 
13375                 Notification[] pages = getParcelableArrayFromBundle(
13376                         wearableBundle, KEY_PAGES, Notification.class);
13377                 if (pages != null) {
13378                     Collections.addAll(mPages, pages);
13379                 }
13380 
13381                 mBackground = wearableBundle.getParcelable(KEY_BACKGROUND, Bitmap.class);
13382                 mContentIcon = wearableBundle.getInt(KEY_CONTENT_ICON);
13383                 mContentIconGravity = wearableBundle.getInt(KEY_CONTENT_ICON_GRAVITY,
13384                         DEFAULT_CONTENT_ICON_GRAVITY);
13385                 mContentActionIndex = wearableBundle.getInt(KEY_CONTENT_ACTION_INDEX,
13386                         UNSET_ACTION_INDEX);
13387                 mCustomSizePreset = wearableBundle.getInt(KEY_CUSTOM_SIZE_PRESET,
13388                         SIZE_DEFAULT);
13389                 mCustomContentHeight = wearableBundle.getInt(KEY_CUSTOM_CONTENT_HEIGHT);
13390                 mGravity = wearableBundle.getInt(KEY_GRAVITY, DEFAULT_GRAVITY);
13391                 mHintScreenTimeout = wearableBundle.getInt(KEY_HINT_SCREEN_TIMEOUT);
13392                 mDismissalId = wearableBundle.getString(KEY_DISMISSAL_ID);
13393                 mBridgeTag = wearableBundle.getString(KEY_BRIDGE_TAG);
13394             }
13395         }
13396 
13397         /**
13398          * Apply wearable extensions to a notification that is being built. This is typically
13399          * called by the {@link android.app.Notification.Builder#extend} method of
13400          * {@link android.app.Notification.Builder}.
13401          */
13402         @Override
extend(Notification.Builder builder)13403         public Notification.Builder extend(Notification.Builder builder) {
13404             Bundle wearableBundle = new Bundle();
13405 
13406             if (!mActions.isEmpty()) {
13407                 wearableBundle.putParcelableArrayList(KEY_ACTIONS, mActions);
13408             }
13409             if (mFlags != DEFAULT_FLAGS) {
13410                 wearableBundle.putInt(KEY_FLAGS, mFlags);
13411             }
13412             if (mDisplayIntent != null) {
13413                 wearableBundle.putParcelable(KEY_DISPLAY_INTENT, mDisplayIntent);
13414             }
13415             if (!mPages.isEmpty()) {
13416                 wearableBundle.putParcelableArray(KEY_PAGES, mPages.toArray(
13417                         new Notification[mPages.size()]));
13418             }
13419 
13420             if (mBackground != null) {
13421                 // Keeping WearableExtender backgrounds in memory despite them being deprecated has
13422                 // added noticeable increase in system server and system ui memory usage. After
13423                 // target VERSION_CODE#VANILLA_ICE_CREAM the background will not be populated
13424                 // anymore.
13425                 if (CompatChanges.isChangeEnabled(WEARABLE_EXTENDER_BACKGROUND_BLOCKED)) {
13426                     Log.d(TAG, "Use of background in WearableExtenders has been deprecated and "
13427                             + "will not be populated anymore.");
13428                 } else {
13429                     wearableBundle.putParcelable(KEY_BACKGROUND, mBackground);
13430                 }
13431             }
13432 
13433             if (mContentIcon != 0) {
13434                 wearableBundle.putInt(KEY_CONTENT_ICON, mContentIcon);
13435             }
13436             if (mContentIconGravity != DEFAULT_CONTENT_ICON_GRAVITY) {
13437                 wearableBundle.putInt(KEY_CONTENT_ICON_GRAVITY, mContentIconGravity);
13438             }
13439             if (mContentActionIndex != UNSET_ACTION_INDEX) {
13440                 wearableBundle.putInt(KEY_CONTENT_ACTION_INDEX,
13441                         mContentActionIndex);
13442             }
13443             if (mCustomSizePreset != SIZE_DEFAULT) {
13444                 wearableBundle.putInt(KEY_CUSTOM_SIZE_PRESET, mCustomSizePreset);
13445             }
13446             if (mCustomContentHeight != 0) {
13447                 wearableBundle.putInt(KEY_CUSTOM_CONTENT_HEIGHT, mCustomContentHeight);
13448             }
13449             if (mGravity != DEFAULT_GRAVITY) {
13450                 wearableBundle.putInt(KEY_GRAVITY, mGravity);
13451             }
13452             if (mHintScreenTimeout != 0) {
13453                 wearableBundle.putInt(KEY_HINT_SCREEN_TIMEOUT, mHintScreenTimeout);
13454             }
13455             if (mDismissalId != null) {
13456                 wearableBundle.putString(KEY_DISMISSAL_ID, mDismissalId);
13457             }
13458             if (mBridgeTag != null) {
13459                 wearableBundle.putString(KEY_BRIDGE_TAG, mBridgeTag);
13460             }
13461 
13462             builder.getExtras().putBundle(EXTRA_WEARABLE_EXTENSIONS, wearableBundle);
13463             return builder;
13464         }
13465 
13466         @Override
clone()13467         public WearableExtender clone() {
13468             WearableExtender that = new WearableExtender();
13469             that.mActions = new ArrayList<Action>(this.mActions);
13470             that.mFlags = this.mFlags;
13471             that.mDisplayIntent = this.mDisplayIntent;
13472             that.mPages = new ArrayList<Notification>(this.mPages);
13473             that.mBackground = this.mBackground;
13474             that.mContentIcon = this.mContentIcon;
13475             that.mContentIconGravity = this.mContentIconGravity;
13476             that.mContentActionIndex = this.mContentActionIndex;
13477             that.mCustomSizePreset = this.mCustomSizePreset;
13478             that.mCustomContentHeight = this.mCustomContentHeight;
13479             that.mGravity = this.mGravity;
13480             that.mHintScreenTimeout = this.mHintScreenTimeout;
13481             that.mDismissalId = this.mDismissalId;
13482             that.mBridgeTag = this.mBridgeTag;
13483             return that;
13484         }
13485 
13486         /**
13487          * Add a wearable action to this notification.
13488          *
13489          * <p>When wearable actions are added using this method, the set of actions that
13490          * show on a wearable device splits from devices that only show actions added
13491          * using {@link android.app.Notification.Builder#addAction}. This allows for customization
13492          * of which actions display on different devices.
13493          *
13494          * @param action the action to add to this notification
13495          * @return this object for method chaining
13496          * @see android.app.Notification.Action
13497          */
addAction(Action action)13498         public WearableExtender addAction(Action action) {
13499             mActions.add(action);
13500             return this;
13501         }
13502 
13503         /**
13504          * Adds wearable actions to this notification.
13505          *
13506          * <p>When wearable actions are added using this method, the set of actions that
13507          * show on a wearable device splits from devices that only show actions added
13508          * using {@link android.app.Notification.Builder#addAction}. This allows for customization
13509          * of which actions display on different devices.
13510          *
13511          * @param actions the actions to add to this notification
13512          * @return this object for method chaining
13513          * @see android.app.Notification.Action
13514          */
addActions(List<Action> actions)13515         public WearableExtender addActions(List<Action> actions) {
13516             mActions.addAll(actions);
13517             return this;
13518         }
13519 
13520         /**
13521          * Clear all wearable actions present on this builder.
13522          * @return this object for method chaining.
13523          * @see #addAction
13524          */
clearActions()13525         public WearableExtender clearActions() {
13526             mActions.clear();
13527             return this;
13528         }
13529 
13530         /**
13531          * Get the wearable actions present on this notification.
13532          */
getActions()13533         public List<Action> getActions() {
13534             return mActions;
13535         }
13536 
13537         /**
13538          * Set an intent to launch inside of an activity view when displaying
13539          * this notification. The {@link PendingIntent} provided should be for an activity.
13540          *
13541          * <pre class="prettyprint">
13542          * Intent displayIntent = new Intent(context, MyDisplayActivity.class);
13543          * PendingIntent displayPendingIntent = PendingIntent.getActivity(context,
13544          *         0, displayIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
13545          * Notification notif = new Notification.Builder(context)
13546          *         .extend(new Notification.WearableExtender()
13547          *                 .setDisplayIntent(displayPendingIntent)
13548          *                 .setCustomSizePreset(Notification.WearableExtender.SIZE_MEDIUM))
13549          *         .build();</pre>
13550          *
13551          * <p>The activity to launch needs to allow embedding, must be exported, and
13552          * should have an empty task affinity. It is also recommended to use the device
13553          * default light theme.
13554          *
13555          * <p>Example AndroidManifest.xml entry:
13556          * <pre class="prettyprint">
13557          * &lt;activity android:name=&quot;com.example.MyDisplayActivity&quot;
13558          *     android:exported=&quot;true&quot;
13559          *     android:allowEmbedded=&quot;true&quot;
13560          *     android:taskAffinity=&quot;&quot;
13561          *     android:theme=&quot;@android:style/Theme.DeviceDefault.Light&quot; /&gt;</pre>
13562          *
13563          * @param intent the {@link PendingIntent} for an activity
13564          * @return this object for method chaining
13565          * @see android.app.Notification.WearableExtender#getDisplayIntent
13566          * @deprecated Display intents are no longer supported.
13567          */
13568         @Deprecated
setDisplayIntent(PendingIntent intent)13569         public WearableExtender setDisplayIntent(PendingIntent intent) {
13570             mDisplayIntent = intent;
13571             return this;
13572         }
13573 
13574         /**
13575          * Get the intent to launch inside of an activity view when displaying this
13576          * notification. This {@code PendingIntent} should be for an activity.
13577          *
13578          * @deprecated Display intents are no longer supported.
13579          */
13580         @Deprecated
getDisplayIntent()13581         public PendingIntent getDisplayIntent() {
13582             return mDisplayIntent;
13583         }
13584 
13585         /**
13586          * Add an additional page of content to display with this notification. The current
13587          * notification forms the first page, and pages added using this function form
13588          * subsequent pages. This field can be used to separate a notification into multiple
13589          * sections.
13590          *
13591          * @param page the notification to add as another page
13592          * @return this object for method chaining
13593          * @see android.app.Notification.WearableExtender#getPages
13594          * @deprecated Multiple content pages are no longer supported.
13595          */
13596         @Deprecated
addPage(Notification page)13597         public WearableExtender addPage(Notification page) {
13598             mPages.add(page);
13599             return this;
13600         }
13601 
13602         /**
13603          * Add additional pages of content to display with this notification. The current
13604          * notification forms the first page, and pages added using this function form
13605          * subsequent pages. This field can be used to separate a notification into multiple
13606          * sections.
13607          *
13608          * @param pages a list of notifications
13609          * @return this object for method chaining
13610          * @see android.app.Notification.WearableExtender#getPages
13611          * @deprecated Multiple content pages are no longer supported.
13612          */
13613         @Deprecated
addPages(List<Notification> pages)13614         public WearableExtender addPages(List<Notification> pages) {
13615             mPages.addAll(pages);
13616             return this;
13617         }
13618 
13619         /**
13620          * Clear all additional pages present on this builder.
13621          * @return this object for method chaining.
13622          * @see #addPage
13623          * @deprecated Multiple content pages are no longer supported.
13624          */
13625         @Deprecated
clearPages()13626         public WearableExtender clearPages() {
13627             mPages.clear();
13628             return this;
13629         }
13630 
13631         /**
13632          * Get the array of additional pages of content for displaying this notification. The
13633          * current notification forms the first page, and elements within this array form
13634          * subsequent pages. This field can be used to separate a notification into multiple
13635          * sections.
13636          * @return the pages for this notification
13637          * @deprecated Multiple content pages are no longer supported.
13638          */
13639         @Deprecated
getPages()13640         public List<Notification> getPages() {
13641             return mPages;
13642         }
13643 
13644         /**
13645          * Set a background image to be displayed behind the notification content.
13646          * Contrary to the {@link android.app.Notification.BigPictureStyle}, this background
13647          * will work with any notification style.
13648          *
13649          * @param background the background bitmap
13650          * @return this object for method chaining
13651          * @removed Not functional since {@link Build.VERSION_CODES#VANILLA_ICE_CREAM}.
13652          *          The wearable background is not used by wearables anymore and uses up
13653          *          unnecessary memory.
13654          */
13655         @Deprecated
setBackground(Bitmap background)13656         public WearableExtender setBackground(Bitmap background) {
13657             // Keeping WearableExtender backgrounds in memory despite them being deprecated has
13658             // added noticeable increase in system server and system ui memory usage. After
13659             // target VERSION_CODE#VANILLA_ICE_CREAM the background will not be populated anymore.
13660             if (CompatChanges.isChangeEnabled(WEARABLE_EXTENDER_BACKGROUND_BLOCKED)) {
13661                 Log.d(TAG, "Use of background in WearableExtenders has been deprecated and "
13662                         + "will not be populated anymore.");
13663             } else {
13664                 mBackground = background;
13665             }
13666             return this;
13667         }
13668 
13669         /**
13670          * Get a background image to be displayed behind the notification content.
13671          * Contrary to the {@link android.app.Notification.BigPictureStyle}, this background
13672          * will work with any notification style.
13673          *
13674          * @return the background image
13675          * @removed Not functional since {@link Build.VERSION_CODES#VANILLA_ICE_CREAM}. The
13676          *          wearable background is not used by wearables anymore and uses up
13677          *          unnecessary memory.
13678          */
13679         @Deprecated
getBackground()13680         public Bitmap getBackground() {
13681             Log.w(TAG, "Use of background in WearableExtender has been removed, returning null.");
13682             return mBackground;
13683         }
13684 
13685         /**
13686          * Set an icon that goes with the content of this notification.
13687          */
13688         @Deprecated
setContentIcon(int icon)13689         public WearableExtender setContentIcon(int icon) {
13690             mContentIcon = icon;
13691             return this;
13692         }
13693 
13694         /**
13695          * Get an icon that goes with the content of this notification.
13696          */
13697         @Deprecated
getContentIcon()13698         public int getContentIcon() {
13699             return mContentIcon;
13700         }
13701 
13702         /**
13703          * Set the gravity that the content icon should have within the notification display.
13704          * Supported values include {@link android.view.Gravity#START} and
13705          * {@link android.view.Gravity#END}. The default value is {@link android.view.Gravity#END}.
13706          * @see #setContentIcon
13707          */
13708         @Deprecated
setContentIconGravity(int contentIconGravity)13709         public WearableExtender setContentIconGravity(int contentIconGravity) {
13710             mContentIconGravity = contentIconGravity;
13711             return this;
13712         }
13713 
13714         /**
13715          * Get the gravity that the content icon should have within the notification display.
13716          * Supported values include {@link android.view.Gravity#START} and
13717          * {@link android.view.Gravity#END}. The default value is {@link android.view.Gravity#END}.
13718          * @see #getContentIcon
13719          */
13720         @Deprecated
getContentIconGravity()13721         public int getContentIconGravity() {
13722             return mContentIconGravity;
13723         }
13724 
13725         /**
13726          * Set an action from this notification's actions as the primary action. If the action has a
13727          * {@link RemoteInput} associated with it, shortcuts to the options for that input are shown
13728          * directly on the notification.
13729          *
13730          * @param actionIndex The index of the primary action.
13731          *                    If wearable actions were added to the main notification, this index
13732          *                    will apply to that list, otherwise it will apply to the regular
13733          *                    actions list.
13734          */
setContentAction(int actionIndex)13735         public WearableExtender setContentAction(int actionIndex) {
13736             mContentActionIndex = actionIndex;
13737             return this;
13738         }
13739 
13740         /**
13741          * Get the index of the notification action, if any, that was specified as the primary
13742          * action.
13743          *
13744          * <p>If wearable specific actions were added to the main notification, this index will
13745          * apply to that list, otherwise it will apply to the regular actions list.
13746          *
13747          * @return the action index or {@link #UNSET_ACTION_INDEX} if no action was selected.
13748          */
getContentAction()13749         public int getContentAction() {
13750             return mContentActionIndex;
13751         }
13752 
13753         /**
13754          * Set the gravity that this notification should have within the available viewport space.
13755          * Supported values include {@link android.view.Gravity#TOP},
13756          * {@link android.view.Gravity#CENTER_VERTICAL} and {@link android.view.Gravity#BOTTOM}.
13757          * The default value is {@link android.view.Gravity#BOTTOM}.
13758          */
13759         @Deprecated
setGravity(int gravity)13760         public WearableExtender setGravity(int gravity) {
13761             mGravity = gravity;
13762             return this;
13763         }
13764 
13765         /**
13766          * Get the gravity that this notification should have within the available viewport space.
13767          * Supported values include {@link android.view.Gravity#TOP},
13768          * {@link android.view.Gravity#CENTER_VERTICAL} and {@link android.view.Gravity#BOTTOM}.
13769          * The default value is {@link android.view.Gravity#BOTTOM}.
13770          */
13771         @Deprecated
getGravity()13772         public int getGravity() {
13773             return mGravity;
13774         }
13775 
13776         /**
13777          * Set the custom size preset for the display of this notification out of the available
13778          * presets found in {@link android.app.Notification.WearableExtender}, e.g.
13779          * {@link #SIZE_LARGE}.
13780          * <p>Some custom size presets are only applicable for custom display notifications created
13781          * using {@link android.app.Notification.WearableExtender#setDisplayIntent}. Check the
13782          * documentation for the preset in question. See also
13783          * {@link #setCustomContentHeight} and {@link #getCustomSizePreset}.
13784          */
13785         @Deprecated
setCustomSizePreset(int sizePreset)13786         public WearableExtender setCustomSizePreset(int sizePreset) {
13787             mCustomSizePreset = sizePreset;
13788             return this;
13789         }
13790 
13791         /**
13792          * Get the custom size preset for the display of this notification out of the available
13793          * presets found in {@link android.app.Notification.WearableExtender}, e.g.
13794          * {@link #SIZE_LARGE}.
13795          * <p>Some custom size presets are only applicable for custom display notifications created
13796          * using {@link #setDisplayIntent}. Check the documentation for the preset in question.
13797          * See also {@link #setCustomContentHeight} and {@link #setCustomSizePreset}.
13798          */
13799         @Deprecated
getCustomSizePreset()13800         public int getCustomSizePreset() {
13801             return mCustomSizePreset;
13802         }
13803 
13804         /**
13805          * Set the custom height in pixels for the display of this notification's content.
13806          * <p>This option is only available for custom display notifications created
13807          * using {@link android.app.Notification.WearableExtender#setDisplayIntent}. See also
13808          * {@link android.app.Notification.WearableExtender#setCustomSizePreset} and
13809          * {@link #getCustomContentHeight}.
13810          */
13811         @Deprecated
setCustomContentHeight(int height)13812         public WearableExtender setCustomContentHeight(int height) {
13813             mCustomContentHeight = height;
13814             return this;
13815         }
13816 
13817         /**
13818          * Get the custom height in pixels for the display of this notification's content.
13819          * <p>This option is only available for custom display notifications created
13820          * using {@link #setDisplayIntent}. See also {@link #setCustomSizePreset} and
13821          * {@link #setCustomContentHeight}.
13822          */
13823         @Deprecated
getCustomContentHeight()13824         public int getCustomContentHeight() {
13825             return mCustomContentHeight;
13826         }
13827 
13828         /**
13829          * Set whether the scrolling position for the contents of this notification should start
13830          * at the bottom of the contents instead of the top when the contents are too long to
13831          * display within the screen.  Default is false (start scroll at the top).
13832          */
setStartScrollBottom(boolean startScrollBottom)13833         public WearableExtender setStartScrollBottom(boolean startScrollBottom) {
13834             setFlag(FLAG_START_SCROLL_BOTTOM, startScrollBottom);
13835             return this;
13836         }
13837 
13838         /**
13839          * Get whether the scrolling position for the contents of this notification should start
13840          * at the bottom of the contents instead of the top when the contents are too long to
13841          * display within the screen. Default is false (start scroll at the top).
13842          */
getStartScrollBottom()13843         public boolean getStartScrollBottom() {
13844             return (mFlags & FLAG_START_SCROLL_BOTTOM) != 0;
13845         }
13846 
13847         /**
13848          * Set whether the content intent is available when the wearable device is not connected
13849          * to a companion device.  The user can still trigger this intent when the wearable device
13850          * is offline, but a visual hint will indicate that the content intent may not be available.
13851          * Defaults to true.
13852          */
setContentIntentAvailableOffline( boolean contentIntentAvailableOffline)13853         public WearableExtender setContentIntentAvailableOffline(
13854                 boolean contentIntentAvailableOffline) {
13855             setFlag(FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE, contentIntentAvailableOffline);
13856             return this;
13857         }
13858 
13859         /**
13860          * Get whether the content intent is available when the wearable device is not connected
13861          * to a companion device.  The user can still trigger this intent when the wearable device
13862          * is offline, but a visual hint will indicate that the content intent may not be available.
13863          * Defaults to true.
13864          */
getContentIntentAvailableOffline()13865         public boolean getContentIntentAvailableOffline() {
13866             return (mFlags & FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE) != 0;
13867         }
13868 
13869         /**
13870          * Set a hint that this notification's icon should not be displayed.
13871          * @param hintHideIcon {@code true} to hide the icon, {@code false} otherwise.
13872          * @return this object for method chaining
13873          */
13874         @Deprecated
setHintHideIcon(boolean hintHideIcon)13875         public WearableExtender setHintHideIcon(boolean hintHideIcon) {
13876             setFlag(FLAG_HINT_HIDE_ICON, hintHideIcon);
13877             return this;
13878         }
13879 
13880         /**
13881          * Get a hint that this notification's icon should not be displayed.
13882          * @return {@code true} if this icon should not be displayed, false otherwise.
13883          * The default value is {@code false} if this was never set.
13884          */
13885         @Deprecated
getHintHideIcon()13886         public boolean getHintHideIcon() {
13887             return (mFlags & FLAG_HINT_HIDE_ICON) != 0;
13888         }
13889 
13890         /**
13891          * Set a visual hint that only the background image of this notification should be
13892          * displayed, and other semantic content should be hidden. This hint is only applicable
13893          * to sub-pages added using {@link #addPage}.
13894          */
13895         @Deprecated
setHintShowBackgroundOnly(boolean hintShowBackgroundOnly)13896         public WearableExtender setHintShowBackgroundOnly(boolean hintShowBackgroundOnly) {
13897             setFlag(FLAG_HINT_SHOW_BACKGROUND_ONLY, hintShowBackgroundOnly);
13898             return this;
13899         }
13900 
13901         /**
13902          * Get a visual hint that only the background image of this notification should be
13903          * displayed, and other semantic content should be hidden. This hint is only applicable
13904          * to sub-pages added using {@link android.app.Notification.WearableExtender#addPage}.
13905          */
13906         @Deprecated
getHintShowBackgroundOnly()13907         public boolean getHintShowBackgroundOnly() {
13908             return (mFlags & FLAG_HINT_SHOW_BACKGROUND_ONLY) != 0;
13909         }
13910 
13911         /**
13912          * Set a hint that this notification's background should not be clipped if possible,
13913          * and should instead be resized to fully display on the screen, retaining the aspect
13914          * ratio of the image. This can be useful for images like barcodes or qr codes.
13915          * @param hintAvoidBackgroundClipping {@code true} to avoid clipping if possible.
13916          * @return this object for method chaining
13917          */
13918         @Deprecated
setHintAvoidBackgroundClipping( boolean hintAvoidBackgroundClipping)13919         public WearableExtender setHintAvoidBackgroundClipping(
13920                 boolean hintAvoidBackgroundClipping) {
13921             setFlag(FLAG_HINT_AVOID_BACKGROUND_CLIPPING, hintAvoidBackgroundClipping);
13922             return this;
13923         }
13924 
13925         /**
13926          * Get a hint that this notification's background should not be clipped if possible,
13927          * and should instead be resized to fully display on the screen, retaining the aspect
13928          * ratio of the image. This can be useful for images like barcodes or qr codes.
13929          * @return {@code true} if it's ok if the background is clipped on the screen, false
13930          * otherwise. The default value is {@code false} if this was never set.
13931          */
13932         @Deprecated
getHintAvoidBackgroundClipping()13933         public boolean getHintAvoidBackgroundClipping() {
13934             return (mFlags & FLAG_HINT_AVOID_BACKGROUND_CLIPPING) != 0;
13935         }
13936 
13937         /**
13938          * Set a hint that the screen should remain on for at least this duration when
13939          * this notification is displayed on the screen.
13940          * @param timeout The requested screen timeout in milliseconds. Can also be either
13941          *     {@link #SCREEN_TIMEOUT_SHORT} or {@link #SCREEN_TIMEOUT_LONG}.
13942          * @return this object for method chaining
13943          */
13944         @Deprecated
setHintScreenTimeout(int timeout)13945         public WearableExtender setHintScreenTimeout(int timeout) {
13946             mHintScreenTimeout = timeout;
13947             return this;
13948         }
13949 
13950         /**
13951          * Get the duration, in milliseconds, that the screen should remain on for
13952          * when this notification is displayed.
13953          * @return the duration in milliseconds if > 0, or either one of the sentinel values
13954          *     {@link #SCREEN_TIMEOUT_SHORT} or {@link #SCREEN_TIMEOUT_LONG}.
13955          */
13956         @Deprecated
getHintScreenTimeout()13957         public int getHintScreenTimeout() {
13958             return mHintScreenTimeout;
13959         }
13960 
13961         /**
13962          * Set a hint that this notification's {@link BigPictureStyle} (if present) should be
13963          * converted to low-bit and displayed in ambient mode, especially useful for barcodes and
13964          * qr codes, as well as other simple black-and-white tickets.
13965          * @param hintAmbientBigPicture {@code true} to enable converstion and ambient.
13966          * @return this object for method chaining
13967          * @deprecated This feature is no longer supported.
13968          */
13969         @Deprecated
setHintAmbientBigPicture(boolean hintAmbientBigPicture)13970         public WearableExtender setHintAmbientBigPicture(boolean hintAmbientBigPicture) {
13971             setFlag(FLAG_BIG_PICTURE_AMBIENT, hintAmbientBigPicture);
13972             return this;
13973         }
13974 
13975         /**
13976          * Get a hint that this notification's {@link BigPictureStyle} (if present) should be
13977          * converted to low-bit and displayed in ambient mode, especially useful for barcodes and
13978          * qr codes, as well as other simple black-and-white tickets.
13979          * @return {@code true} if it should be displayed in ambient, false otherwise
13980          * otherwise. The default value is {@code false} if this was never set.
13981          * @deprecated This feature is no longer supported.
13982          */
13983         @Deprecated
getHintAmbientBigPicture()13984         public boolean getHintAmbientBigPicture() {
13985             return (mFlags & FLAG_BIG_PICTURE_AMBIENT) != 0;
13986         }
13987 
13988         /**
13989          * Set a hint that this notification's content intent will launch an {@link Activity}
13990          * directly, telling the platform that it can generate the appropriate transitions.
13991          * @param hintContentIntentLaunchesActivity {@code true} if the content intent will launch
13992          * an activity and transitions should be generated, false otherwise.
13993          * @return this object for method chaining
13994          */
setHintContentIntentLaunchesActivity( boolean hintContentIntentLaunchesActivity)13995         public WearableExtender setHintContentIntentLaunchesActivity(
13996                 boolean hintContentIntentLaunchesActivity) {
13997             setFlag(FLAG_HINT_CONTENT_INTENT_LAUNCHES_ACTIVITY, hintContentIntentLaunchesActivity);
13998             return this;
13999         }
14000 
14001         /**
14002          * Get a hint that this notification's content intent will launch an {@link Activity}
14003          * directly, telling the platform that it can generate the appropriate transitions
14004          * @return {@code true} if the content intent will launch an activity and transitions should
14005          * be generated, false otherwise. The default value is {@code false} if this was never set.
14006          */
getHintContentIntentLaunchesActivity()14007         public boolean getHintContentIntentLaunchesActivity() {
14008             return (mFlags & FLAG_HINT_CONTENT_INTENT_LAUNCHES_ACTIVITY) != 0;
14009         }
14010 
14011         /**
14012          * Sets the dismissal id for this notification. If a notification is posted with a
14013          * dismissal id, then when that notification is canceled, notifications on other wearables
14014          * and the paired Android phone having that same dismissal id will also be canceled. See
14015          * <a href="{@docRoot}wear/notifications/index.html">Adding Wearable Features to
14016          * Notifications</a> for more information.
14017          * @param dismissalId the dismissal id of the notification.
14018          * @return this object for method chaining
14019          */
setDismissalId(String dismissalId)14020         public WearableExtender setDismissalId(String dismissalId) {
14021             mDismissalId = dismissalId;
14022             return this;
14023         }
14024 
14025         /**
14026          * Returns the dismissal id of the notification.
14027          * @return the dismissal id of the notification or null if it has not been set.
14028          */
getDismissalId()14029         public String getDismissalId() {
14030             return mDismissalId;
14031         }
14032 
14033         /**
14034          * Sets a bridge tag for this notification. A bridge tag can be set for notifications
14035          * posted from a phone to provide finer-grained control on what notifications are bridged
14036          * to wearables. See <a href="{@docRoot}wear/notifications/index.html">Adding Wearable
14037          * Features to Notifications</a> for more information.
14038          * @param bridgeTag the bridge tag of the notification.
14039          * @return this object for method chaining
14040          */
setBridgeTag(String bridgeTag)14041         public WearableExtender setBridgeTag(String bridgeTag) {
14042             mBridgeTag = bridgeTag;
14043             return this;
14044         }
14045 
14046         /**
14047          * Returns the bridge tag of the notification.
14048          * @return the bridge tag or null if not present.
14049          */
getBridgeTag()14050         public String getBridgeTag() {
14051             return mBridgeTag;
14052         }
14053 
setFlag(int mask, boolean value)14054         private void setFlag(int mask, boolean value) {
14055             if (value) {
14056                 mFlags |= mask;
14057             } else {
14058                 mFlags &= ~mask;
14059             }
14060         }
14061 
visitUris(@onNull Consumer<Uri> visitor)14062         private void visitUris(@NonNull Consumer<Uri> visitor) {
14063             for (Action action : mActions) {
14064                 action.visitUris(visitor);
14065             }
14066         }
14067     }
14068 
14069     /**
14070      * <p>Helper class to add Android Auto extensions to notifications. To create a notification
14071      * with car extensions:
14072      *
14073      * <ol>
14074      *  <li>Create an {@link Notification.Builder}, setting any desired
14075      *  properties.
14076      *  <li>Create a {@link CarExtender}.
14077      *  <li>Set car-specific properties using the {@code add} and {@code set} methods of
14078      *  {@link CarExtender}.
14079      *  <li>Call {@link Notification.Builder#extend(Notification.Extender)}
14080      *  to apply the extensions to a notification.
14081      * </ol>
14082      *
14083      * <pre class="prettyprint">
14084      * Notification notification = new Notification.Builder(context)
14085      *         ...
14086      *         .extend(new CarExtender()
14087      *                 .set*(...))
14088      *         .build();
14089      * </pre>
14090      *
14091      * <p>Car extensions can be accessed on an existing notification by using the
14092      * {@code CarExtender(Notification)} constructor, and then using the {@code get} methods
14093      * to access values.
14094      */
14095     public static final class CarExtender implements Extender {
14096         private static final String TAG = "CarExtender";
14097 
14098         private static final String EXTRA_CAR_EXTENDER = "android.car.EXTENSIONS";
14099         private static final String EXTRA_LARGE_ICON = "large_icon";
14100         private static final String EXTRA_CONVERSATION = "car_conversation";
14101         private static final String EXTRA_COLOR = "app_color";
14102 
14103         private Bitmap mLargeIcon;
14104         private UnreadConversation mUnreadConversation;
14105         private int mColor = Notification.COLOR_DEFAULT;
14106 
14107         /**
14108          * Create a {@link CarExtender} with default options.
14109          */
CarExtender()14110         public CarExtender() {
14111         }
14112 
14113         /**
14114          * Create a {@link CarExtender} from the CarExtender options of an existing Notification.
14115          *
14116          * @param notif The notification from which to copy options.
14117          */
CarExtender(Notification notif)14118         public CarExtender(Notification notif) {
14119             Bundle carBundle = notif.extras == null ?
14120                     null : notif.extras.getBundle(EXTRA_CAR_EXTENDER);
14121             if (carBundle != null) {
14122                 mLargeIcon = carBundle.getParcelable(EXTRA_LARGE_ICON, Bitmap.class);
14123                 mColor = carBundle.getInt(EXTRA_COLOR, Notification.COLOR_DEFAULT);
14124 
14125                 Bundle b = carBundle.getBundle(EXTRA_CONVERSATION);
14126                 mUnreadConversation = UnreadConversation.getUnreadConversationFromBundle(b);
14127             }
14128         }
14129 
14130         /**
14131          * Apply car extensions to a notification that is being built. This is typically called by
14132          * the {@link Notification.Builder#extend(Notification.Extender)}
14133          * method of {@link Notification.Builder}.
14134          */
14135         @Override
extend(Notification.Builder builder)14136         public Notification.Builder extend(Notification.Builder builder) {
14137             Bundle carExtensions = new Bundle();
14138 
14139             if (mLargeIcon != null) {
14140                 carExtensions.putParcelable(EXTRA_LARGE_ICON, mLargeIcon);
14141             }
14142             if (mColor != Notification.COLOR_DEFAULT) {
14143                 carExtensions.putInt(EXTRA_COLOR, mColor);
14144             }
14145 
14146             if (mUnreadConversation != null) {
14147                 Bundle b = mUnreadConversation.getBundleForUnreadConversation();
14148                 carExtensions.putBundle(EXTRA_CONVERSATION, b);
14149             }
14150 
14151             builder.getExtras().putBundle(EXTRA_CAR_EXTENDER, carExtensions);
14152             return builder;
14153         }
14154 
14155         /**
14156          * Sets the accent color to use when Android Auto presents the notification.
14157          *
14158          * Android Auto uses the color set with {@link Notification.Builder#setColor(int)}
14159          * to accent the displayed notification. However, not all colors are acceptable in an
14160          * automotive setting. This method can be used to override the color provided in the
14161          * notification in such a situation.
14162          */
setColor(@olorInt int color)14163         public CarExtender setColor(@ColorInt int color) {
14164             mColor = color;
14165             return this;
14166         }
14167 
14168         /**
14169          * Gets the accent color.
14170          *
14171          * @see #setColor
14172          */
14173         @ColorInt
getColor()14174         public int getColor() {
14175             return mColor;
14176         }
14177 
14178         /**
14179          * Sets the large icon of the car notification.
14180          *
14181          * If no large icon is set in the extender, Android Auto will display the icon
14182          * specified by {@link Notification.Builder#setLargeIcon(android.graphics.Bitmap)}
14183          *
14184          * @param largeIcon The large icon to use in the car notification.
14185          * @return This object for method chaining.
14186          */
setLargeIcon(Bitmap largeIcon)14187         public CarExtender setLargeIcon(Bitmap largeIcon) {
14188             mLargeIcon = largeIcon;
14189             return this;
14190         }
14191 
14192         /**
14193          * Gets the large icon used in this car notification, or null if no icon has been set.
14194          *
14195          * @return The large icon for the car notification.
14196          * @see CarExtender#setLargeIcon
14197          */
getLargeIcon()14198         public Bitmap getLargeIcon() {
14199             return mLargeIcon;
14200         }
14201 
14202         /**
14203          * Sets the unread conversation in a message notification.
14204          *
14205          * @param unreadConversation The unread part of the conversation this notification conveys.
14206          * @return This object for method chaining.
14207          */
setUnreadConversation(UnreadConversation unreadConversation)14208         public CarExtender setUnreadConversation(UnreadConversation unreadConversation) {
14209             mUnreadConversation = unreadConversation;
14210             return this;
14211         }
14212 
14213         /**
14214          * Returns the unread conversation conveyed by this notification.
14215          *
14216          * @see #setUnreadConversation(UnreadConversation)
14217          */
getUnreadConversation()14218         public UnreadConversation getUnreadConversation() {
14219             return mUnreadConversation;
14220         }
14221 
14222         /**
14223          * A class which holds the unread messages from a conversation.
14224          */
14225         public static class UnreadConversation {
14226             private static final String KEY_AUTHOR = "author";
14227             private static final String KEY_TEXT = "text";
14228             private static final String KEY_MESSAGES = "messages";
14229             static final String KEY_REMOTE_INPUT = "remote_input";
14230             static final String KEY_ON_REPLY = "on_reply";
14231             static final String KEY_ON_READ = "on_read";
14232             private static final String KEY_PARTICIPANTS = "participants";
14233             private static final String KEY_TIMESTAMP = "timestamp";
14234 
14235             private final String[] mMessages;
14236             private final RemoteInput mRemoteInput;
14237             private final PendingIntent mReplyPendingIntent;
14238             private final PendingIntent mReadPendingIntent;
14239             private final String[] mParticipants;
14240             private final long mLatestTimestamp;
14241 
UnreadConversation(String[] messages, RemoteInput remoteInput, PendingIntent replyPendingIntent, PendingIntent readPendingIntent, String[] participants, long latestTimestamp)14242             UnreadConversation(String[] messages, RemoteInput remoteInput,
14243                     PendingIntent replyPendingIntent, PendingIntent readPendingIntent,
14244                     String[] participants, long latestTimestamp) {
14245                 mMessages = messages;
14246                 mRemoteInput = remoteInput;
14247                 mReadPendingIntent = readPendingIntent;
14248                 mReplyPendingIntent = replyPendingIntent;
14249                 mParticipants = participants;
14250                 mLatestTimestamp = latestTimestamp;
14251             }
14252 
14253             /**
14254              * Gets the list of messages conveyed by this notification.
14255              */
getMessages()14256             public String[] getMessages() {
14257                 return mMessages;
14258             }
14259 
14260             /**
14261              * Gets the remote input that will be used to convey the response to a message list, or
14262              * null if no such remote input exists.
14263              */
getRemoteInput()14264             public RemoteInput getRemoteInput() {
14265                 return mRemoteInput;
14266             }
14267 
14268             /**
14269              * Gets the pending intent that will be triggered when the user replies to this
14270              * notification.
14271              */
getReplyPendingIntent()14272             public PendingIntent getReplyPendingIntent() {
14273                 return mReplyPendingIntent;
14274             }
14275 
14276             /**
14277              * Gets the pending intent that Android Auto will send after it reads aloud all messages
14278              * in this object's message list.
14279              */
getReadPendingIntent()14280             public PendingIntent getReadPendingIntent() {
14281                 return mReadPendingIntent;
14282             }
14283 
14284             /**
14285              * Gets the participants in the conversation.
14286              */
getParticipants()14287             public String[] getParticipants() {
14288                 return mParticipants;
14289             }
14290 
14291             /**
14292              * Gets the firs participant in the conversation.
14293              */
getParticipant()14294             public String getParticipant() {
14295                 return mParticipants.length > 0 ? mParticipants[0] : null;
14296             }
14297 
14298             /**
14299              * Gets the timestamp of the conversation.
14300              */
getLatestTimestamp()14301             public long getLatestTimestamp() {
14302                 return mLatestTimestamp;
14303             }
14304 
getBundleForUnreadConversation()14305             Bundle getBundleForUnreadConversation() {
14306                 Bundle b = new Bundle();
14307                 String author = null;
14308                 if (mParticipants != null && mParticipants.length > 1) {
14309                     author = mParticipants[0];
14310                 }
14311                 Parcelable[] messages = new Parcelable[mMessages.length];
14312                 for (int i = 0; i < messages.length; i++) {
14313                     Bundle m = new Bundle();
14314                     m.putString(KEY_TEXT, mMessages[i]);
14315                     m.putString(KEY_AUTHOR, author);
14316                     messages[i] = m;
14317                 }
14318                 b.putParcelableArray(KEY_MESSAGES, messages);
14319                 if (mRemoteInput != null) {
14320                     b.putParcelable(KEY_REMOTE_INPUT, mRemoteInput);
14321                 }
14322                 b.putParcelable(KEY_ON_REPLY, mReplyPendingIntent);
14323                 b.putParcelable(KEY_ON_READ, mReadPendingIntent);
14324                 b.putStringArray(KEY_PARTICIPANTS, mParticipants);
14325                 b.putLong(KEY_TIMESTAMP, mLatestTimestamp);
14326                 return b;
14327             }
14328 
getUnreadConversationFromBundle(Bundle b)14329             static UnreadConversation getUnreadConversationFromBundle(Bundle b) {
14330                 if (b == null) {
14331                     return null;
14332                 }
14333                 Parcelable[] parcelableMessages = b.getParcelableArray(KEY_MESSAGES,
14334                         Parcelable.class);
14335                 String[] messages = null;
14336                 if (parcelableMessages != null) {
14337                     String[] tmp = new String[parcelableMessages.length];
14338                     boolean success = true;
14339                     for (int i = 0; i < tmp.length; i++) {
14340                         if (!(parcelableMessages[i] instanceof Bundle)) {
14341                             success = false;
14342                             break;
14343                         }
14344                         tmp[i] = ((Bundle) parcelableMessages[i]).getString(KEY_TEXT);
14345                         if (tmp[i] == null) {
14346                             success = false;
14347                             break;
14348                         }
14349                     }
14350                     if (success) {
14351                         messages = tmp;
14352                     } else {
14353                         return null;
14354                     }
14355                 }
14356 
14357                 PendingIntent onRead = b.getParcelable(KEY_ON_READ, PendingIntent.class);
14358                 PendingIntent onReply = b.getParcelable(KEY_ON_REPLY, PendingIntent.class);
14359 
14360                 RemoteInput remoteInput = b.getParcelable(KEY_REMOTE_INPUT, RemoteInput.class);
14361 
14362                 String[] participants = b.getStringArray(KEY_PARTICIPANTS);
14363                 if (participants == null || participants.length != 1) {
14364                     return null;
14365                 }
14366 
14367                 return new UnreadConversation(messages,
14368                         remoteInput,
14369                         onReply,
14370                         onRead,
14371                         participants, b.getLong(KEY_TIMESTAMP));
14372             }
14373         };
14374 
14375         /**
14376          * Builder class for {@link CarExtender.UnreadConversation} objects.
14377          */
14378         public static class Builder {
14379             private final List<String> mMessages = new ArrayList<String>();
14380             private final String mParticipant;
14381             private RemoteInput mRemoteInput;
14382             private PendingIntent mReadPendingIntent;
14383             private PendingIntent mReplyPendingIntent;
14384             private long mLatestTimestamp;
14385 
14386             /**
14387              * Constructs a new builder for {@link CarExtender.UnreadConversation}.
14388              *
14389              * @param name The name of the other participant in the conversation.
14390              */
Builder(String name)14391             public Builder(String name) {
14392                 mParticipant = name;
14393             }
14394 
14395             /**
14396              * Appends a new unread message to the list of messages for this conversation.
14397              *
14398              * The messages should be added from oldest to newest.
14399              *
14400              * @param message The text of the new unread message.
14401              * @return This object for method chaining.
14402              */
addMessage(String message)14403             public Builder addMessage(String message) {
14404                 mMessages.add(message);
14405                 return this;
14406             }
14407 
14408             /**
14409              * Sets the pending intent and remote input which will convey the reply to this
14410              * notification.
14411              *
14412              * @param pendingIntent The pending intent which will be triggered on a reply.
14413              * @param remoteInput The remote input parcelable which will carry the reply.
14414              * @return This object for method chaining.
14415              *
14416              * @see CarExtender.UnreadConversation#getRemoteInput
14417              * @see CarExtender.UnreadConversation#getReplyPendingIntent
14418              */
setReplyAction( PendingIntent pendingIntent, RemoteInput remoteInput)14419             public Builder setReplyAction(
14420                     PendingIntent pendingIntent, RemoteInput remoteInput) {
14421                 mRemoteInput = remoteInput;
14422                 mReplyPendingIntent = pendingIntent;
14423 
14424                 return this;
14425             }
14426 
14427             /**
14428              * Sets the pending intent that will be sent once the messages in this notification
14429              * are read.
14430              *
14431              * @param pendingIntent The pending intent to use.
14432              * @return This object for method chaining.
14433              */
setReadPendingIntent(PendingIntent pendingIntent)14434             public Builder setReadPendingIntent(PendingIntent pendingIntent) {
14435                 mReadPendingIntent = pendingIntent;
14436                 return this;
14437             }
14438 
14439             /**
14440              * Sets the timestamp of the most recent message in an unread conversation.
14441              *
14442              * If a messaging notification has been posted by your application and has not
14443              * yet been cancelled, posting a later notification with the same id and tag
14444              * but without a newer timestamp may result in Android Auto not displaying a
14445              * heads up notification for the later notification.
14446              *
14447              * @param timestamp The timestamp of the most recent message in the conversation.
14448              * @return This object for method chaining.
14449              */
setLatestTimestamp(long timestamp)14450             public Builder setLatestTimestamp(long timestamp) {
14451                 mLatestTimestamp = timestamp;
14452                 return this;
14453             }
14454 
14455             /**
14456              * Builds a new unread conversation object.
14457              *
14458              * @return The new unread conversation object.
14459              */
build()14460             public UnreadConversation build() {
14461                 String[] messages = mMessages.toArray(new String[mMessages.size()]);
14462                 String[] participants = { mParticipant };
14463                 return new UnreadConversation(messages, mRemoteInput, mReplyPendingIntent,
14464                         mReadPendingIntent, participants, mLatestTimestamp);
14465             }
14466         }
14467     }
14468 
14469     /**
14470      * <p>Helper class to add Android TV extensions to notifications. To create a notification
14471      * with a TV extension:
14472      *
14473      * <ol>
14474      *  <li>Create an {@link Notification.Builder}, setting any desired properties.
14475      *  <li>Create a {@link TvExtender}.
14476      *  <li>Set TV-specific properties using the {@code set} methods of
14477      *  {@link TvExtender}.
14478      *  <li>Call {@link Notification.Builder#extend(Notification.Extender)}
14479      *  to apply the extension to a notification.
14480      * </ol>
14481      *
14482      * <pre class="prettyprint">
14483      * Notification notification = new Notification.Builder(context)
14484      *         ...
14485      *         .extend(new TvExtender()
14486      *                 .set*(...))
14487      *         .build();
14488      * </pre>
14489      *
14490      * <p>TV extensions can be accessed on an existing notification by using the
14491      * {@code TvExtender(Notification)} constructor, and then using the {@code get} methods
14492      * to access values.
14493      */
14494     @FlaggedApi(Flags.FLAG_API_TVEXTENDER)
14495     public static final class TvExtender implements Extender {
14496         private static final String TAG = "TvExtender";
14497 
14498         private static final String EXTRA_TV_EXTENDER = "android.tv.EXTENSIONS";
14499         private static final String EXTRA_FLAGS = "flags";
14500         static final String EXTRA_CONTENT_INTENT = "content_intent";
14501         static final String EXTRA_DELETE_INTENT = "delete_intent";
14502         private static final String EXTRA_CHANNEL_ID = "channel_id";
14503         private static final String EXTRA_SUPPRESS_SHOW_OVER_APPS = "suppressShowOverApps";
14504 
14505         // Flags bitwise-ored to mFlags
14506         private static final int FLAG_AVAILABLE_ON_TV = 0x1;
14507 
14508         private int mFlags;
14509         private String mChannelId;
14510         private PendingIntent mContentIntent;
14511         private PendingIntent mDeleteIntent;
14512         private boolean mSuppressShowOverApps;
14513 
14514         /**
14515          * Create a {@link TvExtender} with default options.
14516          */
TvExtender()14517         public TvExtender() {
14518             mFlags = FLAG_AVAILABLE_ON_TV;
14519         }
14520 
14521         /**
14522          * Create a {@link TvExtender} from the TvExtender options of an existing Notification.
14523          *
14524          * @param notif The notification from which to copy options.
14525          */
TvExtender(@onNull Notification notif)14526         public TvExtender(@NonNull Notification notif) {
14527             Bundle bundle = notif.extras == null ?
14528                 null : notif.extras.getBundle(EXTRA_TV_EXTENDER);
14529             if (bundle != null) {
14530                 mFlags = bundle.getInt(EXTRA_FLAGS);
14531                 mChannelId = bundle.getString(EXTRA_CHANNEL_ID);
14532                 mSuppressShowOverApps = bundle.getBoolean(EXTRA_SUPPRESS_SHOW_OVER_APPS);
14533                 mContentIntent = bundle.getParcelable(EXTRA_CONTENT_INTENT, PendingIntent.class);
14534                 mDeleteIntent = bundle.getParcelable(EXTRA_DELETE_INTENT, PendingIntent.class);
14535             }
14536         }
14537 
14538         /**
14539          * Apply a TV extension to a notification that is being built. This is typically called by
14540          * the {@link Notification.Builder#extend(Notification.Extender)}
14541          * method of {@link Notification.Builder}.
14542          */
14543         @Override
14544         @NonNull
extend(@onNull Notification.Builder builder)14545         public Notification.Builder extend(@NonNull Notification.Builder builder) {
14546             Bundle bundle = new Bundle();
14547 
14548             bundle.putInt(EXTRA_FLAGS, mFlags);
14549             bundle.putString(EXTRA_CHANNEL_ID, mChannelId);
14550             bundle.putBoolean(EXTRA_SUPPRESS_SHOW_OVER_APPS, mSuppressShowOverApps);
14551             if (mContentIntent != null) {
14552                 bundle.putParcelable(EXTRA_CONTENT_INTENT, mContentIntent);
14553             }
14554 
14555             if (mDeleteIntent != null) {
14556                 bundle.putParcelable(EXTRA_DELETE_INTENT, mDeleteIntent);
14557             }
14558 
14559             builder.getExtras().putBundle(EXTRA_TV_EXTENDER, bundle);
14560             return builder;
14561         }
14562 
14563         /**
14564          * Returns true if this notification should be shown on TV. This method returns true
14565          * if the notification was extended with a TvExtender.
14566          */
isAvailableOnTv()14567         public boolean isAvailableOnTv() {
14568             return (mFlags & FLAG_AVAILABLE_ON_TV) != 0;
14569         }
14570 
14571         /**
14572          * Specifies the channel the notification should be delivered on when shown on TV.
14573          * It can be different from the channel that the notification is delivered to when
14574          * posting on a non-TV device. Prefer to use {@link setChannelId(String)}.
14575          *
14576          * @hide
14577          */
14578         @SystemApi
setChannel(String channelId)14579         public TvExtender setChannel(String channelId) {
14580             mChannelId = channelId;
14581             return this;
14582         }
14583 
14584         /**
14585          * Specifies the channel the notification should be delivered on when shown on TV.
14586          * It can be different from the channel that the notification is delivered to when
14587          * posting on a non-TV device.
14588          *
14589          * @return this object for method chaining
14590          */
14591         @NonNull
setChannelId(@ullable String channelId)14592         public TvExtender setChannelId(@Nullable String channelId) {
14593             mChannelId = channelId;
14594             return this;
14595         }
14596 
14597         /**
14598          * @removed
14599          * @hide
14600          */
14601         @Deprecated
14602         @SystemApi
getChannel()14603         public String getChannel() {
14604             return mChannelId;
14605         }
14606 
14607         /**
14608          * Returns the id of the channel this notification posts to on TV.
14609          */
14610         @Nullable
getChannelId()14611         public String getChannelId() {
14612             return mChannelId;
14613         }
14614 
14615         /**
14616          * Supplies a {@link PendingIntent} to be sent when the notification is selected on TV.
14617          * If provided, it is used instead of the content intent specified
14618          * at the level of Notification.
14619          *
14620          * @param intent the {@link PendingIntent} for the associated notification content
14621          * @return this object for method chaining
14622          */
14623         @NonNull
setContentIntent(@ullable PendingIntent intent)14624         public TvExtender setContentIntent(@Nullable PendingIntent intent) {
14625             mContentIntent = intent;
14626             return this;
14627         }
14628 
14629         /**
14630          * Returns the TV-specific content intent.  If this method returns null, the
14631          * main content intent on the notification should be used.
14632          *
14633          * @see Notification#contentIntent
14634          */
14635         @Nullable
getContentIntent()14636         public PendingIntent getContentIntent() {
14637             return mContentIntent;
14638         }
14639 
14640         /**
14641          * Supplies a {@link PendingIntent} to send when the notification is cleared explicitly
14642          * by the user on TV.  If provided, it is used instead of the delete intent specified
14643          * at the level of Notification.
14644          *
14645          * @param intent the {@link PendingIntent} for the associated notification deletion
14646          * @return this object for method chaining
14647          */
14648         @NonNull
setDeleteIntent(@ullable PendingIntent intent)14649         public TvExtender setDeleteIntent(@Nullable PendingIntent intent) {
14650             mDeleteIntent = intent;
14651             return this;
14652         }
14653 
14654         /**
14655          * Returns the TV-specific delete intent.  If this method returns null, the
14656          * main delete intent on the notification should be used.
14657          *
14658          * @see Notification#deleteIntent
14659          */
14660         @Nullable
getDeleteIntent()14661         public PendingIntent getDeleteIntent() {
14662             return mDeleteIntent;
14663         }
14664 
14665         /**
14666          * Specifies whether this notification should suppress showing a message over top of apps
14667          * outside of the launcher.
14668          *
14669          * @param suppress whether the notification should suppress showing over apps.
14670          * @return this object for method chaining
14671          */
14672         @NonNull
setSuppressShowOverApps(boolean suppress)14673         public TvExtender setSuppressShowOverApps(boolean suppress) {
14674             mSuppressShowOverApps = suppress;
14675             return this;
14676         }
14677 
14678         /**
14679          * Returns true if this notification should not show messages over top of apps
14680          * outside of the launcher.
14681          *
14682          * @hide
14683          */
14684         @SystemApi
getSuppressShowOverApps()14685         public boolean getSuppressShowOverApps() {
14686             return mSuppressShowOverApps;
14687         }
14688 
14689         /**
14690          * Returns true if this notification should not show messages over top of apps
14691          * outside of the launcher.
14692          */
isSuppressShowOverApps()14693         public boolean isSuppressShowOverApps() {
14694             return mSuppressShowOverApps;
14695         }
14696     }
14697 
14698     /**
14699      * Get an array of Parcelable objects from a parcelable array bundle field.
14700      * Update the bundle to have a typed array so fetches in the future don't need
14701      * to do an array copy.
14702      */
14703     @Nullable
getParcelableArrayFromBundle( Bundle bundle, String key, Class<T> itemClass)14704     private static <T extends Parcelable> T[] getParcelableArrayFromBundle(
14705             Bundle bundle, String key, Class<T> itemClass) {
14706         final Parcelable[] array = bundle.getParcelableArray(key, Parcelable.class);
14707         final Class<?> arrayClass = Array.newInstance(itemClass, 0).getClass();
14708         if (arrayClass.isInstance(array) || array == null) {
14709             return (T[]) array;
14710         }
14711         final T[] typedArray = (T[]) Array.newInstance(itemClass, array.length);
14712         for (int i = 0; i < array.length; i++) {
14713             typedArray[i] = (T) array[i];
14714         }
14715         bundle.putParcelableArray(key, typedArray);
14716         return typedArray;
14717     }
14718 
14719     private static class BuilderRemoteViews extends RemoteViews {
BuilderRemoteViews(Parcel parcel)14720         public BuilderRemoteViews(Parcel parcel) {
14721             super(parcel);
14722         }
14723 
BuilderRemoteViews(ApplicationInfo appInfo, int layoutId)14724         public BuilderRemoteViews(ApplicationInfo appInfo, int layoutId) {
14725             super(appInfo, layoutId);
14726         }
14727 
14728         @Override
clone()14729         public BuilderRemoteViews clone() {
14730             Parcel p = Parcel.obtain();
14731             writeToParcel(p, 0);
14732             p.setDataPosition(0);
14733             BuilderRemoteViews brv = new BuilderRemoteViews(p);
14734             p.recycle();
14735             return brv;
14736         }
14737 
14738         /**
14739          * Override and return true, since {@link RemoteViews#onLoadClass(Class)} is not overridden.
14740          *
14741          * @see RemoteViews#shouldUseStaticFilter()
14742          */
14743         @Override
shouldUseStaticFilter()14744         protected boolean shouldUseStaticFilter() {
14745             return true;
14746         }
14747     }
14748 
14749     /**
14750      * A result object where information about the template that was created is saved.
14751      */
14752     private static class TemplateBindResult {
14753         boolean mRightIconVisible;
14754         float mRightIconWidthDp;
14755         float mRightIconHeightDp;
14756 
14757         /**
14758          * The margin end that needs to be added to the heading so that it won't overlap
14759          * with the large icon.  This value includes the space required to accommodate the large
14760          * icon, but should be added to the space needed to accommodate the expander. This does
14761          * not include the 16dp content margin that all notification views must have.
14762          */
14763         public final MarginSet mHeadingExtraMarginSet = new MarginSet();
14764 
14765         /**
14766          * The margin end that needs to be added to the heading so that it won't overlap
14767          * with the large icon.  This value includes the space required to accommodate the large
14768          * icon as well as the expander.  This does not include the 16dp content margin that all
14769          * notification views must have.
14770          */
14771         public final MarginSet mHeadingFullMarginSet = new MarginSet();
14772 
14773         /**
14774          * The margin end that needs to be added to the title text of the big state
14775          * so that it won't overlap with the large icon, but assuming the text can run under
14776          * the expander when that icon is not visible.
14777          */
14778         public final MarginSet mTitleMarginSet = new MarginSet();
14779 
setRightIconState(boolean visible, float widthDp, float heightDp, float marginEndDpIfVisible, float spaceForExpanderDp)14780         public void setRightIconState(boolean visible, float widthDp, float heightDp,
14781                 float marginEndDpIfVisible, float spaceForExpanderDp) {
14782             mRightIconVisible = visible;
14783             mRightIconWidthDp = widthDp;
14784             mRightIconHeightDp = heightDp;
14785             mHeadingExtraMarginSet.setValues(
14786                     /* valueIfGone = */ 0,
14787                     /* valueIfVisible = */ marginEndDpIfVisible);
14788             mHeadingFullMarginSet.setValues(
14789                     /* valueIfGone = */ spaceForExpanderDp,
14790                     /* valueIfVisible = */ marginEndDpIfVisible + spaceForExpanderDp);
14791             mTitleMarginSet.setValues(
14792                     /* valueIfGone = */ 0,
14793                     /* valueIfVisible = */ marginEndDpIfVisible + spaceForExpanderDp);
14794         }
14795 
14796         /**
14797          * This contains the end margins for a view when the right icon is visible or not.  These
14798          * values are both needed so that NotificationGroupingUtil can 'move' the right_icon to the
14799          * left_icon and adjust the margins, and to undo that change as well.
14800          */
14801         private class MarginSet {
14802             private float mValueIfGone;
14803             private float mValueIfVisible;
14804 
setValues(float valueIfGone, float valueIfVisible)14805             public void setValues(float valueIfGone, float valueIfVisible) {
14806                 mValueIfGone = valueIfGone;
14807                 mValueIfVisible = valueIfVisible;
14808             }
14809 
applyToView(@onNull RemoteViews views, @IdRes int viewId)14810             public void applyToView(@NonNull RemoteViews views, @IdRes int viewId) {
14811                 applyToView(views, viewId, 0);
14812             }
14813 
applyToView(@onNull RemoteViews views, @IdRes int viewId, float extraMarginDp)14814             public void applyToView(@NonNull RemoteViews views, @IdRes int viewId,
14815                     float extraMarginDp) {
14816                 final float marginEndDp = getDpValue() + extraMarginDp;
14817                 if (viewId == R.id.notification_header) {
14818                     views.setFloat(R.id.notification_header,
14819                             "setTopLineExtraMarginEndDp", marginEndDp);
14820                 } else if (viewId == R.id.text || viewId == R.id.big_text) {
14821                     if (mValueIfGone != 0) {
14822                         throw new RuntimeException("Programming error: `text` and `big_text` use "
14823                                 + "ImageFloatingTextView which can either show a margin or not; "
14824                                 + "thus mValueIfGone must be 0, but it was " + mValueIfGone);
14825                     }
14826                     // Note that the caller must set "setNumIndentLines" to a positive int in order
14827                     //  for this margin to do anything at all.
14828                     views.setFloat(viewId, "setImageEndMarginDp", mValueIfVisible);
14829                     views.setBoolean(viewId, "setHasImage", mRightIconVisible);
14830                     // Apply just the *extra* margin as the view layout margin; this will be
14831                     //  unchanged depending on the visibility of the image, but it means that the
14832                     //  extra margin applies to *every* line of text instead of just indented lines.
14833                     views.setViewLayoutMargin(viewId, RemoteViews.MARGIN_END,
14834                             extraMarginDp, TypedValue.COMPLEX_UNIT_DIP);
14835                 } else {
14836                     views.setViewLayoutMargin(viewId, RemoteViews.MARGIN_END,
14837                                     marginEndDp, TypedValue.COMPLEX_UNIT_DIP);
14838                 }
14839                 if (mRightIconVisible) {
14840                     views.setIntTag(viewId, R.id.tag_margin_end_when_icon_visible,
14841                             TypedValue.createComplexDimension(
14842                                     mValueIfVisible + extraMarginDp, TypedValue.COMPLEX_UNIT_DIP));
14843                     views.setIntTag(viewId, R.id.tag_margin_end_when_icon_gone,
14844                             TypedValue.createComplexDimension(
14845                                     mValueIfGone + extraMarginDp, TypedValue.COMPLEX_UNIT_DIP));
14846                 }
14847             }
14848 
getDpValue()14849             public float getDpValue() {
14850                 return mRightIconVisible ? mValueIfVisible : mValueIfGone;
14851             }
14852         }
14853     }
14854 
14855     private static class StandardTemplateParams {
14856         /**
14857          * Notifications will be minimally decorated with ONLY an icon and expander:
14858          * <li>A large icon is never shown.
14859          * <li>A progress bar is never shown.
14860          * <li>The expanded and heads up states do not show actions, even if provided.
14861          */
14862         public static final int DECORATION_MINIMAL = 1;
14863 
14864         /**
14865          * Notifications will be partially decorated with AT LEAST an icon and expander:
14866          * <li>A large icon is shown if provided.
14867          * <li>A progress bar is shown if provided and enough space remains below the content.
14868          * <li>Actions are shown in the expanded and heads up states.
14869          */
14870         public static final int DECORATION_PARTIAL = 2;
14871 
14872         public static int VIEW_TYPE_UNSPECIFIED = 0;
14873         public static int VIEW_TYPE_NORMAL = 1;
14874         public static int VIEW_TYPE_EXPANDED = 2;
14875         public static int VIEW_TYPE_HEADS_UP = 3;
14876         public static int VIEW_TYPE_MINIMIZED = 4;    // header only for minimized state
14877         public static int VIEW_TYPE_PUBLIC = 5;       // header only for automatic public version
14878         public static int VIEW_TYPE_GROUP_HEADER = 6; // header only for top of group
14879 
14880         int mViewType = VIEW_TYPE_UNSPECIFIED;
14881         boolean mHeaderless;
14882         boolean mHideAppName;
14883         boolean mHideTitle;
14884         boolean mHideSubText;
14885         boolean mHideTime;
14886         boolean mHideActions;
14887         boolean mHideProgress;
14888         boolean mHideSnoozeButton;
14889         boolean mHideLeftIcon;
14890         boolean mHideRightIcon;
14891         Icon mPromotedPicture;
14892         boolean mCallStyleActions;
14893         boolean mAllowTextWithProgress;
14894         int mTitleViewId;
14895         int mTextViewId;
14896         @Nullable CharSequence mTitle;
14897         @Nullable CharSequence mText;
14898         @Nullable CharSequence mHeaderTextSecondary;
14899         @Nullable CharSequence mSubText;
14900         int maxRemoteInputHistory = Style.MAX_REMOTE_INPUT_HISTORY_LINES;
14901         boolean allowColorization  = true;
14902         boolean mHighlightExpander = false;
14903 
reset()14904         final StandardTemplateParams reset() {
14905             mViewType = VIEW_TYPE_UNSPECIFIED;
14906             mHeaderless = false;
14907             mHideAppName = false;
14908             mHideTitle = false;
14909             mHideSubText = false;
14910             mHideTime = false;
14911             mHideActions = false;
14912             mHideProgress = false;
14913             mHideSnoozeButton = false;
14914             mHideLeftIcon = false;
14915             mHideRightIcon = false;
14916             mPromotedPicture = null;
14917             mCallStyleActions = false;
14918             mAllowTextWithProgress = false;
14919             mTitleViewId = R.id.title;
14920             mTextViewId = R.id.text;
14921             mTitle = null;
14922             mText = null;
14923             mSubText = null;
14924             mHeaderTextSecondary = null;
14925             maxRemoteInputHistory = Style.MAX_REMOTE_INPUT_HISTORY_LINES;
14926             allowColorization = true;
14927             mHighlightExpander = false;
14928             return this;
14929         }
14930 
hasTitle()14931         final boolean hasTitle() {
14932             return !TextUtils.isEmpty(mTitle) && !mHideTitle;
14933         }
14934 
viewType(int viewType)14935         final StandardTemplateParams viewType(int viewType) {
14936             mViewType = viewType;
14937             return this;
14938         }
14939 
headerless(boolean headerless)14940         public StandardTemplateParams headerless(boolean headerless) {
14941             mHeaderless = headerless;
14942             return this;
14943         }
14944 
hideAppName(boolean hideAppName)14945         public StandardTemplateParams hideAppName(boolean hideAppName) {
14946             mHideAppName = hideAppName;
14947             return this;
14948         }
14949 
hideSubText(boolean hideSubText)14950         public StandardTemplateParams hideSubText(boolean hideSubText) {
14951             mHideSubText = hideSubText;
14952             return this;
14953         }
14954 
hideTime(boolean hideTime)14955         public StandardTemplateParams hideTime(boolean hideTime) {
14956             mHideTime = hideTime;
14957             return this;
14958         }
14959 
hideActions(boolean hideActions)14960         final StandardTemplateParams hideActions(boolean hideActions) {
14961             this.mHideActions = hideActions;
14962             return this;
14963         }
14964 
hideProgress(boolean hideProgress)14965         final StandardTemplateParams hideProgress(boolean hideProgress) {
14966             this.mHideProgress = hideProgress;
14967             return this;
14968         }
14969 
hideTitle(boolean hideTitle)14970         final StandardTemplateParams hideTitle(boolean hideTitle) {
14971             this.mHideTitle = hideTitle;
14972             return this;
14973         }
14974 
callStyleActions(boolean callStyleActions)14975         final StandardTemplateParams callStyleActions(boolean callStyleActions) {
14976             this.mCallStyleActions = callStyleActions;
14977             return this;
14978         }
14979 
allowTextWithProgress(boolean allowTextWithProgress)14980         final StandardTemplateParams allowTextWithProgress(boolean allowTextWithProgress) {
14981             this.mAllowTextWithProgress = allowTextWithProgress;
14982             return this;
14983         }
14984 
hideSnoozeButton(boolean hideSnoozeButton)14985         final StandardTemplateParams hideSnoozeButton(boolean hideSnoozeButton) {
14986             this.mHideSnoozeButton = hideSnoozeButton;
14987             return this;
14988         }
14989 
promotedPicture(Icon promotedPicture)14990         final StandardTemplateParams promotedPicture(Icon promotedPicture) {
14991             this.mPromotedPicture = promotedPicture;
14992             return this;
14993         }
14994 
titleViewId(int titleViewId)14995         public StandardTemplateParams titleViewId(int titleViewId) {
14996             mTitleViewId = titleViewId;
14997             return this;
14998         }
14999 
textViewId(int textViewId)15000         public StandardTemplateParams textViewId(int textViewId) {
15001             mTextViewId = textViewId;
15002             return this;
15003         }
15004 
title(@ullable CharSequence title)15005         final StandardTemplateParams title(@Nullable CharSequence title) {
15006             this.mTitle = title;
15007             return this;
15008         }
15009 
text(@ullable CharSequence text)15010         final StandardTemplateParams text(@Nullable CharSequence text) {
15011             this.mText = text;
15012             return this;
15013         }
15014 
summaryText(@ullable CharSequence text)15015         final StandardTemplateParams summaryText(@Nullable CharSequence text) {
15016             this.mSubText = text;
15017             return this;
15018         }
15019 
headerTextSecondary(@ullable CharSequence text)15020         final StandardTemplateParams headerTextSecondary(@Nullable CharSequence text) {
15021             this.mHeaderTextSecondary = text;
15022             return this;
15023         }
15024 
15025 
hideLeftIcon(boolean hideLeftIcon)15026         final StandardTemplateParams hideLeftIcon(boolean hideLeftIcon) {
15027             this.mHideLeftIcon = hideLeftIcon;
15028             return this;
15029         }
15030 
hideRightIcon(boolean hideRightIcon)15031         final StandardTemplateParams hideRightIcon(boolean hideRightIcon) {
15032             this.mHideRightIcon = hideRightIcon;
15033             return this;
15034         }
15035 
disallowColorization()15036         final StandardTemplateParams disallowColorization() {
15037             this.allowColorization = false;
15038             return this;
15039         }
15040 
highlightExpander(boolean highlight)15041         final StandardTemplateParams highlightExpander(boolean highlight) {
15042             this.mHighlightExpander = highlight;
15043             return this;
15044         }
15045 
fillTextsFrom(Builder b)15046         final StandardTemplateParams fillTextsFrom(Builder b) {
15047             Bundle extras = b.mN.extras;
15048             this.mTitle = b.processLegacyText(extras.getCharSequence(EXTRA_TITLE));
15049             this.mText = b.processLegacyText(extras.getCharSequence(EXTRA_TEXT));
15050             this.mSubText = extras.getCharSequence(EXTRA_SUB_TEXT);
15051             return this;
15052         }
15053 
15054         /**
15055          * Set the maximum lines of remote input history lines allowed.
15056          * @param maxRemoteInputHistory The number of lines.
15057          * @return The builder for method chaining.
15058          */
setMaxRemoteInputHistory(int maxRemoteInputHistory)15059         public StandardTemplateParams setMaxRemoteInputHistory(int maxRemoteInputHistory) {
15060             this.maxRemoteInputHistory = maxRemoteInputHistory;
15061             return this;
15062         }
15063 
decorationType(int decorationType)15064         public StandardTemplateParams decorationType(int decorationType) {
15065             hideTitle(true);
15066             // Minimally decorated custom views do not show certain pieces of chrome that have
15067             // always been shown when using DecoratedCustomViewStyle.
15068             boolean hideOtherFields = decorationType <= DECORATION_MINIMAL;
15069             hideLeftIcon(false);  // The left icon decoration is better than showing nothing.
15070             hideRightIcon(hideOtherFields);
15071             hideProgress(hideOtherFields);
15072             hideActions(hideOtherFields);
15073             return this;
15074         }
15075     }
15076 
15077     /**
15078      * A utility which stores and calculates the palette of colors used to color notifications.
15079      * @hide
15080      */
15081     public static class Colors {
15082         private int mPaletteIsForRawColor = COLOR_INVALID;
15083         private boolean mPaletteIsForColorized = false;
15084         private boolean mPaletteIsForNightMode = false;
15085         // The following colors are the palette
15086         private int mBackgroundColor = COLOR_INVALID;
15087         private int mProtectionColor = COLOR_INVALID;
15088         private int mPrimaryTextColor = COLOR_INVALID;
15089         private int mSecondaryTextColor = COLOR_INVALID;
15090         private int mPrimaryAccentColor = COLOR_INVALID;
15091         private int mSecondaryAccentColor = COLOR_INVALID;
15092         private int mTertiaryAccentColor = COLOR_INVALID;
15093         private int mOnTertiaryAccentTextColor = COLOR_INVALID;
15094         private int mTertiaryFixedDimAccentColor = COLOR_INVALID;
15095         private int mOnTertiaryFixedAccentTextColor = COLOR_INVALID;
15096 
15097         private int mErrorColor = COLOR_INVALID;
15098         private int mContrastColor = COLOR_INVALID;
15099         private int mRippleAlpha = 0x33;
15100 
15101         /**
15102          * A utility for obtaining a TypedArray of the given DayNight-styled attributes, which
15103          * returns null when the context is a mock with no theme.
15104          *
15105          * NOTE: Calling this method is expensive, as creating a new ContextThemeWrapper
15106          * instances can allocate as much as 5MB of memory, so its important to call this method
15107          * only when necessary, getting as many attributes as possible from each call.
15108          *
15109          * @see Resources.Theme#obtainStyledAttributes(int[])
15110          */
15111         @Nullable
obtainDayNightAttributes(@onNull Context ctx, @NonNull @StyleableRes int[] attrs)15112         private static TypedArray obtainDayNightAttributes(@NonNull Context ctx,
15113                 @NonNull @StyleableRes int[] attrs) {
15114             // when testing, the mock context may have no theme
15115             if (ctx.getTheme() == null) {
15116                 return null;
15117             }
15118             Resources.Theme theme = new ContextThemeWrapper(ctx,
15119                     R.style.Theme_DeviceDefault_DayNight).getTheme();
15120             return theme.obtainStyledAttributes(attrs);
15121         }
15122 
15123         /** A null-safe wrapper of TypedArray.getColor because mocks return null */
getColor(@ullable TypedArray ta, int index, @ColorInt int defValue)15124         private static @ColorInt int getColor(@Nullable TypedArray ta, int index,
15125                 @ColorInt int defValue) {
15126             return ta == null ? defValue : ta.getColor(index, defValue);
15127         }
15128 
15129         /**
15130          * Resolve the palette.  If the inputs have not changed, this will be a no-op.
15131          * This does not handle invalidating the resolved colors when the context itself changes,
15132          * because that case does not happen in the current notification inflation pipeline; we will
15133          * recreate a new builder (and thus a new palette) when reinflating notifications for a new
15134          * theme (admittedly, we do the same for night mode, but that's easy to check).
15135          *
15136          * @param ctx the builder context.
15137          * @param rawColor the notification's color; may be COLOR_DEFAULT, but may never have alpha.
15138          * @param isColorized whether the notification is colorized.
15139          * @param nightMode whether the UI is in night mode.
15140          */
resolvePalette(Context ctx, int rawColor, boolean isColorized, boolean nightMode)15141         public void resolvePalette(Context ctx, int rawColor,
15142                 boolean isColorized, boolean nightMode) {
15143             if (mPaletteIsForRawColor == rawColor
15144                     && mPaletteIsForColorized == isColorized
15145                     && mPaletteIsForNightMode == nightMode) {
15146                 return;
15147             }
15148             mPaletteIsForRawColor = rawColor;
15149             mPaletteIsForColorized = isColorized;
15150             mPaletteIsForNightMode = nightMode;
15151 
15152             if (isColorized) {
15153                 if (rawColor == COLOR_DEFAULT) {
15154                     mBackgroundColor = ctx.getColor(R.color.materialColorSecondary);
15155                 } else {
15156                     mBackgroundColor = rawColor;
15157                 }
15158                 if (Flags.uiRichOngoing()) {
15159                     boolean isBgDark = Notification.Builder.isColorDark(mBackgroundColor);
15160                     int onSurfaceColorExtreme = isBgDark ? Color.WHITE : Color.BLACK;
15161                     mPrimaryTextColor = ContrastColorUtil.ensureContrast(
15162                             ColorUtils.blendARGB(mBackgroundColor, onSurfaceColorExtreme, 0.9f),
15163                             mBackgroundColor, isBgDark, 4.5);
15164                     mSecondaryTextColor = ContrastColorUtil.ensureContrast(
15165                             ColorUtils.blendARGB(mBackgroundColor, onSurfaceColorExtreme, 0.8f),
15166                             mBackgroundColor, isBgDark, 4.5);
15167                 } else {
15168                     mPrimaryTextColor = ContrastColorUtil.findAlphaToMeetContrast(
15169                             ContrastColorUtil.resolvePrimaryColor(ctx, mBackgroundColor, nightMode),
15170                             mBackgroundColor, 4.5);
15171                     mSecondaryTextColor = ContrastColorUtil.findAlphaToMeetContrast(
15172                             ContrastColorUtil.resolveSecondaryColor(ctx,
15173                                     mBackgroundColor, nightMode), mBackgroundColor, 4.5);
15174                 }
15175                 mContrastColor = mPrimaryTextColor;
15176                 mPrimaryAccentColor = mPrimaryTextColor;
15177                 mSecondaryAccentColor = mSecondaryTextColor;
15178                 mTertiaryAccentColor = flattenAlpha(mPrimaryTextColor, mBackgroundColor);
15179                 mOnTertiaryAccentTextColor = mBackgroundColor;
15180                 mTertiaryFixedDimAccentColor = mTertiaryAccentColor;
15181                 mOnTertiaryFixedAccentTextColor = mOnTertiaryAccentTextColor;
15182                 mErrorColor = mPrimaryTextColor;
15183                 mRippleAlpha = 0x33;
15184             } else {
15185                 int[] attrs = {
15186                         R.attr.colorError,
15187                         R.attr.colorControlHighlight
15188                 };
15189 
15190                 mBackgroundColor = ctx.getColor(R.color.materialColorSurfaceContainerHigh);
15191                 mPrimaryTextColor = ctx.getColor(R.color.materialColorOnSurface);
15192                 mSecondaryTextColor = ctx.getColor(R.color.materialColorOnSurfaceVariant);
15193                 mPrimaryAccentColor = ctx.getColor(R.color.materialColorPrimary);
15194                 mSecondaryAccentColor = ctx.getColor(R.color.materialColorSecondary);
15195                 mTertiaryAccentColor = ctx.getColor(R.color.materialColorTertiary);
15196                 mOnTertiaryAccentTextColor = ctx.getColor(R.color.materialColorOnTertiary);
15197                 mTertiaryFixedDimAccentColor = ctx.getColor(
15198                         R.color.materialColorTertiaryFixedDim);
15199                 mOnTertiaryFixedAccentTextColor = ctx.getColor(
15200                         R.color.materialColorOnTertiaryFixed);
15201 
15202                 try (TypedArray ta = obtainDayNightAttributes(ctx, attrs)) {
15203                     mErrorColor = getColor(ta, 0, COLOR_INVALID);
15204                     mRippleAlpha = Color.alpha(getColor(ta, 1, 0x33ffffff));
15205                 }
15206                 mContrastColor = calculateContrastColor(ctx, rawColor, mPrimaryAccentColor,
15207                         mBackgroundColor, nightMode);
15208 
15209                 // make sure every color has a valid value
15210                 if (mPrimaryTextColor == COLOR_INVALID) {
15211                     mPrimaryTextColor = ContrastColorUtil.resolvePrimaryColor(
15212                             ctx, mBackgroundColor, nightMode);
15213                 }
15214                 if (mSecondaryTextColor == COLOR_INVALID) {
15215                     mSecondaryTextColor = ContrastColorUtil.resolveSecondaryColor(
15216                             ctx, mBackgroundColor, nightMode);
15217                 }
15218                 if (mPrimaryAccentColor == COLOR_INVALID) {
15219                     mPrimaryAccentColor = mContrastColor;
15220                 }
15221                 if (mSecondaryAccentColor == COLOR_INVALID) {
15222                     mSecondaryAccentColor = mContrastColor;
15223                 }
15224                 if (mTertiaryAccentColor == COLOR_INVALID) {
15225                     mTertiaryAccentColor = mContrastColor;
15226                 }
15227                 if (mOnTertiaryAccentTextColor == COLOR_INVALID) {
15228                     mOnTertiaryAccentTextColor = ColorUtils.setAlphaComponent(
15229                             ContrastColorUtil.resolvePrimaryColor(
15230                                     ctx, mTertiaryAccentColor, nightMode), 0xFF);
15231                 }
15232                 if (mTertiaryFixedDimAccentColor == COLOR_INVALID) {
15233                     mTertiaryFixedDimAccentColor = mContrastColor;
15234                 }
15235                 if (mOnTertiaryFixedAccentTextColor == COLOR_INVALID) {
15236                     mOnTertiaryFixedAccentTextColor = ColorUtils.setAlphaComponent(
15237                             ContrastColorUtil.resolvePrimaryColor(
15238                                     ctx, mTertiaryFixedDimAccentColor, nightMode), 0xFF);
15239                 }
15240                 if (mErrorColor == COLOR_INVALID) {
15241                     mErrorColor = mPrimaryTextColor;
15242                 }
15243             }
15244             // make sure every color has a valid value
15245             mProtectionColor = ColorUtils.blendARGB(mPrimaryTextColor, mBackgroundColor, 0.9f);
15246         }
15247 
15248         /** calculates the contrast color for the non-colorized notifications */
calculateContrastColor(Context ctx, @ColorInt int rawColor, @ColorInt int accentColor, @ColorInt int backgroundColor, boolean nightMode)15249         private static @ColorInt int calculateContrastColor(Context ctx, @ColorInt int rawColor,
15250                 @ColorInt int accentColor, @ColorInt int backgroundColor, boolean nightMode) {
15251             int color;
15252             if (rawColor == COLOR_DEFAULT) {
15253                 color = accentColor;
15254                 if (color == COLOR_INVALID) {
15255                     color = ContrastColorUtil.resolveDefaultColor(ctx, backgroundColor, nightMode);
15256                 }
15257             } else {
15258                 color = ContrastColorUtil.resolveContrastColor(ctx, rawColor, backgroundColor,
15259                         nightMode);
15260             }
15261             return flattenAlpha(color, backgroundColor);
15262         }
15263 
15264         /** remove any alpha by manually blending it with the given background. */
flattenAlpha(@olorInt int color, @ColorInt int background)15265         private static @ColorInt int flattenAlpha(@ColorInt int color, @ColorInt int background) {
15266             return Color.alpha(color) == 0xff ? color
15267                     : ContrastColorUtil.compositeColors(color, background);
15268         }
15269 
15270         /** @return the notification's background color */
getBackgroundColor()15271         public @ColorInt int getBackgroundColor() {
15272             return mBackgroundColor;
15273         }
15274 
15275         /**
15276          * @return the "surface protection" color from the theme,
15277          * or a variant of the normal background color when colorized.
15278          */
getProtectionColor()15279         public @ColorInt int getProtectionColor() {
15280             return mProtectionColor;
15281         }
15282 
15283         /** @return the color for the most prominent text */
getPrimaryTextColor()15284         public @ColorInt int getPrimaryTextColor() {
15285             return mPrimaryTextColor;
15286         }
15287 
15288         /** @return the color for less prominent text */
getSecondaryTextColor()15289         public @ColorInt int getSecondaryTextColor() {
15290             return mSecondaryTextColor;
15291         }
15292 
15293         /** @return the theme's accent color for colored UI elements. */
getPrimaryAccentColor()15294         public @ColorInt int getPrimaryAccentColor() {
15295             return mPrimaryAccentColor;
15296         }
15297 
15298         /** @return the theme's secondary accent color for colored UI elements. */
getSecondaryAccentColor()15299         public @ColorInt int getSecondaryAccentColor() {
15300             return mSecondaryAccentColor;
15301         }
15302 
15303         /** @return the theme's tertiary accent color for colored UI elements. */
getTertiaryAccentColor()15304         public @ColorInt int getTertiaryAccentColor() {
15305             return mTertiaryAccentColor;
15306         }
15307 
15308         /** @return the theme's text color to be used on the tertiary accent color. */
getOnTertiaryAccentTextColor()15309         public @ColorInt int getOnTertiaryAccentTextColor() {
15310             return mOnTertiaryAccentTextColor;
15311         }
15312 
15313         /** @return the theme's tertiary fixed dim accent color for colored UI elements. */
getTertiaryFixedDimAccentColor()15314         public @ColorInt int getTertiaryFixedDimAccentColor() {
15315             return mTertiaryFixedDimAccentColor;
15316         }
15317 
15318         /** @return the theme's text color to be used on the tertiary fixed accent color. */
getOnTertiaryFixedAccentTextColor()15319         public @ColorInt int getOnTertiaryFixedAccentTextColor() {
15320             return mOnTertiaryFixedAccentTextColor;
15321         }
15322 
15323         /**
15324          * @return the contrast-adjusted version of the color provided by the app, or the
15325          * primary text color when colorized.
15326          */
getContrastColor()15327         public @ColorInt int getContrastColor() {
15328             return mContrastColor;
15329         }
15330 
15331         /** @return the theme's error color, or the primary text color when colorized. */
getErrorColor()15332         public @ColorInt int getErrorColor() {
15333             return mErrorColor;
15334         }
15335 
15336         /** @return the alpha component of the current theme's control highlight color. */
getRippleAlpha()15337         public int getRippleAlpha() {
15338             return mRippleAlpha;
15339         }
15340     }
15341 }
15342