• 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.admin.DevicePolicyResources.Drawables.Source.NOTIFICATION;
21 import static android.app.admin.DevicePolicyResources.Drawables.Style.SOLID_COLORED;
22 import static android.app.admin.DevicePolicyResources.Drawables.WORK_PROFILE_ICON;
23 import static android.app.admin.DevicePolicyResources.UNDEFINED;
24 import static android.graphics.drawable.Icon.TYPE_URI;
25 import static android.graphics.drawable.Icon.TYPE_URI_ADAPTIVE_BITMAP;
26 
27 import static java.util.Objects.requireNonNull;
28 
29 import android.annotation.ColorInt;
30 import android.annotation.ColorRes;
31 import android.annotation.DimenRes;
32 import android.annotation.Dimension;
33 import android.annotation.DrawableRes;
34 import android.annotation.IdRes;
35 import android.annotation.IntDef;
36 import android.annotation.NonNull;
37 import android.annotation.Nullable;
38 import android.annotation.RequiresPermission;
39 import android.annotation.SdkConstant;
40 import android.annotation.SdkConstant.SdkConstantType;
41 import android.annotation.StringRes;
42 import android.annotation.StyleableRes;
43 import android.annotation.SuppressLint;
44 import android.annotation.SystemApi;
45 import android.annotation.TestApi;
46 import android.app.admin.DevicePolicyManager;
47 import android.compat.annotation.UnsupportedAppUsage;
48 import android.content.Context;
49 import android.content.Intent;
50 import android.content.LocusId;
51 import android.content.pm.ApplicationInfo;
52 import android.content.pm.PackageManager;
53 import android.content.pm.PackageManager.NameNotFoundException;
54 import android.content.pm.ShortcutInfo;
55 import android.content.res.ColorStateList;
56 import android.content.res.Configuration;
57 import android.content.res.Resources;
58 import android.content.res.TypedArray;
59 import android.graphics.Bitmap;
60 import android.graphics.Canvas;
61 import android.graphics.Color;
62 import android.graphics.PorterDuff;
63 import android.graphics.drawable.Drawable;
64 import android.graphics.drawable.Icon;
65 import android.media.AudioAttributes;
66 import android.media.AudioManager;
67 import android.media.PlayerBase;
68 import android.media.session.MediaSession;
69 import android.net.Uri;
70 import android.os.BadParcelableException;
71 import android.os.Build;
72 import android.os.Bundle;
73 import android.os.IBinder;
74 import android.os.Parcel;
75 import android.os.Parcelable;
76 import android.os.SystemClock;
77 import android.os.SystemProperties;
78 import android.os.UserHandle;
79 import android.os.UserManager;
80 import android.provider.Settings;
81 import android.text.BidiFormatter;
82 import android.text.SpannableStringBuilder;
83 import android.text.Spanned;
84 import android.text.TextUtils;
85 import android.text.style.AbsoluteSizeSpan;
86 import android.text.style.CharacterStyle;
87 import android.text.style.ForegroundColorSpan;
88 import android.text.style.RelativeSizeSpan;
89 import android.text.style.TextAppearanceSpan;
90 import android.util.ArraySet;
91 import android.util.Log;
92 import android.util.Pair;
93 import android.util.SparseArray;
94 import android.util.TypedValue;
95 import android.util.proto.ProtoOutputStream;
96 import android.view.ContextThemeWrapper;
97 import android.view.Gravity;
98 import android.view.View;
99 import android.view.contentcapture.ContentCaptureContext;
100 import android.widget.ProgressBar;
101 import android.widget.RemoteViews;
102 
103 import com.android.internal.R;
104 import com.android.internal.annotations.VisibleForTesting;
105 import com.android.internal.graphics.ColorUtils;
106 import com.android.internal.util.ArrayUtils;
107 import com.android.internal.util.ContrastColorUtil;
108 
109 import java.lang.annotation.Retention;
110 import java.lang.annotation.RetentionPolicy;
111 import java.lang.reflect.Array;
112 import java.lang.reflect.Constructor;
113 import java.util.ArrayList;
114 import java.util.Arrays;
115 import java.util.Collections;
116 import java.util.List;
117 import java.util.Objects;
118 import java.util.Set;
119 import java.util.function.Consumer;
120 
121 /**
122  * A class that represents how a persistent notification is to be presented to
123  * the user using the {@link android.app.NotificationManager}.
124  *
125  * <p>The {@link Notification.Builder Notification.Builder} has been added to make it
126  * easier to construct Notifications.</p>
127  *
128  * <div class="special reference">
129  * <h3>Developer Guides</h3>
130  * <p>For a guide to creating notifications, read the
131  * <a href="{@docRoot}guide/topics/ui/notifiers/notifications.html">Status Bar Notifications</a>
132  * developer guide.</p>
133  * </div>
134  */
135 public class Notification implements Parcelable
136 {
137     private static final String TAG = "Notification";
138 
139     /**
140      * @hide
141      */
142     @Retention(RetentionPolicy.SOURCE)
143     @IntDef({
144             FOREGROUND_SERVICE_DEFAULT,
145             FOREGROUND_SERVICE_IMMEDIATE,
146             FOREGROUND_SERVICE_DEFERRED
147     })
148     public @interface ServiceNotificationPolicy {};
149 
150     /**
151      * If the Notification associated with starting a foreground service has been
152      * built using setForegroundServiceBehavior() with this behavior, display of
153      * the notification will usually be suppressed for a short time to avoid visual
154      * disturbances to the user.
155      * @see Notification.Builder#setForegroundServiceBehavior(int)
156      * @see #FOREGROUND_SERVICE_IMMEDIATE
157      * @see #FOREGROUND_SERVICE_DEFERRED
158      */
159     public static final @ServiceNotificationPolicy int FOREGROUND_SERVICE_DEFAULT = 0;
160 
161     /**
162      * If the Notification associated with starting a foreground service has been
163      * built using setForegroundServiceBehavior() with this behavior, display of
164      * the notification will be immediate even if the default behavior would be
165      * to defer visibility for a short time.
166      * @see Notification.Builder#setForegroundServiceBehavior(int)
167      * @see #FOREGROUND_SERVICE_DEFAULT
168      * @see #FOREGROUND_SERVICE_DEFERRED
169      */
170     public static final @ServiceNotificationPolicy int FOREGROUND_SERVICE_IMMEDIATE = 1;
171 
172     /**
173      * If the Notification associated with starting a foreground service has been
174      * built using setForegroundServiceBehavior() with this behavior, display of
175      * the notification will usually be suppressed for a short time to avoid visual
176      * disturbances to the user.
177      * @see Notification.Builder#setForegroundServiceBehavior(int)
178      * @see #FOREGROUND_SERVICE_DEFAULT
179      * @see #FOREGROUND_SERVICE_IMMEDIATE
180      */
181     public static final @ServiceNotificationPolicy int FOREGROUND_SERVICE_DEFERRED = 2;
182 
183     @ServiceNotificationPolicy
184     private int mFgsDeferBehavior;
185 
186     /**
187      * An activity that provides a user interface for adjusting notification preferences for its
188      * containing application.
189      */
190     @SdkConstant(SdkConstantType.INTENT_CATEGORY)
191     public static final String INTENT_CATEGORY_NOTIFICATION_PREFERENCES
192             = "android.intent.category.NOTIFICATION_PREFERENCES";
193 
194     /**
195      * Optional extra for {@link #INTENT_CATEGORY_NOTIFICATION_PREFERENCES}. If provided, will
196      * contain a {@link NotificationChannel#getId() channel id} that can be used to narrow down
197      * what settings should be shown in the target app.
198      */
199     public static final String EXTRA_CHANNEL_ID = "android.intent.extra.CHANNEL_ID";
200 
201     /**
202      * Optional extra for {@link #INTENT_CATEGORY_NOTIFICATION_PREFERENCES}. If provided, will
203      * contain a {@link NotificationChannelGroup#getId() group id} that can be used to narrow down
204      * what settings should be shown in the target app.
205      */
206     public static final String EXTRA_CHANNEL_GROUP_ID = "android.intent.extra.CHANNEL_GROUP_ID";
207 
208     /**
209      * Optional extra for {@link #INTENT_CATEGORY_NOTIFICATION_PREFERENCES}. If provided, will
210      * contain the tag provided to {@link NotificationManager#notify(String, int, Notification)}
211      * that can be used to narrow down what settings should be shown in the target app.
212      */
213     public static final String EXTRA_NOTIFICATION_TAG = "android.intent.extra.NOTIFICATION_TAG";
214 
215     /**
216      * Optional extra for {@link #INTENT_CATEGORY_NOTIFICATION_PREFERENCES}. If provided, will
217      * contain the id provided to {@link NotificationManager#notify(String, int, Notification)}
218      * that can be used to narrow down what settings should be shown in the target app.
219      */
220     public static final String EXTRA_NOTIFICATION_ID = "android.intent.extra.NOTIFICATION_ID";
221 
222     /**
223      * Use all default values (where applicable).
224      */
225     public static final int DEFAULT_ALL = ~0;
226 
227     /**
228      * Use the default notification sound. This will ignore any given
229      * {@link #sound}.
230      *
231      * <p>
232      * A notification that is noisy is more likely to be presented as a heads-up notification.
233      * </p>
234      *
235      * @see #defaults
236      */
237 
238     public static final int DEFAULT_SOUND = 1;
239 
240     /**
241      * Use the default notification vibrate. This will ignore any given
242      * {@link #vibrate}. Using phone vibration requires the
243      * {@link android.Manifest.permission#VIBRATE VIBRATE} permission.
244      *
245      * <p>
246      * A notification that vibrates is more likely to be presented as a heads-up notification.
247      * </p>
248      *
249      * @see #defaults
250      */
251 
252     public static final int DEFAULT_VIBRATE = 2;
253 
254     /**
255      * Use the default notification lights. This will ignore the
256      * {@link #FLAG_SHOW_LIGHTS} bit, and {@link #ledARGB}, {@link #ledOffMS}, or
257      * {@link #ledOnMS}.
258      *
259      * @see #defaults
260      */
261 
262     public static final int DEFAULT_LIGHTS = 4;
263 
264     /**
265      * Maximum length of CharSequences accepted by Builder and friends.
266      *
267      * <p>
268      * Avoids spamming the system with overly large strings such as full e-mails.
269      */
270     private static final int MAX_CHARSEQUENCE_LENGTH = 1024;
271 
272     /**
273      * Maximum entries of reply text that are accepted by Builder and friends.
274      */
275     private static final int MAX_REPLY_HISTORY = 5;
276 
277     /**
278      * Maximum aspect ratio of the large icon. 16:9
279      */
280     private static final float MAX_LARGE_ICON_ASPECT_RATIO = 16f / 9f;
281 
282     /**
283      * Maximum number of (generic) action buttons in a notification (contextual action buttons are
284      * handled separately).
285      * @hide
286      */
287     public static final int MAX_ACTION_BUTTONS = 3;
288 
289     /**
290      * If the notification contained an unsent draft for a RemoteInput when the user clicked on it,
291      * we're adding the draft as a String extra to the {@link #contentIntent} using this key.
292      *
293      * <p>Apps may use this extra to prepopulate text fields in the app, where the user usually
294      * sends messages.</p>
295      */
296     public static final String EXTRA_REMOTE_INPUT_DRAFT = "android.remoteInputDraft";
297 
298     /**
299      * A timestamp related to this notification, in milliseconds since the epoch.
300      *
301      * Default value: {@link System#currentTimeMillis() Now}.
302      *
303      * Choose a timestamp that will be most relevant to the user. For most finite events, this
304      * corresponds to the time the event happened (or will happen, in the case of events that have
305      * yet to occur but about which the user is being informed). Indefinite events should be
306      * timestamped according to when the activity began.
307      *
308      * Some examples:
309      *
310      * <ul>
311      *   <li>Notification of a new chat message should be stamped when the message was received.</li>
312      *   <li>Notification of an ongoing file download (with a progress bar, for example) should be stamped when the download started.</li>
313      *   <li>Notification of a completed file download should be stamped when the download finished.</li>
314      *   <li>Notification of an upcoming meeting should be stamped with the time the meeting will begin (that is, in the future).</li>
315      *   <li>Notification of an ongoing stopwatch (increasing timer) should be stamped with the watch's start time.
316      *   <li>Notification of an ongoing countdown timer should be stamped with the timer's end time.
317      * </ul>
318      *
319      * For apps targeting {@link android.os.Build.VERSION_CODES#N} and above, this time is not shown
320      * anymore by default and must be opted into by using
321      * {@link android.app.Notification.Builder#setShowWhen(boolean)}
322      */
323     public long when;
324 
325     /**
326      * The creation time of the notification
327      */
328     private long creationTime;
329 
330     /**
331      * The resource id of a drawable to use as the icon in the status bar.
332      *
333      * @deprecated Use {@link Builder#setSmallIcon(Icon)} instead.
334      */
335     @Deprecated
336     @DrawableRes
337     public int icon;
338 
339     /**
340      * If the icon in the status bar is to have more than one level, you can set this.  Otherwise,
341      * leave it at its default value of 0.
342      *
343      * @see android.widget.ImageView#setImageLevel
344      * @see android.graphics.drawable.Drawable#setLevel
345      */
346     public int iconLevel;
347 
348     /**
349      * The number of events that this notification represents. For example, in a new mail
350      * notification, this could be the number of unread messages.
351      *
352      * The system may or may not use this field to modify the appearance of the notification.
353      * Starting with {@link android.os.Build.VERSION_CODES#O}, the number may be displayed as a
354      * badge icon in Launchers that support badging.
355      */
356     public int number = 0;
357 
358     /**
359      * The intent to execute when the expanded status entry is clicked.  If
360      * this is an activity, it must include the
361      * {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK} flag, which requires
362      * that you take care of task management as described in the
363      * <a href="{@docRoot}guide/topics/fundamentals/tasks-and-back-stack.html">Tasks and Back
364      * Stack</a> document.  In particular, make sure to read the
365      * <a href="{@docRoot}/training/notify-user/navigation">Start
366      * an Activity from a Notification</a> page for the correct ways to launch an application from a
367      * notification.
368      */
369     public PendingIntent contentIntent;
370 
371     /**
372      * The intent to execute when the notification is explicitly dismissed by the user, either with
373      * the "Clear All" button or by swiping it away individually.
374      *
375      * This probably shouldn't be launching an activity since several of those will be sent
376      * at the same time.
377      */
378     public PendingIntent deleteIntent;
379 
380     /**
381      * An intent to launch instead of posting the notification to the status bar.
382      *
383      * <p>
384      * The system UI may choose to display a heads-up notification, instead of
385      * launching this intent, while the user is using the device.
386      * </p>
387      *
388      * @see Notification.Builder#setFullScreenIntent
389      */
390     public PendingIntent fullScreenIntent;
391 
392     /**
393      * Text that summarizes this notification for accessibility services.
394      *
395      * As of the L release, this text is no longer shown on screen, but it is still useful to
396      * accessibility services (where it serves as an audible announcement of the notification's
397      * appearance).
398      *
399      * @see #tickerView
400      */
401     public CharSequence tickerText;
402 
403     /**
404      * Formerly, a view showing the {@link #tickerText}.
405      *
406      * No longer displayed in the status bar as of API 21.
407      */
408     @Deprecated
409     public RemoteViews tickerView;
410 
411     /**
412      * The view that will represent this notification in the notification list (which is pulled
413      * down from the status bar).
414      *
415      * As of N, this field may be null. The notification view is determined by the inputs
416      * to {@link Notification.Builder}; a custom RemoteViews can optionally be
417      * supplied with {@link Notification.Builder#setCustomContentView(RemoteViews)}.
418      */
419     @Deprecated
420     public RemoteViews contentView;
421 
422     /**
423      * A large-format version of {@link #contentView}, giving the Notification an
424      * opportunity to show more detail. The system UI may choose to show this
425      * instead of the normal content view at its discretion.
426      *
427      * As of N, this field may be null. The expanded notification view is determined by the
428      * inputs to {@link Notification.Builder}; a custom RemoteViews can optionally be
429      * supplied with {@link Notification.Builder#setCustomBigContentView(RemoteViews)}.
430      */
431     @Deprecated
432     public RemoteViews bigContentView;
433 
434 
435     /**
436      * A medium-format version of {@link #contentView}, providing the Notification an
437      * opportunity to add action buttons to contentView. At its discretion, the system UI may
438      * choose to show this as a heads-up notification, which will pop up so the user can see
439      * it without leaving their current activity.
440      *
441      * As of N, this field may be null. The heads-up notification view is determined by the
442      * inputs to {@link Notification.Builder}; a custom RemoteViews can optionally be
443      * supplied with {@link Notification.Builder#setCustomHeadsUpContentView(RemoteViews)}.
444      */
445     @Deprecated
446     public RemoteViews headsUpContentView;
447 
448     private boolean mUsesStandardHeader;
449 
450     private static final ArraySet<Integer> STANDARD_LAYOUTS = new ArraySet<>();
451     static {
452         STANDARD_LAYOUTS.add(R.layout.notification_template_material_base);
453         STANDARD_LAYOUTS.add(R.layout.notification_template_material_heads_up_base);
454         STANDARD_LAYOUTS.add(R.layout.notification_template_material_big_base);
455         STANDARD_LAYOUTS.add(R.layout.notification_template_material_big_picture);
456         STANDARD_LAYOUTS.add(R.layout.notification_template_material_big_text);
457         STANDARD_LAYOUTS.add(R.layout.notification_template_material_inbox);
458         STANDARD_LAYOUTS.add(R.layout.notification_template_material_messaging);
459         STANDARD_LAYOUTS.add(R.layout.notification_template_material_big_messaging);
460         STANDARD_LAYOUTS.add(R.layout.notification_template_material_conversation);
461         STANDARD_LAYOUTS.add(R.layout.notification_template_material_media);
462         STANDARD_LAYOUTS.add(R.layout.notification_template_material_big_media);
463         STANDARD_LAYOUTS.add(R.layout.notification_template_material_call);
464         STANDARD_LAYOUTS.add(R.layout.notification_template_material_big_call);
465         STANDARD_LAYOUTS.add(R.layout.notification_template_header);
466     }
467 
468     /**
469      * A large bitmap to be shown in the notification content area.
470      *
471      * @deprecated Use {@link Builder#setLargeIcon(Icon)} instead.
472      */
473     @Deprecated
474     public Bitmap largeIcon;
475 
476     /**
477      * The sound to play.
478      *
479      * <p>
480      * A notification that is noisy is more likely to be presented as a heads-up notification.
481      * </p>
482      *
483      * <p>
484      * To play the default notification sound, see {@link #defaults}.
485      * </p>
486      * @deprecated use {@link NotificationChannel#getSound()}.
487      */
488     @Deprecated
489     public Uri sound;
490 
491     /**
492      * Use this constant as the value for audioStreamType to request that
493      * the default stream type for notifications be used.  Currently the
494      * default stream type is {@link AudioManager#STREAM_NOTIFICATION}.
495      *
496      * @deprecated Use {@link NotificationChannel#getAudioAttributes()} instead.
497      */
498     @Deprecated
499     public static final int STREAM_DEFAULT = -1;
500 
501     /**
502      * The audio stream type to use when playing the sound.
503      * Should be one of the STREAM_ constants from
504      * {@link android.media.AudioManager}.
505      *
506      * @deprecated Use {@link #audioAttributes} instead.
507      */
508     @Deprecated
509     public int audioStreamType = STREAM_DEFAULT;
510 
511     /**
512      * The default value of {@link #audioAttributes}.
513      */
514     public static final AudioAttributes AUDIO_ATTRIBUTES_DEFAULT = new AudioAttributes.Builder()
515             .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
516             .setUsage(AudioAttributes.USAGE_NOTIFICATION)
517             .build();
518 
519     /**
520      * The {@link AudioAttributes audio attributes} to use when playing the sound.
521      *
522      * @deprecated use {@link NotificationChannel#getAudioAttributes()} instead.
523      */
524     @Deprecated
525     public AudioAttributes audioAttributes = AUDIO_ATTRIBUTES_DEFAULT;
526 
527     /**
528      * The pattern with which to vibrate.
529      *
530      * <p>
531      * To vibrate the default pattern, see {@link #defaults}.
532      * </p>
533      *
534      * @see android.os.Vibrator#vibrate(long[],int)
535      * @deprecated use {@link NotificationChannel#getVibrationPattern()}.
536      */
537     @Deprecated
538     public long[] vibrate;
539 
540     /**
541      * The color of the led.  The hardware will do its best approximation.
542      *
543      * @see #FLAG_SHOW_LIGHTS
544      * @see #flags
545      * @deprecated use {@link NotificationChannel#shouldShowLights()}.
546      */
547     @ColorInt
548     @Deprecated
549     public int ledARGB;
550 
551     /**
552      * The number of milliseconds for the LED to be on while it's flashing.
553      * The hardware will do its best approximation.
554      *
555      * @see #FLAG_SHOW_LIGHTS
556      * @see #flags
557      * @deprecated use {@link NotificationChannel#shouldShowLights()}.
558      */
559     @Deprecated
560     public int ledOnMS;
561 
562     /**
563      * The number of milliseconds for the LED to be off while it's flashing.
564      * The hardware will do its best approximation.
565      *
566      * @see #FLAG_SHOW_LIGHTS
567      * @see #flags
568      *
569      * @deprecated use {@link NotificationChannel#shouldShowLights()}.
570      */
571     @Deprecated
572     public int ledOffMS;
573 
574     /**
575      * Specifies which values should be taken from the defaults.
576      * <p>
577      * To set, OR the desired from {@link #DEFAULT_SOUND},
578      * {@link #DEFAULT_VIBRATE}, {@link #DEFAULT_LIGHTS}. For all default
579      * values, use {@link #DEFAULT_ALL}.
580      * </p>
581      *
582      * @deprecated use {@link NotificationChannel#getSound()} and
583      * {@link NotificationChannel#shouldShowLights()} and
584      * {@link NotificationChannel#shouldVibrate()}.
585      */
586     @Deprecated
587     public int defaults;
588 
589     /**
590      * Bit to be bitwise-ored into the {@link #flags} field that should be
591      * set if you want the LED on for this notification.
592      * <ul>
593      * <li>To turn the LED off, pass 0 in the alpha channel for colorARGB
594      *      or 0 for both ledOnMS and ledOffMS.</li>
595      * <li>To turn the LED on, pass 1 for ledOnMS and 0 for ledOffMS.</li>
596      * <li>To flash the LED, pass the number of milliseconds that it should
597      *      be on and off to ledOnMS and ledOffMS.</li>
598      * </ul>
599      * <p>
600      * Since hardware varies, you are not guaranteed that any of the values
601      * you pass are honored exactly.  Use the system defaults if possible
602      * because they will be set to values that work on any given hardware.
603      * <p>
604      * The alpha channel must be set for forward compatibility.
605      *
606      * @deprecated use {@link NotificationChannel#shouldShowLights()}.
607      */
608     @Deprecated
609     public static final int FLAG_SHOW_LIGHTS        = 0x00000001;
610 
611     /**
612      * Bit to be bitwise-ored into the {@link #flags} field that should be
613      * set if this notification is in reference to something that is ongoing,
614      * like a phone call.  It should not be set if this notification is in
615      * reference to something that happened at a particular point in time,
616      * like a missed phone call.
617      */
618     public static final int FLAG_ONGOING_EVENT      = 0x00000002;
619 
620     /**
621      * Bit to be bitwise-ored into the {@link #flags} field that if set,
622      * the audio will be repeated until the notification is
623      * cancelled or the notification window is opened.
624      */
625     public static final int FLAG_INSISTENT          = 0x00000004;
626 
627     /**
628      * Bit to be bitwise-ored into the {@link #flags} field that should be
629      * set if you would only like the sound, vibrate and ticker to be played
630      * if the notification was not already showing.
631      *
632      * Note that using this flag will stop any ongoing alerting behaviour such
633      * as sound, vibration or blinking notification LED.
634      */
635     public static final int FLAG_ONLY_ALERT_ONCE    = 0x00000008;
636 
637     /**
638      * Bit to be bitwise-ored into the {@link #flags} field that should be
639      * set if the notification should be canceled when it is clicked by the
640      * user.
641      */
642     public static final int FLAG_AUTO_CANCEL        = 0x00000010;
643 
644     /**
645      * Bit to be bitwise-ored into the {@link #flags} field that should be
646      * set if the notification should not be canceled when the user clicks
647      * the Clear all button.
648      */
649     public static final int FLAG_NO_CLEAR           = 0x00000020;
650 
651     /**
652      * Bit to be bitwise-ored into the {@link #flags} field that should be
653      * set if this notification represents a currently running service.  This
654      * will normally be set for you by {@link Service#startForeground}.
655      */
656     public static final int FLAG_FOREGROUND_SERVICE = 0x00000040;
657 
658     /**
659      * Obsolete flag indicating high-priority notifications; use the priority field instead.
660      *
661      * @deprecated Use {@link #priority} with a positive value.
662      */
663     @Deprecated
664     public static final int FLAG_HIGH_PRIORITY      = 0x00000080;
665 
666     /**
667      * Bit to be bitswise-ored into the {@link #flags} field that should be
668      * set if this notification is relevant to the current device only
669      * and it is not recommended that it bridge to other devices.
670      */
671     public static final int FLAG_LOCAL_ONLY         = 0x00000100;
672 
673     /**
674      * Bit to be bitswise-ored into the {@link #flags} field that should be
675      * set if this notification is the group summary for a group of notifications.
676      * Grouped notifications may display in a cluster or stack on devices which
677      * support such rendering. Requires a group key also be set using {@link Builder#setGroup}.
678      */
679     public static final int FLAG_GROUP_SUMMARY      = 0x00000200;
680 
681     /**
682      * Bit to be bitswise-ored into the {@link #flags} field that should be
683      * set if this notification is the group summary for an auto-group of notifications.
684      *
685      * @hide
686      */
687     @SystemApi
688     public static final int FLAG_AUTOGROUP_SUMMARY  = 0x00000400;
689 
690     /**
691      * @hide
692      */
693     public static final int FLAG_CAN_COLORIZE = 0x00000800;
694 
695     /**
696      * Bit to be bitswised-ored into the {@link #flags} field that should be
697      * set by the system if this notification is showing as a bubble.
698      *
699      * Applications cannot set this flag directly; they should instead call
700      * {@link Notification.Builder#setBubbleMetadata(BubbleMetadata)} to
701      * request that a notification be displayed as a bubble, and then check
702      * this flag to see whether that request was honored by the system.
703      */
704     public static final int FLAG_BUBBLE = 0x00001000;
705 
706     private static final List<Class<? extends Style>> PLATFORM_STYLE_CLASSES = Arrays.asList(
707             BigTextStyle.class, BigPictureStyle.class, InboxStyle.class, MediaStyle.class,
708             DecoratedCustomViewStyle.class, DecoratedMediaCustomViewStyle.class,
709             MessagingStyle.class, CallStyle.class);
710 
711     /** @hide */
712     @IntDef(flag = true, prefix = { "FLAG_" }, value = {FLAG_SHOW_LIGHTS, FLAG_ONGOING_EVENT,
713             FLAG_INSISTENT, FLAG_ONLY_ALERT_ONCE,
714             FLAG_AUTO_CANCEL, FLAG_NO_CLEAR, FLAG_FOREGROUND_SERVICE, FLAG_HIGH_PRIORITY,
715             FLAG_LOCAL_ONLY, FLAG_GROUP_SUMMARY, FLAG_AUTOGROUP_SUMMARY, FLAG_BUBBLE})
716     @Retention(RetentionPolicy.SOURCE)
717     public @interface NotificationFlags{};
718 
719     public int flags;
720 
721     /** @hide */
722     @IntDef(prefix = { "PRIORITY_" }, value = {
723             PRIORITY_DEFAULT,
724             PRIORITY_LOW,
725             PRIORITY_MIN,
726             PRIORITY_HIGH,
727             PRIORITY_MAX
728     })
729     @Retention(RetentionPolicy.SOURCE)
730     public @interface Priority {}
731 
732     /**
733      * Default notification {@link #priority}. If your application does not prioritize its own
734      * notifications, use this value for all notifications.
735      *
736      * @deprecated use {@link NotificationManager#IMPORTANCE_DEFAULT} instead.
737      */
738     @Deprecated
739     public static final int PRIORITY_DEFAULT = 0;
740 
741     /**
742      * Lower {@link #priority}, for items that are less important. The UI may choose to show these
743      * items smaller, or at a different position in the list, compared with your app's
744      * {@link #PRIORITY_DEFAULT} items.
745      *
746      * @deprecated use {@link NotificationManager#IMPORTANCE_LOW} instead.
747      */
748     @Deprecated
749     public static final int PRIORITY_LOW = -1;
750 
751     /**
752      * Lowest {@link #priority}; these items might not be shown to the user except under special
753      * circumstances, such as detailed notification logs.
754      *
755      * @deprecated use {@link NotificationManager#IMPORTANCE_MIN} instead.
756      */
757     @Deprecated
758     public static final int PRIORITY_MIN = -2;
759 
760     /**
761      * Higher {@link #priority}, for more important notifications or alerts. The UI may choose to
762      * show these items larger, or at a different position in notification lists, compared with
763      * your app's {@link #PRIORITY_DEFAULT} items.
764      *
765      * @deprecated use {@link NotificationManager#IMPORTANCE_HIGH} instead.
766      */
767     @Deprecated
768     public static final int PRIORITY_HIGH = 1;
769 
770     /**
771      * Highest {@link #priority}, for your application's most important items that require the
772      * user's prompt attention or input.
773      *
774      * @deprecated use {@link NotificationManager#IMPORTANCE_HIGH} instead.
775      */
776     @Deprecated
777     public static final int PRIORITY_MAX = 2;
778 
779     /**
780      * Relative priority for this notification.
781      *
782      * Priority is an indication of how much of the user's valuable attention should be consumed by
783      * this notification. Low-priority notifications may be hidden from the user in certain
784      * situations, while the user might be interrupted for a higher-priority notification. The
785      * system will make a determination about how to interpret this priority when presenting
786      * the notification.
787      *
788      * <p>
789      * A notification that is at least {@link #PRIORITY_HIGH} is more likely to be presented
790      * as a heads-up notification.
791      * </p>
792      *
793      * @deprecated use {@link NotificationChannel#getImportance()} instead.
794      */
795     @Priority
796     @Deprecated
797     public int priority;
798 
799     /**
800      * Accent color (an ARGB integer like the constants in {@link android.graphics.Color})
801      * to be applied by the standard Style templates when presenting this notification.
802      *
803      * The current template design constructs a colorful header image by overlaying the
804      * {@link #icon} image (stenciled in white) atop a field of this color. Alpha components are
805      * ignored.
806      */
807     @ColorInt
808     public int color = COLOR_DEFAULT;
809 
810     /**
811      * Special value of {@link #color} telling the system not to decorate this notification with
812      * any special color but instead use default colors when presenting this notification.
813      */
814     @ColorInt
815     public static final int COLOR_DEFAULT = 0; // AKA Color.TRANSPARENT
816 
817     /**
818      * Special value of {@link #color} used as a place holder for an invalid color.
819      * @hide
820      */
821     @ColorInt
822     public static final int COLOR_INVALID = 1;
823 
824     /**
825      * Sphere of visibility of this notification, which affects how and when the SystemUI reveals
826      * the notification's presence and contents in untrusted situations (namely, on the secure
827      * lockscreen).
828      *
829      * The default level, {@link #VISIBILITY_PRIVATE}, behaves exactly as notifications have always
830      * done on Android: The notification's {@link #icon} and {@link #tickerText} (if available) are
831      * shown in all situations, but the contents are only available if the device is unlocked for
832      * the appropriate user.
833      *
834      * A more permissive policy can be expressed by {@link #VISIBILITY_PUBLIC}; such a notification
835      * can be read even in an "insecure" context (that is, above a secure lockscreen).
836      * To modify the public version of this notification—for example, to redact some portions—see
837      * {@link Builder#setPublicVersion(Notification)}.
838      *
839      * Finally, a notification can be made {@link #VISIBILITY_SECRET}, which will suppress its icon
840      * and ticker until the user has bypassed the lockscreen.
841      */
842     public @Visibility int visibility;
843 
844     /** @hide */
845     @IntDef(prefix = { "VISIBILITY_" }, value = {
846             VISIBILITY_PUBLIC,
847             VISIBILITY_PRIVATE,
848             VISIBILITY_SECRET,
849     })
850     @Retention(RetentionPolicy.SOURCE)
851     public @interface Visibility {}
852 
853     /**
854      * Notification visibility: Show this notification in its entirety on all lockscreens.
855      *
856      * {@see #visibility}
857      */
858     public static final int VISIBILITY_PUBLIC = 1;
859 
860     /**
861      * Notification visibility: Show this notification on all lockscreens, but conceal sensitive or
862      * private information on secure lockscreens.
863      *
864      * {@see #visibility}
865      */
866     public static final int VISIBILITY_PRIVATE = 0;
867 
868     /**
869      * Notification visibility: Do not reveal any part of this notification on a secure lockscreen.
870      *
871      * {@see #visibility}
872      */
873     public static final int VISIBILITY_SECRET = -1;
874 
875     /**
876      * @hide
877      */
878     @IntDef(prefix = "VISIBILITY_", value = {
879             VISIBILITY_PUBLIC,
880             VISIBILITY_PRIVATE,
881             VISIBILITY_SECRET,
882             NotificationManager.VISIBILITY_NO_OVERRIDE
883     })
884     public @interface NotificationVisibilityOverride{};
885 
886     /**
887      * Notification category: incoming call (voice or video) or similar synchronous communication request.
888      */
889     public static final String CATEGORY_CALL = "call";
890 
891     /**
892      * Notification category: map turn-by-turn navigation.
893      */
894     public static final String CATEGORY_NAVIGATION = "navigation";
895 
896     /**
897      * Notification category: incoming direct message (SMS, instant message, etc.).
898      */
899     public static final String CATEGORY_MESSAGE = "msg";
900 
901     /**
902      * Notification category: asynchronous bulk message (email).
903      */
904     public static final String CATEGORY_EMAIL = "email";
905 
906     /**
907      * Notification category: calendar event.
908      */
909     public static final String CATEGORY_EVENT = "event";
910 
911     /**
912      * Notification category: promotion or advertisement.
913      */
914     public static final String CATEGORY_PROMO = "promo";
915 
916     /**
917      * Notification category: alarm or timer.
918      */
919     public static final String CATEGORY_ALARM = "alarm";
920 
921     /**
922      * Notification category: progress of a long-running background operation.
923      */
924     public static final String CATEGORY_PROGRESS = "progress";
925 
926     /**
927      * Notification category: social network or sharing update.
928      */
929     public static final String CATEGORY_SOCIAL = "social";
930 
931     /**
932      * Notification category: error in background operation or authentication status.
933      */
934     public static final String CATEGORY_ERROR = "err";
935 
936     /**
937      * Notification category: media transport control for playback.
938      */
939     public static final String CATEGORY_TRANSPORT = "transport";
940 
941     /**
942      * Notification category: system or device status update.  Reserved for system use.
943      */
944     public static final String CATEGORY_SYSTEM = "sys";
945 
946     /**
947      * Notification category: indication of running background service.
948      */
949     public static final String CATEGORY_SERVICE = "service";
950 
951     /**
952      * Notification category: a specific, timely recommendation for a single thing.
953      * For example, a news app might want to recommend a news story it believes the user will
954      * want to read next.
955      */
956     public static final String CATEGORY_RECOMMENDATION = "recommendation";
957 
958     /**
959      * Notification category: ongoing information about device or contextual status.
960      */
961     public static final String CATEGORY_STATUS = "status";
962 
963     /**
964      * Notification category: user-scheduled reminder.
965      */
966     public static final String CATEGORY_REMINDER = "reminder";
967 
968     /**
969      * Notification category: extreme car emergencies.
970      * @hide
971      */
972     @SystemApi
973     public static final String CATEGORY_CAR_EMERGENCY = "car_emergency";
974 
975     /**
976      * Notification category: car warnings.
977      * @hide
978      */
979     @SystemApi
980     public static final String CATEGORY_CAR_WARNING = "car_warning";
981 
982     /**
983      * Notification category: general car system information.
984      * @hide
985      */
986     @SystemApi
987     public static final String CATEGORY_CAR_INFORMATION = "car_information";
988 
989     /**
990      * Notification category: tracking a user's workout.
991      */
992     public static final String CATEGORY_WORKOUT = "workout";
993 
994     /**
995      * Notification category: temporarily sharing location.
996      */
997     public static final String CATEGORY_LOCATION_SHARING = "location_sharing";
998 
999     /**
1000      * Notification category: running stopwatch.
1001      */
1002     public static final String CATEGORY_STOPWATCH = "stopwatch";
1003 
1004     /**
1005      * Notification category: missed call.
1006      */
1007     public static final String CATEGORY_MISSED_CALL = "missed_call";
1008 
1009     /**
1010      * One of the predefined notification categories (see the <code>CATEGORY_*</code> constants)
1011      * that best describes this Notification.  May be used by the system for ranking and filtering.
1012      */
1013     public String category;
1014 
1015     @UnsupportedAppUsage
1016     private String mGroupKey;
1017 
1018     /**
1019      * Get the key used to group this notification into a cluster or stack
1020      * with other notifications on devices which support such rendering.
1021      */
getGroup()1022     public String getGroup() {
1023         return mGroupKey;
1024     }
1025 
1026     private String mSortKey;
1027 
1028     /**
1029      * Get a sort key that orders this notification among other notifications from the
1030      * same package. This can be useful if an external sort was already applied and an app
1031      * would like to preserve this. Notifications will be sorted lexicographically using this
1032      * value, although providing different priorities in addition to providing sort key may
1033      * cause this value to be ignored.
1034      *
1035      * <p>This sort key can also be used to order members of a notification group. See
1036      * {@link Builder#setGroup}.
1037      *
1038      * @see String#compareTo(String)
1039      */
getSortKey()1040     public String getSortKey() {
1041         return mSortKey;
1042     }
1043 
1044     /**
1045      * Additional semantic data to be carried around with this Notification.
1046      * <p>
1047      * The extras keys defined here are intended to capture the original inputs to {@link Builder}
1048      * APIs, and are intended to be used by
1049      * {@link android.service.notification.NotificationListenerService} implementations to extract
1050      * detailed information from notification objects.
1051      */
1052     public Bundle extras = new Bundle();
1053 
1054     /**
1055      * All pending intents in the notification as the system needs to be able to access them but
1056      * touching the extras bundle in the system process is not safe because the bundle may contain
1057      * custom parcelable objects.
1058      *
1059      * @hide
1060      */
1061     @UnsupportedAppUsage
1062     public ArraySet<PendingIntent> allPendingIntents;
1063 
1064     /**
1065      * Token identifying the notification that is applying doze/bgcheck allowlisting to the
1066      * pending intents inside of it, so only those will get the behavior.
1067      *
1068      * @hide
1069      */
1070     private IBinder mAllowlistToken;
1071 
1072     /**
1073      * Must be set by a process to start associating tokens with Notification objects
1074      * coming in to it.  This is set by NotificationManagerService.
1075      *
1076      * @hide
1077      */
1078     static public IBinder processAllowlistToken;
1079 
1080     /**
1081      * {@link #extras} key: this is the title of the notification,
1082      * as supplied to {@link Builder#setContentTitle(CharSequence)}.
1083      */
1084     public static final String EXTRA_TITLE = "android.title";
1085 
1086     /**
1087      * {@link #extras} key: this is the title of the notification when shown in expanded form,
1088      * e.g. as supplied to {@link BigTextStyle#setBigContentTitle(CharSequence)}.
1089      */
1090     public static final String EXTRA_TITLE_BIG = EXTRA_TITLE + ".big";
1091 
1092     /**
1093      * {@link #extras} key: this is the main text payload, as supplied to
1094      * {@link Builder#setContentText(CharSequence)}.
1095      */
1096     public static final String EXTRA_TEXT = "android.text";
1097 
1098     /**
1099      * {@link #extras} key: this is a third line of text, as supplied to
1100      * {@link Builder#setSubText(CharSequence)}.
1101      */
1102     public static final String EXTRA_SUB_TEXT = "android.subText";
1103 
1104     /**
1105      * {@link #extras} key: this is the remote input history, as supplied to
1106      * {@link Builder#setRemoteInputHistory(CharSequence[])}.
1107      *
1108      * Apps can fill this through {@link Builder#setRemoteInputHistory(CharSequence[])}
1109      * with the most recent inputs that have been sent through a {@link RemoteInput} of this
1110      * Notification and are expected to clear it once the it is no longer relevant (e.g. for chat
1111      * notifications once the other party has responded).
1112      *
1113      * The extra with this key is of type CharSequence[] and contains the most recent entry at
1114      * the 0 index, the second most recent at the 1 index, etc.
1115      *
1116      * @see Builder#setRemoteInputHistory(CharSequence[])
1117      */
1118     public static final String EXTRA_REMOTE_INPUT_HISTORY = "android.remoteInputHistory";
1119 
1120 
1121     /**
1122      * {@link #extras} key: this is a remote input history which can include media messages
1123      * in addition to text, as supplied to
1124      * {@link Builder#setRemoteInputHistory(RemoteInputHistoryItem[])} or
1125      * {@link Builder#setRemoteInputHistory(CharSequence[])}.
1126      *
1127      * SystemUI can populate this through
1128      * {@link Builder#setRemoteInputHistory(RemoteInputHistoryItem[])} with the most recent inputs
1129      * that have been sent through a {@link RemoteInput} of this Notification. These items can
1130      * represent either media content (specified by a URI and a MIME type) or a text message
1131      * (described by a CharSequence).
1132      *
1133      * To maintain compatibility, this can also be set by apps with
1134      * {@link Builder#setRemoteInputHistory(CharSequence[])}, which will create a
1135      * {@link RemoteInputHistoryItem} for each of the provided text-only messages.
1136      *
1137      * The extra with this key is of type {@link RemoteInputHistoryItem[]} and contains the most
1138      * recent entry at the 0 index, the second most recent at the 1 index, etc.
1139      *
1140      * @see Builder#setRemoteInputHistory(RemoteInputHistoryItem[])
1141      * @hide
1142      */
1143     public static final String EXTRA_REMOTE_INPUT_HISTORY_ITEMS = "android.remoteInputHistoryItems";
1144 
1145     /**
1146      * {@link #extras} key: boolean as supplied to
1147      * {@link Builder#setShowRemoteInputSpinner(boolean)}.
1148      *
1149      * If set to true, then the view displaying the remote input history from
1150      * {@link Builder#setRemoteInputHistory(CharSequence[])} will have a progress spinner.
1151      *
1152      * @see Builder#setShowRemoteInputSpinner(boolean)
1153      * @hide
1154      */
1155     public static final String EXTRA_SHOW_REMOTE_INPUT_SPINNER = "android.remoteInputSpinner";
1156 
1157     /**
1158      * {@link #extras} key: boolean as supplied to
1159      * {@link Builder#setHideSmartReplies(boolean)}.
1160      *
1161      * If set to true, then any smart reply buttons will be hidden.
1162      *
1163      * @see Builder#setHideSmartReplies(boolean)
1164      * @hide
1165      */
1166     public static final String EXTRA_HIDE_SMART_REPLIES = "android.hideSmartReplies";
1167 
1168     /**
1169      * {@link #extras} key: this is a small piece of additional text as supplied to
1170      * {@link Builder#setContentInfo(CharSequence)}.
1171      */
1172     public static final String EXTRA_INFO_TEXT = "android.infoText";
1173 
1174     /**
1175      * {@link #extras} key: this is a line of summary information intended to be shown
1176      * alongside expanded notifications, as supplied to (e.g.)
1177      * {@link BigTextStyle#setSummaryText(CharSequence)}.
1178      */
1179     public static final String EXTRA_SUMMARY_TEXT = "android.summaryText";
1180 
1181     /**
1182      * {@link #extras} key: this is the longer text shown in the big form of a
1183      * {@link BigTextStyle} notification, as supplied to
1184      * {@link BigTextStyle#bigText(CharSequence)}.
1185      */
1186     public static final String EXTRA_BIG_TEXT = "android.bigText";
1187 
1188     /**
1189      * {@link #extras} key: this is the resource ID of the notification's main small icon, as
1190      * supplied to {@link Builder#setSmallIcon(int)}.
1191      *
1192      * @deprecated Use {@link #getSmallIcon()}, which supports a wider variety of icon sources.
1193      */
1194     @Deprecated
1195     public static final String EXTRA_SMALL_ICON = "android.icon";
1196 
1197     /**
1198      * {@link #extras} key: this is a bitmap to be used instead of the small icon when showing the
1199      * notification payload, as
1200      * supplied to {@link Builder#setLargeIcon(android.graphics.Bitmap)}.
1201      *
1202      * @deprecated Use {@link #getLargeIcon()}, which supports a wider variety of icon sources.
1203      */
1204     @Deprecated
1205     public static final String EXTRA_LARGE_ICON = "android.largeIcon";
1206 
1207     /**
1208      * {@link #extras} key: this is a bitmap to be used instead of the one from
1209      * {@link Builder#setLargeIcon(android.graphics.Bitmap)} when the notification is
1210      * shown in its expanded form, as supplied to
1211      * {@link BigPictureStyle#bigLargeIcon(android.graphics.Bitmap)}.
1212      */
1213     public static final String EXTRA_LARGE_ICON_BIG = EXTRA_LARGE_ICON + ".big";
1214 
1215     /**
1216      * {@link #extras} key: this is the progress value supplied to
1217      * {@link Builder#setProgress(int, int, boolean)}.
1218      */
1219     public static final String EXTRA_PROGRESS = "android.progress";
1220 
1221     /**
1222      * {@link #extras} key: this is the maximum value supplied to
1223      * {@link Builder#setProgress(int, int, boolean)}.
1224      */
1225     public static final String EXTRA_PROGRESS_MAX = "android.progressMax";
1226 
1227     /**
1228      * {@link #extras} key: whether the progress bar is indeterminate, supplied to
1229      * {@link Builder#setProgress(int, int, boolean)}.
1230      */
1231     public static final String EXTRA_PROGRESS_INDETERMINATE = "android.progressIndeterminate";
1232 
1233     /**
1234      * {@link #extras} key: whether {@link #when} should be shown as a count-up timer (specifically
1235      * a {@link android.widget.Chronometer}) instead of a timestamp, as supplied to
1236      * {@link Builder#setUsesChronometer(boolean)}.
1237      */
1238     public static final String EXTRA_SHOW_CHRONOMETER = "android.showChronometer";
1239 
1240     /**
1241      * {@link #extras} key: whether the chronometer set on the notification should count down
1242      * instead of counting up. Is only relevant if key {@link #EXTRA_SHOW_CHRONOMETER} is present.
1243      * This extra is a boolean. The default is false.
1244      */
1245     public static final String EXTRA_CHRONOMETER_COUNT_DOWN = "android.chronometerCountDown";
1246 
1247     /**
1248      * {@link #extras} key: whether {@link #when} should be shown,
1249      * as supplied to {@link Builder#setShowWhen(boolean)}.
1250      */
1251     public static final String EXTRA_SHOW_WHEN = "android.showWhen";
1252 
1253     /**
1254      * {@link #extras} key: this is a bitmap to be shown in {@link BigPictureStyle} expanded
1255      * notifications, supplied to {@link BigPictureStyle#bigPicture(android.graphics.Bitmap)}.
1256      */
1257     public static final String EXTRA_PICTURE = "android.picture";
1258 
1259     /**
1260      * {@link #extras} key: this is an {@link Icon} of an image to be
1261      * shown in {@link BigPictureStyle} expanded notifications, supplied to
1262      * {@link BigPictureStyle#bigPicture(Icon)}.
1263      */
1264     public static final String EXTRA_PICTURE_ICON = "android.pictureIcon";
1265 
1266     /**
1267      * {@link #extras} key: this is a content description of the big picture supplied from
1268      * {@link BigPictureStyle#bigPicture(Bitmap)}, supplied to
1269      * {@link BigPictureStyle#setContentDescription(CharSequence)}.
1270      */
1271     public static final String EXTRA_PICTURE_CONTENT_DESCRIPTION =
1272             "android.pictureContentDescription";
1273 
1274     /**
1275      * {@link #extras} key: this is a boolean to indicate that the
1276      * {@link BigPictureStyle#bigPicture(Bitmap) big picture} is to be shown in the collapsed state
1277      * of a {@link BigPictureStyle} notification.  This will replace a
1278      * {@link Builder#setLargeIcon(Icon) large icon} in that state if one was provided.
1279      */
1280     public static final String EXTRA_SHOW_BIG_PICTURE_WHEN_COLLAPSED =
1281             "android.showBigPictureWhenCollapsed";
1282 
1283     /**
1284      * {@link #extras} key: An array of CharSequences to show in {@link InboxStyle} expanded
1285      * notifications, each of which was supplied to {@link InboxStyle#addLine(CharSequence)}.
1286      */
1287     public static final String EXTRA_TEXT_LINES = "android.textLines";
1288 
1289     /**
1290      * {@link #extras} key: A string representing the name of the specific
1291      * {@link android.app.Notification.Style} used to create this notification.
1292      */
1293     public static final String EXTRA_TEMPLATE = "android.template";
1294 
1295     /**
1296      * {@link #extras} key: A String array containing the people that this notification relates to,
1297      * each of which was supplied to {@link Builder#addPerson(String)}.
1298      *
1299      * @deprecated the actual objects are now in {@link #EXTRA_PEOPLE_LIST}
1300      */
1301     public static final String EXTRA_PEOPLE = "android.people";
1302 
1303     /**
1304      * {@link #extras} key: An arrayList of {@link Person} objects containing the people that
1305      * this notification relates to.
1306      */
1307     public static final String EXTRA_PEOPLE_LIST = "android.people.list";
1308 
1309     /**
1310      * Allow certain system-generated notifications to appear before the device is provisioned.
1311      * Only available to notifications coming from the android package.
1312      * @hide
1313      */
1314     @SystemApi
1315     @RequiresPermission(android.Manifest.permission.NOTIFICATION_DURING_SETUP)
1316     public static final String EXTRA_ALLOW_DURING_SETUP = "android.allowDuringSetup";
1317 
1318     /**
1319      * {@link #extras} key:
1320      * flat {@link String} representation of a {@link android.content.ContentUris content URI}
1321      * pointing to an image that can be displayed in the background when the notification is
1322      * selected. Used on television platforms. The URI must point to an image stream suitable for
1323      * passing into {@link android.graphics.BitmapFactory#decodeStream(java.io.InputStream)
1324      * BitmapFactory.decodeStream}; all other content types will be ignored.
1325      */
1326     public static final String EXTRA_BACKGROUND_IMAGE_URI = "android.backgroundImageUri";
1327 
1328     /**
1329      * {@link #extras} key: A
1330      * {@link android.media.session.MediaSession.Token} associated with a
1331      * {@link android.app.Notification.MediaStyle} notification.
1332      */
1333     public static final String EXTRA_MEDIA_SESSION = "android.mediaSession";
1334 
1335     /**
1336      * {@link #extras} key: A {@code CharSequence} name of a remote device used for a media session
1337      * associated with a {@link Notification.MediaStyle} notification. This will show in the media
1338      * controls output switcher instead of the local device name.
1339      * @hide
1340      */
1341     @TestApi
1342     public static final String EXTRA_MEDIA_REMOTE_DEVICE = "android.mediaRemoteDevice";
1343 
1344     /**
1345      * {@link #extras} key: A {@code int} resource ID for an icon that should show in the output
1346      * switcher of the media controls for a {@link Notification.MediaStyle} notification.
1347      * @hide
1348      */
1349     @TestApi
1350     public static final String EXTRA_MEDIA_REMOTE_ICON = "android.mediaRemoteIcon";
1351 
1352     /**
1353      * {@link #extras} key: A {@code PendingIntent} that will replace the default action for the
1354      * media controls output switcher chip, associated with a {@link Notification.MediaStyle}
1355      * notification. This should launch an activity.
1356      * @hide
1357      */
1358     @TestApi
1359     public static final String EXTRA_MEDIA_REMOTE_INTENT = "android.mediaRemoteIntent";
1360 
1361     /**
1362      * {@link #extras} key: the indices of actions to be shown in the compact view,
1363      * as supplied to (e.g.) {@link MediaStyle#setShowActionsInCompactView(int...)}.
1364      */
1365     public static final String EXTRA_COMPACT_ACTIONS = "android.compactActions";
1366 
1367     /**
1368      * {@link #extras} key: the username to be displayed for all messages sent by the user including
1369      * direct replies
1370      * {@link android.app.Notification.MessagingStyle} notification. This extra is a
1371      * {@link CharSequence}
1372      *
1373      * @deprecated use {@link #EXTRA_MESSAGING_PERSON}
1374      */
1375     public static final String EXTRA_SELF_DISPLAY_NAME = "android.selfDisplayName";
1376 
1377     /**
1378      * {@link #extras} key: the person to be displayed for all messages sent by the user including
1379      * direct replies
1380      * {@link android.app.Notification.MessagingStyle} notification. This extra is a
1381      * {@link Person}
1382      */
1383     public static final String EXTRA_MESSAGING_PERSON = "android.messagingUser";
1384 
1385     /**
1386      * {@link #extras} key: a {@link CharSequence} to be displayed as the title to a conversation
1387      * represented by a {@link android.app.Notification.MessagingStyle}
1388      */
1389     public static final String EXTRA_CONVERSATION_TITLE = "android.conversationTitle";
1390 
1391     /** @hide */
1392     public static final String EXTRA_CONVERSATION_ICON = "android.conversationIcon";
1393 
1394     /** @hide */
1395     public static final String EXTRA_CONVERSATION_UNREAD_MESSAGE_COUNT =
1396             "android.conversationUnreadMessageCount";
1397 
1398     /**
1399      * {@link #extras} key: an array of {@link android.app.Notification.MessagingStyle.Message}
1400      * bundles provided by a
1401      * {@link android.app.Notification.MessagingStyle} notification. This extra is a parcelable
1402      * array of bundles.
1403      */
1404     public static final String EXTRA_MESSAGES = "android.messages";
1405 
1406     /**
1407      * {@link #extras} key: an array of
1408      * {@link android.app.Notification.MessagingStyle#addHistoricMessage historic}
1409      * {@link android.app.Notification.MessagingStyle.Message} bundles provided by a
1410      * {@link android.app.Notification.MessagingStyle} notification. This extra is a parcelable
1411      * array of bundles.
1412      */
1413     public static final String EXTRA_HISTORIC_MESSAGES = "android.messages.historic";
1414 
1415     /**
1416      * {@link #extras} key: whether the {@link android.app.Notification.MessagingStyle} notification
1417      * represents a group conversation.
1418      */
1419     public static final String EXTRA_IS_GROUP_CONVERSATION = "android.isGroupConversation";
1420 
1421     /**
1422      * {@link #extras} key: the type of call represented by the
1423      * {@link android.app.Notification.CallStyle} notification. This extra is an int.
1424      * @hide
1425      */
1426     public static final String EXTRA_CALL_TYPE = "android.callType";
1427 
1428     /**
1429      * {@link #extras} key: whether the  {@link android.app.Notification.CallStyle} notification
1430      * is for a call that will activate video when answered. This extra is a boolean.
1431      */
1432     public static final String EXTRA_CALL_IS_VIDEO = "android.callIsVideo";
1433 
1434     /**
1435      * {@link #extras} key: the person to be displayed as calling for the
1436      * {@link android.app.Notification.CallStyle} notification. This extra is a {@link Person}.
1437      */
1438     public static final String EXTRA_CALL_PERSON = "android.callPerson";
1439 
1440     /**
1441      * {@link #extras} key: the icon to be displayed as a verification status of the caller on a
1442      * {@link android.app.Notification.CallStyle} notification. This extra is an {@link Icon}.
1443      */
1444     public static final String EXTRA_VERIFICATION_ICON = "android.verificationIcon";
1445 
1446     /**
1447      * {@link #extras} key: the text to be displayed as a verification status of the caller on a
1448      * {@link android.app.Notification.CallStyle} notification. This extra is a
1449      * {@link CharSequence}.
1450      */
1451     public static final String EXTRA_VERIFICATION_TEXT = "android.verificationText";
1452 
1453     /**
1454      * {@link #extras} key: the intent to be sent when the users answers a
1455      * {@link android.app.Notification.CallStyle} notification. This extra is a
1456      * {@link PendingIntent}.
1457      */
1458     public static final String EXTRA_ANSWER_INTENT = "android.answerIntent";
1459 
1460     /**
1461      * {@link #extras} key: the intent to be sent when the users declines a
1462      * {@link android.app.Notification.CallStyle} notification. This extra is a
1463      * {@link PendingIntent}.
1464      */
1465     public static final String EXTRA_DECLINE_INTENT = "android.declineIntent";
1466 
1467     /**
1468      * {@link #extras} key: the intent to be sent when the users hangs up a
1469      * {@link android.app.Notification.CallStyle} notification. This extra is a
1470      * {@link PendingIntent}.
1471      */
1472     public static final String EXTRA_HANG_UP_INTENT = "android.hangUpIntent";
1473 
1474     /**
1475      * {@link #extras} key: the color used as a hint for the Answer action button of a
1476      * {@link android.app.Notification.CallStyle} notification. This extra is a {@link ColorInt}.
1477      */
1478     public static final String EXTRA_ANSWER_COLOR = "android.answerColor";
1479 
1480     /**
1481      * {@link #extras} key: the color used as a hint for the Decline or Hang Up action button of a
1482      * {@link android.app.Notification.CallStyle} notification. This extra is a {@link ColorInt}.
1483      */
1484     public static final String EXTRA_DECLINE_COLOR = "android.declineColor";
1485 
1486     /**
1487      * {@link #extras} key: whether the notification should be colorized as
1488      * supplied to {@link Builder#setColorized(boolean)}.
1489      */
1490     public static final String EXTRA_COLORIZED = "android.colorized";
1491 
1492     /**
1493      * @hide
1494      */
1495     public static final String EXTRA_BUILDER_APPLICATION_INFO = "android.appInfo";
1496 
1497     /**
1498      * @hide
1499      */
1500     public static final String EXTRA_CONTAINS_CUSTOM_VIEW = "android.contains.customView";
1501 
1502     /**
1503      * @hide
1504      */
1505     public static final String EXTRA_REDUCED_IMAGES = "android.reduced.images";
1506 
1507     /**
1508      * {@link #extras} key: the audio contents of this notification.
1509      *
1510      * This is for use when rendering the notification on an audio-focused interface;
1511      * the audio contents are a complete sound sample that contains the contents/body of the
1512      * notification. This may be used in substitute of a Text-to-Speech reading of the
1513      * notification. For example if the notification represents a voice message this should point
1514      * to the audio of that message.
1515      *
1516      * The data stored under this key should be a String representation of a Uri that contains the
1517      * audio contents in one of the following formats: WAV, PCM 16-bit, AMR-WB.
1518      *
1519      * This extra is unnecessary if you are using {@code MessagingStyle} since each {@code Message}
1520      * has a field for holding data URI. That field can be used for audio.
1521      * See {@code Message#setData}.
1522      *
1523      * Example usage:
1524      * <pre>
1525      * {@code
1526      * Notification.Builder myBuilder = (build your Notification as normal);
1527      * myBuilder.getExtras().putString(EXTRA_AUDIO_CONTENTS_URI, myAudioUri.toString());
1528      * }
1529      * </pre>
1530      */
1531     public static final String EXTRA_AUDIO_CONTENTS_URI = "android.audioContents";
1532 
1533     /** @hide */
1534     @SystemApi
1535     @RequiresPermission(android.Manifest.permission.SUBSTITUTE_NOTIFICATION_APP_NAME)
1536     public static final String EXTRA_SUBSTITUTE_APP_NAME = "android.substName";
1537 
1538     /**
1539      * This is set on the notifications shown by system_server about apps running foreground
1540      * services. It indicates that the notification should be shown
1541      * only if any of the given apps do not already have a properly tagged
1542      * {@link #FLAG_FOREGROUND_SERVICE} notification currently visible to the user.
1543      * This is a string array of all package names of the apps.
1544      * @hide
1545      */
1546     public static final String EXTRA_FOREGROUND_APPS = "android.foregroundApps";
1547 
1548     @UnsupportedAppUsage
1549     private Icon mSmallIcon;
1550     @UnsupportedAppUsage
1551     private Icon mLargeIcon;
1552 
1553     @UnsupportedAppUsage
1554     private String mChannelId;
1555     private long mTimeout;
1556 
1557     private String mShortcutId;
1558     private LocusId mLocusId;
1559     private CharSequence mSettingsText;
1560 
1561     private BubbleMetadata mBubbleMetadata;
1562 
1563     /** @hide */
1564     @IntDef(prefix = { "GROUP_ALERT_" }, value = {
1565             GROUP_ALERT_ALL, GROUP_ALERT_CHILDREN, GROUP_ALERT_SUMMARY
1566     })
1567     @Retention(RetentionPolicy.SOURCE)
1568     public @interface GroupAlertBehavior {}
1569 
1570     /**
1571      * Constant for {@link Builder#setGroupAlertBehavior(int)}, meaning that all notifications in a
1572      * group with sound or vibration ought to make sound or vibrate (respectively), so this
1573      * notification will not be muted when it is in a group.
1574      */
1575     public static final int GROUP_ALERT_ALL = 0;
1576 
1577     /**
1578      * Constant for {@link Builder#setGroupAlertBehavior(int)}, meaning that all children
1579      * notification in a group should be silenced (no sound or vibration) even if they are posted
1580      * to a {@link NotificationChannel} that has sound and/or vibration. Use this constant to
1581      * mute this notification if this notification is a group child. This must be applied to all
1582      * children notifications you want to mute.
1583      *
1584      * <p> For example, you might want to use this constant if you post a number of children
1585      * notifications at once (say, after a periodic sync), and only need to notify the user
1586      * audibly once.
1587      */
1588     public static final int GROUP_ALERT_SUMMARY = 1;
1589 
1590     /**
1591      * Constant for {@link Builder#setGroupAlertBehavior(int)}, meaning that the summary
1592      * notification in a group should be silenced (no sound or vibration) even if they are
1593      * posted to a {@link NotificationChannel} that has sound and/or vibration. Use this constant
1594      * to mute this notification if this notification is a group summary.
1595      *
1596      * <p>For example, you might want to use this constant if only the children notifications
1597      * in your group have content and the summary is only used to visually group notifications
1598      * rather than to alert the user that new information is available.
1599      */
1600     public static final int GROUP_ALERT_CHILDREN = 2;
1601 
1602     private int mGroupAlertBehavior = GROUP_ALERT_ALL;
1603 
1604     /**
1605      * If this notification is being shown as a badge, always show as a number.
1606      */
1607     public static final int BADGE_ICON_NONE = 0;
1608 
1609     /**
1610      * If this notification is being shown as a badge, use the {@link #getSmallIcon()} to
1611      * represent this notification.
1612      */
1613     public static final int BADGE_ICON_SMALL = 1;
1614 
1615     /**
1616      * If this notification is being shown as a badge, use the {@link #getLargeIcon()} to
1617      * represent this notification.
1618      */
1619     public static final int BADGE_ICON_LARGE = 2;
1620     private int mBadgeIcon = BADGE_ICON_NONE;
1621 
1622     /**
1623      * Determines whether the platform can generate contextual actions for a notification.
1624      */
1625     private boolean mAllowSystemGeneratedContextualActions = true;
1626 
1627     /**
1628      * Structure to encapsulate a named action that can be shown as part of this notification.
1629      * It must include an icon, a label, and a {@link PendingIntent} to be fired when the action is
1630      * selected by the user.
1631      * <p>
1632      * Apps should use {@link Notification.Builder#addAction(int, CharSequence, PendingIntent)}
1633      * or {@link Notification.Builder#addAction(Notification.Action)}
1634      * to attach actions.
1635      * <p>
1636      * As of Android {@link android.os.Build.VERSION_CODES#S}, apps targeting API level {@link
1637      * android.os.Build.VERSION_CODES#S} or higher won't be able to start activities while
1638      * processing broadcast receivers or services in response to notification action clicks. To
1639      * launch an activity in those cases, provide a {@link PendingIntent} for the activity itself.
1640      */
1641     public static class Action implements Parcelable {
1642         /**
1643          * {@link #extras} key: Keys to a {@link Parcelable} {@link ArrayList} of
1644          * {@link RemoteInput}s.
1645          *
1646          * This is intended for {@link RemoteInput}s that only accept data, meaning
1647          * {@link RemoteInput#getAllowFreeFormInput} is false, {@link RemoteInput#getChoices}
1648          * is null or empty, and {@link RemoteInput#getAllowedDataTypes} is non-null and not
1649          * empty. These {@link RemoteInput}s will be ignored by devices that do not
1650          * support non-text-based {@link RemoteInput}s. See {@link Builder#build}.
1651          *
1652          * You can test if a RemoteInput matches these constraints using
1653          * {@link RemoteInput#isDataOnly}.
1654          */
1655         private static final String EXTRA_DATA_ONLY_INPUTS = "android.extra.DATA_ONLY_INPUTS";
1656 
1657         /**
1658          * {@link }: No semantic action defined.
1659          */
1660         public static final int SEMANTIC_ACTION_NONE = 0;
1661 
1662         /**
1663          * {@code SemanticAction}: Reply to a conversation, chat, group, or wherever replies
1664          * may be appropriate.
1665          */
1666         public static final int SEMANTIC_ACTION_REPLY = 1;
1667 
1668         /**
1669          * {@code SemanticAction}: Mark content as read.
1670          */
1671         public static final int SEMANTIC_ACTION_MARK_AS_READ = 2;
1672 
1673         /**
1674          * {@code SemanticAction}: Mark content as unread.
1675          */
1676         public static final int SEMANTIC_ACTION_MARK_AS_UNREAD = 3;
1677 
1678         /**
1679          * {@code SemanticAction}: Delete the content associated with the notification. This
1680          * could mean deleting an email, message, etc.
1681          */
1682         public static final int SEMANTIC_ACTION_DELETE = 4;
1683 
1684         /**
1685          * {@code SemanticAction}: Archive the content associated with the notification. This
1686          * could mean archiving an email, message, etc.
1687          */
1688         public static final int SEMANTIC_ACTION_ARCHIVE = 5;
1689 
1690         /**
1691          * {@code SemanticAction}: Mute the content associated with the notification. This could
1692          * mean silencing a conversation or currently playing media.
1693          */
1694         public static final int SEMANTIC_ACTION_MUTE = 6;
1695 
1696         /**
1697          * {@code SemanticAction}: Unmute the content associated with the notification. This could
1698          * mean un-silencing a conversation or currently playing media.
1699          */
1700         public static final int SEMANTIC_ACTION_UNMUTE = 7;
1701 
1702         /**
1703          * {@code SemanticAction}: Mark content with a thumbs up.
1704          */
1705         public static final int SEMANTIC_ACTION_THUMBS_UP = 8;
1706 
1707         /**
1708          * {@code SemanticAction}: Mark content with a thumbs down.
1709          */
1710         public static final int SEMANTIC_ACTION_THUMBS_DOWN = 9;
1711 
1712         /**
1713          * {@code SemanticAction}: Call a contact, group, etc.
1714          */
1715         public static final int SEMANTIC_ACTION_CALL = 10;
1716 
1717         /**
1718          * {@code SemanticAction}: Mark the conversation associated with the notification as a
1719          * priority. Note that this is only for use by the notification assistant services. The
1720          * type will be ignored for actions an app adds to its own notifications.
1721          * @hide
1722          */
1723         @SystemApi
1724         public static final int SEMANTIC_ACTION_MARK_CONVERSATION_AS_PRIORITY = 11;
1725 
1726         /**
1727          * {@code SemanticAction}: Mark content as a potential phishing attempt.
1728          * Note that this is only for use by the notification assistant services. The type will
1729          * be ignored for actions an app adds to its own notifications.
1730          * @hide
1731          */
1732         @SystemApi
1733         public static final int SEMANTIC_ACTION_CONVERSATION_IS_PHISHING = 12;
1734 
1735         private final Bundle mExtras;
1736         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
1737         private Icon mIcon;
1738         private final RemoteInput[] mRemoteInputs;
1739         private boolean mAllowGeneratedReplies = true;
1740         private final @SemanticAction int mSemanticAction;
1741         private final boolean mIsContextual;
1742         private boolean mAuthenticationRequired;
1743 
1744         /**
1745          * Small icon representing the action.
1746          *
1747          * @deprecated Use {@link Action#getIcon()} instead.
1748          */
1749         @Deprecated
1750         public int icon;
1751 
1752         /**
1753          * Title of the action.
1754          */
1755         public CharSequence title;
1756 
1757         /**
1758          * Intent to send when the user invokes this action. May be null, in which case the action
1759          * may be rendered in a disabled presentation by the system UI.
1760          */
1761         public PendingIntent actionIntent;
1762 
Action(Parcel in)1763         private Action(Parcel in) {
1764             if (in.readInt() != 0) {
1765                 mIcon = Icon.CREATOR.createFromParcel(in);
1766                 if (mIcon.getType() == Icon.TYPE_RESOURCE) {
1767                     icon = mIcon.getResId();
1768                 }
1769             }
1770             title = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
1771             if (in.readInt() == 1) {
1772                 actionIntent = PendingIntent.CREATOR.createFromParcel(in);
1773             }
1774             mExtras = Bundle.setDefusable(in.readBundle(), true);
1775             mRemoteInputs = in.createTypedArray(RemoteInput.CREATOR);
1776             mAllowGeneratedReplies = in.readInt() == 1;
1777             mSemanticAction = in.readInt();
1778             mIsContextual = in.readInt() == 1;
1779             mAuthenticationRequired = in.readInt() == 1;
1780         }
1781 
1782         /**
1783          * @deprecated Use {@link android.app.Notification.Action.Builder}.
1784          */
1785         @Deprecated
Action(int icon, CharSequence title, PendingIntent intent)1786         public Action(int icon, CharSequence title, PendingIntent intent) {
1787             this(Icon.createWithResource("", icon), title, intent, new Bundle(), null, true,
1788                     SEMANTIC_ACTION_NONE, false /* isContextual */, false /* requireAuth */);
1789         }
1790 
1791         /** 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)1792         private Action(Icon icon, CharSequence title, PendingIntent intent, Bundle extras,
1793                 RemoteInput[] remoteInputs, boolean allowGeneratedReplies,
1794                 @SemanticAction int semanticAction, boolean isContextual,
1795                 boolean requireAuth) {
1796             this.mIcon = icon;
1797             if (icon != null && icon.getType() == Icon.TYPE_RESOURCE) {
1798                 this.icon = icon.getResId();
1799             }
1800             this.title = title;
1801             this.actionIntent = intent;
1802             this.mExtras = extras != null ? extras : new Bundle();
1803             this.mRemoteInputs = remoteInputs;
1804             this.mAllowGeneratedReplies = allowGeneratedReplies;
1805             this.mSemanticAction = semanticAction;
1806             this.mIsContextual = isContextual;
1807             this.mAuthenticationRequired = requireAuth;
1808         }
1809 
1810         /**
1811          * Return an icon representing the action.
1812          */
getIcon()1813         public Icon getIcon() {
1814             if (mIcon == null && icon != 0) {
1815                 // you snuck an icon in here without using the builder; let's try to keep it
1816                 mIcon = Icon.createWithResource("", icon);
1817             }
1818             return mIcon;
1819         }
1820 
1821         /**
1822          * Get additional metadata carried around with this Action.
1823          */
getExtras()1824         public Bundle getExtras() {
1825             return mExtras;
1826         }
1827 
1828         /**
1829          * Return whether the platform should automatically generate possible replies for this
1830          * {@link Action}
1831          */
getAllowGeneratedReplies()1832         public boolean getAllowGeneratedReplies() {
1833             return mAllowGeneratedReplies;
1834         }
1835 
1836         /**
1837          * Get the list of inputs to be collected from the user when this action is sent.
1838          * May return null if no remote inputs were added. Only returns inputs which accept
1839          * a text input. For inputs which only accept data use {@link #getDataOnlyRemoteInputs}.
1840          */
getRemoteInputs()1841         public RemoteInput[] getRemoteInputs() {
1842             return mRemoteInputs;
1843         }
1844 
1845         /**
1846          * Returns the {@code SemanticAction} associated with this {@link Action}. A
1847          * {@code SemanticAction} denotes what an {@link Action}'s {@link PendingIntent} will do
1848          * (eg. reply, mark as read, delete, etc).
1849          */
getSemanticAction()1850         public @SemanticAction int getSemanticAction() {
1851             return mSemanticAction;
1852         }
1853 
1854         /**
1855          * Returns whether this is a contextual Action, i.e. whether the action is dependent on the
1856          * notification message body. An example of a contextual action could be an action opening a
1857          * map application with an address shown in the notification.
1858          */
isContextual()1859         public boolean isContextual() {
1860             return mIsContextual;
1861         }
1862 
1863         /**
1864          * Get the list of inputs to be collected from the user that ONLY accept data when this
1865          * action is sent. These remote inputs are guaranteed to return true on a call to
1866          * {@link RemoteInput#isDataOnly}.
1867          *
1868          * Returns null if there are no data-only remote inputs.
1869          *
1870          * This method exists so that legacy RemoteInput collectors that pre-date the addition
1871          * of non-textual RemoteInputs do not access these remote inputs.
1872          */
getDataOnlyRemoteInputs()1873         public RemoteInput[] getDataOnlyRemoteInputs() {
1874             return getParcelableArrayFromBundle(mExtras, EXTRA_DATA_ONLY_INPUTS, RemoteInput.class);
1875         }
1876 
1877         /**
1878          * Returns whether the OS should only send this action's {@link PendingIntent} on an
1879          * unlocked device.
1880          *
1881          * If the device is locked when the action is invoked, the OS should show the keyguard and
1882          * require successful authentication before invoking the intent.
1883          */
isAuthenticationRequired()1884         public boolean isAuthenticationRequired() {
1885             return mAuthenticationRequired;
1886         }
1887 
1888         /**
1889          * Builder class for {@link Action} objects.
1890          */
1891         public static final class Builder {
1892             @Nullable private final Icon mIcon;
1893             @Nullable private final CharSequence mTitle;
1894             @Nullable private final PendingIntent mIntent;
1895             private boolean mAllowGeneratedReplies = true;
1896             @NonNull private final Bundle mExtras;
1897             @Nullable private ArrayList<RemoteInput> mRemoteInputs;
1898             private @SemanticAction int mSemanticAction;
1899             private boolean mIsContextual;
1900             private boolean mAuthenticationRequired;
1901 
1902             /**
1903              * Construct a new builder for {@link Action} object.
1904              * <p>As of Android {@link android.os.Build.VERSION_CODES#N},
1905              * action button icons will not be displayed on action buttons, but are still required
1906              * and are available to
1907              * {@link android.service.notification.NotificationListenerService notification listeners},
1908              * which may display them in other contexts, for example on a wearable device.
1909              * @param icon icon to show for this action
1910              * @param title the title of the action
1911              * @param intent the {@link PendingIntent} to fire when users trigger this action
1912              */
1913             @Deprecated
Builder(int icon, CharSequence title, PendingIntent intent)1914             public Builder(int icon, CharSequence title, PendingIntent intent) {
1915                 this(Icon.createWithResource("", icon), title, intent);
1916             }
1917 
1918             /**
1919              * Construct a new builder for {@link Action} object.
1920              *
1921              * <p>As of Android {@link android.os.Build.VERSION_CODES#S}, apps targeting API level
1922              * {@link android.os.Build.VERSION_CODES#S} or higher won't be able to start activities
1923              * while processing broadcast receivers or services in response to notification action
1924              * clicks. To launch an activity in those cases, provide a {@link PendingIntent} for the
1925              * activity itself.
1926              *
1927              * <p>How an Action is displayed, including whether the {@code icon}, {@code text}, or
1928              * both are displayed or required, depends on where and how the action is used, and the
1929              * {@link Style} applied to the Notification.
1930              *
1931              * <p>As of Android {@link android.os.Build.VERSION_CODES#N}, action button icons
1932              * will not be displayed on action buttons, but are still required and are available
1933              * to {@link android.service.notification.NotificationListenerService notification
1934              * listeners}, which may display them in other contexts, for example on a wearable
1935              * device.
1936              *
1937              * <p>When the {@code title} is a {@link android.text.Spanned}, any colors set by a
1938              * {@link ForegroundColorSpan} or {@link TextAppearanceSpan} may be removed or displayed
1939              * with an altered in luminance to ensure proper contrast within the Notification.
1940              *
1941              * @param icon icon to show for this action
1942              * @param title the title of the action
1943              * @param intent the {@link PendingIntent} to fire when users trigger this action
1944              */
Builder(Icon icon, CharSequence title, PendingIntent intent)1945             public Builder(Icon icon, CharSequence title, PendingIntent intent) {
1946                 this(icon, title, intent, new Bundle(), null, true, SEMANTIC_ACTION_NONE, false);
1947             }
1948 
1949             /**
1950              * Construct a new builder for {@link Action} object using the fields from an
1951              * {@link Action}.
1952              * @param action the action to read fields from.
1953              */
Builder(Action action)1954             public Builder(Action action) {
1955                 this(action.getIcon(), action.title, action.actionIntent,
1956                         new Bundle(action.mExtras), action.getRemoteInputs(),
1957                         action.getAllowGeneratedReplies(), action.getSemanticAction(),
1958                         action.isAuthenticationRequired());
1959             }
1960 
Builder(@ullable Icon icon, @Nullable CharSequence title, @Nullable PendingIntent intent, @NonNull Bundle extras, @Nullable RemoteInput[] remoteInputs, boolean allowGeneratedReplies, @SemanticAction int semanticAction, boolean authRequired)1961             private Builder(@Nullable Icon icon, @Nullable CharSequence title,
1962                     @Nullable PendingIntent intent, @NonNull Bundle extras,
1963                     @Nullable RemoteInput[] remoteInputs, boolean allowGeneratedReplies,
1964                     @SemanticAction int semanticAction, boolean authRequired) {
1965                 mIcon = icon;
1966                 mTitle = title;
1967                 mIntent = intent;
1968                 mExtras = extras;
1969                 if (remoteInputs != null) {
1970                     mRemoteInputs = new ArrayList<>(remoteInputs.length);
1971                     Collections.addAll(mRemoteInputs, remoteInputs);
1972                 }
1973                 mAllowGeneratedReplies = allowGeneratedReplies;
1974                 mSemanticAction = semanticAction;
1975                 mAuthenticationRequired = authRequired;
1976             }
1977 
1978             /**
1979              * Merge additional metadata into this builder.
1980              *
1981              * <p>Values within the Bundle will replace existing extras values in this Builder.
1982              *
1983              * @see Notification.Action#extras
1984              */
1985             @NonNull
addExtras(Bundle extras)1986             public Builder addExtras(Bundle extras) {
1987                 if (extras != null) {
1988                     mExtras.putAll(extras);
1989                 }
1990                 return this;
1991             }
1992 
1993             /**
1994              * Get the metadata Bundle used by this Builder.
1995              *
1996              * <p>The returned Bundle is shared with this Builder.
1997              */
1998             @NonNull
getExtras()1999             public Bundle getExtras() {
2000                 return mExtras;
2001             }
2002 
2003             /**
2004              * Add an input to be collected from the user when this action is sent.
2005              * Response values can be retrieved from the fired intent by using the
2006              * {@link RemoteInput#getResultsFromIntent} function.
2007              * @param remoteInput a {@link RemoteInput} to add to the action
2008              * @return this object for method chaining
2009              */
2010             @NonNull
addRemoteInput(RemoteInput remoteInput)2011             public Builder addRemoteInput(RemoteInput remoteInput) {
2012                 if (mRemoteInputs == null) {
2013                     mRemoteInputs = new ArrayList<RemoteInput>();
2014                 }
2015                 mRemoteInputs.add(remoteInput);
2016                 return this;
2017             }
2018 
2019             /**
2020              * Set whether the platform should automatically generate possible replies to add to
2021              * {@link RemoteInput#getChoices()}. If the {@link Action} doesn't have a
2022              * {@link RemoteInput}, this has no effect.
2023              * @param allowGeneratedReplies {@code true} to allow generated replies, {@code false}
2024              * otherwise
2025              * @return this object for method chaining
2026              * The default value is {@code true}
2027              */
2028             @NonNull
setAllowGeneratedReplies(boolean allowGeneratedReplies)2029             public Builder setAllowGeneratedReplies(boolean allowGeneratedReplies) {
2030                 mAllowGeneratedReplies = allowGeneratedReplies;
2031                 return this;
2032             }
2033 
2034             /**
2035              * Sets the {@code SemanticAction} for this {@link Action}. A
2036              * {@code SemanticAction} denotes what an {@link Action}'s
2037              * {@link PendingIntent} will do (eg. reply, mark as read, delete, etc).
2038              * @param semanticAction a SemanticAction defined within {@link Action} with
2039              * {@code SEMANTIC_ACTION_} prefixes
2040              * @return this object for method chaining
2041              */
2042             @NonNull
setSemanticAction(@emanticAction int semanticAction)2043             public Builder setSemanticAction(@SemanticAction int semanticAction) {
2044                 mSemanticAction = semanticAction;
2045                 return this;
2046             }
2047 
2048             /**
2049              * Sets whether this {@link Action} is a contextual action, i.e. whether the action is
2050              * dependent on the notification message body. An example of a contextual action could
2051              * be an action opening a map application with an address shown in the notification.
2052              */
2053             @NonNull
setContextual(boolean isContextual)2054             public Builder setContextual(boolean isContextual) {
2055                 mIsContextual = isContextual;
2056                 return this;
2057             }
2058 
2059             /**
2060              * Apply an extender to this action builder. Extenders may be used to add
2061              * metadata or change options on this builder.
2062              */
2063             @NonNull
extend(Extender extender)2064             public Builder extend(Extender extender) {
2065                 extender.extend(this);
2066                 return this;
2067             }
2068 
2069             /**
2070              * Sets whether the OS should only send this action's {@link PendingIntent} on an
2071              * unlocked device.
2072              *
2073              * If this is true and the device is locked when the action is invoked, the OS will
2074              * show the keyguard and require successful authentication before invoking the intent.
2075              * If this is false and the device is locked, the OS will decide whether authentication
2076              * should be required.
2077              */
2078             @NonNull
setAuthenticationRequired(boolean authenticationRequired)2079             public Builder setAuthenticationRequired(boolean authenticationRequired) {
2080                 mAuthenticationRequired = authenticationRequired;
2081                 return this;
2082             }
2083 
2084             /**
2085              * Throws an NPE if we are building a contextual action missing one of the fields
2086              * necessary to display the action.
2087              */
checkContextualActionNullFields()2088             private void checkContextualActionNullFields() {
2089                 if (!mIsContextual) return;
2090 
2091                 if (mIcon == null) {
2092                     throw new NullPointerException("Contextual Actions must contain a valid icon");
2093                 }
2094 
2095                 if (mIntent == null) {
2096                     throw new NullPointerException(
2097                             "Contextual Actions must contain a valid PendingIntent");
2098                 }
2099             }
2100 
2101             /**
2102              * Combine all of the options that have been set and return a new {@link Action}
2103              * object.
2104              * @return the built action
2105              */
2106             @NonNull
build()2107             public Action build() {
2108                 checkContextualActionNullFields();
2109 
2110                 ArrayList<RemoteInput> dataOnlyInputs = new ArrayList<>();
2111                 RemoteInput[] previousDataInputs = getParcelableArrayFromBundle(
2112                         mExtras, EXTRA_DATA_ONLY_INPUTS, RemoteInput.class);
2113                 if (previousDataInputs != null) {
2114                     for (RemoteInput input : previousDataInputs) {
2115                         dataOnlyInputs.add(input);
2116                     }
2117                 }
2118                 List<RemoteInput> textInputs = new ArrayList<>();
2119                 if (mRemoteInputs != null) {
2120                     for (RemoteInput input : mRemoteInputs) {
2121                         if (input.isDataOnly()) {
2122                             dataOnlyInputs.add(input);
2123                         } else {
2124                             textInputs.add(input);
2125                         }
2126                     }
2127                 }
2128                 if (!dataOnlyInputs.isEmpty()) {
2129                     RemoteInput[] dataInputsArr =
2130                             dataOnlyInputs.toArray(new RemoteInput[dataOnlyInputs.size()]);
2131                     mExtras.putParcelableArray(EXTRA_DATA_ONLY_INPUTS, dataInputsArr);
2132                 }
2133                 RemoteInput[] textInputsArr = textInputs.isEmpty()
2134                         ? null : textInputs.toArray(new RemoteInput[textInputs.size()]);
2135                 return new Action(mIcon, mTitle, mIntent, mExtras, textInputsArr,
2136                         mAllowGeneratedReplies, mSemanticAction, mIsContextual,
2137                         mAuthenticationRequired);
2138             }
2139         }
2140 
2141         @Override
clone()2142         public Action clone() {
2143             return new Action(
2144                     getIcon(),
2145                     title,
2146                     actionIntent, // safe to alias
2147                     mExtras == null ? new Bundle() : new Bundle(mExtras),
2148                     getRemoteInputs(),
2149                     getAllowGeneratedReplies(),
2150                     getSemanticAction(),
2151                     isContextual(),
2152                     isAuthenticationRequired());
2153         }
2154 
2155         @Override
describeContents()2156         public int describeContents() {
2157             return 0;
2158         }
2159 
2160         @Override
writeToParcel(Parcel out, int flags)2161         public void writeToParcel(Parcel out, int flags) {
2162             final Icon ic = getIcon();
2163             if (ic != null) {
2164                 out.writeInt(1);
2165                 ic.writeToParcel(out, 0);
2166             } else {
2167                 out.writeInt(0);
2168             }
2169             TextUtils.writeToParcel(title, out, flags);
2170             if (actionIntent != null) {
2171                 out.writeInt(1);
2172                 actionIntent.writeToParcel(out, flags);
2173             } else {
2174                 out.writeInt(0);
2175             }
2176             out.writeBundle(mExtras);
2177             out.writeTypedArray(mRemoteInputs, flags);
2178             out.writeInt(mAllowGeneratedReplies ? 1 : 0);
2179             out.writeInt(mSemanticAction);
2180             out.writeInt(mIsContextual ? 1 : 0);
2181             out.writeInt(mAuthenticationRequired ? 1 : 0);
2182         }
2183 
2184         public static final @android.annotation.NonNull Parcelable.Creator<Action> CREATOR =
2185                 new Parcelable.Creator<Action>() {
2186             public Action createFromParcel(Parcel in) {
2187                 return new Action(in);
2188             }
2189             public Action[] newArray(int size) {
2190                 return new Action[size];
2191             }
2192         };
2193 
2194         /**
2195          * Extender interface for use with {@link Builder#extend}. Extenders may be used to add
2196          * metadata or change options on an action builder.
2197          */
2198         public interface Extender {
2199             /**
2200              * Apply this extender to a notification action builder.
2201              * @param builder the builder to be modified.
2202              * @return the build object for chaining.
2203              */
extend(Builder builder)2204             public Builder extend(Builder builder);
2205         }
2206 
2207         /**
2208          * Wearable extender for notification actions. To add extensions to an action,
2209          * create a new {@link android.app.Notification.Action.WearableExtender} object using
2210          * the {@code WearableExtender()} constructor and apply it to a
2211          * {@link android.app.Notification.Action.Builder} using
2212          * {@link android.app.Notification.Action.Builder#extend}.
2213          *
2214          * <pre class="prettyprint">
2215          * Notification.Action action = new Notification.Action.Builder(
2216          *         R.drawable.archive_all, "Archive all", actionIntent)
2217          *         .extend(new Notification.Action.WearableExtender()
2218          *                 .setAvailableOffline(false))
2219          *         .build();</pre>
2220          */
2221         public static final class WearableExtender implements Extender {
2222             /** Notification action extra which contains wearable extensions */
2223             private static final String EXTRA_WEARABLE_EXTENSIONS = "android.wearable.EXTENSIONS";
2224 
2225             // Keys within EXTRA_WEARABLE_EXTENSIONS for wearable options.
2226             private static final String KEY_FLAGS = "flags";
2227             private static final String KEY_IN_PROGRESS_LABEL = "inProgressLabel";
2228             private static final String KEY_CONFIRM_LABEL = "confirmLabel";
2229             private static final String KEY_CANCEL_LABEL = "cancelLabel";
2230 
2231             // Flags bitwise-ored to mFlags
2232             private static final int FLAG_AVAILABLE_OFFLINE = 0x1;
2233             private static final int FLAG_HINT_LAUNCHES_ACTIVITY = 1 << 1;
2234             private static final int FLAG_HINT_DISPLAY_INLINE = 1 << 2;
2235 
2236             // Default value for flags integer
2237             private static final int DEFAULT_FLAGS = FLAG_AVAILABLE_OFFLINE;
2238 
2239             private int mFlags = DEFAULT_FLAGS;
2240 
2241             private CharSequence mInProgressLabel;
2242             private CharSequence mConfirmLabel;
2243             private CharSequence mCancelLabel;
2244 
2245             /**
2246              * Create a {@link android.app.Notification.Action.WearableExtender} with default
2247              * options.
2248              */
WearableExtender()2249             public WearableExtender() {
2250             }
2251 
2252             /**
2253              * Create a {@link android.app.Notification.Action.WearableExtender} by reading
2254              * wearable options present in an existing notification action.
2255              * @param action the notification action to inspect.
2256              */
WearableExtender(Action action)2257             public WearableExtender(Action action) {
2258                 Bundle wearableBundle = action.getExtras().getBundle(EXTRA_WEARABLE_EXTENSIONS);
2259                 if (wearableBundle != null) {
2260                     mFlags = wearableBundle.getInt(KEY_FLAGS, DEFAULT_FLAGS);
2261                     mInProgressLabel = wearableBundle.getCharSequence(KEY_IN_PROGRESS_LABEL);
2262                     mConfirmLabel = wearableBundle.getCharSequence(KEY_CONFIRM_LABEL);
2263                     mCancelLabel = wearableBundle.getCharSequence(KEY_CANCEL_LABEL);
2264                 }
2265             }
2266 
2267             /**
2268              * Apply wearable extensions to a notification action that is being built. This is
2269              * typically called by the {@link android.app.Notification.Action.Builder#extend}
2270              * method of {@link android.app.Notification.Action.Builder}.
2271              */
2272             @Override
extend(Action.Builder builder)2273             public Action.Builder extend(Action.Builder builder) {
2274                 Bundle wearableBundle = new Bundle();
2275 
2276                 if (mFlags != DEFAULT_FLAGS) {
2277                     wearableBundle.putInt(KEY_FLAGS, mFlags);
2278                 }
2279                 if (mInProgressLabel != null) {
2280                     wearableBundle.putCharSequence(KEY_IN_PROGRESS_LABEL, mInProgressLabel);
2281                 }
2282                 if (mConfirmLabel != null) {
2283                     wearableBundle.putCharSequence(KEY_CONFIRM_LABEL, mConfirmLabel);
2284                 }
2285                 if (mCancelLabel != null) {
2286                     wearableBundle.putCharSequence(KEY_CANCEL_LABEL, mCancelLabel);
2287                 }
2288 
2289                 builder.getExtras().putBundle(EXTRA_WEARABLE_EXTENSIONS, wearableBundle);
2290                 return builder;
2291             }
2292 
2293             @Override
clone()2294             public WearableExtender clone() {
2295                 WearableExtender that = new WearableExtender();
2296                 that.mFlags = this.mFlags;
2297                 that.mInProgressLabel = this.mInProgressLabel;
2298                 that.mConfirmLabel = this.mConfirmLabel;
2299                 that.mCancelLabel = this.mCancelLabel;
2300                 return that;
2301             }
2302 
2303             /**
2304              * Set whether this action is available when the wearable device is not connected to
2305              * a companion device. The user can still trigger this action when the wearable device is
2306              * offline, but a visual hint will indicate that the action may not be available.
2307              * Defaults to true.
2308              */
setAvailableOffline(boolean availableOffline)2309             public WearableExtender setAvailableOffline(boolean availableOffline) {
2310                 setFlag(FLAG_AVAILABLE_OFFLINE, availableOffline);
2311                 return this;
2312             }
2313 
2314             /**
2315              * Get whether this action is available when the wearable device is not connected to
2316              * a companion device. The user can still trigger this action when the wearable device is
2317              * offline, but a visual hint will indicate that the action may not be available.
2318              * Defaults to true.
2319              */
isAvailableOffline()2320             public boolean isAvailableOffline() {
2321                 return (mFlags & FLAG_AVAILABLE_OFFLINE) != 0;
2322             }
2323 
setFlag(int mask, boolean value)2324             private void setFlag(int mask, boolean value) {
2325                 if (value) {
2326                     mFlags |= mask;
2327                 } else {
2328                     mFlags &= ~mask;
2329                 }
2330             }
2331 
2332             /**
2333              * Set a label to display while the wearable is preparing to automatically execute the
2334              * action. This is usually a 'ing' verb ending in ellipsis like "Sending..."
2335              *
2336              * @param label the label to display while the action is being prepared to execute
2337              * @return this object for method chaining
2338              */
2339             @Deprecated
setInProgressLabel(CharSequence label)2340             public WearableExtender setInProgressLabel(CharSequence label) {
2341                 mInProgressLabel = label;
2342                 return this;
2343             }
2344 
2345             /**
2346              * Get the label to display while the wearable is preparing to automatically execute
2347              * the action. This is usually a 'ing' verb ending in ellipsis like "Sending..."
2348              *
2349              * @return the label to display while the action is being prepared to execute
2350              */
2351             @Deprecated
getInProgressLabel()2352             public CharSequence getInProgressLabel() {
2353                 return mInProgressLabel;
2354             }
2355 
2356             /**
2357              * Set a label to display to confirm that the action should be executed.
2358              * This is usually an imperative verb like "Send".
2359              *
2360              * @param label the label to confirm the action should be executed
2361              * @return this object for method chaining
2362              */
2363             @Deprecated
setConfirmLabel(CharSequence label)2364             public WearableExtender setConfirmLabel(CharSequence label) {
2365                 mConfirmLabel = label;
2366                 return this;
2367             }
2368 
2369             /**
2370              * Get the label to display to confirm that the action should be executed.
2371              * This is usually an imperative verb like "Send".
2372              *
2373              * @return the label to confirm the action should be executed
2374              */
2375             @Deprecated
getConfirmLabel()2376             public CharSequence getConfirmLabel() {
2377                 return mConfirmLabel;
2378             }
2379 
2380             /**
2381              * Set a label to display to cancel the action.
2382              * This is usually an imperative verb, like "Cancel".
2383              *
2384              * @param label the label to display to cancel the action
2385              * @return this object for method chaining
2386              */
2387             @Deprecated
setCancelLabel(CharSequence label)2388             public WearableExtender setCancelLabel(CharSequence label) {
2389                 mCancelLabel = label;
2390                 return this;
2391             }
2392 
2393             /**
2394              * Get the label to display to cancel the action.
2395              * This is usually an imperative verb like "Cancel".
2396              *
2397              * @return the label to display to cancel the action
2398              */
2399             @Deprecated
getCancelLabel()2400             public CharSequence getCancelLabel() {
2401                 return mCancelLabel;
2402             }
2403 
2404             /**
2405              * Set a hint that this Action will launch an {@link Activity} directly, telling the
2406              * platform that it can generate the appropriate transitions.
2407              * @param hintLaunchesActivity {@code true} if the content intent will launch
2408              * an activity and transitions should be generated, false otherwise.
2409              * @return this object for method chaining
2410              */
setHintLaunchesActivity( boolean hintLaunchesActivity)2411             public WearableExtender setHintLaunchesActivity(
2412                     boolean hintLaunchesActivity) {
2413                 setFlag(FLAG_HINT_LAUNCHES_ACTIVITY, hintLaunchesActivity);
2414                 return this;
2415             }
2416 
2417             /**
2418              * Get a hint that this Action will launch an {@link Activity} directly, telling the
2419              * platform that it can generate the appropriate transitions
2420              * @return {@code true} if the content intent will launch an activity and transitions
2421              * should be generated, false otherwise. The default value is {@code false} if this was
2422              * never set.
2423              */
getHintLaunchesActivity()2424             public boolean getHintLaunchesActivity() {
2425                 return (mFlags & FLAG_HINT_LAUNCHES_ACTIVITY) != 0;
2426             }
2427 
2428             /**
2429              * Set a hint that this Action should be displayed inline.
2430              *
2431              * @param hintDisplayInline {@code true} if action should be displayed inline, false
2432              *        otherwise
2433              * @return this object for method chaining
2434              */
setHintDisplayActionInline( boolean hintDisplayInline)2435             public WearableExtender setHintDisplayActionInline(
2436                     boolean hintDisplayInline) {
2437                 setFlag(FLAG_HINT_DISPLAY_INLINE, hintDisplayInline);
2438                 return this;
2439             }
2440 
2441             /**
2442              * Get a hint that this Action should be displayed inline.
2443              *
2444              * @return {@code true} if the Action should be displayed inline, {@code false}
2445              *         otherwise. The default value is {@code false} if this was never set.
2446              */
getHintDisplayActionInline()2447             public boolean getHintDisplayActionInline() {
2448                 return (mFlags & FLAG_HINT_DISPLAY_INLINE) != 0;
2449             }
2450         }
2451 
2452         /**
2453          * Provides meaning to an {@link Action} that hints at what the associated
2454          * {@link PendingIntent} will do. For example, an {@link Action} with a
2455          * {@link PendingIntent} that replies to a text message notification may have the
2456          * {@link #SEMANTIC_ACTION_REPLY} {@code SemanticAction} set within it.
2457          *
2458          * @hide
2459          */
2460         @IntDef(prefix = { "SEMANTIC_ACTION_" }, value = {
2461                 SEMANTIC_ACTION_NONE,
2462                 SEMANTIC_ACTION_REPLY,
2463                 SEMANTIC_ACTION_MARK_AS_READ,
2464                 SEMANTIC_ACTION_MARK_AS_UNREAD,
2465                 SEMANTIC_ACTION_DELETE,
2466                 SEMANTIC_ACTION_ARCHIVE,
2467                 SEMANTIC_ACTION_MUTE,
2468                 SEMANTIC_ACTION_UNMUTE,
2469                 SEMANTIC_ACTION_THUMBS_UP,
2470                 SEMANTIC_ACTION_THUMBS_DOWN,
2471                 SEMANTIC_ACTION_CALL,
2472                 SEMANTIC_ACTION_MARK_CONVERSATION_AS_PRIORITY,
2473                 SEMANTIC_ACTION_CONVERSATION_IS_PHISHING
2474         })
2475         @Retention(RetentionPolicy.SOURCE)
2476         public @interface SemanticAction {}
2477     }
2478 
2479     /**
2480      * Array of all {@link Action} structures attached to this notification by
2481      * {@link Builder#addAction(int, CharSequence, PendingIntent)}. Mostly useful for instances of
2482      * {@link android.service.notification.NotificationListenerService} that provide an alternative
2483      * interface for invoking actions.
2484      */
2485     public Action[] actions;
2486 
2487     /**
2488      * Replacement version of this notification whose content will be shown
2489      * in an insecure context such as atop a secure keyguard. See {@link #visibility}
2490      * and {@link #VISIBILITY_PUBLIC}.
2491      */
2492     public Notification publicVersion;
2493 
2494     /**
2495      * Constructs a Notification object with default values.
2496      * You might want to consider using {@link Builder} instead.
2497      */
Notification()2498     public Notification()
2499     {
2500         this.when = System.currentTimeMillis();
2501         this.creationTime = System.currentTimeMillis();
2502         this.priority = PRIORITY_DEFAULT;
2503     }
2504 
2505     /**
2506      * @hide
2507      */
2508     @UnsupportedAppUsage
Notification(Context context, int icon, CharSequence tickerText, long when, CharSequence contentTitle, CharSequence contentText, Intent contentIntent)2509     public Notification(Context context, int icon, CharSequence tickerText, long when,
2510             CharSequence contentTitle, CharSequence contentText, Intent contentIntent)
2511     {
2512         new Builder(context)
2513                 .setWhen(when)
2514                 .setSmallIcon(icon)
2515                 .setTicker(tickerText)
2516                 .setContentTitle(contentTitle)
2517                 .setContentText(contentText)
2518                 .setContentIntent(PendingIntent.getActivity(
2519                         context, 0, contentIntent, PendingIntent.FLAG_MUTABLE))
2520                 .buildInto(this);
2521     }
2522 
2523     /**
2524      * Constructs a Notification object with the information needed to
2525      * have a status bar icon without the standard expanded view.
2526      *
2527      * @param icon          The resource id of the icon to put in the status bar.
2528      * @param tickerText    The text that flows by in the status bar when the notification first
2529      *                      activates.
2530      * @param when          The time to show in the time field.  In the System.currentTimeMillis
2531      *                      timebase.
2532      *
2533      * @deprecated Use {@link Builder} instead.
2534      */
2535     @Deprecated
Notification(int icon, CharSequence tickerText, long when)2536     public Notification(int icon, CharSequence tickerText, long when)
2537     {
2538         this.icon = icon;
2539         this.tickerText = tickerText;
2540         this.when = when;
2541         this.creationTime = System.currentTimeMillis();
2542     }
2543 
2544     /**
2545      * Unflatten the notification from a parcel.
2546      */
2547     @SuppressWarnings("unchecked")
Notification(Parcel parcel)2548     public Notification(Parcel parcel) {
2549         // IMPORTANT: Add unmarshaling code in readFromParcel as the pending
2550         // intents in extras are always written as the last entry.
2551         readFromParcelImpl(parcel);
2552         // Must be read last!
2553         allPendingIntents = (ArraySet<PendingIntent>) parcel.readArraySet(null);
2554     }
2555 
readFromParcelImpl(Parcel parcel)2556     private void readFromParcelImpl(Parcel parcel)
2557     {
2558         int version = parcel.readInt();
2559 
2560         mAllowlistToken = parcel.readStrongBinder();
2561         if (mAllowlistToken == null) {
2562             mAllowlistToken = processAllowlistToken;
2563         }
2564         // Propagate this token to all pending intents that are unmarshalled from the parcel.
2565         parcel.setClassCookie(PendingIntent.class, mAllowlistToken);
2566 
2567         when = parcel.readLong();
2568         creationTime = parcel.readLong();
2569         if (parcel.readInt() != 0) {
2570             mSmallIcon = Icon.CREATOR.createFromParcel(parcel);
2571             if (mSmallIcon.getType() == Icon.TYPE_RESOURCE) {
2572                 icon = mSmallIcon.getResId();
2573             }
2574         }
2575         number = parcel.readInt();
2576         if (parcel.readInt() != 0) {
2577             contentIntent = PendingIntent.CREATOR.createFromParcel(parcel);
2578         }
2579         if (parcel.readInt() != 0) {
2580             deleteIntent = PendingIntent.CREATOR.createFromParcel(parcel);
2581         }
2582         if (parcel.readInt() != 0) {
2583             tickerText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
2584         }
2585         if (parcel.readInt() != 0) {
2586             tickerView = RemoteViews.CREATOR.createFromParcel(parcel);
2587         }
2588         if (parcel.readInt() != 0) {
2589             contentView = RemoteViews.CREATOR.createFromParcel(parcel);
2590         }
2591         if (parcel.readInt() != 0) {
2592             mLargeIcon = Icon.CREATOR.createFromParcel(parcel);
2593         }
2594         defaults = parcel.readInt();
2595         flags = parcel.readInt();
2596         if (parcel.readInt() != 0) {
2597             sound = Uri.CREATOR.createFromParcel(parcel);
2598         }
2599 
2600         audioStreamType = parcel.readInt();
2601         if (parcel.readInt() != 0) {
2602             audioAttributes = AudioAttributes.CREATOR.createFromParcel(parcel);
2603         }
2604         vibrate = parcel.createLongArray();
2605         ledARGB = parcel.readInt();
2606         ledOnMS = parcel.readInt();
2607         ledOffMS = parcel.readInt();
2608         iconLevel = parcel.readInt();
2609 
2610         if (parcel.readInt() != 0) {
2611             fullScreenIntent = PendingIntent.CREATOR.createFromParcel(parcel);
2612         }
2613 
2614         priority = parcel.readInt();
2615 
2616         category = parcel.readString8();
2617 
2618         mGroupKey = parcel.readString8();
2619 
2620         mSortKey = parcel.readString8();
2621 
2622         extras = Bundle.setDefusable(parcel.readBundle(), true); // may be null
2623         fixDuplicateExtras();
2624 
2625         actions = parcel.createTypedArray(Action.CREATOR); // may be null
2626 
2627         if (parcel.readInt() != 0) {
2628             bigContentView = RemoteViews.CREATOR.createFromParcel(parcel);
2629         }
2630 
2631         if (parcel.readInt() != 0) {
2632             headsUpContentView = RemoteViews.CREATOR.createFromParcel(parcel);
2633         }
2634 
2635         visibility = parcel.readInt();
2636 
2637         if (parcel.readInt() != 0) {
2638             publicVersion = Notification.CREATOR.createFromParcel(parcel);
2639         }
2640 
2641         color = parcel.readInt();
2642 
2643         if (parcel.readInt() != 0) {
2644             mChannelId = parcel.readString8();
2645         }
2646         mTimeout = parcel.readLong();
2647 
2648         if (parcel.readInt() != 0) {
2649             mShortcutId = parcel.readString8();
2650         }
2651 
2652         if (parcel.readInt() != 0) {
2653             mLocusId = LocusId.CREATOR.createFromParcel(parcel);
2654         }
2655 
2656         mBadgeIcon = parcel.readInt();
2657 
2658         if (parcel.readInt() != 0) {
2659             mSettingsText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
2660         }
2661 
2662         mGroupAlertBehavior = parcel.readInt();
2663         if (parcel.readInt() != 0) {
2664             mBubbleMetadata = BubbleMetadata.CREATOR.createFromParcel(parcel);
2665         }
2666 
2667         mAllowSystemGeneratedContextualActions = parcel.readBoolean();
2668 
2669         mFgsDeferBehavior = parcel.readInt();
2670     }
2671 
2672     @Override
clone()2673     public Notification clone() {
2674         Notification that = new Notification();
2675         cloneInto(that, true);
2676         return that;
2677     }
2678 
2679     /**
2680      * Copy all (or if heavy is false, all except Bitmaps and RemoteViews) members
2681      * of this into that.
2682      * @hide
2683      */
cloneInto(Notification that, boolean heavy)2684     public void cloneInto(Notification that, boolean heavy) {
2685         that.mAllowlistToken = this.mAllowlistToken;
2686         that.when = this.when;
2687         that.creationTime = this.creationTime;
2688         that.mSmallIcon = this.mSmallIcon;
2689         that.number = this.number;
2690 
2691         // PendingIntents are global, so there's no reason (or way) to clone them.
2692         that.contentIntent = this.contentIntent;
2693         that.deleteIntent = this.deleteIntent;
2694         that.fullScreenIntent = this.fullScreenIntent;
2695 
2696         if (this.tickerText != null) {
2697             that.tickerText = this.tickerText.toString();
2698         }
2699         if (heavy && this.tickerView != null) {
2700             that.tickerView = this.tickerView.clone();
2701         }
2702         if (heavy && this.contentView != null) {
2703             that.contentView = this.contentView.clone();
2704         }
2705         if (heavy && this.mLargeIcon != null) {
2706             that.mLargeIcon = this.mLargeIcon;
2707         }
2708         that.iconLevel = this.iconLevel;
2709         that.sound = this.sound; // android.net.Uri is immutable
2710         that.audioStreamType = this.audioStreamType;
2711         if (this.audioAttributes != null) {
2712             that.audioAttributes = new AudioAttributes.Builder(this.audioAttributes).build();
2713         }
2714 
2715         final long[] vibrate = this.vibrate;
2716         if (vibrate != null) {
2717             final int N = vibrate.length;
2718             final long[] vib = that.vibrate = new long[N];
2719             System.arraycopy(vibrate, 0, vib, 0, N);
2720         }
2721 
2722         that.ledARGB = this.ledARGB;
2723         that.ledOnMS = this.ledOnMS;
2724         that.ledOffMS = this.ledOffMS;
2725         that.defaults = this.defaults;
2726 
2727         that.flags = this.flags;
2728 
2729         that.priority = this.priority;
2730 
2731         that.category = this.category;
2732 
2733         that.mGroupKey = this.mGroupKey;
2734 
2735         that.mSortKey = this.mSortKey;
2736 
2737         if (this.extras != null) {
2738             try {
2739                 that.extras = new Bundle(this.extras);
2740                 // will unparcel
2741                 that.extras.size();
2742             } catch (BadParcelableException e) {
2743                 Log.e(TAG, "could not unparcel extras from notification: " + this, e);
2744                 that.extras = null;
2745             }
2746         }
2747 
2748         if (!ArrayUtils.isEmpty(allPendingIntents)) {
2749             that.allPendingIntents = new ArraySet<>(allPendingIntents);
2750         }
2751 
2752         if (this.actions != null) {
2753             that.actions = new Action[this.actions.length];
2754             for(int i=0; i<this.actions.length; i++) {
2755                 if ( this.actions[i] != null) {
2756                     that.actions[i] = this.actions[i].clone();
2757                 }
2758             }
2759         }
2760 
2761         if (heavy && this.bigContentView != null) {
2762             that.bigContentView = this.bigContentView.clone();
2763         }
2764 
2765         if (heavy && this.headsUpContentView != null) {
2766             that.headsUpContentView = this.headsUpContentView.clone();
2767         }
2768 
2769         that.visibility = this.visibility;
2770 
2771         if (this.publicVersion != null) {
2772             that.publicVersion = new Notification();
2773             this.publicVersion.cloneInto(that.publicVersion, heavy);
2774         }
2775 
2776         that.color = this.color;
2777 
2778         that.mChannelId = this.mChannelId;
2779         that.mTimeout = this.mTimeout;
2780         that.mShortcutId = this.mShortcutId;
2781         that.mLocusId = this.mLocusId;
2782         that.mBadgeIcon = this.mBadgeIcon;
2783         that.mSettingsText = this.mSettingsText;
2784         that.mGroupAlertBehavior = this.mGroupAlertBehavior;
2785         that.mFgsDeferBehavior = this.mFgsDeferBehavior;
2786         that.mBubbleMetadata = this.mBubbleMetadata;
2787         that.mAllowSystemGeneratedContextualActions = this.mAllowSystemGeneratedContextualActions;
2788 
2789         if (!heavy) {
2790             that.lightenPayload(); // will clean out extras
2791         }
2792     }
2793 
visitIconUri(@onNull Consumer<Uri> visitor, @Nullable Icon icon)2794     private static void visitIconUri(@NonNull Consumer<Uri> visitor, @Nullable Icon icon) {
2795         if (icon == null) return;
2796         final int iconType = icon.getType();
2797         if (iconType == TYPE_URI || iconType == TYPE_URI_ADAPTIVE_BITMAP) {
2798             visitor.accept(icon.getUri());
2799         }
2800     }
2801 
2802     /**
2803      * Note all {@link Uri} that are referenced internally, with the expectation
2804      * that Uri permission grants will need to be issued to ensure the recipient
2805      * of this object is able to render its contents.
2806      *
2807      * @hide
2808      */
visitUris(@onNull Consumer<Uri> visitor)2809     public void visitUris(@NonNull Consumer<Uri> visitor) {
2810         if (publicVersion != null) {
2811             publicVersion.visitUris(visitor);
2812         }
2813 
2814         visitor.accept(sound);
2815 
2816         if (tickerView != null) tickerView.visitUris(visitor);
2817         if (contentView != null) contentView.visitUris(visitor);
2818         if (bigContentView != null) bigContentView.visitUris(visitor);
2819         if (headsUpContentView != null) headsUpContentView.visitUris(visitor);
2820 
2821         visitIconUri(visitor, mSmallIcon);
2822         visitIconUri(visitor, mLargeIcon);
2823 
2824         if (actions != null) {
2825             for (Action action : actions) {
2826                 visitIconUri(visitor, action.getIcon());
2827             }
2828         }
2829 
2830         if (extras != null) {
2831             visitIconUri(visitor, extras.getParcelable(EXTRA_LARGE_ICON_BIG, Icon.class));
2832             visitIconUri(visitor, extras.getParcelable(EXTRA_PICTURE_ICON, Icon.class));
2833 
2834             // NOTE: The documentation of EXTRA_AUDIO_CONTENTS_URI explicitly says that it is a
2835             // String representation of a Uri, but the previous implementation (and unit test) of
2836             // this method has always treated it as a Uri object. Given the inconsistency,
2837             // supporting both going forward is the safest choice.
2838             Object audioContentsUri = extras.get(EXTRA_AUDIO_CONTENTS_URI);
2839             if (audioContentsUri instanceof Uri) {
2840                 visitor.accept((Uri) audioContentsUri);
2841             } else if (audioContentsUri instanceof String) {
2842                 visitor.accept(Uri.parse((String) audioContentsUri));
2843             }
2844 
2845             if (extras.containsKey(EXTRA_BACKGROUND_IMAGE_URI)) {
2846                 visitor.accept(Uri.parse(extras.getString(EXTRA_BACKGROUND_IMAGE_URI)));
2847             }
2848 
2849             ArrayList<Person> people = extras.getParcelableArrayList(EXTRA_PEOPLE_LIST);
2850             if (people != null && !people.isEmpty()) {
2851                 for (Person p : people) {
2852                     visitor.accept(p.getIconUri());
2853                 }
2854             }
2855 
2856             final Person person = extras.getParcelable(EXTRA_MESSAGING_PERSON, Person.class);
2857             if (person != null) {
2858                 visitor.accept(person.getIconUri());
2859             }
2860 
2861             final RemoteInputHistoryItem[] history = (RemoteInputHistoryItem[])
2862                     extras.getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS);
2863             if (history != null) {
2864                 for (int i = 0; i < history.length; i++) {
2865                     RemoteInputHistoryItem item = history[i];
2866                     if (item.getUri() != null) {
2867                         visitor.accept(item.getUri());
2868                     }
2869                 }
2870             }
2871         }
2872 
2873         if (isStyle(MessagingStyle.class) && extras != null) {
2874             final Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES);
2875             if (!ArrayUtils.isEmpty(messages)) {
2876                 for (MessagingStyle.Message message : MessagingStyle.Message
2877                         .getMessagesFromBundleArray(messages)) {
2878                     visitor.accept(message.getDataUri());
2879 
2880                     Person senderPerson = message.getSenderPerson();
2881                     if (senderPerson != null) {
2882                         visitor.accept(senderPerson.getIconUri());
2883                     }
2884                 }
2885             }
2886 
2887             final Parcelable[] historic = extras.getParcelableArray(EXTRA_HISTORIC_MESSAGES);
2888             if (!ArrayUtils.isEmpty(historic)) {
2889                 for (MessagingStyle.Message message : MessagingStyle.Message
2890                         .getMessagesFromBundleArray(historic)) {
2891                     visitor.accept(message.getDataUri());
2892 
2893                     Person senderPerson = message.getSenderPerson();
2894                     if (senderPerson != null) {
2895                         visitor.accept(senderPerson.getIconUri());
2896                     }
2897                 }
2898             }
2899 
2900             visitIconUri(visitor, extras.getParcelable(EXTRA_CONVERSATION_ICON));
2901         }
2902 
2903         if (isStyle(CallStyle.class) & extras != null) {
2904             Person callPerson = extras.getParcelable(EXTRA_CALL_PERSON);
2905             if (callPerson != null) {
2906                 visitor.accept(callPerson.getIconUri());
2907             }
2908             visitIconUri(visitor, extras.getParcelable(EXTRA_VERIFICATION_ICON));
2909         }
2910 
2911         if (mBubbleMetadata != null) {
2912             visitIconUri(visitor, mBubbleMetadata.getIcon());
2913         }
2914     }
2915 
2916     /**
2917      * Removes heavyweight parts of the Notification object for archival or for sending to
2918      * listeners when the full contents are not necessary.
2919      * @hide
2920      */
lightenPayload()2921     public final void lightenPayload() {
2922         tickerView = null;
2923         contentView = null;
2924         bigContentView = null;
2925         headsUpContentView = null;
2926         mLargeIcon = null;
2927         if (extras != null && !extras.isEmpty()) {
2928             final Set<String> keyset = extras.keySet();
2929             final int N = keyset.size();
2930             final String[] keys = keyset.toArray(new String[N]);
2931             for (int i=0; i<N; i++) {
2932                 final String key = keys[i];
2933                 if (TvExtender.EXTRA_TV_EXTENDER.equals(key)) {
2934                     continue;
2935                 }
2936                 final Object obj = extras.get(key);
2937                 if (obj != null &&
2938                     (  obj instanceof Parcelable
2939                     || obj instanceof Parcelable[]
2940                     || obj instanceof SparseArray
2941                     || obj instanceof ArrayList)) {
2942                     extras.remove(key);
2943                 }
2944             }
2945         }
2946     }
2947 
2948     /**
2949      * Make sure this CharSequence is safe to put into a bundle, which basically
2950      * means it had better not be some custom Parcelable implementation.
2951      * @hide
2952      */
safeCharSequence(CharSequence cs)2953     public static CharSequence safeCharSequence(CharSequence cs) {
2954         if (cs == null) return cs;
2955         if (cs.length() > MAX_CHARSEQUENCE_LENGTH) {
2956             cs = cs.subSequence(0, MAX_CHARSEQUENCE_LENGTH);
2957         }
2958         if (cs instanceof Parcelable) {
2959             Log.e(TAG, "warning: " + cs.getClass().getCanonicalName()
2960                     + " instance is a custom Parcelable and not allowed in Notification");
2961             return cs.toString();
2962         }
2963         return removeTextSizeSpans(cs);
2964     }
2965 
removeTextSizeSpans(CharSequence charSequence)2966     private static CharSequence removeTextSizeSpans(CharSequence charSequence) {
2967         if (charSequence instanceof Spanned) {
2968             Spanned ss = (Spanned) charSequence;
2969             Object[] spans = ss.getSpans(0, ss.length(), Object.class);
2970             SpannableStringBuilder builder = new SpannableStringBuilder(ss.toString());
2971             for (Object span : spans) {
2972                 Object resultSpan = span;
2973                 if (resultSpan instanceof CharacterStyle) {
2974                     resultSpan = ((CharacterStyle) span).getUnderlying();
2975                 }
2976                 if (resultSpan instanceof TextAppearanceSpan) {
2977                     TextAppearanceSpan originalSpan = (TextAppearanceSpan) resultSpan;
2978                     resultSpan = new TextAppearanceSpan(
2979                             originalSpan.getFamily(),
2980                             originalSpan.getTextStyle(),
2981                             -1,
2982                             originalSpan.getTextColor(),
2983                             originalSpan.getLinkTextColor());
2984                 } else if (resultSpan instanceof RelativeSizeSpan
2985                         || resultSpan instanceof AbsoluteSizeSpan) {
2986                     continue;
2987                 } else {
2988                     resultSpan = span;
2989                 }
2990                 builder.setSpan(resultSpan, ss.getSpanStart(span), ss.getSpanEnd(span),
2991                         ss.getSpanFlags(span));
2992             }
2993             return builder;
2994         }
2995         return charSequence;
2996     }
2997 
describeContents()2998     public int describeContents() {
2999         return 0;
3000     }
3001 
3002     /**
3003      * Flatten this notification into a parcel.
3004      */
writeToParcel(Parcel parcel, int flags)3005     public void writeToParcel(Parcel parcel, int flags) {
3006         // We need to mark all pending intents getting into the notification
3007         // system as being put there to later allow the notification ranker
3008         // to launch them and by doing so add the app to the battery saver white
3009         // list for a short period of time. The problem is that the system
3010         // cannot look into the extras as there may be parcelables there that
3011         // the platform does not know how to handle. To go around that we have
3012         // an explicit list of the pending intents in the extras bundle.
3013         final boolean collectPendingIntents = (allPendingIntents == null);
3014         if (collectPendingIntents) {
3015             PendingIntent.setOnMarshaledListener(
3016                     (PendingIntent intent, Parcel out, int outFlags) -> {
3017                 if (parcel == out) {
3018                     synchronized (this) {
3019                         if (allPendingIntents == null) {
3020                             allPendingIntents = new ArraySet<>();
3021                         }
3022                         allPendingIntents.add(intent);
3023                     }
3024                 }
3025             });
3026         }
3027         try {
3028             // IMPORTANT: Add marshaling code in writeToParcelImpl as we
3029             // want to intercept all pending events written to the parcel.
3030             writeToParcelImpl(parcel, flags);
3031             synchronized (this) {
3032                 // Must be written last!
3033                 parcel.writeArraySet(allPendingIntents);
3034             }
3035         } finally {
3036             if (collectPendingIntents) {
3037                 PendingIntent.setOnMarshaledListener(null);
3038             }
3039         }
3040     }
3041 
writeToParcelImpl(Parcel parcel, int flags)3042     private void writeToParcelImpl(Parcel parcel, int flags) {
3043         parcel.writeInt(1);
3044 
3045         parcel.writeStrongBinder(mAllowlistToken);
3046         parcel.writeLong(when);
3047         parcel.writeLong(creationTime);
3048         if (mSmallIcon == null && icon != 0) {
3049             // you snuck an icon in here without using the builder; let's try to keep it
3050             mSmallIcon = Icon.createWithResource("", icon);
3051         }
3052         if (mSmallIcon != null) {
3053             parcel.writeInt(1);
3054             mSmallIcon.writeToParcel(parcel, 0);
3055         } else {
3056             parcel.writeInt(0);
3057         }
3058         parcel.writeInt(number);
3059         if (contentIntent != null) {
3060             parcel.writeInt(1);
3061             contentIntent.writeToParcel(parcel, 0);
3062         } else {
3063             parcel.writeInt(0);
3064         }
3065         if (deleteIntent != null) {
3066             parcel.writeInt(1);
3067             deleteIntent.writeToParcel(parcel, 0);
3068         } else {
3069             parcel.writeInt(0);
3070         }
3071         if (tickerText != null) {
3072             parcel.writeInt(1);
3073             TextUtils.writeToParcel(tickerText, parcel, flags);
3074         } else {
3075             parcel.writeInt(0);
3076         }
3077         if (tickerView != null) {
3078             parcel.writeInt(1);
3079             tickerView.writeToParcel(parcel, 0);
3080         } else {
3081             parcel.writeInt(0);
3082         }
3083         if (contentView != null) {
3084             parcel.writeInt(1);
3085             contentView.writeToParcel(parcel, 0);
3086         } else {
3087             parcel.writeInt(0);
3088         }
3089         if (mLargeIcon == null && largeIcon != null) {
3090             // you snuck an icon in here without using the builder; let's try to keep it
3091             mLargeIcon = Icon.createWithBitmap(largeIcon);
3092         }
3093         if (mLargeIcon != null) {
3094             parcel.writeInt(1);
3095             mLargeIcon.writeToParcel(parcel, 0);
3096         } else {
3097             parcel.writeInt(0);
3098         }
3099 
3100         parcel.writeInt(defaults);
3101         parcel.writeInt(this.flags);
3102 
3103         if (sound != null) {
3104             parcel.writeInt(1);
3105             sound.writeToParcel(parcel, 0);
3106         } else {
3107             parcel.writeInt(0);
3108         }
3109         parcel.writeInt(audioStreamType);
3110 
3111         if (audioAttributes != null) {
3112             parcel.writeInt(1);
3113             audioAttributes.writeToParcel(parcel, 0);
3114         } else {
3115             parcel.writeInt(0);
3116         }
3117 
3118         parcel.writeLongArray(vibrate);
3119         parcel.writeInt(ledARGB);
3120         parcel.writeInt(ledOnMS);
3121         parcel.writeInt(ledOffMS);
3122         parcel.writeInt(iconLevel);
3123 
3124         if (fullScreenIntent != null) {
3125             parcel.writeInt(1);
3126             fullScreenIntent.writeToParcel(parcel, 0);
3127         } else {
3128             parcel.writeInt(0);
3129         }
3130 
3131         parcel.writeInt(priority);
3132 
3133         parcel.writeString8(category);
3134 
3135         parcel.writeString8(mGroupKey);
3136 
3137         parcel.writeString8(mSortKey);
3138 
3139         parcel.writeBundle(extras); // null ok
3140 
3141         parcel.writeTypedArray(actions, 0); // null ok
3142 
3143         if (bigContentView != null) {
3144             parcel.writeInt(1);
3145             bigContentView.writeToParcel(parcel, 0);
3146         } else {
3147             parcel.writeInt(0);
3148         }
3149 
3150         if (headsUpContentView != null) {
3151             parcel.writeInt(1);
3152             headsUpContentView.writeToParcel(parcel, 0);
3153         } else {
3154             parcel.writeInt(0);
3155         }
3156 
3157         parcel.writeInt(visibility);
3158 
3159         if (publicVersion != null) {
3160             parcel.writeInt(1);
3161             publicVersion.writeToParcel(parcel, 0);
3162         } else {
3163             parcel.writeInt(0);
3164         }
3165 
3166         parcel.writeInt(color);
3167 
3168         if (mChannelId != null) {
3169             parcel.writeInt(1);
3170             parcel.writeString8(mChannelId);
3171         } else {
3172             parcel.writeInt(0);
3173         }
3174         parcel.writeLong(mTimeout);
3175 
3176         if (mShortcutId != null) {
3177             parcel.writeInt(1);
3178             parcel.writeString8(mShortcutId);
3179         } else {
3180             parcel.writeInt(0);
3181         }
3182 
3183         if (mLocusId != null) {
3184             parcel.writeInt(1);
3185             mLocusId.writeToParcel(parcel, 0);
3186         } else {
3187             parcel.writeInt(0);
3188         }
3189 
3190         parcel.writeInt(mBadgeIcon);
3191 
3192         if (mSettingsText != null) {
3193             parcel.writeInt(1);
3194             TextUtils.writeToParcel(mSettingsText, parcel, flags);
3195         } else {
3196             parcel.writeInt(0);
3197         }
3198 
3199         parcel.writeInt(mGroupAlertBehavior);
3200 
3201         if (mBubbleMetadata != null) {
3202             parcel.writeInt(1);
3203             mBubbleMetadata.writeToParcel(parcel, 0);
3204         } else {
3205             parcel.writeInt(0);
3206         }
3207 
3208         parcel.writeBoolean(mAllowSystemGeneratedContextualActions);
3209 
3210         parcel.writeInt(mFgsDeferBehavior);
3211 
3212         // mUsesStandardHeader is not written because it should be recomputed in listeners
3213     }
3214 
3215     /**
3216      * Parcelable.Creator that instantiates Notification objects
3217      */
3218     public static final @android.annotation.NonNull Parcelable.Creator<Notification> CREATOR
3219             = new Parcelable.Creator<Notification>()
3220     {
3221         public Notification createFromParcel(Parcel parcel)
3222         {
3223             return new Notification(parcel);
3224         }
3225 
3226         public Notification[] newArray(int size)
3227         {
3228             return new Notification[size];
3229         }
3230     };
3231 
3232     /**
3233      * @hide
3234      */
areActionsVisiblyDifferent(Notification first, Notification second)3235     public static boolean areActionsVisiblyDifferent(Notification first, Notification second) {
3236         Notification.Action[] firstAs = first.actions;
3237         Notification.Action[] secondAs = second.actions;
3238         if (firstAs == null && secondAs != null || firstAs != null && secondAs == null) {
3239             return true;
3240         }
3241         if (firstAs != null && secondAs != null) {
3242             if (firstAs.length != secondAs.length) {
3243                 return true;
3244             }
3245             for (int i = 0; i < firstAs.length; i++) {
3246                 if (!Objects.equals(String.valueOf(firstAs[i].title),
3247                         String.valueOf(secondAs[i].title))) {
3248                     return true;
3249                 }
3250                 RemoteInput[] firstRs = firstAs[i].getRemoteInputs();
3251                 RemoteInput[] secondRs = secondAs[i].getRemoteInputs();
3252                 if (firstRs == null) {
3253                     firstRs = new RemoteInput[0];
3254                 }
3255                 if (secondRs == null) {
3256                     secondRs = new RemoteInput[0];
3257                 }
3258                 if (firstRs.length != secondRs.length) {
3259                     return true;
3260                 }
3261                 for (int j = 0; j < firstRs.length; j++) {
3262                     if (!Objects.equals(String.valueOf(firstRs[j].getLabel()),
3263                             String.valueOf(secondRs[j].getLabel()))) {
3264                         return true;
3265                     }
3266                 }
3267             }
3268         }
3269         return false;
3270     }
3271 
3272     /**
3273      * @hide
3274      */
areStyledNotificationsVisiblyDifferent(Builder first, Builder second)3275     public static boolean areStyledNotificationsVisiblyDifferent(Builder first, Builder second) {
3276         if (first.getStyle() == null) {
3277             return second.getStyle() != null;
3278         }
3279         if (second.getStyle() == null) {
3280             return true;
3281         }
3282         return first.getStyle().areNotificationsVisiblyDifferent(second.getStyle());
3283     }
3284 
3285     /**
3286      * @hide
3287      */
areRemoteViewsChanged(Builder first, Builder second)3288     public static boolean areRemoteViewsChanged(Builder first, Builder second) {
3289         if (!Objects.equals(first.usesStandardHeader(), second.usesStandardHeader())) {
3290             return true;
3291         }
3292 
3293         if (areRemoteViewsChanged(first.mN.contentView, second.mN.contentView)) {
3294             return true;
3295         }
3296         if (areRemoteViewsChanged(first.mN.bigContentView, second.mN.bigContentView)) {
3297             return true;
3298         }
3299         if (areRemoteViewsChanged(first.mN.headsUpContentView, second.mN.headsUpContentView)) {
3300             return true;
3301         }
3302 
3303         return false;
3304     }
3305 
areRemoteViewsChanged(RemoteViews first, RemoteViews second)3306     private static boolean areRemoteViewsChanged(RemoteViews first, RemoteViews second) {
3307         if (first == null && second == null) {
3308             return false;
3309         }
3310         if (first == null && second != null || first != null && second == null) {
3311             return true;
3312         }
3313 
3314         if (!Objects.equals(first.getLayoutId(), second.getLayoutId())) {
3315             return true;
3316         }
3317 
3318         if (!Objects.equals(first.getSequenceNumber(), second.getSequenceNumber())) {
3319             return true;
3320         }
3321 
3322         return false;
3323     }
3324 
3325     /**
3326      * Parcelling creates multiple copies of objects in {@code extras}. Fix them.
3327      * <p>
3328      * For backwards compatibility {@code extras} holds some references to "real" member data such
3329      * as {@link getLargeIcon()} which is mirrored by {@link #EXTRA_LARGE_ICON}. This is mostly
3330      * fine as long as the object stays in one process.
3331      * <p>
3332      * However, once the notification goes into a parcel each reference gets marshalled separately,
3333      * wasting memory. Especially with large images on Auto and TV, this is worth fixing.
3334      */
fixDuplicateExtras()3335     private void fixDuplicateExtras() {
3336         if (extras != null) {
3337             fixDuplicateExtra(mLargeIcon, EXTRA_LARGE_ICON);
3338         }
3339     }
3340 
3341     /**
3342      * If we find an extra that's exactly the same as one of the "real" fields but refers to a
3343      * separate object, replace it with the field's version to avoid holding duplicate copies.
3344      */
fixDuplicateExtra(@ullable Parcelable original, @NonNull String extraName)3345     private void fixDuplicateExtra(@Nullable Parcelable original, @NonNull String extraName) {
3346         if (original != null && extras.getParcelable(extraName) != null) {
3347             extras.putParcelable(extraName, original);
3348         }
3349     }
3350 
3351     /**
3352      * Sets the {@link #contentView} field to be a view with the standard "Latest Event"
3353      * layout.
3354      *
3355      * <p>Uses the {@link #icon} and {@link #when} fields to set the icon and time fields
3356      * in the view.</p>
3357      * @param context       The context for your application / activity.
3358      * @param contentTitle The title that goes in the expanded entry.
3359      * @param contentText  The text that goes in the expanded entry.
3360      * @param contentIntent The intent to launch when the user clicks the expanded notification.
3361      * If this is an activity, it must include the
3362      * {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK} flag, which requires
3363      * that you take care of task management as described in the
3364      * <a href="{@docRoot}guide/topics/fundamentals/tasks-and-back-stack.html">Tasks and Back
3365      * Stack</a> document.
3366      *
3367      * @deprecated Use {@link Builder} instead.
3368      * @removed
3369      */
3370     @Deprecated
setLatestEventInfo(Context context, CharSequence contentTitle, CharSequence contentText, PendingIntent contentIntent)3371     public void setLatestEventInfo(Context context,
3372             CharSequence contentTitle, CharSequence contentText, PendingIntent contentIntent) {
3373         if (context.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1){
3374             Log.e(TAG, "setLatestEventInfo() is deprecated and you should feel deprecated.",
3375                     new Throwable());
3376         }
3377 
3378         if (context.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N) {
3379             extras.putBoolean(EXTRA_SHOW_WHEN, true);
3380         }
3381 
3382         // ensure that any information already set directly is preserved
3383         final Notification.Builder builder = new Notification.Builder(context, this);
3384 
3385         // now apply the latestEventInfo fields
3386         if (contentTitle != null) {
3387             builder.setContentTitle(contentTitle);
3388         }
3389         if (contentText != null) {
3390             builder.setContentText(contentText);
3391         }
3392         builder.setContentIntent(contentIntent);
3393 
3394         builder.build(); // callers expect this notification to be ready to use
3395     }
3396 
3397     /**
3398      * Sets the token used for background operations for the pending intents associated with this
3399      * notification.
3400      *
3401      * This token is automatically set during deserialization for you, you usually won't need to
3402      * call this unless you want to change the existing token, if any.
3403      *
3404      * @hide
3405      */
setAllowlistToken(@ullable IBinder token)3406     public void setAllowlistToken(@Nullable IBinder token) {
3407         mAllowlistToken = token;
3408     }
3409 
3410     /**
3411      * @hide
3412      */
addFieldsFromContext(Context context, Notification notification)3413     public static void addFieldsFromContext(Context context, Notification notification) {
3414         addFieldsFromContext(context.getApplicationInfo(), notification);
3415     }
3416 
3417     /**
3418      * @hide
3419      */
addFieldsFromContext(ApplicationInfo ai, Notification notification)3420     public static void addFieldsFromContext(ApplicationInfo ai, Notification notification) {
3421         notification.extras.putParcelable(EXTRA_BUILDER_APPLICATION_INFO, ai);
3422     }
3423 
3424     /**
3425      * @hide
3426      */
dumpDebug(ProtoOutputStream proto, long fieldId)3427     public void dumpDebug(ProtoOutputStream proto, long fieldId) {
3428         long token = proto.start(fieldId);
3429         proto.write(NotificationProto.CHANNEL_ID, getChannelId());
3430         proto.write(NotificationProto.HAS_TICKER_TEXT, this.tickerText != null);
3431         proto.write(NotificationProto.FLAGS, this.flags);
3432         proto.write(NotificationProto.COLOR, this.color);
3433         proto.write(NotificationProto.CATEGORY, this.category);
3434         proto.write(NotificationProto.GROUP_KEY, this.mGroupKey);
3435         proto.write(NotificationProto.SORT_KEY, this.mSortKey);
3436         if (this.actions != null) {
3437             proto.write(NotificationProto.ACTION_LENGTH, this.actions.length);
3438         }
3439         if (this.visibility >= VISIBILITY_SECRET && this.visibility <= VISIBILITY_PUBLIC) {
3440             proto.write(NotificationProto.VISIBILITY, this.visibility);
3441         }
3442         if (publicVersion != null) {
3443             publicVersion.dumpDebug(proto, NotificationProto.PUBLIC_VERSION);
3444         }
3445         proto.end(token);
3446     }
3447 
3448     @Override
toString()3449     public String toString() {
3450         StringBuilder sb = new StringBuilder();
3451         sb.append("Notification(channel=");
3452         sb.append(getChannelId());
3453         sb.append(" shortcut=");
3454         sb.append(getShortcutId());
3455         sb.append(" contentView=");
3456         if (contentView != null) {
3457             sb.append(contentView.getPackage());
3458             sb.append("/0x");
3459             sb.append(Integer.toHexString(contentView.getLayoutId()));
3460         } else {
3461             sb.append("null");
3462         }
3463         sb.append(" vibrate=");
3464         if ((this.defaults & DEFAULT_VIBRATE) != 0) {
3465             sb.append("default");
3466         } else if (this.vibrate != null) {
3467             int N = this.vibrate.length-1;
3468             sb.append("[");
3469             for (int i=0; i<N; i++) {
3470                 sb.append(this.vibrate[i]);
3471                 sb.append(',');
3472             }
3473             if (N != -1) {
3474                 sb.append(this.vibrate[N]);
3475             }
3476             sb.append("]");
3477         } else {
3478             sb.append("null");
3479         }
3480         sb.append(" sound=");
3481         if ((this.defaults & DEFAULT_SOUND) != 0) {
3482             sb.append("default");
3483         } else if (this.sound != null) {
3484             sb.append(this.sound.toString());
3485         } else {
3486             sb.append("null");
3487         }
3488         if (this.tickerText != null) {
3489             sb.append(" tick");
3490         }
3491         sb.append(" defaults=0x");
3492         sb.append(Integer.toHexString(this.defaults));
3493         sb.append(" flags=0x");
3494         sb.append(Integer.toHexString(this.flags));
3495         sb.append(String.format(" color=0x%08x", this.color));
3496         if (this.category != null) {
3497             sb.append(" category=");
3498             sb.append(this.category);
3499         }
3500         if (this.mGroupKey != null) {
3501             sb.append(" groupKey=");
3502             sb.append(this.mGroupKey);
3503         }
3504         if (this.mSortKey != null) {
3505             sb.append(" sortKey=");
3506             sb.append(this.mSortKey);
3507         }
3508         if (actions != null) {
3509             sb.append(" actions=");
3510             sb.append(actions.length);
3511         }
3512         sb.append(" vis=");
3513         sb.append(visibilityToString(this.visibility));
3514         if (this.publicVersion != null) {
3515             sb.append(" publicVersion=");
3516             sb.append(publicVersion.toString());
3517         }
3518         if (this.mLocusId != null) {
3519             sb.append(" locusId=");
3520             sb.append(this.mLocusId); // LocusId.toString() is PII safe.
3521         }
3522         sb.append(")");
3523         return sb.toString();
3524     }
3525 
3526     /**
3527      * {@hide}
3528      */
visibilityToString(int vis)3529     public static String visibilityToString(int vis) {
3530         switch (vis) {
3531             case VISIBILITY_PRIVATE:
3532                 return "PRIVATE";
3533             case VISIBILITY_PUBLIC:
3534                 return "PUBLIC";
3535             case VISIBILITY_SECRET:
3536                 return "SECRET";
3537             default:
3538                 return "UNKNOWN(" + String.valueOf(vis) + ")";
3539         }
3540     }
3541 
3542     /**
3543      * {@hide}
3544      */
priorityToString(@riority int pri)3545     public static String priorityToString(@Priority int pri) {
3546         switch (pri) {
3547             case PRIORITY_MIN:
3548                 return "MIN";
3549             case PRIORITY_LOW:
3550                 return "LOW";
3551             case PRIORITY_DEFAULT:
3552                 return "DEFAULT";
3553             case PRIORITY_HIGH:
3554                 return "HIGH";
3555             case PRIORITY_MAX:
3556                 return "MAX";
3557             default:
3558                 return "UNKNOWN(" + String.valueOf(pri) + ")";
3559         }
3560     }
3561 
3562     /**
3563      * @hide
3564      */
hasCompletedProgress()3565     public boolean hasCompletedProgress() {
3566         // not a progress notification; can't be complete
3567         if (!extras.containsKey(EXTRA_PROGRESS)
3568                 || !extras.containsKey(EXTRA_PROGRESS_MAX)) {
3569             return false;
3570         }
3571         // many apps use max 0 for 'indeterminate'; not complete
3572         if (extras.getInt(EXTRA_PROGRESS_MAX) == 0) {
3573             return false;
3574         }
3575         return extras.getInt(EXTRA_PROGRESS) == extras.getInt(EXTRA_PROGRESS_MAX);
3576     }
3577 
3578     /** @removed */
3579     @Deprecated
getChannel()3580     public String getChannel() {
3581         return mChannelId;
3582     }
3583 
3584     /**
3585      * Returns the id of the channel this notification posts to.
3586      */
getChannelId()3587     public String getChannelId() {
3588         return mChannelId;
3589     }
3590 
3591     /** @removed */
3592     @Deprecated
getTimeout()3593     public long getTimeout() {
3594         return mTimeout;
3595     }
3596 
3597     /**
3598      * Returns the duration from posting after which this notification should be canceled by the
3599      * system, if it's not canceled already.
3600      */
getTimeoutAfter()3601     public long getTimeoutAfter() {
3602         return mTimeout;
3603     }
3604 
3605     /**
3606      * Returns what icon should be shown for this notification if it is being displayed in a
3607      * Launcher that supports badging. Will be one of {@link #BADGE_ICON_NONE},
3608      * {@link #BADGE_ICON_SMALL}, or {@link #BADGE_ICON_LARGE}.
3609      */
getBadgeIconType()3610     public int getBadgeIconType() {
3611         return mBadgeIcon;
3612     }
3613 
3614     /**
3615      * Returns the {@link ShortcutInfo#getId() id} that this notification supersedes, if any.
3616      *
3617      * <p>Used by some Launchers that display notification content to hide shortcuts that duplicate
3618      * notifications.
3619      */
getShortcutId()3620     public String getShortcutId() {
3621         return mShortcutId;
3622     }
3623 
3624     /**
3625      * Gets the {@link LocusId} associated with this notification.
3626      *
3627      * <p>Used by the device's intelligence services to correlate objects (such as
3628      * {@link ShortcutInfo} and {@link ContentCaptureContext}) that are correlated.
3629      */
3630     @Nullable
getLocusId()3631     public LocusId getLocusId() {
3632         return mLocusId;
3633     }
3634 
3635     /**
3636      * Returns the settings text provided to {@link Builder#setSettingsText(CharSequence)}.
3637      */
getSettingsText()3638     public CharSequence getSettingsText() {
3639         return mSettingsText;
3640     }
3641 
3642     /**
3643      * Returns which type of notifications in a group are responsible for audibly alerting the
3644      * user. See {@link #GROUP_ALERT_ALL}, {@link #GROUP_ALERT_CHILDREN},
3645      * {@link #GROUP_ALERT_SUMMARY}.
3646      */
getGroupAlertBehavior()3647     public @GroupAlertBehavior int getGroupAlertBehavior() {
3648         return mGroupAlertBehavior;
3649     }
3650 
3651     /**
3652      * Returns the bubble metadata that will be used to display app content in a floating window
3653      * over the existing foreground activity.
3654      */
3655     @Nullable
getBubbleMetadata()3656     public BubbleMetadata getBubbleMetadata() {
3657         return mBubbleMetadata;
3658     }
3659 
3660     /**
3661      * Sets the {@link BubbleMetadata} for this notification.
3662      * @hide
3663      */
setBubbleMetadata(BubbleMetadata data)3664     public void setBubbleMetadata(BubbleMetadata data) {
3665         mBubbleMetadata = data;
3666     }
3667 
3668     /**
3669      * Returns whether the platform is allowed (by the app developer) to generate contextual actions
3670      * for this notification.
3671      */
getAllowSystemGeneratedContextualActions()3672     public boolean getAllowSystemGeneratedContextualActions() {
3673         return mAllowSystemGeneratedContextualActions;
3674     }
3675 
3676     /**
3677      * The small icon representing this notification in the status bar and content view.
3678      *
3679      * @return the small icon representing this notification.
3680      *
3681      * @see Builder#getSmallIcon()
3682      * @see Builder#setSmallIcon(Icon)
3683      */
getSmallIcon()3684     public Icon getSmallIcon() {
3685         return mSmallIcon;
3686     }
3687 
3688     /**
3689      * Used when notifying to clean up legacy small icons.
3690      * @hide
3691      */
3692     @UnsupportedAppUsage
setSmallIcon(Icon icon)3693     public void setSmallIcon(Icon icon) {
3694         mSmallIcon = icon;
3695     }
3696 
3697     /**
3698      * The large icon shown in this notification's content view.
3699      * @see Builder#getLargeIcon()
3700      * @see Builder#setLargeIcon(Icon)
3701      */
getLargeIcon()3702     public Icon getLargeIcon() {
3703         return mLargeIcon;
3704     }
3705 
3706     /**
3707      * @hide
3708      */
3709     @UnsupportedAppUsage
isGroupSummary()3710     public boolean isGroupSummary() {
3711         return mGroupKey != null && (flags & FLAG_GROUP_SUMMARY) != 0;
3712     }
3713 
3714     /**
3715      * @hide
3716      */
3717     @UnsupportedAppUsage
isGroupChild()3718     public boolean isGroupChild() {
3719         return mGroupKey != null && (flags & FLAG_GROUP_SUMMARY) == 0;
3720     }
3721 
3722     /**
3723      * @hide
3724      */
suppressAlertingDueToGrouping()3725     public boolean suppressAlertingDueToGrouping() {
3726         if (isGroupSummary()
3727                 && getGroupAlertBehavior() == Notification.GROUP_ALERT_CHILDREN) {
3728             return true;
3729         } else if (isGroupChild()
3730                 && getGroupAlertBehavior() == Notification.GROUP_ALERT_SUMMARY) {
3731             return true;
3732         }
3733         return false;
3734     }
3735 
3736 
3737     /**
3738      * Finds and returns a remote input and its corresponding action.
3739      *
3740      * @param requiresFreeform requires the remoteinput to allow freeform or not.
3741      * @return the result pair, {@code null} if no result is found.
3742      */
3743     @Nullable
findRemoteInputActionPair(boolean requiresFreeform)3744     public Pair<RemoteInput, Action> findRemoteInputActionPair(boolean requiresFreeform) {
3745         if (actions == null) {
3746             return null;
3747         }
3748         for (Notification.Action action : actions) {
3749             if (action.getRemoteInputs() == null) {
3750                 continue;
3751             }
3752             RemoteInput resultRemoteInput = null;
3753             for (RemoteInput remoteInput : action.getRemoteInputs()) {
3754                 if (remoteInput.getAllowFreeFormInput() || !requiresFreeform) {
3755                     resultRemoteInput = remoteInput;
3756                 }
3757             }
3758             if (resultRemoteInput != null) {
3759                 return Pair.create(resultRemoteInput, action);
3760             }
3761         }
3762         return null;
3763     }
3764 
3765     /**
3766      * Returns the actions that are contextual (that is, suggested because of the content of the
3767      * notification) out of the actions in this notification.
3768      */
getContextualActions()3769     public @NonNull List<Notification.Action> getContextualActions() {
3770         if (actions == null) return Collections.emptyList();
3771 
3772         List<Notification.Action> contextualActions = new ArrayList<>();
3773         for (Notification.Action action : actions) {
3774             if (action.isContextual()) {
3775                 contextualActions.add(action);
3776             }
3777         }
3778         return contextualActions;
3779     }
3780 
3781     /**
3782      * Builder class for {@link Notification} objects.
3783      *
3784      * Provides a convenient way to set the various fields of a {@link Notification} and generate
3785      * content views using the platform's notification layout template. If your app supports
3786      * versions of Android as old as API level 4, you can instead use
3787      * {@link androidx.core.app.NotificationCompat.Builder NotificationCompat.Builder},
3788      * available in the <a href="{@docRoot}tools/extras/support-library.html">Android Support
3789      * library</a>.
3790      *
3791      * <p>Example:
3792      *
3793      * <pre class="prettyprint">
3794      * Notification noti = new Notification.Builder(mContext)
3795      *         .setContentTitle(&quot;New mail from &quot; + sender.toString())
3796      *         .setContentText(subject)
3797      *         .setSmallIcon(R.drawable.new_mail)
3798      *         .setLargeIcon(aBitmap)
3799      *         .build();
3800      * </pre>
3801      */
3802     public static class Builder {
3803         /**
3804          * @hide
3805          */
3806         public static final String EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT =
3807                 "android.rebuild.contentViewActionCount";
3808         /**
3809          * @hide
3810          */
3811         public static final String EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT
3812                 = "android.rebuild.bigViewActionCount";
3813         /**
3814          * @hide
3815          */
3816         public static final String EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT
3817                 = "android.rebuild.hudViewActionCount";
3818 
3819         private static final boolean USE_ONLY_TITLE_IN_LOW_PRIORITY_SUMMARY =
3820                 SystemProperties.getBoolean("notifications.only_title", true);
3821 
3822         /**
3823          * The lightness difference that has to be added to the primary text color to obtain the
3824          * secondary text color when the background is light.
3825          */
3826         private static final int LIGHTNESS_TEXT_DIFFERENCE_LIGHT = 20;
3827 
3828         /**
3829          * The lightness difference that has to be added to the primary text color to obtain the
3830          * secondary text color when the background is dark.
3831          * A bit less then the above value, since it looks better on dark backgrounds.
3832          */
3833         private static final int LIGHTNESS_TEXT_DIFFERENCE_DARK = -10;
3834 
3835         private Context mContext;
3836         private Notification mN;
3837         private Bundle mUserExtras = new Bundle();
3838         private Style mStyle;
3839         @UnsupportedAppUsage
3840         private ArrayList<Action> mActions = new ArrayList<>(MAX_ACTION_BUTTONS);
3841         private ArrayList<Person> mPersonList = new ArrayList<>();
3842         private ContrastColorUtil mColorUtil;
3843         private boolean mIsLegacy;
3844         private boolean mIsLegacyInitialized;
3845 
3846         /**
3847          * Caches an instance of StandardTemplateParams. Note that this may have been used before,
3848          * so make sure to call {@link StandardTemplateParams#reset()} before using it.
3849          */
3850         StandardTemplateParams mParams = new StandardTemplateParams();
3851         Colors mColors = new Colors();
3852 
3853         private boolean mTintActionButtons;
3854         private boolean mInNightMode;
3855 
3856         /**
3857          * Constructs a new Builder with the defaults:
3858          *
3859          * @param context
3860          *            A {@link Context} that will be used by the Builder to construct the
3861          *            RemoteViews. The Context will not be held past the lifetime of this Builder
3862          *            object.
3863          * @param channelId
3864          *            The constructed Notification will be posted on this
3865          *            {@link NotificationChannel}. To use a NotificationChannel, it must first be
3866          *            created using {@link NotificationManager#createNotificationChannel}.
3867          */
Builder(Context context, String channelId)3868         public Builder(Context context, String channelId) {
3869             this(context, (Notification) null);
3870             mN.mChannelId = channelId;
3871         }
3872 
3873         /**
3874          * @deprecated use {@link #Builder(Context, String)}
3875          * instead. All posted Notifications must specify a NotificationChannel Id.
3876          */
3877         @Deprecated
Builder(Context context)3878         public Builder(Context context) {
3879             this(context, (Notification) null);
3880         }
3881 
3882         /**
3883          * @hide
3884          */
Builder(Context context, Notification toAdopt)3885         public Builder(Context context, Notification toAdopt) {
3886             mContext = context;
3887             Resources res = mContext.getResources();
3888             mTintActionButtons = res.getBoolean(R.bool.config_tintNotificationActionButtons);
3889 
3890             if (res.getBoolean(R.bool.config_enableNightMode)) {
3891                 Configuration currentConfig = res.getConfiguration();
3892                 mInNightMode = (currentConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK)
3893                         == Configuration.UI_MODE_NIGHT_YES;
3894             }
3895 
3896             if (toAdopt == null) {
3897                 mN = new Notification();
3898                 if (context.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N) {
3899                     mN.extras.putBoolean(EXTRA_SHOW_WHEN, true);
3900                 }
3901                 mN.priority = PRIORITY_DEFAULT;
3902                 mN.visibility = VISIBILITY_PRIVATE;
3903             } else {
3904                 mN = toAdopt;
3905                 if (mN.actions != null) {
3906                     Collections.addAll(mActions, mN.actions);
3907                 }
3908 
3909                 if (mN.extras.containsKey(EXTRA_PEOPLE_LIST)) {
3910                     ArrayList<Person> people = mN.extras.getParcelableArrayList(EXTRA_PEOPLE_LIST);
3911                     mPersonList.addAll(people);
3912                 }
3913 
3914                 if (mN.getSmallIcon() == null && mN.icon != 0) {
3915                     setSmallIcon(mN.icon);
3916                 }
3917 
3918                 if (mN.getLargeIcon() == null && mN.largeIcon != null) {
3919                     setLargeIcon(mN.largeIcon);
3920                 }
3921 
3922                 String templateClass = mN.extras.getString(EXTRA_TEMPLATE);
3923                 if (!TextUtils.isEmpty(templateClass)) {
3924                     final Class<? extends Style> styleClass
3925                             = getNotificationStyleClass(templateClass);
3926                     if (styleClass == null) {
3927                         Log.d(TAG, "Unknown style class: " + templateClass);
3928                     } else {
3929                         try {
3930                             final Constructor<? extends Style> ctor =
3931                                     styleClass.getDeclaredConstructor();
3932                             ctor.setAccessible(true);
3933                             final Style style = ctor.newInstance();
3934                             style.restoreFromExtras(mN.extras);
3935 
3936                             if (style != null) {
3937                                 setStyle(style);
3938                             }
3939                         } catch (Throwable t) {
3940                             Log.e(TAG, "Could not create Style", t);
3941                         }
3942                     }
3943                 }
3944             }
3945         }
3946 
getColorUtil()3947         private ContrastColorUtil getColorUtil() {
3948             if (mColorUtil == null) {
3949                 mColorUtil = ContrastColorUtil.getInstance(mContext);
3950             }
3951             return mColorUtil;
3952         }
3953 
3954         /**
3955          * From Android 11, messaging notifications (those that use {@link MessagingStyle}) that
3956          * use this method to link to a published long-lived sharing shortcut may appear in a
3957          * dedicated Conversation section of the shade and may show configuration options that
3958          * are unique to conversations. This behavior should be reserved for person to person(s)
3959          * conversations where there is a likely social obligation for an individual to respond.
3960          * <p>
3961          * For example, the following are some examples of notifications that belong in the
3962          * conversation space:
3963          * <ul>
3964          * <li>1:1 conversations between two individuals</li>
3965          * <li>Group conversations between individuals where everyone can contribute</li>
3966          * </ul>
3967          * And the following are some examples of notifications that do not belong in the
3968          * conversation space:
3969          * <ul>
3970          * <li>Advertisements from a bot (even if personal and contextualized)</li>
3971          * <li>Engagement notifications from a bot</li>
3972          * <li>Directional conversations where there is an active speaker and many passive
3973          * individuals</li>
3974          * <li>Stream / posting updates from other individuals</li>
3975          * <li>Email, document comments, or other conversation types that are not real-time</li>
3976          * </ul>
3977          * </p>
3978          *
3979          * <p>
3980          * Additionally, this method can be used for all types of notifications to mark this
3981          * notification as duplicative of a Launcher shortcut. Launchers that show badges or
3982          * notification content may then suppress the shortcut in favor of the content of this
3983          * notification.
3984          * <p>
3985          * If this notification has {@link BubbleMetadata} attached that was created with
3986          * a shortcutId a check will be performed to ensure the shortcutId supplied to bubble
3987          * metadata matches the shortcutId set here, if one was set. If the shortcutId's were
3988          * specified but do not match, an exception is thrown.
3989          *
3990          * @param shortcutId the {@link ShortcutInfo#getId() id} of the shortcut this notification
3991          *                   is linked to
3992          *
3993          * @see BubbleMetadata.Builder#Builder(String)
3994          */
3995         @NonNull
setShortcutId(String shortcutId)3996         public Builder setShortcutId(String shortcutId) {
3997             mN.mShortcutId = shortcutId;
3998             return this;
3999         }
4000 
4001         /**
4002          * Sets the {@link LocusId} associated with this notification.
4003          *
4004          * <p>This method should be called when the {@link LocusId} is used in other places (such
4005          * as {@link ShortcutInfo} and {@link ContentCaptureContext}) so the device's intelligence
4006          * services can correlate them.
4007          */
4008         @NonNull
setLocusId(@ullable LocusId locusId)4009         public Builder setLocusId(@Nullable LocusId locusId) {
4010             mN.mLocusId = locusId;
4011             return this;
4012         }
4013 
4014         /**
4015          * Sets which icon to display as a badge for this notification.
4016          *
4017          * Must be one of {@link #BADGE_ICON_NONE}, {@link #BADGE_ICON_SMALL},
4018          * {@link #BADGE_ICON_LARGE}.
4019          *
4020          * Note: This value might be ignored, for launchers that don't support badge icons.
4021          */
4022         @NonNull
setBadgeIconType(int icon)4023         public Builder setBadgeIconType(int icon) {
4024             mN.mBadgeIcon = icon;
4025             return this;
4026         }
4027 
4028         /**
4029          * Sets the group alert behavior for this notification. Use this method to mute this
4030          * notification if alerts for this notification's group should be handled by a different
4031          * notification. This is only applicable for notifications that belong to a
4032          * {@link #setGroup(String) group}. This must be called on all notifications you want to
4033          * mute. For example, if you want only the summary of your group to make noise, all
4034          * children in the group should have the group alert behavior {@link #GROUP_ALERT_SUMMARY}.
4035          *
4036          * <p> The default value is {@link #GROUP_ALERT_ALL}.</p>
4037          */
4038         @NonNull
setGroupAlertBehavior(@roupAlertBehavior int groupAlertBehavior)4039         public Builder setGroupAlertBehavior(@GroupAlertBehavior int groupAlertBehavior) {
4040             mN.mGroupAlertBehavior = groupAlertBehavior;
4041             return this;
4042         }
4043 
4044         /**
4045          * Sets the {@link BubbleMetadata} that will be used to display app content in a floating
4046          * window over the existing foreground activity.
4047          *
4048          * <p>This data will be ignored unless the notification is posted to a channel that
4049          * allows {@link NotificationChannel#canBubble() bubbles}.</p>
4050          *
4051          * <p>Notifications allowed to bubble that have valid bubble metadata will display in
4052          * collapsed state outside of the notification shade on unlocked devices. When a user
4053          * interacts with the collapsed state, the bubble intent will be invoked and displayed.</p>
4054          */
4055         @NonNull
setBubbleMetadata(@ullable BubbleMetadata data)4056         public Builder setBubbleMetadata(@Nullable BubbleMetadata data) {
4057             mN.mBubbleMetadata = data;
4058             return this;
4059         }
4060 
4061         /** @removed */
4062         @Deprecated
setChannel(String channelId)4063         public Builder setChannel(String channelId) {
4064             mN.mChannelId = channelId;
4065             return this;
4066         }
4067 
4068         /**
4069          * Specifies the channel the notification should be delivered on.
4070          */
4071         @NonNull
setChannelId(String channelId)4072         public Builder setChannelId(String channelId) {
4073             mN.mChannelId = channelId;
4074             return this;
4075         }
4076 
4077         /** @removed */
4078         @Deprecated
setTimeout(long durationMs)4079         public Builder setTimeout(long durationMs) {
4080             mN.mTimeout = durationMs;
4081             return this;
4082         }
4083 
4084         /**
4085          * Specifies a duration in milliseconds after which this notification should be canceled,
4086          * if it is not already canceled.
4087          */
4088         @NonNull
setTimeoutAfter(long durationMs)4089         public Builder setTimeoutAfter(long durationMs) {
4090             mN.mTimeout = durationMs;
4091             return this;
4092         }
4093 
4094         /**
4095          * Add a timestamp pertaining to the notification (usually the time the event occurred).
4096          *
4097          * For apps targeting {@link android.os.Build.VERSION_CODES#N} and above, this time is not
4098          * shown anymore by default and must be opted into by using
4099          * {@link android.app.Notification.Builder#setShowWhen(boolean)}
4100          *
4101          * @see Notification#when
4102          */
4103         @NonNull
setWhen(long when)4104         public Builder setWhen(long when) {
4105             mN.when = when;
4106             return this;
4107         }
4108 
4109         /**
4110          * Control whether the timestamp set with {@link #setWhen(long) setWhen} is shown
4111          * in the content view.
4112          * For apps targeting {@link android.os.Build.VERSION_CODES#N} and above, this defaults to
4113          * {@code false}. For earlier apps, the default is {@code true}.
4114          */
4115         @NonNull
setShowWhen(boolean show)4116         public Builder setShowWhen(boolean show) {
4117             mN.extras.putBoolean(EXTRA_SHOW_WHEN, show);
4118             return this;
4119         }
4120 
4121         /**
4122          * Show the {@link Notification#when} field as a stopwatch.
4123          *
4124          * Instead of presenting <code>when</code> as a timestamp, the notification will show an
4125          * automatically updating display of the minutes and seconds since <code>when</code>.
4126          *
4127          * Useful when showing an elapsed time (like an ongoing phone call).
4128          *
4129          * The counter can also be set to count down to <code>when</code> when using
4130          * {@link #setChronometerCountDown(boolean)}.
4131          *
4132          * @see android.widget.Chronometer
4133          * @see Notification#when
4134          * @see #setChronometerCountDown(boolean)
4135          */
4136         @NonNull
setUsesChronometer(boolean b)4137         public Builder setUsesChronometer(boolean b) {
4138             mN.extras.putBoolean(EXTRA_SHOW_CHRONOMETER, b);
4139             return this;
4140         }
4141 
4142         /**
4143          * Sets the Chronometer to count down instead of counting up.
4144          *
4145          * <p>This is only relevant if {@link #setUsesChronometer(boolean)} has been set to true.
4146          * If it isn't set the chronometer will count up.
4147          *
4148          * @see #setUsesChronometer(boolean)
4149          */
4150         @NonNull
setChronometerCountDown(boolean countDown)4151         public Builder setChronometerCountDown(boolean countDown) {
4152             mN.extras.putBoolean(EXTRA_CHRONOMETER_COUNT_DOWN, countDown);
4153             return this;
4154         }
4155 
4156         /**
4157          * Set the small icon resource, which will be used to represent the notification in the
4158          * status bar.
4159          *
4160 
4161          * The platform template for the expanded view will draw this icon in the left, unless a
4162          * {@link #setLargeIcon(Bitmap) large icon} has also been specified, in which case the small
4163          * icon will be moved to the right-hand side.
4164          *
4165 
4166          * @param icon
4167          *            A resource ID in the application's package of the drawable to use.
4168          * @see Notification#icon
4169          */
4170         @NonNull
setSmallIcon(@rawableRes int icon)4171         public Builder setSmallIcon(@DrawableRes int icon) {
4172             return setSmallIcon(icon != 0
4173                     ? Icon.createWithResource(mContext, icon)
4174                     : null);
4175         }
4176 
4177         /**
4178          * A variant of {@link #setSmallIcon(int) setSmallIcon(int)} that takes an additional
4179          * level parameter for when the icon is a {@link android.graphics.drawable.LevelListDrawable
4180          * LevelListDrawable}.
4181          *
4182          * @param icon A resource ID in the application's package of the drawable to use.
4183          * @param level The level to use for the icon.
4184          *
4185          * @see Notification#icon
4186          * @see Notification#iconLevel
4187          */
4188         @NonNull
setSmallIcon(@rawableRes int icon, int level)4189         public Builder setSmallIcon(@DrawableRes int icon, int level) {
4190             mN.iconLevel = level;
4191             return setSmallIcon(icon);
4192         }
4193 
4194         /**
4195          * Set the small icon, which will be used to represent the notification in the
4196          * status bar and content view (unless overridden there by a
4197          * {@link #setLargeIcon(Bitmap) large icon}).
4198          *
4199          * @param icon An Icon object to use.
4200          * @see Notification#icon
4201          */
4202         @NonNull
setSmallIcon(Icon icon)4203         public Builder setSmallIcon(Icon icon) {
4204             mN.setSmallIcon(icon);
4205             if (icon != null && icon.getType() == Icon.TYPE_RESOURCE) {
4206                 mN.icon = icon.getResId();
4207             }
4208             return this;
4209         }
4210 
4211         /**
4212          * Set the first line of text in the platform notification template.
4213          */
4214         @NonNull
setContentTitle(CharSequence title)4215         public Builder setContentTitle(CharSequence title) {
4216             mN.extras.putCharSequence(EXTRA_TITLE, safeCharSequence(title));
4217             return this;
4218         }
4219 
4220         /**
4221          * Set the second line of text in the platform notification template.
4222          */
4223         @NonNull
setContentText(CharSequence text)4224         public Builder setContentText(CharSequence text) {
4225             mN.extras.putCharSequence(EXTRA_TEXT, safeCharSequence(text));
4226             return this;
4227         }
4228 
4229         /**
4230          * This provides some additional information that is displayed in the notification. No
4231          * guarantees are given where exactly it is displayed.
4232          *
4233          * <p>This information should only be provided if it provides an essential
4234          * benefit to the understanding of the notification. The more text you provide the
4235          * less readable it becomes. For example, an email client should only provide the account
4236          * name here if more than one email account has been added.</p>
4237          *
4238          * <p>As of {@link android.os.Build.VERSION_CODES#N} this information is displayed in the
4239          * notification header area.
4240          *
4241          * On Android versions before {@link android.os.Build.VERSION_CODES#N}
4242          * this will be shown in the third line of text in the platform notification template.
4243          * You should not be using {@link #setProgress(int, int, boolean)} at the
4244          * same time on those versions; they occupy the same place.
4245          * </p>
4246          */
4247         @NonNull
setSubText(CharSequence text)4248         public Builder setSubText(CharSequence text) {
4249             mN.extras.putCharSequence(EXTRA_SUB_TEXT, safeCharSequence(text));
4250             return this;
4251         }
4252 
4253         /**
4254          * Provides text that will appear as a link to your application's settings.
4255          *
4256          * <p>This text does not appear within notification {@link Style templates} but may
4257          * appear when the user uses an affordance to learn more about the notification.
4258          * Additionally, this text will not appear unless you provide a valid link target by
4259          * handling {@link #INTENT_CATEGORY_NOTIFICATION_PREFERENCES}.
4260          *
4261          * <p>This text is meant to be concise description about what the user can customize
4262          * when they click on this link. The recommended maximum length is 40 characters.
4263          * @param text
4264          * @return
4265          */
4266         @NonNull
setSettingsText(CharSequence text)4267         public Builder setSettingsText(CharSequence text) {
4268             mN.mSettingsText = safeCharSequence(text);
4269             return this;
4270         }
4271 
4272         /**
4273          * Set the remote input history.
4274          *
4275          * This should be set to the most recent inputs that have been sent
4276          * through a {@link RemoteInput} of this Notification and cleared once the it is no
4277          * longer relevant (e.g. for chat notifications once the other party has responded).
4278          *
4279          * The most recent input must be stored at the 0 index, the second most recent at the
4280          * 1 index, etc. Note that the system will limit both how far back the inputs will be shown
4281          * and how much of each individual input is shown.
4282          *
4283          * <p>Note: The reply text will only be shown on notifications that have least one action
4284          * with a {@code RemoteInput}.</p>
4285          */
4286         @NonNull
setRemoteInputHistory(CharSequence[] text)4287         public Builder setRemoteInputHistory(CharSequence[] text) {
4288             if (text == null) {
4289                 mN.extras.putCharSequenceArray(EXTRA_REMOTE_INPUT_HISTORY, null);
4290             } else {
4291                 final int itemCount = Math.min(MAX_REPLY_HISTORY, text.length);
4292                 CharSequence[] safe = new CharSequence[itemCount];
4293                 RemoteInputHistoryItem[] items = new RemoteInputHistoryItem[itemCount];
4294                 for (int i = 0; i < itemCount; i++) {
4295                     safe[i] = safeCharSequence(text[i]);
4296                     items[i] = new RemoteInputHistoryItem(text[i]);
4297                 }
4298                 mN.extras.putCharSequenceArray(EXTRA_REMOTE_INPUT_HISTORY, safe);
4299 
4300                 // Also add these messages as structured history items.
4301                 mN.extras.putParcelableArray(EXTRA_REMOTE_INPUT_HISTORY_ITEMS, items);
4302             }
4303             return this;
4304         }
4305 
4306         /**
4307          * Set the remote input history, with support for embedding URIs and mime types for
4308          * images and other media.
4309          * @hide
4310          */
4311         @NonNull
setRemoteInputHistory(RemoteInputHistoryItem[] items)4312         public Builder setRemoteInputHistory(RemoteInputHistoryItem[] items) {
4313             if (items == null) {
4314                 mN.extras.putParcelableArray(EXTRA_REMOTE_INPUT_HISTORY_ITEMS, null);
4315             } else {
4316                 final int itemCount = Math.min(MAX_REPLY_HISTORY, items.length);
4317                 RemoteInputHistoryItem[] history = new RemoteInputHistoryItem[itemCount];
4318                 for (int i = 0; i < itemCount; i++) {
4319                     history[i] = items[i];
4320                 }
4321                 mN.extras.putParcelableArray(EXTRA_REMOTE_INPUT_HISTORY_ITEMS, history);
4322             }
4323             return this;
4324         }
4325 
4326         /**
4327          * Sets whether remote history entries view should have a spinner.
4328          * @hide
4329          */
4330         @NonNull
setShowRemoteInputSpinner(boolean showSpinner)4331         public Builder setShowRemoteInputSpinner(boolean showSpinner) {
4332             mN.extras.putBoolean(EXTRA_SHOW_REMOTE_INPUT_SPINNER, showSpinner);
4333             return this;
4334         }
4335 
4336         /**
4337          * Sets whether smart reply buttons should be hidden.
4338          * @hide
4339          */
4340         @NonNull
setHideSmartReplies(boolean hideSmartReplies)4341         public Builder setHideSmartReplies(boolean hideSmartReplies) {
4342             mN.extras.putBoolean(EXTRA_HIDE_SMART_REPLIES, hideSmartReplies);
4343             return this;
4344         }
4345 
4346         /**
4347          * Sets the number of items this notification represents. May be displayed as a badge count
4348          * for Launchers that support badging.
4349          */
4350         @NonNull
setNumber(int number)4351         public Builder setNumber(int number) {
4352             mN.number = number;
4353             return this;
4354         }
4355 
4356         /**
4357          * A small piece of additional information pertaining to this notification.
4358          *
4359          * The platform template will draw this on the last line of the notification, at the far
4360          * right (to the right of a smallIcon if it has been placed there).
4361          *
4362          * @deprecated use {@link #setSubText(CharSequence)} instead to set a text in the header.
4363          * For legacy apps targeting a version below {@link android.os.Build.VERSION_CODES#N} this
4364          * field will still show up, but the subtext will take precedence.
4365          */
4366         @Deprecated
setContentInfo(CharSequence info)4367         public Builder setContentInfo(CharSequence info) {
4368             mN.extras.putCharSequence(EXTRA_INFO_TEXT, safeCharSequence(info));
4369             return this;
4370         }
4371 
4372         /**
4373          * Set the progress this notification represents.
4374          *
4375          * The platform template will represent this using a {@link ProgressBar}.
4376          */
4377         @NonNull
setProgress(int max, int progress, boolean indeterminate)4378         public Builder setProgress(int max, int progress, boolean indeterminate) {
4379             mN.extras.putInt(EXTRA_PROGRESS, progress);
4380             mN.extras.putInt(EXTRA_PROGRESS_MAX, max);
4381             mN.extras.putBoolean(EXTRA_PROGRESS_INDETERMINATE, indeterminate);
4382             return this;
4383         }
4384 
4385         /**
4386          * Supply a custom RemoteViews to use instead of the platform template.
4387          *
4388          * Use {@link #setCustomContentView(RemoteViews)} instead.
4389          */
4390         @Deprecated
setContent(RemoteViews views)4391         public Builder setContent(RemoteViews views) {
4392             return setCustomContentView(views);
4393         }
4394 
4395         /**
4396          * Supply custom RemoteViews to use instead of the platform template.
4397          *
4398          * This will override the layout that would otherwise be constructed by this Builder
4399          * object.
4400          */
4401         @NonNull
setCustomContentView(RemoteViews contentView)4402         public Builder setCustomContentView(RemoteViews contentView) {
4403             mN.contentView = contentView;
4404             return this;
4405         }
4406 
4407         /**
4408          * Supply custom RemoteViews to use instead of the platform template in the expanded form.
4409          *
4410          * This will override the expanded layout that would otherwise be constructed by this
4411          * Builder object.
4412          */
4413         @NonNull
setCustomBigContentView(RemoteViews contentView)4414         public Builder setCustomBigContentView(RemoteViews contentView) {
4415             mN.bigContentView = contentView;
4416             return this;
4417         }
4418 
4419         /**
4420          * Supply custom RemoteViews to use instead of the platform template in the heads up dialog.
4421          *
4422          * This will override the heads-up layout that would otherwise be constructed by this
4423          * Builder object.
4424          */
4425         @NonNull
setCustomHeadsUpContentView(RemoteViews contentView)4426         public Builder setCustomHeadsUpContentView(RemoteViews contentView) {
4427             mN.headsUpContentView = contentView;
4428             return this;
4429         }
4430 
4431         /**
4432          * Supply a {@link PendingIntent} to be sent when the notification is clicked.
4433          *
4434          * <p>As of Android {@link android.os.Build.VERSION_CODES#S}, apps targeting API level
4435          * {@link android.os.Build.VERSION_CODES#S} or higher won't be able to start activities
4436          * while processing broadcast receivers or services in response to notification clicks. To
4437          * launch an activity in those cases, provide a {@link PendingIntent} for the activity
4438          * itself.
4439          *
4440          * <p>As of {@link android.os.Build.VERSION_CODES#HONEYCOMB}, if this field is unset and you
4441          * have specified a custom RemoteViews with {@link #setContent(RemoteViews)}, you can use
4442          * {@link RemoteViews#setOnClickPendingIntent RemoteViews.setOnClickPendingIntent(int,PendingIntent)}
4443          * to assign PendingIntents to individual views in that custom layout (i.e., to create
4444          * clickable buttons inside the notification view).
4445          *
4446          * @see Notification#contentIntent Notification.contentIntent
4447          */
4448         @NonNull
setContentIntent(PendingIntent intent)4449         public Builder setContentIntent(PendingIntent intent) {
4450             mN.contentIntent = intent;
4451             return this;
4452         }
4453 
4454         /**
4455          * Supply a {@link PendingIntent} to send when the notification is cleared explicitly by the user.
4456          *
4457          * @see Notification#deleteIntent
4458          */
4459         @NonNull
setDeleteIntent(PendingIntent intent)4460         public Builder setDeleteIntent(PendingIntent intent) {
4461             mN.deleteIntent = intent;
4462             return this;
4463         }
4464 
4465         /**
4466          * An intent to launch instead of posting the notification to the status bar.
4467          * Only for use with extremely high-priority notifications demanding the user's
4468          * <strong>immediate</strong> attention, such as an incoming phone call or
4469          * alarm clock that the user has explicitly set to a particular time.
4470          * If this facility is used for something else, please give the user an option
4471          * to turn it off and use a normal notification, as this can be extremely
4472          * disruptive.
4473          *
4474          * <p>
4475          * The system UI may choose to display a heads-up notification, instead of
4476          * launching this intent, while the user is using the device.
4477          * </p>
4478          * <p>Apps targeting {@link Build.VERSION_CODES#Q} and above will have to request
4479          * a permission ({@link android.Manifest.permission#USE_FULL_SCREEN_INTENT}) in order to
4480          * use full screen intents.</p>
4481          * <p>
4482          * To be launched as a full screen intent, the notification must also be posted to a
4483          * channel with importance level set to IMPORTANCE_HIGH or higher.
4484          * </p>
4485          *
4486          * @param intent The pending intent to launch.
4487          * @param highPriority Passing true will cause this notification to be sent
4488          *          even if other notifications are suppressed.
4489          *
4490          * @see Notification#fullScreenIntent
4491          */
4492         @NonNull
setFullScreenIntent(PendingIntent intent, boolean highPriority)4493         public Builder setFullScreenIntent(PendingIntent intent, boolean highPriority) {
4494             mN.fullScreenIntent = intent;
4495             setFlag(FLAG_HIGH_PRIORITY, highPriority);
4496             return this;
4497         }
4498 
4499         /**
4500          * Set the "ticker" text which is sent to accessibility services.
4501          *
4502          * @see Notification#tickerText
4503          */
4504         @NonNull
setTicker(CharSequence tickerText)4505         public Builder setTicker(CharSequence tickerText) {
4506             mN.tickerText = safeCharSequence(tickerText);
4507             return this;
4508         }
4509 
4510         /**
4511          * Obsolete version of {@link #setTicker(CharSequence)}.
4512          *
4513          */
4514         @Deprecated
setTicker(CharSequence tickerText, RemoteViews views)4515         public Builder setTicker(CharSequence tickerText, RemoteViews views) {
4516             setTicker(tickerText);
4517             // views is ignored
4518             return this;
4519         }
4520 
4521         /**
4522          * Add a large icon to the notification content view.
4523          *
4524          * In the platform template, this image will be shown either on the right of the
4525          * notification, with an aspect ratio of up to 16:9, or (when the notification is grouped)
4526          * on the left in place of the {@link #setSmallIcon(Icon) small icon}.
4527          */
4528         @NonNull
setLargeIcon(Bitmap b)4529         public Builder setLargeIcon(Bitmap b) {
4530             return setLargeIcon(b != null ? Icon.createWithBitmap(b) : null);
4531         }
4532 
4533         /**
4534          * Add a large icon to the notification content view.
4535          *
4536          * In the platform template, this image will be shown either on the right of the
4537          * notification, with an aspect ratio of up to 16:9, or (when the notification is grouped)
4538          * on the left in place of the {@link #setSmallIcon(Icon) small icon}.
4539          */
4540         @NonNull
setLargeIcon(Icon icon)4541         public Builder setLargeIcon(Icon icon) {
4542             mN.mLargeIcon = icon;
4543             mN.extras.putParcelable(EXTRA_LARGE_ICON, icon);
4544             return this;
4545         }
4546 
4547         /**
4548          * Set the sound to play.
4549          *
4550          * It will be played using the {@link #AUDIO_ATTRIBUTES_DEFAULT default audio attributes}
4551          * for notifications.
4552          *
4553          * @deprecated use {@link NotificationChannel#setSound(Uri, AudioAttributes)} instead.
4554          */
4555         @Deprecated
setSound(Uri sound)4556         public Builder setSound(Uri sound) {
4557             mN.sound = sound;
4558             mN.audioAttributes = AUDIO_ATTRIBUTES_DEFAULT;
4559             return this;
4560         }
4561 
4562         /**
4563          * Set the sound to play, along with a specific stream on which to play it.
4564          *
4565          * See {@link android.media.AudioManager} for the <code>STREAM_</code> constants.
4566          *
4567          * @deprecated use {@link NotificationChannel#setSound(Uri, AudioAttributes)}.
4568          */
4569         @Deprecated
setSound(Uri sound, int streamType)4570         public Builder setSound(Uri sound, int streamType) {
4571             PlayerBase.deprecateStreamTypeForPlayback(streamType, "Notification", "setSound()");
4572             mN.sound = sound;
4573             mN.audioStreamType = streamType;
4574             return this;
4575         }
4576 
4577         /**
4578          * Set the sound to play, along with specific {@link AudioAttributes audio attributes} to
4579          * use during playback.
4580          *
4581          * @deprecated use {@link NotificationChannel#setSound(Uri, AudioAttributes)} instead.
4582          * @see Notification#sound
4583          */
4584         @Deprecated
setSound(Uri sound, AudioAttributes audioAttributes)4585         public Builder setSound(Uri sound, AudioAttributes audioAttributes) {
4586             mN.sound = sound;
4587             mN.audioAttributes = audioAttributes;
4588             return this;
4589         }
4590 
4591         /**
4592          * Set the vibration pattern to use.
4593          *
4594          * See {@link android.os.Vibrator#vibrate(long[], int)} for a discussion of the
4595          * <code>pattern</code> parameter.
4596          *
4597          * <p>
4598          * A notification that vibrates is more likely to be presented as a heads-up notification.
4599          * </p>
4600          *
4601          * @deprecated use {@link NotificationChannel#setVibrationPattern(long[])} instead.
4602          * @see Notification#vibrate
4603          */
4604         @Deprecated
setVibrate(long[] pattern)4605         public Builder setVibrate(long[] pattern) {
4606             mN.vibrate = pattern;
4607             return this;
4608         }
4609 
4610         /**
4611          * Set the desired color for the indicator LED on the device, as well as the
4612          * blink duty cycle (specified in milliseconds).
4613          *
4614 
4615          * Not all devices will honor all (or even any) of these values.
4616          *
4617          * @deprecated use {@link NotificationChannel#enableLights(boolean)} instead.
4618          * @see Notification#ledARGB
4619          * @see Notification#ledOnMS
4620          * @see Notification#ledOffMS
4621          */
4622         @Deprecated
setLights(@olorInt int argb, int onMs, int offMs)4623         public Builder setLights(@ColorInt int argb, int onMs, int offMs) {
4624             mN.ledARGB = argb;
4625             mN.ledOnMS = onMs;
4626             mN.ledOffMS = offMs;
4627             if (onMs != 0 || offMs != 0) {
4628                 mN.flags |= FLAG_SHOW_LIGHTS;
4629             }
4630             return this;
4631         }
4632 
4633         /**
4634          * Set whether this is an "ongoing" notification.
4635          *
4636 
4637          * Ongoing notifications cannot be dismissed by the user, so your application or service
4638          * must take care of canceling them.
4639          *
4640 
4641          * They are typically used to indicate a background task that the user is actively engaged
4642          * with (e.g., playing music) or is pending in some way and therefore occupying the device
4643          * (e.g., a file download, sync operation, active network connection).
4644          *
4645 
4646          * @see Notification#FLAG_ONGOING_EVENT
4647          */
4648         @NonNull
setOngoing(boolean ongoing)4649         public Builder setOngoing(boolean ongoing) {
4650             setFlag(FLAG_ONGOING_EVENT, ongoing);
4651             return this;
4652         }
4653 
4654         /**
4655          * Set whether this notification should be colorized. When set, the color set with
4656          * {@link #setColor(int)} will be used as the background color of this notification.
4657          * <p>
4658          * This should only be used for high priority ongoing tasks like navigation, an ongoing
4659          * call, or other similarly high-priority events for the user.
4660          * <p>
4661          * For most styles, the coloring will only be applied if the notification is for a
4662          * foreground service notification.
4663          * However, for {@link MediaStyle} and {@link DecoratedMediaCustomViewStyle} notifications
4664          * that have a media session attached there is no such requirement.
4665          *
4666          * @see #setColor(int)
4667          * @see MediaStyle#setMediaSession(MediaSession.Token)
4668          */
4669         @NonNull
setColorized(boolean colorize)4670         public Builder setColorized(boolean colorize) {
4671             mN.extras.putBoolean(EXTRA_COLORIZED, colorize);
4672             return this;
4673         }
4674 
4675         /**
4676          * Set this flag if you would only like the sound, vibrate
4677          * and ticker to be played if the notification is not already showing.
4678          *
4679          * Note that using this flag will stop any ongoing alerting behaviour such
4680          * as sound, vibration or blinking notification LED.
4681          *
4682          * @see Notification#FLAG_ONLY_ALERT_ONCE
4683          */
4684         @NonNull
setOnlyAlertOnce(boolean onlyAlertOnce)4685         public Builder setOnlyAlertOnce(boolean onlyAlertOnce) {
4686             setFlag(FLAG_ONLY_ALERT_ONCE, onlyAlertOnce);
4687             return this;
4688         }
4689 
4690         /**
4691          * Specify a desired visibility policy for a Notification associated with a
4692          * foreground service.  By default, the system can choose to defer
4693          * visibility of the notification for a short time after the service is
4694          * started.  Pass
4695          * {@link Notification#FOREGROUND_SERVICE_IMMEDIATE FOREGROUND_SERVICE_IMMEDIATE}
4696          * to this method in order to guarantee that visibility is never deferred.  Pass
4697          * {@link Notification#FOREGROUND_SERVICE_DEFERRED FOREGROUND_SERVICE_DEFERRED}
4698          * to request that visibility is deferred whenever possible.
4699          *
4700          * <p class="note">Note that deferred visibility is not guaranteed.  There
4701          * may be some circumstances under which the system will show the foreground
4702          * service's associated Notification immediately even when the app has used
4703          * this method to explicitly request deferred display.</p>
4704          * @param behavior One of
4705          * {@link Notification#FOREGROUND_SERVICE_DEFAULT FOREGROUND_SERVICE_DEFAULT},
4706          * {@link Notification#FOREGROUND_SERVICE_IMMEDIATE FOREGROUND_SERVICE_IMMEDIATE},
4707          * or {@link Notification#FOREGROUND_SERVICE_DEFERRED FOREGROUND_SERVICE_DEFERRED}
4708          * @return
4709          */
4710         @NonNull
setForegroundServiceBehavior(@erviceNotificationPolicy int behavior)4711         public Builder setForegroundServiceBehavior(@ServiceNotificationPolicy int behavior) {
4712             mN.mFgsDeferBehavior = behavior;
4713             return this;
4714         }
4715 
4716         /**
4717          * Make this notification automatically dismissed when the user touches it.
4718          *
4719          * @see Notification#FLAG_AUTO_CANCEL
4720          */
4721         @NonNull
setAutoCancel(boolean autoCancel)4722         public Builder setAutoCancel(boolean autoCancel) {
4723             setFlag(FLAG_AUTO_CANCEL, autoCancel);
4724             return this;
4725         }
4726 
4727         /**
4728          * Set whether or not this notification should not bridge to other devices.
4729          *
4730          * <p>Some notifications can be bridged to other devices for remote display.
4731          * This hint can be set to recommend this notification not be bridged.
4732          */
4733         @NonNull
setLocalOnly(boolean localOnly)4734         public Builder setLocalOnly(boolean localOnly) {
4735             setFlag(FLAG_LOCAL_ONLY, localOnly);
4736             return this;
4737         }
4738 
4739         /**
4740          * Set which notification properties will be inherited from system defaults.
4741          * <p>
4742          * The value should be one or more of the following fields combined with
4743          * bitwise-or:
4744          * {@link #DEFAULT_SOUND}, {@link #DEFAULT_VIBRATE}, {@link #DEFAULT_LIGHTS}.
4745          * <p>
4746          * For all default values, use {@link #DEFAULT_ALL}.
4747          *
4748          * @deprecated use {@link NotificationChannel#enableVibration(boolean)} and
4749          * {@link NotificationChannel#enableLights(boolean)} and
4750          * {@link NotificationChannel#setSound(Uri, AudioAttributes)} instead.
4751          */
4752         @Deprecated
setDefaults(int defaults)4753         public Builder setDefaults(int defaults) {
4754             mN.defaults = defaults;
4755             return this;
4756         }
4757 
4758         /**
4759          * Set the priority of this notification.
4760          *
4761          * @see Notification#priority
4762          * @deprecated use {@link NotificationChannel#setImportance(int)} instead.
4763          */
4764         @Deprecated
setPriority(@riority int pri)4765         public Builder setPriority(@Priority int pri) {
4766             mN.priority = pri;
4767             return this;
4768         }
4769 
4770         /**
4771          * Set the notification category.
4772          *
4773          * @see Notification#category
4774          */
4775         @NonNull
setCategory(String category)4776         public Builder setCategory(String category) {
4777             mN.category = category;
4778             return this;
4779         }
4780 
4781         /**
4782          * Add a person that is relevant to this notification.
4783          *
4784          * <P>
4785          * Depending on user preferences, this annotation may allow the notification to pass
4786          * through interruption filters, if this notification is of category {@link #CATEGORY_CALL}
4787          * or {@link #CATEGORY_MESSAGE}. The addition of people may also cause this notification to
4788          * appear more prominently in the user interface.
4789          * </P>
4790          *
4791          * <P>
4792          * The person should be specified by the {@code String} representation of a
4793          * {@link android.provider.ContactsContract.Contacts#CONTENT_LOOKUP_URI}.
4794          * </P>
4795          *
4796          * <P>The system will also attempt to resolve {@code mailto:} and {@code tel:} schema
4797          * URIs.  The path part of these URIs must exist in the contacts database, in the
4798          * appropriate column, or the reference will be discarded as invalid. Telephone schema
4799          * URIs will be resolved by {@link android.provider.ContactsContract.PhoneLookup}.
4800          * It is also possible to provide a URI with the schema {@code name:} in order to uniquely
4801          * identify a person without an entry in the contacts database.
4802          * </P>
4803          *
4804          * @param uri A URI for the person.
4805          * @see Notification#EXTRA_PEOPLE
4806          * @deprecated use {@link #addPerson(Person)}
4807          */
addPerson(String uri)4808         public Builder addPerson(String uri) {
4809             addPerson(new Person.Builder().setUri(uri).build());
4810             return this;
4811         }
4812 
4813         /**
4814          * Add a person that is relevant to this notification.
4815          *
4816          * <P>
4817          * Depending on user preferences, this annotation may allow the notification to pass
4818          * through interruption filters, if this notification is of category {@link #CATEGORY_CALL}
4819          * or {@link #CATEGORY_MESSAGE}. The addition of people may also cause this notification to
4820          * appear more prominently in the user interface.
4821          * </P>
4822          *
4823          * <P>
4824          * A person should usually contain a uri in order to benefit from the ranking boost.
4825          * However, even if no uri is provided, it's beneficial to provide other people in the
4826          * notification, such that listeners and voice only devices can announce and handle them
4827          * properly.
4828          * </P>
4829          *
4830          * @param person the person to add.
4831          * @see Notification#EXTRA_PEOPLE_LIST
4832          */
4833         @NonNull
addPerson(Person person)4834         public Builder addPerson(Person person) {
4835             mPersonList.add(person);
4836             return this;
4837         }
4838 
4839         /**
4840          * Set this notification to be part of a group of notifications sharing the same key.
4841          * Grouped notifications may display in a cluster or stack on devices which
4842          * support such rendering.
4843          *
4844          * <p>To make this notification the summary for its group, also call
4845          * {@link #setGroupSummary}. A sort order can be specified for group members by using
4846          * {@link #setSortKey}.
4847          * @param groupKey The group key of the group.
4848          * @return this object for method chaining
4849          */
4850         @NonNull
setGroup(String groupKey)4851         public Builder setGroup(String groupKey) {
4852             mN.mGroupKey = groupKey;
4853             return this;
4854         }
4855 
4856         /**
4857          * Set this notification to be the group summary for a group of notifications.
4858          * Grouped notifications may display in a cluster or stack on devices which
4859          * support such rendering. If thereRequires a group key also be set using {@link #setGroup}.
4860          * The group summary may be suppressed if too few notifications are included in the group.
4861          * @param isGroupSummary Whether this notification should be a group summary.
4862          * @return this object for method chaining
4863          */
4864         @NonNull
setGroupSummary(boolean isGroupSummary)4865         public Builder setGroupSummary(boolean isGroupSummary) {
4866             setFlag(FLAG_GROUP_SUMMARY, isGroupSummary);
4867             return this;
4868         }
4869 
4870         /**
4871          * Set a sort key that orders this notification among other notifications from the
4872          * same package. This can be useful if an external sort was already applied and an app
4873          * would like to preserve this. Notifications will be sorted lexicographically using this
4874          * value, although providing different priorities in addition to providing sort key may
4875          * cause this value to be ignored.
4876          *
4877          * <p>This sort key can also be used to order members of a notification group. See
4878          * {@link #setGroup}.
4879          *
4880          * @see String#compareTo(String)
4881          */
4882         @NonNull
setSortKey(String sortKey)4883         public Builder setSortKey(String sortKey) {
4884             mN.mSortKey = sortKey;
4885             return this;
4886         }
4887 
4888         /**
4889          * Merge additional metadata into this notification.
4890          *
4891          * <p>Values within the Bundle will replace existing extras values in this Builder.
4892          *
4893          * @see Notification#extras
4894          */
4895         @NonNull
addExtras(Bundle extras)4896         public Builder addExtras(Bundle extras) {
4897             if (extras != null) {
4898                 mUserExtras.putAll(extras);
4899             }
4900             return this;
4901         }
4902 
4903         /**
4904          * Set metadata for this notification.
4905          *
4906          * <p>A reference to the Bundle is held for the lifetime of this Builder, and the Bundle's
4907          * current contents are copied into the Notification each time {@link #build()} is
4908          * called.
4909          *
4910          * <p>Replaces any existing extras values with those from the provided Bundle.
4911          * Use {@link #addExtras} to merge in metadata instead.
4912          *
4913          * @see Notification#extras
4914          */
4915         @NonNull
setExtras(Bundle extras)4916         public Builder setExtras(Bundle extras) {
4917             if (extras != null) {
4918                 mUserExtras = extras;
4919             }
4920             return this;
4921         }
4922 
4923         /**
4924          * Get the current metadata Bundle used by this notification Builder.
4925          *
4926          * <p>The returned Bundle is shared with this Builder.
4927          *
4928          * <p>The current contents of this Bundle are copied into the Notification each time
4929          * {@link #build()} is called.
4930          *
4931          * @see Notification#extras
4932          */
getExtras()4933         public Bundle getExtras() {
4934             return mUserExtras;
4935         }
4936 
getAllExtras()4937         private Bundle getAllExtras() {
4938             final Bundle saveExtras = (Bundle) mUserExtras.clone();
4939             saveExtras.putAll(mN.extras);
4940             return saveExtras;
4941         }
4942 
4943         /**
4944          * Add an action to this notification. Actions are typically displayed by
4945          * the system as a button adjacent to the notification content.
4946          * <p>
4947          * Every action must have an icon (32dp square and matching the
4948          * <a href="{@docRoot}design/style/iconography.html#action-bar">Holo
4949          * Dark action bar</a> visual style), a textual label, and a {@link PendingIntent}.
4950          * <p>
4951          * A notification in its expanded form can display up to 3 actions, from left to right in
4952          * the order they were added. Actions will not be displayed when the notification is
4953          * collapsed, however, so be sure that any essential functions may be accessed by the user
4954          * in some other way (for example, in the Activity pointed to by {@link #contentIntent}).
4955          * <p>
4956          * As of Android {@link android.os.Build.VERSION_CODES#S}, apps targeting API level
4957          * {@link android.os.Build.VERSION_CODES#S} or higher won't be able to start activities
4958          * while processing broadcast receivers or services in response to notification action
4959          * clicks. To launch an activity in those cases, provide a {@link PendingIntent} to the
4960          * activity itself.
4961          * <p>
4962          * As of Android {@link android.os.Build.VERSION_CODES#N},
4963          * action button icons will not be displayed on action buttons, but are still required
4964          * and are available to
4965          * {@link android.service.notification.NotificationListenerService notification listeners},
4966          * which may display them in other contexts, for example on a wearable device.
4967          *
4968          * @param icon Resource ID of a drawable that represents the action.
4969          * @param title Text describing the action.
4970          * @param intent PendingIntent to be fired when the action is invoked.
4971          *
4972          * @deprecated Use {@link #addAction(Action)} instead.
4973          */
4974         @Deprecated
addAction(int icon, CharSequence title, PendingIntent intent)4975         public Builder addAction(int icon, CharSequence title, PendingIntent intent) {
4976             mActions.add(new Action(icon, safeCharSequence(title), intent));
4977             return this;
4978         }
4979 
4980         /**
4981          * Add an action to this notification. Actions are typically displayed by
4982          * the system as a button adjacent to the notification content.
4983          * <p>
4984          * Every action must have an icon (32dp square and matching the
4985          * <a href="{@docRoot}design/style/iconography.html#action-bar">Holo
4986          * Dark action bar</a> visual style), a textual label, and a {@link PendingIntent}.
4987          * <p>
4988          * A notification in its expanded form can display up to 3 actions, from left to right in
4989          * the order they were added. Actions will not be displayed when the notification is
4990          * collapsed, however, so be sure that any essential functions may be accessed by the user
4991          * in some other way (for example, in the Activity pointed to by {@link #contentIntent}).
4992          *
4993          * @param action The action to add.
4994          */
4995         @NonNull
addAction(Action action)4996         public Builder addAction(Action action) {
4997             if (action != null) {
4998                 mActions.add(action);
4999             }
5000             return this;
5001         }
5002 
5003         /**
5004          * Alter the complete list of actions attached to this notification.
5005          * @see #addAction(Action).
5006          *
5007          * @param actions
5008          * @return
5009          */
5010         @NonNull
setActions(Action... actions)5011         public Builder setActions(Action... actions) {
5012             mActions.clear();
5013             for (int i = 0; i < actions.length; i++) {
5014                 if (actions[i] != null) {
5015                     mActions.add(actions[i]);
5016                 }
5017             }
5018             return this;
5019         }
5020 
5021         /**
5022          * Add a rich notification style to be applied at build time.
5023          *
5024          * @param style Object responsible for modifying the notification style.
5025          */
5026         @NonNull
setStyle(Style style)5027         public Builder setStyle(Style style) {
5028             if (mStyle != style) {
5029                 mStyle = style;
5030                 if (mStyle != null) {
5031                     mStyle.setBuilder(this);
5032                     mN.extras.putString(EXTRA_TEMPLATE, style.getClass().getName());
5033                 }  else {
5034                     mN.extras.remove(EXTRA_TEMPLATE);
5035                 }
5036             }
5037             return this;
5038         }
5039 
5040         /**
5041          * Returns the style set by {@link #setStyle(Style)}.
5042          */
getStyle()5043         public Style getStyle() {
5044             return mStyle;
5045         }
5046 
5047         /**
5048          * Specify the value of {@link #visibility}.
5049          *
5050          * @return The same Builder.
5051          */
5052         @NonNull
setVisibility(@isibility int visibility)5053         public Builder setVisibility(@Visibility int visibility) {
5054             mN.visibility = visibility;
5055             return this;
5056         }
5057 
5058         /**
5059          * Supply a replacement Notification whose contents should be shown in insecure contexts
5060          * (i.e. atop the secure lockscreen). See {@link #visibility} and {@link #VISIBILITY_PUBLIC}.
5061          * @param n A replacement notification, presumably with some or all info redacted.
5062          * @return The same Builder.
5063          */
5064         @NonNull
setPublicVersion(Notification n)5065         public Builder setPublicVersion(Notification n) {
5066             if (n != null) {
5067                 mN.publicVersion = new Notification();
5068                 n.cloneInto(mN.publicVersion, /*heavy=*/ true);
5069             } else {
5070                 mN.publicVersion = null;
5071             }
5072             return this;
5073         }
5074 
5075         /**
5076          * Apply an extender to this notification builder. Extenders may be used to add
5077          * metadata or change options on this builder.
5078          */
5079         @NonNull
extend(Extender extender)5080         public Builder extend(Extender extender) {
5081             extender.extend(this);
5082             return this;
5083         }
5084 
5085         /**
5086          * Set the value for a notification flag
5087          *
5088          * @param mask Bit mask of the flag
5089          * @param value Status (on/off) of the flag
5090          *
5091          * @return The same Builder.
5092          */
5093         @NonNull
setFlag(@otificationFlags int mask, boolean value)5094         public Builder setFlag(@NotificationFlags int mask, boolean value) {
5095             if (value) {
5096                 mN.flags |= mask;
5097             } else {
5098                 mN.flags &= ~mask;
5099             }
5100             return this;
5101         }
5102 
5103         /**
5104          * Sets {@link Notification#color}.
5105          *
5106          * @param argb The accent color to use
5107          *
5108          * @return The same Builder.
5109          */
5110         @NonNull
setColor(@olorInt int argb)5111         public Builder setColor(@ColorInt int argb) {
5112             mN.color = argb;
5113             sanitizeColor();
5114             return this;
5115         }
5116 
bindPhishingAlertIcon(RemoteViews contentView, StandardTemplateParams p)5117         private void bindPhishingAlertIcon(RemoteViews contentView, StandardTemplateParams p) {
5118             contentView.setDrawableTint(
5119                     R.id.phishing_alert,
5120                     false /* targetBackground */,
5121                     getColors(p).getErrorColor(),
5122                     PorterDuff.Mode.SRC_ATOP);
5123         }
5124 
getProfileBadgeDrawable()5125         private Drawable getProfileBadgeDrawable() {
5126             if (mContext.getUserId() == UserHandle.USER_SYSTEM) {
5127                 // This user can never be a badged profile,
5128                 // and also includes USER_ALL system notifications.
5129                 return null;
5130             }
5131             // Note: This assumes that the current user can read the profile badge of the
5132             // originating user.
5133             DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
5134             return dpm.getResources().getDrawable(
5135                     getUpdatableProfileBadgeId(), SOLID_COLORED, NOTIFICATION,
5136                     this::getDefaultProfileBadgeDrawable);
5137         }
5138 
getUpdatableProfileBadgeId()5139         private String getUpdatableProfileBadgeId() {
5140             return mContext.getSystemService(UserManager.class).isManagedProfile()
5141                     ? WORK_PROFILE_ICON : UNDEFINED;
5142         }
5143 
getDefaultProfileBadgeDrawable()5144         private Drawable getDefaultProfileBadgeDrawable() {
5145             return mContext.getPackageManager().getUserBadgeForDensityNoBackground(
5146                     new UserHandle(mContext.getUserId()), 0);
5147         }
5148 
getProfileBadge()5149         private Bitmap getProfileBadge() {
5150             Drawable badge = getProfileBadgeDrawable();
5151             if (badge == null) {
5152                 return null;
5153             }
5154             final int size = mContext.getResources().getDimensionPixelSize(
5155                     R.dimen.notification_badge_size);
5156             Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
5157             Canvas canvas = new Canvas(bitmap);
5158             badge.setBounds(0, 0, size, size);
5159             badge.draw(canvas);
5160             return bitmap;
5161         }
5162 
bindProfileBadge(RemoteViews contentView, StandardTemplateParams p)5163         private void bindProfileBadge(RemoteViews contentView, StandardTemplateParams p) {
5164             Bitmap profileBadge = getProfileBadge();
5165 
5166             if (profileBadge != null) {
5167                 contentView.setImageViewBitmap(R.id.profile_badge, profileBadge);
5168                 contentView.setViewVisibility(R.id.profile_badge, View.VISIBLE);
5169                 if (isBackgroundColorized(p)) {
5170                     contentView.setDrawableTint(R.id.profile_badge, false,
5171                             getPrimaryTextColor(p), PorterDuff.Mode.SRC_ATOP);
5172                 }
5173             }
5174         }
5175 
bindAlertedIcon(RemoteViews contentView, StandardTemplateParams p)5176         private void bindAlertedIcon(RemoteViews contentView, StandardTemplateParams p) {
5177             contentView.setDrawableTint(
5178                     R.id.alerted_icon,
5179                     false /* targetBackground */,
5180                     getColors(p).getSecondaryTextColor(),
5181                     PorterDuff.Mode.SRC_IN);
5182         }
5183 
5184         /**
5185          * @hide
5186          */
usesStandardHeader()5187         public boolean usesStandardHeader() {
5188             if (mN.mUsesStandardHeader) {
5189                 return true;
5190             }
5191             if (mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.N) {
5192                 if (mN.contentView == null && mN.bigContentView == null) {
5193                     return true;
5194                 }
5195             }
5196             boolean contentViewUsesHeader = mN.contentView == null
5197                     || STANDARD_LAYOUTS.contains(mN.contentView.getLayoutId());
5198             boolean bigContentViewUsesHeader = mN.bigContentView == null
5199                     || STANDARD_LAYOUTS.contains(mN.bigContentView.getLayoutId());
5200             return contentViewUsesHeader && bigContentViewUsesHeader;
5201         }
5202 
resetStandardTemplate(RemoteViews contentView)5203         private void resetStandardTemplate(RemoteViews contentView) {
5204             resetNotificationHeader(contentView);
5205             contentView.setViewVisibility(R.id.right_icon, View.GONE);
5206             contentView.setViewVisibility(R.id.title, View.GONE);
5207             contentView.setTextViewText(R.id.title, null);
5208             contentView.setViewVisibility(R.id.text, View.GONE);
5209             contentView.setTextViewText(R.id.text, null);
5210         }
5211 
5212         /**
5213          * Resets the notification header to its original state
5214          */
resetNotificationHeader(RemoteViews contentView)5215         private void resetNotificationHeader(RemoteViews contentView) {
5216             // Small icon doesn't need to be reset, as it's always set. Resetting would prevent
5217             // re-using the drawable when the notification is updated.
5218             contentView.setBoolean(R.id.expand_button, "setExpanded", false);
5219             contentView.setViewVisibility(R.id.app_name_text, View.GONE);
5220             contentView.setTextViewText(R.id.app_name_text, null);
5221             contentView.setViewVisibility(R.id.chronometer, View.GONE);
5222             contentView.setViewVisibility(R.id.header_text, View.GONE);
5223             contentView.setTextViewText(R.id.header_text, null);
5224             contentView.setViewVisibility(R.id.header_text_secondary, View.GONE);
5225             contentView.setTextViewText(R.id.header_text_secondary, null);
5226             contentView.setViewVisibility(R.id.header_text_divider, View.GONE);
5227             contentView.setViewVisibility(R.id.header_text_secondary_divider, View.GONE);
5228             contentView.setViewVisibility(R.id.time_divider, View.GONE);
5229             contentView.setViewVisibility(R.id.time, View.GONE);
5230             contentView.setImageViewIcon(R.id.profile_badge, null);
5231             contentView.setViewVisibility(R.id.profile_badge, View.GONE);
5232             mN.mUsesStandardHeader = false;
5233         }
5234 
applyStandardTemplate(int resId, StandardTemplateParams p, TemplateBindResult result)5235         private RemoteViews applyStandardTemplate(int resId, StandardTemplateParams p,
5236                 TemplateBindResult result) {
5237             p.headerless(resId == getBaseLayoutResource()
5238                     || resId == getHeadsUpBaseLayoutResource()
5239                     || resId == getMessagingLayoutResource()
5240                     || resId == R.layout.notification_template_material_media);
5241             RemoteViews contentView = new BuilderRemoteViews(mContext.getApplicationInfo(), resId);
5242 
5243             resetStandardTemplate(contentView);
5244 
5245             final Bundle ex = mN.extras;
5246             updateBackgroundColor(contentView, p);
5247             bindNotificationHeader(contentView, p);
5248             bindLargeIconAndApplyMargin(contentView, p, result);
5249             boolean showProgress = handleProgressBar(contentView, ex, p);
5250             boolean hasSecondLine = showProgress;
5251             if (p.hasTitle()) {
5252                 contentView.setViewVisibility(p.mTitleViewId, View.VISIBLE);
5253                 contentView.setTextViewText(p.mTitleViewId, processTextSpans(p.title));
5254                 setTextViewColorPrimary(contentView, p.mTitleViewId, p);
5255             } else if (p.mTitleViewId != R.id.title) {
5256                 // This alternate title view ID is not cleared by resetStandardTemplate
5257                 contentView.setViewVisibility(p.mTitleViewId, View.GONE);
5258                 contentView.setTextViewText(p.mTitleViewId, null);
5259             }
5260             if (p.text != null && p.text.length() != 0
5261                     && (!showProgress || p.mAllowTextWithProgress)) {
5262                 contentView.setViewVisibility(p.mTextViewId, View.VISIBLE);
5263                 contentView.setTextViewText(p.mTextViewId, processTextSpans(p.text));
5264                 setTextViewColorSecondary(contentView, p.mTextViewId, p);
5265                 hasSecondLine = true;
5266             } else if (p.mTextViewId != R.id.text) {
5267                 // This alternate text view ID is not cleared by resetStandardTemplate
5268                 contentView.setViewVisibility(p.mTextViewId, View.GONE);
5269                 contentView.setTextViewText(p.mTextViewId, null);
5270             }
5271             setHeaderlessVerticalMargins(contentView, p, hasSecondLine);
5272 
5273             return contentView;
5274         }
5275 
setHeaderlessVerticalMargins(RemoteViews contentView, StandardTemplateParams p, boolean hasSecondLine)5276         private static void setHeaderlessVerticalMargins(RemoteViews contentView,
5277                 StandardTemplateParams p, boolean hasSecondLine) {
5278             if (!p.mHeaderless) {
5279                 return;
5280             }
5281             int marginDimen = hasSecondLine
5282                     ? R.dimen.notification_headerless_margin_twoline
5283                     : R.dimen.notification_headerless_margin_oneline;
5284             contentView.setViewLayoutMarginDimen(R.id.notification_headerless_view_column,
5285                     RemoteViews.MARGIN_TOP, marginDimen);
5286             contentView.setViewLayoutMarginDimen(R.id.notification_headerless_view_column,
5287                     RemoteViews.MARGIN_BOTTOM, marginDimen);
5288         }
5289 
processTextSpans(CharSequence text)5290         private CharSequence processTextSpans(CharSequence text) {
5291             if (mInNightMode) {
5292                 return ContrastColorUtil.clearColorSpans(text);
5293             }
5294             return text;
5295         }
5296 
setTextViewColorPrimary(RemoteViews contentView, @IdRes int id, StandardTemplateParams p)5297         private void setTextViewColorPrimary(RemoteViews contentView, @IdRes int id,
5298                 StandardTemplateParams p) {
5299             contentView.setTextColor(id, getPrimaryTextColor(p));
5300         }
5301 
5302         /**
5303          * @param p the template params to inflate this with
5304          * @return the primary text color
5305          * @hide
5306          */
5307         @VisibleForTesting
getPrimaryTextColor(StandardTemplateParams p)5308         public @ColorInt int getPrimaryTextColor(StandardTemplateParams p) {
5309             return getColors(p).getPrimaryTextColor();
5310         }
5311 
5312         /**
5313          * @param p the template params to inflate this with
5314          * @return the secondary text color
5315          * @hide
5316          */
5317         @VisibleForTesting
getSecondaryTextColor(StandardTemplateParams p)5318         public @ColorInt int getSecondaryTextColor(StandardTemplateParams p) {
5319             return getColors(p).getSecondaryTextColor();
5320         }
5321 
setTextViewColorSecondary(RemoteViews contentView, @IdRes int id, StandardTemplateParams p)5322         private void setTextViewColorSecondary(RemoteViews contentView, @IdRes int id,
5323                 StandardTemplateParams p) {
5324             contentView.setTextColor(id, getSecondaryTextColor(p));
5325         }
5326 
getColors(StandardTemplateParams p)5327         private Colors getColors(StandardTemplateParams p) {
5328             mColors.resolvePalette(mContext, mN.color, isBackgroundColorized(p), mInNightMode);
5329             return mColors;
5330         }
5331 
updateBackgroundColor(RemoteViews contentView, StandardTemplateParams p)5332         private void updateBackgroundColor(RemoteViews contentView,
5333                 StandardTemplateParams p) {
5334             if (isBackgroundColorized(p)) {
5335                 contentView.setInt(R.id.status_bar_latest_event_content, "setBackgroundColor",
5336                         getBackgroundColor(p));
5337             } else {
5338                 // Clear it!
5339                 contentView.setInt(R.id.status_bar_latest_event_content, "setBackgroundResource",
5340                         0);
5341             }
5342         }
5343 
handleProgressBar(RemoteViews contentView, Bundle ex, StandardTemplateParams p)5344         private boolean handleProgressBar(RemoteViews contentView, Bundle ex,
5345                 StandardTemplateParams p) {
5346             final int max = ex.getInt(EXTRA_PROGRESS_MAX, 0);
5347             final int progress = ex.getInt(EXTRA_PROGRESS, 0);
5348             final boolean ind = ex.getBoolean(EXTRA_PROGRESS_INDETERMINATE);
5349             if (!p.mHideProgress && (max != 0 || ind)) {
5350                 contentView.setViewVisibility(com.android.internal.R.id.progress, View.VISIBLE);
5351                 contentView.setProgressBar(R.id.progress, max, progress, ind);
5352                 contentView.setProgressBackgroundTintList(R.id.progress,
5353                         mContext.getColorStateList(R.color.notification_progress_background_color));
5354                 ColorStateList progressTint = ColorStateList.valueOf(getPrimaryAccentColor(p));
5355                 contentView.setProgressTintList(R.id.progress, progressTint);
5356                 contentView.setProgressIndeterminateTintList(R.id.progress, progressTint);
5357                 return true;
5358             } else {
5359                 contentView.setViewVisibility(R.id.progress, View.GONE);
5360                 return false;
5361             }
5362         }
5363 
bindLargeIconAndApplyMargin(RemoteViews contentView, @NonNull StandardTemplateParams p, @Nullable TemplateBindResult result)5364         private void bindLargeIconAndApplyMargin(RemoteViews contentView,
5365                 @NonNull StandardTemplateParams p,
5366                 @Nullable TemplateBindResult result) {
5367             if (result == null) {
5368                 result = new TemplateBindResult();
5369             }
5370             bindLargeIcon(contentView, p, result);
5371             if (!p.mHeaderless) {
5372                 // views in states with a header (big states)
5373                 result.mHeadingExtraMarginSet.applyToView(contentView, R.id.notification_header);
5374                 result.mTitleMarginSet.applyToView(contentView, R.id.title);
5375                 // If there is no title, the text (or big_text) needs to wrap around the image
5376                 result.mTitleMarginSet.applyToView(contentView, p.mTextViewId);
5377                 contentView.setInt(p.mTextViewId, "setNumIndentLines", p.hasTitle() ? 0 : 1);
5378             }
5379         }
5380 
5381         // This code is executed on behalf of other apps' notifications, sometimes even by 3p apps,
5382         // a use case that is not supported by the Compat Framework library.  Workarounds to resolve
5383         // the change's state in NotificationManagerService were very complex. These behavior
5384         // changes are entirely visual, and should otherwise be undetectable by apps.
5385         @SuppressWarnings("AndroidFrameworkCompatChange")
calculateRightIconDimens(Icon rightIcon, boolean isPromotedPicture, @NonNull TemplateBindResult result)5386         private void calculateRightIconDimens(Icon rightIcon, boolean isPromotedPicture,
5387                 @NonNull TemplateBindResult result) {
5388             final Resources resources = mContext.getResources();
5389             final float density = resources.getDisplayMetrics().density;
5390             final float iconMarginDp = resources.getDimension(
5391                     R.dimen.notification_right_icon_content_margin) / density;
5392             final float contentMarginDp = resources.getDimension(
5393                     R.dimen.notification_content_margin_end) / density;
5394             final float expanderSizeDp = resources.getDimension(
5395                     R.dimen.notification_header_expand_icon_size) / density - contentMarginDp;
5396             final float viewHeightDp = resources.getDimension(
5397                     R.dimen.notification_right_icon_size) / density;
5398             float viewWidthDp = viewHeightDp;  // icons are 1:1 by default
5399             if (rightIcon != null && (isPromotedPicture
5400                     || mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.S)) {
5401                 Drawable drawable = rightIcon.loadDrawable(mContext);
5402                 if (drawable != null) {
5403                     int iconWidth = drawable.getIntrinsicWidth();
5404                     int iconHeight = drawable.getIntrinsicHeight();
5405                     if (iconWidth > iconHeight && iconHeight > 0) {
5406                         final float maxViewWidthDp = viewHeightDp * MAX_LARGE_ICON_ASPECT_RATIO;
5407                         viewWidthDp = Math.min(viewHeightDp * iconWidth / iconHeight,
5408                                 maxViewWidthDp);
5409                     }
5410                 }
5411             }
5412             final float extraMarginEndDpIfVisible = viewWidthDp + iconMarginDp;
5413             result.setRightIconState(rightIcon != null /* visible */, viewWidthDp,
5414                     viewHeightDp, extraMarginEndDpIfVisible, expanderSizeDp);
5415         }
5416 
5417         /**
5418          * Bind the large icon.
5419          */
bindLargeIcon(RemoteViews contentView, @NonNull StandardTemplateParams p, @NonNull TemplateBindResult result)5420         private void bindLargeIcon(RemoteViews contentView, @NonNull StandardTemplateParams p,
5421                 @NonNull TemplateBindResult result) {
5422             if (mN.mLargeIcon == null && mN.largeIcon != null) {
5423                 mN.mLargeIcon = Icon.createWithBitmap(mN.largeIcon);
5424             }
5425 
5426             // Determine the left and right icons
5427             Icon leftIcon = p.mHideLeftIcon ? null : mN.mLargeIcon;
5428             Icon rightIcon = p.mHideRightIcon ? null
5429                     : (p.mPromotedPicture != null ? p.mPromotedPicture : mN.mLargeIcon);
5430 
5431             // Apply the left icon (without duplicating the bitmap)
5432             if (leftIcon != rightIcon || leftIcon == null) {
5433                 // If the leftIcon is explicitly hidden or different from the rightIcon, then set it
5434                 // explicitly and make sure it won't take the right_icon drawable.
5435                 contentView.setImageViewIcon(R.id.left_icon, leftIcon);
5436                 contentView.setIntTag(R.id.left_icon, R.id.tag_uses_right_icon_drawable, 0);
5437             } else {
5438                 // If the leftIcon equals the rightIcon, just set the flag to use the right_icon
5439                 // drawable.  This avoids the view having two copies of the same bitmap.
5440                 contentView.setIntTag(R.id.left_icon, R.id.tag_uses_right_icon_drawable, 1);
5441             }
5442 
5443             // Always calculate dimens to populate `result` for the GONE case
5444             boolean isPromotedPicture = p.mPromotedPicture != null;
5445             calculateRightIconDimens(rightIcon, isPromotedPicture, result);
5446 
5447             // Bind the right icon
5448             if (rightIcon != null) {
5449                 contentView.setViewLayoutWidth(R.id.right_icon,
5450                         result.mRightIconWidthDp, TypedValue.COMPLEX_UNIT_DIP);
5451                 contentView.setViewLayoutHeight(R.id.right_icon,
5452                         result.mRightIconHeightDp, TypedValue.COMPLEX_UNIT_DIP);
5453                 contentView.setViewVisibility(R.id.right_icon, View.VISIBLE);
5454                 contentView.setImageViewIcon(R.id.right_icon, rightIcon);
5455                 contentView.setIntTag(R.id.right_icon, R.id.tag_keep_when_showing_left_icon,
5456                         isPromotedPicture ? 1 : 0);
5457                 processLargeLegacyIcon(rightIcon, contentView, p);
5458             } else {
5459                 // The "reset" doesn't clear the drawable, so we do it here.  This clear is
5460                 // important because the presence of a drawable in this view (regardless of the
5461                 // visibility) is used by NotificationGroupingUtil to set the visibility.
5462                 contentView.setImageViewIcon(R.id.right_icon, null);
5463                 contentView.setIntTag(R.id.right_icon, R.id.tag_keep_when_showing_left_icon, 0);
5464             }
5465         }
5466 
bindNotificationHeader(RemoteViews contentView, StandardTemplateParams p)5467         private void bindNotificationHeader(RemoteViews contentView, StandardTemplateParams p) {
5468             bindSmallIcon(contentView, p);
5469             // Populate text left-to-right so that separators are only shown between strings
5470             boolean hasTextToLeft = bindHeaderAppName(contentView, p, false /* force */);
5471             hasTextToLeft |= bindHeaderTextSecondary(contentView, p, hasTextToLeft);
5472             hasTextToLeft |= bindHeaderText(contentView, p, hasTextToLeft);
5473             if (!hasTextToLeft) {
5474                 // If there's still no text, force add the app name so there is some text.
5475                 hasTextToLeft |= bindHeaderAppName(contentView, p, true /* force */);
5476             }
5477             bindHeaderChronometerAndTime(contentView, p, hasTextToLeft);
5478             bindPhishingAlertIcon(contentView, p);
5479             bindProfileBadge(contentView, p);
5480             bindAlertedIcon(contentView, p);
5481             bindExpandButton(contentView, p);
5482             mN.mUsesStandardHeader = true;
5483         }
5484 
bindExpandButton(RemoteViews contentView, StandardTemplateParams p)5485         private void bindExpandButton(RemoteViews contentView, StandardTemplateParams p) {
5486             // set default colors
5487             int bgColor = getBackgroundColor(p);
5488             int pillColor = Colors.flattenAlpha(getColors(p).getProtectionColor(), bgColor);
5489             int textColor = Colors.flattenAlpha(getPrimaryTextColor(p), pillColor);
5490             contentView.setInt(R.id.expand_button, "setDefaultTextColor", textColor);
5491             contentView.setInt(R.id.expand_button, "setDefaultPillColor", pillColor);
5492             // Use different highlighted colors for conversations' unread count
5493             if (p.mHighlightExpander) {
5494                 pillColor = Colors.flattenAlpha(getColors(p).getTertiaryAccentColor(), bgColor);
5495                 textColor = Colors.flattenAlpha(getColors(p).getOnAccentTextColor(), pillColor);
5496             }
5497             contentView.setInt(R.id.expand_button, "setHighlightTextColor", textColor);
5498             contentView.setInt(R.id.expand_button, "setHighlightPillColor", pillColor);
5499         }
5500 
bindHeaderChronometerAndTime(RemoteViews contentView, StandardTemplateParams p, boolean hasTextToLeft)5501         private void bindHeaderChronometerAndTime(RemoteViews contentView,
5502                 StandardTemplateParams p, boolean hasTextToLeft) {
5503             if (!p.mHideTime && showsTimeOrChronometer()) {
5504                 if (hasTextToLeft) {
5505                     contentView.setViewVisibility(R.id.time_divider, View.VISIBLE);
5506                     setTextViewColorSecondary(contentView, R.id.time_divider, p);
5507                 }
5508                 if (mN.extras.getBoolean(EXTRA_SHOW_CHRONOMETER)) {
5509                     contentView.setViewVisibility(R.id.chronometer, View.VISIBLE);
5510                     contentView.setLong(R.id.chronometer, "setBase",
5511                             mN.when + (SystemClock.elapsedRealtime() - System.currentTimeMillis()));
5512                     contentView.setBoolean(R.id.chronometer, "setStarted", true);
5513                     boolean countsDown = mN.extras.getBoolean(EXTRA_CHRONOMETER_COUNT_DOWN);
5514                     contentView.setChronometerCountDown(R.id.chronometer, countsDown);
5515                     setTextViewColorSecondary(contentView, R.id.chronometer, p);
5516                 } else {
5517                     contentView.setViewVisibility(R.id.time, View.VISIBLE);
5518                     contentView.setLong(R.id.time, "setTime", mN.when);
5519                     setTextViewColorSecondary(contentView, R.id.time, p);
5520                 }
5521             } else {
5522                 // We still want a time to be set but gone, such that we can show and hide it
5523                 // on demand in case it's a child notification without anything in the header
5524                 contentView.setLong(R.id.time, "setTime", mN.when != 0 ? mN.when : mN.creationTime);
5525                 setTextViewColorSecondary(contentView, R.id.time, p);
5526             }
5527         }
5528 
5529         /**
5530          * @return true if the header text will be visible
5531          */
bindHeaderText(RemoteViews contentView, StandardTemplateParams p, boolean hasTextToLeft)5532         private boolean bindHeaderText(RemoteViews contentView, StandardTemplateParams p,
5533                 boolean hasTextToLeft) {
5534             if (p.mHideSubText) {
5535                 return false;
5536             }
5537             CharSequence summaryText = p.summaryText;
5538             if (summaryText == null && mStyle != null && mStyle.mSummaryTextSet
5539                     && mStyle.hasSummaryInHeader()) {
5540                 summaryText = mStyle.mSummaryText;
5541             }
5542             if (summaryText == null
5543                     && mContext.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N
5544                     && mN.extras.getCharSequence(EXTRA_INFO_TEXT) != null) {
5545                 summaryText = mN.extras.getCharSequence(EXTRA_INFO_TEXT);
5546             }
5547             if (!TextUtils.isEmpty(summaryText)) {
5548                 // TODO: Remove the span entirely to only have the string with propper formating.
5549                 contentView.setTextViewText(R.id.header_text, processTextSpans(
5550                         processLegacyText(summaryText)));
5551                 setTextViewColorSecondary(contentView, R.id.header_text, p);
5552                 contentView.setViewVisibility(R.id.header_text, View.VISIBLE);
5553                 if (hasTextToLeft) {
5554                     contentView.setViewVisibility(R.id.header_text_divider, View.VISIBLE);
5555                     setTextViewColorSecondary(contentView, R.id.header_text_divider, p);
5556                 }
5557                 return true;
5558             }
5559             return false;
5560         }
5561 
5562         /**
5563          * @return true if the secondary header text will be visible
5564          */
bindHeaderTextSecondary(RemoteViews contentView, StandardTemplateParams p, boolean hasTextToLeft)5565         private boolean bindHeaderTextSecondary(RemoteViews contentView, StandardTemplateParams p,
5566                 boolean hasTextToLeft) {
5567             if (p.mHideSubText) {
5568                 return false;
5569             }
5570             if (!TextUtils.isEmpty(p.headerTextSecondary)) {
5571                 contentView.setTextViewText(R.id.header_text_secondary, processTextSpans(
5572                         processLegacyText(p.headerTextSecondary)));
5573                 setTextViewColorSecondary(contentView, R.id.header_text_secondary, p);
5574                 contentView.setViewVisibility(R.id.header_text_secondary, View.VISIBLE);
5575                 if (hasTextToLeft) {
5576                     contentView.setViewVisibility(R.id.header_text_secondary_divider, View.VISIBLE);
5577                     setTextViewColorSecondary(contentView, R.id.header_text_secondary_divider, p);
5578                 }
5579                 return true;
5580             }
5581             return false;
5582         }
5583 
5584         /**
5585          * @hide
5586          */
5587         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
loadHeaderAppName()5588         public String loadHeaderAppName() {
5589             CharSequence name = null;
5590             final PackageManager pm = mContext.getPackageManager();
5591             if (mN.extras.containsKey(EXTRA_SUBSTITUTE_APP_NAME)) {
5592                 // only system packages which lump together a bunch of unrelated stuff
5593                 // may substitute a different name to make the purpose of the
5594                 // notification more clear. the correct package label should always
5595                 // be accessible via SystemUI.
5596                 final String pkg = mContext.getPackageName();
5597                 final String subName = mN.extras.getString(EXTRA_SUBSTITUTE_APP_NAME);
5598                 if (PackageManager.PERMISSION_GRANTED == pm.checkPermission(
5599                         android.Manifest.permission.SUBSTITUTE_NOTIFICATION_APP_NAME, pkg)) {
5600                     name = subName;
5601                 } else {
5602                     Log.w(TAG, "warning: pkg "
5603                             + pkg + " attempting to substitute app name '" + subName
5604                             + "' without holding perm "
5605                             + android.Manifest.permission.SUBSTITUTE_NOTIFICATION_APP_NAME);
5606                 }
5607             }
5608             if (TextUtils.isEmpty(name)) {
5609                 name = pm.getApplicationLabel(mContext.getApplicationInfo());
5610             }
5611             if (TextUtils.isEmpty(name)) {
5612                 // still nothing?
5613                 return null;
5614             }
5615 
5616             return String.valueOf(name);
5617         }
5618 
5619         /**
5620          * @return true if the app name will be visible
5621          */
bindHeaderAppName(RemoteViews contentView, StandardTemplateParams p, boolean force)5622         private boolean bindHeaderAppName(RemoteViews contentView, StandardTemplateParams p,
5623                 boolean force) {
5624             if (p.mViewType == StandardTemplateParams.VIEW_TYPE_MINIMIZED && !force) {
5625                 // unless the force flag is set, don't show the app name in the minimized state.
5626                 return false;
5627             }
5628             if (p.mHeaderless && p.hasTitle()) {
5629                 // the headerless template will have the TITLE in this position; return true to
5630                 // keep the divider visible between that title and the next text element.
5631                 return true;
5632             }
5633             if (p.mHideAppName) {
5634                 // The app name is being hidden, so we definitely want to return here.
5635                 // Assume that there is a title which will replace it in the header.
5636                 return p.hasTitle();
5637             }
5638             contentView.setViewVisibility(R.id.app_name_text, View.VISIBLE);
5639             contentView.setTextViewText(R.id.app_name_text, loadHeaderAppName());
5640             contentView.setTextColor(R.id.app_name_text, getSecondaryTextColor(p));
5641             return true;
5642         }
5643 
5644         /**
5645          * Determines if the notification should be colorized *for the purposes of applying colors*.
5646          * If this is the minimized view of a colorized notification, this will return false so that
5647          * internal coloring logic can still render the notification normally.
5648          */
isBackgroundColorized(StandardTemplateParams p)5649         private boolean isBackgroundColorized(StandardTemplateParams p) {
5650             return p.allowColorization && mN.isColorized();
5651         }
5652 
isCallActionColorCustomizable()5653         private boolean isCallActionColorCustomizable() {
5654             // NOTE: this doesn't need to check StandardTemplateParams.allowColorization because
5655             //  that is only used for disallowing colorization of headers for the minimized state,
5656             //  and neither of those conditions applies when showing actions.
5657             //  Not requiring StandardTemplateParams as an argument simplifies the creation process.
5658             return mN.isColorized() && mContext.getResources().getBoolean(
5659                     R.bool.config_callNotificationActionColorsRequireColorized);
5660         }
5661 
bindSmallIcon(RemoteViews contentView, StandardTemplateParams p)5662         private void bindSmallIcon(RemoteViews contentView, StandardTemplateParams p) {
5663             if (mN.mSmallIcon == null && mN.icon != 0) {
5664                 mN.mSmallIcon = Icon.createWithResource(mContext, mN.icon);
5665             }
5666             contentView.setImageViewIcon(R.id.icon, mN.mSmallIcon);
5667             contentView.setInt(R.id.icon, "setImageLevel", mN.iconLevel);
5668             processSmallIconColor(mN.mSmallIcon, contentView, p);
5669         }
5670 
5671         /**
5672          * @return true if the built notification will show the time or the chronometer; false
5673          *         otherwise
5674          */
showsTimeOrChronometer()5675         private boolean showsTimeOrChronometer() {
5676             return mN.showsTime() || mN.showsChronometer();
5677         }
5678 
resetStandardTemplateWithActions(RemoteViews big)5679         private void resetStandardTemplateWithActions(RemoteViews big) {
5680             // actions_container is only reset when there are no actions to avoid focus issues with
5681             // remote inputs.
5682             big.setViewVisibility(R.id.actions, View.GONE);
5683             big.removeAllViews(R.id.actions);
5684 
5685             big.setViewVisibility(R.id.notification_material_reply_container, View.GONE);
5686             big.setTextViewText(R.id.notification_material_reply_text_1, null);
5687             big.setViewVisibility(R.id.notification_material_reply_text_1_container, View.GONE);
5688             big.setViewVisibility(R.id.notification_material_reply_progress, View.GONE);
5689 
5690             big.setViewVisibility(R.id.notification_material_reply_text_2, View.GONE);
5691             big.setTextViewText(R.id.notification_material_reply_text_2, null);
5692             big.setViewVisibility(R.id.notification_material_reply_text_3, View.GONE);
5693             big.setTextViewText(R.id.notification_material_reply_text_3, null);
5694 
5695             // This may get erased by bindSnoozeAction
5696             big.setViewLayoutMarginDimen(R.id.notification_action_list_margin_target,
5697                     RemoteViews.MARGIN_BOTTOM, R.dimen.notification_content_margin);
5698         }
5699 
bindSnoozeAction(RemoteViews big, StandardTemplateParams p)5700         private void bindSnoozeAction(RemoteViews big, StandardTemplateParams p) {
5701             boolean hideSnoozeButton = mN.isForegroundService() || mN.fullScreenIntent != null
5702                     || isBackgroundColorized(p)
5703                     || p.mViewType != StandardTemplateParams.VIEW_TYPE_BIG;
5704             big.setBoolean(R.id.snooze_button, "setEnabled", !hideSnoozeButton);
5705             if (hideSnoozeButton) {
5706                 // Only hide; NotificationContentView will show it when it adds the click listener
5707                 big.setViewVisibility(R.id.snooze_button, View.GONE);
5708             }
5709 
5710             final boolean snoozeEnabled = !hideSnoozeButton
5711                     && mContext.getContentResolver() != null
5712                     && isSnoozeSettingEnabled();
5713             if (snoozeEnabled) {
5714                 big.setViewLayoutMarginDimen(R.id.notification_action_list_margin_target,
5715                         RemoteViews.MARGIN_BOTTOM, 0);
5716             }
5717         }
5718 
isSnoozeSettingEnabled()5719         private boolean isSnoozeSettingEnabled() {
5720             try {
5721                 return Settings.Secure.getInt(mContext.getContentResolver(),
5722                     Settings.Secure.SHOW_NOTIFICATION_SNOOZE, 0) == 1;
5723             } catch (SecurityException ex) {
5724                 // Most 3p apps can't access this snooze setting, so their NotificationListeners
5725                 // would be unable to create notification views if we propagated this exception.
5726                 return false;
5727             }
5728         }
5729 
5730         /**
5731          * Returns the actions that are not contextual.
5732          */
getNonContextualActions()5733         private @NonNull List<Notification.Action> getNonContextualActions() {
5734             if (mActions == null) return Collections.emptyList();
5735             List<Notification.Action> standardActions = new ArrayList<>();
5736             for (Notification.Action action : mActions) {
5737                 if (!action.isContextual()) {
5738                     standardActions.add(action);
5739                 }
5740             }
5741             return standardActions;
5742         }
5743 
applyStandardTemplateWithActions(int layoutId, StandardTemplateParams p, TemplateBindResult result)5744         private RemoteViews applyStandardTemplateWithActions(int layoutId,
5745                 StandardTemplateParams p, TemplateBindResult result) {
5746             RemoteViews big = applyStandardTemplate(layoutId, p, result);
5747 
5748             resetStandardTemplateWithActions(big);
5749             bindSnoozeAction(big, p);
5750             // color the snooze and bubble actions with the theme color
5751             ColorStateList actionColor = ColorStateList.valueOf(getStandardActionColor(p));
5752             big.setColorStateList(R.id.snooze_button, "setImageTintList", actionColor);
5753             big.setColorStateList(R.id.bubble_button, "setImageTintList", actionColor);
5754 
5755             boolean validRemoteInput = false;
5756 
5757             // In the UI, contextual actions appear separately from the standard actions, so we
5758             // filter them out here.
5759             List<Notification.Action> nonContextualActions = getNonContextualActions();
5760 
5761             int numActions = Math.min(nonContextualActions.size(), MAX_ACTION_BUTTONS);
5762             boolean emphazisedMode = mN.fullScreenIntent != null || p.mCallStyleActions;
5763             if (p.mCallStyleActions) {
5764                 // Clear view padding to allow buttons to start on the left edge.
5765                 // This must be done before 'setEmphasizedMode' which sets top/bottom margins.
5766                 big.setViewPadding(R.id.actions, 0, 0, 0, 0);
5767                 // Add an optional indent that will make buttons start at the correct column when
5768                 // there is enough space to do so (and fall back to the left edge if not).
5769                 big.setInt(R.id.actions, "setCollapsibleIndentDimen",
5770                         R.dimen.call_notification_collapsible_indent);
5771             }
5772             big.setBoolean(R.id.actions, "setEmphasizedMode", emphazisedMode);
5773             if (numActions > 0 && !p.mHideActions) {
5774                 big.setViewVisibility(R.id.actions_container, View.VISIBLE);
5775                 big.setViewVisibility(R.id.actions, View.VISIBLE);
5776                 big.setViewLayoutMarginDimen(R.id.notification_action_list_margin_target,
5777                         RemoteViews.MARGIN_BOTTOM, 0);
5778                 for (int i = 0; i < numActions; i++) {
5779                     Action action = nonContextualActions.get(i);
5780 
5781                     boolean actionHasValidInput = hasValidRemoteInput(action);
5782                     validRemoteInput |= actionHasValidInput;
5783 
5784                     final RemoteViews button = generateActionButton(action, emphazisedMode, p);
5785                     if (actionHasValidInput && !emphazisedMode) {
5786                         // Clear the drawable
5787                         button.setInt(R.id.action0, "setBackgroundResource", 0);
5788                     }
5789                     if (emphazisedMode && i > 0) {
5790                         // Clear start margin from non-first buttons to reduce the gap between them.
5791                         //  (8dp remaining gap is from all buttons' standard 4dp inset).
5792                         button.setViewLayoutMarginDimen(R.id.action0, RemoteViews.MARGIN_START, 0);
5793                     }
5794                     big.addView(R.id.actions, button);
5795                 }
5796             } else {
5797                 big.setViewVisibility(R.id.actions_container, View.GONE);
5798             }
5799 
5800             RemoteInputHistoryItem[] replyText = getParcelableArrayFromBundle(
5801                     mN.extras, EXTRA_REMOTE_INPUT_HISTORY_ITEMS, RemoteInputHistoryItem.class);
5802             if (validRemoteInput && replyText != null && replyText.length > 0
5803                     && !TextUtils.isEmpty(replyText[0].getText())
5804                     && p.maxRemoteInputHistory > 0) {
5805                 boolean showSpinner = mN.extras.getBoolean(EXTRA_SHOW_REMOTE_INPUT_SPINNER);
5806                 big.setViewVisibility(R.id.notification_material_reply_container, View.VISIBLE);
5807                 big.setViewVisibility(R.id.notification_material_reply_text_1_container,
5808                         View.VISIBLE);
5809                 big.setTextViewText(R.id.notification_material_reply_text_1,
5810                         processTextSpans(replyText[0].getText()));
5811                 setTextViewColorSecondary(big, R.id.notification_material_reply_text_1, p);
5812                 big.setViewVisibility(R.id.notification_material_reply_progress,
5813                         showSpinner ? View.VISIBLE : View.GONE);
5814                 big.setProgressIndeterminateTintList(
5815                         R.id.notification_material_reply_progress,
5816                         ColorStateList.valueOf(getPrimaryAccentColor(p)));
5817 
5818                 if (replyText.length > 1 && !TextUtils.isEmpty(replyText[1].getText())
5819                         && p.maxRemoteInputHistory > 1) {
5820                     big.setViewVisibility(R.id.notification_material_reply_text_2, View.VISIBLE);
5821                     big.setTextViewText(R.id.notification_material_reply_text_2,
5822                             processTextSpans(replyText[1].getText()));
5823                     setTextViewColorSecondary(big, R.id.notification_material_reply_text_2, p);
5824 
5825                     if (replyText.length > 2 && !TextUtils.isEmpty(replyText[2].getText())
5826                             && p.maxRemoteInputHistory > 2) {
5827                         big.setViewVisibility(
5828                                 R.id.notification_material_reply_text_3, View.VISIBLE);
5829                         big.setTextViewText(R.id.notification_material_reply_text_3,
5830                                 processTextSpans(replyText[2].getText()));
5831                         setTextViewColorSecondary(big, R.id.notification_material_reply_text_3, p);
5832                     }
5833                 }
5834             }
5835 
5836             return big;
5837         }
5838 
hasValidRemoteInput(Action action)5839         private boolean hasValidRemoteInput(Action action) {
5840             if (TextUtils.isEmpty(action.title) || action.actionIntent == null) {
5841                 // Weird actions
5842                 return false;
5843             }
5844 
5845             RemoteInput[] remoteInputs = action.getRemoteInputs();
5846             if (remoteInputs == null) {
5847                 return false;
5848             }
5849 
5850             for (RemoteInput r : remoteInputs) {
5851                 CharSequence[] choices = r.getChoices();
5852                 if (r.getAllowFreeFormInput() || (choices != null && choices.length != 0)) {
5853                     return true;
5854                 }
5855             }
5856             return false;
5857         }
5858 
5859         /**
5860          * Construct a RemoteViews for the final 1U notification layout. In order:
5861          *   1. Custom contentView from the caller
5862          *   2. Style's proposed content view
5863          *   3. Standard template view
5864          */
createContentView()5865         public RemoteViews createContentView() {
5866             return createContentView(false /* increasedheight */ );
5867         }
5868 
5869         // This code is executed on behalf of other apps' notifications, sometimes even by 3p apps,
5870         // a use case that is not supported by the Compat Framework library.  Workarounds to resolve
5871         // the change's state in NotificationManagerService were very complex. While it's possible
5872         // apps can detect the change, it's most likely that the changes will simply result in
5873         // visual regressions.
5874         @SuppressWarnings("AndroidFrameworkCompatChange")
fullyCustomViewRequiresDecoration(boolean fromStyle)5875         private boolean fullyCustomViewRequiresDecoration(boolean fromStyle) {
5876             // Custom views which come from a platform style class are safe, and thus do not need to
5877             // be wrapped.  Any subclass of those styles has the opportunity to make arbitrary
5878             // changes to the RemoteViews, and thus can't be trusted as a fully vetted view.
5879             if (fromStyle && PLATFORM_STYLE_CLASSES.contains(mStyle.getClass())) {
5880                 return false;
5881             }
5882             return mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.S;
5883         }
5884 
minimallyDecoratedContentView(@onNull RemoteViews customContent)5885         private RemoteViews minimallyDecoratedContentView(@NonNull RemoteViews customContent) {
5886             StandardTemplateParams p = mParams.reset()
5887                     .viewType(StandardTemplateParams.VIEW_TYPE_NORMAL)
5888                     .decorationType(StandardTemplateParams.DECORATION_MINIMAL)
5889                     .fillTextsFrom(this);
5890             TemplateBindResult result = new TemplateBindResult();
5891             RemoteViews standard = applyStandardTemplate(getBaseLayoutResource(), p, result);
5892             buildCustomContentIntoTemplate(mContext, standard, customContent,
5893                     p, result);
5894             return standard;
5895         }
5896 
minimallyDecoratedBigContentView(@onNull RemoteViews customContent)5897         private RemoteViews minimallyDecoratedBigContentView(@NonNull RemoteViews customContent) {
5898             StandardTemplateParams p = mParams.reset()
5899                     .viewType(StandardTemplateParams.VIEW_TYPE_BIG)
5900                     .decorationType(StandardTemplateParams.DECORATION_MINIMAL)
5901                     .fillTextsFrom(this);
5902             TemplateBindResult result = new TemplateBindResult();
5903             RemoteViews standard = applyStandardTemplateWithActions(getBigBaseLayoutResource(),
5904                     p, result);
5905             buildCustomContentIntoTemplate(mContext, standard, customContent,
5906                     p, result);
5907             makeHeaderExpanded(standard);
5908             return standard;
5909         }
5910 
minimallyDecoratedHeadsUpContentView( @onNull RemoteViews customContent)5911         private RemoteViews minimallyDecoratedHeadsUpContentView(
5912                 @NonNull RemoteViews customContent) {
5913             StandardTemplateParams p = mParams.reset()
5914                     .viewType(StandardTemplateParams.VIEW_TYPE_HEADS_UP)
5915                     .decorationType(StandardTemplateParams.DECORATION_MINIMAL)
5916                     .fillTextsFrom(this);
5917             TemplateBindResult result = new TemplateBindResult();
5918             RemoteViews standard = applyStandardTemplateWithActions(getHeadsUpBaseLayoutResource(),
5919                     p, result);
5920             buildCustomContentIntoTemplate(mContext, standard, customContent,
5921                     p, result);
5922             return standard;
5923         }
5924 
5925         /**
5926          * Construct a RemoteViews for the smaller content view.
5927          *
5928          *   @param increasedHeight true if this layout be created with an increased height. Some
5929          *   styles may support showing more then just that basic 1U size
5930          *   and the system may decide to render important notifications
5931          *   slightly bigger even when collapsed.
5932          *
5933          *   @hide
5934          */
createContentView(boolean increasedHeight)5935         public RemoteViews createContentView(boolean increasedHeight) {
5936             if (useExistingRemoteView(mN.contentView)) {
5937                 return fullyCustomViewRequiresDecoration(false /* fromStyle */)
5938                         ? minimallyDecoratedContentView(mN.contentView) : mN.contentView;
5939             } else if (mStyle != null) {
5940                 final RemoteViews styleView = mStyle.makeContentView(increasedHeight);
5941                 if (styleView != null) {
5942                     return fullyCustomViewRequiresDecoration(true /* fromStyle */)
5943                             ? minimallyDecoratedContentView(styleView) : styleView;
5944                 }
5945             }
5946             StandardTemplateParams p = mParams.reset()
5947                     .viewType(StandardTemplateParams.VIEW_TYPE_NORMAL)
5948                     .fillTextsFrom(this);
5949             return applyStandardTemplate(getBaseLayoutResource(), p, null /* result */);
5950         }
5951 
useExistingRemoteView(RemoteViews customContent)5952         private boolean useExistingRemoteView(RemoteViews customContent) {
5953             if (customContent == null) {
5954                 return false;
5955             }
5956             if (styleDisplaysCustomViewInline()) {
5957                 // the provided custom view is intended to be wrapped by the style.
5958                 return false;
5959             }
5960             if (fullyCustomViewRequiresDecoration(false)
5961                     && STANDARD_LAYOUTS.contains(customContent.getLayoutId())) {
5962                 // If the app's custom views are objects returned from Builder.create*ContentView()
5963                 // then the app is most likely attempting to spoof the user.  Even if they are not,
5964                 // the result would be broken (b/189189308) so we will ignore it.
5965                 Log.w(TAG, "For apps targeting S, a custom content view that is a modified "
5966                         + "version of any standard layout is disallowed.");
5967                 return false;
5968             }
5969             return true;
5970         }
5971 
5972         /**
5973          * Construct a RemoteViews for the final big notification layout.
5974          */
createBigContentView()5975         public RemoteViews createBigContentView() {
5976             RemoteViews result = null;
5977             if (useExistingRemoteView(mN.bigContentView)) {
5978                 return fullyCustomViewRequiresDecoration(false /* fromStyle */)
5979                         ? minimallyDecoratedBigContentView(mN.bigContentView) : mN.bigContentView;
5980             }
5981             if (mStyle != null) {
5982                 result = mStyle.makeBigContentView();
5983                 if (fullyCustomViewRequiresDecoration(true /* fromStyle */)) {
5984                     result = minimallyDecoratedBigContentView(result);
5985                 }
5986             }
5987             if (result == null) {
5988                 if (bigContentViewRequired()) {
5989                     StandardTemplateParams p = mParams.reset()
5990                             .viewType(StandardTemplateParams.VIEW_TYPE_BIG)
5991                             .allowTextWithProgress(true)
5992                             .fillTextsFrom(this);
5993                     result = applyStandardTemplateWithActions(getBigBaseLayoutResource(), p,
5994                             null /* result */);
5995                 }
5996             }
5997             makeHeaderExpanded(result);
5998             return result;
5999         }
6000 
6001         // This code is executed on behalf of other apps' notifications, sometimes even by 3p apps,
6002         // a use case that is not supported by the Compat Framework library.  Workarounds to resolve
6003         // the change's state in NotificationManagerService were very complex. While it's possible
6004         // apps can detect the change, it's most likely that the changes will simply result in
6005         // visual regressions.
6006         @SuppressWarnings("AndroidFrameworkCompatChange")
bigContentViewRequired()6007         private boolean bigContentViewRequired() {
6008             if (mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.S) {
6009                 return true;
6010             }
6011             // Notifications with contentView and without a bigContentView, style, or actions would
6012             // not have an expanded state before S, so showing the standard template expanded state
6013             // usually looks wrong, so we keep it simple and don't show the expanded state.
6014             boolean exempt = mN.contentView != null && mN.bigContentView == null
6015                     && mStyle == null && mActions.size() == 0;
6016             return !exempt;
6017         }
6018 
6019         /**
6020          * Construct a RemoteViews for the final notification header only. This will not be
6021          * colorized.
6022          *
6023          * @hide
6024          */
makeNotificationGroupHeader()6025         public RemoteViews makeNotificationGroupHeader() {
6026             return makeNotificationHeader(mParams.reset()
6027                     .viewType(StandardTemplateParams.VIEW_TYPE_GROUP_HEADER)
6028                     .fillTextsFrom(this));
6029         }
6030 
6031         /**
6032          * Construct a RemoteViews for the final notification header only. This will not be
6033          * colorized.
6034          *
6035          * @param p the template params to inflate this with
6036          */
makeNotificationHeader(StandardTemplateParams p)6037         private RemoteViews makeNotificationHeader(StandardTemplateParams p) {
6038             // Headers on their own are never colorized
6039             p.disallowColorization();
6040             RemoteViews header = new BuilderRemoteViews(mContext.getApplicationInfo(),
6041                     R.layout.notification_template_header);
6042             resetNotificationHeader(header);
6043             bindNotificationHeader(header, p);
6044             return header;
6045         }
6046 
6047         /**
6048          * Construct a RemoteViews for the ambient version of the notification.
6049          *
6050          * @hide
6051          */
makeAmbientNotification()6052         public RemoteViews makeAmbientNotification() {
6053             RemoteViews headsUpContentView = createHeadsUpContentView(false /* increasedHeight */);
6054             if (headsUpContentView != null) {
6055                 return headsUpContentView;
6056             }
6057             return createContentView();
6058         }
6059 
6060         /**
6061          * Adapt the Notification header if this view is used as an expanded view.
6062          *
6063          * @hide
6064          */
makeHeaderExpanded(RemoteViews result)6065         public static void makeHeaderExpanded(RemoteViews result) {
6066             if (result != null) {
6067                 result.setBoolean(R.id.expand_button, "setExpanded", true);
6068             }
6069         }
6070 
6071         /**
6072          * Construct a RemoteViews for the final heads-up notification layout.
6073          *
6074          * @param increasedHeight true if this layout be created with an increased height. Some
6075          * styles may support showing more then just that basic 1U size
6076          * and the system may decide to render important notifications
6077          * slightly bigger even when collapsed.
6078          *
6079          * @hide
6080          */
createHeadsUpContentView(boolean increasedHeight)6081         public RemoteViews createHeadsUpContentView(boolean increasedHeight) {
6082             if (useExistingRemoteView(mN.headsUpContentView)) {
6083                 return fullyCustomViewRequiresDecoration(false /* fromStyle */)
6084                         ? minimallyDecoratedHeadsUpContentView(mN.headsUpContentView)
6085                         : mN.headsUpContentView;
6086             } else if (mStyle != null) {
6087                 final RemoteViews styleView = mStyle.makeHeadsUpContentView(increasedHeight);
6088                 if (styleView != null) {
6089                     return fullyCustomViewRequiresDecoration(true /* fromStyle */)
6090                             ? minimallyDecoratedHeadsUpContentView(styleView) : styleView;
6091                 }
6092             } else if (mActions.size() == 0) {
6093                 return null;
6094             }
6095 
6096             // We only want at most a single remote input history to be shown here, otherwise
6097             // the content would become squished.
6098             StandardTemplateParams p = mParams.reset()
6099                     .viewType(StandardTemplateParams.VIEW_TYPE_HEADS_UP)
6100                     .fillTextsFrom(this)
6101                     .setMaxRemoteInputHistory(1);
6102             return applyStandardTemplateWithActions(getHeadsUpBaseLayoutResource(), p,
6103                     null /* result */);
6104         }
6105 
6106         /**
6107          * Construct a RemoteViews for the final heads-up notification layout.
6108          */
createHeadsUpContentView()6109         public RemoteViews createHeadsUpContentView() {
6110             return createHeadsUpContentView(false /* useIncreasedHeight */);
6111         }
6112 
6113         /**
6114          * Construct a RemoteViews for the display in public contexts like on the lockscreen.
6115          *
6116          * @param isLowPriority is this notification low priority
6117          * @hide
6118          */
6119         @UnsupportedAppUsage
makePublicContentView(boolean isLowPriority)6120         public RemoteViews makePublicContentView(boolean isLowPriority) {
6121             if (mN.publicVersion != null) {
6122                 final Builder builder = recoverBuilder(mContext, mN.publicVersion);
6123                 return builder.createContentView();
6124             }
6125             Bundle savedBundle = mN.extras;
6126             Style style = mStyle;
6127             mStyle = null;
6128             Icon largeIcon = mN.mLargeIcon;
6129             mN.mLargeIcon = null;
6130             Bitmap largeIconLegacy = mN.largeIcon;
6131             mN.largeIcon = null;
6132             ArrayList<Action> actions = mActions;
6133             mActions = new ArrayList<>();
6134             Bundle publicExtras = new Bundle();
6135             publicExtras.putBoolean(EXTRA_SHOW_WHEN,
6136                     savedBundle.getBoolean(EXTRA_SHOW_WHEN));
6137             publicExtras.putBoolean(EXTRA_SHOW_CHRONOMETER,
6138                     savedBundle.getBoolean(EXTRA_SHOW_CHRONOMETER));
6139             publicExtras.putBoolean(EXTRA_CHRONOMETER_COUNT_DOWN,
6140                     savedBundle.getBoolean(EXTRA_CHRONOMETER_COUNT_DOWN));
6141             String appName = savedBundle.getString(EXTRA_SUBSTITUTE_APP_NAME);
6142             if (appName != null) {
6143                 publicExtras.putString(EXTRA_SUBSTITUTE_APP_NAME, appName);
6144             }
6145             mN.extras = publicExtras;
6146             RemoteViews view;
6147             StandardTemplateParams params = mParams.reset()
6148                     .viewType(StandardTemplateParams.VIEW_TYPE_PUBLIC)
6149                     .fillTextsFrom(this);
6150             if (isLowPriority) {
6151                 params.highlightExpander(false);
6152             }
6153             view = makeNotificationHeader(params);
6154             view.setBoolean(R.id.notification_header, "setExpandOnlyOnButton", true);
6155             mN.extras = savedBundle;
6156             mN.mLargeIcon = largeIcon;
6157             mN.largeIcon = largeIconLegacy;
6158             mActions = actions;
6159             mStyle = style;
6160             return view;
6161         }
6162 
6163         /**
6164          * Construct a content view for the display when low - priority
6165          *
6166          * @param useRegularSubtext uses the normal subtext set if there is one available. Otherwise
6167          *                          a new subtext is created consisting of the content of the
6168          *                          notification.
6169          * @hide
6170          */
makeLowPriorityContentView(boolean useRegularSubtext)6171         public RemoteViews makeLowPriorityContentView(boolean useRegularSubtext) {
6172             StandardTemplateParams p = mParams.reset()
6173                     .viewType(StandardTemplateParams.VIEW_TYPE_MINIMIZED)
6174                     .highlightExpander(false)
6175                     .fillTextsFrom(this);
6176             if (!useRegularSubtext || TextUtils.isEmpty(p.summaryText)) {
6177                 p.summaryText(createSummaryText());
6178             }
6179             RemoteViews header = makeNotificationHeader(p);
6180             header.setBoolean(R.id.notification_header, "setAcceptAllTouches", true);
6181             // The low priority header has no app name and shows the text
6182             header.setBoolean(R.id.notification_header, "styleTextAsTitle", true);
6183             return header;
6184         }
6185 
createSummaryText()6186         private CharSequence createSummaryText() {
6187             CharSequence titleText = mN.extras.getCharSequence(Notification.EXTRA_TITLE);
6188             if (USE_ONLY_TITLE_IN_LOW_PRIORITY_SUMMARY) {
6189                 return titleText;
6190             }
6191             SpannableStringBuilder summary = new SpannableStringBuilder();
6192             if (titleText == null) {
6193                 titleText = mN.extras.getCharSequence(Notification.EXTRA_TITLE_BIG);
6194             }
6195             BidiFormatter bidi = BidiFormatter.getInstance();
6196             if (titleText != null) {
6197                 summary.append(bidi.unicodeWrap(titleText));
6198             }
6199             CharSequence contentText = mN.extras.getCharSequence(Notification.EXTRA_TEXT);
6200             if (titleText != null && contentText != null) {
6201                 summary.append(bidi.unicodeWrap(mContext.getText(
6202                         R.string.notification_header_divider_symbol_with_spaces)));
6203             }
6204             if (contentText != null) {
6205                 summary.append(bidi.unicodeWrap(contentText));
6206             }
6207             return summary;
6208         }
6209 
generateActionButton(Action action, boolean emphasizedMode, StandardTemplateParams p)6210         private RemoteViews generateActionButton(Action action, boolean emphasizedMode,
6211                 StandardTemplateParams p) {
6212             final boolean tombstone = (action.actionIntent == null);
6213             final RemoteViews button = new BuilderRemoteViews(mContext.getApplicationInfo(),
6214                     getActionButtonLayoutResource(emphasizedMode, tombstone));
6215             if (!tombstone) {
6216                 button.setOnClickPendingIntent(R.id.action0, action.actionIntent);
6217             }
6218             button.setContentDescription(R.id.action0, action.title);
6219             if (action.mRemoteInputs != null) {
6220                 button.setRemoteInputs(R.id.action0, action.mRemoteInputs);
6221             }
6222             if (emphasizedMode) {
6223                 // change the background bgColor
6224                 CharSequence title = action.title;
6225                 int buttonFillColor = getColors(p).getSecondaryAccentColor();
6226                 if (tombstone) {
6227                     buttonFillColor = setAlphaComponentByFloatDimen(mContext,
6228                             ContrastColorUtil.resolveSecondaryColor(
6229                                     mContext, getColors(p).getBackgroundColor(), mInNightMode),
6230                             R.dimen.notification_action_disabled_container_alpha);
6231                 }
6232                 if (isLegacy()) {
6233                     title = ContrastColorUtil.clearColorSpans(title);
6234                 } else {
6235                     // Check for a full-length span color to use as the button fill color.
6236                     Integer fullLengthColor = getFullLengthSpanColor(title);
6237                     if (fullLengthColor != null) {
6238                         // Ensure the custom button fill has 1.3:1 contrast w/ notification bg.
6239                         int notifBackgroundColor = getColors(p).getBackgroundColor();
6240                         buttonFillColor = ensureButtonFillContrast(
6241                                 fullLengthColor, notifBackgroundColor);
6242                     }
6243                     // Remove full-length color spans and ensure text contrast with the button fill.
6244                     title = ensureColorSpanContrast(title, buttonFillColor);
6245                 }
6246                 button.setTextViewText(R.id.action0, processTextSpans(title));
6247                 int textColor = ContrastColorUtil.resolvePrimaryColor(mContext,
6248                         buttonFillColor, mInNightMode);
6249                 if (tombstone) {
6250                     textColor = setAlphaComponentByFloatDimen(mContext,
6251                             ContrastColorUtil.resolveSecondaryColor(
6252                                     mContext, getColors(p).getBackgroundColor(), mInNightMode),
6253                             R.dimen.notification_action_disabled_content_alpha);
6254                 }
6255                 button.setTextColor(R.id.action0, textColor);
6256                 // We only want about 20% alpha for the ripple
6257                 final int rippleColor = (textColor & 0x00ffffff) | 0x33000000;
6258                 button.setColorStateList(R.id.action0, "setRippleColor",
6259                         ColorStateList.valueOf(rippleColor));
6260                 button.setColorStateList(R.id.action0, "setButtonBackground",
6261                         ColorStateList.valueOf(buttonFillColor));
6262                 if (p.mCallStyleActions) {
6263                     button.setImageViewIcon(R.id.action0, action.getIcon());
6264                     boolean priority = action.getExtras().getBoolean(CallStyle.KEY_ACTION_PRIORITY);
6265                     button.setBoolean(R.id.action0, "setIsPriority", priority);
6266                     int minWidthDimen =
6267                             priority ? R.dimen.call_notification_system_action_min_width : 0;
6268                     button.setIntDimen(R.id.action0, "setMinimumWidth", minWidthDimen);
6269                 }
6270             } else {
6271                 button.setTextViewText(R.id.action0, processTextSpans(
6272                         processLegacyText(action.title)));
6273                 button.setTextColor(R.id.action0, getStandardActionColor(p));
6274             }
6275             // CallStyle notifications add action buttons which don't actually exist in mActions,
6276             //  so we have to omit the index in that case.
6277             int actionIndex = mActions.indexOf(action);
6278             if (actionIndex != -1) {
6279                 button.setIntTag(R.id.action0, R.id.notification_action_index_tag, actionIndex);
6280             }
6281             return button;
6282         }
6283 
getActionButtonLayoutResource(boolean emphasizedMode, boolean tombstone)6284         private int getActionButtonLayoutResource(boolean emphasizedMode, boolean tombstone) {
6285             if (emphasizedMode) {
6286                 return tombstone ? getEmphasizedTombstoneActionLayoutResource()
6287                         : getEmphasizedActionLayoutResource();
6288             } else {
6289                 return tombstone ? getActionTombstoneLayoutResource()
6290                         : getActionLayoutResource();
6291             }
6292         }
6293 
6294         /**
6295          * Set the alpha component of {@code color} to be {@code alphaDimenResId}.
6296          */
setAlphaComponentByFloatDimen(Context context, @ColorInt int color, @DimenRes int alphaDimenResId)6297         private static int setAlphaComponentByFloatDimen(Context context, @ColorInt int color,
6298                 @DimenRes int alphaDimenResId) {
6299             final TypedValue alphaValue = new TypedValue();
6300             context.getResources().getValue(alphaDimenResId, alphaValue, true);
6301             return ColorUtils.setAlphaComponent(color, Math.round(alphaValue.getFloat() * 255));
6302         }
6303 
6304         /**
6305          * Extract the color from a full-length span from the text.
6306          *
6307          * @param charSequence the charSequence containing spans
6308          * @return the raw color of the text's last full-length span containing a color, or null if
6309          * no full-length span sets the text color.
6310          * @hide
6311          */
6312         @VisibleForTesting
6313         @Nullable
getFullLengthSpanColor(CharSequence charSequence)6314         public static Integer getFullLengthSpanColor(CharSequence charSequence) {
6315             // NOTE: this method preserves the functionality that for a CharSequence with multiple
6316             // full-length spans, the color of the last one is used.
6317             Integer result = null;
6318             if (charSequence instanceof Spanned) {
6319                 Spanned ss = (Spanned) charSequence;
6320                 Object[] spans = ss.getSpans(0, ss.length(), Object.class);
6321                 // First read through all full-length spans to get the button fill color, which will
6322                 //  be used as the background color for ensuring contrast of non-full-length spans.
6323                 for (Object span : spans) {
6324                     int spanStart = ss.getSpanStart(span);
6325                     int spanEnd = ss.getSpanEnd(span);
6326                     boolean fullLength = (spanEnd - spanStart) == charSequence.length();
6327                     if (!fullLength) {
6328                         continue;
6329                     }
6330                     if (span instanceof TextAppearanceSpan) {
6331                         TextAppearanceSpan originalSpan = (TextAppearanceSpan) span;
6332                         ColorStateList textColor = originalSpan.getTextColor();
6333                         if (textColor != null) {
6334                             result = textColor.getDefaultColor();
6335                         }
6336                     } else if (span instanceof ForegroundColorSpan) {
6337                         ForegroundColorSpan originalSpan = (ForegroundColorSpan) span;
6338                         result = originalSpan.getForegroundColor();
6339                     }
6340                 }
6341             }
6342             return result;
6343         }
6344 
6345         /**
6346          * Ensures contrast on color spans against a background color.
6347          * Note that any full-length color spans will be removed instead of being contrasted.
6348          *
6349          * @param charSequence the charSequence on which the spans are
6350          * @param background the background color to ensure the contrast against
6351          * @return the contrasted charSequence
6352          * @hide
6353          */
6354         @VisibleForTesting
ensureColorSpanContrast(CharSequence charSequence, int background)6355         public static CharSequence ensureColorSpanContrast(CharSequence charSequence,
6356                 int background) {
6357             if (charSequence instanceof Spanned) {
6358                 Spanned ss = (Spanned) charSequence;
6359                 Object[] spans = ss.getSpans(0, ss.length(), Object.class);
6360                 SpannableStringBuilder builder = new SpannableStringBuilder(ss.toString());
6361                 for (Object span : spans) {
6362                     Object resultSpan = span;
6363                     int spanStart = ss.getSpanStart(span);
6364                     int spanEnd = ss.getSpanEnd(span);
6365                     boolean fullLength = (spanEnd - spanStart) == charSequence.length();
6366                     if (resultSpan instanceof CharacterStyle) {
6367                         resultSpan = ((CharacterStyle) span).getUnderlying();
6368                     }
6369                     if (resultSpan instanceof TextAppearanceSpan) {
6370                         TextAppearanceSpan originalSpan = (TextAppearanceSpan) resultSpan;
6371                         ColorStateList textColor = originalSpan.getTextColor();
6372                         if (textColor != null) {
6373                             if (fullLength) {
6374                                 // Let's drop the color from the span
6375                                 textColor = null;
6376                             } else {
6377                                 int[] colors = textColor.getColors();
6378                                 int[] newColors = new int[colors.length];
6379                                 for (int i = 0; i < newColors.length; i++) {
6380                                     boolean isBgDark = isColorDark(background);
6381                                     newColors[i] = ContrastColorUtil.ensureLargeTextContrast(
6382                                             colors[i], background, isBgDark);
6383                                 }
6384                                 textColor = new ColorStateList(textColor.getStates().clone(),
6385                                         newColors);
6386                             }
6387                             resultSpan = new TextAppearanceSpan(
6388                                     originalSpan.getFamily(),
6389                                     originalSpan.getTextStyle(),
6390                                     originalSpan.getTextSize(),
6391                                     textColor,
6392                                     originalSpan.getLinkTextColor());
6393                         }
6394                     } else if (resultSpan instanceof ForegroundColorSpan) {
6395                         if (fullLength) {
6396                             resultSpan = null;
6397                         } else {
6398                             ForegroundColorSpan originalSpan = (ForegroundColorSpan) resultSpan;
6399                             int foregroundColor = originalSpan.getForegroundColor();
6400                             boolean isBgDark = isColorDark(background);
6401                             foregroundColor = ContrastColorUtil.ensureLargeTextContrast(
6402                                     foregroundColor, background, isBgDark);
6403                             resultSpan = new ForegroundColorSpan(foregroundColor);
6404                         }
6405                     } else {
6406                         resultSpan = span;
6407                     }
6408                     if (resultSpan != null) {
6409                         builder.setSpan(resultSpan, spanStart, spanEnd, ss.getSpanFlags(span));
6410                     }
6411                 }
6412                 return builder;
6413             }
6414             return charSequence;
6415         }
6416 
6417         /**
6418          * Determines if the color is light or dark.  Specifically, this is using the same metric as
6419          * {@link ContrastColorUtil#resolvePrimaryColor(Context, int, boolean)} and peers so that
6420          * the direction of color shift is consistent.
6421          *
6422          * @param color the color to check
6423          * @return true if the color has higher contrast with white than black
6424          * @hide
6425          */
isColorDark(int color)6426         public static boolean isColorDark(int color) {
6427             // as per ContrastColorUtil.shouldUseDark, this uses the color contrast midpoint.
6428             return ContrastColorUtil.calculateLuminance(color) <= 0.17912878474;
6429         }
6430 
6431         /**
6432          * Finds a button fill color with sufficient contrast over bg (1.3:1) that has the same hue
6433          * as the original color, but is lightened or darkened depending on whether the background
6434          * is dark or light.
6435          *
6436          * @hide
6437          */
6438         @VisibleForTesting
ensureButtonFillContrast(int color, int bg)6439         public static int ensureButtonFillContrast(int color, int bg) {
6440             return isColorDark(bg)
6441                     ? ContrastColorUtil.findContrastColorAgainstDark(color, bg, true, 1.3)
6442                     : ContrastColorUtil.findContrastColor(color, bg, true, 1.3);
6443         }
6444 
6445 
6446         /**
6447          * @return Whether we are currently building a notification from a legacy (an app that
6448          *         doesn't create material notifications by itself) app.
6449          */
isLegacy()6450         private boolean isLegacy() {
6451             if (!mIsLegacyInitialized) {
6452                 mIsLegacy = mContext.getApplicationInfo().targetSdkVersion
6453                         < Build.VERSION_CODES.LOLLIPOP;
6454                 mIsLegacyInitialized = true;
6455             }
6456             return mIsLegacy;
6457         }
6458 
6459         private CharSequence processLegacyText(CharSequence charSequence) {
6460             boolean isAlreadyLightText = isLegacy() || textColorsNeedInversion();
6461             if (isAlreadyLightText) {
6462                 return getColorUtil().invertCharSequenceColors(charSequence);
6463             } else {
6464                 return charSequence;
6465             }
6466         }
6467 
6468         /**
6469          * Apply any necessary colors to the small icon
6470          */
6471         private void processSmallIconColor(Icon smallIcon, RemoteViews contentView,
6472                 StandardTemplateParams p) {
6473             boolean colorable = !isLegacy() || getColorUtil().isGrayscaleIcon(mContext, smallIcon);
6474             int color = getSmallIconColor(p);
6475             contentView.setInt(R.id.icon, "setBackgroundColor",
6476                     getBackgroundColor(p));
6477             contentView.setInt(R.id.icon, "setOriginalIconColor",
6478                     colorable ? color : COLOR_INVALID);
6479         }
6480 
6481         /**
6482          * Make the largeIcon dark if it's a fake smallIcon (that is,
6483          * if it's grayscale).
6484          */
6485         // TODO: also check bounds, transparency, that sort of thing.
6486         private void processLargeLegacyIcon(Icon largeIcon, RemoteViews contentView,
6487                 StandardTemplateParams p) {
6488             if (largeIcon != null && isLegacy()
6489                     && getColorUtil().isGrayscaleIcon(mContext, largeIcon)) {
6490                 // resolve color will fall back to the default when legacy
6491                 int color = getSmallIconColor(p);
6492                 contentView.setInt(R.id.icon, "setOriginalIconColor", color);
6493             }
6494         }
6495 
6496         private void sanitizeColor() {
6497             if (mN.color != COLOR_DEFAULT) {
6498                 mN.color |= 0xFF000000; // no alpha for custom colors
6499             }
6500         }
6501 
6502         /**
6503          * Gets the standard action button color
6504          */
6505         private @ColorInt int getStandardActionColor(Notification.StandardTemplateParams p) {
6506             return mTintActionButtons || isBackgroundColorized(p)
6507                     ? getPrimaryAccentColor(p) : getSecondaryTextColor(p);
6508         }
6509 
6510         /**
6511          * Gets the foreground color of the small icon.  If the notification is colorized, this
6512          * is the primary text color, otherwise it's the contrast-adjusted app-provided color.
6513          */
6514         private @ColorInt int getSmallIconColor(StandardTemplateParams p) {
6515             return getColors(p).getContrastColor();
6516         }
6517 
6518         /** @return the theme's accent color for colored UI elements. */
6519         private @ColorInt int getPrimaryAccentColor(StandardTemplateParams p) {
6520             return getColors(p).getPrimaryAccentColor();
6521         }
6522 
6523         /**
6524          * Apply the unstyled operations and return a new {@link Notification} object.
6525          * @hide
6526          */
6527         @NonNull
6528         public Notification buildUnstyled() {
6529             if (mActions.size() > 0) {
6530                 mN.actions = new Action[mActions.size()];
6531                 mActions.toArray(mN.actions);
6532             }
6533             if (!mPersonList.isEmpty()) {
6534                 mN.extras.putParcelableArrayList(EXTRA_PEOPLE_LIST, mPersonList);
6535             }
6536             if (mN.bigContentView != null || mN.contentView != null
6537                     || mN.headsUpContentView != null) {
6538                 mN.extras.putBoolean(EXTRA_CONTAINS_CUSTOM_VIEW, true);
6539             }
6540             return mN;
6541         }
6542 
6543         /**
6544          * Creates a Builder from an existing notification so further changes can be made.
6545          * @param context The context for your application / activity.
6546          * @param n The notification to create a Builder from.
6547          */
6548         @NonNull
recoverBuilder(Context context, Notification n)6549         public static Notification.Builder recoverBuilder(Context context, Notification n) {
6550             // Re-create notification context so we can access app resources.
6551             ApplicationInfo applicationInfo = n.extras.getParcelable(
6552                     EXTRA_BUILDER_APPLICATION_INFO, ApplicationInfo.class);
6553             Context builderContext;
6554             if (applicationInfo != null) {
6555                 try {
6556                     builderContext = context.createApplicationContext(applicationInfo,
6557                             Context.CONTEXT_RESTRICTED);
6558                 } catch (NameNotFoundException e) {
6559                     Log.e(TAG, "ApplicationInfo " + applicationInfo + " not found");
6560                     builderContext = context;  // try with our context
6561                 }
6562             } else {
6563                 builderContext = context; // try with given context
6564             }
6565 
6566             return new Builder(builderContext, n);
6567         }
6568 
6569         /**
6570          * Determines whether the platform can generate contextual actions for a notification.
6571          * By default this is true.
6572          */
6573         @NonNull
setAllowSystemGeneratedContextualActions(boolean allowed)6574         public Builder setAllowSystemGeneratedContextualActions(boolean allowed) {
6575             mN.mAllowSystemGeneratedContextualActions = allowed;
6576             return this;
6577         }
6578 
6579         /**
6580          * @deprecated Use {@link #build()} instead.
6581          */
6582         @Deprecated
getNotification()6583         public Notification getNotification() {
6584             return build();
6585         }
6586 
6587         /**
6588          * Combine all of the options that have been set and return a new {@link Notification}
6589          * object.
6590          *
6591          * If this notification has {@link BubbleMetadata} attached that was created with
6592          * a shortcutId a check will be performed to ensure the shortcutId supplied to bubble
6593          * metadata matches the shortcutId set on the  notification builder, if one was set.
6594          * If the shortcutId's were specified but do not match, an exception is thrown here.
6595          *
6596          * @see BubbleMetadata.Builder#Builder(String)
6597          * @see #setShortcutId(String)
6598          */
6599         @NonNull
build()6600         public Notification build() {
6601             // Check shortcut id matches
6602             if (mN.mShortcutId != null
6603                     && mN.mBubbleMetadata != null
6604                     && mN.mBubbleMetadata.getShortcutId() != null
6605                     && !mN.mShortcutId.equals(mN.mBubbleMetadata.getShortcutId())) {
6606                 throw new IllegalArgumentException(
6607                         "Notification and BubbleMetadata shortcut id's don't match,"
6608                                 + " notification: " + mN.mShortcutId
6609                                 + " vs bubble: " + mN.mBubbleMetadata.getShortcutId());
6610             }
6611 
6612             // first, add any extras from the calling code
6613             if (mUserExtras != null) {
6614                 mN.extras = getAllExtras();
6615             }
6616 
6617             mN.creationTime = System.currentTimeMillis();
6618 
6619             // lazy stuff from mContext; see comment in Builder(Context, Notification)
6620             Notification.addFieldsFromContext(mContext, mN);
6621 
6622             buildUnstyled();
6623 
6624             if (mStyle != null) {
6625                 mStyle.reduceImageSizes(mContext);
6626                 mStyle.purgeResources();
6627                 mStyle.validate(mContext);
6628                 mStyle.buildStyled(mN);
6629             }
6630             mN.reduceImageSizes(mContext);
6631 
6632             if (mContext.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N
6633                     && !styleDisplaysCustomViewInline()) {
6634                 RemoteViews newContentView = mN.contentView;
6635                 RemoteViews newBigContentView = mN.bigContentView;
6636                 RemoteViews newHeadsUpContentView = mN.headsUpContentView;
6637                 if (newContentView == null) {
6638                     newContentView = createContentView();
6639                     mN.extras.putInt(EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT,
6640                             newContentView.getSequenceNumber());
6641                 }
6642                 if (newBigContentView == null) {
6643                     newBigContentView = createBigContentView();
6644                     if (newBigContentView != null) {
6645                         mN.extras.putInt(EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT,
6646                                 newBigContentView.getSequenceNumber());
6647                     }
6648                 }
6649                 if (newHeadsUpContentView == null) {
6650                     newHeadsUpContentView = createHeadsUpContentView();
6651                     if (newHeadsUpContentView != null) {
6652                         mN.extras.putInt(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT,
6653                                 newHeadsUpContentView.getSequenceNumber());
6654                     }
6655                 }
6656                 // Don't set any of the content views until after they have all been generated,
6657                 //  to avoid the generated .contentView triggering the logic which skips generating
6658                 //  the .bigContentView.
6659                 mN.contentView = newContentView;
6660                 mN.bigContentView = newBigContentView;
6661                 mN.headsUpContentView = newHeadsUpContentView;
6662             }
6663 
6664             if ((mN.defaults & DEFAULT_LIGHTS) != 0) {
6665                 mN.flags |= FLAG_SHOW_LIGHTS;
6666             }
6667 
6668             mN.allPendingIntents = null;
6669 
6670             return mN;
6671         }
6672 
styleDisplaysCustomViewInline()6673         private boolean styleDisplaysCustomViewInline() {
6674             return mStyle != null && mStyle.displayCustomViewInline();
6675         }
6676 
6677         /**
6678          * Apply this Builder to an existing {@link Notification} object.
6679          *
6680          * @hide
6681          */
6682         @NonNull
buildInto(@onNull Notification n)6683         public Notification buildInto(@NonNull Notification n) {
6684             build().cloneInto(n, true);
6685             return n;
6686         }
6687 
6688         /**
6689          * Removes RemoteViews that were created for compatibility from {@param n}, if they did not
6690          * change.
6691          *
6692          * @return {@param n}, if no stripping is needed, otherwise a stripped clone of {@param n}.
6693          *
6694          * @hide
6695          */
maybeCloneStrippedForDelivery(Notification n)6696         public static Notification maybeCloneStrippedForDelivery(Notification n) {
6697             String templateClass = n.extras.getString(EXTRA_TEMPLATE);
6698 
6699             // Only strip views for known Styles because we won't know how to
6700             // re-create them otherwise.
6701             if (!TextUtils.isEmpty(templateClass)
6702                     && getNotificationStyleClass(templateClass) == null) {
6703                 return n;
6704             }
6705 
6706             // Only strip unmodified BuilderRemoteViews.
6707             boolean stripContentView = n.contentView instanceof BuilderRemoteViews &&
6708                     n.extras.getInt(EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT, -1) ==
6709                             n.contentView.getSequenceNumber();
6710             boolean stripBigContentView = n.bigContentView instanceof BuilderRemoteViews &&
6711                     n.extras.getInt(EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT, -1) ==
6712                             n.bigContentView.getSequenceNumber();
6713             boolean stripHeadsUpContentView = n.headsUpContentView instanceof BuilderRemoteViews &&
6714                     n.extras.getInt(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT, -1) ==
6715                             n.headsUpContentView.getSequenceNumber();
6716 
6717             // Nothing to do here, no need to clone.
6718             if (!stripContentView && !stripBigContentView && !stripHeadsUpContentView) {
6719                 return n;
6720             }
6721 
6722             Notification clone = n.clone();
6723             if (stripContentView) {
6724                 clone.contentView = null;
6725                 clone.extras.remove(EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT);
6726             }
6727             if (stripBigContentView) {
6728                 clone.bigContentView = null;
6729                 clone.extras.remove(EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT);
6730             }
6731             if (stripHeadsUpContentView) {
6732                 clone.headsUpContentView = null;
6733                 clone.extras.remove(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT);
6734             }
6735             return clone;
6736         }
6737 
6738         @UnsupportedAppUsage
getBaseLayoutResource()6739         private int getBaseLayoutResource() {
6740             return R.layout.notification_template_material_base;
6741         }
6742 
getHeadsUpBaseLayoutResource()6743         private int getHeadsUpBaseLayoutResource() {
6744             return R.layout.notification_template_material_heads_up_base;
6745         }
6746 
getBigBaseLayoutResource()6747         private int getBigBaseLayoutResource() {
6748             return R.layout.notification_template_material_big_base;
6749         }
6750 
getBigPictureLayoutResource()6751         private int getBigPictureLayoutResource() {
6752             return R.layout.notification_template_material_big_picture;
6753         }
6754 
getBigTextLayoutResource()6755         private int getBigTextLayoutResource() {
6756             return R.layout.notification_template_material_big_text;
6757         }
6758 
getInboxLayoutResource()6759         private int getInboxLayoutResource() {
6760             return R.layout.notification_template_material_inbox;
6761         }
6762 
getMessagingLayoutResource()6763         private int getMessagingLayoutResource() {
6764             return R.layout.notification_template_material_messaging;
6765         }
6766 
getBigMessagingLayoutResource()6767         private int getBigMessagingLayoutResource() {
6768             return R.layout.notification_template_material_big_messaging;
6769         }
6770 
getConversationLayoutResource()6771         private int getConversationLayoutResource() {
6772             return R.layout.notification_template_material_conversation;
6773         }
6774 
getActionLayoutResource()6775         private int getActionLayoutResource() {
6776             return R.layout.notification_material_action;
6777         }
6778 
getEmphasizedActionLayoutResource()6779         private int getEmphasizedActionLayoutResource() {
6780             return R.layout.notification_material_action_emphasized;
6781         }
6782 
getEmphasizedTombstoneActionLayoutResource()6783         private int getEmphasizedTombstoneActionLayoutResource() {
6784             return R.layout.notification_material_action_emphasized_tombstone;
6785         }
6786 
getActionTombstoneLayoutResource()6787         private int getActionTombstoneLayoutResource() {
6788             return R.layout.notification_material_action_tombstone;
6789         }
6790 
getBackgroundColor(StandardTemplateParams p)6791         private @ColorInt int getBackgroundColor(StandardTemplateParams p) {
6792             return getColors(p).getBackgroundColor();
6793         }
6794 
textColorsNeedInversion()6795         private boolean textColorsNeedInversion() {
6796             if (mStyle == null || !MediaStyle.class.equals(mStyle.getClass())) {
6797                 return false;
6798             }
6799             int targetSdkVersion = mContext.getApplicationInfo().targetSdkVersion;
6800             return targetSdkVersion > Build.VERSION_CODES.M
6801                     && targetSdkVersion < Build.VERSION_CODES.O;
6802         }
6803 
6804         /**
6805          * Get the text that should be displayed in the statusBar when heads upped. This is
6806          * usually just the app name, but may be different depending on the style.
6807          *
6808          * @param publicMode If true, return a text that is safe to display in public.
6809          *
6810          * @hide
6811          */
getHeadsUpStatusBarText(boolean publicMode)6812         public CharSequence getHeadsUpStatusBarText(boolean publicMode) {
6813             if (mStyle != null && !publicMode) {
6814                 CharSequence text = mStyle.getHeadsUpStatusBarText();
6815                 if (!TextUtils.isEmpty(text)) {
6816                     return text;
6817                 }
6818             }
6819             return loadHeaderAppName();
6820         }
6821 
6822         /**
6823          * @return if this builder uses a template
6824          *
6825          * @hide
6826          */
usesTemplate()6827         public boolean usesTemplate() {
6828             return (mN.contentView == null && mN.headsUpContentView == null
6829                     && mN.bigContentView == null)
6830                     || styleDisplaysCustomViewInline();
6831         }
6832     }
6833 
6834     /**
6835      * Reduces the image sizes to conform to a maximum allowed size. This also processes all custom
6836      * remote views.
6837      *
6838      * @hide
6839      */
reduceImageSizes(Context context)6840     void reduceImageSizes(Context context) {
6841         if (extras.getBoolean(EXTRA_REDUCED_IMAGES)) {
6842             return;
6843         }
6844         boolean isLowRam = ActivityManager.isLowRamDeviceStatic();
6845 
6846         if (mSmallIcon != null
6847                 // Only bitmap icons can be downscaled.
6848                 && (mSmallIcon.getType() == Icon.TYPE_BITMAP
6849                         || mSmallIcon.getType() == Icon.TYPE_ADAPTIVE_BITMAP)) {
6850             Resources resources = context.getResources();
6851             int maxSize = resources.getDimensionPixelSize(
6852                     isLowRam ? R.dimen.notification_small_icon_size_low_ram
6853                             : R.dimen.notification_small_icon_size);
6854             mSmallIcon.scaleDownIfNecessary(maxSize, maxSize);
6855         }
6856 
6857         if (mLargeIcon != null || largeIcon != null) {
6858             Resources resources = context.getResources();
6859             Class<? extends Style> style = getNotificationStyle();
6860             int maxSize = resources.getDimensionPixelSize(isLowRam
6861                     ? R.dimen.notification_right_icon_size_low_ram
6862                     : R.dimen.notification_right_icon_size);
6863             if (mLargeIcon != null) {
6864                 mLargeIcon.scaleDownIfNecessary(maxSize, maxSize);
6865             }
6866             if (largeIcon != null) {
6867                 largeIcon = Icon.scaleDownIfNecessary(largeIcon, maxSize, maxSize);
6868             }
6869         }
6870         reduceImageSizesForRemoteView(contentView, context, isLowRam);
6871         reduceImageSizesForRemoteView(headsUpContentView, context, isLowRam);
6872         reduceImageSizesForRemoteView(bigContentView, context, isLowRam);
6873         extras.putBoolean(EXTRA_REDUCED_IMAGES, true);
6874     }
6875 
reduceImageSizesForRemoteView(RemoteViews remoteView, Context context, boolean isLowRam)6876     private void reduceImageSizesForRemoteView(RemoteViews remoteView, Context context,
6877             boolean isLowRam) {
6878         if (remoteView != null) {
6879             Resources resources = context.getResources();
6880             int maxWidth = resources.getDimensionPixelSize(isLowRam
6881                     ? R.dimen.notification_custom_view_max_image_width_low_ram
6882                     : R.dimen.notification_custom_view_max_image_width);
6883             int maxHeight = resources.getDimensionPixelSize(isLowRam
6884                     ? R.dimen.notification_custom_view_max_image_height_low_ram
6885                     : R.dimen.notification_custom_view_max_image_height);
6886             remoteView.reduceImageSizes(maxWidth, maxHeight);
6887         }
6888     }
6889 
6890     /**
6891      * @return whether this notification is a foreground service notification
6892      * @hide
6893      */
isForegroundService()6894     public boolean isForegroundService() {
6895         return (flags & Notification.FLAG_FOREGROUND_SERVICE) != 0;
6896     }
6897 
6898     /**
6899      * Describe whether this notification's content such that it should always display
6900      * immediately when tied to a foreground service, even if the system might generally
6901      * avoid showing the notifications for short-lived foreground service lifetimes.
6902      *
6903      * Immediate visibility of the Notification is indicated when:
6904      * <ul>
6905      *     <li>The app specifically indicated it with
6906      *         {@link Notification.Builder#setForegroundServiceBehavior(int)
6907      *         setForegroundServiceBehavior(BEHAVIOR_IMMEDIATE_DISPLAY)}</li>
6908      *     <li>It is a media notification or has an associated media session</li>
6909      *     <li>It is a call or navigation notification</li>
6910      *     <li>It provides additional action affordances</li>
6911      * </ul>
6912      *
6913      * If the app has specified
6914      * {@code NotificationBuilder.setForegroundServiceBehavior(BEHAVIOR_DEFERRED_DISPLAY)}
6915      * then this method will return {@code false} and notification visibility will be
6916      * deferred following the service's transition to the foreground state even in the
6917      * circumstances described above.
6918      *
6919      * @return whether this notification should be displayed immediately when
6920      * its associated service transitions to the foreground state
6921      * @hide
6922      */
6923     @TestApi
shouldShowForegroundImmediately()6924     public boolean shouldShowForegroundImmediately() {
6925         // Has the app demanded immediate display?
6926         if (mFgsDeferBehavior == FOREGROUND_SERVICE_IMMEDIATE) {
6927             return true;
6928         }
6929 
6930         // Has the app demanded deferred display?
6931         if (mFgsDeferBehavior == FOREGROUND_SERVICE_DEFERRED) {
6932             return false;
6933         }
6934 
6935         // We show these sorts of notifications immediately in the absence of
6936         // any explicit app declaration
6937         if (isMediaNotification()
6938                     || CATEGORY_CALL.equals(category)
6939                     || CATEGORY_NAVIGATION.equals(category)
6940                     || (actions != null && actions.length > 0)) {
6941             return true;
6942         }
6943 
6944         // No extenuating circumstances: defer visibility
6945         return false;
6946     }
6947 
6948     /**
6949      * Has forced deferral for FGS purposes been specified?
6950      * @hide
6951      */
isForegroundDisplayForceDeferred()6952     public boolean isForegroundDisplayForceDeferred() {
6953         return FOREGROUND_SERVICE_DEFERRED == mFgsDeferBehavior;
6954     }
6955 
6956     /**
6957      * @return the style class of this notification
6958      * @hide
6959      */
getNotificationStyle()6960     public Class<? extends Notification.Style> getNotificationStyle() {
6961         String templateClass = extras.getString(Notification.EXTRA_TEMPLATE);
6962 
6963         if (!TextUtils.isEmpty(templateClass)) {
6964             return Notification.getNotificationStyleClass(templateClass);
6965         }
6966         return null;
6967     }
6968 
6969     /**
6970      * @return whether the style of this notification is the one provided
6971      * @hide
6972      */
isStyle(@onNull Class<? extends Style> styleClass)6973     public boolean isStyle(@NonNull Class<? extends Style> styleClass) {
6974         String templateClass = extras.getString(Notification.EXTRA_TEMPLATE);
6975         return Objects.equals(templateClass, styleClass.getName());
6976     }
6977 
6978     /**
6979      * @return true if this notification is colorized *for the purposes of ranking*.  If the
6980      * {@link #color} is {@link #COLOR_DEFAULT} this will be true, even though the actual
6981      * appearance of the notification may not be "colorized".
6982      *
6983      * @hide
6984      */
isColorized()6985     public boolean isColorized() {
6986         return extras.getBoolean(EXTRA_COLORIZED)
6987                 && (hasColorizedPermission() || isForegroundService());
6988     }
6989 
6990     /**
6991      * Returns whether an app can colorize due to the android.permission.USE_COLORIZED_NOTIFICATIONS
6992      * permission. The permission is checked when a notification is enqueued.
6993      *
6994      * @hide
6995      */
hasColorizedPermission()6996     public boolean hasColorizedPermission() {
6997         return (flags & Notification.FLAG_CAN_COLORIZE) != 0;
6998     }
6999 
7000     /**
7001      * @return true if this is a media style notification with a media session
7002      *
7003      * @hide
7004      */
isMediaNotification()7005     public boolean isMediaNotification() {
7006         Class<? extends Style> style = getNotificationStyle();
7007         boolean isMediaStyle = (MediaStyle.class.equals(style)
7008                 || DecoratedMediaCustomViewStyle.class.equals(style));
7009 
7010         boolean hasMediaSession = extras.getParcelable(Notification.EXTRA_MEDIA_SESSION,
7011                 MediaSession.Token.class) != null;
7012 
7013         return isMediaStyle && hasMediaSession;
7014     }
7015 
7016     /**
7017      * @return true if this notification is showing as a bubble
7018      *
7019      * @hide
7020      */
isBubbleNotification()7021     public boolean isBubbleNotification() {
7022         return (flags & Notification.FLAG_BUBBLE) != 0;
7023     }
7024 
hasLargeIcon()7025     private boolean hasLargeIcon() {
7026         return mLargeIcon != null || largeIcon != null;
7027     }
7028 
7029     /**
7030      * @return true if the notification will show the time; false otherwise
7031      * @hide
7032      */
showsTime()7033     public boolean showsTime() {
7034         return when != 0 && extras.getBoolean(EXTRA_SHOW_WHEN);
7035     }
7036 
7037     /**
7038      * @return true if the notification will show a chronometer; false otherwise
7039      * @hide
7040      */
showsChronometer()7041     public boolean showsChronometer() {
7042         return when != 0 && extras.getBoolean(EXTRA_SHOW_CHRONOMETER);
7043     }
7044 
7045     /**
7046      * @return true if the notification has image
7047      */
hasImage()7048     public boolean hasImage() {
7049         if (isStyle(MessagingStyle.class) && extras != null) {
7050             final Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES);
7051             if (!ArrayUtils.isEmpty(messages)) {
7052                 for (MessagingStyle.Message m : MessagingStyle.Message
7053                         .getMessagesFromBundleArray(messages)) {
7054                     if (m.getDataUri() != null
7055                             && m.getDataMimeType() != null
7056                             && m.getDataMimeType().startsWith("image/")) {
7057                         return true;
7058                     }
7059                 }
7060             }
7061         } else if (hasLargeIcon()) {
7062             return true;
7063         } else if (extras.containsKey(EXTRA_BACKGROUND_IMAGE_URI)) {
7064             return true;
7065         }
7066         return false;
7067     }
7068 
7069 
7070     /**
7071      * @removed
7072      */
7073     @SystemApi
getNotificationStyleClass(String templateClass)7074     public static Class<? extends Style> getNotificationStyleClass(String templateClass) {
7075         for (Class<? extends Style> innerClass : PLATFORM_STYLE_CLASSES) {
7076             if (templateClass.equals(innerClass.getName())) {
7077                 return innerClass;
7078             }
7079         }
7080         return null;
7081     }
7082 
buildCustomContentIntoTemplate(@onNull Context context, @NonNull RemoteViews template, @Nullable RemoteViews customContent, @NonNull StandardTemplateParams p, @NonNull TemplateBindResult result)7083     private static void buildCustomContentIntoTemplate(@NonNull Context context,
7084             @NonNull RemoteViews template, @Nullable RemoteViews customContent,
7085             @NonNull StandardTemplateParams p, @NonNull TemplateBindResult result) {
7086         int childIndex = -1;
7087         if (customContent != null) {
7088             // Need to clone customContent before adding, because otherwise it can no longer be
7089             // parceled independently of remoteViews.
7090             customContent = customContent.clone();
7091             if (p.mHeaderless) {
7092                 template.removeFromParent(R.id.notification_top_line);
7093                 // We do not know how many lines ar emote view has, so we presume it has 2;  this
7094                 // ensures that we don't under-pad the content, which could lead to abuse, at the
7095                 // cost of making single-line custom content over-padded.
7096                 Builder.setHeaderlessVerticalMargins(template, p, true /* hasSecondLine */);
7097             } else {
7098                 // also update the end margin to account for the large icon or expander
7099                 Resources resources = context.getResources();
7100                 result.mTitleMarginSet.applyToView(template, R.id.notification_main_column,
7101                         resources.getDimension(R.dimen.notification_content_margin_end)
7102                                 / resources.getDisplayMetrics().density);
7103             }
7104             template.removeAllViewsExceptId(R.id.notification_main_column, R.id.progress);
7105             template.addView(R.id.notification_main_column, customContent, 0 /* index */);
7106             template.addFlags(RemoteViews.FLAG_REAPPLY_DISALLOWED);
7107             childIndex = 0;
7108         }
7109         template.setIntTag(R.id.notification_main_column,
7110                 com.android.internal.R.id.notification_custom_view_index_tag,
7111                 childIndex);
7112     }
7113 
7114     /**
7115      * An object that can apply a rich notification style to a {@link Notification.Builder}
7116      * object.
7117      */
7118     public static abstract class Style {
7119 
7120         /**
7121          * The number of items allowed simulatanously in the remote input history.
7122          * @hide
7123          */
7124         static final int MAX_REMOTE_INPUT_HISTORY_LINES = 3;
7125         private CharSequence mBigContentTitle;
7126 
7127         /**
7128          * @hide
7129          */
7130         protected CharSequence mSummaryText = null;
7131 
7132         /**
7133          * @hide
7134          */
7135         protected boolean mSummaryTextSet = false;
7136 
7137         protected Builder mBuilder;
7138 
7139         /**
7140          * Overrides ContentTitle in the big form of the template.
7141          * This defaults to the value passed to setContentTitle().
7142          */
internalSetBigContentTitle(CharSequence title)7143         protected void internalSetBigContentTitle(CharSequence title) {
7144             mBigContentTitle = title;
7145         }
7146 
7147         /**
7148          * Set the first line of text after the detail section in the big form of the template.
7149          */
internalSetSummaryText(CharSequence cs)7150         protected void internalSetSummaryText(CharSequence cs) {
7151             mSummaryText = cs;
7152             mSummaryTextSet = true;
7153         }
7154 
setBuilder(Builder builder)7155         public void setBuilder(Builder builder) {
7156             if (mBuilder != builder) {
7157                 mBuilder = builder;
7158                 if (mBuilder != null) {
7159                     mBuilder.setStyle(this);
7160                 }
7161             }
7162         }
7163 
checkBuilder()7164         protected void checkBuilder() {
7165             if (mBuilder == null) {
7166                 throw new IllegalArgumentException("Style requires a valid Builder object");
7167             }
7168         }
7169 
getStandardView(int layoutId)7170         protected RemoteViews getStandardView(int layoutId) {
7171             // TODO(jeffdq): set the view type based on the layout resource?
7172             StandardTemplateParams p = mBuilder.mParams.reset()
7173                     .viewType(StandardTemplateParams.VIEW_TYPE_UNSPECIFIED)
7174                     .fillTextsFrom(mBuilder);
7175             return getStandardView(layoutId, p, null);
7176         }
7177 
7178 
7179         /**
7180          * Get the standard view for this style.
7181          *
7182          * @param layoutId The layout id to use.
7183          * @param p the params for this inflation.
7184          * @param result The result where template bind information is saved.
7185          * @return A remoteView for this style.
7186          * @hide
7187          */
getStandardView(int layoutId, StandardTemplateParams p, TemplateBindResult result)7188         protected RemoteViews getStandardView(int layoutId, StandardTemplateParams p,
7189                 TemplateBindResult result) {
7190             checkBuilder();
7191 
7192             if (mBigContentTitle != null) {
7193                 p.title = mBigContentTitle;
7194             }
7195 
7196             return mBuilder.applyStandardTemplateWithActions(layoutId, p, result);
7197         }
7198 
7199         /**
7200          * Construct a Style-specific RemoteViews for the collapsed notification layout.
7201          * The default implementation has nothing additional to add.
7202          *
7203          * @param increasedHeight true if this layout be created with an increased height.
7204          * @hide
7205          */
makeContentView(boolean increasedHeight)7206         public RemoteViews makeContentView(boolean increasedHeight) {
7207             return null;
7208         }
7209 
7210         /**
7211          * Construct a Style-specific RemoteViews for the final big notification layout.
7212          * @hide
7213          */
makeBigContentView()7214         public RemoteViews makeBigContentView() {
7215             return null;
7216         }
7217 
7218         /**
7219          * Construct a Style-specific RemoteViews for the final HUN layout.
7220          *
7221          * @param increasedHeight true if this layout be created with an increased height.
7222          * @hide
7223          */
makeHeadsUpContentView(boolean increasedHeight)7224         public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
7225             return null;
7226         }
7227 
7228         /**
7229          * Apply any style-specific extras to this notification before shipping it out.
7230          * @hide
7231          */
addExtras(Bundle extras)7232         public void addExtras(Bundle extras) {
7233             if (mSummaryTextSet) {
7234                 extras.putCharSequence(EXTRA_SUMMARY_TEXT, mSummaryText);
7235             }
7236             if (mBigContentTitle != null) {
7237                 extras.putCharSequence(EXTRA_TITLE_BIG, mBigContentTitle);
7238             }
7239             extras.putString(EXTRA_TEMPLATE, this.getClass().getName());
7240         }
7241 
7242         /**
7243          * Reconstruct the internal state of this Style object from extras.
7244          * @hide
7245          */
restoreFromExtras(Bundle extras)7246         protected void restoreFromExtras(Bundle extras) {
7247             if (extras.containsKey(EXTRA_SUMMARY_TEXT)) {
7248                 mSummaryText = extras.getCharSequence(EXTRA_SUMMARY_TEXT);
7249                 mSummaryTextSet = true;
7250             }
7251             if (extras.containsKey(EXTRA_TITLE_BIG)) {
7252                 mBigContentTitle = extras.getCharSequence(EXTRA_TITLE_BIG);
7253             }
7254         }
7255 
7256 
7257         /**
7258          * @hide
7259          */
buildStyled(Notification wip)7260         public Notification buildStyled(Notification wip) {
7261             addExtras(wip.extras);
7262             return wip;
7263         }
7264 
7265         /**
7266          * @hide
7267          */
purgeResources()7268         public void purgeResources() {}
7269 
7270         /**
7271          * Calls {@link android.app.Notification.Builder#build()} on the Builder this Style is
7272          * attached to.
7273          *
7274          * @return the fully constructed Notification.
7275          */
build()7276         public Notification build() {
7277             checkBuilder();
7278             return mBuilder.build();
7279         }
7280 
7281         /**
7282          * @hide
7283          * @return Whether we should put the summary be put into the notification header
7284          */
hasSummaryInHeader()7285         public boolean hasSummaryInHeader() {
7286             return true;
7287         }
7288 
7289         /**
7290          * @hide
7291          * @return Whether custom content views are displayed inline in the style
7292          */
displayCustomViewInline()7293         public boolean displayCustomViewInline() {
7294             return false;
7295         }
7296 
7297         /**
7298          * Reduces the image sizes contained in this style.
7299          *
7300          * @hide
7301          */
reduceImageSizes(Context context)7302         public void reduceImageSizes(Context context) {
7303         }
7304 
7305         /**
7306          * Validate that this style was properly composed. This is called at build time.
7307          * @hide
7308          */
validate(Context context)7309         public void validate(Context context) {
7310         }
7311 
7312         /**
7313          * @hide
7314          */
areNotificationsVisiblyDifferent(Style other)7315         public abstract boolean areNotificationsVisiblyDifferent(Style other);
7316 
7317         /**
7318          * @return the text that should be displayed in the statusBar when heads-upped.
7319          * If {@code null} is returned, the default implementation will be used.
7320          *
7321          * @hide
7322          */
getHeadsUpStatusBarText()7323         public CharSequence getHeadsUpStatusBarText() {
7324             return null;
7325         }
7326     }
7327 
7328     /**
7329      * Helper class for generating large-format notifications that include a large image attachment.
7330      *
7331      * Here's how you'd set the <code>BigPictureStyle</code> on a notification:
7332      * <pre class="prettyprint">
7333      * Notification notif = new Notification.Builder(mContext)
7334      *     .setContentTitle(&quot;New photo from &quot; + sender.toString())
7335      *     .setContentText(subject)
7336      *     .setSmallIcon(R.drawable.new_post)
7337      *     .setLargeIcon(aBitmap)
7338      *     .setStyle(new Notification.BigPictureStyle()
7339      *         .bigPicture(aBigBitmap))
7340      *     .build();
7341      * </pre>
7342      *
7343      * @see Notification#bigContentView
7344      */
7345     public static class BigPictureStyle extends Style {
7346         private Icon mPictureIcon;
7347         private Icon mBigLargeIcon;
7348         private boolean mBigLargeIconSet = false;
7349         private CharSequence mPictureContentDescription;
7350         private boolean mShowBigPictureWhenCollapsed;
7351 
BigPictureStyle()7352         public BigPictureStyle() {
7353         }
7354 
7355         /**
7356          * @deprecated use {@code BigPictureStyle()}.
7357          */
7358         @Deprecated
BigPictureStyle(Builder builder)7359         public BigPictureStyle(Builder builder) {
7360             setBuilder(builder);
7361         }
7362 
7363         /**
7364          * Overrides ContentTitle in the big form of the template.
7365          * This defaults to the value passed to setContentTitle().
7366          */
7367         @NonNull
setBigContentTitle(@ullable CharSequence title)7368         public BigPictureStyle setBigContentTitle(@Nullable CharSequence title) {
7369             internalSetBigContentTitle(safeCharSequence(title));
7370             return this;
7371         }
7372 
7373         /**
7374          * Set the first line of text after the detail section in the big form of the template.
7375          */
7376         @NonNull
setSummaryText(@ullable CharSequence cs)7377         public BigPictureStyle setSummaryText(@Nullable CharSequence cs) {
7378             internalSetSummaryText(safeCharSequence(cs));
7379             return this;
7380         }
7381 
7382         /**
7383          * Set the content description of the big picture.
7384          */
7385         @NonNull
setContentDescription( @ullable CharSequence contentDescription)7386         public BigPictureStyle setContentDescription(
7387                 @Nullable CharSequence contentDescription) {
7388             mPictureContentDescription = contentDescription;
7389             return this;
7390         }
7391 
7392         /**
7393          * @hide
7394          */
7395         @Nullable
getBigPicture()7396         public Icon getBigPicture() {
7397             if (mPictureIcon != null) {
7398                 return mPictureIcon;
7399             }
7400             return null;
7401         }
7402 
7403         /**
7404          * Provide the bitmap to be used as the payload for the BigPicture notification.
7405          */
7406         @NonNull
bigPicture(@ullable Bitmap b)7407         public BigPictureStyle bigPicture(@Nullable Bitmap b) {
7408             mPictureIcon = b == null ? null : Icon.createWithBitmap(b);
7409             return this;
7410         }
7411 
7412         /**
7413          * Provide the content Uri to be used as the payload for the BigPicture notification.
7414          */
7415         @NonNull
bigPicture(@ullable Icon icon)7416         public BigPictureStyle bigPicture(@Nullable Icon icon) {
7417             mPictureIcon = icon;
7418             return this;
7419         }
7420 
7421         /**
7422          * When set, the {@link #bigPicture(Bitmap) big picture} of this style will be promoted and
7423          * shown in place of the {@link Builder#setLargeIcon(Icon) large icon} in the collapsed
7424          * state of this notification.
7425          */
7426         @NonNull
showBigPictureWhenCollapsed(boolean show)7427         public BigPictureStyle showBigPictureWhenCollapsed(boolean show) {
7428             mShowBigPictureWhenCollapsed = show;
7429             return this;
7430         }
7431 
7432         /**
7433          * Override the large icon when the big notification is shown.
7434          */
7435         @NonNull
bigLargeIcon(@ullable Bitmap b)7436         public BigPictureStyle bigLargeIcon(@Nullable Bitmap b) {
7437             return bigLargeIcon(b != null ? Icon.createWithBitmap(b) : null);
7438         }
7439 
7440         /**
7441          * Override the large icon when the big notification is shown.
7442          */
7443         @NonNull
bigLargeIcon(@ullable Icon icon)7444         public BigPictureStyle bigLargeIcon(@Nullable Icon icon) {
7445             mBigLargeIconSet = true;
7446             mBigLargeIcon = icon;
7447             return this;
7448         }
7449 
7450         /** @hide */
7451         public static final int MIN_ASHMEM_BITMAP_SIZE = 128 * (1 << 10);
7452 
7453         /**
7454          * @hide
7455          */
7456         @Override
purgeResources()7457         public void purgeResources() {
7458             super.purgeResources();
7459             if (mPictureIcon != null) {
7460                 mPictureIcon.convertToAshmem();
7461             }
7462             if (mBigLargeIcon != null) {
7463                 mBigLargeIcon.convertToAshmem();
7464             }
7465         }
7466 
7467         /**
7468          * @hide
7469          */
7470         @Override
reduceImageSizes(Context context)7471         public void reduceImageSizes(Context context) {
7472             super.reduceImageSizes(context);
7473             Resources resources = context.getResources();
7474             boolean isLowRam = ActivityManager.isLowRamDeviceStatic();
7475             if (mPictureIcon != null) {
7476                 int maxPictureWidth = resources.getDimensionPixelSize(isLowRam
7477                         ? R.dimen.notification_big_picture_max_height_low_ram
7478                         : R.dimen.notification_big_picture_max_height);
7479                 int maxPictureHeight = resources.getDimensionPixelSize(isLowRam
7480                         ? R.dimen.notification_big_picture_max_width_low_ram
7481                         : R.dimen.notification_big_picture_max_width);
7482                 mPictureIcon.scaleDownIfNecessary(maxPictureWidth, maxPictureHeight);
7483             }
7484             if (mBigLargeIcon != null) {
7485                 int rightIconSize = resources.getDimensionPixelSize(isLowRam
7486                         ? R.dimen.notification_right_icon_size_low_ram
7487                         : R.dimen.notification_right_icon_size);
7488                 mBigLargeIcon.scaleDownIfNecessary(rightIconSize, rightIconSize);
7489             }
7490         }
7491 
7492         /**
7493          * @hide
7494          */
7495         @Override
makeContentView(boolean increasedHeight)7496         public RemoteViews makeContentView(boolean increasedHeight) {
7497             if (mPictureIcon == null || !mShowBigPictureWhenCollapsed) {
7498                 return super.makeContentView(increasedHeight);
7499             }
7500 
7501             StandardTemplateParams p = mBuilder.mParams.reset()
7502                     .viewType(StandardTemplateParams.VIEW_TYPE_NORMAL)
7503                     .fillTextsFrom(mBuilder)
7504                     .promotedPicture(mPictureIcon);
7505             return getStandardView(mBuilder.getBaseLayoutResource(), p, null /* result */);
7506         }
7507 
7508         /**
7509          * @hide
7510          */
7511         @Override
makeHeadsUpContentView(boolean increasedHeight)7512         public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
7513             if (mPictureIcon == null || !mShowBigPictureWhenCollapsed) {
7514                 return super.makeHeadsUpContentView(increasedHeight);
7515             }
7516 
7517             StandardTemplateParams p = mBuilder.mParams.reset()
7518                     .viewType(StandardTemplateParams.VIEW_TYPE_HEADS_UP)
7519                     .fillTextsFrom(mBuilder)
7520                     .promotedPicture(mPictureIcon);
7521             return getStandardView(mBuilder.getHeadsUpBaseLayoutResource(), p, null /* result */);
7522         }
7523 
7524         /**
7525          * @hide
7526          */
makeBigContentView()7527         public RemoteViews makeBigContentView() {
7528             // Replace mN.mLargeIcon with mBigLargeIcon if mBigLargeIconSet
7529             // This covers the following cases:
7530             //   1. mBigLargeIconSet -> mBigLargeIcon (null or non-null) applies, overrides
7531             //          mN.mLargeIcon
7532             //   2. !mBigLargeIconSet -> mN.mLargeIcon applies
7533             Icon oldLargeIcon = null;
7534             Bitmap largeIconLegacy = null;
7535             if (mBigLargeIconSet) {
7536                 oldLargeIcon = mBuilder.mN.mLargeIcon;
7537                 mBuilder.mN.mLargeIcon = mBigLargeIcon;
7538                 // The legacy largeIcon might not allow us to clear the image, as it's taken in
7539                 // replacement if the other one is null. Because we're restoring these legacy icons
7540                 // for old listeners, this is in general non-null.
7541                 largeIconLegacy = mBuilder.mN.largeIcon;
7542                 mBuilder.mN.largeIcon = null;
7543             }
7544 
7545             StandardTemplateParams p = mBuilder.mParams.reset()
7546                     .viewType(StandardTemplateParams.VIEW_TYPE_BIG).fillTextsFrom(mBuilder);
7547             RemoteViews contentView = getStandardView(mBuilder.getBigPictureLayoutResource(),
7548                     p, null /* result */);
7549             if (mSummaryTextSet) {
7550                 contentView.setTextViewText(R.id.text, mBuilder.processTextSpans(
7551                         mBuilder.processLegacyText(mSummaryText)));
7552                 mBuilder.setTextViewColorSecondary(contentView, R.id.text, p);
7553                 contentView.setViewVisibility(R.id.text, View.VISIBLE);
7554             }
7555 
7556             if (mBigLargeIconSet) {
7557                 mBuilder.mN.mLargeIcon = oldLargeIcon;
7558                 mBuilder.mN.largeIcon = largeIconLegacy;
7559             }
7560 
7561             contentView.setImageViewIcon(R.id.big_picture, mPictureIcon);
7562 
7563             if (mPictureContentDescription != null) {
7564                 contentView.setContentDescription(R.id.big_picture, mPictureContentDescription);
7565             }
7566 
7567             return contentView;
7568         }
7569 
7570         /**
7571          * @hide
7572          */
addExtras(Bundle extras)7573         public void addExtras(Bundle extras) {
7574             super.addExtras(extras);
7575 
7576             if (mBigLargeIconSet) {
7577                 extras.putParcelable(EXTRA_LARGE_ICON_BIG, mBigLargeIcon);
7578             }
7579             if (mPictureContentDescription != null) {
7580                 extras.putCharSequence(EXTRA_PICTURE_CONTENT_DESCRIPTION,
7581                         mPictureContentDescription);
7582             }
7583             extras.putBoolean(EXTRA_SHOW_BIG_PICTURE_WHEN_COLLAPSED, mShowBigPictureWhenCollapsed);
7584 
7585             // If the icon contains a bitmap, use the old extra so that listeners which look for
7586             // that extra can still find the picture.  Don't include the new extra in that case,
7587             // to avoid duplicating data.
7588             if (mPictureIcon != null && mPictureIcon.getType() == Icon.TYPE_BITMAP) {
7589                 extras.putParcelable(EXTRA_PICTURE, mPictureIcon.getBitmap());
7590                 extras.putParcelable(EXTRA_PICTURE_ICON, null);
7591             } else {
7592                 extras.putParcelable(EXTRA_PICTURE, null);
7593                 extras.putParcelable(EXTRA_PICTURE_ICON, mPictureIcon);
7594             }
7595         }
7596 
7597         /**
7598          * @hide
7599          */
7600         @Override
restoreFromExtras(Bundle extras)7601         protected void restoreFromExtras(Bundle extras) {
7602             super.restoreFromExtras(extras);
7603 
7604             if (extras.containsKey(EXTRA_LARGE_ICON_BIG)) {
7605                 mBigLargeIconSet = true;
7606                 mBigLargeIcon = extras.getParcelable(EXTRA_LARGE_ICON_BIG, Icon.class);
7607             }
7608 
7609             if (extras.containsKey(EXTRA_PICTURE_CONTENT_DESCRIPTION)) {
7610                 mPictureContentDescription =
7611                         extras.getCharSequence(EXTRA_PICTURE_CONTENT_DESCRIPTION);
7612             }
7613 
7614             mShowBigPictureWhenCollapsed = extras.getBoolean(EXTRA_SHOW_BIG_PICTURE_WHEN_COLLAPSED);
7615 
7616             mPictureIcon = getPictureIcon(extras);
7617         }
7618 
7619         /** @hide */
7620         @Nullable
getPictureIcon(@ullable Bundle extras)7621         public static Icon getPictureIcon(@Nullable Bundle extras) {
7622             if (extras == null) return null;
7623             // When this style adds a picture, we only add one of the keys.  If both were added,
7624             // it would most likely be a legacy app trying to override the picture in some way.
7625             // Because of that case it's better to give precedence to the legacy field.
7626             Bitmap bitmapPicture = extras.getParcelable(EXTRA_PICTURE, Bitmap.class);
7627             if (bitmapPicture != null) {
7628                 return Icon.createWithBitmap(bitmapPicture);
7629             } else {
7630                 return extras.getParcelable(EXTRA_PICTURE_ICON, Icon.class);
7631             }
7632         }
7633 
7634         /**
7635          * @hide
7636          */
7637         @Override
hasSummaryInHeader()7638         public boolean hasSummaryInHeader() {
7639             return false;
7640         }
7641 
7642         /**
7643          * @hide
7644          * Note that we aren't actually comparing the contents of the bitmaps here, so this
7645          * is only doing a cursory inspection. Bitmaps of equal size will appear the same.
7646          */
7647         @Override
areNotificationsVisiblyDifferent(Style other)7648         public boolean areNotificationsVisiblyDifferent(Style other) {
7649             if (other == null || getClass() != other.getClass()) {
7650                 return true;
7651             }
7652             BigPictureStyle otherS = (BigPictureStyle) other;
7653             return areIconsObviouslyDifferent(getBigPicture(), otherS.getBigPicture());
7654         }
7655 
areIconsObviouslyDifferent(Icon a, Icon b)7656         private static boolean areIconsObviouslyDifferent(Icon a, Icon b) {
7657             if (a == b) {
7658                 return false;
7659             }
7660             if (a == null || b == null) {
7661                 return true;
7662             }
7663             if (a.sameAs(b)) {
7664                 return false;
7665             }
7666             final int aType = a.getType();
7667             if (aType != b.getType()) {
7668                 return true;
7669             }
7670             if (aType == Icon.TYPE_BITMAP || aType == Icon.TYPE_ADAPTIVE_BITMAP) {
7671                 final Bitmap aBitmap = a.getBitmap();
7672                 final Bitmap bBitmap = b.getBitmap();
7673                 return aBitmap.getWidth() != bBitmap.getWidth()
7674                         || aBitmap.getHeight() != bBitmap.getHeight()
7675                         || aBitmap.getConfig() != bBitmap.getConfig()
7676                         || aBitmap.getGenerationId() != bBitmap.getGenerationId();
7677             }
7678             return true;
7679         }
7680     }
7681 
7682     /**
7683      * Helper class for generating large-format notifications that include a lot of text.
7684      *
7685      * Here's how you'd set the <code>BigTextStyle</code> on a notification:
7686      * <pre class="prettyprint">
7687      * Notification notif = new Notification.Builder(mContext)
7688      *     .setContentTitle(&quot;New mail from &quot; + sender.toString())
7689      *     .setContentText(subject)
7690      *     .setSmallIcon(R.drawable.new_mail)
7691      *     .setLargeIcon(aBitmap)
7692      *     .setStyle(new Notification.BigTextStyle()
7693      *         .bigText(aVeryLongString))
7694      *     .build();
7695      * </pre>
7696      *
7697      * @see Notification#bigContentView
7698      */
7699     public static class BigTextStyle extends Style {
7700 
7701         private CharSequence mBigText;
7702 
BigTextStyle()7703         public BigTextStyle() {
7704         }
7705 
7706         /**
7707          * @deprecated use {@code BigTextStyle()}.
7708          */
7709         @Deprecated
BigTextStyle(Builder builder)7710         public BigTextStyle(Builder builder) {
7711             setBuilder(builder);
7712         }
7713 
7714         /**
7715          * Overrides ContentTitle in the big form of the template.
7716          * This defaults to the value passed to setContentTitle().
7717          */
setBigContentTitle(CharSequence title)7718         public BigTextStyle setBigContentTitle(CharSequence title) {
7719             internalSetBigContentTitle(safeCharSequence(title));
7720             return this;
7721         }
7722 
7723         /**
7724          * Set the first line of text after the detail section in the big form of the template.
7725          */
setSummaryText(CharSequence cs)7726         public BigTextStyle setSummaryText(CharSequence cs) {
7727             internalSetSummaryText(safeCharSequence(cs));
7728             return this;
7729         }
7730 
7731         /**
7732          * Provide the longer text to be displayed in the big form of the
7733          * template in place of the content text.
7734          */
bigText(CharSequence cs)7735         public BigTextStyle bigText(CharSequence cs) {
7736             mBigText = safeCharSequence(cs);
7737             return this;
7738         }
7739 
7740         /**
7741          * @hide
7742          */
getBigText()7743         public CharSequence getBigText() {
7744             return mBigText;
7745         }
7746 
7747         /**
7748          * @hide
7749          */
addExtras(Bundle extras)7750         public void addExtras(Bundle extras) {
7751             super.addExtras(extras);
7752 
7753             extras.putCharSequence(EXTRA_BIG_TEXT, mBigText);
7754         }
7755 
7756         /**
7757          * @hide
7758          */
7759         @Override
restoreFromExtras(Bundle extras)7760         protected void restoreFromExtras(Bundle extras) {
7761             super.restoreFromExtras(extras);
7762 
7763             mBigText = extras.getCharSequence(EXTRA_BIG_TEXT);
7764         }
7765 
7766         /**
7767          * @param increasedHeight true if this layout be created with an increased height.
7768          *
7769          * @hide
7770          */
7771         @Override
makeContentView(boolean increasedHeight)7772         public RemoteViews makeContentView(boolean increasedHeight) {
7773             if (increasedHeight) {
7774                 ArrayList<Action> originalActions = mBuilder.mActions;
7775                 mBuilder.mActions = new ArrayList<>();
7776                 RemoteViews remoteViews = makeBigContentView();
7777                 mBuilder.mActions = originalActions;
7778                 return remoteViews;
7779             }
7780             return super.makeContentView(increasedHeight);
7781         }
7782 
7783         /**
7784          * @hide
7785          */
7786         @Override
makeHeadsUpContentView(boolean increasedHeight)7787         public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
7788             if (increasedHeight && mBuilder.mActions.size() > 0) {
7789                 // TODO(b/163626038): pass VIEW_TYPE_HEADS_UP?
7790                 return makeBigContentView();
7791             }
7792             return super.makeHeadsUpContentView(increasedHeight);
7793         }
7794 
7795         /**
7796          * @hide
7797          */
makeBigContentView()7798         public RemoteViews makeBigContentView() {
7799             StandardTemplateParams p = mBuilder.mParams.reset()
7800                     .viewType(StandardTemplateParams.VIEW_TYPE_BIG)
7801                     .allowTextWithProgress(true)
7802                     .textViewId(R.id.big_text)
7803                     .fillTextsFrom(mBuilder);
7804 
7805             // Replace the text with the big text, but only if the big text is not empty.
7806             CharSequence bigTextText = mBuilder.processLegacyText(mBigText);
7807             if (!TextUtils.isEmpty(bigTextText)) {
7808                 p.text(bigTextText);
7809             }
7810 
7811             return getStandardView(mBuilder.getBigTextLayoutResource(), p, null /* result */);
7812         }
7813 
7814         /**
7815          * @hide
7816          * Spans are ignored when comparing text for visual difference.
7817          */
7818         @Override
areNotificationsVisiblyDifferent(Style other)7819         public boolean areNotificationsVisiblyDifferent(Style other) {
7820             if (other == null || getClass() != other.getClass()) {
7821                 return true;
7822             }
7823             BigTextStyle newS = (BigTextStyle) other;
7824             return !Objects.equals(String.valueOf(getBigText()), String.valueOf(newS.getBigText()));
7825         }
7826 
7827     }
7828 
7829     /**
7830      * Helper class for generating large-format notifications that include multiple back-and-forth
7831      * messages of varying types between any number of people.
7832      *
7833      * <p>
7834      * If the platform does not provide large-format notifications, this method has no effect. The
7835      * user will always see the normal notification view.
7836      *
7837      * <p>
7838      * If the app is targeting Android {@link android.os.Build.VERSION_CODES#P} and above, it is
7839      * required to use the {@link Person} class in order to get an optimal rendering of the
7840      * notification and its avatars. For conversations involving multiple people, the app should
7841      * also make sure that it marks the conversation as a group with
7842      * {@link #setGroupConversation(boolean)}.
7843      *
7844      * <p>
7845      * This class is a "rebuilder": It attaches to a Builder object and modifies its behavior.
7846      * Here's an example of how this may be used:
7847      * <pre class="prettyprint">
7848      *
7849      * Person user = new Person.Builder().setIcon(userIcon).setName(userName).build();
7850      * MessagingStyle style = new MessagingStyle(user)
7851      *      .addMessage(messages[1].getText(), messages[1].getTime(), messages[1].getPerson())
7852      *      .addMessage(messages[2].getText(), messages[2].getTime(), messages[2].getPerson())
7853      *      .setGroupConversation(hasMultiplePeople());
7854      *
7855      * Notification noti = new Notification.Builder()
7856      *     .setContentTitle(&quot;2 new messages with &quot; + sender.toString())
7857      *     .setContentText(subject)
7858      *     .setSmallIcon(R.drawable.new_message)
7859      *     .setLargeIcon(aBitmap)
7860      *     .setStyle(style)
7861      *     .build();
7862      * </pre>
7863      */
7864     public static class MessagingStyle extends Style {
7865 
7866         /**
7867          * The maximum number of messages that will be retained in the Notification itself (the
7868          * number displayed is up to the platform).
7869          */
7870         public static final int MAXIMUM_RETAINED_MESSAGES = 25;
7871 
7872 
7873         /** @hide */
7874         public static final int CONVERSATION_TYPE_LEGACY = 0;
7875         /** @hide */
7876         public static final int CONVERSATION_TYPE_NORMAL = 1;
7877         /** @hide */
7878         public static final int CONVERSATION_TYPE_IMPORTANT = 2;
7879 
7880         /** @hide */
7881         @IntDef(prefix = {"CONVERSATION_TYPE_"}, value = {
7882                 CONVERSATION_TYPE_LEGACY, CONVERSATION_TYPE_NORMAL, CONVERSATION_TYPE_IMPORTANT
7883         })
7884         @Retention(RetentionPolicy.SOURCE)
7885         public @interface ConversationType {}
7886 
7887         @NonNull Person mUser;
7888         @Nullable CharSequence mConversationTitle;
7889         @Nullable Icon mShortcutIcon;
7890         List<Message> mMessages = new ArrayList<>();
7891         List<Message> mHistoricMessages = new ArrayList<>();
7892         boolean mIsGroupConversation;
7893         @ConversationType int mConversationType = CONVERSATION_TYPE_LEGACY;
7894         int mUnreadMessageCount;
7895 
MessagingStyle()7896         MessagingStyle() {
7897         }
7898 
7899         /**
7900          * @param userDisplayName Required - the name to be displayed for any replies sent by the
7901          * user before the posting app reposts the notification with those messages after they've
7902          * been actually sent and in previous messages sent by the user added in
7903          * {@link #addMessage(Notification.MessagingStyle.Message)}
7904          *
7905          * @deprecated use {@code MessagingStyle(Person)}
7906          */
MessagingStyle(@onNull CharSequence userDisplayName)7907         public MessagingStyle(@NonNull CharSequence userDisplayName) {
7908             this(new Person.Builder().setName(userDisplayName).build());
7909         }
7910 
7911         /**
7912          * @param user Required - The person displayed for any messages that are sent by the
7913          * user. Any messages added with {@link #addMessage(Notification.MessagingStyle.Message)}
7914          * who don't have a Person associated with it will be displayed as if they were sent
7915          * by this user. The user also needs to have a valid name associated with it, which is
7916          * enforced starting in Android {@link android.os.Build.VERSION_CODES#P}.
7917          */
MessagingStyle(@onNull Person user)7918         public MessagingStyle(@NonNull Person user) {
7919             mUser = user;
7920         }
7921 
7922         /**
7923          * Validate that this style was properly composed. This is called at build time.
7924          * @hide
7925          */
7926         @Override
validate(Context context)7927         public void validate(Context context) {
7928             super.validate(context);
7929             if (context.getApplicationInfo().targetSdkVersion
7930                     >= Build.VERSION_CODES.P && (mUser == null || mUser.getName() == null)) {
7931                 throw new RuntimeException("User must be valid and have a name.");
7932             }
7933         }
7934 
7935         /**
7936          * @return the text that should be displayed in the statusBar when heads upped.
7937          * If {@code null} is returned, the default implementation will be used.
7938          *
7939          * @hide
7940          */
7941         @Override
getHeadsUpStatusBarText()7942         public CharSequence getHeadsUpStatusBarText() {
7943             CharSequence conversationTitle = !TextUtils.isEmpty(super.mBigContentTitle)
7944                     ? super.mBigContentTitle
7945                     : mConversationTitle;
7946             if (mConversationType == CONVERSATION_TYPE_LEGACY
7947                     && !TextUtils.isEmpty(conversationTitle) && !hasOnlyWhiteSpaceSenders()) {
7948                 return conversationTitle;
7949             }
7950             return null;
7951         }
7952 
7953         /**
7954          * @return the user to be displayed for any replies sent by the user
7955          */
7956         @NonNull
getUser()7957         public Person getUser() {
7958             return mUser;
7959         }
7960 
7961         /**
7962          * Returns the name to be displayed for any replies sent by the user
7963          *
7964          * @deprecated use {@link #getUser()} instead
7965          */
getUserDisplayName()7966         public CharSequence getUserDisplayName() {
7967             return mUser.getName();
7968         }
7969 
7970         /**
7971          * Sets the title to be displayed on this conversation. May be set to {@code null}.
7972          *
7973          * <p>Starting in {@link Build.VERSION_CODES#R}, this conversation title will be ignored
7974          * if a valid shortcutId is added via {@link Notification.Builder#setShortcutId(String)}.
7975          * In this case, {@link ShortcutInfo#getLongLabel()} (or, if missing,
7976          * {@link ShortcutInfo#getShortLabel()}) will be shown as the conversation title
7977          * instead.
7978          *
7979          * <p>This API's behavior was changed in SDK version {@link Build.VERSION_CODES#P}. If your
7980          * application's target version is less than {@link Build.VERSION_CODES#P}, setting a
7981          * conversation title to a non-null value will make {@link #isGroupConversation()} return
7982          * {@code true} and passing {@code null} will make it return {@code false}. In
7983          * {@link Build.VERSION_CODES#P} and beyond, use {@link #setGroupConversation(boolean)}
7984          * to set group conversation status.
7985          *
7986          * @param conversationTitle Title displayed for this conversation
7987          * @return this object for method chaining
7988          */
setConversationTitle(@ullable CharSequence conversationTitle)7989         public MessagingStyle setConversationTitle(@Nullable CharSequence conversationTitle) {
7990             mConversationTitle = conversationTitle;
7991             return this;
7992         }
7993 
7994         /**
7995          * Return the title to be displayed on this conversation. May return {@code null}.
7996          */
7997         @Nullable
getConversationTitle()7998         public CharSequence getConversationTitle() {
7999             return mConversationTitle;
8000         }
8001 
8002         /**
8003          * Sets the icon to be displayed on the conversation, derived from the shortcutId.
8004          *
8005          * @hide
8006          */
setShortcutIcon(@ullable Icon conversationIcon)8007         public MessagingStyle setShortcutIcon(@Nullable Icon conversationIcon) {
8008             mShortcutIcon = conversationIcon;
8009             return this;
8010         }
8011 
8012         /**
8013          * Return the icon to be displayed on this conversation, derived from the shortcutId. May
8014          * return {@code null}.
8015          *
8016          * @hide
8017          */
8018         @Nullable
getShortcutIcon()8019         public Icon getShortcutIcon() {
8020             return mShortcutIcon;
8021         }
8022 
8023         /**
8024          * Sets the conversation type of this MessageStyle notification.
8025          * {@link #CONVERSATION_TYPE_LEGACY} will use the "older" layout from pre-R,
8026          * {@link #CONVERSATION_TYPE_NORMAL} will use the new "conversation" layout, and
8027          * {@link #CONVERSATION_TYPE_IMPORTANT} will add additional "important" treatments.
8028          *
8029          * @hide
8030          */
setConversationType(@onversationType int conversationType)8031         public MessagingStyle setConversationType(@ConversationType int conversationType) {
8032             mConversationType = conversationType;
8033             return this;
8034         }
8035 
8036         /** @hide */
8037         @ConversationType
getConversationType()8038         public int getConversationType() {
8039             return mConversationType;
8040         }
8041 
8042         /** @hide */
getUnreadMessageCount()8043         public int getUnreadMessageCount() {
8044             return mUnreadMessageCount;
8045         }
8046 
8047         /** @hide */
setUnreadMessageCount(int unreadMessageCount)8048         public MessagingStyle setUnreadMessageCount(int unreadMessageCount) {
8049             mUnreadMessageCount = unreadMessageCount;
8050             return this;
8051         }
8052 
8053         /**
8054          * Adds a message for display by this notification. Convenience call for a simple
8055          * {@link Message} in {@link #addMessage(Notification.MessagingStyle.Message)}.
8056          * @param text A {@link CharSequence} to be displayed as the message content
8057          * @param timestamp Time in milliseconds at which the message arrived
8058          * @param sender A {@link CharSequence} to be used for displaying the name of the
8059          * sender. Should be <code>null</code> for messages by the current user, in which case
8060          * the platform will insert {@link #getUserDisplayName()}.
8061          * Should be unique amongst all individuals in the conversation, and should be
8062          * consistent during re-posts of the notification.
8063          *
8064          * @see Message#Message(CharSequence, long, CharSequence)
8065          *
8066          * @return this object for method chaining
8067          *
8068          * @deprecated use {@link #addMessage(CharSequence, long, Person)}
8069          */
addMessage(CharSequence text, long timestamp, CharSequence sender)8070         public MessagingStyle addMessage(CharSequence text, long timestamp, CharSequence sender) {
8071             return addMessage(text, timestamp,
8072                     sender == null ? null : new Person.Builder().setName(sender).build());
8073         }
8074 
8075         /**
8076          * Adds a message for display by this notification. Convenience call for a simple
8077          * {@link Message} in {@link #addMessage(Notification.MessagingStyle.Message)}.
8078          * @param text A {@link CharSequence} to be displayed as the message content
8079          * @param timestamp Time in milliseconds at which the message arrived
8080          * @param sender The {@link Person} who sent the message.
8081          * Should be <code>null</code> for messages by the current user, in which case
8082          * the platform will insert the user set in {@code MessagingStyle(Person)}.
8083          *
8084          * @see Message#Message(CharSequence, long, CharSequence)
8085          *
8086          * @return this object for method chaining
8087          */
addMessage(@onNull CharSequence text, long timestamp, @Nullable Person sender)8088         public MessagingStyle addMessage(@NonNull CharSequence text, long timestamp,
8089                 @Nullable Person sender) {
8090             return addMessage(new Message(text, timestamp, sender));
8091         }
8092 
8093         /**
8094          * Adds a {@link Message} for display in this notification.
8095          *
8096          * <p>The messages should be added in chronologic order, i.e. the oldest first,
8097          * the newest last.
8098          *
8099          * @param message The {@link Message} to be displayed
8100          * @return this object for method chaining
8101          */
addMessage(Message message)8102         public MessagingStyle addMessage(Message message) {
8103             mMessages.add(message);
8104             if (mMessages.size() > MAXIMUM_RETAINED_MESSAGES) {
8105                 mMessages.remove(0);
8106             }
8107             return this;
8108         }
8109 
8110         /**
8111          * Adds a {@link Message} for historic context in this notification.
8112          *
8113          * <p>Messages should be added as historic if they are not the main subject of the
8114          * notification but may give context to a conversation. The system may choose to present
8115          * them only when relevant, e.g. when replying to a message through a {@link RemoteInput}.
8116          *
8117          * <p>The messages should be added in chronologic order, i.e. the oldest first,
8118          * the newest last.
8119          *
8120          * @param message The historic {@link Message} to be added
8121          * @return this object for method chaining
8122          */
addHistoricMessage(Message message)8123         public MessagingStyle addHistoricMessage(Message message) {
8124             mHistoricMessages.add(message);
8125             if (mHistoricMessages.size() > MAXIMUM_RETAINED_MESSAGES) {
8126                 mHistoricMessages.remove(0);
8127             }
8128             return this;
8129         }
8130 
8131         /**
8132          * Gets the list of {@code Message} objects that represent the notification.
8133          */
getMessages()8134         public List<Message> getMessages() {
8135             return mMessages;
8136         }
8137 
8138         /**
8139          * Gets the list of historic {@code Message}s in the notification.
8140          */
getHistoricMessages()8141         public List<Message> getHistoricMessages() {
8142             return mHistoricMessages;
8143         }
8144 
8145         /**
8146          * Sets whether this conversation notification represents a group. If the app is targeting
8147          * Android P, this is required if the app wants to display the largeIcon set with
8148          * {@link Notification.Builder#setLargeIcon(Bitmap)}, otherwise it will be hidden.
8149          *
8150          * @param isGroupConversation {@code true} if the conversation represents a group,
8151          * {@code false} otherwise.
8152          * @return this object for method chaining
8153          */
setGroupConversation(boolean isGroupConversation)8154         public MessagingStyle setGroupConversation(boolean isGroupConversation) {
8155             mIsGroupConversation = isGroupConversation;
8156             return this;
8157         }
8158 
8159         /**
8160          * Returns {@code true} if this notification represents a group conversation, otherwise
8161          * {@code false}.
8162          *
8163          * <p> If the application that generated this {@link MessagingStyle} targets an SDK version
8164          * less than {@link Build.VERSION_CODES#P}, this method becomes dependent on whether or
8165          * not the conversation title is set; returning {@code true} if the conversation title is
8166          * a non-null value, or {@code false} otherwise. From {@link Build.VERSION_CODES#P} forward,
8167          * this method returns what's set by {@link #setGroupConversation(boolean)} allowing for
8168          * named, non-group conversations.
8169          *
8170          * @see #setConversationTitle(CharSequence)
8171          */
isGroupConversation()8172         public boolean isGroupConversation() {
8173             // When target SDK version is < P, a non-null conversation title dictates if this is
8174             // as group conversation.
8175             if (mBuilder != null
8176                     && mBuilder.mContext.getApplicationInfo().targetSdkVersion
8177                             < Build.VERSION_CODES.P) {
8178                 return mConversationTitle != null;
8179             }
8180 
8181             return mIsGroupConversation;
8182         }
8183 
8184         /**
8185          * @hide
8186          */
8187         @Override
addExtras(Bundle extras)8188         public void addExtras(Bundle extras) {
8189             super.addExtras(extras);
8190             if (mUser != null) {
8191                 // For legacy usages
8192                 extras.putCharSequence(EXTRA_SELF_DISPLAY_NAME, mUser.getName());
8193                 extras.putParcelable(EXTRA_MESSAGING_PERSON, mUser);
8194             }
8195             if (mConversationTitle != null) {
8196                 extras.putCharSequence(EXTRA_CONVERSATION_TITLE, mConversationTitle);
8197             }
8198             if (!mMessages.isEmpty()) { extras.putParcelableArray(EXTRA_MESSAGES,
8199                     Message.getBundleArrayForMessages(mMessages));
8200             }
8201             if (!mHistoricMessages.isEmpty()) { extras.putParcelableArray(EXTRA_HISTORIC_MESSAGES,
8202                     Message.getBundleArrayForMessages(mHistoricMessages));
8203             }
8204             if (mShortcutIcon != null) {
8205                 extras.putParcelable(EXTRA_CONVERSATION_ICON, mShortcutIcon);
8206             }
8207             extras.putInt(EXTRA_CONVERSATION_UNREAD_MESSAGE_COUNT, mUnreadMessageCount);
8208 
8209             fixTitleAndTextExtras(extras);
8210             extras.putBoolean(EXTRA_IS_GROUP_CONVERSATION, mIsGroupConversation);
8211         }
8212 
fixTitleAndTextExtras(Bundle extras)8213         private void fixTitleAndTextExtras(Bundle extras) {
8214             Message m = findLatestIncomingMessage();
8215             CharSequence text = (m == null) ? null : m.mText;
8216             CharSequence sender = m == null ? null
8217                     : m.mSender == null || TextUtils.isEmpty(m.mSender.getName())
8218                             ? mUser.getName() : m.mSender.getName();
8219             CharSequence title;
8220             if (!TextUtils.isEmpty(mConversationTitle)) {
8221                 if (!TextUtils.isEmpty(sender)) {
8222                     BidiFormatter bidi = BidiFormatter.getInstance();
8223                     title = mBuilder.mContext.getString(
8224                             com.android.internal.R.string.notification_messaging_title_template,
8225                             bidi.unicodeWrap(mConversationTitle), bidi.unicodeWrap(sender));
8226                 } else {
8227                     title = mConversationTitle;
8228                 }
8229             } else {
8230                 title = sender;
8231             }
8232 
8233             if (title != null) {
8234                 extras.putCharSequence(EXTRA_TITLE, title);
8235             }
8236             if (text != null) {
8237                 extras.putCharSequence(EXTRA_TEXT, text);
8238             }
8239         }
8240 
8241         /**
8242          * @hide
8243          */
8244         @Override
restoreFromExtras(Bundle extras)8245         protected void restoreFromExtras(Bundle extras) {
8246             super.restoreFromExtras(extras);
8247 
8248             mUser = extras.getParcelable(EXTRA_MESSAGING_PERSON, Person.class);
8249             if (mUser == null) {
8250                 CharSequence displayName = extras.getCharSequence(EXTRA_SELF_DISPLAY_NAME);
8251                 mUser = new Person.Builder().setName(displayName).build();
8252             }
8253             mConversationTitle = extras.getCharSequence(EXTRA_CONVERSATION_TITLE);
8254             Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES);
8255             mMessages = Message.getMessagesFromBundleArray(messages);
8256             Parcelable[] histMessages = extras.getParcelableArray(EXTRA_HISTORIC_MESSAGES);
8257             mHistoricMessages = Message.getMessagesFromBundleArray(histMessages);
8258             mIsGroupConversation = extras.getBoolean(EXTRA_IS_GROUP_CONVERSATION);
8259             mUnreadMessageCount = extras.getInt(EXTRA_CONVERSATION_UNREAD_MESSAGE_COUNT);
8260             mShortcutIcon = extras.getParcelable(EXTRA_CONVERSATION_ICON, Icon.class);
8261         }
8262 
8263         /**
8264          * @hide
8265          */
8266         @Override
makeContentView(boolean increasedHeight)8267         public RemoteViews makeContentView(boolean increasedHeight) {
8268             // All messaging templates contain the actions
8269             ArrayList<Action> originalActions = mBuilder.mActions;
8270             try {
8271                 mBuilder.mActions = new ArrayList<>();
8272                 return makeMessagingView(StandardTemplateParams.VIEW_TYPE_NORMAL);
8273             } finally {
8274                 mBuilder.mActions = originalActions;
8275             }
8276         }
8277 
8278         /**
8279          * @hide
8280          * Spans are ignored when comparing text for visual difference.
8281          */
8282         @Override
areNotificationsVisiblyDifferent(Style other)8283         public boolean areNotificationsVisiblyDifferent(Style other) {
8284             if (other == null || getClass() != other.getClass()) {
8285                 return true;
8286             }
8287             MessagingStyle newS = (MessagingStyle) other;
8288             List<MessagingStyle.Message> oldMs = getMessages();
8289             List<MessagingStyle.Message> newMs = newS.getMessages();
8290 
8291             if (oldMs == null || newMs == null) {
8292                 newMs = new ArrayList<>();
8293             }
8294 
8295             int n = oldMs.size();
8296             if (n != newMs.size()) {
8297                 return true;
8298             }
8299             for (int i = 0; i < n; i++) {
8300                 MessagingStyle.Message oldM = oldMs.get(i);
8301                 MessagingStyle.Message newM = newMs.get(i);
8302                 if (!Objects.equals(
8303                         String.valueOf(oldM.getText()),
8304                         String.valueOf(newM.getText()))) {
8305                     return true;
8306                 }
8307                 if (!Objects.equals(oldM.getDataUri(), newM.getDataUri())) {
8308                     return true;
8309                 }
8310                 String oldSender = String.valueOf(oldM.getSenderPerson() == null
8311                         ? oldM.getSender()
8312                         : oldM.getSenderPerson().getName());
8313                 String newSender = String.valueOf(newM.getSenderPerson() == null
8314                         ? newM.getSender()
8315                         : newM.getSenderPerson().getName());
8316                 if (!Objects.equals(oldSender, newSender)) {
8317                     return true;
8318                 }
8319 
8320                 String oldKey = oldM.getSenderPerson() == null
8321                         ? null : oldM.getSenderPerson().getKey();
8322                 String newKey = newM.getSenderPerson() == null
8323                         ? null : newM.getSenderPerson().getKey();
8324                 if (!Objects.equals(oldKey, newKey)) {
8325                     return true;
8326                 }
8327                 // Other fields (like timestamp) intentionally excluded
8328             }
8329             return false;
8330         }
8331 
findLatestIncomingMessage()8332         private Message findLatestIncomingMessage() {
8333             return findLatestIncomingMessage(mMessages);
8334         }
8335 
8336         /**
8337          * @hide
8338          */
8339         @Nullable
findLatestIncomingMessage( List<Message> messages)8340         public static Message findLatestIncomingMessage(
8341                 List<Message> messages) {
8342             for (int i = messages.size() - 1; i >= 0; i--) {
8343                 Message m = messages.get(i);
8344                 // Incoming messages have a non-empty sender.
8345                 if (m.mSender != null && !TextUtils.isEmpty(m.mSender.getName())) {
8346                     return m;
8347                 }
8348             }
8349             if (!messages.isEmpty()) {
8350                 // No incoming messages, fall back to outgoing message
8351                 return messages.get(messages.size() - 1);
8352             }
8353             return null;
8354         }
8355 
8356         /**
8357          * @hide
8358          */
8359         @Override
makeBigContentView()8360         public RemoteViews makeBigContentView() {
8361             return makeMessagingView(StandardTemplateParams.VIEW_TYPE_BIG);
8362         }
8363 
8364         /**
8365          * Create a messaging layout.
8366          *
8367          * @param viewType one of StandardTemplateParams.VIEW_TYPE_NORMAL, VIEW_TYPE_BIG,
8368          *                VIEW_TYPE_HEADS_UP
8369          * @return the created remoteView.
8370          */
8371         @NonNull
makeMessagingView(int viewType)8372         private RemoteViews makeMessagingView(int viewType) {
8373             boolean isCollapsed = viewType != StandardTemplateParams.VIEW_TYPE_BIG;
8374             boolean hideRightIcons = viewType != StandardTemplateParams.VIEW_TYPE_NORMAL;
8375             boolean isConversationLayout = mConversationType != CONVERSATION_TYPE_LEGACY;
8376             boolean isImportantConversation = mConversationType == CONVERSATION_TYPE_IMPORTANT;
8377             boolean isHeaderless = !isConversationLayout && isCollapsed;
8378 
8379             CharSequence conversationTitle = !TextUtils.isEmpty(super.mBigContentTitle)
8380                     ? super.mBigContentTitle
8381                     : mConversationTitle;
8382             boolean atLeastP = mBuilder.mContext.getApplicationInfo().targetSdkVersion
8383                     >= Build.VERSION_CODES.P;
8384             boolean isOneToOne;
8385             CharSequence nameReplacement = null;
8386             if (!atLeastP) {
8387                 isOneToOne = TextUtils.isEmpty(conversationTitle);
8388                 if (hasOnlyWhiteSpaceSenders()) {
8389                     isOneToOne = true;
8390                     nameReplacement = conversationTitle;
8391                     conversationTitle = null;
8392                 }
8393             } else {
8394                 isOneToOne = !isGroupConversation();
8395             }
8396             if (isHeaderless && isOneToOne && TextUtils.isEmpty(conversationTitle)) {
8397                 conversationTitle = getOtherPersonName();
8398             }
8399 
8400             Icon largeIcon = mBuilder.mN.mLargeIcon;
8401             TemplateBindResult bindResult = new TemplateBindResult();
8402             StandardTemplateParams p = mBuilder.mParams.reset()
8403                     .viewType(viewType)
8404                     .highlightExpander(isConversationLayout)
8405                     .hideProgress(true)
8406                     .title(isHeaderless ? conversationTitle : null)
8407                     .text(null)
8408                     .hideLeftIcon(isOneToOne)
8409                     .hideRightIcon(hideRightIcons || isOneToOne)
8410                     .headerTextSecondary(isHeaderless ? null : conversationTitle);
8411             RemoteViews contentView = mBuilder.applyStandardTemplateWithActions(
8412                     isConversationLayout
8413                             ? mBuilder.getConversationLayoutResource()
8414                             : isCollapsed
8415                                     ? mBuilder.getMessagingLayoutResource()
8416                                     : mBuilder.getBigMessagingLayoutResource(),
8417                     p,
8418                     bindResult);
8419             if (isConversationLayout) {
8420                 mBuilder.setTextViewColorPrimary(contentView, R.id.conversation_text, p);
8421                 mBuilder.setTextViewColorSecondary(contentView, R.id.app_name_divider, p);
8422             }
8423 
8424             addExtras(mBuilder.mN.extras);
8425             contentView.setInt(R.id.status_bar_latest_event_content, "setLayoutColor",
8426                     mBuilder.getSmallIconColor(p));
8427             contentView.setInt(R.id.status_bar_latest_event_content, "setSenderTextColor",
8428                     mBuilder.getPrimaryTextColor(p));
8429             contentView.setInt(R.id.status_bar_latest_event_content, "setMessageTextColor",
8430                     mBuilder.getSecondaryTextColor(p));
8431             contentView.setInt(R.id.status_bar_latest_event_content,
8432                     "setNotificationBackgroundColor",
8433                     mBuilder.getBackgroundColor(p));
8434             contentView.setBoolean(R.id.status_bar_latest_event_content, "setIsCollapsed",
8435                     isCollapsed);
8436             contentView.setIcon(R.id.status_bar_latest_event_content, "setAvatarReplacement",
8437                     mBuilder.mN.mLargeIcon);
8438             contentView.setCharSequence(R.id.status_bar_latest_event_content, "setNameReplacement",
8439                     nameReplacement);
8440             contentView.setBoolean(R.id.status_bar_latest_event_content, "setIsOneToOne",
8441                     isOneToOne);
8442             contentView.setCharSequence(R.id.status_bar_latest_event_content,
8443                     "setConversationTitle", conversationTitle);
8444             if (isConversationLayout) {
8445                 contentView.setIcon(R.id.status_bar_latest_event_content,
8446                         "setShortcutIcon", mShortcutIcon);
8447                 contentView.setBoolean(R.id.status_bar_latest_event_content,
8448                         "setIsImportantConversation", isImportantConversation);
8449             }
8450             if (isHeaderless) {
8451                 // Collapsed legacy messaging style has a 1-line limit.
8452                 contentView.setInt(R.id.notification_messaging, "setMaxDisplayedLines", 1);
8453             }
8454             contentView.setIcon(R.id.status_bar_latest_event_content, "setLargeIcon",
8455                     largeIcon);
8456             contentView.setBundle(R.id.status_bar_latest_event_content, "setData",
8457                     mBuilder.mN.extras);
8458             return contentView;
8459         }
8460 
getKey(Person person)8461         private CharSequence getKey(Person person) {
8462             return person == null ? null
8463                     : person.getKey() == null ? person.getName() : person.getKey();
8464         }
8465 
getOtherPersonName()8466         private CharSequence getOtherPersonName() {
8467             CharSequence userKey = getKey(mUser);
8468             for (int i = mMessages.size() - 1; i >= 0; i--) {
8469                 Person sender = mMessages.get(i).getSenderPerson();
8470                 if (sender != null && !TextUtils.equals(userKey, getKey(sender))) {
8471                     return sender.getName();
8472                 }
8473             }
8474             return null;
8475         }
8476 
hasOnlyWhiteSpaceSenders()8477         private boolean hasOnlyWhiteSpaceSenders() {
8478             for (int i = 0; i < mMessages.size(); i++) {
8479                 Message m = mMessages.get(i);
8480                 Person sender = m.getSenderPerson();
8481                 if (sender != null && !isWhiteSpace(sender.getName())) {
8482                     return false;
8483                 }
8484             }
8485             return true;
8486         }
8487 
isWhiteSpace(CharSequence sender)8488         private boolean isWhiteSpace(CharSequence sender) {
8489             if (TextUtils.isEmpty(sender)) {
8490                 return true;
8491             }
8492             if (sender.toString().matches("^\\s*$")) {
8493                 return true;
8494             }
8495             // Let's check if we only have 0 whitespace chars. Some apps did this as a workaround
8496             // For the presentation that we had.
8497             for (int i = 0; i < sender.length(); i++) {
8498                 char c = sender.charAt(i);
8499                 if (c != '\u200B') {
8500                     return false;
8501                 }
8502             }
8503             return true;
8504         }
8505 
8506         /**
8507          * @hide
8508          */
8509         @Override
makeHeadsUpContentView(boolean increasedHeight)8510         public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
8511             return makeMessagingView(StandardTemplateParams.VIEW_TYPE_HEADS_UP);
8512         }
8513 
8514         /**
8515          * @hide
8516          */
8517         @Override
reduceImageSizes(Context context)8518         public void reduceImageSizes(Context context) {
8519             super.reduceImageSizes(context);
8520             Resources resources = context.getResources();
8521             boolean isLowRam = ActivityManager.isLowRamDeviceStatic();
8522             if (mShortcutIcon != null) {
8523                 int maxSize = resources.getDimensionPixelSize(
8524                         isLowRam ? R.dimen.notification_small_icon_size_low_ram
8525                                 : R.dimen.notification_small_icon_size);
8526                 mShortcutIcon.scaleDownIfNecessary(maxSize, maxSize);
8527             }
8528 
8529             int maxAvatarSize = resources.getDimensionPixelSize(
8530                     isLowRam ? R.dimen.notification_person_icon_max_size_low_ram
8531                             : R.dimen.notification_person_icon_max_size);
8532             if (mUser != null && mUser.getIcon() != null) {
8533                 mUser.getIcon().scaleDownIfNecessary(maxAvatarSize, maxAvatarSize);
8534             }
8535 
8536             reduceMessagesIconSizes(mMessages, maxAvatarSize);
8537             reduceMessagesIconSizes(mHistoricMessages, maxAvatarSize);
8538         }
8539 
8540         /**
8541          * @hide
8542          */
reduceMessagesIconSizes(@ullable List<Message> messages, int maxSize)8543         private static void reduceMessagesIconSizes(@Nullable List<Message> messages, int maxSize) {
8544             if (messages == null) {
8545                 return;
8546             }
8547 
8548             for (Message message : messages) {
8549                 Person sender = message.mSender;
8550                 if (sender != null) {
8551                     Icon icon = sender.getIcon();
8552                     if (icon != null) {
8553                         icon.scaleDownIfNecessary(maxSize, maxSize);
8554                     }
8555                 }
8556             }
8557         }
8558 
8559         public static final class Message {
8560             /** @hide */
8561             public static final String KEY_TEXT = "text";
8562             static final String KEY_TIMESTAMP = "time";
8563             static final String KEY_SENDER = "sender";
8564             static final String KEY_SENDER_PERSON = "sender_person";
8565             static final String KEY_DATA_MIME_TYPE = "type";
8566             static final String KEY_DATA_URI= "uri";
8567             static final String KEY_EXTRAS_BUNDLE = "extras";
8568             static final String KEY_REMOTE_INPUT_HISTORY = "remote_input_history";
8569 
8570             private final CharSequence mText;
8571             private final long mTimestamp;
8572             @Nullable
8573             private final Person mSender;
8574             /** True if this message was generated from the extra
8575              *  {@link Notification#EXTRA_REMOTE_INPUT_HISTORY_ITEMS}
8576              */
8577             private final boolean mRemoteInputHistory;
8578 
8579             private Bundle mExtras = new Bundle();
8580             private String mDataMimeType;
8581             private Uri mDataUri;
8582 
8583             /**
8584              * Constructor
8585              * @param text A {@link CharSequence} to be displayed as the message content
8586              * @param timestamp Time at which the message arrived
8587              * @param sender A {@link CharSequence} to be used for displaying the name of the
8588              * sender. Should be <code>null</code> for messages by the current user, in which case
8589              * the platform will insert {@link MessagingStyle#getUserDisplayName()}.
8590              * Should be unique amongst all individuals in the conversation, and should be
8591              * consistent during re-posts of the notification.
8592              *
8593              *  @deprecated use {@code Message(CharSequence, long, Person)}
8594              */
Message(CharSequence text, long timestamp, CharSequence sender)8595             public Message(CharSequence text, long timestamp, CharSequence sender){
8596                 this(text, timestamp, sender == null ? null
8597                         : new Person.Builder().setName(sender).build());
8598             }
8599 
8600             /**
8601              * Constructor
8602              * @param text A {@link CharSequence} to be displayed as the message content
8603              * @param timestamp Time at which the message arrived
8604              * @param sender The {@link Person} who sent the message.
8605              * Should be <code>null</code> for messages by the current user, in which case
8606              * the platform will insert the user set in {@code MessagingStyle(Person)}.
8607              * <p>
8608              * The person provided should contain an Icon, set with
8609              * {@link Person.Builder#setIcon(Icon)} and also have a name provided
8610              * with {@link Person.Builder#setName(CharSequence)}. If multiple users have the same
8611              * name, consider providing a key with {@link Person.Builder#setKey(String)} in order
8612              * to differentiate between the different users.
8613              * </p>
8614              */
Message(@onNull CharSequence text, long timestamp, @Nullable Person sender)8615             public Message(@NonNull CharSequence text, long timestamp, @Nullable Person sender) {
8616                 this(text, timestamp, sender, false /* remoteHistory */);
8617             }
8618 
8619             /**
8620              * Constructor
8621              * @param text A {@link CharSequence} to be displayed as the message content
8622              * @param timestamp Time at which the message arrived
8623              * @param sender The {@link Person} who sent the message.
8624              * Should be <code>null</code> for messages by the current user, in which case
8625              * the platform will insert the user set in {@code MessagingStyle(Person)}.
8626              * @param remoteInputHistory True if the messages was generated from the extra
8627              * {@link Notification#EXTRA_REMOTE_INPUT_HISTORY_ITEMS}.
8628              * <p>
8629              * The person provided should contain an Icon, set with
8630              * {@link Person.Builder#setIcon(Icon)} and also have a name provided
8631              * with {@link Person.Builder#setName(CharSequence)}. If multiple users have the same
8632              * name, consider providing a key with {@link Person.Builder#setKey(String)} in order
8633              * to differentiate between the different users.
8634              * </p>
8635              * @hide
8636              */
Message(@onNull CharSequence text, long timestamp, @Nullable Person sender, boolean remoteInputHistory)8637             public Message(@NonNull CharSequence text, long timestamp, @Nullable Person sender,
8638                     boolean remoteInputHistory) {
8639                 mText = safeCharSequence(text);
8640                 mTimestamp = timestamp;
8641                 mSender = sender;
8642                 mRemoteInputHistory = remoteInputHistory;
8643             }
8644 
8645             /**
8646              * Sets a binary blob of data and an associated MIME type for a message. In the case
8647              * where the platform doesn't support the MIME type, the original text provided in the
8648              * constructor will be used.
8649              * @param dataMimeType The MIME type of the content. See
8650              * <a href="{@docRoot}notifications/messaging.html"> for the list of supported MIME
8651              * types on Android and Android Wear.
8652              * @param dataUri The uri containing the content whose type is given by the MIME type.
8653              * <p class="note">
8654              * <ol>
8655              *   <li>Notification Listeners including the System UI need permission to access the
8656              *       data the Uri points to. The recommended ways to do this are:</li>
8657              *   <li>Store the data in your own ContentProvider, making sure that other apps have
8658              *       the correct permission to access your provider. The preferred mechanism for
8659              *       providing access is to use per-URI permissions which are temporary and only
8660              *       grant access to the receiving application. An easy way to create a
8661              *       ContentProvider like this is to use the FileProvider helper class.</li>
8662              *   <li>Use the system MediaStore. The MediaStore is primarily aimed at video, audio
8663              *       and image MIME types, however beginning with Android 3.0 (API level 11) it can
8664              *       also store non-media types (see MediaStore.Files for more info). Files can be
8665              *       inserted into the MediaStore using scanFile() after which a content:// style
8666              *       Uri suitable for sharing is passed to the provided onScanCompleted() callback.
8667              *       Note that once added to the system MediaStore the content is accessible to any
8668              *       app on the device.</li>
8669              * </ol>
8670              * @return this object for method chaining
8671              */
setData(String dataMimeType, Uri dataUri)8672             public Message setData(String dataMimeType, Uri dataUri) {
8673                 mDataMimeType = dataMimeType;
8674                 mDataUri = dataUri;
8675                 return this;
8676             }
8677 
8678             /**
8679              * Get the text to be used for this message, or the fallback text if a type and content
8680              * Uri have been set
8681              */
getText()8682             public CharSequence getText() {
8683                 return mText;
8684             }
8685 
8686             /**
8687              * Get the time at which this message arrived
8688              */
getTimestamp()8689             public long getTimestamp() {
8690                 return mTimestamp;
8691             }
8692 
8693             /**
8694              * Get the extras Bundle for this message.
8695              */
getExtras()8696             public Bundle getExtras() {
8697                 return mExtras;
8698             }
8699 
8700             /**
8701              * Get the text used to display the contact's name in the messaging experience
8702              *
8703              * @deprecated use {@link #getSenderPerson()}
8704              */
getSender()8705             public CharSequence getSender() {
8706                 return mSender == null ? null : mSender.getName();
8707             }
8708 
8709             /**
8710              * Get the sender associated with this message.
8711              */
8712             @Nullable
getSenderPerson()8713             public Person getSenderPerson() {
8714                 return mSender;
8715             }
8716 
8717             /**
8718              * Get the MIME type of the data pointed to by the Uri
8719              */
getDataMimeType()8720             public String getDataMimeType() {
8721                 return mDataMimeType;
8722             }
8723 
8724             /**
8725              * Get the Uri pointing to the content of the message. Can be null, in which case
8726              * {@see #getText()} is used.
8727              */
getDataUri()8728             public Uri getDataUri() {
8729                 return mDataUri;
8730             }
8731 
8732             /**
8733              * @return True if the message was generated from
8734              * {@link Notification#EXTRA_REMOTE_INPUT_HISTORY_ITEMS}.
8735              * @hide
8736              */
isRemoteInputHistory()8737             public boolean isRemoteInputHistory() {
8738                 return mRemoteInputHistory;
8739             }
8740 
8741             /**
8742              * @hide
8743              */
8744             @VisibleForTesting
toBundle()8745             public Bundle toBundle() {
8746                 Bundle bundle = new Bundle();
8747                 if (mText != null) {
8748                     bundle.putCharSequence(KEY_TEXT, mText);
8749                 }
8750                 bundle.putLong(KEY_TIMESTAMP, mTimestamp);
8751                 if (mSender != null) {
8752                     // Legacy listeners need this
8753                     bundle.putCharSequence(KEY_SENDER, safeCharSequence(mSender.getName()));
8754                     bundle.putParcelable(KEY_SENDER_PERSON, mSender);
8755                 }
8756                 if (mDataMimeType != null) {
8757                     bundle.putString(KEY_DATA_MIME_TYPE, mDataMimeType);
8758                 }
8759                 if (mDataUri != null) {
8760                     bundle.putParcelable(KEY_DATA_URI, mDataUri);
8761                 }
8762                 if (mExtras != null) {
8763                     bundle.putBundle(KEY_EXTRAS_BUNDLE, mExtras);
8764                 }
8765                 if (mRemoteInputHistory) {
8766                     bundle.putBoolean(KEY_REMOTE_INPUT_HISTORY, mRemoteInputHistory);
8767                 }
8768                 return bundle;
8769             }
8770 
getBundleArrayForMessages(List<Message> messages)8771             static Bundle[] getBundleArrayForMessages(List<Message> messages) {
8772                 Bundle[] bundles = new Bundle[messages.size()];
8773                 final int N = messages.size();
8774                 for (int i = 0; i < N; i++) {
8775                     bundles[i] = messages.get(i).toBundle();
8776                 }
8777                 return bundles;
8778             }
8779 
8780             /**
8781              * Returns a list of messages read from the given bundle list, e.g.
8782              * {@link #EXTRA_MESSAGES} or {@link #EXTRA_HISTORIC_MESSAGES}.
8783              */
8784             @NonNull
getMessagesFromBundleArray(@ullable Parcelable[] bundles)8785             public static List<Message> getMessagesFromBundleArray(@Nullable Parcelable[] bundles) {
8786                 if (bundles == null) {
8787                     return new ArrayList<>();
8788                 }
8789                 List<Message> messages = new ArrayList<>(bundles.length);
8790                 for (int i = 0; i < bundles.length; i++) {
8791                     if (bundles[i] instanceof Bundle) {
8792                         Message message = getMessageFromBundle((Bundle)bundles[i]);
8793                         if (message != null) {
8794                             messages.add(message);
8795                         }
8796                     }
8797                 }
8798                 return messages;
8799             }
8800 
8801             /**
8802              * Returns the message that is stored in the bundle (e.g. one of the values in the lists
8803              * in {@link #EXTRA_MESSAGES} or {@link #EXTRA_HISTORIC_MESSAGES}) or null if the
8804              * message couldn't be resolved.
8805              * @hide
8806              */
8807             @Nullable
getMessageFromBundle(@onNull Bundle bundle)8808             public static Message getMessageFromBundle(@NonNull Bundle bundle) {
8809                 try {
8810                     if (!bundle.containsKey(KEY_TEXT) || !bundle.containsKey(KEY_TIMESTAMP)) {
8811                         return null;
8812                     } else {
8813 
8814                         Person senderPerson = bundle.getParcelable(KEY_SENDER_PERSON, Person.class);
8815                         if (senderPerson == null) {
8816                             // Legacy apps that use compat don't actually provide the sender objects
8817                             // We need to fix the compat version to provide people / use
8818                             // the native api instead
8819                             CharSequence senderName = bundle.getCharSequence(KEY_SENDER);
8820                             if (senderName != null) {
8821                                 senderPerson = new Person.Builder().setName(senderName).build();
8822                             }
8823                         }
8824                         Message message = new Message(bundle.getCharSequence(KEY_TEXT),
8825                                 bundle.getLong(KEY_TIMESTAMP),
8826                                 senderPerson,
8827                                 bundle.getBoolean(KEY_REMOTE_INPUT_HISTORY, false));
8828                         if (bundle.containsKey(KEY_DATA_MIME_TYPE) &&
8829                                 bundle.containsKey(KEY_DATA_URI)) {
8830                             message.setData(bundle.getString(KEY_DATA_MIME_TYPE),
8831                                     bundle.getParcelable(KEY_DATA_URI, Uri.class));
8832                         }
8833                         if (bundle.containsKey(KEY_EXTRAS_BUNDLE)) {
8834                             message.getExtras().putAll(bundle.getBundle(KEY_EXTRAS_BUNDLE));
8835                         }
8836                         return message;
8837                     }
8838                 } catch (ClassCastException e) {
8839                     return null;
8840                 }
8841             }
8842         }
8843     }
8844 
8845     /**
8846      * Helper class for generating large-format notifications that include a list of (up to 5) strings.
8847      *
8848      * Here's how you'd set the <code>InboxStyle</code> on a notification:
8849      * <pre class="prettyprint">
8850      * Notification notif = new Notification.Builder(mContext)
8851      *     .setContentTitle(&quot;5 New mails from &quot; + sender.toString())
8852      *     .setContentText(subject)
8853      *     .setSmallIcon(R.drawable.new_mail)
8854      *     .setLargeIcon(aBitmap)
8855      *     .setStyle(new Notification.InboxStyle()
8856      *         .addLine(str1)
8857      *         .addLine(str2)
8858      *         .setContentTitle(&quot;&quot;)
8859      *         .setSummaryText(&quot;+3 more&quot;))
8860      *     .build();
8861      * </pre>
8862      *
8863      * @see Notification#bigContentView
8864      */
8865     public static class InboxStyle extends Style {
8866 
8867         /**
8868          * The number of lines of remote input history allowed until we start reducing lines.
8869          */
8870         private static final int NUMBER_OF_HISTORY_ALLOWED_UNTIL_REDUCTION = 1;
8871         private ArrayList<CharSequence> mTexts = new ArrayList<CharSequence>(5);
8872 
InboxStyle()8873         public InboxStyle() {
8874         }
8875 
8876         /**
8877          * @deprecated use {@code InboxStyle()}.
8878          */
8879         @Deprecated
InboxStyle(Builder builder)8880         public InboxStyle(Builder builder) {
8881             setBuilder(builder);
8882         }
8883 
8884         /**
8885          * Overrides ContentTitle in the big form of the template.
8886          * This defaults to the value passed to setContentTitle().
8887          */
setBigContentTitle(CharSequence title)8888         public InboxStyle setBigContentTitle(CharSequence title) {
8889             internalSetBigContentTitle(safeCharSequence(title));
8890             return this;
8891         }
8892 
8893         /**
8894          * Set the first line of text after the detail section in the big form of the template.
8895          */
setSummaryText(CharSequence cs)8896         public InboxStyle setSummaryText(CharSequence cs) {
8897             internalSetSummaryText(safeCharSequence(cs));
8898             return this;
8899         }
8900 
8901         /**
8902          * Append a line to the digest section of the Inbox notification.
8903          */
addLine(CharSequence cs)8904         public InboxStyle addLine(CharSequence cs) {
8905             mTexts.add(safeCharSequence(cs));
8906             return this;
8907         }
8908 
8909         /**
8910          * @hide
8911          */
getLines()8912         public ArrayList<CharSequence> getLines() {
8913             return mTexts;
8914         }
8915 
8916         /**
8917          * @hide
8918          */
addExtras(Bundle extras)8919         public void addExtras(Bundle extras) {
8920             super.addExtras(extras);
8921 
8922             CharSequence[] a = new CharSequence[mTexts.size()];
8923             extras.putCharSequenceArray(EXTRA_TEXT_LINES, mTexts.toArray(a));
8924         }
8925 
8926         /**
8927          * @hide
8928          */
8929         @Override
restoreFromExtras(Bundle extras)8930         protected void restoreFromExtras(Bundle extras) {
8931             super.restoreFromExtras(extras);
8932 
8933             mTexts.clear();
8934             if (extras.containsKey(EXTRA_TEXT_LINES)) {
8935                 Collections.addAll(mTexts, extras.getCharSequenceArray(EXTRA_TEXT_LINES));
8936             }
8937         }
8938 
8939         /**
8940          * @hide
8941          */
makeBigContentView()8942         public RemoteViews makeBigContentView() {
8943             StandardTemplateParams p = mBuilder.mParams.reset()
8944                     .viewType(StandardTemplateParams.VIEW_TYPE_BIG)
8945                     .fillTextsFrom(mBuilder).text(null);
8946             TemplateBindResult result = new TemplateBindResult();
8947             RemoteViews contentView = getStandardView(mBuilder.getInboxLayoutResource(), p, result);
8948 
8949             int[] rowIds = {R.id.inbox_text0, R.id.inbox_text1, R.id.inbox_text2, R.id.inbox_text3,
8950                     R.id.inbox_text4, R.id.inbox_text5, R.id.inbox_text6};
8951 
8952             // Make sure all rows are gone in case we reuse a view.
8953             for (int rowId : rowIds) {
8954                 contentView.setViewVisibility(rowId, View.GONE);
8955             }
8956 
8957             int i=0;
8958             int topPadding = mBuilder.mContext.getResources().getDimensionPixelSize(
8959                     R.dimen.notification_inbox_item_top_padding);
8960             boolean first = true;
8961             int onlyViewId = 0;
8962             int maxRows = rowIds.length;
8963             if (mBuilder.mActions.size() > 0) {
8964                 maxRows--;
8965             }
8966             RemoteInputHistoryItem[] remoteInputHistory = getParcelableArrayFromBundle(
8967                     mBuilder.mN.extras, EXTRA_REMOTE_INPUT_HISTORY_ITEMS,
8968                     RemoteInputHistoryItem.class);
8969             if (remoteInputHistory != null
8970                     && remoteInputHistory.length > NUMBER_OF_HISTORY_ALLOWED_UNTIL_REDUCTION) {
8971                 // Let's remove some messages to make room for the remote input history.
8972                 // 1 is always able to fit, but let's remove them if they are 2 or 3
8973                 int numRemoteInputs = Math.min(remoteInputHistory.length,
8974                         MAX_REMOTE_INPUT_HISTORY_LINES);
8975                 int totalNumRows = mTexts.size() + numRemoteInputs
8976                         - NUMBER_OF_HISTORY_ALLOWED_UNTIL_REDUCTION;
8977                 if (totalNumRows > maxRows) {
8978                     int overflow = totalNumRows - maxRows;
8979                     if (mTexts.size() > maxRows) {
8980                         // Heuristic: if the Texts don't fit anyway, we'll rather drop the last
8981                         // few messages, even with the remote input
8982                         maxRows -= overflow;
8983                     } else  {
8984                         // otherwise we drop the first messages
8985                         i = overflow;
8986                     }
8987                 }
8988             }
8989             while (i < mTexts.size() && i < maxRows) {
8990                 CharSequence str = mTexts.get(i);
8991                 if (!TextUtils.isEmpty(str)) {
8992                     contentView.setViewVisibility(rowIds[i], View.VISIBLE);
8993                     contentView.setTextViewText(rowIds[i],
8994                             mBuilder.processTextSpans(mBuilder.processLegacyText(str)));
8995                     mBuilder.setTextViewColorSecondary(contentView, rowIds[i], p);
8996                     contentView.setViewPadding(rowIds[i], 0, topPadding, 0, 0);
8997                     if (first) {
8998                         onlyViewId = rowIds[i];
8999                     } else {
9000                         onlyViewId = 0;
9001                     }
9002                     first = false;
9003                 }
9004                 i++;
9005             }
9006             if (onlyViewId != 0) {
9007                 // We only have 1 entry, lets make it look like the normal Text of a Bigtext
9008                 topPadding = mBuilder.mContext.getResources().getDimensionPixelSize(
9009                         R.dimen.notification_text_margin_top);
9010                 contentView.setViewPadding(onlyViewId, 0, topPadding, 0, 0);
9011             }
9012 
9013             return contentView;
9014         }
9015 
9016         /**
9017          * @hide
9018          */
9019         @Override
areNotificationsVisiblyDifferent(Style other)9020         public boolean areNotificationsVisiblyDifferent(Style other) {
9021             if (other == null || getClass() != other.getClass()) {
9022                 return true;
9023             }
9024             InboxStyle newS = (InboxStyle) other;
9025 
9026             final ArrayList<CharSequence> myLines = getLines();
9027             final ArrayList<CharSequence> newLines = newS.getLines();
9028             final int n = myLines.size();
9029             if (n != newLines.size()) {
9030                 return true;
9031             }
9032 
9033             for (int i = 0; i < n; i++) {
9034                 if (!Objects.equals(
9035                         String.valueOf(myLines.get(i)),
9036                         String.valueOf(newLines.get(i)))) {
9037                     return true;
9038                 }
9039             }
9040             return false;
9041         }
9042     }
9043 
9044     /**
9045      * Notification style for media playback notifications.
9046      *
9047      * In the expanded form, {@link Notification#bigContentView}, up to 5
9048      * {@link Notification.Action}s specified with
9049      * {@link Notification.Builder#addAction(Action) addAction} will be
9050      * shown as icon-only pushbuttons, suitable for transport controls. The Bitmap given to
9051      * {@link Notification.Builder#setLargeIcon(android.graphics.Bitmap) setLargeIcon()} will be
9052      * treated as album artwork.
9053      * <p>
9054      * Unlike the other styles provided here, MediaStyle can also modify the standard-size
9055      * {@link Notification#contentView}; by providing action indices to
9056      * {@link #setShowActionsInCompactView(int...)} you can promote up to 3 actions to be displayed
9057      * in the standard view alongside the usual content.
9058      * <p>
9059      * Notifications created with MediaStyle will have their category set to
9060      * {@link Notification#CATEGORY_TRANSPORT CATEGORY_TRANSPORT} unless you set a different
9061      * category using {@link Notification.Builder#setCategory(String) setCategory()}.
9062      * <p>
9063      * Finally, if you attach a {@link android.media.session.MediaSession.Token} using
9064      * {@link android.app.Notification.MediaStyle#setMediaSession(MediaSession.Token)},
9065      * the System UI can identify this as a notification representing an active media session
9066      * and respond accordingly (by showing album artwork in the lockscreen, for example).
9067      *
9068      * <p>
9069      * Starting at {@link android.os.Build.VERSION_CODES#O Android O} any notification that has a
9070      * media session attached with {@link #setMediaSession(MediaSession.Token)} will be colorized.
9071      * You can opt-out of this behavior by using {@link Notification.Builder#setColorized(boolean)}.
9072      * <p>
9073      *
9074      * To use this style with your Notification, feed it to
9075      * {@link Notification.Builder#setStyle(android.app.Notification.Style)} like so:
9076      * <pre class="prettyprint">
9077      * Notification noti = new Notification.Builder()
9078      *     .setSmallIcon(R.drawable.ic_stat_player)
9079      *     .setContentTitle(&quot;Track title&quot;)
9080      *     .setContentText(&quot;Artist - Album&quot;)
9081      *     .setLargeIcon(albumArtBitmap))
9082      *     .setStyle(<b>new Notification.MediaStyle()</b>
9083      *         .setMediaSession(mySession))
9084      *     .build();
9085      * </pre>
9086      *
9087      * @see Notification#bigContentView
9088      * @see Notification.Builder#setColorized(boolean)
9089      */
9090     public static class MediaStyle extends Style {
9091         // Changing max media buttons requires also changing templates
9092         // (notification_template_material_media and notification_template_material_big_media).
9093         static final int MAX_MEDIA_BUTTONS_IN_COMPACT = 3;
9094         static final int MAX_MEDIA_BUTTONS = 5;
9095         @IdRes private static final int[] MEDIA_BUTTON_IDS = {
9096                 R.id.action0,
9097                 R.id.action1,
9098                 R.id.action2,
9099                 R.id.action3,
9100                 R.id.action4,
9101         };
9102 
9103         private int[] mActionsToShowInCompact = null;
9104         private MediaSession.Token mToken;
9105         private CharSequence mDeviceName;
9106         private int mDeviceIcon;
9107         private PendingIntent mDeviceIntent;
9108 
MediaStyle()9109         public MediaStyle() {
9110         }
9111 
9112         /**
9113          * @deprecated use {@code MediaStyle()}.
9114          */
9115         @Deprecated
MediaStyle(Builder builder)9116         public MediaStyle(Builder builder) {
9117             setBuilder(builder);
9118         }
9119 
9120         /**
9121          * Request up to 3 actions (by index in the order of addition) to be shown in the compact
9122          * notification view.
9123          *
9124          * @param actions the indices of the actions to show in the compact notification view
9125          */
setShowActionsInCompactView(int...actions)9126         public MediaStyle setShowActionsInCompactView(int...actions) {
9127             mActionsToShowInCompact = actions;
9128             return this;
9129         }
9130 
9131         /**
9132          * Attach a {@link android.media.session.MediaSession.Token} to this Notification
9133          * to provide additional playback information and control to the SystemUI.
9134          */
setMediaSession(MediaSession.Token token)9135         public MediaStyle setMediaSession(MediaSession.Token token) {
9136             mToken = token;
9137             return this;
9138         }
9139 
9140         /**
9141          * For media notifications associated with playback on a remote device, provide device
9142          * information that will replace the default values for the output switcher chip on the
9143          * media control, as well as an intent to use when the output switcher chip is tapped,
9144          * on devices where this is supported.
9145          * <p>
9146          * This method is intended for system applications to provide information and/or
9147          * functionality that would otherwise be unavailable to the default output switcher because
9148          * the media originated on a remote device.
9149          *
9150          * @param deviceName The name of the remote device to display
9151          * @param iconResource Icon resource representing the device
9152          * @param chipIntent PendingIntent to send when the output switcher is tapped. May be
9153          *                   {@code null}, in which case the output switcher will be disabled.
9154          *                   This intent should open an Activity or it will be ignored.
9155          * @return MediaStyle
9156          *
9157          * @hide
9158          */
9159         @SystemApi
9160         @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL)
9161         @NonNull
setRemotePlaybackInfo(@onNull CharSequence deviceName, @DrawableRes int iconResource, @Nullable PendingIntent chipIntent)9162         public MediaStyle setRemotePlaybackInfo(@NonNull CharSequence deviceName,
9163                 @DrawableRes int iconResource, @Nullable PendingIntent chipIntent) {
9164             mDeviceName = deviceName;
9165             mDeviceIcon = iconResource;
9166             mDeviceIntent = chipIntent;
9167             return this;
9168         }
9169 
9170         /**
9171          * @hide
9172          */
9173         @Override
9174         @UnsupportedAppUsage
buildStyled(Notification wip)9175         public Notification buildStyled(Notification wip) {
9176             super.buildStyled(wip);
9177             if (wip.category == null) {
9178                 wip.category = Notification.CATEGORY_TRANSPORT;
9179             }
9180             return wip;
9181         }
9182 
9183         /**
9184          * @hide
9185          */
9186         @Override
makeContentView(boolean increasedHeight)9187         public RemoteViews makeContentView(boolean increasedHeight) {
9188             return makeMediaContentView(null /* customContent */);
9189         }
9190 
9191         /**
9192          * @hide
9193          */
9194         @Override
makeBigContentView()9195         public RemoteViews makeBigContentView() {
9196             return makeMediaBigContentView(null /* customContent */);
9197         }
9198 
9199         /**
9200          * @hide
9201          */
9202         @Override
makeHeadsUpContentView(boolean increasedHeight)9203         public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
9204             return makeMediaContentView(null /* customContent */);
9205         }
9206 
9207         /** @hide */
9208         @Override
addExtras(Bundle extras)9209         public void addExtras(Bundle extras) {
9210             super.addExtras(extras);
9211 
9212             if (mToken != null) {
9213                 extras.putParcelable(EXTRA_MEDIA_SESSION, mToken);
9214             }
9215             if (mActionsToShowInCompact != null) {
9216                 extras.putIntArray(EXTRA_COMPACT_ACTIONS, mActionsToShowInCompact);
9217             }
9218             if (mDeviceName != null) {
9219                 extras.putCharSequence(EXTRA_MEDIA_REMOTE_DEVICE, mDeviceName);
9220             }
9221             if (mDeviceIcon > 0) {
9222                 extras.putInt(EXTRA_MEDIA_REMOTE_ICON, mDeviceIcon);
9223             }
9224             if (mDeviceIntent != null) {
9225                 extras.putParcelable(EXTRA_MEDIA_REMOTE_INTENT, mDeviceIntent);
9226             }
9227         }
9228 
9229         /**
9230          * @hide
9231          */
9232         @Override
restoreFromExtras(Bundle extras)9233         protected void restoreFromExtras(Bundle extras) {
9234             super.restoreFromExtras(extras);
9235 
9236             if (extras.containsKey(EXTRA_MEDIA_SESSION)) {
9237                 mToken = extras.getParcelable(EXTRA_MEDIA_SESSION, MediaSession.Token.class);
9238             }
9239             if (extras.containsKey(EXTRA_COMPACT_ACTIONS)) {
9240                 mActionsToShowInCompact = extras.getIntArray(EXTRA_COMPACT_ACTIONS);
9241             }
9242             if (extras.containsKey(EXTRA_MEDIA_REMOTE_DEVICE)) {
9243                 mDeviceName = extras.getCharSequence(EXTRA_MEDIA_REMOTE_DEVICE);
9244             }
9245             if (extras.containsKey(EXTRA_MEDIA_REMOTE_ICON)) {
9246                 mDeviceIcon = extras.getInt(EXTRA_MEDIA_REMOTE_ICON);
9247             }
9248             if (extras.containsKey(EXTRA_MEDIA_REMOTE_INTENT)) {
9249                 mDeviceIntent = extras.getParcelable(
9250                         EXTRA_MEDIA_REMOTE_INTENT, PendingIntent.class);
9251             }
9252         }
9253 
9254         /**
9255          * @hide
9256          */
9257         @Override
areNotificationsVisiblyDifferent(Style other)9258         public boolean areNotificationsVisiblyDifferent(Style other) {
9259             if (other == null || getClass() != other.getClass()) {
9260                 return true;
9261             }
9262             // All fields to compare are on the Notification object
9263             return false;
9264         }
9265 
bindMediaActionButton(RemoteViews container, @IdRes int buttonId, Action action, StandardTemplateParams p)9266         private void bindMediaActionButton(RemoteViews container, @IdRes int buttonId,
9267                 Action action, StandardTemplateParams p) {
9268             final boolean tombstone = (action.actionIntent == null);
9269             container.setViewVisibility(buttonId, View.VISIBLE);
9270             container.setImageViewIcon(buttonId, action.getIcon());
9271 
9272             // If the action buttons should not be tinted, then just use the default
9273             // notification color. Otherwise, just use the passed-in color.
9274             int tintColor = mBuilder.getStandardActionColor(p);
9275 
9276             container.setDrawableTint(buttonId, false, tintColor,
9277                     PorterDuff.Mode.SRC_ATOP);
9278 
9279             int rippleAlpha = mBuilder.getColors(p).getRippleAlpha();
9280             int rippleColor = Color.argb(rippleAlpha, Color.red(tintColor), Color.green(tintColor),
9281                     Color.blue(tintColor));
9282             container.setRippleDrawableColor(buttonId, ColorStateList.valueOf(rippleColor));
9283 
9284             if (!tombstone) {
9285                 container.setOnClickPendingIntent(buttonId, action.actionIntent);
9286             }
9287             container.setContentDescription(buttonId, action.title);
9288         }
9289 
9290         /** @hide */
makeMediaContentView(@ullable RemoteViews customContent)9291         protected RemoteViews makeMediaContentView(@Nullable RemoteViews customContent) {
9292             final int numActions = mBuilder.mActions.size();
9293             final int numActionsToShow = Math.min(mActionsToShowInCompact == null
9294                     ? 0 : mActionsToShowInCompact.length, MAX_MEDIA_BUTTONS_IN_COMPACT);
9295             if (numActionsToShow > numActions) {
9296                 throw new IllegalArgumentException(String.format(
9297                         "setShowActionsInCompactView: action %d out of bounds (max %d)",
9298                         numActions, numActions - 1));
9299             }
9300 
9301             StandardTemplateParams p = mBuilder.mParams.reset()
9302                     .viewType(StandardTemplateParams.VIEW_TYPE_NORMAL)
9303                     .hideTime(numActionsToShow > 1)       // hide if actions wider than a right icon
9304                     .hideSubText(numActionsToShow > 1)    // hide if actions wider than a right icon
9305                     .hideLeftIcon(false)                  // allow large icon on left when grouped
9306                     .hideRightIcon(numActionsToShow > 0)  // right icon or actions; not both
9307                     .hideProgress(true)
9308                     .fillTextsFrom(mBuilder);
9309             TemplateBindResult result = new TemplateBindResult();
9310             RemoteViews template = mBuilder.applyStandardTemplate(
9311                     R.layout.notification_template_material_media, p,
9312                     null /* result */);
9313 
9314             for (int i = 0; i < MAX_MEDIA_BUTTONS_IN_COMPACT; i++) {
9315                 if (i < numActionsToShow) {
9316                     final Action action = mBuilder.mActions.get(mActionsToShowInCompact[i]);
9317                     bindMediaActionButton(template, MEDIA_BUTTON_IDS[i], action, p);
9318                 } else {
9319                     template.setViewVisibility(MEDIA_BUTTON_IDS[i], View.GONE);
9320                 }
9321             }
9322             // Prevent a swooping expand animation when there are no actions
9323             boolean hasActions = numActionsToShow != 0;
9324             template.setViewVisibility(R.id.media_actions, hasActions ? View.VISIBLE : View.GONE);
9325 
9326             // Add custom view if provided by subclass.
9327             buildCustomContentIntoTemplate(mBuilder.mContext, template, customContent, p, result);
9328             return template;
9329         }
9330 
9331         /** @hide */
makeMediaBigContentView(@ullable RemoteViews customContent)9332         protected RemoteViews makeMediaBigContentView(@Nullable RemoteViews customContent) {
9333             final int actionCount = Math.min(mBuilder.mActions.size(), MAX_MEDIA_BUTTONS);
9334             StandardTemplateParams p = mBuilder.mParams.reset()
9335                     .viewType(StandardTemplateParams.VIEW_TYPE_BIG)
9336                     .hideProgress(true)
9337                     .fillTextsFrom(mBuilder);
9338             TemplateBindResult result = new TemplateBindResult();
9339             RemoteViews template = mBuilder.applyStandardTemplate(
9340                     R.layout.notification_template_material_big_media, p , result);
9341 
9342             for (int i = 0; i < MAX_MEDIA_BUTTONS; i++) {
9343                 if (i < actionCount) {
9344                     bindMediaActionButton(template,
9345                             MEDIA_BUTTON_IDS[i], mBuilder.mActions.get(i), p);
9346                 } else {
9347                     template.setViewVisibility(MEDIA_BUTTON_IDS[i], View.GONE);
9348                 }
9349             }
9350             buildCustomContentIntoTemplate(mBuilder.mContext, template, customContent, p, result);
9351             return template;
9352         }
9353     }
9354 
9355     /**
9356      * Helper class for generating large-format notifications that include a large image attachment.
9357      *
9358      * Here's how you'd set the <code>CallStyle</code> on a notification:
9359      * <pre class="prettyprint">
9360      * Notification notif = new Notification.Builder(mContext)
9361      *     .setSmallIcon(R.drawable.new_post)
9362      *     .setStyle(Notification.CallStyle.forIncomingCall(caller, declineIntent, answerIntent))
9363      *     .build();
9364      * </pre>
9365      */
9366     public static class CallStyle extends Style {
9367         /** @hide */
9368         public static final int CALL_TYPE_INCOMING = 1;
9369         /** @hide */
9370         public static final int CALL_TYPE_ONGOING = 2;
9371         /** @hide */
9372         public static final int CALL_TYPE_SCREENING = 3;
9373 
9374         /**
9375          * This is a key used privately on the action.extras to give spacing priority
9376          * to the required call actions
9377          */
9378         private static final String KEY_ACTION_PRIORITY = "key_action_priority";
9379 
9380         private int mCallType;
9381         private Person mPerson;
9382         private PendingIntent mAnswerIntent;
9383         private PendingIntent mDeclineIntent;
9384         private PendingIntent mHangUpIntent;
9385         private boolean mIsVideo;
9386         private Integer mAnswerButtonColor;
9387         private Integer mDeclineButtonColor;
9388         private Icon mVerificationIcon;
9389         private CharSequence mVerificationText;
9390 
CallStyle()9391         CallStyle() {
9392         }
9393 
9394         /**
9395          * Create a CallStyle for an incoming call.
9396          * This notification will have a decline and an answer action, will allow a single
9397          * custom {@link Builder#addAction(Action) action}, and will have a default
9398          * {@link Builder#setContentText(CharSequence) content text} for an incoming call.
9399          *
9400          * @param person        The person displayed as the caller.
9401          *                      The person also needs to have a non-empty name associated with it.
9402          * @param declineIntent The intent to be sent when the user taps the decline action
9403          * @param answerIntent  The intent to be sent when the user taps the answer action
9404          */
9405         @NonNull
forIncomingCall(@onNull Person person, @NonNull PendingIntent declineIntent, @NonNull PendingIntent answerIntent)9406         public static CallStyle forIncomingCall(@NonNull Person person,
9407                 @NonNull PendingIntent declineIntent, @NonNull PendingIntent answerIntent) {
9408             return new CallStyle(CALL_TYPE_INCOMING, person,
9409                     null /* hangUpIntent */,
9410                     requireNonNull(declineIntent, "declineIntent is required"),
9411                     requireNonNull(answerIntent, "answerIntent is required")
9412             );
9413         }
9414 
9415         /**
9416          * Create a CallStyle for an ongoing call.
9417          * This notification will have a hang up action, will allow up to two
9418          * custom {@link Builder#addAction(Action) actions}, and will have a default
9419          * {@link Builder#setContentText(CharSequence) content text} for an ongoing call.
9420          *
9421          * @param person       The person displayed as being on the other end of the call.
9422          *                     The person also needs to have a non-empty name associated with it.
9423          * @param hangUpIntent The intent to be sent when the user taps the hang up action
9424          */
9425         @NonNull
forOngoingCall(@onNull Person person, @NonNull PendingIntent hangUpIntent)9426         public static CallStyle forOngoingCall(@NonNull Person person,
9427                 @NonNull PendingIntent hangUpIntent) {
9428             return new CallStyle(CALL_TYPE_ONGOING, person,
9429                     requireNonNull(hangUpIntent, "hangUpIntent is required"),
9430                     null /* declineIntent */,
9431                     null /* answerIntent */
9432             );
9433         }
9434 
9435         /**
9436          * Create a CallStyle for a call that is being screened.
9437          * This notification will have a hang up and an answer action, will allow a single
9438          * custom {@link Builder#addAction(Action) action}, and will have a default
9439          * {@link Builder#setContentText(CharSequence) content text} for a call that is being
9440          * screened.
9441          *
9442          * @param person       The person displayed as the caller.
9443          *                     The person also needs to have a non-empty name associated with it.
9444          * @param hangUpIntent The intent to be sent when the user taps the hang up action
9445          * @param answerIntent The intent to be sent when the user taps the answer action
9446          */
9447         @NonNull
forScreeningCall(@onNull Person person, @NonNull PendingIntent hangUpIntent, @NonNull PendingIntent answerIntent)9448         public static CallStyle forScreeningCall(@NonNull Person person,
9449                 @NonNull PendingIntent hangUpIntent, @NonNull PendingIntent answerIntent) {
9450             return new CallStyle(CALL_TYPE_SCREENING, person,
9451                     requireNonNull(hangUpIntent, "hangUpIntent is required"),
9452                     null /* declineIntent */,
9453                     requireNonNull(answerIntent, "answerIntent is required")
9454             );
9455         }
9456 
9457         /**
9458          * @param person The person displayed for the incoming call.
9459          *             The user also needs to have a non-empty name associated with it.
9460          * @param hangUpIntent The intent to be sent when the user taps the hang up action
9461          * @param declineIntent The intent to be sent when the user taps the decline action
9462          * @param answerIntent The intent to be sent when the user taps the answer action
9463          */
CallStyle(int callType, @NonNull Person person, @Nullable PendingIntent hangUpIntent, @Nullable PendingIntent declineIntent, @Nullable PendingIntent answerIntent)9464         private CallStyle(int callType, @NonNull Person person,
9465                 @Nullable PendingIntent hangUpIntent, @Nullable PendingIntent declineIntent,
9466                 @Nullable PendingIntent answerIntent) {
9467             if (person == null || TextUtils.isEmpty(person.getName())) {
9468                 throw new IllegalArgumentException("person must have a non-empty a name");
9469             }
9470             mCallType = callType;
9471             mPerson = person;
9472             mAnswerIntent = answerIntent;
9473             mDeclineIntent = declineIntent;
9474             mHangUpIntent = hangUpIntent;
9475         }
9476 
9477         /**
9478          * Sets whether the call is a video call, which may affect the icons or text used on the
9479          * required action buttons.
9480          */
9481         @NonNull
setIsVideo(boolean isVideo)9482         public CallStyle setIsVideo(boolean isVideo) {
9483             mIsVideo = isVideo;
9484             return this;
9485         }
9486 
9487         /**
9488          * Optional icon to be displayed with {@link #setVerificationText(CharSequence) text}
9489          * as a verification status of the caller.
9490          */
9491         @NonNull
setVerificationIcon(@ullable Icon verificationIcon)9492         public CallStyle setVerificationIcon(@Nullable Icon verificationIcon) {
9493             mVerificationIcon = verificationIcon;
9494             return this;
9495         }
9496 
9497         /**
9498          * Optional text to be displayed with an {@link #setVerificationIcon(Icon) icon}
9499          * as a verification status of the caller.
9500          */
9501         @NonNull
setVerificationText(@ullable CharSequence verificationText)9502         public CallStyle setVerificationText(@Nullable CharSequence verificationText) {
9503             mVerificationText = safeCharSequence(verificationText);
9504             return this;
9505         }
9506 
9507         /**
9508          * Optional color to be used as a hint for the Answer action button's color.
9509          * The system may change this color to ensure sufficient contrast with the background.
9510          * The system may choose to disregard this hint if the notification is not colorized.
9511          */
9512         @NonNull
setAnswerButtonColorHint(@olorInt int color)9513         public CallStyle setAnswerButtonColorHint(@ColorInt int color) {
9514             mAnswerButtonColor = color;
9515             return this;
9516         }
9517 
9518         /**
9519          * Optional color to be used as a hint for the Decline or Hang Up action button's color.
9520          * The system may change this color to ensure sufficient contrast with the background.
9521          * The system may choose to disregard this hint if the notification is not colorized.
9522          */
9523         @NonNull
setDeclineButtonColorHint(@olorInt int color)9524         public CallStyle setDeclineButtonColorHint(@ColorInt int color) {
9525             mDeclineButtonColor = color;
9526             return this;
9527         }
9528 
9529         /** @hide */
9530         @Override
buildStyled(Notification wip)9531         public Notification buildStyled(Notification wip) {
9532             wip = super.buildStyled(wip);
9533             // ensure that the actions in the builder and notification are corrected.
9534             mBuilder.mActions = getActionsListWithSystemActions();
9535             wip.actions = new Action[mBuilder.mActions.size()];
9536             mBuilder.mActions.toArray(wip.actions);
9537             return wip;
9538         }
9539 
9540         /**
9541          * @hide
9542          */
displayCustomViewInline()9543         public boolean displayCustomViewInline() {
9544             // This is a lie; True is returned to make sure that the custom view is not used
9545             // instead of the template, but it will not actually be included.
9546             return true;
9547         }
9548 
9549         /**
9550          * @hide
9551          */
9552         @Override
purgeResources()9553         public void purgeResources() {
9554             super.purgeResources();
9555             if (mVerificationIcon != null) {
9556                 mVerificationIcon.convertToAshmem();
9557             }
9558         }
9559 
9560         /**
9561          * @hide
9562          */
9563         @Override
reduceImageSizes(Context context)9564         public void reduceImageSizes(Context context) {
9565             super.reduceImageSizes(context);
9566             if (mVerificationIcon != null) {
9567                 int rightIconSize = context.getResources().getDimensionPixelSize(
9568                         ActivityManager.isLowRamDeviceStatic()
9569                                 ? R.dimen.notification_right_icon_size_low_ram
9570                                 : R.dimen.notification_right_icon_size);
9571                 mVerificationIcon.scaleDownIfNecessary(rightIconSize, rightIconSize);
9572             }
9573         }
9574 
9575         /**
9576          * @hide
9577          */
9578         @Override
makeContentView(boolean increasedHeight)9579         public RemoteViews makeContentView(boolean increasedHeight) {
9580             return makeCallLayout(StandardTemplateParams.VIEW_TYPE_NORMAL);
9581         }
9582 
9583         /**
9584          * @hide
9585          */
9586         @Override
makeHeadsUpContentView(boolean increasedHeight)9587         public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
9588             return makeCallLayout(StandardTemplateParams.VIEW_TYPE_HEADS_UP);
9589         }
9590 
9591         /**
9592          * @hide
9593          */
makeBigContentView()9594         public RemoteViews makeBigContentView() {
9595             return makeCallLayout(StandardTemplateParams.VIEW_TYPE_BIG);
9596         }
9597 
9598         @NonNull
makeNegativeAction()9599         private Action makeNegativeAction() {
9600             if (mDeclineIntent == null) {
9601                 return makeAction(R.drawable.ic_call_decline,
9602                         R.string.call_notification_hang_up_action,
9603                         mDeclineButtonColor, R.color.call_notification_decline_color,
9604                         mHangUpIntent);
9605             } else {
9606                 return makeAction(R.drawable.ic_call_decline,
9607                         R.string.call_notification_decline_action,
9608                         mDeclineButtonColor, R.color.call_notification_decline_color,
9609                         mDeclineIntent);
9610             }
9611         }
9612 
9613         @Nullable
makeAnswerAction()9614         private Action makeAnswerAction() {
9615             return mAnswerIntent == null ? null : makeAction(
9616                     mIsVideo ? R.drawable.ic_call_answer_video : R.drawable.ic_call_answer,
9617                     mIsVideo ? R.string.call_notification_answer_video_action
9618                             : R.string.call_notification_answer_action,
9619                     mAnswerButtonColor, R.color.call_notification_answer_color,
9620                     mAnswerIntent);
9621         }
9622 
9623         @NonNull
makeAction(@rawableRes int icon, @StringRes int title, @ColorInt Integer colorInt, @ColorRes int defaultColorRes, PendingIntent intent)9624         private Action makeAction(@DrawableRes int icon, @StringRes int title,
9625                 @ColorInt Integer colorInt, @ColorRes int defaultColorRes, PendingIntent intent) {
9626             if (colorInt == null || !mBuilder.isCallActionColorCustomizable()) {
9627                 colorInt = mBuilder.mContext.getColor(defaultColorRes);
9628             }
9629             Action action = new Action.Builder(Icon.createWithResource("", icon),
9630                     new SpannableStringBuilder().append(mBuilder.mContext.getString(title),
9631                             new ForegroundColorSpan(colorInt),
9632                             SpannableStringBuilder.SPAN_INCLUSIVE_INCLUSIVE),
9633                     intent).build();
9634             action.getExtras().putBoolean(KEY_ACTION_PRIORITY, true);
9635             return action;
9636         }
9637 
isActionAddedByCallStyle(Action action)9638         private boolean isActionAddedByCallStyle(Action action) {
9639             // This is an internal extra added by the style to these actions. If an app were to add
9640             // this extra to the action themselves, the action would be dropped.  :shrug:
9641             return action != null && action.getExtras().getBoolean(KEY_ACTION_PRIORITY);
9642         }
9643 
9644         /**
9645          * Gets the actions list for the call with the answer/decline/hangUp actions inserted in
9646          * the correct place.  This returns the correct result even if the system actions have
9647          * already been added, and even if more actions were added since then.
9648          * @hide
9649          */
9650         @NonNull
getActionsListWithSystemActions()9651         public ArrayList<Action> getActionsListWithSystemActions() {
9652             // Define the system actions we expect to see
9653             final Action firstAction = makeNegativeAction();
9654             final Action lastAction = makeAnswerAction();
9655 
9656             // Start creating the result list.
9657             int nonContextualActionSlotsRemaining = MAX_ACTION_BUTTONS;
9658             ArrayList<Action> resultActions = new ArrayList<>(MAX_ACTION_BUTTONS);
9659 
9660             // Always have a first action.
9661             resultActions.add(firstAction);
9662             --nonContextualActionSlotsRemaining;
9663 
9664             // Copy actions into the new list, correcting system actions.
9665             if (mBuilder.mActions != null) {
9666                 for (Notification.Action action : mBuilder.mActions) {
9667                     if (action.isContextual()) {
9668                         // Always include all contextual actions
9669                         resultActions.add(action);
9670                     } else if (isActionAddedByCallStyle(action)) {
9671                         // Drop any old versions of system actions
9672                     } else {
9673                         // Copy non-contextual actions; decrement the remaining action slots.
9674                         resultActions.add(action);
9675                         --nonContextualActionSlotsRemaining;
9676                     }
9677                     // If there's exactly one action slot left, fill it with the lastAction.
9678                     if (lastAction != null && nonContextualActionSlotsRemaining == 1) {
9679                         resultActions.add(lastAction);
9680                         --nonContextualActionSlotsRemaining;
9681                     }
9682                 }
9683             }
9684             // If there are any action slots left, the lastAction still needs to be added.
9685             if (lastAction != null && nonContextualActionSlotsRemaining >= 1) {
9686                 resultActions.add(lastAction);
9687             }
9688             return resultActions;
9689         }
9690 
makeCallLayout(int viewType)9691         private RemoteViews makeCallLayout(int viewType) {
9692             final boolean isCollapsed = viewType == StandardTemplateParams.VIEW_TYPE_NORMAL;
9693             Bundle extras = mBuilder.mN.extras;
9694             CharSequence title = mPerson != null ? mPerson.getName() : null;
9695             CharSequence text = mBuilder.processLegacyText(extras.getCharSequence(EXTRA_TEXT));
9696             if (text == null) {
9697                 text = getDefaultText();
9698             }
9699 
9700             // Bind standard template
9701             StandardTemplateParams p = mBuilder.mParams.reset()
9702                     .viewType(viewType)
9703                     .callStyleActions(true)
9704                     .allowTextWithProgress(true)
9705                     .hideLeftIcon(true)
9706                     .hideRightIcon(true)
9707                     .hideAppName(isCollapsed)
9708                     .titleViewId(R.id.conversation_text)
9709                     .title(title)
9710                     .text(text)
9711                     .summaryText(mBuilder.processLegacyText(mVerificationText));
9712             mBuilder.mActions = getActionsListWithSystemActions();
9713             final RemoteViews contentView;
9714             if (isCollapsed) {
9715                 contentView = mBuilder.applyStandardTemplate(
9716                         R.layout.notification_template_material_call, p, null /* result */);
9717             } else {
9718                 contentView = mBuilder.applyStandardTemplateWithActions(
9719                         R.layout.notification_template_material_big_call, p, null /* result */);
9720             }
9721 
9722             // Bind some extra conversation-specific header fields.
9723             if (!p.mHideAppName) {
9724                 mBuilder.setTextViewColorSecondary(contentView, R.id.app_name_divider, p);
9725                 contentView.setViewVisibility(R.id.app_name_divider, View.VISIBLE);
9726             }
9727             bindCallerVerification(contentView, p);
9728 
9729             // Bind some custom CallLayout properties
9730             contentView.setInt(R.id.status_bar_latest_event_content, "setLayoutColor",
9731                     mBuilder.getSmallIconColor(p));
9732             contentView.setInt(R.id.status_bar_latest_event_content,
9733                     "setNotificationBackgroundColor", mBuilder.getBackgroundColor(p));
9734             contentView.setIcon(R.id.status_bar_latest_event_content, "setLargeIcon",
9735                     mBuilder.mN.mLargeIcon);
9736             contentView.setBundle(R.id.status_bar_latest_event_content, "setData",
9737                     mBuilder.mN.extras);
9738 
9739             return contentView;
9740         }
9741 
bindCallerVerification(RemoteViews contentView, StandardTemplateParams p)9742         private void bindCallerVerification(RemoteViews contentView, StandardTemplateParams p) {
9743             String iconContentDescription = null;
9744             boolean showDivider = true;
9745             if (mVerificationIcon != null) {
9746                 contentView.setImageViewIcon(R.id.verification_icon, mVerificationIcon);
9747                 contentView.setDrawableTint(R.id.verification_icon, false /* targetBackground */,
9748                         mBuilder.getSecondaryTextColor(p), PorterDuff.Mode.SRC_ATOP);
9749                 contentView.setViewVisibility(R.id.verification_icon, View.VISIBLE);
9750                 iconContentDescription = mBuilder.mContext.getString(
9751                         R.string.notification_verified_content_description);
9752                 showDivider = false;  // the icon replaces the divider
9753             } else {
9754                 contentView.setViewVisibility(R.id.verification_icon, View.GONE);
9755             }
9756             if (!TextUtils.isEmpty(mVerificationText)) {
9757                 contentView.setTextViewText(R.id.verification_text, mVerificationText);
9758                 mBuilder.setTextViewColorSecondary(contentView, R.id.verification_text, p);
9759                 contentView.setViewVisibility(R.id.verification_text, View.VISIBLE);
9760                 iconContentDescription = null;  // let the app's text take precedence
9761             } else {
9762                 contentView.setViewVisibility(R.id.verification_text, View.GONE);
9763                 showDivider = false;  // no divider if no text
9764             }
9765             contentView.setContentDescription(R.id.verification_icon, iconContentDescription);
9766             if (showDivider) {
9767                 contentView.setViewVisibility(R.id.verification_divider, View.VISIBLE);
9768                 mBuilder.setTextViewColorSecondary(contentView, R.id.verification_divider, p);
9769             } else {
9770                 contentView.setViewVisibility(R.id.verification_divider, View.GONE);
9771             }
9772         }
9773 
9774         @Nullable
getDefaultText()9775         private String getDefaultText() {
9776             switch (mCallType) {
9777                 case CALL_TYPE_INCOMING:
9778                     return mBuilder.mContext.getString(R.string.call_notification_incoming_text);
9779                 case CALL_TYPE_ONGOING:
9780                     return mBuilder.mContext.getString(R.string.call_notification_ongoing_text);
9781                 case CALL_TYPE_SCREENING:
9782                     return mBuilder.mContext.getString(R.string.call_notification_screening_text);
9783             }
9784             return null;
9785         }
9786 
9787         /**
9788          * @hide
9789          */
addExtras(Bundle extras)9790         public void addExtras(Bundle extras) {
9791             super.addExtras(extras);
9792             extras.putInt(EXTRA_CALL_TYPE, mCallType);
9793             extras.putBoolean(EXTRA_CALL_IS_VIDEO, mIsVideo);
9794             extras.putParcelable(EXTRA_CALL_PERSON, mPerson);
9795             if (mVerificationIcon != null) {
9796                 extras.putParcelable(EXTRA_VERIFICATION_ICON, mVerificationIcon);
9797             }
9798             if (mVerificationText != null) {
9799                 extras.putCharSequence(EXTRA_VERIFICATION_TEXT, mVerificationText);
9800             }
9801             if (mAnswerIntent != null) {
9802                 extras.putParcelable(EXTRA_ANSWER_INTENT, mAnswerIntent);
9803             }
9804             if (mDeclineIntent != null) {
9805                 extras.putParcelable(EXTRA_DECLINE_INTENT, mDeclineIntent);
9806             }
9807             if (mHangUpIntent != null) {
9808                 extras.putParcelable(EXTRA_HANG_UP_INTENT, mHangUpIntent);
9809             }
9810             if (mAnswerButtonColor != null) {
9811                 extras.putInt(EXTRA_ANSWER_COLOR, mAnswerButtonColor);
9812             }
9813             if (mDeclineButtonColor != null) {
9814                 extras.putInt(EXTRA_DECLINE_COLOR, mDeclineButtonColor);
9815             }
9816             fixTitleAndTextExtras(extras);
9817         }
9818 
fixTitleAndTextExtras(Bundle extras)9819         private void fixTitleAndTextExtras(Bundle extras) {
9820             CharSequence sender = mPerson != null ? mPerson.getName() : null;
9821             if (sender != null) {
9822                 extras.putCharSequence(EXTRA_TITLE, sender);
9823             }
9824             if (extras.getCharSequence(EXTRA_TEXT) == null) {
9825                 extras.putCharSequence(EXTRA_TEXT, getDefaultText());
9826             }
9827         }
9828 
9829         /**
9830          * @hide
9831          */
9832         @Override
restoreFromExtras(Bundle extras)9833         protected void restoreFromExtras(Bundle extras) {
9834             super.restoreFromExtras(extras);
9835             mCallType = extras.getInt(EXTRA_CALL_TYPE);
9836             mIsVideo = extras.getBoolean(EXTRA_CALL_IS_VIDEO);
9837             mPerson = extras.getParcelable(EXTRA_CALL_PERSON, Person.class);
9838             mVerificationIcon = extras.getParcelable(EXTRA_VERIFICATION_ICON);
9839             mVerificationText = extras.getCharSequence(EXTRA_VERIFICATION_TEXT);
9840             mAnswerIntent = extras.getParcelable(EXTRA_ANSWER_INTENT, PendingIntent.class);
9841             mDeclineIntent = extras.getParcelable(EXTRA_DECLINE_INTENT, PendingIntent.class);
9842             mHangUpIntent = extras.getParcelable(EXTRA_HANG_UP_INTENT, PendingIntent.class);
9843             mAnswerButtonColor = extras.containsKey(EXTRA_ANSWER_COLOR)
9844                     ? extras.getInt(EXTRA_ANSWER_COLOR) : null;
9845             mDeclineButtonColor = extras.containsKey(EXTRA_DECLINE_COLOR)
9846                     ? extras.getInt(EXTRA_DECLINE_COLOR) : null;
9847         }
9848 
9849         /**
9850          * @hide
9851          */
9852         @Override
hasSummaryInHeader()9853         public boolean hasSummaryInHeader() {
9854             return false;
9855         }
9856 
9857         /**
9858          * @hide
9859          */
9860         @Override
areNotificationsVisiblyDifferent(Style other)9861         public boolean areNotificationsVisiblyDifferent(Style other) {
9862             if (other == null || getClass() != other.getClass()) {
9863                 return true;
9864             }
9865             CallStyle otherS = (CallStyle) other;
9866             return !Objects.equals(mCallType, otherS.mCallType)
9867                     || !Objects.equals(mPerson, otherS.mPerson)
9868                     || !Objects.equals(mVerificationText, otherS.mVerificationText);
9869         }
9870     }
9871 
9872     /**
9873      * Notification style for custom views that are decorated by the system
9874      *
9875      * <p>Instead of providing a notification that is completely custom, a developer can set this
9876      * style and still obtain system decorations like the notification header with the expand
9877      * affordance and actions.
9878      *
9879      * <p>Use {@link android.app.Notification.Builder#setCustomContentView(RemoteViews)},
9880      * {@link android.app.Notification.Builder#setCustomBigContentView(RemoteViews)} and
9881      * {@link android.app.Notification.Builder#setCustomHeadsUpContentView(RemoteViews)} to set the
9882      * corresponding custom views to display.
9883      *
9884      * To use this style with your Notification, feed it to
9885      * {@link Notification.Builder#setStyle(android.app.Notification.Style)} like so:
9886      * <pre class="prettyprint">
9887      * Notification noti = new Notification.Builder()
9888      *     .setSmallIcon(R.drawable.ic_stat_player)
9889      *     .setLargeIcon(albumArtBitmap))
9890      *     .setCustomContentView(contentView);
9891      *     .setStyle(<b>new Notification.DecoratedCustomViewStyle()</b>)
9892      *     .build();
9893      * </pre>
9894      */
9895     public static class DecoratedCustomViewStyle extends Style {
9896 
DecoratedCustomViewStyle()9897         public DecoratedCustomViewStyle() {
9898         }
9899 
9900         /**
9901          * @hide
9902          */
displayCustomViewInline()9903         public boolean displayCustomViewInline() {
9904             return true;
9905         }
9906 
9907         /**
9908          * @hide
9909          */
9910         @Override
makeContentView(boolean increasedHeight)9911         public RemoteViews makeContentView(boolean increasedHeight) {
9912             return makeStandardTemplateWithCustomContent(mBuilder.mN.contentView);
9913         }
9914 
9915         /**
9916          * @hide
9917          */
9918         @Override
makeBigContentView()9919         public RemoteViews makeBigContentView() {
9920             return makeDecoratedBigContentView();
9921         }
9922 
9923         /**
9924          * @hide
9925          */
9926         @Override
makeHeadsUpContentView(boolean increasedHeight)9927         public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
9928             return makeDecoratedHeadsUpContentView();
9929         }
9930 
makeDecoratedHeadsUpContentView()9931         private RemoteViews makeDecoratedHeadsUpContentView() {
9932             RemoteViews headsUpContentView = mBuilder.mN.headsUpContentView == null
9933                     ? mBuilder.mN.contentView
9934                     : mBuilder.mN.headsUpContentView;
9935             if (headsUpContentView == null) {
9936                 return null;  // no custom view; use the default behavior
9937             }
9938             if (mBuilder.mActions.size() == 0) {
9939                return makeStandardTemplateWithCustomContent(headsUpContentView);
9940             }
9941             TemplateBindResult result = new TemplateBindResult();
9942             StandardTemplateParams p = mBuilder.mParams.reset()
9943                     .viewType(StandardTemplateParams.VIEW_TYPE_HEADS_UP)
9944                     .decorationType(StandardTemplateParams.DECORATION_PARTIAL)
9945                     .fillTextsFrom(mBuilder);
9946             RemoteViews remoteViews = mBuilder.applyStandardTemplateWithActions(
9947                     mBuilder.getHeadsUpBaseLayoutResource(), p, result);
9948             buildCustomContentIntoTemplate(mBuilder.mContext, remoteViews, headsUpContentView,
9949                     p, result);
9950             return remoteViews;
9951         }
9952 
makeStandardTemplateWithCustomContent(RemoteViews customContent)9953         private RemoteViews makeStandardTemplateWithCustomContent(RemoteViews customContent) {
9954             if (customContent == null) {
9955                 return null;  // no custom view; use the default behavior
9956             }
9957             TemplateBindResult result = new TemplateBindResult();
9958             StandardTemplateParams p = mBuilder.mParams.reset()
9959                     .viewType(StandardTemplateParams.VIEW_TYPE_NORMAL)
9960                     .decorationType(StandardTemplateParams.DECORATION_PARTIAL)
9961                     .fillTextsFrom(mBuilder);
9962             RemoteViews remoteViews = mBuilder.applyStandardTemplate(
9963                     mBuilder.getBaseLayoutResource(), p, result);
9964             buildCustomContentIntoTemplate(mBuilder.mContext, remoteViews, customContent,
9965                     p, result);
9966             return remoteViews;
9967         }
9968 
makeDecoratedBigContentView()9969         private RemoteViews makeDecoratedBigContentView() {
9970             RemoteViews bigContentView = mBuilder.mN.bigContentView == null
9971                     ? mBuilder.mN.contentView
9972                     : mBuilder.mN.bigContentView;
9973             if (bigContentView == null) {
9974                 return null;  // no custom view; use the default behavior
9975             }
9976             TemplateBindResult result = new TemplateBindResult();
9977             StandardTemplateParams p = mBuilder.mParams.reset()
9978                     .viewType(StandardTemplateParams.VIEW_TYPE_BIG)
9979                     .decorationType(StandardTemplateParams.DECORATION_PARTIAL)
9980                     .fillTextsFrom(mBuilder);
9981             RemoteViews remoteViews = mBuilder.applyStandardTemplateWithActions(
9982                     mBuilder.getBigBaseLayoutResource(), p, result);
9983             buildCustomContentIntoTemplate(mBuilder.mContext, remoteViews, bigContentView,
9984                     p, result);
9985             return remoteViews;
9986         }
9987 
9988         /**
9989          * @hide
9990          */
9991         @Override
areNotificationsVisiblyDifferent(Style other)9992         public boolean areNotificationsVisiblyDifferent(Style other) {
9993             if (other == null || getClass() != other.getClass()) {
9994                 return true;
9995             }
9996             // Comparison done for all custom RemoteViews, independent of style
9997             return false;
9998         }
9999     }
10000 
10001     /**
10002      * Notification style for media custom views that are decorated by the system
10003      *
10004      * <p>Instead of providing a media notification that is completely custom, a developer can set
10005      * this style and still obtain system decorations like the notification header with the expand
10006      * affordance and actions.
10007      *
10008      * <p>Use {@link android.app.Notification.Builder#setCustomContentView(RemoteViews)},
10009      * {@link android.app.Notification.Builder#setCustomBigContentView(RemoteViews)} and
10010      * {@link android.app.Notification.Builder#setCustomHeadsUpContentView(RemoteViews)} to set the
10011      * corresponding custom views to display.
10012      * <p>
10013      * Contrary to {@link MediaStyle} a developer has to opt-in to the colorizing of the
10014      * notification by using {@link Notification.Builder#setColorized(boolean)}.
10015      * <p>
10016      * To use this style with your Notification, feed it to
10017      * {@link Notification.Builder#setStyle(android.app.Notification.Style)} like so:
10018      * <pre class="prettyprint">
10019      * Notification noti = new Notification.Builder()
10020      *     .setSmallIcon(R.drawable.ic_stat_player)
10021      *     .setLargeIcon(albumArtBitmap))
10022      *     .setCustomContentView(contentView);
10023      *     .setStyle(<b>new Notification.DecoratedMediaCustomViewStyle()</b>
10024      *          .setMediaSession(mySession))
10025      *     .build();
10026      * </pre>
10027      *
10028      * @see android.app.Notification.DecoratedCustomViewStyle
10029      * @see android.app.Notification.MediaStyle
10030      */
10031     public static class DecoratedMediaCustomViewStyle extends MediaStyle {
10032 
DecoratedMediaCustomViewStyle()10033         public DecoratedMediaCustomViewStyle() {
10034         }
10035 
10036         /**
10037          * @hide
10038          */
displayCustomViewInline()10039         public boolean displayCustomViewInline() {
10040             return true;
10041         }
10042 
10043         /**
10044          * @hide
10045          */
10046         @Override
makeContentView(boolean increasedHeight)10047         public RemoteViews makeContentView(boolean increasedHeight) {
10048             return makeMediaContentView(mBuilder.mN.contentView);
10049         }
10050 
10051         /**
10052          * @hide
10053          */
10054         @Override
makeBigContentView()10055         public RemoteViews makeBigContentView() {
10056             RemoteViews customContent = mBuilder.mN.bigContentView != null
10057                     ? mBuilder.mN.bigContentView
10058                     : mBuilder.mN.contentView;
10059             return makeMediaBigContentView(customContent);
10060         }
10061 
10062         /**
10063          * @hide
10064          */
10065         @Override
makeHeadsUpContentView(boolean increasedHeight)10066         public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
10067             RemoteViews customContent = mBuilder.mN.headsUpContentView != null
10068                     ? mBuilder.mN.headsUpContentView
10069                     : mBuilder.mN.contentView;
10070             return makeMediaBigContentView(customContent);
10071         }
10072 
10073         /**
10074          * @hide
10075          */
10076         @Override
areNotificationsVisiblyDifferent(Style other)10077         public boolean areNotificationsVisiblyDifferent(Style other) {
10078             if (other == null || getClass() != other.getClass()) {
10079                 return true;
10080             }
10081             // Comparison done for all custom RemoteViews, independent of style
10082             return false;
10083         }
10084     }
10085 
10086     /**
10087      * Encapsulates the information needed to display a notification as a bubble.
10088      *
10089      * <p>A bubble is used to display app content in a floating window over the existing
10090      * foreground activity. A bubble has a collapsed state represented by an icon and an
10091      * expanded state that displays an activity. These may be defined via
10092      * {@link Builder#Builder(PendingIntent, Icon)} or they may
10093      * be defined via an existing shortcut using {@link Builder#Builder(String)}.
10094      * </p>
10095      *
10096      * <b>Notifications with a valid and allowed bubble will display in collapsed state
10097      * outside of the notification shade on unlocked devices. When a user interacts with the
10098      * collapsed bubble, the bubble activity will be invoked and displayed.</b>
10099      *
10100      * @see Notification.Builder#setBubbleMetadata(BubbleMetadata)
10101      */
10102     public static final class BubbleMetadata implements Parcelable {
10103 
10104         private PendingIntent mPendingIntent;
10105         private PendingIntent mDeleteIntent;
10106         private Icon mIcon;
10107         private int mDesiredHeight;
10108         @DimenRes private int mDesiredHeightResId;
10109         private int mFlags;
10110         private String mShortcutId;
10111 
10112         /**
10113          * If set and the app creating the bubble is in the foreground, the bubble will be posted
10114          * in its expanded state.
10115          *
10116          * <p>This flag has no effect if the app posting the bubble is not in the foreground.
10117          * The app is considered foreground if it is visible and on the screen, note that
10118          * a foreground service does not qualify.
10119          * </p>
10120          *
10121          * <p>Generally this flag should only be set if the user has performed an action to request
10122          * or create a bubble.</p>
10123          *
10124          * @hide
10125          */
10126         public static final int FLAG_AUTO_EXPAND_BUBBLE = 0x00000001;
10127 
10128         /**
10129          * Indicates whether the notification associated with the bubble is being visually
10130          * suppressed from the notification shade. When <code>true</code> the notification is
10131          * hidden, when <code>false</code> the notification shows as normal.
10132          *
10133          * <p>Apps sending bubbles may set this flag so that the bubble is posted <b>without</b>
10134          * the associated notification in the notification shade.</p>
10135          *
10136          * <p>Generally this flag should only be set by the app if the user has performed an
10137          * action to request or create a bubble, or if the user has seen the content in the
10138          * notification and the notification is no longer relevant. </p>
10139          *
10140          * <p>The system will also update this flag with <code>true</code> to hide the notification
10141          * from the user once the bubble has been expanded. </p>
10142          *
10143          * @hide
10144          */
10145         public static final int FLAG_SUPPRESS_NOTIFICATION = 0x00000002;
10146 
10147         /**
10148          * Indicates whether the bubble should be visually suppressed from the bubble stack if the
10149          * user is viewing the same content outside of the bubble. For example, the user has a
10150          * bubble with Alice and then opens up the main app and navigates to Alice's page.
10151          *
10152          * @hide
10153          */
10154         public static final int FLAG_SUPPRESSABLE_BUBBLE = 0x00000004;
10155 
10156         /**
10157          * Indicates whether the bubble is visually suppressed from the bubble stack.
10158          *
10159          * @hide
10160          */
10161         public static final int FLAG_SUPPRESS_BUBBLE = 0x00000008;
10162 
BubbleMetadata(PendingIntent expandIntent, PendingIntent deleteIntent, Icon icon, int height, @DimenRes int heightResId, String shortcutId)10163         private BubbleMetadata(PendingIntent expandIntent, PendingIntent deleteIntent,
10164                 Icon icon, int height, @DimenRes int heightResId, String shortcutId) {
10165             mPendingIntent = expandIntent;
10166             mIcon = icon;
10167             mDesiredHeight = height;
10168             mDesiredHeightResId = heightResId;
10169             mDeleteIntent = deleteIntent;
10170             mShortcutId = shortcutId;
10171         }
10172 
BubbleMetadata(Parcel in)10173         private BubbleMetadata(Parcel in) {
10174             if (in.readInt() != 0) {
10175                 mPendingIntent = PendingIntent.CREATOR.createFromParcel(in);
10176             }
10177             if (in.readInt() != 0) {
10178                 mIcon = Icon.CREATOR.createFromParcel(in);
10179             }
10180             mDesiredHeight = in.readInt();
10181             mFlags = in.readInt();
10182             if (in.readInt() != 0) {
10183                 mDeleteIntent = PendingIntent.CREATOR.createFromParcel(in);
10184             }
10185             mDesiredHeightResId = in.readInt();
10186             if (in.readInt() != 0) {
10187                 mShortcutId = in.readString8();
10188             }
10189         }
10190 
10191         /**
10192          * @return the shortcut id used for this bubble if created via
10193          * {@link Builder#Builder(String)} or null if created
10194          * via {@link Builder#Builder(PendingIntent, Icon)}.
10195          */
10196         @Nullable
getShortcutId()10197         public String getShortcutId() {
10198             return mShortcutId;
10199         }
10200 
10201         /**
10202          * @return the pending intent used to populate the floating window for this bubble, or
10203          * null if this bubble is created via {@link Builder#Builder(String)}.
10204          */
10205         @SuppressLint("InvalidNullConversion")
10206         @Nullable
getIntent()10207         public PendingIntent getIntent() {
10208             return mPendingIntent;
10209         }
10210 
10211         /**
10212          * @deprecated use {@link #getIntent()} instead.
10213          * @removed Removed from the R SDK but was never publicly stable.
10214          */
10215         @Nullable
10216         @Deprecated
getBubbleIntent()10217         public PendingIntent getBubbleIntent() {
10218             return mPendingIntent;
10219         }
10220 
10221         /**
10222          * @return the pending intent to send when the bubble is dismissed by a user, if one exists.
10223          */
10224         @Nullable
getDeleteIntent()10225         public PendingIntent getDeleteIntent() {
10226             return mDeleteIntent;
10227         }
10228 
10229         /**
10230          * @return the icon that will be displayed for this bubble when it is collapsed, or null
10231          * if the bubble is created via {@link Builder#Builder(String)}.
10232          */
10233         @SuppressLint("InvalidNullConversion")
10234         @Nullable
getIcon()10235         public Icon getIcon() {
10236             return mIcon;
10237         }
10238 
10239         /**
10240          * @deprecated use {@link #getIcon()} instead.
10241          * @removed Removed from the R SDK but was never publicly stable.
10242          */
10243         @Nullable
10244         @Deprecated
getBubbleIcon()10245         public Icon getBubbleIcon() {
10246             return mIcon;
10247         }
10248 
10249         /**
10250          * @return the ideal height, in DPs, for the floating window that app content defined by
10251          * {@link #getIntent()} for this bubble. A value of 0 indicates a desired height has
10252          * not been set.
10253          */
10254         @Dimension(unit = DP)
getDesiredHeight()10255         public int getDesiredHeight() {
10256             return mDesiredHeight;
10257         }
10258 
10259         /**
10260          * @return the resId of ideal height for the floating window that app content defined by
10261          * {@link #getIntent()} for this bubble. A value of 0 indicates a res value has not
10262          * been provided for the desired height.
10263          */
10264         @DimenRes
getDesiredHeightResId()10265         public int getDesiredHeightResId() {
10266             return mDesiredHeightResId;
10267         }
10268 
10269         /**
10270          * @return whether this bubble should auto expand when it is posted.
10271          *
10272          * @see BubbleMetadata.Builder#setAutoExpandBubble(boolean)
10273          */
getAutoExpandBubble()10274         public boolean getAutoExpandBubble() {
10275             return (mFlags & FLAG_AUTO_EXPAND_BUBBLE) != 0;
10276         }
10277 
10278         /**
10279          * Indicates whether the notification associated with the bubble is being visually
10280          * suppressed from the notification shade. When <code>true</code> the notification is
10281          * hidden, when <code>false</code> the notification shows as normal.
10282          *
10283          * <p>Apps sending bubbles may set this flag so that the bubble is posted <b>without</b>
10284          * the associated notification in the notification shade.</p>
10285          *
10286          * <p>Generally the app should only set this flag if the user has performed an
10287          * action to request or create a bubble, or if the user has seen the content in the
10288          * notification and the notification is no longer relevant. </p>
10289          *
10290          * <p>The system will update this flag with <code>true</code> to hide the notification
10291          * from the user once the bubble has been expanded.</p>
10292          *
10293          * @return whether this bubble should suppress the notification when it is posted.
10294          *
10295          * @see BubbleMetadata.Builder#setSuppressNotification(boolean)
10296          */
isNotificationSuppressed()10297         public boolean isNotificationSuppressed() {
10298             return (mFlags & FLAG_SUPPRESS_NOTIFICATION) != 0;
10299         }
10300 
10301         /**
10302          * Indicates whether the bubble should be visually suppressed from the bubble stack if the
10303          * user is viewing the same content outside of the bubble. For example, the user has a
10304          * bubble with Alice and then opens up the main app and navigates to Alice's page.
10305          *
10306          * To match the activity and the bubble notification, the bubble notification should
10307          * have a locus id set that matches a locus id set on the activity.
10308          *
10309          * @return whether this bubble should be suppressed when the same content is visible
10310          * outside of the bubble.
10311          *
10312          * @see BubbleMetadata.Builder#setSuppressableBubble(boolean)
10313          */
isBubbleSuppressable()10314         public boolean isBubbleSuppressable() {
10315             return (mFlags & FLAG_SUPPRESSABLE_BUBBLE) != 0;
10316         }
10317 
10318         /**
10319          * Indicates whether the bubble is currently visually suppressed from the bubble stack.
10320          *
10321          * @see BubbleMetadata.Builder#setSuppressableBubble(boolean)
10322          */
isBubbleSuppressed()10323         public boolean isBubbleSuppressed() {
10324             return (mFlags & FLAG_SUPPRESS_BUBBLE) != 0;
10325         }
10326 
10327         /**
10328          * Sets whether the notification associated with the bubble is being visually
10329          * suppressed from the notification shade. When <code>true</code> the notification is
10330          * hidden, when <code>false</code> the notification shows as normal.
10331          *
10332          * @hide
10333          */
setSuppressNotification(boolean suppressed)10334         public void setSuppressNotification(boolean suppressed) {
10335             if (suppressed) {
10336                 mFlags |= Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION;
10337             } else {
10338                 mFlags &= ~Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION;
10339             }
10340         }
10341 
10342         /**
10343          * Sets whether the bubble should be visually suppressed from the bubble stack if the
10344          * user is viewing the same content outside of the bubble. For example, the user has a
10345          * bubble with Alice and then opens up the main app and navigates to Alice's page.
10346          *
10347          * @hide
10348          */
setSuppressBubble(boolean suppressed)10349         public void setSuppressBubble(boolean suppressed) {
10350             if (suppressed) {
10351                 mFlags |= Notification.BubbleMetadata.FLAG_SUPPRESS_BUBBLE;
10352             } else {
10353                 mFlags &= ~Notification.BubbleMetadata.FLAG_SUPPRESS_BUBBLE;
10354             }
10355         }
10356 
10357         /**
10358          * @hide
10359          */
setFlags(int flags)10360         public void setFlags(int flags) {
10361             mFlags = flags;
10362         }
10363 
10364         /**
10365          * @hide
10366          */
getFlags()10367         public int getFlags() {
10368             return mFlags;
10369         }
10370 
10371         public static final @android.annotation.NonNull Parcelable.Creator<BubbleMetadata> CREATOR =
10372                 new Parcelable.Creator<BubbleMetadata>() {
10373 
10374                     @Override
10375                     public BubbleMetadata createFromParcel(Parcel source) {
10376                         return new BubbleMetadata(source);
10377                     }
10378 
10379                     @Override
10380                     public BubbleMetadata[] newArray(int size) {
10381                         return new BubbleMetadata[size];
10382                     }
10383                 };
10384 
10385         @Override
describeContents()10386         public int describeContents() {
10387             return 0;
10388         }
10389 
10390         @Override
writeToParcel(Parcel out, int flags)10391         public void writeToParcel(Parcel out, int flags) {
10392             out.writeInt(mPendingIntent != null ? 1 : 0);
10393             if (mPendingIntent != null) {
10394                 mPendingIntent.writeToParcel(out, 0);
10395             }
10396             out.writeInt(mIcon != null ? 1 : 0);
10397             if (mIcon != null) {
10398                 mIcon.writeToParcel(out, 0);
10399             }
10400             out.writeInt(mDesiredHeight);
10401             out.writeInt(mFlags);
10402             out.writeInt(mDeleteIntent != null ? 1 : 0);
10403             if (mDeleteIntent != null) {
10404                 mDeleteIntent.writeToParcel(out, 0);
10405             }
10406             out.writeInt(mDesiredHeightResId);
10407             out.writeInt(TextUtils.isEmpty(mShortcutId) ? 0 : 1);
10408             if (!TextUtils.isEmpty(mShortcutId)) {
10409                 out.writeString8(mShortcutId);
10410             }
10411         }
10412 
10413         /**
10414          * Builder to construct a {@link BubbleMetadata} object.
10415          */
10416         public static final class Builder {
10417 
10418             private PendingIntent mPendingIntent;
10419             private Icon mIcon;
10420             private int mDesiredHeight;
10421             @DimenRes private int mDesiredHeightResId;
10422             private int mFlags;
10423             private PendingIntent mDeleteIntent;
10424             private String mShortcutId;
10425 
10426             /**
10427              * @deprecated use {@link Builder#Builder(String)} for a bubble created via a
10428              * {@link ShortcutInfo} or {@link Builder#Builder(PendingIntent, Icon)} for a bubble
10429              * created via a {@link PendingIntent}.
10430              */
10431             @Deprecated
Builder()10432             public Builder() {
10433             }
10434 
10435             /**
10436              * Creates a {@link BubbleMetadata.Builder} based on a {@link ShortcutInfo}. To create
10437              * a shortcut bubble, ensure that the shortcut associated with the provided
10438              * {@param shortcutId} is published as a dynamic shortcut that was built with
10439              * {@link ShortcutInfo.Builder#setLongLived(boolean)} being true, otherwise your
10440              * notification will not be able to bubble.
10441              *
10442              * <p>The shortcut icon will be used to represent the bubble when it is collapsed.</p>
10443              *
10444              * <p>The shortcut activity will be used when the bubble is expanded. This will display
10445              * the shortcut activity in a floating window over the existing foreground activity.</p>
10446              *
10447              * <p>When the activity is launched from a bubble,
10448              * {@link Activity#isLaunchedFromBubble()} will return with {@code true}.
10449              * </p>
10450              *
10451              * <p>If the shortcut has not been published when the bubble notification is sent,
10452              * no bubble will be produced. If the shortcut is deleted while the bubble is active,
10453              * the bubble will be removed.</p>
10454              *
10455              * @throws NullPointerException if shortcutId is null.
10456              *
10457              * @see ShortcutInfo
10458              * @see ShortcutInfo.Builder#setLongLived(boolean)
10459              * @see android.content.pm.ShortcutManager#addDynamicShortcuts(List)
10460              */
Builder(@onNull String shortcutId)10461             public Builder(@NonNull String shortcutId) {
10462                 if (TextUtils.isEmpty(shortcutId)) {
10463                     throw new NullPointerException("Bubble requires a non-null shortcut id");
10464                 }
10465                 mShortcutId = shortcutId;
10466             }
10467 
10468             /**
10469              * Creates a {@link BubbleMetadata.Builder} based on the provided intent and icon.
10470              *
10471              * <p>The icon will be used to represent the bubble when it is collapsed. An icon
10472              * should be representative of the content within the bubble. If your app produces
10473              * multiple bubbles, the icon should be unique for each of them.</p>
10474              *
10475              * <p>The intent that will be used when the bubble is expanded. This will display the
10476              * app content in a floating window over the existing foreground activity. The intent
10477              * should point to a resizable activity. </p>
10478              *
10479              * <p>When the activity is launched from a bubble,
10480              * {@link Activity#isLaunchedFromBubble()} will return with {@code true}.
10481              * </p>
10482              *
10483              * Note that the pending intent used here requires PendingIntent.FLAG_MUTABLE.
10484              *
10485              * @throws NullPointerException if intent is null.
10486              * @throws NullPointerException if icon is null.
10487              */
Builder(@onNull PendingIntent intent, @NonNull Icon icon)10488             public Builder(@NonNull PendingIntent intent, @NonNull Icon icon) {
10489                 if (intent == null) {
10490                     throw new NullPointerException("Bubble requires non-null pending intent");
10491                 }
10492                 if (icon == null) {
10493                     throw new NullPointerException("Bubbles require non-null icon");
10494                 }
10495                 if (icon.getType() != TYPE_URI_ADAPTIVE_BITMAP
10496                         && icon.getType() != TYPE_URI) {
10497                     Log.w(TAG, "Bubbles work best with icons of TYPE_URI or "
10498                             + "TYPE_URI_ADAPTIVE_BITMAP. "
10499                             + "In the future, using an icon of this type will be required.");
10500                 }
10501                 mPendingIntent = intent;
10502                 mIcon = icon;
10503             }
10504 
10505             /**
10506              * @deprecated use {@link Builder#Builder(String)} instead.
10507              * @removed Removed from the R SDK but was never publicly stable.
10508              */
10509             @NonNull
10510             @Deprecated
createShortcutBubble(@onNull String shortcutId)10511             public BubbleMetadata.Builder createShortcutBubble(@NonNull String shortcutId) {
10512                 if (!TextUtils.isEmpty(shortcutId)) {
10513                     // If shortcut id is set, we don't use these if they were previously set.
10514                     mPendingIntent = null;
10515                     mIcon = null;
10516                 }
10517                 mShortcutId = shortcutId;
10518                 return this;
10519             }
10520 
10521             /**
10522              * @deprecated use {@link Builder#Builder(PendingIntent, Icon)} instead.
10523              * @removed Removed from the R SDK but was never publicly stable.
10524              */
10525             @NonNull
10526             @Deprecated
createIntentBubble(@onNull PendingIntent intent, @NonNull Icon icon)10527             public BubbleMetadata.Builder createIntentBubble(@NonNull PendingIntent intent,
10528                     @NonNull Icon icon) {
10529                 if (intent == null) {
10530                     throw new IllegalArgumentException("Bubble requires non-null pending intent");
10531                 }
10532                 if (icon == null) {
10533                     throw new IllegalArgumentException("Bubbles require non-null icon");
10534                 }
10535                 if (icon.getType() != TYPE_URI_ADAPTIVE_BITMAP
10536                         && icon.getType() != TYPE_URI) {
10537                     Log.w(TAG, "Bubbles work best with icons of TYPE_URI or "
10538                             + "TYPE_URI_ADAPTIVE_BITMAP. "
10539                             + "In the future, using an icon of this type will be required.");
10540                 }
10541                 mShortcutId = null;
10542                 mPendingIntent = intent;
10543                 mIcon = icon;
10544                 return this;
10545             }
10546 
10547             /**
10548              * Sets the intent for the bubble.
10549              *
10550              * <p>The intent that will be used when the bubble is expanded. This will display the
10551              * app content in a floating window over the existing foreground activity. The intent
10552              * should point to a resizable activity. </p>
10553              *
10554              * @throws NullPointerException  if intent is null.
10555              * @throws IllegalStateException if this builder was created via
10556              *                               {@link Builder#Builder(String)}.
10557              */
10558             @NonNull
setIntent(@onNull PendingIntent intent)10559             public BubbleMetadata.Builder setIntent(@NonNull PendingIntent intent) {
10560                 if (mShortcutId != null) {
10561                     throw new IllegalStateException("Created as a shortcut bubble, cannot set a "
10562                             + "PendingIntent. Consider using "
10563                             + "BubbleMetadata.Builder(PendingIntent,Icon) instead.");
10564                 }
10565                 if (intent == null) {
10566                     throw new NullPointerException("Bubble requires non-null pending intent");
10567                 }
10568                 mPendingIntent = intent;
10569                 return this;
10570             }
10571 
10572             /**
10573              * Sets the icon for the bubble. Can only be used if the bubble was created
10574              * via {@link Builder#Builder(PendingIntent, Icon)}.
10575              *
10576              * <p>The icon will be used to represent the bubble when it is collapsed. An icon
10577              * should be representative of the content within the bubble. If your app produces
10578              * multiple bubbles, the icon should be unique for each of them.</p>
10579              *
10580              * <p>It is recommended to use an {@link Icon} of type {@link Icon#TYPE_URI}
10581              * or {@link Icon#TYPE_URI_ADAPTIVE_BITMAP}</p>
10582              *
10583              * @throws NullPointerException  if icon is null.
10584              * @throws IllegalStateException if this builder was created via
10585              *                               {@link Builder#Builder(String)}.
10586              */
10587             @NonNull
setIcon(@onNull Icon icon)10588             public BubbleMetadata.Builder setIcon(@NonNull Icon icon) {
10589                 if (mShortcutId != null) {
10590                     throw new IllegalStateException("Created as a shortcut bubble, cannot set an "
10591                             + "Icon. Consider using "
10592                             + "BubbleMetadata.Builder(PendingIntent,Icon) instead.");
10593                 }
10594                 if (icon == null) {
10595                     throw new NullPointerException("Bubbles require non-null icon");
10596                 }
10597                 if (icon.getType() != TYPE_URI_ADAPTIVE_BITMAP
10598                         && icon.getType() != TYPE_URI) {
10599                     Log.w(TAG, "Bubbles work best with icons of TYPE_URI or "
10600                             + "TYPE_URI_ADAPTIVE_BITMAP. "
10601                             + "In the future, using an icon of this type will be required.");
10602                 }
10603                 mIcon = icon;
10604                 return this;
10605             }
10606 
10607             /**
10608              * Sets the desired height in DPs for the expanded content of the bubble.
10609              *
10610              * <p>This height may not be respected if there is not enough space on the screen or if
10611              * the provided height is too small to be useful.</p>
10612              *
10613              * <p>If {@link #setDesiredHeightResId(int)} was previously called on this builder, the
10614              * previous value set will be cleared after calling this method, and this value will
10615              * be used instead.</p>
10616              *
10617              * <p>A desired height (in DPs or via resID) is optional.</p>
10618              *
10619              * @see #setDesiredHeightResId(int)
10620              */
10621             @NonNull
setDesiredHeight(@imensionunit = DP) int height)10622             public BubbleMetadata.Builder setDesiredHeight(@Dimension(unit = DP) int height) {
10623                 mDesiredHeight = Math.max(height, 0);
10624                 mDesiredHeightResId = 0;
10625                 return this;
10626             }
10627 
10628 
10629             /**
10630              * Sets the desired height via resId for the expanded content of the bubble.
10631              *
10632              * <p>This height may not be respected if there is not enough space on the screen or if
10633              * the provided height is too small to be useful.</p>
10634              *
10635              * <p>If {@link #setDesiredHeight(int)} was previously called on this builder, the
10636              * previous value set will be cleared after calling this method, and this value will
10637              * be used instead.</p>
10638              *
10639              * <p>A desired height (in DPs or via resID) is optional.</p>
10640              *
10641              * @see #setDesiredHeight(int)
10642              */
10643             @NonNull
setDesiredHeightResId(@imenRes int heightResId)10644             public BubbleMetadata.Builder setDesiredHeightResId(@DimenRes int heightResId) {
10645                 mDesiredHeightResId = heightResId;
10646                 mDesiredHeight = 0;
10647                 return this;
10648             }
10649 
10650             /**
10651              * Sets whether the bubble will be posted in its expanded state.
10652              *
10653              * <p>This flag has no effect if the app posting the bubble is not in the foreground.
10654              * The app is considered foreground if it is visible and on the screen, note that
10655              * a foreground service does not qualify.
10656              * </p>
10657              *
10658              * <p>Generally, this flag should only be set if the user has performed an action to
10659              * request or create a bubble.</p>
10660              *
10661              * <p>Setting this flag is optional; it defaults to false.</p>
10662              */
10663             @NonNull
setAutoExpandBubble(boolean shouldExpand)10664             public BubbleMetadata.Builder setAutoExpandBubble(boolean shouldExpand) {
10665                 setFlag(FLAG_AUTO_EXPAND_BUBBLE, shouldExpand);
10666                 return this;
10667             }
10668 
10669             /**
10670              * Sets whether the bubble will be posted <b>without</b> the associated notification in
10671              * the notification shade.
10672              *
10673              * <p>Generally, this flag should only be set if the user has performed an action to
10674              * request or create a bubble, or if the user has seen the content in the notification
10675              * and the notification is no longer relevant.</p>
10676              *
10677              * <p>Setting this flag is optional; it defaults to false.</p>
10678              */
10679             @NonNull
setSuppressNotification(boolean shouldSuppressNotif)10680             public BubbleMetadata.Builder setSuppressNotification(boolean shouldSuppressNotif) {
10681                 setFlag(FLAG_SUPPRESS_NOTIFICATION, shouldSuppressNotif);
10682                 return this;
10683             }
10684 
10685             /**
10686              * Indicates whether the bubble should be visually suppressed from the bubble stack if
10687              * the user is viewing the same content outside of the bubble. For example, the user has
10688              * a bubble with Alice and then opens up the main app and navigates to Alice's page.
10689              *
10690              * To match the activity and the bubble notification, the bubble notification should
10691              * have a locus id set that matches a locus id set on the activity.
10692              *
10693              * {@link Notification.Builder#setLocusId(LocusId)}
10694              * {@link Activity#setLocusContext(LocusId, Bundle)}
10695              */
10696             @NonNull
setSuppressableBubble(boolean suppressBubble)10697             public BubbleMetadata.Builder setSuppressableBubble(boolean suppressBubble) {
10698                 setFlag(FLAG_SUPPRESSABLE_BUBBLE, suppressBubble);
10699                 return this;
10700             }
10701 
10702             /**
10703              * Sets an intent to send when this bubble is explicitly removed by the user.
10704              *
10705              * <p>Setting a delete intent is optional.</p>
10706              */
10707             @NonNull
setDeleteIntent(@ullable PendingIntent deleteIntent)10708             public BubbleMetadata.Builder setDeleteIntent(@Nullable PendingIntent deleteIntent) {
10709                 mDeleteIntent = deleteIntent;
10710                 return this;
10711             }
10712 
10713             /**
10714              * Creates the {@link BubbleMetadata} defined by this builder.
10715              *
10716              * @throws NullPointerException if required elements have not been set.
10717              */
10718             @NonNull
build()10719             public BubbleMetadata build() {
10720                 if (mShortcutId == null && mPendingIntent == null) {
10721                     throw new NullPointerException(
10722                             "Must supply pending intent or shortcut to bubble");
10723                 }
10724                 if (mShortcutId == null && mIcon == null) {
10725                     throw new NullPointerException(
10726                             "Must supply an icon or shortcut for the bubble");
10727                 }
10728                 BubbleMetadata data = new BubbleMetadata(mPendingIntent, mDeleteIntent,
10729                         mIcon, mDesiredHeight, mDesiredHeightResId, mShortcutId);
10730                 data.setFlags(mFlags);
10731                 return data;
10732             }
10733 
10734             /**
10735              * @hide
10736              */
setFlag(int mask, boolean value)10737             public BubbleMetadata.Builder setFlag(int mask, boolean value) {
10738                 if (value) {
10739                     mFlags |= mask;
10740                 } else {
10741                     mFlags &= ~mask;
10742                 }
10743                 return this;
10744             }
10745         }
10746     }
10747 
10748 
10749     // When adding a new Style subclass here, don't forget to update
10750     // Builder.getNotificationStyleClass.
10751 
10752     /**
10753      * Extender interface for use with {@link Builder#extend}. Extenders may be used to add
10754      * metadata or change options on a notification builder.
10755      */
10756     public interface Extender {
10757         /**
10758          * Apply this extender to a notification builder.
10759          * @param builder the builder to be modified.
10760          * @return the build object for chaining.
10761          */
extend(Builder builder)10762         public Builder extend(Builder builder);
10763     }
10764 
10765     /**
10766      * Helper class to add wearable extensions to notifications.
10767      * <p class="note"> See
10768      * <a href="{@docRoot}wear/notifications/creating.html">Creating Notifications
10769      * for Android Wear</a> for more information on how to use this class.
10770      * <p>
10771      * To create a notification with wearable extensions:
10772      * <ol>
10773      *   <li>Create a {@link android.app.Notification.Builder}, setting any desired
10774      *   properties.
10775      *   <li>Create a {@link android.app.Notification.WearableExtender}.
10776      *   <li>Set wearable-specific properties using the
10777      *   {@code add} and {@code set} methods of {@link android.app.Notification.WearableExtender}.
10778      *   <li>Call {@link android.app.Notification.Builder#extend} to apply the extensions to a
10779      *   notification.
10780      *   <li>Post the notification to the notification system with the
10781      *   {@code NotificationManager.notify(...)} methods.
10782      * </ol>
10783      *
10784      * <pre class="prettyprint">
10785      * Notification notif = new Notification.Builder(mContext)
10786      *         .setContentTitle(&quot;New mail from &quot; + sender.toString())
10787      *         .setContentText(subject)
10788      *         .setSmallIcon(R.drawable.new_mail)
10789      *         .extend(new Notification.WearableExtender()
10790      *                 .setContentIcon(R.drawable.new_mail))
10791      *         .build();
10792      * NotificationManager notificationManger =
10793      *         (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
10794      * notificationManger.notify(0, notif);</pre>
10795      *
10796      * <p>Wearable extensions can be accessed on an existing notification by using the
10797      * {@code WearableExtender(Notification)} constructor,
10798      * and then using the {@code get} methods to access values.
10799      *
10800      * <pre class="prettyprint">
10801      * Notification.WearableExtender wearableExtender = new Notification.WearableExtender(
10802      *         notification);
10803      * List&lt;Notification&gt; pages = wearableExtender.getPages();</pre>
10804      */
10805     public static final class WearableExtender implements Extender {
10806         /**
10807          * Sentinel value for an action index that is unset.
10808          */
10809         public static final int UNSET_ACTION_INDEX = -1;
10810 
10811         /**
10812          * Size value for use with {@link #setCustomSizePreset} to show this notification with
10813          * default sizing.
10814          * <p>For custom display notifications created using {@link #setDisplayIntent},
10815          * the default is {@link #SIZE_MEDIUM}. All other notifications size automatically based
10816          * on their content.
10817          *
10818          * @deprecated Display intents are no longer supported.
10819          */
10820         @Deprecated
10821         public static final int SIZE_DEFAULT = 0;
10822 
10823         /**
10824          * Size value for use with {@link #setCustomSizePreset} to show this notification
10825          * with an extra small size.
10826          * <p>This value is only applicable for custom display notifications created using
10827          * {@link #setDisplayIntent}.
10828          *
10829          * @deprecated Display intents are no longer supported.
10830          */
10831         @Deprecated
10832         public static final int SIZE_XSMALL = 1;
10833 
10834         /**
10835          * Size value for use with {@link #setCustomSizePreset} to show this notification
10836          * with a small size.
10837          * <p>This value is only applicable for custom display notifications created using
10838          * {@link #setDisplayIntent}.
10839          *
10840          * @deprecated Display intents are no longer supported.
10841          */
10842         @Deprecated
10843         public static final int SIZE_SMALL = 2;
10844 
10845         /**
10846          * Size value for use with {@link #setCustomSizePreset} to show this notification
10847          * with a medium size.
10848          * <p>This value is only applicable for custom display notifications created using
10849          * {@link #setDisplayIntent}.
10850          *
10851          * @deprecated Display intents are no longer supported.
10852          */
10853         @Deprecated
10854         public static final int SIZE_MEDIUM = 3;
10855 
10856         /**
10857          * Size value for use with {@link #setCustomSizePreset} to show this notification
10858          * with a large size.
10859          * <p>This value is only applicable for custom display notifications created using
10860          * {@link #setDisplayIntent}.
10861          *
10862          * @deprecated Display intents are no longer supported.
10863          */
10864         @Deprecated
10865         public static final int SIZE_LARGE = 4;
10866 
10867         /**
10868          * Size value for use with {@link #setCustomSizePreset} to show this notification
10869          * full screen.
10870          * <p>This value is only applicable for custom display notifications created using
10871          * {@link #setDisplayIntent}.
10872          *
10873          * @deprecated Display intents are no longer supported.
10874          */
10875         @Deprecated
10876         public static final int SIZE_FULL_SCREEN = 5;
10877 
10878         /**
10879          * Sentinel value for use with {@link #setHintScreenTimeout} to keep the screen on for a
10880          * short amount of time when this notification is displayed on the screen. This
10881          * is the default value.
10882          *
10883          * @deprecated This feature is no longer supported.
10884          */
10885         @Deprecated
10886         public static final int SCREEN_TIMEOUT_SHORT = 0;
10887 
10888         /**
10889          * Sentinel value for use with {@link #setHintScreenTimeout} to keep the screen on
10890          * for a longer amount of time when this notification is displayed on the screen.
10891          *
10892          * @deprecated This feature is no longer supported.
10893          */
10894         @Deprecated
10895         public static final int SCREEN_TIMEOUT_LONG = -1;
10896 
10897         /** Notification extra which contains wearable extensions */
10898         private static final String EXTRA_WEARABLE_EXTENSIONS = "android.wearable.EXTENSIONS";
10899 
10900         // Keys within EXTRA_WEARABLE_EXTENSIONS for wearable options.
10901         private static final String KEY_ACTIONS = "actions";
10902         private static final String KEY_FLAGS = "flags";
10903         static final String KEY_DISPLAY_INTENT = "displayIntent";
10904         private static final String KEY_PAGES = "pages";
10905         static final String KEY_BACKGROUND = "background";
10906         private static final String KEY_CONTENT_ICON = "contentIcon";
10907         private static final String KEY_CONTENT_ICON_GRAVITY = "contentIconGravity";
10908         private static final String KEY_CONTENT_ACTION_INDEX = "contentActionIndex";
10909         private static final String KEY_CUSTOM_SIZE_PRESET = "customSizePreset";
10910         private static final String KEY_CUSTOM_CONTENT_HEIGHT = "customContentHeight";
10911         private static final String KEY_GRAVITY = "gravity";
10912         private static final String KEY_HINT_SCREEN_TIMEOUT = "hintScreenTimeout";
10913         private static final String KEY_DISMISSAL_ID = "dismissalId";
10914         private static final String KEY_BRIDGE_TAG = "bridgeTag";
10915 
10916         // Flags bitwise-ored to mFlags
10917         private static final int FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE = 0x1;
10918         private static final int FLAG_HINT_HIDE_ICON = 1 << 1;
10919         private static final int FLAG_HINT_SHOW_BACKGROUND_ONLY = 1 << 2;
10920         private static final int FLAG_START_SCROLL_BOTTOM = 1 << 3;
10921         private static final int FLAG_HINT_AVOID_BACKGROUND_CLIPPING = 1 << 4;
10922         private static final int FLAG_BIG_PICTURE_AMBIENT = 1 << 5;
10923         private static final int FLAG_HINT_CONTENT_INTENT_LAUNCHES_ACTIVITY = 1 << 6;
10924 
10925         // Default value for flags integer
10926         private static final int DEFAULT_FLAGS = FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE;
10927 
10928         private static final int DEFAULT_CONTENT_ICON_GRAVITY = Gravity.END;
10929         private static final int DEFAULT_GRAVITY = Gravity.BOTTOM;
10930 
10931         private ArrayList<Action> mActions = new ArrayList<Action>();
10932         private int mFlags = DEFAULT_FLAGS;
10933         private PendingIntent mDisplayIntent;
10934         private ArrayList<Notification> mPages = new ArrayList<Notification>();
10935         private Bitmap mBackground;
10936         private int mContentIcon;
10937         private int mContentIconGravity = DEFAULT_CONTENT_ICON_GRAVITY;
10938         private int mContentActionIndex = UNSET_ACTION_INDEX;
10939         private int mCustomSizePreset = SIZE_DEFAULT;
10940         private int mCustomContentHeight;
10941         private int mGravity = DEFAULT_GRAVITY;
10942         private int mHintScreenTimeout;
10943         private String mDismissalId;
10944         private String mBridgeTag;
10945 
10946         /**
10947          * Create a {@link android.app.Notification.WearableExtender} with default
10948          * options.
10949          */
WearableExtender()10950         public WearableExtender() {
10951         }
10952 
WearableExtender(Notification notif)10953         public WearableExtender(Notification notif) {
10954             Bundle wearableBundle = notif.extras.getBundle(EXTRA_WEARABLE_EXTENSIONS);
10955             if (wearableBundle != null) {
10956                 List<Action> actions = wearableBundle.getParcelableArrayList(KEY_ACTIONS);
10957                 if (actions != null) {
10958                     mActions.addAll(actions);
10959                 }
10960 
10961                 mFlags = wearableBundle.getInt(KEY_FLAGS, DEFAULT_FLAGS);
10962                 mDisplayIntent = wearableBundle.getParcelable(
10963                         KEY_DISPLAY_INTENT, PendingIntent.class);
10964 
10965                 Notification[] pages = getParcelableArrayFromBundle(
10966                         wearableBundle, KEY_PAGES, Notification.class);
10967                 if (pages != null) {
10968                     Collections.addAll(mPages, pages);
10969                 }
10970 
10971                 mBackground = wearableBundle.getParcelable(KEY_BACKGROUND, Bitmap.class);
10972                 mContentIcon = wearableBundle.getInt(KEY_CONTENT_ICON);
10973                 mContentIconGravity = wearableBundle.getInt(KEY_CONTENT_ICON_GRAVITY,
10974                         DEFAULT_CONTENT_ICON_GRAVITY);
10975                 mContentActionIndex = wearableBundle.getInt(KEY_CONTENT_ACTION_INDEX,
10976                         UNSET_ACTION_INDEX);
10977                 mCustomSizePreset = wearableBundle.getInt(KEY_CUSTOM_SIZE_PRESET,
10978                         SIZE_DEFAULT);
10979                 mCustomContentHeight = wearableBundle.getInt(KEY_CUSTOM_CONTENT_HEIGHT);
10980                 mGravity = wearableBundle.getInt(KEY_GRAVITY, DEFAULT_GRAVITY);
10981                 mHintScreenTimeout = wearableBundle.getInt(KEY_HINT_SCREEN_TIMEOUT);
10982                 mDismissalId = wearableBundle.getString(KEY_DISMISSAL_ID);
10983                 mBridgeTag = wearableBundle.getString(KEY_BRIDGE_TAG);
10984             }
10985         }
10986 
10987         /**
10988          * Apply wearable extensions to a notification that is being built. This is typically
10989          * called by the {@link android.app.Notification.Builder#extend} method of
10990          * {@link android.app.Notification.Builder}.
10991          */
10992         @Override
extend(Notification.Builder builder)10993         public Notification.Builder extend(Notification.Builder builder) {
10994             Bundle wearableBundle = new Bundle();
10995 
10996             if (!mActions.isEmpty()) {
10997                 wearableBundle.putParcelableArrayList(KEY_ACTIONS, mActions);
10998             }
10999             if (mFlags != DEFAULT_FLAGS) {
11000                 wearableBundle.putInt(KEY_FLAGS, mFlags);
11001             }
11002             if (mDisplayIntent != null) {
11003                 wearableBundle.putParcelable(KEY_DISPLAY_INTENT, mDisplayIntent);
11004             }
11005             if (!mPages.isEmpty()) {
11006                 wearableBundle.putParcelableArray(KEY_PAGES, mPages.toArray(
11007                         new Notification[mPages.size()]));
11008             }
11009             if (mBackground != null) {
11010                 wearableBundle.putParcelable(KEY_BACKGROUND, mBackground);
11011             }
11012             if (mContentIcon != 0) {
11013                 wearableBundle.putInt(KEY_CONTENT_ICON, mContentIcon);
11014             }
11015             if (mContentIconGravity != DEFAULT_CONTENT_ICON_GRAVITY) {
11016                 wearableBundle.putInt(KEY_CONTENT_ICON_GRAVITY, mContentIconGravity);
11017             }
11018             if (mContentActionIndex != UNSET_ACTION_INDEX) {
11019                 wearableBundle.putInt(KEY_CONTENT_ACTION_INDEX,
11020                         mContentActionIndex);
11021             }
11022             if (mCustomSizePreset != SIZE_DEFAULT) {
11023                 wearableBundle.putInt(KEY_CUSTOM_SIZE_PRESET, mCustomSizePreset);
11024             }
11025             if (mCustomContentHeight != 0) {
11026                 wearableBundle.putInt(KEY_CUSTOM_CONTENT_HEIGHT, mCustomContentHeight);
11027             }
11028             if (mGravity != DEFAULT_GRAVITY) {
11029                 wearableBundle.putInt(KEY_GRAVITY, mGravity);
11030             }
11031             if (mHintScreenTimeout != 0) {
11032                 wearableBundle.putInt(KEY_HINT_SCREEN_TIMEOUT, mHintScreenTimeout);
11033             }
11034             if (mDismissalId != null) {
11035                 wearableBundle.putString(KEY_DISMISSAL_ID, mDismissalId);
11036             }
11037             if (mBridgeTag != null) {
11038                 wearableBundle.putString(KEY_BRIDGE_TAG, mBridgeTag);
11039             }
11040 
11041             builder.getExtras().putBundle(EXTRA_WEARABLE_EXTENSIONS, wearableBundle);
11042             return builder;
11043         }
11044 
11045         @Override
clone()11046         public WearableExtender clone() {
11047             WearableExtender that = new WearableExtender();
11048             that.mActions = new ArrayList<Action>(this.mActions);
11049             that.mFlags = this.mFlags;
11050             that.mDisplayIntent = this.mDisplayIntent;
11051             that.mPages = new ArrayList<Notification>(this.mPages);
11052             that.mBackground = this.mBackground;
11053             that.mContentIcon = this.mContentIcon;
11054             that.mContentIconGravity = this.mContentIconGravity;
11055             that.mContentActionIndex = this.mContentActionIndex;
11056             that.mCustomSizePreset = this.mCustomSizePreset;
11057             that.mCustomContentHeight = this.mCustomContentHeight;
11058             that.mGravity = this.mGravity;
11059             that.mHintScreenTimeout = this.mHintScreenTimeout;
11060             that.mDismissalId = this.mDismissalId;
11061             that.mBridgeTag = this.mBridgeTag;
11062             return that;
11063         }
11064 
11065         /**
11066          * Add a wearable action to this notification.
11067          *
11068          * <p>When wearable actions are added using this method, the set of actions that
11069          * show on a wearable device splits from devices that only show actions added
11070          * using {@link android.app.Notification.Builder#addAction}. This allows for customization
11071          * of which actions display on different devices.
11072          *
11073          * @param action the action to add to this notification
11074          * @return this object for method chaining
11075          * @see android.app.Notification.Action
11076          */
addAction(Action action)11077         public WearableExtender addAction(Action action) {
11078             mActions.add(action);
11079             return this;
11080         }
11081 
11082         /**
11083          * Adds wearable actions to this notification.
11084          *
11085          * <p>When wearable actions are added using this method, the set of actions that
11086          * show on a wearable device splits from devices that only show actions added
11087          * using {@link android.app.Notification.Builder#addAction}. This allows for customization
11088          * of which actions display on different devices.
11089          *
11090          * @param actions the actions to add to this notification
11091          * @return this object for method chaining
11092          * @see android.app.Notification.Action
11093          */
addActions(List<Action> actions)11094         public WearableExtender addActions(List<Action> actions) {
11095             mActions.addAll(actions);
11096             return this;
11097         }
11098 
11099         /**
11100          * Clear all wearable actions present on this builder.
11101          * @return this object for method chaining.
11102          * @see #addAction
11103          */
clearActions()11104         public WearableExtender clearActions() {
11105             mActions.clear();
11106             return this;
11107         }
11108 
11109         /**
11110          * Get the wearable actions present on this notification.
11111          */
getActions()11112         public List<Action> getActions() {
11113             return mActions;
11114         }
11115 
11116         /**
11117          * Set an intent to launch inside of an activity view when displaying
11118          * this notification. The {@link PendingIntent} provided should be for an activity.
11119          *
11120          * <pre class="prettyprint">
11121          * Intent displayIntent = new Intent(context, MyDisplayActivity.class);
11122          * PendingIntent displayPendingIntent = PendingIntent.getActivity(context,
11123          *         0, displayIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
11124          * Notification notif = new Notification.Builder(context)
11125          *         .extend(new Notification.WearableExtender()
11126          *                 .setDisplayIntent(displayPendingIntent)
11127          *                 .setCustomSizePreset(Notification.WearableExtender.SIZE_MEDIUM))
11128          *         .build();</pre>
11129          *
11130          * <p>The activity to launch needs to allow embedding, must be exported, and
11131          * should have an empty task affinity. It is also recommended to use the device
11132          * default light theme.
11133          *
11134          * <p>Example AndroidManifest.xml entry:
11135          * <pre class="prettyprint">
11136          * &lt;activity android:name=&quot;com.example.MyDisplayActivity&quot;
11137          *     android:exported=&quot;true&quot;
11138          *     android:allowEmbedded=&quot;true&quot;
11139          *     android:taskAffinity=&quot;&quot;
11140          *     android:theme=&quot;@android:style/Theme.DeviceDefault.Light&quot; /&gt;</pre>
11141          *
11142          * @param intent the {@link PendingIntent} for an activity
11143          * @return this object for method chaining
11144          * @see android.app.Notification.WearableExtender#getDisplayIntent
11145          * @deprecated Display intents are no longer supported.
11146          */
11147         @Deprecated
setDisplayIntent(PendingIntent intent)11148         public WearableExtender setDisplayIntent(PendingIntent intent) {
11149             mDisplayIntent = intent;
11150             return this;
11151         }
11152 
11153         /**
11154          * Get the intent to launch inside of an activity view when displaying this
11155          * notification. This {@code PendingIntent} should be for an activity.
11156          *
11157          * @deprecated Display intents are no longer supported.
11158          */
11159         @Deprecated
getDisplayIntent()11160         public PendingIntent getDisplayIntent() {
11161             return mDisplayIntent;
11162         }
11163 
11164         /**
11165          * Add an additional page of content to display with this notification. The current
11166          * notification forms the first page, and pages added using this function form
11167          * subsequent pages. This field can be used to separate a notification into multiple
11168          * sections.
11169          *
11170          * @param page the notification to add as another page
11171          * @return this object for method chaining
11172          * @see android.app.Notification.WearableExtender#getPages
11173          * @deprecated Multiple content pages are no longer supported.
11174          */
11175         @Deprecated
addPage(Notification page)11176         public WearableExtender addPage(Notification page) {
11177             mPages.add(page);
11178             return this;
11179         }
11180 
11181         /**
11182          * Add additional pages of content to display with this notification. The current
11183          * notification forms the first page, and pages added using this function form
11184          * subsequent pages. This field can be used to separate a notification into multiple
11185          * sections.
11186          *
11187          * @param pages a list of notifications
11188          * @return this object for method chaining
11189          * @see android.app.Notification.WearableExtender#getPages
11190          * @deprecated Multiple content pages are no longer supported.
11191          */
11192         @Deprecated
addPages(List<Notification> pages)11193         public WearableExtender addPages(List<Notification> pages) {
11194             mPages.addAll(pages);
11195             return this;
11196         }
11197 
11198         /**
11199          * Clear all additional pages present on this builder.
11200          * @return this object for method chaining.
11201          * @see #addPage
11202          * @deprecated Multiple content pages are no longer supported.
11203          */
11204         @Deprecated
clearPages()11205         public WearableExtender clearPages() {
11206             mPages.clear();
11207             return this;
11208         }
11209 
11210         /**
11211          * Get the array of additional pages of content for displaying this notification. The
11212          * current notification forms the first page, and elements within this array form
11213          * subsequent pages. This field can be used to separate a notification into multiple
11214          * sections.
11215          * @return the pages for this notification
11216          * @deprecated Multiple content pages are no longer supported.
11217          */
11218         @Deprecated
getPages()11219         public List<Notification> getPages() {
11220             return mPages;
11221         }
11222 
11223         /**
11224          * Set a background image to be displayed behind the notification content.
11225          * Contrary to the {@link android.app.Notification.BigPictureStyle}, this background
11226          * will work with any notification style.
11227          *
11228          * @param background the background bitmap
11229          * @return this object for method chaining
11230          * @see android.app.Notification.WearableExtender#getBackground
11231          * @deprecated Background images are no longer supported.
11232          */
11233         @Deprecated
setBackground(Bitmap background)11234         public WearableExtender setBackground(Bitmap background) {
11235             mBackground = background;
11236             return this;
11237         }
11238 
11239         /**
11240          * Get a background image to be displayed behind the notification content.
11241          * Contrary to the {@link android.app.Notification.BigPictureStyle}, this background
11242          * will work with any notification style.
11243          *
11244          * @return the background image
11245          * @see android.app.Notification.WearableExtender#setBackground
11246          * @deprecated Background images are no longer supported.
11247          */
11248         @Deprecated
getBackground()11249         public Bitmap getBackground() {
11250             return mBackground;
11251         }
11252 
11253         /**
11254          * Set an icon that goes with the content of this notification.
11255          */
11256         @Deprecated
setContentIcon(int icon)11257         public WearableExtender setContentIcon(int icon) {
11258             mContentIcon = icon;
11259             return this;
11260         }
11261 
11262         /**
11263          * Get an icon that goes with the content of this notification.
11264          */
11265         @Deprecated
getContentIcon()11266         public int getContentIcon() {
11267             return mContentIcon;
11268         }
11269 
11270         /**
11271          * Set the gravity that the content icon should have within the notification display.
11272          * Supported values include {@link android.view.Gravity#START} and
11273          * {@link android.view.Gravity#END}. The default value is {@link android.view.Gravity#END}.
11274          * @see #setContentIcon
11275          */
11276         @Deprecated
setContentIconGravity(int contentIconGravity)11277         public WearableExtender setContentIconGravity(int contentIconGravity) {
11278             mContentIconGravity = contentIconGravity;
11279             return this;
11280         }
11281 
11282         /**
11283          * Get the gravity that the content icon should have within the notification display.
11284          * Supported values include {@link android.view.Gravity#START} and
11285          * {@link android.view.Gravity#END}. The default value is {@link android.view.Gravity#END}.
11286          * @see #getContentIcon
11287          */
11288         @Deprecated
getContentIconGravity()11289         public int getContentIconGravity() {
11290             return mContentIconGravity;
11291         }
11292 
11293         /**
11294          * Set an action from this notification's actions as the primary action. If the action has a
11295          * {@link RemoteInput} associated with it, shortcuts to the options for that input are shown
11296          * directly on the notification.
11297          *
11298          * @param actionIndex The index of the primary action.
11299          *                    If wearable actions were added to the main notification, this index
11300          *                    will apply to that list, otherwise it will apply to the regular
11301          *                    actions list.
11302          */
setContentAction(int actionIndex)11303         public WearableExtender setContentAction(int actionIndex) {
11304             mContentActionIndex = actionIndex;
11305             return this;
11306         }
11307 
11308         /**
11309          * Get the index of the notification action, if any, that was specified as the primary
11310          * action.
11311          *
11312          * <p>If wearable specific actions were added to the main notification, this index will
11313          * apply to that list, otherwise it will apply to the regular actions list.
11314          *
11315          * @return the action index or {@link #UNSET_ACTION_INDEX} if no action was selected.
11316          */
getContentAction()11317         public int getContentAction() {
11318             return mContentActionIndex;
11319         }
11320 
11321         /**
11322          * Set the gravity that this notification should have within the available viewport space.
11323          * Supported values include {@link android.view.Gravity#TOP},
11324          * {@link android.view.Gravity#CENTER_VERTICAL} and {@link android.view.Gravity#BOTTOM}.
11325          * The default value is {@link android.view.Gravity#BOTTOM}.
11326          */
11327         @Deprecated
setGravity(int gravity)11328         public WearableExtender setGravity(int gravity) {
11329             mGravity = gravity;
11330             return this;
11331         }
11332 
11333         /**
11334          * Get the gravity that this notification should have within the available viewport space.
11335          * Supported values include {@link android.view.Gravity#TOP},
11336          * {@link android.view.Gravity#CENTER_VERTICAL} and {@link android.view.Gravity#BOTTOM}.
11337          * The default value is {@link android.view.Gravity#BOTTOM}.
11338          */
11339         @Deprecated
getGravity()11340         public int getGravity() {
11341             return mGravity;
11342         }
11343 
11344         /**
11345          * Set the custom size preset for the display of this notification out of the available
11346          * presets found in {@link android.app.Notification.WearableExtender}, e.g.
11347          * {@link #SIZE_LARGE}.
11348          * <p>Some custom size presets are only applicable for custom display notifications created
11349          * using {@link android.app.Notification.WearableExtender#setDisplayIntent}. Check the
11350          * documentation for the preset in question. See also
11351          * {@link #setCustomContentHeight} and {@link #getCustomSizePreset}.
11352          */
11353         @Deprecated
setCustomSizePreset(int sizePreset)11354         public WearableExtender setCustomSizePreset(int sizePreset) {
11355             mCustomSizePreset = sizePreset;
11356             return this;
11357         }
11358 
11359         /**
11360          * Get the custom size preset for the display of this notification out of the available
11361          * presets found in {@link android.app.Notification.WearableExtender}, e.g.
11362          * {@link #SIZE_LARGE}.
11363          * <p>Some custom size presets are only applicable for custom display notifications created
11364          * using {@link #setDisplayIntent}. Check the documentation for the preset in question.
11365          * See also {@link #setCustomContentHeight} and {@link #setCustomSizePreset}.
11366          */
11367         @Deprecated
getCustomSizePreset()11368         public int getCustomSizePreset() {
11369             return mCustomSizePreset;
11370         }
11371 
11372         /**
11373          * Set the custom height in pixels for the display of this notification's content.
11374          * <p>This option is only available for custom display notifications created
11375          * using {@link android.app.Notification.WearableExtender#setDisplayIntent}. See also
11376          * {@link android.app.Notification.WearableExtender#setCustomSizePreset} and
11377          * {@link #getCustomContentHeight}.
11378          */
11379         @Deprecated
setCustomContentHeight(int height)11380         public WearableExtender setCustomContentHeight(int height) {
11381             mCustomContentHeight = height;
11382             return this;
11383         }
11384 
11385         /**
11386          * Get the custom height in pixels for the display of this notification's content.
11387          * <p>This option is only available for custom display notifications created
11388          * using {@link #setDisplayIntent}. See also {@link #setCustomSizePreset} and
11389          * {@link #setCustomContentHeight}.
11390          */
11391         @Deprecated
getCustomContentHeight()11392         public int getCustomContentHeight() {
11393             return mCustomContentHeight;
11394         }
11395 
11396         /**
11397          * Set whether the scrolling position for the contents of this notification should start
11398          * at the bottom of the contents instead of the top when the contents are too long to
11399          * display within the screen.  Default is false (start scroll at the top).
11400          */
setStartScrollBottom(boolean startScrollBottom)11401         public WearableExtender setStartScrollBottom(boolean startScrollBottom) {
11402             setFlag(FLAG_START_SCROLL_BOTTOM, startScrollBottom);
11403             return this;
11404         }
11405 
11406         /**
11407          * Get whether the scrolling position for the contents of this notification should start
11408          * at the bottom of the contents instead of the top when the contents are too long to
11409          * display within the screen. Default is false (start scroll at the top).
11410          */
getStartScrollBottom()11411         public boolean getStartScrollBottom() {
11412             return (mFlags & FLAG_START_SCROLL_BOTTOM) != 0;
11413         }
11414 
11415         /**
11416          * Set whether the content intent is available when the wearable device is not connected
11417          * to a companion device.  The user can still trigger this intent when the wearable device
11418          * is offline, but a visual hint will indicate that the content intent may not be available.
11419          * Defaults to true.
11420          */
setContentIntentAvailableOffline( boolean contentIntentAvailableOffline)11421         public WearableExtender setContentIntentAvailableOffline(
11422                 boolean contentIntentAvailableOffline) {
11423             setFlag(FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE, contentIntentAvailableOffline);
11424             return this;
11425         }
11426 
11427         /**
11428          * Get whether the content intent is available when the wearable device is not connected
11429          * to a companion device.  The user can still trigger this intent when the wearable device
11430          * is offline, but a visual hint will indicate that the content intent may not be available.
11431          * Defaults to true.
11432          */
getContentIntentAvailableOffline()11433         public boolean getContentIntentAvailableOffline() {
11434             return (mFlags & FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE) != 0;
11435         }
11436 
11437         /**
11438          * Set a hint that this notification's icon should not be displayed.
11439          * @param hintHideIcon {@code true} to hide the icon, {@code false} otherwise.
11440          * @return this object for method chaining
11441          */
11442         @Deprecated
setHintHideIcon(boolean hintHideIcon)11443         public WearableExtender setHintHideIcon(boolean hintHideIcon) {
11444             setFlag(FLAG_HINT_HIDE_ICON, hintHideIcon);
11445             return this;
11446         }
11447 
11448         /**
11449          * Get a hint that this notification's icon should not be displayed.
11450          * @return {@code true} if this icon should not be displayed, false otherwise.
11451          * The default value is {@code false} if this was never set.
11452          */
11453         @Deprecated
getHintHideIcon()11454         public boolean getHintHideIcon() {
11455             return (mFlags & FLAG_HINT_HIDE_ICON) != 0;
11456         }
11457 
11458         /**
11459          * Set a visual hint that only the background image of this notification should be
11460          * displayed, and other semantic content should be hidden. This hint is only applicable
11461          * to sub-pages added using {@link #addPage}.
11462          */
11463         @Deprecated
setHintShowBackgroundOnly(boolean hintShowBackgroundOnly)11464         public WearableExtender setHintShowBackgroundOnly(boolean hintShowBackgroundOnly) {
11465             setFlag(FLAG_HINT_SHOW_BACKGROUND_ONLY, hintShowBackgroundOnly);
11466             return this;
11467         }
11468 
11469         /**
11470          * Get a visual hint that only the background image of this notification should be
11471          * displayed, and other semantic content should be hidden. This hint is only applicable
11472          * to sub-pages added using {@link android.app.Notification.WearableExtender#addPage}.
11473          */
11474         @Deprecated
getHintShowBackgroundOnly()11475         public boolean getHintShowBackgroundOnly() {
11476             return (mFlags & FLAG_HINT_SHOW_BACKGROUND_ONLY) != 0;
11477         }
11478 
11479         /**
11480          * Set a hint that this notification's background should not be clipped if possible,
11481          * and should instead be resized to fully display on the screen, retaining the aspect
11482          * ratio of the image. This can be useful for images like barcodes or qr codes.
11483          * @param hintAvoidBackgroundClipping {@code true} to avoid clipping if possible.
11484          * @return this object for method chaining
11485          */
11486         @Deprecated
setHintAvoidBackgroundClipping( boolean hintAvoidBackgroundClipping)11487         public WearableExtender setHintAvoidBackgroundClipping(
11488                 boolean hintAvoidBackgroundClipping) {
11489             setFlag(FLAG_HINT_AVOID_BACKGROUND_CLIPPING, hintAvoidBackgroundClipping);
11490             return this;
11491         }
11492 
11493         /**
11494          * Get a hint that this notification's background should not be clipped if possible,
11495          * and should instead be resized to fully display on the screen, retaining the aspect
11496          * ratio of the image. This can be useful for images like barcodes or qr codes.
11497          * @return {@code true} if it's ok if the background is clipped on the screen, false
11498          * otherwise. The default value is {@code false} if this was never set.
11499          */
11500         @Deprecated
getHintAvoidBackgroundClipping()11501         public boolean getHintAvoidBackgroundClipping() {
11502             return (mFlags & FLAG_HINT_AVOID_BACKGROUND_CLIPPING) != 0;
11503         }
11504 
11505         /**
11506          * Set a hint that the screen should remain on for at least this duration when
11507          * this notification is displayed on the screen.
11508          * @param timeout The requested screen timeout in milliseconds. Can also be either
11509          *     {@link #SCREEN_TIMEOUT_SHORT} or {@link #SCREEN_TIMEOUT_LONG}.
11510          * @return this object for method chaining
11511          */
11512         @Deprecated
setHintScreenTimeout(int timeout)11513         public WearableExtender setHintScreenTimeout(int timeout) {
11514             mHintScreenTimeout = timeout;
11515             return this;
11516         }
11517 
11518         /**
11519          * Get the duration, in milliseconds, that the screen should remain on for
11520          * when this notification is displayed.
11521          * @return the duration in milliseconds if > 0, or either one of the sentinel values
11522          *     {@link #SCREEN_TIMEOUT_SHORT} or {@link #SCREEN_TIMEOUT_LONG}.
11523          */
11524         @Deprecated
getHintScreenTimeout()11525         public int getHintScreenTimeout() {
11526             return mHintScreenTimeout;
11527         }
11528 
11529         /**
11530          * Set a hint that this notification's {@link BigPictureStyle} (if present) should be
11531          * converted to low-bit and displayed in ambient mode, especially useful for barcodes and
11532          * qr codes, as well as other simple black-and-white tickets.
11533          * @param hintAmbientBigPicture {@code true} to enable converstion and ambient.
11534          * @return this object for method chaining
11535          * @deprecated This feature is no longer supported.
11536          */
11537         @Deprecated
setHintAmbientBigPicture(boolean hintAmbientBigPicture)11538         public WearableExtender setHintAmbientBigPicture(boolean hintAmbientBigPicture) {
11539             setFlag(FLAG_BIG_PICTURE_AMBIENT, hintAmbientBigPicture);
11540             return this;
11541         }
11542 
11543         /**
11544          * Get a hint that this notification's {@link BigPictureStyle} (if present) should be
11545          * converted to low-bit and displayed in ambient mode, especially useful for barcodes and
11546          * qr codes, as well as other simple black-and-white tickets.
11547          * @return {@code true} if it should be displayed in ambient, false otherwise
11548          * otherwise. The default value is {@code false} if this was never set.
11549          * @deprecated This feature is no longer supported.
11550          */
11551         @Deprecated
getHintAmbientBigPicture()11552         public boolean getHintAmbientBigPicture() {
11553             return (mFlags & FLAG_BIG_PICTURE_AMBIENT) != 0;
11554         }
11555 
11556         /**
11557          * Set a hint that this notification's content intent will launch an {@link Activity}
11558          * directly, telling the platform that it can generate the appropriate transitions.
11559          * @param hintContentIntentLaunchesActivity {@code true} if the content intent will launch
11560          * an activity and transitions should be generated, false otherwise.
11561          * @return this object for method chaining
11562          */
setHintContentIntentLaunchesActivity( boolean hintContentIntentLaunchesActivity)11563         public WearableExtender setHintContentIntentLaunchesActivity(
11564                 boolean hintContentIntentLaunchesActivity) {
11565             setFlag(FLAG_HINT_CONTENT_INTENT_LAUNCHES_ACTIVITY, hintContentIntentLaunchesActivity);
11566             return this;
11567         }
11568 
11569         /**
11570          * Get a hint that this notification's content intent will launch an {@link Activity}
11571          * directly, telling the platform that it can generate the appropriate transitions
11572          * @return {@code true} if the content intent will launch an activity and transitions should
11573          * be generated, false otherwise. The default value is {@code false} if this was never set.
11574          */
getHintContentIntentLaunchesActivity()11575         public boolean getHintContentIntentLaunchesActivity() {
11576             return (mFlags & FLAG_HINT_CONTENT_INTENT_LAUNCHES_ACTIVITY) != 0;
11577         }
11578 
11579         /**
11580          * Sets the dismissal id for this notification. If a notification is posted with a
11581          * dismissal id, then when that notification is canceled, notifications on other wearables
11582          * and the paired Android phone having that same dismissal id will also be canceled. See
11583          * <a href="{@docRoot}wear/notifications/index.html">Adding Wearable Features to
11584          * Notifications</a> for more information.
11585          * @param dismissalId the dismissal id of the notification.
11586          * @return this object for method chaining
11587          */
setDismissalId(String dismissalId)11588         public WearableExtender setDismissalId(String dismissalId) {
11589             mDismissalId = dismissalId;
11590             return this;
11591         }
11592 
11593         /**
11594          * Returns the dismissal id of the notification.
11595          * @return the dismissal id of the notification or null if it has not been set.
11596          */
getDismissalId()11597         public String getDismissalId() {
11598             return mDismissalId;
11599         }
11600 
11601         /**
11602          * Sets a bridge tag for this notification. A bridge tag can be set for notifications
11603          * posted from a phone to provide finer-grained control on what notifications are bridged
11604          * to wearables. See <a href="{@docRoot}wear/notifications/index.html">Adding Wearable
11605          * Features to Notifications</a> for more information.
11606          * @param bridgeTag the bridge tag of the notification.
11607          * @return this object for method chaining
11608          */
setBridgeTag(String bridgeTag)11609         public WearableExtender setBridgeTag(String bridgeTag) {
11610             mBridgeTag = bridgeTag;
11611             return this;
11612         }
11613 
11614         /**
11615          * Returns the bridge tag of the notification.
11616          * @return the bridge tag or null if not present.
11617          */
getBridgeTag()11618         public String getBridgeTag() {
11619             return mBridgeTag;
11620         }
11621 
setFlag(int mask, boolean value)11622         private void setFlag(int mask, boolean value) {
11623             if (value) {
11624                 mFlags |= mask;
11625             } else {
11626                 mFlags &= ~mask;
11627             }
11628         }
11629     }
11630 
11631     /**
11632      * <p>Helper class to add Android Auto extensions to notifications. To create a notification
11633      * with car extensions:
11634      *
11635      * <ol>
11636      *  <li>Create an {@link Notification.Builder}, setting any desired
11637      *  properties.
11638      *  <li>Create a {@link CarExtender}.
11639      *  <li>Set car-specific properties using the {@code add} and {@code set} methods of
11640      *  {@link CarExtender}.
11641      *  <li>Call {@link Notification.Builder#extend(Notification.Extender)}
11642      *  to apply the extensions to a notification.
11643      * </ol>
11644      *
11645      * <pre class="prettyprint">
11646      * Notification notification = new Notification.Builder(context)
11647      *         ...
11648      *         .extend(new CarExtender()
11649      *                 .set*(...))
11650      *         .build();
11651      * </pre>
11652      *
11653      * <p>Car extensions can be accessed on an existing notification by using the
11654      * {@code CarExtender(Notification)} constructor, and then using the {@code get} methods
11655      * to access values.
11656      */
11657     public static final class CarExtender implements Extender {
11658         private static final String TAG = "CarExtender";
11659 
11660         private static final String EXTRA_CAR_EXTENDER = "android.car.EXTENSIONS";
11661         private static final String EXTRA_LARGE_ICON = "large_icon";
11662         private static final String EXTRA_CONVERSATION = "car_conversation";
11663         private static final String EXTRA_COLOR = "app_color";
11664 
11665         private Bitmap mLargeIcon;
11666         private UnreadConversation mUnreadConversation;
11667         private int mColor = Notification.COLOR_DEFAULT;
11668 
11669         /**
11670          * Create a {@link CarExtender} with default options.
11671          */
CarExtender()11672         public CarExtender() {
11673         }
11674 
11675         /**
11676          * Create a {@link CarExtender} from the CarExtender options of an existing Notification.
11677          *
11678          * @param notif The notification from which to copy options.
11679          */
CarExtender(Notification notif)11680         public CarExtender(Notification notif) {
11681             Bundle carBundle = notif.extras == null ?
11682                     null : notif.extras.getBundle(EXTRA_CAR_EXTENDER);
11683             if (carBundle != null) {
11684                 mLargeIcon = carBundle.getParcelable(EXTRA_LARGE_ICON, Bitmap.class);
11685                 mColor = carBundle.getInt(EXTRA_COLOR, Notification.COLOR_DEFAULT);
11686 
11687                 Bundle b = carBundle.getBundle(EXTRA_CONVERSATION);
11688                 mUnreadConversation = UnreadConversation.getUnreadConversationFromBundle(b);
11689             }
11690         }
11691 
11692         /**
11693          * Apply car extensions to a notification that is being built. This is typically called by
11694          * the {@link Notification.Builder#extend(Notification.Extender)}
11695          * method of {@link Notification.Builder}.
11696          */
11697         @Override
extend(Notification.Builder builder)11698         public Notification.Builder extend(Notification.Builder builder) {
11699             Bundle carExtensions = new Bundle();
11700 
11701             if (mLargeIcon != null) {
11702                 carExtensions.putParcelable(EXTRA_LARGE_ICON, mLargeIcon);
11703             }
11704             if (mColor != Notification.COLOR_DEFAULT) {
11705                 carExtensions.putInt(EXTRA_COLOR, mColor);
11706             }
11707 
11708             if (mUnreadConversation != null) {
11709                 Bundle b = mUnreadConversation.getBundleForUnreadConversation();
11710                 carExtensions.putBundle(EXTRA_CONVERSATION, b);
11711             }
11712 
11713             builder.getExtras().putBundle(EXTRA_CAR_EXTENDER, carExtensions);
11714             return builder;
11715         }
11716 
11717         /**
11718          * Sets the accent color to use when Android Auto presents the notification.
11719          *
11720          * Android Auto uses the color set with {@link Notification.Builder#setColor(int)}
11721          * to accent the displayed notification. However, not all colors are acceptable in an
11722          * automotive setting. This method can be used to override the color provided in the
11723          * notification in such a situation.
11724          */
setColor(@olorInt int color)11725         public CarExtender setColor(@ColorInt int color) {
11726             mColor = color;
11727             return this;
11728         }
11729 
11730         /**
11731          * Gets the accent color.
11732          *
11733          * @see #setColor
11734          */
11735         @ColorInt
getColor()11736         public int getColor() {
11737             return mColor;
11738         }
11739 
11740         /**
11741          * Sets the large icon of the car notification.
11742          *
11743          * If no large icon is set in the extender, Android Auto will display the icon
11744          * specified by {@link Notification.Builder#setLargeIcon(android.graphics.Bitmap)}
11745          *
11746          * @param largeIcon The large icon to use in the car notification.
11747          * @return This object for method chaining.
11748          */
setLargeIcon(Bitmap largeIcon)11749         public CarExtender setLargeIcon(Bitmap largeIcon) {
11750             mLargeIcon = largeIcon;
11751             return this;
11752         }
11753 
11754         /**
11755          * Gets the large icon used in this car notification, or null if no icon has been set.
11756          *
11757          * @return The large icon for the car notification.
11758          * @see CarExtender#setLargeIcon
11759          */
getLargeIcon()11760         public Bitmap getLargeIcon() {
11761             return mLargeIcon;
11762         }
11763 
11764         /**
11765          * Sets the unread conversation in a message notification.
11766          *
11767          * @param unreadConversation The unread part of the conversation this notification conveys.
11768          * @return This object for method chaining.
11769          */
setUnreadConversation(UnreadConversation unreadConversation)11770         public CarExtender setUnreadConversation(UnreadConversation unreadConversation) {
11771             mUnreadConversation = unreadConversation;
11772             return this;
11773         }
11774 
11775         /**
11776          * Returns the unread conversation conveyed by this notification.
11777          * @see #setUnreadConversation(UnreadConversation)
11778          */
getUnreadConversation()11779         public UnreadConversation getUnreadConversation() {
11780             return mUnreadConversation;
11781         }
11782 
11783         /**
11784          * A class which holds the unread messages from a conversation.
11785          */
11786         public static class UnreadConversation {
11787             private static final String KEY_AUTHOR = "author";
11788             private static final String KEY_TEXT = "text";
11789             private static final String KEY_MESSAGES = "messages";
11790             static final String KEY_REMOTE_INPUT = "remote_input";
11791             static final String KEY_ON_REPLY = "on_reply";
11792             static final String KEY_ON_READ = "on_read";
11793             private static final String KEY_PARTICIPANTS = "participants";
11794             private static final String KEY_TIMESTAMP = "timestamp";
11795 
11796             private final String[] mMessages;
11797             private final RemoteInput mRemoteInput;
11798             private final PendingIntent mReplyPendingIntent;
11799             private final PendingIntent mReadPendingIntent;
11800             private final String[] mParticipants;
11801             private final long mLatestTimestamp;
11802 
UnreadConversation(String[] messages, RemoteInput remoteInput, PendingIntent replyPendingIntent, PendingIntent readPendingIntent, String[] participants, long latestTimestamp)11803             UnreadConversation(String[] messages, RemoteInput remoteInput,
11804                     PendingIntent replyPendingIntent, PendingIntent readPendingIntent,
11805                     String[] participants, long latestTimestamp) {
11806                 mMessages = messages;
11807                 mRemoteInput = remoteInput;
11808                 mReadPendingIntent = readPendingIntent;
11809                 mReplyPendingIntent = replyPendingIntent;
11810                 mParticipants = participants;
11811                 mLatestTimestamp = latestTimestamp;
11812             }
11813 
11814             /**
11815              * Gets the list of messages conveyed by this notification.
11816              */
getMessages()11817             public String[] getMessages() {
11818                 return mMessages;
11819             }
11820 
11821             /**
11822              * Gets the remote input that will be used to convey the response to a message list, or
11823              * null if no such remote input exists.
11824              */
getRemoteInput()11825             public RemoteInput getRemoteInput() {
11826                 return mRemoteInput;
11827             }
11828 
11829             /**
11830              * Gets the pending intent that will be triggered when the user replies to this
11831              * notification.
11832              */
getReplyPendingIntent()11833             public PendingIntent getReplyPendingIntent() {
11834                 return mReplyPendingIntent;
11835             }
11836 
11837             /**
11838              * Gets the pending intent that Android Auto will send after it reads aloud all messages
11839              * in this object's message list.
11840              */
getReadPendingIntent()11841             public PendingIntent getReadPendingIntent() {
11842                 return mReadPendingIntent;
11843             }
11844 
11845             /**
11846              * Gets the participants in the conversation.
11847              */
getParticipants()11848             public String[] getParticipants() {
11849                 return mParticipants;
11850             }
11851 
11852             /**
11853              * Gets the firs participant in the conversation.
11854              */
getParticipant()11855             public String getParticipant() {
11856                 return mParticipants.length > 0 ? mParticipants[0] : null;
11857             }
11858 
11859             /**
11860              * Gets the timestamp of the conversation.
11861              */
getLatestTimestamp()11862             public long getLatestTimestamp() {
11863                 return mLatestTimestamp;
11864             }
11865 
getBundleForUnreadConversation()11866             Bundle getBundleForUnreadConversation() {
11867                 Bundle b = new Bundle();
11868                 String author = null;
11869                 if (mParticipants != null && mParticipants.length > 1) {
11870                     author = mParticipants[0];
11871                 }
11872                 Parcelable[] messages = new Parcelable[mMessages.length];
11873                 for (int i = 0; i < messages.length; i++) {
11874                     Bundle m = new Bundle();
11875                     m.putString(KEY_TEXT, mMessages[i]);
11876                     m.putString(KEY_AUTHOR, author);
11877                     messages[i] = m;
11878                 }
11879                 b.putParcelableArray(KEY_MESSAGES, messages);
11880                 if (mRemoteInput != null) {
11881                     b.putParcelable(KEY_REMOTE_INPUT, mRemoteInput);
11882                 }
11883                 b.putParcelable(KEY_ON_REPLY, mReplyPendingIntent);
11884                 b.putParcelable(KEY_ON_READ, mReadPendingIntent);
11885                 b.putStringArray(KEY_PARTICIPANTS, mParticipants);
11886                 b.putLong(KEY_TIMESTAMP, mLatestTimestamp);
11887                 return b;
11888             }
11889 
getUnreadConversationFromBundle(Bundle b)11890             static UnreadConversation getUnreadConversationFromBundle(Bundle b) {
11891                 if (b == null) {
11892                     return null;
11893                 }
11894                 Parcelable[] parcelableMessages = b.getParcelableArray(KEY_MESSAGES);
11895                 String[] messages = null;
11896                 if (parcelableMessages != null) {
11897                     String[] tmp = new String[parcelableMessages.length];
11898                     boolean success = true;
11899                     for (int i = 0; i < tmp.length; i++) {
11900                         if (!(parcelableMessages[i] instanceof Bundle)) {
11901                             success = false;
11902                             break;
11903                         }
11904                         tmp[i] = ((Bundle) parcelableMessages[i]).getString(KEY_TEXT);
11905                         if (tmp[i] == null) {
11906                             success = false;
11907                             break;
11908                         }
11909                     }
11910                     if (success) {
11911                         messages = tmp;
11912                     } else {
11913                         return null;
11914                     }
11915                 }
11916 
11917                 PendingIntent onRead = b.getParcelable(KEY_ON_READ, PendingIntent.class);
11918                 PendingIntent onReply = b.getParcelable(KEY_ON_REPLY, PendingIntent.class);
11919 
11920                 RemoteInput remoteInput = b.getParcelable(KEY_REMOTE_INPUT, RemoteInput.class);
11921 
11922                 String[] participants = b.getStringArray(KEY_PARTICIPANTS);
11923                 if (participants == null || participants.length != 1) {
11924                     return null;
11925                 }
11926 
11927                 return new UnreadConversation(messages,
11928                         remoteInput,
11929                         onReply,
11930                         onRead,
11931                         participants, b.getLong(KEY_TIMESTAMP));
11932             }
11933         };
11934 
11935         /**
11936          * Builder class for {@link CarExtender.UnreadConversation} objects.
11937          */
11938         public static class Builder {
11939             private final List<String> mMessages = new ArrayList<String>();
11940             private final String mParticipant;
11941             private RemoteInput mRemoteInput;
11942             private PendingIntent mReadPendingIntent;
11943             private PendingIntent mReplyPendingIntent;
11944             private long mLatestTimestamp;
11945 
11946             /**
11947              * Constructs a new builder for {@link CarExtender.UnreadConversation}.
11948              *
11949              * @param name The name of the other participant in the conversation.
11950              */
Builder(String name)11951             public Builder(String name) {
11952                 mParticipant = name;
11953             }
11954 
11955             /**
11956              * Appends a new unread message to the list of messages for this conversation.
11957              *
11958              * The messages should be added from oldest to newest.
11959              *
11960              * @param message The text of the new unread message.
11961              * @return This object for method chaining.
11962              */
addMessage(String message)11963             public Builder addMessage(String message) {
11964                 mMessages.add(message);
11965                 return this;
11966             }
11967 
11968             /**
11969              * Sets the pending intent and remote input which will convey the reply to this
11970              * notification.
11971              *
11972              * @param pendingIntent The pending intent which will be triggered on a reply.
11973              * @param remoteInput The remote input parcelable which will carry the reply.
11974              * @return This object for method chaining.
11975              *
11976              * @see CarExtender.UnreadConversation#getRemoteInput
11977              * @see CarExtender.UnreadConversation#getReplyPendingIntent
11978              */
setReplyAction( PendingIntent pendingIntent, RemoteInput remoteInput)11979             public Builder setReplyAction(
11980                     PendingIntent pendingIntent, RemoteInput remoteInput) {
11981                 mRemoteInput = remoteInput;
11982                 mReplyPendingIntent = pendingIntent;
11983 
11984                 return this;
11985             }
11986 
11987             /**
11988              * Sets the pending intent that will be sent once the messages in this notification
11989              * are read.
11990              *
11991              * @param pendingIntent The pending intent to use.
11992              * @return This object for method chaining.
11993              */
setReadPendingIntent(PendingIntent pendingIntent)11994             public Builder setReadPendingIntent(PendingIntent pendingIntent) {
11995                 mReadPendingIntent = pendingIntent;
11996                 return this;
11997             }
11998 
11999             /**
12000              * Sets the timestamp of the most recent message in an unread conversation.
12001              *
12002              * If a messaging notification has been posted by your application and has not
12003              * yet been cancelled, posting a later notification with the same id and tag
12004              * but without a newer timestamp may result in Android Auto not displaying a
12005              * heads up notification for the later notification.
12006              *
12007              * @param timestamp The timestamp of the most recent message in the conversation.
12008              * @return This object for method chaining.
12009              */
setLatestTimestamp(long timestamp)12010             public Builder setLatestTimestamp(long timestamp) {
12011                 mLatestTimestamp = timestamp;
12012                 return this;
12013             }
12014 
12015             /**
12016              * Builds a new unread conversation object.
12017              *
12018              * @return The new unread conversation object.
12019              */
build()12020             public UnreadConversation build() {
12021                 String[] messages = mMessages.toArray(new String[mMessages.size()]);
12022                 String[] participants = { mParticipant };
12023                 return new UnreadConversation(messages, mRemoteInput, mReplyPendingIntent,
12024                         mReadPendingIntent, participants, mLatestTimestamp);
12025             }
12026         }
12027     }
12028 
12029     /**
12030      * <p>Helper class to add Android TV extensions to notifications. To create a notification
12031      * with a TV extension:
12032      *
12033      * <ol>
12034      *  <li>Create an {@link Notification.Builder}, setting any desired properties.
12035      *  <li>Create a {@link TvExtender}.
12036      *  <li>Set TV-specific properties using the {@code set} methods of
12037      *  {@link TvExtender}.
12038      *  <li>Call {@link Notification.Builder#extend(Notification.Extender)}
12039      *  to apply the extension to a notification.
12040      * </ol>
12041      *
12042      * <pre class="prettyprint">
12043      * Notification notification = new Notification.Builder(context)
12044      *         ...
12045      *         .extend(new TvExtender()
12046      *                 .set*(...))
12047      *         .build();
12048      * </pre>
12049      *
12050      * <p>TV extensions can be accessed on an existing notification by using the
12051      * {@code TvExtender(Notification)} constructor, and then using the {@code get} methods
12052      * to access values.
12053      *
12054      * @hide
12055      */
12056     @SystemApi
12057     public static final class TvExtender implements Extender {
12058         private static final String TAG = "TvExtender";
12059 
12060         private static final String EXTRA_TV_EXTENDER = "android.tv.EXTENSIONS";
12061         private static final String EXTRA_FLAGS = "flags";
12062         static final String EXTRA_CONTENT_INTENT = "content_intent";
12063         static final String EXTRA_DELETE_INTENT = "delete_intent";
12064         private static final String EXTRA_CHANNEL_ID = "channel_id";
12065         private static final String EXTRA_SUPPRESS_SHOW_OVER_APPS = "suppressShowOverApps";
12066 
12067         // Flags bitwise-ored to mFlags
12068         private static final int FLAG_AVAILABLE_ON_TV = 0x1;
12069 
12070         private int mFlags;
12071         private String mChannelId;
12072         private PendingIntent mContentIntent;
12073         private PendingIntent mDeleteIntent;
12074         private boolean mSuppressShowOverApps;
12075 
12076         /**
12077          * Create a {@link TvExtender} with default options.
12078          */
TvExtender()12079         public TvExtender() {
12080             mFlags = FLAG_AVAILABLE_ON_TV;
12081         }
12082 
12083         /**
12084          * Create a {@link TvExtender} from the TvExtender options of an existing Notification.
12085          *
12086          * @param notif The notification from which to copy options.
12087          */
TvExtender(Notification notif)12088         public TvExtender(Notification notif) {
12089             Bundle bundle = notif.extras == null ?
12090                 null : notif.extras.getBundle(EXTRA_TV_EXTENDER);
12091             if (bundle != null) {
12092                 mFlags = bundle.getInt(EXTRA_FLAGS);
12093                 mChannelId = bundle.getString(EXTRA_CHANNEL_ID);
12094                 mSuppressShowOverApps = bundle.getBoolean(EXTRA_SUPPRESS_SHOW_OVER_APPS);
12095                 mContentIntent = bundle.getParcelable(EXTRA_CONTENT_INTENT, PendingIntent.class);
12096                 mDeleteIntent = bundle.getParcelable(EXTRA_DELETE_INTENT, PendingIntent.class);
12097             }
12098         }
12099 
12100         /**
12101          * Apply a TV extension to a notification that is being built. This is typically called by
12102          * the {@link Notification.Builder#extend(Notification.Extender)}
12103          * method of {@link Notification.Builder}.
12104          */
12105         @Override
extend(Notification.Builder builder)12106         public Notification.Builder extend(Notification.Builder builder) {
12107             Bundle bundle = new Bundle();
12108 
12109             bundle.putInt(EXTRA_FLAGS, mFlags);
12110             bundle.putString(EXTRA_CHANNEL_ID, mChannelId);
12111             bundle.putBoolean(EXTRA_SUPPRESS_SHOW_OVER_APPS, mSuppressShowOverApps);
12112             if (mContentIntent != null) {
12113                 bundle.putParcelable(EXTRA_CONTENT_INTENT, mContentIntent);
12114             }
12115 
12116             if (mDeleteIntent != null) {
12117                 bundle.putParcelable(EXTRA_DELETE_INTENT, mDeleteIntent);
12118             }
12119 
12120             builder.getExtras().putBundle(EXTRA_TV_EXTENDER, bundle);
12121             return builder;
12122         }
12123 
12124         /**
12125          * Returns true if this notification should be shown on TV. This method return true
12126          * if the notification was extended with a TvExtender.
12127          */
isAvailableOnTv()12128         public boolean isAvailableOnTv() {
12129             return (mFlags & FLAG_AVAILABLE_ON_TV) != 0;
12130         }
12131 
12132         /**
12133          * Specifies the channel the notification should be delivered on when shown on TV.
12134          * It can be different from the channel that the notification is delivered to when
12135          * posting on a non-TV device.
12136          */
setChannel(String channelId)12137         public TvExtender setChannel(String channelId) {
12138             mChannelId = channelId;
12139             return this;
12140         }
12141 
12142         /**
12143          * Specifies the channel the notification should be delivered on when shown on TV.
12144          * It can be different from the channel that the notification is delivered to when
12145          * posting on a non-TV device.
12146          */
setChannelId(String channelId)12147         public TvExtender setChannelId(String channelId) {
12148             mChannelId = channelId;
12149             return this;
12150         }
12151 
12152         /** @removed */
12153         @Deprecated
getChannel()12154         public String getChannel() {
12155             return mChannelId;
12156         }
12157 
12158         /**
12159          * Returns the id of the channel this notification posts to on TV.
12160          */
getChannelId()12161         public String getChannelId() {
12162             return mChannelId;
12163         }
12164 
12165         /**
12166          * Supplies a {@link PendingIntent} to be sent when the notification is selected on TV.
12167          * If provided, it is used instead of the content intent specified
12168          * at the level of Notification.
12169          */
setContentIntent(PendingIntent intent)12170         public TvExtender setContentIntent(PendingIntent intent) {
12171             mContentIntent = intent;
12172             return this;
12173         }
12174 
12175         /**
12176          * Returns the TV-specific content intent.  If this method returns null, the
12177          * main content intent on the notification should be used.
12178          *
12179          * @see {@link Notification#contentIntent}
12180          */
getContentIntent()12181         public PendingIntent getContentIntent() {
12182             return mContentIntent;
12183         }
12184 
12185         /**
12186          * Supplies a {@link PendingIntent} to send when the notification is cleared explicitly
12187          * by the user on TV.  If provided, it is used instead of the delete intent specified
12188          * at the level of Notification.
12189          */
setDeleteIntent(PendingIntent intent)12190         public TvExtender setDeleteIntent(PendingIntent intent) {
12191             mDeleteIntent = intent;
12192             return this;
12193         }
12194 
12195         /**
12196          * Returns the TV-specific delete intent.  If this method returns null, the
12197          * main delete intent on the notification should be used.
12198          *
12199          * @see {@link Notification#deleteIntent}
12200          */
getDeleteIntent()12201         public PendingIntent getDeleteIntent() {
12202             return mDeleteIntent;
12203         }
12204 
12205         /**
12206          * Specifies whether this notification should suppress showing a message over top of apps
12207          * outside of the launcher.
12208          */
setSuppressShowOverApps(boolean suppress)12209         public TvExtender setSuppressShowOverApps(boolean suppress) {
12210             mSuppressShowOverApps = suppress;
12211             return this;
12212         }
12213 
12214         /**
12215          * Returns true if this notification should not show messages over top of apps
12216          * outside of the launcher.
12217          */
getSuppressShowOverApps()12218         public boolean getSuppressShowOverApps() {
12219             return mSuppressShowOverApps;
12220         }
12221     }
12222 
12223     /**
12224      * Get an array of Parcelable objects from a parcelable array bundle field.
12225      * Update the bundle to have a typed array so fetches in the future don't need
12226      * to do an array copy.
12227      */
12228     @Nullable
getParcelableArrayFromBundle( Bundle bundle, String key, Class<T> itemClass)12229     private static <T extends Parcelable> T[] getParcelableArrayFromBundle(
12230             Bundle bundle, String key, Class<T> itemClass) {
12231         final Parcelable[] array = bundle.getParcelableArray(key);
12232         final Class<?> arrayClass = Array.newInstance(itemClass, 0).getClass();
12233         if (arrayClass.isInstance(array) || array == null) {
12234             return (T[]) array;
12235         }
12236         final T[] typedArray = (T[]) Array.newInstance(itemClass, array.length);
12237         for (int i = 0; i < array.length; i++) {
12238             typedArray[i] = (T) array[i];
12239         }
12240         bundle.putParcelableArray(key, typedArray);
12241         return typedArray;
12242     }
12243 
12244     private static class BuilderRemoteViews extends RemoteViews {
BuilderRemoteViews(Parcel parcel)12245         public BuilderRemoteViews(Parcel parcel) {
12246             super(parcel);
12247         }
12248 
BuilderRemoteViews(ApplicationInfo appInfo, int layoutId)12249         public BuilderRemoteViews(ApplicationInfo appInfo, int layoutId) {
12250             super(appInfo, layoutId);
12251         }
12252 
12253         @Override
clone()12254         public BuilderRemoteViews clone() {
12255             Parcel p = Parcel.obtain();
12256             writeToParcel(p, 0);
12257             p.setDataPosition(0);
12258             BuilderRemoteViews brv = new BuilderRemoteViews(p);
12259             p.recycle();
12260             return brv;
12261         }
12262 
12263         /**
12264          * Override and return true, since {@link RemoteViews#onLoadClass(Class)} is not overridden.
12265          *
12266          * @see RemoteViews#shouldUseStaticFilter()
12267          */
12268         @Override
shouldUseStaticFilter()12269         protected boolean shouldUseStaticFilter() {
12270             return true;
12271         }
12272     }
12273 
12274     /**
12275      * A result object where information about the template that was created is saved.
12276      */
12277     private static class TemplateBindResult {
12278         boolean mRightIconVisible;
12279         float mRightIconWidthDp;
12280         float mRightIconHeightDp;
12281 
12282         /**
12283          * The margin end that needs to be added to the heading so that it won't overlap
12284          * with the large icon.  This value includes the space required to accommodate the large
12285          * icon, but should be added to the space needed to accommodate the expander. This does
12286          * not include the 16dp content margin that all notification views must have.
12287          */
12288         public final MarginSet mHeadingExtraMarginSet = new MarginSet();
12289 
12290         /**
12291          * The margin end that needs to be added to the heading so that it won't overlap
12292          * with the large icon.  This value includes the space required to accommodate the large
12293          * icon as well as the expander.  This does not include the 16dp content margin that all
12294          * notification views must have.
12295          */
12296         public final MarginSet mHeadingFullMarginSet = new MarginSet();
12297 
12298         /**
12299          * The margin end that needs to be added to the title text of the big state
12300          * so that it won't overlap with the large icon, but assuming the text can run under
12301          * the expander when that icon is not visible.
12302          */
12303         public final MarginSet mTitleMarginSet = new MarginSet();
12304 
setRightIconState(boolean visible, float widthDp, float heightDp, float marginEndDpIfVisible, float expanderSizeDp)12305         public void setRightIconState(boolean visible, float widthDp, float heightDp,
12306                 float marginEndDpIfVisible, float expanderSizeDp) {
12307             mRightIconVisible = visible;
12308             mRightIconWidthDp = widthDp;
12309             mRightIconHeightDp = heightDp;
12310             mHeadingExtraMarginSet.setValues(0, marginEndDpIfVisible);
12311             mHeadingFullMarginSet.setValues(expanderSizeDp, marginEndDpIfVisible + expanderSizeDp);
12312             mTitleMarginSet.setValues(0, marginEndDpIfVisible + expanderSizeDp);
12313         }
12314 
12315         /**
12316          * This contains the end margins for a view when the right icon is visible or not.  These
12317          * values are both needed so that NotificationGroupingUtil can 'move' the right_icon to the
12318          * left_icon and adjust the margins, and to undo that change as well.
12319          */
12320         private class MarginSet {
12321             private float mValueIfGone;
12322             private float mValueIfVisible;
12323 
setValues(float valueIfGone, float valueIfVisible)12324             public void setValues(float valueIfGone, float valueIfVisible) {
12325                 mValueIfGone = valueIfGone;
12326                 mValueIfVisible = valueIfVisible;
12327             }
12328 
applyToView(@onNull RemoteViews views, @IdRes int viewId)12329             public void applyToView(@NonNull RemoteViews views, @IdRes int viewId) {
12330                 applyToView(views, viewId, 0);
12331             }
12332 
applyToView(@onNull RemoteViews views, @IdRes int viewId, float extraMarginDp)12333             public void applyToView(@NonNull RemoteViews views, @IdRes int viewId,
12334                     float extraMarginDp) {
12335                 final float marginEndDp = getDpValue() + extraMarginDp;
12336                 if (viewId == R.id.notification_header) {
12337                     views.setFloat(R.id.notification_header,
12338                             "setTopLineExtraMarginEndDp", marginEndDp);
12339                 } else if (viewId == R.id.text || viewId == R.id.big_text) {
12340                     if (mValueIfGone != 0) {
12341                         throw new RuntimeException("Programming error: `text` and `big_text` use "
12342                                 + "ImageFloatingTextView which can either show a margin or not; "
12343                                 + "thus mValueIfGone must be 0, but it was " + mValueIfGone);
12344                     }
12345                     // Note that the caller must set "setNumIndentLines" to a positive int in order
12346                     //  for this margin to do anything at all.
12347                     views.setFloat(viewId, "setImageEndMarginDp", mValueIfVisible);
12348                     views.setBoolean(viewId, "setHasImage", mRightIconVisible);
12349                     // Apply just the *extra* margin as the view layout margin; this will be
12350                     //  unchanged depending on the visibility of the image, but it means that the
12351                     //  extra margin applies to *every* line of text instead of just indented lines.
12352                     views.setViewLayoutMargin(viewId, RemoteViews.MARGIN_END,
12353                             extraMarginDp, TypedValue.COMPLEX_UNIT_DIP);
12354                 } else {
12355                     views.setViewLayoutMargin(viewId, RemoteViews.MARGIN_END,
12356                                     marginEndDp, TypedValue.COMPLEX_UNIT_DIP);
12357                 }
12358                 if (mRightIconVisible) {
12359                     views.setIntTag(viewId, R.id.tag_margin_end_when_icon_visible,
12360                             TypedValue.createComplexDimension(
12361                                     mValueIfVisible + extraMarginDp, TypedValue.COMPLEX_UNIT_DIP));
12362                     views.setIntTag(viewId, R.id.tag_margin_end_when_icon_gone,
12363                             TypedValue.createComplexDimension(
12364                                     mValueIfGone + extraMarginDp, TypedValue.COMPLEX_UNIT_DIP));
12365                 }
12366             }
12367 
getDpValue()12368             public float getDpValue() {
12369                 return mRightIconVisible ? mValueIfVisible : mValueIfGone;
12370             }
12371         }
12372     }
12373 
12374     private static class StandardTemplateParams {
12375         /**
12376          * Notifications will be minimally decorated with ONLY an icon and expander:
12377          * <li>A large icon is never shown.
12378          * <li>A progress bar is never shown.
12379          * <li>The expanded and heads up states do not show actions, even if provided.
12380          */
12381         public static final int DECORATION_MINIMAL = 1;
12382 
12383         /**
12384          * Notifications will be partially decorated with AT LEAST an icon and expander:
12385          * <li>A large icon is shown if provided.
12386          * <li>A progress bar is shown if provided and enough space remains below the content.
12387          * <li>Actions are shown in the expanded and heads up states.
12388          */
12389         public static final int DECORATION_PARTIAL = 2;
12390 
12391         public static int VIEW_TYPE_UNSPECIFIED = 0;
12392         public static int VIEW_TYPE_NORMAL = 1;
12393         public static int VIEW_TYPE_BIG = 2;
12394         public static int VIEW_TYPE_HEADS_UP = 3;
12395         public static int VIEW_TYPE_MINIMIZED = 4;    // header only for minimized state
12396         public static int VIEW_TYPE_PUBLIC = 5;       // header only for automatic public version
12397         public static int VIEW_TYPE_GROUP_HEADER = 6; // header only for top of group
12398 
12399         int mViewType = VIEW_TYPE_UNSPECIFIED;
12400         boolean mHeaderless;
12401         boolean mHideAppName;
12402         boolean mHideTitle;
12403         boolean mHideSubText;
12404         boolean mHideTime;
12405         boolean mHideActions;
12406         boolean mHideProgress;
12407         boolean mHideSnoozeButton;
12408         boolean mHideLeftIcon;
12409         boolean mHideRightIcon;
12410         Icon mPromotedPicture;
12411         boolean mCallStyleActions;
12412         boolean mAllowTextWithProgress;
12413         int mTitleViewId;
12414         int mTextViewId;
12415         CharSequence title;
12416         CharSequence text;
12417         CharSequence headerTextSecondary;
12418         CharSequence summaryText;
12419         int maxRemoteInputHistory = Style.MAX_REMOTE_INPUT_HISTORY_LINES;
12420         boolean allowColorization  = true;
12421         boolean mHighlightExpander = false;
12422 
reset()12423         final StandardTemplateParams reset() {
12424             mViewType = VIEW_TYPE_UNSPECIFIED;
12425             mHeaderless = false;
12426             mHideAppName = false;
12427             mHideTitle = false;
12428             mHideSubText = false;
12429             mHideTime = false;
12430             mHideActions = false;
12431             mHideProgress = false;
12432             mHideSnoozeButton = false;
12433             mHideLeftIcon = false;
12434             mHideRightIcon = false;
12435             mPromotedPicture = null;
12436             mCallStyleActions = false;
12437             mAllowTextWithProgress = false;
12438             mTitleViewId = R.id.title;
12439             mTextViewId = R.id.text;
12440             title = null;
12441             text = null;
12442             summaryText = null;
12443             headerTextSecondary = null;
12444             maxRemoteInputHistory = Style.MAX_REMOTE_INPUT_HISTORY_LINES;
12445             allowColorization = true;
12446             mHighlightExpander = false;
12447             return this;
12448         }
12449 
hasTitle()12450         final boolean hasTitle() {
12451             return !TextUtils.isEmpty(title) && !mHideTitle;
12452         }
12453 
viewType(int viewType)12454         final StandardTemplateParams viewType(int viewType) {
12455             mViewType = viewType;
12456             return this;
12457         }
12458 
headerless(boolean headerless)12459         public StandardTemplateParams headerless(boolean headerless) {
12460             mHeaderless = headerless;
12461             return this;
12462         }
12463 
hideAppName(boolean hideAppName)12464         public StandardTemplateParams hideAppName(boolean hideAppName) {
12465             mHideAppName = hideAppName;
12466             return this;
12467         }
12468 
hideSubText(boolean hideSubText)12469         public StandardTemplateParams hideSubText(boolean hideSubText) {
12470             mHideSubText = hideSubText;
12471             return this;
12472         }
12473 
hideTime(boolean hideTime)12474         public StandardTemplateParams hideTime(boolean hideTime) {
12475             mHideTime = hideTime;
12476             return this;
12477         }
12478 
hideActions(boolean hideActions)12479         final StandardTemplateParams hideActions(boolean hideActions) {
12480             this.mHideActions = hideActions;
12481             return this;
12482         }
12483 
hideProgress(boolean hideProgress)12484         final StandardTemplateParams hideProgress(boolean hideProgress) {
12485             this.mHideProgress = hideProgress;
12486             return this;
12487         }
12488 
hideTitle(boolean hideTitle)12489         final StandardTemplateParams hideTitle(boolean hideTitle) {
12490             this.mHideTitle = hideTitle;
12491             return this;
12492         }
12493 
callStyleActions(boolean callStyleActions)12494         final StandardTemplateParams callStyleActions(boolean callStyleActions) {
12495             this.mCallStyleActions = callStyleActions;
12496             return this;
12497         }
12498 
allowTextWithProgress(boolean allowTextWithProgress)12499         final StandardTemplateParams allowTextWithProgress(boolean allowTextWithProgress) {
12500             this.mAllowTextWithProgress = allowTextWithProgress;
12501             return this;
12502         }
12503 
hideSnoozeButton(boolean hideSnoozeButton)12504         final StandardTemplateParams hideSnoozeButton(boolean hideSnoozeButton) {
12505             this.mHideSnoozeButton = hideSnoozeButton;
12506             return this;
12507         }
12508 
promotedPicture(Icon promotedPicture)12509         final StandardTemplateParams promotedPicture(Icon promotedPicture) {
12510             this.mPromotedPicture = promotedPicture;
12511             return this;
12512         }
12513 
titleViewId(int titleViewId)12514         public StandardTemplateParams titleViewId(int titleViewId) {
12515             mTitleViewId = titleViewId;
12516             return this;
12517         }
12518 
textViewId(int textViewId)12519         public StandardTemplateParams textViewId(int textViewId) {
12520             mTextViewId = textViewId;
12521             return this;
12522         }
12523 
title(CharSequence title)12524         final StandardTemplateParams title(CharSequence title) {
12525             this.title = title;
12526             return this;
12527         }
12528 
text(CharSequence text)12529         final StandardTemplateParams text(CharSequence text) {
12530             this.text = text;
12531             return this;
12532         }
12533 
summaryText(CharSequence text)12534         final StandardTemplateParams summaryText(CharSequence text) {
12535             this.summaryText = text;
12536             return this;
12537         }
12538 
headerTextSecondary(CharSequence text)12539         final StandardTemplateParams headerTextSecondary(CharSequence text) {
12540             this.headerTextSecondary = text;
12541             return this;
12542         }
12543 
12544 
hideLeftIcon(boolean hideLeftIcon)12545         final StandardTemplateParams hideLeftIcon(boolean hideLeftIcon) {
12546             this.mHideLeftIcon = hideLeftIcon;
12547             return this;
12548         }
12549 
hideRightIcon(boolean hideRightIcon)12550         final StandardTemplateParams hideRightIcon(boolean hideRightIcon) {
12551             this.mHideRightIcon = hideRightIcon;
12552             return this;
12553         }
12554 
disallowColorization()12555         final StandardTemplateParams disallowColorization() {
12556             this.allowColorization = false;
12557             return this;
12558         }
12559 
highlightExpander(boolean highlight)12560         final StandardTemplateParams highlightExpander(boolean highlight) {
12561             this.mHighlightExpander = highlight;
12562             return this;
12563         }
12564 
fillTextsFrom(Builder b)12565         final StandardTemplateParams fillTextsFrom(Builder b) {
12566             Bundle extras = b.mN.extras;
12567             this.title = b.processLegacyText(extras.getCharSequence(EXTRA_TITLE));
12568             this.text = b.processLegacyText(extras.getCharSequence(EXTRA_TEXT));
12569             this.summaryText = extras.getCharSequence(EXTRA_SUB_TEXT);
12570             return this;
12571         }
12572 
12573         /**
12574          * Set the maximum lines of remote input history lines allowed.
12575          * @param maxRemoteInputHistory The number of lines.
12576          * @return The builder for method chaining.
12577          */
setMaxRemoteInputHistory(int maxRemoteInputHistory)12578         public StandardTemplateParams setMaxRemoteInputHistory(int maxRemoteInputHistory) {
12579             this.maxRemoteInputHistory = maxRemoteInputHistory;
12580             return this;
12581         }
12582 
decorationType(int decorationType)12583         public StandardTemplateParams decorationType(int decorationType) {
12584             hideTitle(true);
12585             // Minimally decorated custom views do not show certain pieces of chrome that have
12586             // always been shown when using DecoratedCustomViewStyle.
12587             boolean hideOtherFields = decorationType <= DECORATION_MINIMAL;
12588             hideLeftIcon(false);  // The left icon decoration is better than showing nothing.
12589             hideRightIcon(hideOtherFields);
12590             hideProgress(hideOtherFields);
12591             hideActions(hideOtherFields);
12592             return this;
12593         }
12594     }
12595 
12596     /**
12597      * A utility which stores and calculates the palette of colors used to color notifications.
12598      * @hide
12599      */
12600     @VisibleForTesting
12601     public static class Colors {
12602         private int mPaletteIsForRawColor = COLOR_INVALID;
12603         private boolean mPaletteIsForColorized = false;
12604         private boolean mPaletteIsForNightMode = false;
12605         // The following colors are the palette
12606         private int mBackgroundColor = COLOR_INVALID;
12607         private int mProtectionColor = COLOR_INVALID;
12608         private int mPrimaryTextColor = COLOR_INVALID;
12609         private int mSecondaryTextColor = COLOR_INVALID;
12610         private int mPrimaryAccentColor = COLOR_INVALID;
12611         private int mSecondaryAccentColor = COLOR_INVALID;
12612         private int mTertiaryAccentColor = COLOR_INVALID;
12613         private int mOnAccentTextColor = COLOR_INVALID;
12614         private int mErrorColor = COLOR_INVALID;
12615         private int mContrastColor = COLOR_INVALID;
12616         private int mRippleAlpha = 0x33;
12617 
12618         /**
12619          * A utility for obtaining a TypedArray of the given DayNight-styled attributes, which
12620          * returns null when the context is a mock with no theme.
12621          *
12622          * NOTE: Calling this method is expensive, as creating a new ContextThemeWrapper
12623          * instances can allocate as much as 5MB of memory, so its important to call this method
12624          * only when necessary, getting as many attributes as possible from each call.
12625          *
12626          * @see Resources.Theme#obtainStyledAttributes(int[])
12627          */
12628         @Nullable
obtainDayNightAttributes(@onNull Context ctx, @NonNull @StyleableRes int[] attrs)12629         private static TypedArray obtainDayNightAttributes(@NonNull Context ctx,
12630                 @NonNull @StyleableRes int[] attrs) {
12631             // when testing, the mock context may have no theme
12632             if (ctx.getTheme() == null) {
12633                 return null;
12634             }
12635             Resources.Theme theme = new ContextThemeWrapper(ctx,
12636                     R.style.Theme_DeviceDefault_DayNight).getTheme();
12637             return theme.obtainStyledAttributes(attrs);
12638         }
12639 
12640         /** A null-safe wrapper of TypedArray.getColor because mocks return null */
getColor(@ullable TypedArray ta, int index, @ColorInt int defValue)12641         private static @ColorInt int getColor(@Nullable TypedArray ta, int index,
12642                 @ColorInt int defValue) {
12643             return ta == null ? defValue : ta.getColor(index, defValue);
12644         }
12645 
12646         /**
12647          * Resolve the palette.  If the inputs have not changed, this will be a no-op.
12648          * This does not handle invalidating the resolved colors when the context itself changes,
12649          * because that case does not happen in the current notification inflation pipeline; we will
12650          * recreate a new builder (and thus a new palette) when reinflating notifications for a new
12651          * theme (admittedly, we do the same for night mode, but that's easy to check).
12652          *
12653          * @param ctx the builder context.
12654          * @param rawColor the notification's color; may be COLOR_DEFAULT, but may never have alpha.
12655          * @param isColorized whether the notification is colorized.
12656          * @param nightMode whether the UI is in night mode.
12657          */
resolvePalette(Context ctx, int rawColor, boolean isColorized, boolean nightMode)12658         public void resolvePalette(Context ctx, int rawColor,
12659                 boolean isColorized, boolean nightMode) {
12660             if (mPaletteIsForRawColor == rawColor
12661                     && mPaletteIsForColorized == isColorized
12662                     && mPaletteIsForNightMode == nightMode) {
12663                 return;
12664             }
12665             mPaletteIsForRawColor = rawColor;
12666             mPaletteIsForColorized = isColorized;
12667             mPaletteIsForNightMode = nightMode;
12668 
12669             if (isColorized) {
12670                 if (rawColor == COLOR_DEFAULT) {
12671                     int[] attrs = {R.attr.colorAccentSecondary};
12672                     try (TypedArray ta = obtainDayNightAttributes(ctx, attrs)) {
12673                         mBackgroundColor = getColor(ta, 0, Color.WHITE);
12674                     }
12675                 } else {
12676                     mBackgroundColor = rawColor;
12677                 }
12678                 mProtectionColor = COLOR_INVALID;  // filled in at the end
12679                 mPrimaryTextColor = ContrastColorUtil.findAlphaToMeetContrast(
12680                         ContrastColorUtil.resolvePrimaryColor(ctx, mBackgroundColor, nightMode),
12681                         mBackgroundColor, 4.5);
12682                 mSecondaryTextColor = ContrastColorUtil.findAlphaToMeetContrast(
12683                         ContrastColorUtil.resolveSecondaryColor(ctx, mBackgroundColor, nightMode),
12684                         mBackgroundColor, 4.5);
12685                 mContrastColor = mPrimaryTextColor;
12686                 mPrimaryAccentColor = mPrimaryTextColor;
12687                 mSecondaryAccentColor = mSecondaryTextColor;
12688                 mTertiaryAccentColor = flattenAlpha(mPrimaryTextColor, mBackgroundColor);
12689                 mOnAccentTextColor = mBackgroundColor;
12690                 mErrorColor = mPrimaryTextColor;
12691                 mRippleAlpha = 0x33;
12692             } else {
12693                 int[] attrs = {
12694                         R.attr.colorSurface,
12695                         R.attr.colorBackgroundFloating,
12696                         R.attr.textColorPrimary,
12697                         R.attr.textColorSecondary,
12698                         R.attr.colorAccent,
12699                         R.attr.colorAccentSecondary,
12700                         R.attr.colorAccentTertiary,
12701                         R.attr.textColorOnAccent,
12702                         R.attr.colorError,
12703                         R.attr.colorControlHighlight
12704                 };
12705                 try (TypedArray ta = obtainDayNightAttributes(ctx, attrs)) {
12706                     mBackgroundColor = getColor(ta, 0, nightMode ? Color.BLACK : Color.WHITE);
12707                     mProtectionColor = getColor(ta, 1, COLOR_INVALID);
12708                     mPrimaryTextColor = getColor(ta, 2, COLOR_INVALID);
12709                     mSecondaryTextColor = getColor(ta, 3, COLOR_INVALID);
12710                     mPrimaryAccentColor = getColor(ta, 4, COLOR_INVALID);
12711                     mSecondaryAccentColor = getColor(ta, 5, COLOR_INVALID);
12712                     mTertiaryAccentColor = getColor(ta, 6, COLOR_INVALID);
12713                     mOnAccentTextColor = getColor(ta, 7, COLOR_INVALID);
12714                     mErrorColor = getColor(ta, 8, COLOR_INVALID);
12715                     mRippleAlpha = Color.alpha(getColor(ta, 9, 0x33ffffff));
12716                 }
12717                 mContrastColor = calculateContrastColor(ctx, rawColor, mPrimaryAccentColor,
12718                         mBackgroundColor, nightMode);
12719 
12720                 // make sure every color has a valid value
12721                 if (mPrimaryTextColor == COLOR_INVALID) {
12722                     mPrimaryTextColor = ContrastColorUtil.resolvePrimaryColor(
12723                             ctx, mBackgroundColor, nightMode);
12724                 }
12725                 if (mSecondaryTextColor == COLOR_INVALID) {
12726                     mSecondaryTextColor = ContrastColorUtil.resolveSecondaryColor(
12727                             ctx, mBackgroundColor, nightMode);
12728                 }
12729                 if (mPrimaryAccentColor == COLOR_INVALID) {
12730                     mPrimaryAccentColor = mContrastColor;
12731                 }
12732                 if (mSecondaryAccentColor == COLOR_INVALID) {
12733                     mSecondaryAccentColor = mContrastColor;
12734                 }
12735                 if (mTertiaryAccentColor == COLOR_INVALID) {
12736                     mTertiaryAccentColor = mContrastColor;
12737                 }
12738                 if (mOnAccentTextColor == COLOR_INVALID) {
12739                     mOnAccentTextColor = ColorUtils.setAlphaComponent(
12740                             ContrastColorUtil.resolvePrimaryColor(
12741                                     ctx, mTertiaryAccentColor, nightMode), 0xFF);
12742                 }
12743                 if (mErrorColor == COLOR_INVALID) {
12744                     mErrorColor = mPrimaryTextColor;
12745                 }
12746             }
12747             // make sure every color has a valid value
12748             if (mProtectionColor == COLOR_INVALID) {
12749                 mProtectionColor = ColorUtils.blendARGB(mPrimaryTextColor, mBackgroundColor, 0.8f);
12750             }
12751         }
12752 
12753         /** calculates the contrast color for the non-colorized notifications */
calculateContrastColor(Context ctx, @ColorInt int rawColor, @ColorInt int accentColor, @ColorInt int backgroundColor, boolean nightMode)12754         private static @ColorInt int calculateContrastColor(Context ctx, @ColorInt int rawColor,
12755                 @ColorInt int accentColor, @ColorInt int backgroundColor, boolean nightMode) {
12756             int color;
12757             if (rawColor == COLOR_DEFAULT) {
12758                 color = accentColor;
12759                 if (color == COLOR_INVALID) {
12760                     color = ContrastColorUtil.resolveDefaultColor(ctx, backgroundColor, nightMode);
12761                 }
12762             } else {
12763                 color = ContrastColorUtil.resolveContrastColor(ctx, rawColor, backgroundColor,
12764                         nightMode);
12765             }
12766             return flattenAlpha(color, backgroundColor);
12767         }
12768 
12769         /** remove any alpha by manually blending it with the given background. */
flattenAlpha(@olorInt int color, @ColorInt int background)12770         private static @ColorInt int flattenAlpha(@ColorInt int color, @ColorInt int background) {
12771             return Color.alpha(color) == 0xff ? color
12772                     : ContrastColorUtil.compositeColors(color, background);
12773         }
12774 
12775         /** @return the notification's background color */
getBackgroundColor()12776         public @ColorInt int getBackgroundColor() {
12777             return mBackgroundColor;
12778         }
12779 
12780         /**
12781          * @return the "surface protection" color from the theme,
12782          * or a variant of the normal background color when colorized.
12783          */
getProtectionColor()12784         public @ColorInt int getProtectionColor() {
12785             return mProtectionColor;
12786         }
12787 
12788         /** @return the color for the most prominent text */
getPrimaryTextColor()12789         public @ColorInt int getPrimaryTextColor() {
12790             return mPrimaryTextColor;
12791         }
12792 
12793         /** @return the color for less prominent text */
getSecondaryTextColor()12794         public @ColorInt int getSecondaryTextColor() {
12795             return mSecondaryTextColor;
12796         }
12797 
12798         /** @return the theme's accent color for colored UI elements. */
getPrimaryAccentColor()12799         public @ColorInt int getPrimaryAccentColor() {
12800             return mPrimaryAccentColor;
12801         }
12802 
12803         /** @return the theme's secondary accent color for colored UI elements. */
getSecondaryAccentColor()12804         public @ColorInt int getSecondaryAccentColor() {
12805             return mSecondaryAccentColor;
12806         }
12807 
12808         /** @return the theme's tertiary accent color for colored UI elements. */
getTertiaryAccentColor()12809         public @ColorInt int getTertiaryAccentColor() {
12810             return mTertiaryAccentColor;
12811         }
12812 
12813         /** @return the theme's text color to be used on the tertiary accent color. */
getOnAccentTextColor()12814         public @ColorInt int getOnAccentTextColor() {
12815             return mOnAccentTextColor;
12816         }
12817 
12818         /**
12819          * @return the contrast-adjusted version of the color provided by the app, or the
12820          * primary text color when colorized.
12821          */
getContrastColor()12822         public @ColorInt int getContrastColor() {
12823             return mContrastColor;
12824         }
12825 
12826         /** @return the theme's error color, or the primary text color when colorized. */
getErrorColor()12827         public @ColorInt int getErrorColor() {
12828             return mErrorColor;
12829         }
12830 
12831         /** @return the alpha component of the current theme's control highlight color. */
getRippleAlpha()12832         public int getRippleAlpha() {
12833             return mRippleAlpha;
12834         }
12835     }
12836 }
12837