• 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.graphics.drawable.Icon.TYPE_URI;
21 import static android.graphics.drawable.Icon.TYPE_URI_ADAPTIVE_BITMAP;
22 
23 import static java.util.Objects.requireNonNull;
24 
25 import android.annotation.ColorInt;
26 import android.annotation.ColorRes;
27 import android.annotation.DimenRes;
28 import android.annotation.Dimension;
29 import android.annotation.DrawableRes;
30 import android.annotation.IdRes;
31 import android.annotation.IntDef;
32 import android.annotation.NonNull;
33 import android.annotation.Nullable;
34 import android.annotation.RequiresPermission;
35 import android.annotation.SdkConstant;
36 import android.annotation.SdkConstant.SdkConstantType;
37 import android.annotation.StringRes;
38 import android.annotation.StyleableRes;
39 import android.annotation.SuppressLint;
40 import android.annotation.SystemApi;
41 import android.annotation.TestApi;
42 import android.compat.annotation.UnsupportedAppUsage;
43 import android.content.Context;
44 import android.content.Intent;
45 import android.content.LocusId;
46 import android.content.pm.ApplicationInfo;
47 import android.content.pm.PackageManager;
48 import android.content.pm.PackageManager.NameNotFoundException;
49 import android.content.pm.ShortcutInfo;
50 import android.content.res.ColorStateList;
51 import android.content.res.Configuration;
52 import android.content.res.Resources;
53 import android.content.res.TypedArray;
54 import android.graphics.Bitmap;
55 import android.graphics.Canvas;
56 import android.graphics.Color;
57 import android.graphics.PorterDuff;
58 import android.graphics.drawable.Drawable;
59 import android.graphics.drawable.Icon;
60 import android.media.AudioAttributes;
61 import android.media.AudioManager;
62 import android.media.PlayerBase;
63 import android.media.session.MediaSession;
64 import android.net.Uri;
65 import android.os.BadParcelableException;
66 import android.os.Build;
67 import android.os.Bundle;
68 import android.os.IBinder;
69 import android.os.Parcel;
70 import android.os.Parcelable;
71 import android.os.SystemClock;
72 import android.os.SystemProperties;
73 import android.os.UserHandle;
74 import android.provider.Settings;
75 import android.text.BidiFormatter;
76 import android.text.SpannableStringBuilder;
77 import android.text.Spanned;
78 import android.text.TextUtils;
79 import android.text.style.AbsoluteSizeSpan;
80 import android.text.style.CharacterStyle;
81 import android.text.style.ForegroundColorSpan;
82 import android.text.style.RelativeSizeSpan;
83 import android.text.style.TextAppearanceSpan;
84 import android.util.ArraySet;
85 import android.util.Log;
86 import android.util.Pair;
87 import android.util.SparseArray;
88 import android.util.TypedValue;
89 import android.util.proto.ProtoOutputStream;
90 import android.view.ContextThemeWrapper;
91 import android.view.Gravity;
92 import android.view.View;
93 import android.view.contentcapture.ContentCaptureContext;
94 import android.widget.ProgressBar;
95 import android.widget.RemoteViews;
96 
97 import com.android.internal.R;
98 import com.android.internal.annotations.VisibleForTesting;
99 import com.android.internal.graphics.ColorUtils;
100 import com.android.internal.util.ArrayUtils;
101 import com.android.internal.util.ContrastColorUtil;
102 
103 import java.lang.annotation.Retention;
104 import java.lang.annotation.RetentionPolicy;
105 import java.lang.reflect.Array;
106 import java.lang.reflect.Constructor;
107 import java.util.ArrayList;
108 import java.util.Arrays;
109 import java.util.Collections;
110 import java.util.List;
111 import java.util.Objects;
112 import java.util.Set;
113 import java.util.function.Consumer;
114 
115 /**
116  * A class that represents how a persistent notification is to be presented to
117  * the user using the {@link android.app.NotificationManager}.
118  *
119  * <p>The {@link Notification.Builder Notification.Builder} has been added to make it
120  * easier to construct Notifications.</p>
121  *
122  * <div class="special reference">
123  * <h3>Developer Guides</h3>
124  * <p>For a guide to creating notifications, read the
125  * <a href="{@docRoot}guide/topics/ui/notifiers/notifications.html">Status Bar Notifications</a>
126  * developer guide.</p>
127  * </div>
128  */
129 public class Notification implements Parcelable
130 {
131     private static final String TAG = "Notification";
132 
133     /**
134      * @hide
135      */
136     @Retention(RetentionPolicy.SOURCE)
137     @IntDef({
138             FOREGROUND_SERVICE_DEFAULT,
139             FOREGROUND_SERVICE_IMMEDIATE,
140             FOREGROUND_SERVICE_DEFERRED
141     })
142     public @interface ServiceNotificationPolicy {};
143 
144     /**
145      * If the Notification associated with starting a foreground service has been
146      * built using setForegroundServiceBehavior() with this behavior, display of
147      * the notification will usually be suppressed for a short time to avoid visual
148      * disturbances to the user.
149      * @see Notification.Builder#setForegroundServiceBehavior(int)
150      * @see #FOREGROUND_SERVICE_IMMEDIATE
151      * @see #FOREGROUND_SERVICE_DEFERRED
152      */
153     public static final @ServiceNotificationPolicy int FOREGROUND_SERVICE_DEFAULT = 0;
154 
155     /**
156      * If the Notification associated with starting a foreground service has been
157      * built using setForegroundServiceBehavior() with this behavior, display of
158      * the notification will be immediate even if the default behavior would be
159      * to defer visibility for a short time.
160      * @see Notification.Builder#setForegroundServiceBehavior(int)
161      * @see #FOREGROUND_SERVICE_DEFAULT
162      * @see #FOREGROUND_SERVICE_DEFERRED
163      */
164     public static final @ServiceNotificationPolicy int FOREGROUND_SERVICE_IMMEDIATE = 1;
165 
166     /**
167      * If the Notification associated with starting a foreground service has been
168      * built using setForegroundServiceBehavior() with this behavior, display of
169      * the notification will usually be suppressed for a short time to avoid visual
170      * disturbances to the user.
171      * @see Notification.Builder#setForegroundServiceBehavior(int)
172      * @see #FOREGROUND_SERVICE_DEFAULT
173      * @see #FOREGROUND_SERVICE_IMMEDIATE
174      */
175     public static final @ServiceNotificationPolicy int FOREGROUND_SERVICE_DEFERRED = 2;
176 
177     @ServiceNotificationPolicy
178     private int mFgsDeferBehavior;
179 
180     /**
181      * An activity that provides a user interface for adjusting notification preferences for its
182      * containing application.
183      */
184     @SdkConstant(SdkConstantType.INTENT_CATEGORY)
185     public static final String INTENT_CATEGORY_NOTIFICATION_PREFERENCES
186             = "android.intent.category.NOTIFICATION_PREFERENCES";
187 
188     /**
189      * Optional extra for {@link #INTENT_CATEGORY_NOTIFICATION_PREFERENCES}. If provided, will
190      * contain a {@link NotificationChannel#getId() channel id} that can be used to narrow down
191      * what settings should be shown in the target app.
192      */
193     public static final String EXTRA_CHANNEL_ID = "android.intent.extra.CHANNEL_ID";
194 
195     /**
196      * Optional extra for {@link #INTENT_CATEGORY_NOTIFICATION_PREFERENCES}. If provided, will
197      * contain a {@link NotificationChannelGroup#getId() group id} that can be used to narrow down
198      * what settings should be shown in the target app.
199      */
200     public static final String EXTRA_CHANNEL_GROUP_ID = "android.intent.extra.CHANNEL_GROUP_ID";
201 
202     /**
203      * Optional extra for {@link #INTENT_CATEGORY_NOTIFICATION_PREFERENCES}. If provided, will
204      * contain the tag provided to {@link NotificationManager#notify(String, int, Notification)}
205      * that can be used to narrow down what settings should be shown in the target app.
206      */
207     public static final String EXTRA_NOTIFICATION_TAG = "android.intent.extra.NOTIFICATION_TAG";
208 
209     /**
210      * Optional extra for {@link #INTENT_CATEGORY_NOTIFICATION_PREFERENCES}. If provided, will
211      * contain the id provided to {@link NotificationManager#notify(String, int, Notification)}
212      * that can be used to narrow down what settings should be shown in the target app.
213      */
214     public static final String EXTRA_NOTIFICATION_ID = "android.intent.extra.NOTIFICATION_ID";
215 
216     /**
217      * Use all default values (where applicable).
218      */
219     public static final int DEFAULT_ALL = ~0;
220 
221     /**
222      * Use the default notification sound. This will ignore any given
223      * {@link #sound}.
224      *
225      * <p>
226      * A notification that is noisy is more likely to be presented as a heads-up notification.
227      * </p>
228      *
229      * @see #defaults
230      */
231 
232     public static final int DEFAULT_SOUND = 1;
233 
234     /**
235      * Use the default notification vibrate. This will ignore any given
236      * {@link #vibrate}. Using phone vibration requires the
237      * {@link android.Manifest.permission#VIBRATE VIBRATE} permission.
238      *
239      * <p>
240      * A notification that vibrates is more likely to be presented as a heads-up notification.
241      * </p>
242      *
243      * @see #defaults
244      */
245 
246     public static final int DEFAULT_VIBRATE = 2;
247 
248     /**
249      * Use the default notification lights. This will ignore the
250      * {@link #FLAG_SHOW_LIGHTS} bit, and {@link #ledARGB}, {@link #ledOffMS}, or
251      * {@link #ledOnMS}.
252      *
253      * @see #defaults
254      */
255 
256     public static final int DEFAULT_LIGHTS = 4;
257 
258     /**
259      * Maximum length of CharSequences accepted by Builder and friends.
260      *
261      * <p>
262      * Avoids spamming the system with overly large strings such as full e-mails.
263      */
264     private static final int MAX_CHARSEQUENCE_LENGTH = 1024;
265 
266     /**
267      * Maximum entries of reply text that are accepted by Builder and friends.
268      */
269     private static final int MAX_REPLY_HISTORY = 5;
270 
271     /**
272      * Maximum aspect ratio of the large icon. 16:9
273      */
274     private static final float MAX_LARGE_ICON_ASPECT_RATIO = 16f / 9f;
275 
276     /**
277      * Maximum number of (generic) action buttons in a notification (contextual action buttons are
278      * handled separately).
279      * @hide
280      */
281     public static final int MAX_ACTION_BUTTONS = 3;
282 
283     /**
284      * If the notification contained an unsent draft for a RemoteInput when the user clicked on it,
285      * we're adding the draft as a String extra to the {@link #contentIntent} using this key.
286      *
287      * <p>Apps may use this extra to prepopulate text fields in the app, where the user usually
288      * sends messages.</p>
289      */
290     public static final String EXTRA_REMOTE_INPUT_DRAFT = "android.remoteInputDraft";
291 
292     /**
293      * A timestamp related to this notification, in milliseconds since the epoch.
294      *
295      * Default value: {@link System#currentTimeMillis() Now}.
296      *
297      * Choose a timestamp that will be most relevant to the user. For most finite events, this
298      * corresponds to the time the event happened (or will happen, in the case of events that have
299      * yet to occur but about which the user is being informed). Indefinite events should be
300      * timestamped according to when the activity began.
301      *
302      * Some examples:
303      *
304      * <ul>
305      *   <li>Notification of a new chat message should be stamped when the message was received.</li>
306      *   <li>Notification of an ongoing file download (with a progress bar, for example) should be stamped when the download started.</li>
307      *   <li>Notification of a completed file download should be stamped when the download finished.</li>
308      *   <li>Notification of an upcoming meeting should be stamped with the time the meeting will begin (that is, in the future).</li>
309      *   <li>Notification of an ongoing stopwatch (increasing timer) should be stamped with the watch's start time.
310      *   <li>Notification of an ongoing countdown timer should be stamped with the timer's end time.
311      * </ul>
312      *
313      * For apps targeting {@link android.os.Build.VERSION_CODES#N} and above, this time is not shown
314      * anymore by default and must be opted into by using
315      * {@link android.app.Notification.Builder#setShowWhen(boolean)}
316      */
317     public long when;
318 
319     /**
320      * The creation time of the notification
321      */
322     private long creationTime;
323 
324     /**
325      * The resource id of a drawable to use as the icon in the status bar.
326      *
327      * @deprecated Use {@link Builder#setSmallIcon(Icon)} instead.
328      */
329     @Deprecated
330     @DrawableRes
331     public int icon;
332 
333     /**
334      * If the icon in the status bar is to have more than one level, you can set this.  Otherwise,
335      * leave it at its default value of 0.
336      *
337      * @see android.widget.ImageView#setImageLevel
338      * @see android.graphics.drawable.Drawable#setLevel
339      */
340     public int iconLevel;
341 
342     /**
343      * The number of events that this notification represents. For example, in a new mail
344      * notification, this could be the number of unread messages.
345      *
346      * The system may or may not use this field to modify the appearance of the notification.
347      * Starting with {@link android.os.Build.VERSION_CODES#O}, the number may be displayed as a
348      * badge icon in Launchers that support badging.
349      */
350     public int number = 0;
351 
352     /**
353      * The intent to execute when the expanded status entry is clicked.  If
354      * this is an activity, it must include the
355      * {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK} flag, which requires
356      * that you take care of task management as described in the
357      * <a href="{@docRoot}guide/topics/fundamentals/tasks-and-back-stack.html">Tasks and Back
358      * Stack</a> document.  In particular, make sure to read the notification section
359      * <a href="{@docRoot}guide/topics/ui/notifiers/notifications.html#HandlingNotifications">Handling
360      * Notifications</a> for the correct ways to launch an application from a
361      * notification.
362      */
363     public PendingIntent contentIntent;
364 
365     /**
366      * The intent to execute when the notification is explicitly dismissed by the user, either with
367      * the "Clear All" button or by swiping it away individually.
368      *
369      * This probably shouldn't be launching an activity since several of those will be sent
370      * at the same time.
371      */
372     public PendingIntent deleteIntent;
373 
374     /**
375      * An intent to launch instead of posting the notification to the status bar.
376      *
377      * <p>
378      * The system UI may choose to display a heads-up notification, instead of
379      * launching this intent, while the user is using the device.
380      * </p>
381      *
382      * @see Notification.Builder#setFullScreenIntent
383      */
384     public PendingIntent fullScreenIntent;
385 
386     /**
387      * Text that summarizes this notification for accessibility services.
388      *
389      * As of the L release, this text is no longer shown on screen, but it is still useful to
390      * accessibility services (where it serves as an audible announcement of the notification's
391      * appearance).
392      *
393      * @see #tickerView
394      */
395     public CharSequence tickerText;
396 
397     /**
398      * Formerly, a view showing the {@link #tickerText}.
399      *
400      * No longer displayed in the status bar as of API 21.
401      */
402     @Deprecated
403     public RemoteViews tickerView;
404 
405     /**
406      * The view that will represent this notification in the notification list (which is pulled
407      * down from the status bar).
408      *
409      * As of N, this field may be null. The notification view is determined by the inputs
410      * to {@link Notification.Builder}; a custom RemoteViews can optionally be
411      * supplied with {@link Notification.Builder#setCustomContentView(RemoteViews)}.
412      */
413     @Deprecated
414     public RemoteViews contentView;
415 
416     /**
417      * A large-format version of {@link #contentView}, giving the Notification an
418      * opportunity to show more detail. The system UI may choose to show this
419      * instead of the normal content view at its discretion.
420      *
421      * As of N, this field may be null. The expanded notification view is determined by the
422      * inputs to {@link Notification.Builder}; a custom RemoteViews can optionally be
423      * supplied with {@link Notification.Builder#setCustomBigContentView(RemoteViews)}.
424      */
425     @Deprecated
426     public RemoteViews bigContentView;
427 
428 
429     /**
430      * A medium-format version of {@link #contentView}, providing the Notification an
431      * opportunity to add action buttons to contentView. At its discretion, the system UI may
432      * choose to show this as a heads-up notification, which will pop up so the user can see
433      * it without leaving their current activity.
434      *
435      * As of N, this field may be null. The heads-up notification view is determined by the
436      * inputs to {@link Notification.Builder}; a custom RemoteViews can optionally be
437      * supplied with {@link Notification.Builder#setCustomHeadsUpContentView(RemoteViews)}.
438      */
439     @Deprecated
440     public RemoteViews headsUpContentView;
441 
442     private boolean mUsesStandardHeader;
443 
444     private static final ArraySet<Integer> STANDARD_LAYOUTS = new ArraySet<>();
445     static {
446         STANDARD_LAYOUTS.add(R.layout.notification_template_material_base);
447         STANDARD_LAYOUTS.add(R.layout.notification_template_material_heads_up_base);
448         STANDARD_LAYOUTS.add(R.layout.notification_template_material_big_base);
449         STANDARD_LAYOUTS.add(R.layout.notification_template_material_big_picture);
450         STANDARD_LAYOUTS.add(R.layout.notification_template_material_big_text);
451         STANDARD_LAYOUTS.add(R.layout.notification_template_material_inbox);
452         STANDARD_LAYOUTS.add(R.layout.notification_template_material_messaging);
453         STANDARD_LAYOUTS.add(R.layout.notification_template_material_big_messaging);
454         STANDARD_LAYOUTS.add(R.layout.notification_template_material_conversation);
455         STANDARD_LAYOUTS.add(R.layout.notification_template_material_media);
456         STANDARD_LAYOUTS.add(R.layout.notification_template_material_big_media);
457         STANDARD_LAYOUTS.add(R.layout.notification_template_material_call);
458         STANDARD_LAYOUTS.add(R.layout.notification_template_material_big_call);
459         STANDARD_LAYOUTS.add(R.layout.notification_template_header);
460     }
461 
462     /**
463      * A large bitmap to be shown in the notification content area.
464      *
465      * @deprecated Use {@link Builder#setLargeIcon(Icon)} instead.
466      */
467     @Deprecated
468     public Bitmap largeIcon;
469 
470     /**
471      * The sound to play.
472      *
473      * <p>
474      * A notification that is noisy is more likely to be presented as a heads-up notification.
475      * </p>
476      *
477      * <p>
478      * To play the default notification sound, see {@link #defaults}.
479      * </p>
480      * @deprecated use {@link NotificationChannel#getSound()}.
481      */
482     @Deprecated
483     public Uri sound;
484 
485     /**
486      * Use this constant as the value for audioStreamType to request that
487      * the default stream type for notifications be used.  Currently the
488      * default stream type is {@link AudioManager#STREAM_NOTIFICATION}.
489      *
490      * @deprecated Use {@link NotificationChannel#getAudioAttributes()} instead.
491      */
492     @Deprecated
493     public static final int STREAM_DEFAULT = -1;
494 
495     /**
496      * The audio stream type to use when playing the sound.
497      * Should be one of the STREAM_ constants from
498      * {@link android.media.AudioManager}.
499      *
500      * @deprecated Use {@link #audioAttributes} instead.
501      */
502     @Deprecated
503     public int audioStreamType = STREAM_DEFAULT;
504 
505     /**
506      * The default value of {@link #audioAttributes}.
507      */
508     public static final AudioAttributes AUDIO_ATTRIBUTES_DEFAULT = new AudioAttributes.Builder()
509             .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
510             .setUsage(AudioAttributes.USAGE_NOTIFICATION)
511             .build();
512 
513     /**
514      * The {@link AudioAttributes audio attributes} to use when playing the sound.
515      *
516      * @deprecated use {@link NotificationChannel#getAudioAttributes()} instead.
517      */
518     @Deprecated
519     public AudioAttributes audioAttributes = AUDIO_ATTRIBUTES_DEFAULT;
520 
521     /**
522      * The pattern with which to vibrate.
523      *
524      * <p>
525      * To vibrate the default pattern, see {@link #defaults}.
526      * </p>
527      *
528      * @see android.os.Vibrator#vibrate(long[],int)
529      * @deprecated use {@link NotificationChannel#getVibrationPattern()}.
530      */
531     @Deprecated
532     public long[] vibrate;
533 
534     /**
535      * The color of the led.  The hardware will do its best approximation.
536      *
537      * @see #FLAG_SHOW_LIGHTS
538      * @see #flags
539      * @deprecated use {@link NotificationChannel#shouldShowLights()}.
540      */
541     @ColorInt
542     @Deprecated
543     public int ledARGB;
544 
545     /**
546      * The number of milliseconds for the LED to be on while it's flashing.
547      * The hardware will do its best approximation.
548      *
549      * @see #FLAG_SHOW_LIGHTS
550      * @see #flags
551      * @deprecated use {@link NotificationChannel#shouldShowLights()}.
552      */
553     @Deprecated
554     public int ledOnMS;
555 
556     /**
557      * The number of milliseconds for the LED to be off while it's flashing.
558      * The hardware will do its best approximation.
559      *
560      * @see #FLAG_SHOW_LIGHTS
561      * @see #flags
562      *
563      * @deprecated use {@link NotificationChannel#shouldShowLights()}.
564      */
565     @Deprecated
566     public int ledOffMS;
567 
568     /**
569      * Specifies which values should be taken from the defaults.
570      * <p>
571      * To set, OR the desired from {@link #DEFAULT_SOUND},
572      * {@link #DEFAULT_VIBRATE}, {@link #DEFAULT_LIGHTS}. For all default
573      * values, use {@link #DEFAULT_ALL}.
574      * </p>
575      *
576      * @deprecated use {@link NotificationChannel#getSound()} and
577      * {@link NotificationChannel#shouldShowLights()} and
578      * {@link NotificationChannel#shouldVibrate()}.
579      */
580     @Deprecated
581     public int defaults;
582 
583     /**
584      * Bit to be bitwise-ored into the {@link #flags} field that should be
585      * set if you want the LED on for this notification.
586      * <ul>
587      * <li>To turn the LED off, pass 0 in the alpha channel for colorARGB
588      *      or 0 for both ledOnMS and ledOffMS.</li>
589      * <li>To turn the LED on, pass 1 for ledOnMS and 0 for ledOffMS.</li>
590      * <li>To flash the LED, pass the number of milliseconds that it should
591      *      be on and off to ledOnMS and ledOffMS.</li>
592      * </ul>
593      * <p>
594      * Since hardware varies, you are not guaranteed that any of the values
595      * you pass are honored exactly.  Use the system defaults if possible
596      * because they will be set to values that work on any given hardware.
597      * <p>
598      * The alpha channel must be set for forward compatibility.
599      *
600      * @deprecated use {@link NotificationChannel#shouldShowLights()}.
601      */
602     @Deprecated
603     public static final int FLAG_SHOW_LIGHTS        = 0x00000001;
604 
605     /**
606      * Bit to be bitwise-ored into the {@link #flags} field that should be
607      * set if this notification is in reference to something that is ongoing,
608      * like a phone call.  It should not be set if this notification is in
609      * reference to something that happened at a particular point in time,
610      * like a missed phone call.
611      */
612     public static final int FLAG_ONGOING_EVENT      = 0x00000002;
613 
614     /**
615      * Bit to be bitwise-ored into the {@link #flags} field that if set,
616      * the audio will be repeated until the notification is
617      * cancelled or the notification window is opened.
618      */
619     public static final int FLAG_INSISTENT          = 0x00000004;
620 
621     /**
622      * Bit to be bitwise-ored into the {@link #flags} field that should be
623      * set if you would only like the sound, vibrate and ticker to be played
624      * if the notification was not already showing.
625      */
626     public static final int FLAG_ONLY_ALERT_ONCE    = 0x00000008;
627 
628     /**
629      * Bit to be bitwise-ored into the {@link #flags} field that should be
630      * set if the notification should be canceled when it is clicked by the
631      * user.
632      */
633     public static final int FLAG_AUTO_CANCEL        = 0x00000010;
634 
635     /**
636      * Bit to be bitwise-ored into the {@link #flags} field that should be
637      * set if the notification should not be canceled when the user clicks
638      * the Clear all button.
639      */
640     public static final int FLAG_NO_CLEAR           = 0x00000020;
641 
642     /**
643      * Bit to be bitwise-ored into the {@link #flags} field that should be
644      * set if this notification represents a currently running service.  This
645      * will normally be set for you by {@link Service#startForeground}.
646      */
647     public static final int FLAG_FOREGROUND_SERVICE = 0x00000040;
648 
649     /**
650      * Obsolete flag indicating high-priority notifications; use the priority field instead.
651      *
652      * @deprecated Use {@link #priority} with a positive value.
653      */
654     @Deprecated
655     public static final int FLAG_HIGH_PRIORITY      = 0x00000080;
656 
657     /**
658      * Bit to be bitswise-ored into the {@link #flags} field that should be
659      * set if this notification is relevant to the current device only
660      * and it is not recommended that it bridge to other devices.
661      */
662     public static final int FLAG_LOCAL_ONLY         = 0x00000100;
663 
664     /**
665      * Bit to be bitswise-ored into the {@link #flags} field that should be
666      * set if this notification is the group summary for a group of notifications.
667      * Grouped notifications may display in a cluster or stack on devices which
668      * support such rendering. Requires a group key also be set using {@link Builder#setGroup}.
669      */
670     public static final int FLAG_GROUP_SUMMARY      = 0x00000200;
671 
672     /**
673      * Bit to be bitswise-ored into the {@link #flags} field that should be
674      * set if this notification is the group summary for an auto-group of notifications.
675      *
676      * @hide
677      */
678     @SystemApi
679     public static final int FLAG_AUTOGROUP_SUMMARY  = 0x00000400;
680 
681     /**
682      * @hide
683      */
684     public static final int FLAG_CAN_COLORIZE = 0x00000800;
685 
686     /**
687      * Bit to be bitswised-ored into the {@link #flags} field that should be
688      * set by the system if this notification is showing as a bubble.
689      *
690      * Applications cannot set this flag directly; they should instead call
691      * {@link Notification.Builder#setBubbleMetadata(BubbleMetadata)} to
692      * request that a notification be displayed as a bubble, and then check
693      * this flag to see whether that request was honored by the system.
694      */
695     public static final int FLAG_BUBBLE = 0x00001000;
696 
697     private static final List<Class<? extends Style>> PLATFORM_STYLE_CLASSES = Arrays.asList(
698             BigTextStyle.class, BigPictureStyle.class, InboxStyle.class, MediaStyle.class,
699             DecoratedCustomViewStyle.class, DecoratedMediaCustomViewStyle.class,
700             MessagingStyle.class, CallStyle.class);
701 
702     /** @hide */
703     @IntDef(flag = true, prefix = { "FLAG_" }, value = {FLAG_SHOW_LIGHTS, FLAG_ONGOING_EVENT,
704             FLAG_INSISTENT, FLAG_ONLY_ALERT_ONCE,
705             FLAG_AUTO_CANCEL, FLAG_NO_CLEAR, FLAG_FOREGROUND_SERVICE, FLAG_HIGH_PRIORITY,
706             FLAG_LOCAL_ONLY, FLAG_GROUP_SUMMARY, FLAG_AUTOGROUP_SUMMARY, FLAG_BUBBLE})
707     @Retention(RetentionPolicy.SOURCE)
708     public @interface NotificationFlags{};
709 
710     public int flags;
711 
712     /** @hide */
713     @IntDef(prefix = { "PRIORITY_" }, value = {
714             PRIORITY_DEFAULT,
715             PRIORITY_LOW,
716             PRIORITY_MIN,
717             PRIORITY_HIGH,
718             PRIORITY_MAX
719     })
720     @Retention(RetentionPolicy.SOURCE)
721     public @interface Priority {}
722 
723     /**
724      * Default notification {@link #priority}. If your application does not prioritize its own
725      * notifications, use this value for all notifications.
726      *
727      * @deprecated use {@link NotificationManager#IMPORTANCE_DEFAULT} instead.
728      */
729     @Deprecated
730     public static final int PRIORITY_DEFAULT = 0;
731 
732     /**
733      * Lower {@link #priority}, for items that are less important. The UI may choose to show these
734      * items smaller, or at a different position in the list, compared with your app's
735      * {@link #PRIORITY_DEFAULT} items.
736      *
737      * @deprecated use {@link NotificationManager#IMPORTANCE_LOW} instead.
738      */
739     @Deprecated
740     public static final int PRIORITY_LOW = -1;
741 
742     /**
743      * Lowest {@link #priority}; these items might not be shown to the user except under special
744      * circumstances, such as detailed notification logs.
745      *
746      * @deprecated use {@link NotificationManager#IMPORTANCE_MIN} instead.
747      */
748     @Deprecated
749     public static final int PRIORITY_MIN = -2;
750 
751     /**
752      * Higher {@link #priority}, for more important notifications or alerts. The UI may choose to
753      * show these items larger, or at a different position in notification lists, compared with
754      * your app's {@link #PRIORITY_DEFAULT} items.
755      *
756      * @deprecated use {@link NotificationManager#IMPORTANCE_HIGH} instead.
757      */
758     @Deprecated
759     public static final int PRIORITY_HIGH = 1;
760 
761     /**
762      * Highest {@link #priority}, for your application's most important items that require the
763      * user's prompt attention or input.
764      *
765      * @deprecated use {@link NotificationManager#IMPORTANCE_HIGH} instead.
766      */
767     @Deprecated
768     public static final int PRIORITY_MAX = 2;
769 
770     /**
771      * Relative priority for this notification.
772      *
773      * Priority is an indication of how much of the user's valuable attention should be consumed by
774      * this notification. Low-priority notifications may be hidden from the user in certain
775      * situations, while the user might be interrupted for a higher-priority notification. The
776      * system will make a determination about how to interpret this priority when presenting
777      * the notification.
778      *
779      * <p>
780      * A notification that is at least {@link #PRIORITY_HIGH} is more likely to be presented
781      * as a heads-up notification.
782      * </p>
783      *
784      * @deprecated use {@link NotificationChannel#getImportance()} instead.
785      */
786     @Priority
787     @Deprecated
788     public int priority;
789 
790     /**
791      * Accent color (an ARGB integer like the constants in {@link android.graphics.Color})
792      * to be applied by the standard Style templates when presenting this notification.
793      *
794      * The current template design constructs a colorful header image by overlaying the
795      * {@link #icon} image (stenciled in white) atop a field of this color. Alpha components are
796      * ignored.
797      */
798     @ColorInt
799     public int color = COLOR_DEFAULT;
800 
801     /**
802      * Special value of {@link #color} telling the system not to decorate this notification with
803      * any special color but instead use default colors when presenting this notification.
804      */
805     @ColorInt
806     public static final int COLOR_DEFAULT = 0; // AKA Color.TRANSPARENT
807 
808     /**
809      * Special value of {@link #color} used as a place holder for an invalid color.
810      * @hide
811      */
812     @ColorInt
813     public static final int COLOR_INVALID = 1;
814 
815     /**
816      * Sphere of visibility of this notification, which affects how and when the SystemUI reveals
817      * the notification's presence and contents in untrusted situations (namely, on the secure
818      * lockscreen).
819      *
820      * The default level, {@link #VISIBILITY_PRIVATE}, behaves exactly as notifications have always
821      * done on Android: The notification's {@link #icon} and {@link #tickerText} (if available) are
822      * shown in all situations, but the contents are only available if the device is unlocked for
823      * the appropriate user.
824      *
825      * A more permissive policy can be expressed by {@link #VISIBILITY_PUBLIC}; such a notification
826      * can be read even in an "insecure" context (that is, above a secure lockscreen).
827      * To modify the public version of this notification—for example, to redact some portions—see
828      * {@link Builder#setPublicVersion(Notification)}.
829      *
830      * Finally, a notification can be made {@link #VISIBILITY_SECRET}, which will suppress its icon
831      * and ticker until the user has bypassed the lockscreen.
832      */
833     public @Visibility int visibility;
834 
835     /** @hide */
836     @IntDef(prefix = { "VISIBILITY_" }, value = {
837             VISIBILITY_PUBLIC,
838             VISIBILITY_PRIVATE,
839             VISIBILITY_SECRET,
840     })
841     @Retention(RetentionPolicy.SOURCE)
842     public @interface Visibility {}
843 
844     /**
845      * Notification visibility: Show this notification in its entirety on all lockscreens.
846      *
847      * {@see #visibility}
848      */
849     public static final int VISIBILITY_PUBLIC = 1;
850 
851     /**
852      * Notification visibility: Show this notification on all lockscreens, but conceal sensitive or
853      * private information on secure lockscreens.
854      *
855      * {@see #visibility}
856      */
857     public static final int VISIBILITY_PRIVATE = 0;
858 
859     /**
860      * Notification visibility: Do not reveal any part of this notification on a secure lockscreen.
861      *
862      * {@see #visibility}
863      */
864     public static final int VISIBILITY_SECRET = -1;
865 
866     /**
867      * @hide
868      */
869     @IntDef(prefix = "VISIBILITY_", value = {
870             VISIBILITY_PUBLIC,
871             VISIBILITY_PRIVATE,
872             VISIBILITY_SECRET,
873             NotificationManager.VISIBILITY_NO_OVERRIDE
874     })
875     public @interface NotificationVisibilityOverride{};
876 
877     /**
878      * Notification category: incoming call (voice or video) or similar synchronous communication request.
879      */
880     public static final String CATEGORY_CALL = "call";
881 
882     /**
883      * Notification category: map turn-by-turn navigation.
884      */
885     public static final String CATEGORY_NAVIGATION = "navigation";
886 
887     /**
888      * Notification category: incoming direct message (SMS, instant message, etc.).
889      */
890     public static final String CATEGORY_MESSAGE = "msg";
891 
892     /**
893      * Notification category: asynchronous bulk message (email).
894      */
895     public static final String CATEGORY_EMAIL = "email";
896 
897     /**
898      * Notification category: calendar event.
899      */
900     public static final String CATEGORY_EVENT = "event";
901 
902     /**
903      * Notification category: promotion or advertisement.
904      */
905     public static final String CATEGORY_PROMO = "promo";
906 
907     /**
908      * Notification category: alarm or timer.
909      */
910     public static final String CATEGORY_ALARM = "alarm";
911 
912     /**
913      * Notification category: progress of a long-running background operation.
914      */
915     public static final String CATEGORY_PROGRESS = "progress";
916 
917     /**
918      * Notification category: social network or sharing update.
919      */
920     public static final String CATEGORY_SOCIAL = "social";
921 
922     /**
923      * Notification category: error in background operation or authentication status.
924      */
925     public static final String CATEGORY_ERROR = "err";
926 
927     /**
928      * Notification category: media transport control for playback.
929      */
930     public static final String CATEGORY_TRANSPORT = "transport";
931 
932     /**
933      * Notification category: system or device status update.  Reserved for system use.
934      */
935     public static final String CATEGORY_SYSTEM = "sys";
936 
937     /**
938      * Notification category: indication of running background service.
939      */
940     public static final String CATEGORY_SERVICE = "service";
941 
942     /**
943      * Notification category: a specific, timely recommendation for a single thing.
944      * For example, a news app might want to recommend a news story it believes the user will
945      * want to read next.
946      */
947     public static final String CATEGORY_RECOMMENDATION = "recommendation";
948 
949     /**
950      * Notification category: ongoing information about device or contextual status.
951      */
952     public static final String CATEGORY_STATUS = "status";
953 
954     /**
955      * Notification category: user-scheduled reminder.
956      */
957     public static final String CATEGORY_REMINDER = "reminder";
958 
959     /**
960      * Notification category: extreme car emergencies.
961      * @hide
962      */
963     @SystemApi
964     public static final String CATEGORY_CAR_EMERGENCY = "car_emergency";
965 
966     /**
967      * Notification category: car warnings.
968      * @hide
969      */
970     @SystemApi
971     public static final String CATEGORY_CAR_WARNING = "car_warning";
972 
973     /**
974      * Notification category: general car system information.
975      * @hide
976      */
977     @SystemApi
978     public static final String CATEGORY_CAR_INFORMATION = "car_information";
979 
980     /**
981      * Notification category: tracking a user's workout.
982      */
983     public static final String CATEGORY_WORKOUT = "workout";
984 
985     /**
986      * Notification category: temporarily sharing location.
987      */
988     public static final String CATEGORY_LOCATION_SHARING = "location_sharing";
989 
990     /**
991      * Notification category: running stopwatch.
992      */
993     public static final String CATEGORY_STOPWATCH = "stopwatch";
994 
995     /**
996      * Notification category: missed call.
997      */
998     public static final String CATEGORY_MISSED_CALL = "missed_call";
999 
1000     /**
1001      * One of the predefined notification categories (see the <code>CATEGORY_*</code> constants)
1002      * that best describes this Notification.  May be used by the system for ranking and filtering.
1003      */
1004     public String category;
1005 
1006     @UnsupportedAppUsage
1007     private String mGroupKey;
1008 
1009     /**
1010      * Get the key used to group this notification into a cluster or stack
1011      * with other notifications on devices which support such rendering.
1012      */
getGroup()1013     public String getGroup() {
1014         return mGroupKey;
1015     }
1016 
1017     private String mSortKey;
1018 
1019     /**
1020      * Get a sort key that orders this notification among other notifications from the
1021      * same package. This can be useful if an external sort was already applied and an app
1022      * would like to preserve this. Notifications will be sorted lexicographically using this
1023      * value, although providing different priorities in addition to providing sort key may
1024      * cause this value to be ignored.
1025      *
1026      * <p>This sort key can also be used to order members of a notification group. See
1027      * {@link Builder#setGroup}.
1028      *
1029      * @see String#compareTo(String)
1030      */
getSortKey()1031     public String getSortKey() {
1032         return mSortKey;
1033     }
1034 
1035     /**
1036      * Additional semantic data to be carried around with this Notification.
1037      * <p>
1038      * The extras keys defined here are intended to capture the original inputs to {@link Builder}
1039      * APIs, and are intended to be used by
1040      * {@link android.service.notification.NotificationListenerService} implementations to extract
1041      * detailed information from notification objects.
1042      */
1043     public Bundle extras = new Bundle();
1044 
1045     /**
1046      * All pending intents in the notification as the system needs to be able to access them but
1047      * touching the extras bundle in the system process is not safe because the bundle may contain
1048      * custom parcelable objects.
1049      *
1050      * @hide
1051      */
1052     @UnsupportedAppUsage
1053     public ArraySet<PendingIntent> allPendingIntents;
1054 
1055     /**
1056      * Token identifying the notification that is applying doze/bgcheck allowlisting to the
1057      * pending intents inside of it, so only those will get the behavior.
1058      *
1059      * @hide
1060      */
1061     private IBinder mAllowlistToken;
1062 
1063     /**
1064      * Must be set by a process to start associating tokens with Notification objects
1065      * coming in to it.  This is set by NotificationManagerService.
1066      *
1067      * @hide
1068      */
1069     static public IBinder processAllowlistToken;
1070 
1071     /**
1072      * {@link #extras} key: this is the title of the notification,
1073      * as supplied to {@link Builder#setContentTitle(CharSequence)}.
1074      */
1075     public static final String EXTRA_TITLE = "android.title";
1076 
1077     /**
1078      * {@link #extras} key: this is the title of the notification when shown in expanded form,
1079      * e.g. as supplied to {@link BigTextStyle#setBigContentTitle(CharSequence)}.
1080      */
1081     public static final String EXTRA_TITLE_BIG = EXTRA_TITLE + ".big";
1082 
1083     /**
1084      * {@link #extras} key: this is the main text payload, as supplied to
1085      * {@link Builder#setContentText(CharSequence)}.
1086      */
1087     public static final String EXTRA_TEXT = "android.text";
1088 
1089     /**
1090      * {@link #extras} key: this is a third line of text, as supplied to
1091      * {@link Builder#setSubText(CharSequence)}.
1092      */
1093     public static final String EXTRA_SUB_TEXT = "android.subText";
1094 
1095     /**
1096      * {@link #extras} key: this is the remote input history, as supplied to
1097      * {@link Builder#setRemoteInputHistory(CharSequence[])}.
1098      *
1099      * Apps can fill this through {@link Builder#setRemoteInputHistory(CharSequence[])}
1100      * with the most recent inputs that have been sent through a {@link RemoteInput} of this
1101      * Notification and are expected to clear it once the it is no longer relevant (e.g. for chat
1102      * notifications once the other party has responded).
1103      *
1104      * The extra with this key is of type CharSequence[] and contains the most recent entry at
1105      * the 0 index, the second most recent at the 1 index, etc.
1106      *
1107      * @see Builder#setRemoteInputHistory(CharSequence[])
1108      */
1109     public static final String EXTRA_REMOTE_INPUT_HISTORY = "android.remoteInputHistory";
1110 
1111 
1112     /**
1113      * {@link #extras} key: this is a remote input history which can include media messages
1114      * in addition to text, as supplied to
1115      * {@link Builder#setRemoteInputHistory(RemoteInputHistoryItem[])} or
1116      * {@link Builder#setRemoteInputHistory(CharSequence[])}.
1117      *
1118      * SystemUI can populate this through
1119      * {@link Builder#setRemoteInputHistory(RemoteInputHistoryItem[])} with the most recent inputs
1120      * that have been sent through a {@link RemoteInput} of this Notification. These items can
1121      * represent either media content (specified by a URI and a MIME type) or a text message
1122      * (described by a CharSequence).
1123      *
1124      * To maintain compatibility, this can also be set by apps with
1125      * {@link Builder#setRemoteInputHistory(CharSequence[])}, which will create a
1126      * {@link RemoteInputHistoryItem} for each of the provided text-only messages.
1127      *
1128      * The extra with this key is of type {@link RemoteInputHistoryItem[]} and contains the most
1129      * recent entry at the 0 index, the second most recent at the 1 index, etc.
1130      *
1131      * @see Builder#setRemoteInputHistory(RemoteInputHistoryItem[])
1132      * @hide
1133      */
1134     public static final String EXTRA_REMOTE_INPUT_HISTORY_ITEMS = "android.remoteInputHistoryItems";
1135 
1136     /**
1137      * {@link #extras} key: boolean as supplied to
1138      * {@link Builder#setShowRemoteInputSpinner(boolean)}.
1139      *
1140      * If set to true, then the view displaying the remote input history from
1141      * {@link Builder#setRemoteInputHistory(CharSequence[])} will have a progress spinner.
1142      *
1143      * @see Builder#setShowRemoteInputSpinner(boolean)
1144      * @hide
1145      */
1146     public static final String EXTRA_SHOW_REMOTE_INPUT_SPINNER = "android.remoteInputSpinner";
1147 
1148     /**
1149      * {@link #extras} key: boolean as supplied to
1150      * {@link Builder#setHideSmartReplies(boolean)}.
1151      *
1152      * If set to true, then any smart reply buttons will be hidden.
1153      *
1154      * @see Builder#setHideSmartReplies(boolean)
1155      * @hide
1156      */
1157     public static final String EXTRA_HIDE_SMART_REPLIES = "android.hideSmartReplies";
1158 
1159     /**
1160      * {@link #extras} key: this is a small piece of additional text as supplied to
1161      * {@link Builder#setContentInfo(CharSequence)}.
1162      */
1163     public static final String EXTRA_INFO_TEXT = "android.infoText";
1164 
1165     /**
1166      * {@link #extras} key: this is a line of summary information intended to be shown
1167      * alongside expanded notifications, as supplied to (e.g.)
1168      * {@link BigTextStyle#setSummaryText(CharSequence)}.
1169      */
1170     public static final String EXTRA_SUMMARY_TEXT = "android.summaryText";
1171 
1172     /**
1173      * {@link #extras} key: this is the longer text shown in the big form of a
1174      * {@link BigTextStyle} notification, as supplied to
1175      * {@link BigTextStyle#bigText(CharSequence)}.
1176      */
1177     public static final String EXTRA_BIG_TEXT = "android.bigText";
1178 
1179     /**
1180      * {@link #extras} key: this is the resource ID of the notification's main small icon, as
1181      * supplied to {@link Builder#setSmallIcon(int)}.
1182      *
1183      * @deprecated Use {@link #getSmallIcon()}, which supports a wider variety of icon sources.
1184      */
1185     @Deprecated
1186     public static final String EXTRA_SMALL_ICON = "android.icon";
1187 
1188     /**
1189      * {@link #extras} key: this is a bitmap to be used instead of the small icon when showing the
1190      * notification payload, as
1191      * supplied to {@link Builder#setLargeIcon(android.graphics.Bitmap)}.
1192      *
1193      * @deprecated Use {@link #getLargeIcon()}, which supports a wider variety of icon sources.
1194      */
1195     @Deprecated
1196     public static final String EXTRA_LARGE_ICON = "android.largeIcon";
1197 
1198     /**
1199      * {@link #extras} key: this is a bitmap to be used instead of the one from
1200      * {@link Builder#setLargeIcon(android.graphics.Bitmap)} when the notification is
1201      * shown in its expanded form, as supplied to
1202      * {@link BigPictureStyle#bigLargeIcon(android.graphics.Bitmap)}.
1203      */
1204     public static final String EXTRA_LARGE_ICON_BIG = EXTRA_LARGE_ICON + ".big";
1205 
1206     /**
1207      * {@link #extras} key: this is the progress value supplied to
1208      * {@link Builder#setProgress(int, int, boolean)}.
1209      */
1210     public static final String EXTRA_PROGRESS = "android.progress";
1211 
1212     /**
1213      * {@link #extras} key: this is the maximum value supplied to
1214      * {@link Builder#setProgress(int, int, boolean)}.
1215      */
1216     public static final String EXTRA_PROGRESS_MAX = "android.progressMax";
1217 
1218     /**
1219      * {@link #extras} key: whether the progress bar is indeterminate, supplied to
1220      * {@link Builder#setProgress(int, int, boolean)}.
1221      */
1222     public static final String EXTRA_PROGRESS_INDETERMINATE = "android.progressIndeterminate";
1223 
1224     /**
1225      * {@link #extras} key: whether {@link #when} should be shown as a count-up timer (specifically
1226      * a {@link android.widget.Chronometer}) instead of a timestamp, as supplied to
1227      * {@link Builder#setUsesChronometer(boolean)}.
1228      */
1229     public static final String EXTRA_SHOW_CHRONOMETER = "android.showChronometer";
1230 
1231     /**
1232      * {@link #extras} key: whether the chronometer set on the notification should count down
1233      * instead of counting up. Is only relevant if key {@link #EXTRA_SHOW_CHRONOMETER} is present.
1234      * This extra is a boolean. The default is false.
1235      */
1236     public static final String EXTRA_CHRONOMETER_COUNT_DOWN = "android.chronometerCountDown";
1237 
1238     /**
1239      * {@link #extras} key: whether {@link #when} should be shown,
1240      * as supplied to {@link Builder#setShowWhen(boolean)}.
1241      */
1242     public static final String EXTRA_SHOW_WHEN = "android.showWhen";
1243 
1244     /**
1245      * {@link #extras} key: this is a bitmap to be shown in {@link BigPictureStyle} expanded
1246      * notifications, supplied to {@link BigPictureStyle#bigPicture(android.graphics.Bitmap)}.
1247      */
1248     public static final String EXTRA_PICTURE = "android.picture";
1249 
1250     /**
1251      * {@link #extras} key: this is an {@link Icon} of an image to be
1252      * shown in {@link BigPictureStyle} expanded notifications, supplied to
1253      * {@link BigPictureStyle#bigPicture(Icon)}.
1254      */
1255     public static final String EXTRA_PICTURE_ICON = "android.pictureIcon";
1256 
1257     /**
1258      * {@link #extras} key: this is a content description of the big picture supplied from
1259      * {@link BigPictureStyle#bigPicture(Bitmap)}, supplied to
1260      * {@link BigPictureStyle#setContentDescription(CharSequence)}.
1261      */
1262     public static final String EXTRA_PICTURE_CONTENT_DESCRIPTION =
1263             "android.pictureContentDescription";
1264 
1265     /**
1266      * {@link #extras} key: this is a boolean to indicate that the
1267      * {@link BigPictureStyle#bigPicture(Bitmap) big picture} is to be shown in the collapsed state
1268      * of a {@link BigPictureStyle} notification.  This will replace a
1269      * {@link Builder#setLargeIcon(Icon) large icon} in that state if one was provided.
1270      */
1271     public static final String EXTRA_SHOW_BIG_PICTURE_WHEN_COLLAPSED =
1272             "android.showBigPictureWhenCollapsed";
1273 
1274     /**
1275      * {@link #extras} key: An array of CharSequences to show in {@link InboxStyle} expanded
1276      * notifications, each of which was supplied to {@link InboxStyle#addLine(CharSequence)}.
1277      */
1278     public static final String EXTRA_TEXT_LINES = "android.textLines";
1279 
1280     /**
1281      * {@link #extras} key: A string representing the name of the specific
1282      * {@link android.app.Notification.Style} used to create this notification.
1283      */
1284     public static final String EXTRA_TEMPLATE = "android.template";
1285 
1286     /**
1287      * {@link #extras} key: A String array containing the people that this notification relates to,
1288      * each of which was supplied to {@link Builder#addPerson(String)}.
1289      *
1290      * @deprecated the actual objects are now in {@link #EXTRA_PEOPLE_LIST}
1291      */
1292     public static final String EXTRA_PEOPLE = "android.people";
1293 
1294     /**
1295      * {@link #extras} key: An arrayList of {@link Person} objects containing the people that
1296      * this notification relates to.
1297      */
1298     public static final String EXTRA_PEOPLE_LIST = "android.people.list";
1299 
1300     /**
1301      * Allow certain system-generated notifications to appear before the device is provisioned.
1302      * Only available to notifications coming from the android package.
1303      * @hide
1304      */
1305     @SystemApi
1306     @RequiresPermission(android.Manifest.permission.NOTIFICATION_DURING_SETUP)
1307     public static final String EXTRA_ALLOW_DURING_SETUP = "android.allowDuringSetup";
1308 
1309     /**
1310      * {@link #extras} key:
1311      * flat {@link String} representation of a {@link android.content.ContentUris content URI}
1312      * pointing to an image that can be displayed in the background when the notification is
1313      * selected. Used on television platforms. The URI must point to an image stream suitable for
1314      * passing into {@link android.graphics.BitmapFactory#decodeStream(java.io.InputStream)
1315      * BitmapFactory.decodeStream}; all other content types will be ignored.
1316      */
1317     public static final String EXTRA_BACKGROUND_IMAGE_URI = "android.backgroundImageUri";
1318 
1319     /**
1320      * {@link #extras} key: A
1321      * {@link android.media.session.MediaSession.Token} associated with a
1322      * {@link android.app.Notification.MediaStyle} notification.
1323      */
1324     public static final String EXTRA_MEDIA_SESSION = "android.mediaSession";
1325 
1326     /**
1327      * {@link #extras} key: the indices of actions to be shown in the compact view,
1328      * as supplied to (e.g.) {@link MediaStyle#setShowActionsInCompactView(int...)}.
1329      */
1330     public static final String EXTRA_COMPACT_ACTIONS = "android.compactActions";
1331 
1332     /**
1333      * {@link #extras} key: the username to be displayed for all messages sent by the user including
1334      * direct replies
1335      * {@link android.app.Notification.MessagingStyle} notification. This extra is a
1336      * {@link CharSequence}
1337      *
1338      * @deprecated use {@link #EXTRA_MESSAGING_PERSON}
1339      */
1340     public static final String EXTRA_SELF_DISPLAY_NAME = "android.selfDisplayName";
1341 
1342     /**
1343      * {@link #extras} key: the person to be displayed for all messages sent by the user including
1344      * direct replies
1345      * {@link android.app.Notification.MessagingStyle} notification. This extra is a
1346      * {@link Person}
1347      */
1348     public static final String EXTRA_MESSAGING_PERSON = "android.messagingUser";
1349 
1350     /**
1351      * {@link #extras} key: a {@link CharSequence} to be displayed as the title to a conversation
1352      * represented by a {@link android.app.Notification.MessagingStyle}
1353      */
1354     public static final String EXTRA_CONVERSATION_TITLE = "android.conversationTitle";
1355 
1356     /** @hide */
1357     public static final String EXTRA_CONVERSATION_ICON = "android.conversationIcon";
1358 
1359     /** @hide */
1360     public static final String EXTRA_CONVERSATION_UNREAD_MESSAGE_COUNT =
1361             "android.conversationUnreadMessageCount";
1362 
1363     /**
1364      * {@link #extras} key: an array of {@link android.app.Notification.MessagingStyle.Message}
1365      * bundles provided by a
1366      * {@link android.app.Notification.MessagingStyle} notification. This extra is a parcelable
1367      * array of bundles.
1368      */
1369     public static final String EXTRA_MESSAGES = "android.messages";
1370 
1371     /**
1372      * {@link #extras} key: an array of
1373      * {@link android.app.Notification.MessagingStyle#addHistoricMessage historic}
1374      * {@link android.app.Notification.MessagingStyle.Message} bundles provided by a
1375      * {@link android.app.Notification.MessagingStyle} notification. This extra is a parcelable
1376      * array of bundles.
1377      */
1378     public static final String EXTRA_HISTORIC_MESSAGES = "android.messages.historic";
1379 
1380     /**
1381      * {@link #extras} key: whether the {@link android.app.Notification.MessagingStyle} notification
1382      * represents a group conversation.
1383      */
1384     public static final String EXTRA_IS_GROUP_CONVERSATION = "android.isGroupConversation";
1385 
1386     /**
1387      * {@link #extras} key: the type of call represented by the
1388      * {@link android.app.Notification.CallStyle} notification. This extra is an int.
1389      * @hide
1390      */
1391     public static final String EXTRA_CALL_TYPE = "android.callType";
1392 
1393     /**
1394      * {@link #extras} key: whether the  {@link android.app.Notification.CallStyle} notification
1395      * is for a call that will activate video when answered. This extra is a boolean.
1396      */
1397     public static final String EXTRA_CALL_IS_VIDEO = "android.callIsVideo";
1398 
1399     /**
1400      * {@link #extras} key: the person to be displayed as calling for the
1401      * {@link android.app.Notification.CallStyle} notification. This extra is a {@link Person}.
1402      */
1403     public static final String EXTRA_CALL_PERSON = "android.callPerson";
1404 
1405     /**
1406      * {@link #extras} key: the icon to be displayed as a verification status of the caller on a
1407      * {@link android.app.Notification.CallStyle} notification. This extra is an {@link Icon}.
1408      */
1409     public static final String EXTRA_VERIFICATION_ICON = "android.verificationIcon";
1410 
1411     /**
1412      * {@link #extras} key: the text to be displayed as a verification status of the caller on a
1413      * {@link android.app.Notification.CallStyle} notification. This extra is a
1414      * {@link CharSequence}.
1415      */
1416     public static final String EXTRA_VERIFICATION_TEXT = "android.verificationText";
1417 
1418     /**
1419      * {@link #extras} key: the intent to be sent when the users answers a
1420      * {@link android.app.Notification.CallStyle} notification. This extra is a
1421      * {@link PendingIntent}.
1422      */
1423     public static final String EXTRA_ANSWER_INTENT = "android.answerIntent";
1424 
1425     /**
1426      * {@link #extras} key: the intent to be sent when the users declines a
1427      * {@link android.app.Notification.CallStyle} notification. This extra is a
1428      * {@link PendingIntent}.
1429      */
1430     public static final String EXTRA_DECLINE_INTENT = "android.declineIntent";
1431 
1432     /**
1433      * {@link #extras} key: the intent to be sent when the users hangs up a
1434      * {@link android.app.Notification.CallStyle} notification. This extra is a
1435      * {@link PendingIntent}.
1436      */
1437     public static final String EXTRA_HANG_UP_INTENT = "android.hangUpIntent";
1438 
1439     /**
1440      * {@link #extras} key: the color used as a hint for the Answer action button of a
1441      * {@link android.app.Notification.CallStyle} notification. This extra is a {@link ColorInt}.
1442      */
1443     public static final String EXTRA_ANSWER_COLOR = "android.answerColor";
1444 
1445     /**
1446      * {@link #extras} key: the color used as a hint for the Decline or Hang Up action button of a
1447      * {@link android.app.Notification.CallStyle} notification. This extra is a {@link ColorInt}.
1448      */
1449     public static final String EXTRA_DECLINE_COLOR = "android.declineColor";
1450 
1451     /**
1452      * {@link #extras} key: whether the notification should be colorized as
1453      * supplied to {@link Builder#setColorized(boolean)}.
1454      */
1455     public static final String EXTRA_COLORIZED = "android.colorized";
1456 
1457     /**
1458      * @hide
1459      */
1460     public static final String EXTRA_BUILDER_APPLICATION_INFO = "android.appInfo";
1461 
1462     /**
1463      * @hide
1464      */
1465     public static final String EXTRA_CONTAINS_CUSTOM_VIEW = "android.contains.customView";
1466 
1467     /**
1468      * @hide
1469      */
1470     public static final String EXTRA_REDUCED_IMAGES = "android.reduced.images";
1471 
1472     /**
1473      * {@link #extras} key: the audio contents of this notification.
1474      *
1475      * This is for use when rendering the notification on an audio-focused interface;
1476      * the audio contents are a complete sound sample that contains the contents/body of the
1477      * notification. This may be used in substitute of a Text-to-Speech reading of the
1478      * notification. For example if the notification represents a voice message this should point
1479      * to the audio of that message.
1480      *
1481      * The data stored under this key should be a String representation of a Uri that contains the
1482      * audio contents in one of the following formats: WAV, PCM 16-bit, AMR-WB.
1483      *
1484      * This extra is unnecessary if you are using {@code MessagingStyle} since each {@code Message}
1485      * has a field for holding data URI. That field can be used for audio.
1486      * See {@code Message#setData}.
1487      *
1488      * Example usage:
1489      * <pre>
1490      * {@code
1491      * Notification.Builder myBuilder = (build your Notification as normal);
1492      * myBuilder.getExtras().putString(EXTRA_AUDIO_CONTENTS_URI, myAudioUri.toString());
1493      * }
1494      * </pre>
1495      */
1496     public static final String EXTRA_AUDIO_CONTENTS_URI = "android.audioContents";
1497 
1498     /** @hide */
1499     @SystemApi
1500     @RequiresPermission(android.Manifest.permission.SUBSTITUTE_NOTIFICATION_APP_NAME)
1501     public static final String EXTRA_SUBSTITUTE_APP_NAME = "android.substName";
1502 
1503     /**
1504      * This is set on the notifications shown by system_server about apps running foreground
1505      * services. It indicates that the notification should be shown
1506      * only if any of the given apps do not already have a properly tagged
1507      * {@link #FLAG_FOREGROUND_SERVICE} notification currently visible to the user.
1508      * This is a string array of all package names of the apps.
1509      * @hide
1510      */
1511     public static final String EXTRA_FOREGROUND_APPS = "android.foregroundApps";
1512 
1513     @UnsupportedAppUsage
1514     private Icon mSmallIcon;
1515     @UnsupportedAppUsage
1516     private Icon mLargeIcon;
1517 
1518     @UnsupportedAppUsage
1519     private String mChannelId;
1520     private long mTimeout;
1521 
1522     private String mShortcutId;
1523     private LocusId mLocusId;
1524     private CharSequence mSettingsText;
1525 
1526     private BubbleMetadata mBubbleMetadata;
1527 
1528     /** @hide */
1529     @IntDef(prefix = { "GROUP_ALERT_" }, value = {
1530             GROUP_ALERT_ALL, GROUP_ALERT_CHILDREN, GROUP_ALERT_SUMMARY
1531     })
1532     @Retention(RetentionPolicy.SOURCE)
1533     public @interface GroupAlertBehavior {}
1534 
1535     /**
1536      * Constant for {@link Builder#setGroupAlertBehavior(int)}, meaning that all notifications in a
1537      * group with sound or vibration ought to make sound or vibrate (respectively), so this
1538      * notification will not be muted when it is in a group.
1539      */
1540     public static final int GROUP_ALERT_ALL = 0;
1541 
1542     /**
1543      * Constant for {@link Builder#setGroupAlertBehavior(int)}, meaning that all children
1544      * notification in a group should be silenced (no sound or vibration) even if they are posted
1545      * to a {@link NotificationChannel} that has sound and/or vibration. Use this constant to
1546      * mute this notification if this notification is a group child. This must be applied to all
1547      * children notifications you want to mute.
1548      *
1549      * <p> For example, you might want to use this constant if you post a number of children
1550      * notifications at once (say, after a periodic sync), and only need to notify the user
1551      * audibly once.
1552      */
1553     public static final int GROUP_ALERT_SUMMARY = 1;
1554 
1555     /**
1556      * Constant for {@link Builder#setGroupAlertBehavior(int)}, meaning that the summary
1557      * notification in a group should be silenced (no sound or vibration) even if they are
1558      * posted to a {@link NotificationChannel} that has sound and/or vibration. Use this constant
1559      * to mute this notification if this notification is a group summary.
1560      *
1561      * <p>For example, you might want to use this constant if only the children notifications
1562      * in your group have content and the summary is only used to visually group notifications
1563      * rather than to alert the user that new information is available.
1564      */
1565     public static final int GROUP_ALERT_CHILDREN = 2;
1566 
1567     private int mGroupAlertBehavior = GROUP_ALERT_ALL;
1568 
1569     /**
1570      * If this notification is being shown as a badge, always show as a number.
1571      */
1572     public static final int BADGE_ICON_NONE = 0;
1573 
1574     /**
1575      * If this notification is being shown as a badge, use the {@link #getSmallIcon()} to
1576      * represent this notification.
1577      */
1578     public static final int BADGE_ICON_SMALL = 1;
1579 
1580     /**
1581      * If this notification is being shown as a badge, use the {@link #getLargeIcon()} to
1582      * represent this notification.
1583      */
1584     public static final int BADGE_ICON_LARGE = 2;
1585     private int mBadgeIcon = BADGE_ICON_NONE;
1586 
1587     /**
1588      * Determines whether the platform can generate contextual actions for a notification.
1589      */
1590     private boolean mAllowSystemGeneratedContextualActions = true;
1591 
1592     /**
1593      * Structure to encapsulate a named action that can be shown as part of this notification.
1594      * It must include an icon, a label, and a {@link PendingIntent} to be fired when the action is
1595      * selected by the user.
1596      * <p>
1597      * Apps should use {@link Notification.Builder#addAction(int, CharSequence, PendingIntent)}
1598      * or {@link Notification.Builder#addAction(Notification.Action)}
1599      * to attach actions.
1600      * <p>
1601      * As of Android {@link android.os.Build.VERSION_CODES#S}, apps targeting API level {@link
1602      * android.os.Build.VERSION_CODES#S} or higher won't be able to start activities while
1603      * processing broadcast receivers or services in response to notification action clicks. To
1604      * launch an activity in those cases, provide a {@link PendingIntent} for the activity itself.
1605      */
1606     public static class Action implements Parcelable {
1607         /**
1608          * {@link #extras} key: Keys to a {@link Parcelable} {@link ArrayList} of
1609          * {@link RemoteInput}s.
1610          *
1611          * This is intended for {@link RemoteInput}s that only accept data, meaning
1612          * {@link RemoteInput#getAllowFreeFormInput} is false, {@link RemoteInput#getChoices}
1613          * is null or empty, and {@link RemoteInput#getAllowedDataTypes} is non-null and not
1614          * empty. These {@link RemoteInput}s will be ignored by devices that do not
1615          * support non-text-based {@link RemoteInput}s. See {@link Builder#build}.
1616          *
1617          * You can test if a RemoteInput matches these constraints using
1618          * {@link RemoteInput#isDataOnly}.
1619          */
1620         private static final String EXTRA_DATA_ONLY_INPUTS = "android.extra.DATA_ONLY_INPUTS";
1621 
1622         /**
1623          * {@link }: No semantic action defined.
1624          */
1625         public static final int SEMANTIC_ACTION_NONE = 0;
1626 
1627         /**
1628          * {@code SemanticAction}: Reply to a conversation, chat, group, or wherever replies
1629          * may be appropriate.
1630          */
1631         public static final int SEMANTIC_ACTION_REPLY = 1;
1632 
1633         /**
1634          * {@code SemanticAction}: Mark content as read.
1635          */
1636         public static final int SEMANTIC_ACTION_MARK_AS_READ = 2;
1637 
1638         /**
1639          * {@code SemanticAction}: Mark content as unread.
1640          */
1641         public static final int SEMANTIC_ACTION_MARK_AS_UNREAD = 3;
1642 
1643         /**
1644          * {@code SemanticAction}: Delete the content associated with the notification. This
1645          * could mean deleting an email, message, etc.
1646          */
1647         public static final int SEMANTIC_ACTION_DELETE = 4;
1648 
1649         /**
1650          * {@code SemanticAction}: Archive the content associated with the notification. This
1651          * could mean archiving an email, message, etc.
1652          */
1653         public static final int SEMANTIC_ACTION_ARCHIVE = 5;
1654 
1655         /**
1656          * {@code SemanticAction}: Mute the content associated with the notification. This could
1657          * mean silencing a conversation or currently playing media.
1658          */
1659         public static final int SEMANTIC_ACTION_MUTE = 6;
1660 
1661         /**
1662          * {@code SemanticAction}: Unmute the content associated with the notification. This could
1663          * mean un-silencing a conversation or currently playing media.
1664          */
1665         public static final int SEMANTIC_ACTION_UNMUTE = 7;
1666 
1667         /**
1668          * {@code SemanticAction}: Mark content with a thumbs up.
1669          */
1670         public static final int SEMANTIC_ACTION_THUMBS_UP = 8;
1671 
1672         /**
1673          * {@code SemanticAction}: Mark content with a thumbs down.
1674          */
1675         public static final int SEMANTIC_ACTION_THUMBS_DOWN = 9;
1676 
1677         /**
1678          * {@code SemanticAction}: Call a contact, group, etc.
1679          */
1680         public static final int SEMANTIC_ACTION_CALL = 10;
1681 
1682         /**
1683          * {@code SemanticAction}: Mark the conversation associated with the notification as a
1684          * priority. Note that this is only for use by the notification assistant services. The
1685          * type will be ignored for actions an app adds to its own notifications.
1686          * @hide
1687          */
1688         @SystemApi
1689         public static final int SEMANTIC_ACTION_MARK_CONVERSATION_AS_PRIORITY = 11;
1690 
1691         /**
1692          * {@code SemanticAction}: Mark content as a potential phishing attempt.
1693          * Note that this is only for use by the notification assistant services. The type will
1694          * be ignored for actions an app adds to its own notifications.
1695          * @hide
1696          */
1697         @SystemApi
1698         public static final int SEMANTIC_ACTION_CONVERSATION_IS_PHISHING = 12;
1699 
1700         private final Bundle mExtras;
1701         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
1702         private Icon mIcon;
1703         private final RemoteInput[] mRemoteInputs;
1704         private boolean mAllowGeneratedReplies = true;
1705         private final @SemanticAction int mSemanticAction;
1706         private final boolean mIsContextual;
1707         private boolean mAuthenticationRequired;
1708 
1709         /**
1710          * Small icon representing the action.
1711          *
1712          * @deprecated Use {@link Action#getIcon()} instead.
1713          */
1714         @Deprecated
1715         public int icon;
1716 
1717         /**
1718          * Title of the action.
1719          */
1720         public CharSequence title;
1721 
1722         /**
1723          * Intent to send when the user invokes this action. May be null, in which case the action
1724          * may be rendered in a disabled presentation by the system UI.
1725          */
1726         public PendingIntent actionIntent;
1727 
Action(Parcel in)1728         private Action(Parcel in) {
1729             if (in.readInt() != 0) {
1730                 mIcon = Icon.CREATOR.createFromParcel(in);
1731                 if (mIcon.getType() == Icon.TYPE_RESOURCE) {
1732                     icon = mIcon.getResId();
1733                 }
1734             }
1735             title = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
1736             if (in.readInt() == 1) {
1737                 actionIntent = PendingIntent.CREATOR.createFromParcel(in);
1738             }
1739             mExtras = Bundle.setDefusable(in.readBundle(), true);
1740             mRemoteInputs = in.createTypedArray(RemoteInput.CREATOR);
1741             mAllowGeneratedReplies = in.readInt() == 1;
1742             mSemanticAction = in.readInt();
1743             mIsContextual = in.readInt() == 1;
1744             mAuthenticationRequired = in.readInt() == 1;
1745         }
1746 
1747         /**
1748          * @deprecated Use {@link android.app.Notification.Action.Builder}.
1749          */
1750         @Deprecated
Action(int icon, CharSequence title, PendingIntent intent)1751         public Action(int icon, CharSequence title, PendingIntent intent) {
1752             this(Icon.createWithResource("", icon), title, intent, new Bundle(), null, true,
1753                     SEMANTIC_ACTION_NONE, false /* isContextual */, false /* requireAuth */);
1754         }
1755 
1756         /** Keep in sync with {@link Notification.Action.Builder#Builder(Action)}! */
Action(Icon icon, CharSequence title, PendingIntent intent, Bundle extras, RemoteInput[] remoteInputs, boolean allowGeneratedReplies, @SemanticAction int semanticAction, boolean isContextual, boolean requireAuth)1757         private Action(Icon icon, CharSequence title, PendingIntent intent, Bundle extras,
1758                 RemoteInput[] remoteInputs, boolean allowGeneratedReplies,
1759                 @SemanticAction int semanticAction, boolean isContextual,
1760                 boolean requireAuth) {
1761             this.mIcon = icon;
1762             if (icon != null && icon.getType() == Icon.TYPE_RESOURCE) {
1763                 this.icon = icon.getResId();
1764             }
1765             this.title = title;
1766             this.actionIntent = intent;
1767             this.mExtras = extras != null ? extras : new Bundle();
1768             this.mRemoteInputs = remoteInputs;
1769             this.mAllowGeneratedReplies = allowGeneratedReplies;
1770             this.mSemanticAction = semanticAction;
1771             this.mIsContextual = isContextual;
1772             this.mAuthenticationRequired = requireAuth;
1773         }
1774 
1775         /**
1776          * Return an icon representing the action.
1777          */
getIcon()1778         public Icon getIcon() {
1779             if (mIcon == null && icon != 0) {
1780                 // you snuck an icon in here without using the builder; let's try to keep it
1781                 mIcon = Icon.createWithResource("", icon);
1782             }
1783             return mIcon;
1784         }
1785 
1786         /**
1787          * Get additional metadata carried around with this Action.
1788          */
getExtras()1789         public Bundle getExtras() {
1790             return mExtras;
1791         }
1792 
1793         /**
1794          * Return whether the platform should automatically generate possible replies for this
1795          * {@link Action}
1796          */
getAllowGeneratedReplies()1797         public boolean getAllowGeneratedReplies() {
1798             return mAllowGeneratedReplies;
1799         }
1800 
1801         /**
1802          * Get the list of inputs to be collected from the user when this action is sent.
1803          * May return null if no remote inputs were added. Only returns inputs which accept
1804          * a text input. For inputs which only accept data use {@link #getDataOnlyRemoteInputs}.
1805          */
getRemoteInputs()1806         public RemoteInput[] getRemoteInputs() {
1807             return mRemoteInputs;
1808         }
1809 
1810         /**
1811          * Returns the {@code SemanticAction} associated with this {@link Action}. A
1812          * {@code SemanticAction} denotes what an {@link Action}'s {@link PendingIntent} will do
1813          * (eg. reply, mark as read, delete, etc).
1814          */
getSemanticAction()1815         public @SemanticAction int getSemanticAction() {
1816             return mSemanticAction;
1817         }
1818 
1819         /**
1820          * Returns whether this is a contextual Action, i.e. whether the action is dependent on the
1821          * notification message body. An example of a contextual action could be an action opening a
1822          * map application with an address shown in the notification.
1823          */
isContextual()1824         public boolean isContextual() {
1825             return mIsContextual;
1826         }
1827 
1828         /**
1829          * Get the list of inputs to be collected from the user that ONLY accept data when this
1830          * action is sent. These remote inputs are guaranteed to return true on a call to
1831          * {@link RemoteInput#isDataOnly}.
1832          *
1833          * Returns null if there are no data-only remote inputs.
1834          *
1835          * This method exists so that legacy RemoteInput collectors that pre-date the addition
1836          * of non-textual RemoteInputs do not access these remote inputs.
1837          */
getDataOnlyRemoteInputs()1838         public RemoteInput[] getDataOnlyRemoteInputs() {
1839             return getParcelableArrayFromBundle(mExtras, EXTRA_DATA_ONLY_INPUTS, RemoteInput.class);
1840         }
1841 
1842         /**
1843          * Returns whether the OS should only send this action's {@link PendingIntent} on an
1844          * unlocked device.
1845          *
1846          * If the device is locked when the action is invoked, the OS should show the keyguard and
1847          * require successful authentication before invoking the intent.
1848          */
isAuthenticationRequired()1849         public boolean isAuthenticationRequired() {
1850             return mAuthenticationRequired;
1851         }
1852 
1853         /**
1854          * Builder class for {@link Action} objects.
1855          */
1856         public static final class Builder {
1857             @Nullable private final Icon mIcon;
1858             @Nullable private final CharSequence mTitle;
1859             @Nullable private final PendingIntent mIntent;
1860             private boolean mAllowGeneratedReplies = true;
1861             @NonNull private final Bundle mExtras;
1862             @Nullable private ArrayList<RemoteInput> mRemoteInputs;
1863             private @SemanticAction int mSemanticAction;
1864             private boolean mIsContextual;
1865             private boolean mAuthenticationRequired;
1866 
1867             /**
1868              * Construct a new builder for {@link Action} object.
1869              * @param icon icon to show for this action
1870              * @param title the title of the action
1871              * @param intent the {@link PendingIntent} to fire when users trigger this action
1872              */
1873             @Deprecated
Builder(int icon, CharSequence title, PendingIntent intent)1874             public Builder(int icon, CharSequence title, PendingIntent intent) {
1875                 this(Icon.createWithResource("", icon), title, intent);
1876             }
1877 
1878             /**
1879              * Construct a new builder for {@link Action} object.
1880              *
1881              * <p>As of Android {@link android.os.Build.VERSION_CODES#S}, apps targeting API level
1882              * {@link android.os.Build.VERSION_CODES#S} or higher won't be able to start activities
1883              * while processing broadcast receivers or services in response to notification action
1884              * clicks. To launch an activity in those cases, provide a {@link PendingIntent} for the
1885              * activity itself.
1886              *
1887              * @param icon icon to show for this action
1888              * @param title the title of the action
1889              * @param intent the {@link PendingIntent} to fire when users trigger this action
1890              */
Builder(Icon icon, CharSequence title, PendingIntent intent)1891             public Builder(Icon icon, CharSequence title, PendingIntent intent) {
1892                 this(icon, title, intent, new Bundle(), null, true, SEMANTIC_ACTION_NONE, false);
1893             }
1894 
1895             /**
1896              * Construct a new builder for {@link Action} object using the fields from an
1897              * {@link Action}.
1898              * @param action the action to read fields from.
1899              */
Builder(Action action)1900             public Builder(Action action) {
1901                 this(action.getIcon(), action.title, action.actionIntent,
1902                         new Bundle(action.mExtras), action.getRemoteInputs(),
1903                         action.getAllowGeneratedReplies(), action.getSemanticAction(),
1904                         action.isAuthenticationRequired());
1905             }
1906 
Builder(@ullable Icon icon, @Nullable CharSequence title, @Nullable PendingIntent intent, @NonNull Bundle extras, @Nullable RemoteInput[] remoteInputs, boolean allowGeneratedReplies, @SemanticAction int semanticAction, boolean authRequired)1907             private Builder(@Nullable Icon icon, @Nullable CharSequence title,
1908                     @Nullable PendingIntent intent, @NonNull Bundle extras,
1909                     @Nullable RemoteInput[] remoteInputs, boolean allowGeneratedReplies,
1910                     @SemanticAction int semanticAction, boolean authRequired) {
1911                 mIcon = icon;
1912                 mTitle = title;
1913                 mIntent = intent;
1914                 mExtras = extras;
1915                 if (remoteInputs != null) {
1916                     mRemoteInputs = new ArrayList<>(remoteInputs.length);
1917                     Collections.addAll(mRemoteInputs, remoteInputs);
1918                 }
1919                 mAllowGeneratedReplies = allowGeneratedReplies;
1920                 mSemanticAction = semanticAction;
1921                 mAuthenticationRequired = authRequired;
1922             }
1923 
1924             /**
1925              * Merge additional metadata into this builder.
1926              *
1927              * <p>Values within the Bundle will replace existing extras values in this Builder.
1928              *
1929              * @see Notification.Action#extras
1930              */
1931             @NonNull
addExtras(Bundle extras)1932             public Builder addExtras(Bundle extras) {
1933                 if (extras != null) {
1934                     mExtras.putAll(extras);
1935                 }
1936                 return this;
1937             }
1938 
1939             /**
1940              * Get the metadata Bundle used by this Builder.
1941              *
1942              * <p>The returned Bundle is shared with this Builder.
1943              */
1944             @NonNull
getExtras()1945             public Bundle getExtras() {
1946                 return mExtras;
1947             }
1948 
1949             /**
1950              * Add an input to be collected from the user when this action is sent.
1951              * Response values can be retrieved from the fired intent by using the
1952              * {@link RemoteInput#getResultsFromIntent} function.
1953              * @param remoteInput a {@link RemoteInput} to add to the action
1954              * @return this object for method chaining
1955              */
1956             @NonNull
addRemoteInput(RemoteInput remoteInput)1957             public Builder addRemoteInput(RemoteInput remoteInput) {
1958                 if (mRemoteInputs == null) {
1959                     mRemoteInputs = new ArrayList<RemoteInput>();
1960                 }
1961                 mRemoteInputs.add(remoteInput);
1962                 return this;
1963             }
1964 
1965             /**
1966              * Set whether the platform should automatically generate possible replies to add to
1967              * {@link RemoteInput#getChoices()}. If the {@link Action} doesn't have a
1968              * {@link RemoteInput}, this has no effect.
1969              * @param allowGeneratedReplies {@code true} to allow generated replies, {@code false}
1970              * otherwise
1971              * @return this object for method chaining
1972              * The default value is {@code true}
1973              */
1974             @NonNull
setAllowGeneratedReplies(boolean allowGeneratedReplies)1975             public Builder setAllowGeneratedReplies(boolean allowGeneratedReplies) {
1976                 mAllowGeneratedReplies = allowGeneratedReplies;
1977                 return this;
1978             }
1979 
1980             /**
1981              * Sets the {@code SemanticAction} for this {@link Action}. A
1982              * {@code SemanticAction} denotes what an {@link Action}'s
1983              * {@link PendingIntent} will do (eg. reply, mark as read, delete, etc).
1984              * @param semanticAction a SemanticAction defined within {@link Action} with
1985              * {@code SEMANTIC_ACTION_} prefixes
1986              * @return this object for method chaining
1987              */
1988             @NonNull
setSemanticAction(@emanticAction int semanticAction)1989             public Builder setSemanticAction(@SemanticAction int semanticAction) {
1990                 mSemanticAction = semanticAction;
1991                 return this;
1992             }
1993 
1994             /**
1995              * Sets whether this {@link Action} is a contextual action, i.e. whether the action is
1996              * dependent on the notification message body. An example of a contextual action could
1997              * be an action opening a map application with an address shown in the notification.
1998              */
1999             @NonNull
setContextual(boolean isContextual)2000             public Builder setContextual(boolean isContextual) {
2001                 mIsContextual = isContextual;
2002                 return this;
2003             }
2004 
2005             /**
2006              * Apply an extender to this action builder. Extenders may be used to add
2007              * metadata or change options on this builder.
2008              */
2009             @NonNull
extend(Extender extender)2010             public Builder extend(Extender extender) {
2011                 extender.extend(this);
2012                 return this;
2013             }
2014 
2015             /**
2016              * Sets whether the OS should only send this action's {@link PendingIntent} on an
2017              * unlocked device.
2018              *
2019              * If this is true and the device is locked when the action is invoked, the OS will
2020              * show the keyguard and require successful authentication before invoking the intent.
2021              * If this is false and the device is locked, the OS will decide whether authentication
2022              * should be required.
2023              */
2024             @NonNull
setAuthenticationRequired(boolean authenticationRequired)2025             public Builder setAuthenticationRequired(boolean authenticationRequired) {
2026                 mAuthenticationRequired = authenticationRequired;
2027                 return this;
2028             }
2029 
2030             /**
2031              * Throws an NPE if we are building a contextual action missing one of the fields
2032              * necessary to display the action.
2033              */
checkContextualActionNullFields()2034             private void checkContextualActionNullFields() {
2035                 if (!mIsContextual) return;
2036 
2037                 if (mIcon == null) {
2038                     throw new NullPointerException("Contextual Actions must contain a valid icon");
2039                 }
2040 
2041                 if (mIntent == null) {
2042                     throw new NullPointerException(
2043                             "Contextual Actions must contain a valid PendingIntent");
2044                 }
2045             }
2046 
2047             /**
2048              * Combine all of the options that have been set and return a new {@link Action}
2049              * object.
2050              * @return the built action
2051              */
2052             @NonNull
build()2053             public Action build() {
2054                 checkContextualActionNullFields();
2055 
2056                 ArrayList<RemoteInput> dataOnlyInputs = new ArrayList<>();
2057                 RemoteInput[] previousDataInputs = getParcelableArrayFromBundle(
2058                         mExtras, EXTRA_DATA_ONLY_INPUTS, RemoteInput.class);
2059                 if (previousDataInputs != null) {
2060                     for (RemoteInput input : previousDataInputs) {
2061                         dataOnlyInputs.add(input);
2062                     }
2063                 }
2064                 List<RemoteInput> textInputs = new ArrayList<>();
2065                 if (mRemoteInputs != null) {
2066                     for (RemoteInput input : mRemoteInputs) {
2067                         if (input.isDataOnly()) {
2068                             dataOnlyInputs.add(input);
2069                         } else {
2070                             textInputs.add(input);
2071                         }
2072                     }
2073                 }
2074                 if (!dataOnlyInputs.isEmpty()) {
2075                     RemoteInput[] dataInputsArr =
2076                             dataOnlyInputs.toArray(new RemoteInput[dataOnlyInputs.size()]);
2077                     mExtras.putParcelableArray(EXTRA_DATA_ONLY_INPUTS, dataInputsArr);
2078                 }
2079                 RemoteInput[] textInputsArr = textInputs.isEmpty()
2080                         ? null : textInputs.toArray(new RemoteInput[textInputs.size()]);
2081                 return new Action(mIcon, mTitle, mIntent, mExtras, textInputsArr,
2082                         mAllowGeneratedReplies, mSemanticAction, mIsContextual,
2083                         mAuthenticationRequired);
2084             }
2085         }
2086 
2087         @Override
clone()2088         public Action clone() {
2089             return new Action(
2090                     getIcon(),
2091                     title,
2092                     actionIntent, // safe to alias
2093                     mExtras == null ? new Bundle() : new Bundle(mExtras),
2094                     getRemoteInputs(),
2095                     getAllowGeneratedReplies(),
2096                     getSemanticAction(),
2097                     isContextual(),
2098                     isAuthenticationRequired());
2099         }
2100 
2101         @Override
describeContents()2102         public int describeContents() {
2103             return 0;
2104         }
2105 
2106         @Override
writeToParcel(Parcel out, int flags)2107         public void writeToParcel(Parcel out, int flags) {
2108             final Icon ic = getIcon();
2109             if (ic != null) {
2110                 out.writeInt(1);
2111                 ic.writeToParcel(out, 0);
2112             } else {
2113                 out.writeInt(0);
2114             }
2115             TextUtils.writeToParcel(title, out, flags);
2116             if (actionIntent != null) {
2117                 out.writeInt(1);
2118                 actionIntent.writeToParcel(out, flags);
2119             } else {
2120                 out.writeInt(0);
2121             }
2122             out.writeBundle(mExtras);
2123             out.writeTypedArray(mRemoteInputs, flags);
2124             out.writeInt(mAllowGeneratedReplies ? 1 : 0);
2125             out.writeInt(mSemanticAction);
2126             out.writeInt(mIsContextual ? 1 : 0);
2127             out.writeInt(mAuthenticationRequired ? 1 : 0);
2128         }
2129 
2130         public static final @android.annotation.NonNull Parcelable.Creator<Action> CREATOR =
2131                 new Parcelable.Creator<Action>() {
2132             public Action createFromParcel(Parcel in) {
2133                 return new Action(in);
2134             }
2135             public Action[] newArray(int size) {
2136                 return new Action[size];
2137             }
2138         };
2139 
2140         /**
2141          * Extender interface for use with {@link Builder#extend}. Extenders may be used to add
2142          * metadata or change options on an action builder.
2143          */
2144         public interface Extender {
2145             /**
2146              * Apply this extender to a notification action builder.
2147              * @param builder the builder to be modified.
2148              * @return the build object for chaining.
2149              */
extend(Builder builder)2150             public Builder extend(Builder builder);
2151         }
2152 
2153         /**
2154          * Wearable extender for notification actions. To add extensions to an action,
2155          * create a new {@link android.app.Notification.Action.WearableExtender} object using
2156          * the {@code WearableExtender()} constructor and apply it to a
2157          * {@link android.app.Notification.Action.Builder} using
2158          * {@link android.app.Notification.Action.Builder#extend}.
2159          *
2160          * <pre class="prettyprint">
2161          * Notification.Action action = new Notification.Action.Builder(
2162          *         R.drawable.archive_all, "Archive all", actionIntent)
2163          *         .extend(new Notification.Action.WearableExtender()
2164          *                 .setAvailableOffline(false))
2165          *         .build();</pre>
2166          */
2167         public static final class WearableExtender implements Extender {
2168             /** Notification action extra which contains wearable extensions */
2169             private static final String EXTRA_WEARABLE_EXTENSIONS = "android.wearable.EXTENSIONS";
2170 
2171             // Keys within EXTRA_WEARABLE_EXTENSIONS for wearable options.
2172             private static final String KEY_FLAGS = "flags";
2173             private static final String KEY_IN_PROGRESS_LABEL = "inProgressLabel";
2174             private static final String KEY_CONFIRM_LABEL = "confirmLabel";
2175             private static final String KEY_CANCEL_LABEL = "cancelLabel";
2176 
2177             // Flags bitwise-ored to mFlags
2178             private static final int FLAG_AVAILABLE_OFFLINE = 0x1;
2179             private static final int FLAG_HINT_LAUNCHES_ACTIVITY = 1 << 1;
2180             private static final int FLAG_HINT_DISPLAY_INLINE = 1 << 2;
2181 
2182             // Default value for flags integer
2183             private static final int DEFAULT_FLAGS = FLAG_AVAILABLE_OFFLINE;
2184 
2185             private int mFlags = DEFAULT_FLAGS;
2186 
2187             private CharSequence mInProgressLabel;
2188             private CharSequence mConfirmLabel;
2189             private CharSequence mCancelLabel;
2190 
2191             /**
2192              * Create a {@link android.app.Notification.Action.WearableExtender} with default
2193              * options.
2194              */
WearableExtender()2195             public WearableExtender() {
2196             }
2197 
2198             /**
2199              * Create a {@link android.app.Notification.Action.WearableExtender} by reading
2200              * wearable options present in an existing notification action.
2201              * @param action the notification action to inspect.
2202              */
WearableExtender(Action action)2203             public WearableExtender(Action action) {
2204                 Bundle wearableBundle = action.getExtras().getBundle(EXTRA_WEARABLE_EXTENSIONS);
2205                 if (wearableBundle != null) {
2206                     mFlags = wearableBundle.getInt(KEY_FLAGS, DEFAULT_FLAGS);
2207                     mInProgressLabel = wearableBundle.getCharSequence(KEY_IN_PROGRESS_LABEL);
2208                     mConfirmLabel = wearableBundle.getCharSequence(KEY_CONFIRM_LABEL);
2209                     mCancelLabel = wearableBundle.getCharSequence(KEY_CANCEL_LABEL);
2210                 }
2211             }
2212 
2213             /**
2214              * Apply wearable extensions to a notification action that is being built. This is
2215              * typically called by the {@link android.app.Notification.Action.Builder#extend}
2216              * method of {@link android.app.Notification.Action.Builder}.
2217              */
2218             @Override
extend(Action.Builder builder)2219             public Action.Builder extend(Action.Builder builder) {
2220                 Bundle wearableBundle = new Bundle();
2221 
2222                 if (mFlags != DEFAULT_FLAGS) {
2223                     wearableBundle.putInt(KEY_FLAGS, mFlags);
2224                 }
2225                 if (mInProgressLabel != null) {
2226                     wearableBundle.putCharSequence(KEY_IN_PROGRESS_LABEL, mInProgressLabel);
2227                 }
2228                 if (mConfirmLabel != null) {
2229                     wearableBundle.putCharSequence(KEY_CONFIRM_LABEL, mConfirmLabel);
2230                 }
2231                 if (mCancelLabel != null) {
2232                     wearableBundle.putCharSequence(KEY_CANCEL_LABEL, mCancelLabel);
2233                 }
2234 
2235                 builder.getExtras().putBundle(EXTRA_WEARABLE_EXTENSIONS, wearableBundle);
2236                 return builder;
2237             }
2238 
2239             @Override
clone()2240             public WearableExtender clone() {
2241                 WearableExtender that = new WearableExtender();
2242                 that.mFlags = this.mFlags;
2243                 that.mInProgressLabel = this.mInProgressLabel;
2244                 that.mConfirmLabel = this.mConfirmLabel;
2245                 that.mCancelLabel = this.mCancelLabel;
2246                 return that;
2247             }
2248 
2249             /**
2250              * Set whether this action is available when the wearable device is not connected to
2251              * a companion device. The user can still trigger this action when the wearable device is
2252              * offline, but a visual hint will indicate that the action may not be available.
2253              * Defaults to true.
2254              */
setAvailableOffline(boolean availableOffline)2255             public WearableExtender setAvailableOffline(boolean availableOffline) {
2256                 setFlag(FLAG_AVAILABLE_OFFLINE, availableOffline);
2257                 return this;
2258             }
2259 
2260             /**
2261              * Get whether this action is available when the wearable device is not connected to
2262              * a companion device. The user can still trigger this action when the wearable device is
2263              * offline, but a visual hint will indicate that the action may not be available.
2264              * Defaults to true.
2265              */
isAvailableOffline()2266             public boolean isAvailableOffline() {
2267                 return (mFlags & FLAG_AVAILABLE_OFFLINE) != 0;
2268             }
2269 
setFlag(int mask, boolean value)2270             private void setFlag(int mask, boolean value) {
2271                 if (value) {
2272                     mFlags |= mask;
2273                 } else {
2274                     mFlags &= ~mask;
2275                 }
2276             }
2277 
2278             /**
2279              * Set a label to display while the wearable is preparing to automatically execute the
2280              * action. This is usually a 'ing' verb ending in ellipsis like "Sending..."
2281              *
2282              * @param label the label to display while the action is being prepared to execute
2283              * @return this object for method chaining
2284              */
2285             @Deprecated
setInProgressLabel(CharSequence label)2286             public WearableExtender setInProgressLabel(CharSequence label) {
2287                 mInProgressLabel = label;
2288                 return this;
2289             }
2290 
2291             /**
2292              * Get the label to display while the wearable is preparing to automatically execute
2293              * the action. This is usually a 'ing' verb ending in ellipsis like "Sending..."
2294              *
2295              * @return the label to display while the action is being prepared to execute
2296              */
2297             @Deprecated
getInProgressLabel()2298             public CharSequence getInProgressLabel() {
2299                 return mInProgressLabel;
2300             }
2301 
2302             /**
2303              * Set a label to display to confirm that the action should be executed.
2304              * This is usually an imperative verb like "Send".
2305              *
2306              * @param label the label to confirm the action should be executed
2307              * @return this object for method chaining
2308              */
2309             @Deprecated
setConfirmLabel(CharSequence label)2310             public WearableExtender setConfirmLabel(CharSequence label) {
2311                 mConfirmLabel = label;
2312                 return this;
2313             }
2314 
2315             /**
2316              * Get the label to display to confirm that the action should be executed.
2317              * This is usually an imperative verb like "Send".
2318              *
2319              * @return the label to confirm the action should be executed
2320              */
2321             @Deprecated
getConfirmLabel()2322             public CharSequence getConfirmLabel() {
2323                 return mConfirmLabel;
2324             }
2325 
2326             /**
2327              * Set a label to display to cancel the action.
2328              * This is usually an imperative verb, like "Cancel".
2329              *
2330              * @param label the label to display to cancel the action
2331              * @return this object for method chaining
2332              */
2333             @Deprecated
setCancelLabel(CharSequence label)2334             public WearableExtender setCancelLabel(CharSequence label) {
2335                 mCancelLabel = label;
2336                 return this;
2337             }
2338 
2339             /**
2340              * Get the label to display to cancel the action.
2341              * This is usually an imperative verb like "Cancel".
2342              *
2343              * @return the label to display to cancel the action
2344              */
2345             @Deprecated
getCancelLabel()2346             public CharSequence getCancelLabel() {
2347                 return mCancelLabel;
2348             }
2349 
2350             /**
2351              * Set a hint that this Action will launch an {@link Activity} directly, telling the
2352              * platform that it can generate the appropriate transitions.
2353              * @param hintLaunchesActivity {@code true} if the content intent will launch
2354              * an activity and transitions should be generated, false otherwise.
2355              * @return this object for method chaining
2356              */
setHintLaunchesActivity( boolean hintLaunchesActivity)2357             public WearableExtender setHintLaunchesActivity(
2358                     boolean hintLaunchesActivity) {
2359                 setFlag(FLAG_HINT_LAUNCHES_ACTIVITY, hintLaunchesActivity);
2360                 return this;
2361             }
2362 
2363             /**
2364              * Get a hint that this Action will launch an {@link Activity} directly, telling the
2365              * platform that it can generate the appropriate transitions
2366              * @return {@code true} if the content intent will launch an activity and transitions
2367              * should be generated, false otherwise. The default value is {@code false} if this was
2368              * never set.
2369              */
getHintLaunchesActivity()2370             public boolean getHintLaunchesActivity() {
2371                 return (mFlags & FLAG_HINT_LAUNCHES_ACTIVITY) != 0;
2372             }
2373 
2374             /**
2375              * Set a hint that this Action should be displayed inline.
2376              *
2377              * @param hintDisplayInline {@code true} if action should be displayed inline, false
2378              *        otherwise
2379              * @return this object for method chaining
2380              */
setHintDisplayActionInline( boolean hintDisplayInline)2381             public WearableExtender setHintDisplayActionInline(
2382                     boolean hintDisplayInline) {
2383                 setFlag(FLAG_HINT_DISPLAY_INLINE, hintDisplayInline);
2384                 return this;
2385             }
2386 
2387             /**
2388              * Get a hint that this Action should be displayed inline.
2389              *
2390              * @return {@code true} if the Action should be displayed inline, {@code false}
2391              *         otherwise. The default value is {@code false} if this was never set.
2392              */
getHintDisplayActionInline()2393             public boolean getHintDisplayActionInline() {
2394                 return (mFlags & FLAG_HINT_DISPLAY_INLINE) != 0;
2395             }
2396         }
2397 
2398         /**
2399          * Provides meaning to an {@link Action} that hints at what the associated
2400          * {@link PendingIntent} will do. For example, an {@link Action} with a
2401          * {@link PendingIntent} that replies to a text message notification may have the
2402          * {@link #SEMANTIC_ACTION_REPLY} {@code SemanticAction} set within it.
2403          *
2404          * @hide
2405          */
2406         @IntDef(prefix = { "SEMANTIC_ACTION_" }, value = {
2407                 SEMANTIC_ACTION_NONE,
2408                 SEMANTIC_ACTION_REPLY,
2409                 SEMANTIC_ACTION_MARK_AS_READ,
2410                 SEMANTIC_ACTION_MARK_AS_UNREAD,
2411                 SEMANTIC_ACTION_DELETE,
2412                 SEMANTIC_ACTION_ARCHIVE,
2413                 SEMANTIC_ACTION_MUTE,
2414                 SEMANTIC_ACTION_UNMUTE,
2415                 SEMANTIC_ACTION_THUMBS_UP,
2416                 SEMANTIC_ACTION_THUMBS_DOWN,
2417                 SEMANTIC_ACTION_CALL,
2418                 SEMANTIC_ACTION_MARK_CONVERSATION_AS_PRIORITY,
2419                 SEMANTIC_ACTION_CONVERSATION_IS_PHISHING
2420         })
2421         @Retention(RetentionPolicy.SOURCE)
2422         public @interface SemanticAction {}
2423     }
2424 
2425     /**
2426      * Array of all {@link Action} structures attached to this notification by
2427      * {@link Builder#addAction(int, CharSequence, PendingIntent)}. Mostly useful for instances of
2428      * {@link android.service.notification.NotificationListenerService} that provide an alternative
2429      * interface for invoking actions.
2430      */
2431     public Action[] actions;
2432 
2433     /**
2434      * Replacement version of this notification whose content will be shown
2435      * in an insecure context such as atop a secure keyguard. See {@link #visibility}
2436      * and {@link #VISIBILITY_PUBLIC}.
2437      */
2438     public Notification publicVersion;
2439 
2440     /**
2441      * Constructs a Notification object with default values.
2442      * You might want to consider using {@link Builder} instead.
2443      */
Notification()2444     public Notification()
2445     {
2446         this.when = System.currentTimeMillis();
2447         this.creationTime = System.currentTimeMillis();
2448         this.priority = PRIORITY_DEFAULT;
2449     }
2450 
2451     /**
2452      * @hide
2453      */
2454     @UnsupportedAppUsage
Notification(Context context, int icon, CharSequence tickerText, long when, CharSequence contentTitle, CharSequence contentText, Intent contentIntent)2455     public Notification(Context context, int icon, CharSequence tickerText, long when,
2456             CharSequence contentTitle, CharSequence contentText, Intent contentIntent)
2457     {
2458         new Builder(context)
2459                 .setWhen(when)
2460                 .setSmallIcon(icon)
2461                 .setTicker(tickerText)
2462                 .setContentTitle(contentTitle)
2463                 .setContentText(contentText)
2464                 .setContentIntent(PendingIntent.getActivity(
2465                         context, 0, contentIntent, PendingIntent.FLAG_MUTABLE))
2466                 .buildInto(this);
2467     }
2468 
2469     /**
2470      * Constructs a Notification object with the information needed to
2471      * have a status bar icon without the standard expanded view.
2472      *
2473      * @param icon          The resource id of the icon to put in the status bar.
2474      * @param tickerText    The text that flows by in the status bar when the notification first
2475      *                      activates.
2476      * @param when          The time to show in the time field.  In the System.currentTimeMillis
2477      *                      timebase.
2478      *
2479      * @deprecated Use {@link Builder} instead.
2480      */
2481     @Deprecated
Notification(int icon, CharSequence tickerText, long when)2482     public Notification(int icon, CharSequence tickerText, long when)
2483     {
2484         this.icon = icon;
2485         this.tickerText = tickerText;
2486         this.when = when;
2487         this.creationTime = System.currentTimeMillis();
2488     }
2489 
2490     /**
2491      * Unflatten the notification from a parcel.
2492      */
2493     @SuppressWarnings("unchecked")
Notification(Parcel parcel)2494     public Notification(Parcel parcel) {
2495         // IMPORTANT: Add unmarshaling code in readFromParcel as the pending
2496         // intents in extras are always written as the last entry.
2497         readFromParcelImpl(parcel);
2498         // Must be read last!
2499         allPendingIntents = (ArraySet<PendingIntent>) parcel.readArraySet(null);
2500     }
2501 
readFromParcelImpl(Parcel parcel)2502     private void readFromParcelImpl(Parcel parcel)
2503     {
2504         int version = parcel.readInt();
2505 
2506         mAllowlistToken = parcel.readStrongBinder();
2507         if (mAllowlistToken == null) {
2508             mAllowlistToken = processAllowlistToken;
2509         }
2510         // Propagate this token to all pending intents that are unmarshalled from the parcel.
2511         parcel.setClassCookie(PendingIntent.class, mAllowlistToken);
2512 
2513         when = parcel.readLong();
2514         creationTime = parcel.readLong();
2515         if (parcel.readInt() != 0) {
2516             mSmallIcon = Icon.CREATOR.createFromParcel(parcel);
2517             if (mSmallIcon.getType() == Icon.TYPE_RESOURCE) {
2518                 icon = mSmallIcon.getResId();
2519             }
2520         }
2521         number = parcel.readInt();
2522         if (parcel.readInt() != 0) {
2523             contentIntent = PendingIntent.CREATOR.createFromParcel(parcel);
2524         }
2525         if (parcel.readInt() != 0) {
2526             deleteIntent = PendingIntent.CREATOR.createFromParcel(parcel);
2527         }
2528         if (parcel.readInt() != 0) {
2529             tickerText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
2530         }
2531         if (parcel.readInt() != 0) {
2532             tickerView = RemoteViews.CREATOR.createFromParcel(parcel);
2533         }
2534         if (parcel.readInt() != 0) {
2535             contentView = RemoteViews.CREATOR.createFromParcel(parcel);
2536         }
2537         if (parcel.readInt() != 0) {
2538             mLargeIcon = Icon.CREATOR.createFromParcel(parcel);
2539         }
2540         defaults = parcel.readInt();
2541         flags = parcel.readInt();
2542         if (parcel.readInt() != 0) {
2543             sound = Uri.CREATOR.createFromParcel(parcel);
2544         }
2545 
2546         audioStreamType = parcel.readInt();
2547         if (parcel.readInt() != 0) {
2548             audioAttributes = AudioAttributes.CREATOR.createFromParcel(parcel);
2549         }
2550         vibrate = parcel.createLongArray();
2551         ledARGB = parcel.readInt();
2552         ledOnMS = parcel.readInt();
2553         ledOffMS = parcel.readInt();
2554         iconLevel = parcel.readInt();
2555 
2556         if (parcel.readInt() != 0) {
2557             fullScreenIntent = PendingIntent.CREATOR.createFromParcel(parcel);
2558         }
2559 
2560         priority = parcel.readInt();
2561 
2562         category = parcel.readString8();
2563 
2564         mGroupKey = parcel.readString8();
2565 
2566         mSortKey = parcel.readString8();
2567 
2568         extras = Bundle.setDefusable(parcel.readBundle(), true); // may be null
2569         fixDuplicateExtras();
2570 
2571         actions = parcel.createTypedArray(Action.CREATOR); // may be null
2572 
2573         if (parcel.readInt() != 0) {
2574             bigContentView = RemoteViews.CREATOR.createFromParcel(parcel);
2575         }
2576 
2577         if (parcel.readInt() != 0) {
2578             headsUpContentView = RemoteViews.CREATOR.createFromParcel(parcel);
2579         }
2580 
2581         visibility = parcel.readInt();
2582 
2583         if (parcel.readInt() != 0) {
2584             publicVersion = Notification.CREATOR.createFromParcel(parcel);
2585         }
2586 
2587         color = parcel.readInt();
2588 
2589         if (parcel.readInt() != 0) {
2590             mChannelId = parcel.readString8();
2591         }
2592         mTimeout = parcel.readLong();
2593 
2594         if (parcel.readInt() != 0) {
2595             mShortcutId = parcel.readString8();
2596         }
2597 
2598         if (parcel.readInt() != 0) {
2599             mLocusId = LocusId.CREATOR.createFromParcel(parcel);
2600         }
2601 
2602         mBadgeIcon = parcel.readInt();
2603 
2604         if (parcel.readInt() != 0) {
2605             mSettingsText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
2606         }
2607 
2608         mGroupAlertBehavior = parcel.readInt();
2609         if (parcel.readInt() != 0) {
2610             mBubbleMetadata = BubbleMetadata.CREATOR.createFromParcel(parcel);
2611         }
2612 
2613         mAllowSystemGeneratedContextualActions = parcel.readBoolean();
2614 
2615         mFgsDeferBehavior = parcel.readInt();
2616     }
2617 
2618     @Override
clone()2619     public Notification clone() {
2620         Notification that = new Notification();
2621         cloneInto(that, true);
2622         return that;
2623     }
2624 
2625     /**
2626      * Copy all (or if heavy is false, all except Bitmaps and RemoteViews) members
2627      * of this into that.
2628      * @hide
2629      */
cloneInto(Notification that, boolean heavy)2630     public void cloneInto(Notification that, boolean heavy) {
2631         that.mAllowlistToken = this.mAllowlistToken;
2632         that.when = this.when;
2633         that.creationTime = this.creationTime;
2634         that.mSmallIcon = this.mSmallIcon;
2635         that.number = this.number;
2636 
2637         // PendingIntents are global, so there's no reason (or way) to clone them.
2638         that.contentIntent = this.contentIntent;
2639         that.deleteIntent = this.deleteIntent;
2640         that.fullScreenIntent = this.fullScreenIntent;
2641 
2642         if (this.tickerText != null) {
2643             that.tickerText = this.tickerText.toString();
2644         }
2645         if (heavy && this.tickerView != null) {
2646             that.tickerView = this.tickerView.clone();
2647         }
2648         if (heavy && this.contentView != null) {
2649             that.contentView = this.contentView.clone();
2650         }
2651         if (heavy && this.mLargeIcon != null) {
2652             that.mLargeIcon = this.mLargeIcon;
2653         }
2654         that.iconLevel = this.iconLevel;
2655         that.sound = this.sound; // android.net.Uri is immutable
2656         that.audioStreamType = this.audioStreamType;
2657         if (this.audioAttributes != null) {
2658             that.audioAttributes = new AudioAttributes.Builder(this.audioAttributes).build();
2659         }
2660 
2661         final long[] vibrate = this.vibrate;
2662         if (vibrate != null) {
2663             final int N = vibrate.length;
2664             final long[] vib = that.vibrate = new long[N];
2665             System.arraycopy(vibrate, 0, vib, 0, N);
2666         }
2667 
2668         that.ledARGB = this.ledARGB;
2669         that.ledOnMS = this.ledOnMS;
2670         that.ledOffMS = this.ledOffMS;
2671         that.defaults = this.defaults;
2672 
2673         that.flags = this.flags;
2674 
2675         that.priority = this.priority;
2676 
2677         that.category = this.category;
2678 
2679         that.mGroupKey = this.mGroupKey;
2680 
2681         that.mSortKey = this.mSortKey;
2682 
2683         if (this.extras != null) {
2684             try {
2685                 that.extras = new Bundle(this.extras);
2686                 // will unparcel
2687                 that.extras.size();
2688             } catch (BadParcelableException e) {
2689                 Log.e(TAG, "could not unparcel extras from notification: " + this, e);
2690                 that.extras = null;
2691             }
2692         }
2693 
2694         if (!ArrayUtils.isEmpty(allPendingIntents)) {
2695             that.allPendingIntents = new ArraySet<>(allPendingIntents);
2696         }
2697 
2698         if (this.actions != null) {
2699             that.actions = new Action[this.actions.length];
2700             for(int i=0; i<this.actions.length; i++) {
2701                 if ( this.actions[i] != null) {
2702                     that.actions[i] = this.actions[i].clone();
2703                 }
2704             }
2705         }
2706 
2707         if (heavy && this.bigContentView != null) {
2708             that.bigContentView = this.bigContentView.clone();
2709         }
2710 
2711         if (heavy && this.headsUpContentView != null) {
2712             that.headsUpContentView = this.headsUpContentView.clone();
2713         }
2714 
2715         that.visibility = this.visibility;
2716 
2717         if (this.publicVersion != null) {
2718             that.publicVersion = new Notification();
2719             this.publicVersion.cloneInto(that.publicVersion, heavy);
2720         }
2721 
2722         that.color = this.color;
2723 
2724         that.mChannelId = this.mChannelId;
2725         that.mTimeout = this.mTimeout;
2726         that.mShortcutId = this.mShortcutId;
2727         that.mLocusId = this.mLocusId;
2728         that.mBadgeIcon = this.mBadgeIcon;
2729         that.mSettingsText = this.mSettingsText;
2730         that.mGroupAlertBehavior = this.mGroupAlertBehavior;
2731         that.mFgsDeferBehavior = this.mFgsDeferBehavior;
2732         that.mBubbleMetadata = this.mBubbleMetadata;
2733         that.mAllowSystemGeneratedContextualActions = this.mAllowSystemGeneratedContextualActions;
2734 
2735         if (!heavy) {
2736             that.lightenPayload(); // will clean out extras
2737         }
2738     }
2739 
visitIconUri(@onNull Consumer<Uri> visitor, @Nullable Icon icon)2740     private static void visitIconUri(@NonNull Consumer<Uri> visitor, @Nullable Icon icon) {
2741         if (icon == null) return;
2742         final int iconType = icon.getType();
2743         if (iconType == TYPE_URI || iconType == TYPE_URI_ADAPTIVE_BITMAP) {
2744             visitor.accept(icon.getUri());
2745         }
2746     }
2747 
2748     /**
2749      * Note all {@link Uri} that are referenced internally, with the expectation
2750      * that Uri permission grants will need to be issued to ensure the recipient
2751      * of this object is able to render its contents.
2752      *
2753      * @hide
2754      */
visitUris(@onNull Consumer<Uri> visitor)2755     public void visitUris(@NonNull Consumer<Uri> visitor) {
2756         visitor.accept(sound);
2757 
2758         if (tickerView != null) tickerView.visitUris(visitor);
2759         if (contentView != null) contentView.visitUris(visitor);
2760         if (bigContentView != null) bigContentView.visitUris(visitor);
2761         if (headsUpContentView != null) headsUpContentView.visitUris(visitor);
2762 
2763         visitIconUri(visitor, mSmallIcon);
2764         visitIconUri(visitor, mLargeIcon);
2765 
2766         if (actions != null) {
2767             for (Action action : actions) {
2768                 visitIconUri(visitor, action.getIcon());
2769             }
2770         }
2771 
2772         if (extras != null) {
2773             visitIconUri(visitor, extras.getParcelable(EXTRA_LARGE_ICON_BIG));
2774             visitIconUri(visitor, extras.getParcelable(EXTRA_PICTURE_ICON));
2775 
2776             // NOTE: The documentation of EXTRA_AUDIO_CONTENTS_URI explicitly says that it is a
2777             // String representation of a Uri, but the previous implementation (and unit test) of
2778             // this method has always treated it as a Uri object. Given the inconsistency,
2779             // supporting both going forward is the safest choice.
2780             Object audioContentsUri = extras.get(EXTRA_AUDIO_CONTENTS_URI);
2781             if (audioContentsUri instanceof Uri) {
2782                 visitor.accept((Uri) audioContentsUri);
2783             } else if (audioContentsUri instanceof String) {
2784                 visitor.accept(Uri.parse((String) audioContentsUri));
2785             }
2786 
2787             if (extras.containsKey(EXTRA_BACKGROUND_IMAGE_URI)) {
2788                 visitor.accept(Uri.parse(extras.getString(EXTRA_BACKGROUND_IMAGE_URI)));
2789             }
2790 
2791             ArrayList<Person> people = extras.getParcelableArrayList(EXTRA_PEOPLE_LIST);
2792             if (people != null && !people.isEmpty()) {
2793                 for (Person p : people) {
2794                     visitor.accept(p.getIconUri());
2795                 }
2796             }
2797 
2798             final Person person = extras.getParcelable(EXTRA_MESSAGING_PERSON);
2799             if (person != null) {
2800                 visitor.accept(person.getIconUri());
2801             }
2802         }
2803 
2804         if (isStyle(MessagingStyle.class) && extras != null) {
2805             final Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES);
2806             if (!ArrayUtils.isEmpty(messages)) {
2807                 for (MessagingStyle.Message message : MessagingStyle.Message
2808                         .getMessagesFromBundleArray(messages)) {
2809                     visitor.accept(message.getDataUri());
2810 
2811                     Person senderPerson = message.getSenderPerson();
2812                     if (senderPerson != null) {
2813                         visitor.accept(senderPerson.getIconUri());
2814                     }
2815                 }
2816             }
2817 
2818             final Parcelable[] historic = extras.getParcelableArray(EXTRA_HISTORIC_MESSAGES);
2819             if (!ArrayUtils.isEmpty(historic)) {
2820                 for (MessagingStyle.Message message : MessagingStyle.Message
2821                         .getMessagesFromBundleArray(historic)) {
2822                     visitor.accept(message.getDataUri());
2823 
2824                     Person senderPerson = message.getSenderPerson();
2825                     if (senderPerson != null) {
2826                         visitor.accept(senderPerson.getIconUri());
2827                     }
2828                 }
2829             }
2830         }
2831 
2832         if (mBubbleMetadata != null) {
2833             visitIconUri(visitor, mBubbleMetadata.getIcon());
2834         }
2835     }
2836 
2837     /**
2838      * Removes heavyweight parts of the Notification object for archival or for sending to
2839      * listeners when the full contents are not necessary.
2840      * @hide
2841      */
lightenPayload()2842     public final void lightenPayload() {
2843         tickerView = null;
2844         contentView = null;
2845         bigContentView = null;
2846         headsUpContentView = null;
2847         mLargeIcon = null;
2848         if (extras != null && !extras.isEmpty()) {
2849             final Set<String> keyset = extras.keySet();
2850             final int N = keyset.size();
2851             final String[] keys = keyset.toArray(new String[N]);
2852             for (int i=0; i<N; i++) {
2853                 final String key = keys[i];
2854                 if (TvExtender.EXTRA_TV_EXTENDER.equals(key)) {
2855                     continue;
2856                 }
2857                 final Object obj = extras.get(key);
2858                 if (obj != null &&
2859                     (  obj instanceof Parcelable
2860                     || obj instanceof Parcelable[]
2861                     || obj instanceof SparseArray
2862                     || obj instanceof ArrayList)) {
2863                     extras.remove(key);
2864                 }
2865             }
2866         }
2867     }
2868 
2869     /**
2870      * Make sure this CharSequence is safe to put into a bundle, which basically
2871      * means it had better not be some custom Parcelable implementation.
2872      * @hide
2873      */
safeCharSequence(CharSequence cs)2874     public static CharSequence safeCharSequence(CharSequence cs) {
2875         if (cs == null) return cs;
2876         if (cs.length() > MAX_CHARSEQUENCE_LENGTH) {
2877             cs = cs.subSequence(0, MAX_CHARSEQUENCE_LENGTH);
2878         }
2879         if (cs instanceof Parcelable) {
2880             Log.e(TAG, "warning: " + cs.getClass().getCanonicalName()
2881                     + " instance is a custom Parcelable and not allowed in Notification");
2882             return cs.toString();
2883         }
2884         return removeTextSizeSpans(cs);
2885     }
2886 
removeTextSizeSpans(CharSequence charSequence)2887     private static CharSequence removeTextSizeSpans(CharSequence charSequence) {
2888         if (charSequence instanceof Spanned) {
2889             Spanned ss = (Spanned) charSequence;
2890             Object[] spans = ss.getSpans(0, ss.length(), Object.class);
2891             SpannableStringBuilder builder = new SpannableStringBuilder(ss.toString());
2892             for (Object span : spans) {
2893                 Object resultSpan = span;
2894                 if (resultSpan instanceof CharacterStyle) {
2895                     resultSpan = ((CharacterStyle) span).getUnderlying();
2896                 }
2897                 if (resultSpan instanceof TextAppearanceSpan) {
2898                     TextAppearanceSpan originalSpan = (TextAppearanceSpan) resultSpan;
2899                     resultSpan = new TextAppearanceSpan(
2900                             originalSpan.getFamily(),
2901                             originalSpan.getTextStyle(),
2902                             -1,
2903                             originalSpan.getTextColor(),
2904                             originalSpan.getLinkTextColor());
2905                 } else if (resultSpan instanceof RelativeSizeSpan
2906                         || resultSpan instanceof AbsoluteSizeSpan) {
2907                     continue;
2908                 } else {
2909                     resultSpan = span;
2910                 }
2911                 builder.setSpan(resultSpan, ss.getSpanStart(span), ss.getSpanEnd(span),
2912                         ss.getSpanFlags(span));
2913             }
2914             return builder;
2915         }
2916         return charSequence;
2917     }
2918 
describeContents()2919     public int describeContents() {
2920         return 0;
2921     }
2922 
2923     /**
2924      * Flatten this notification into a parcel.
2925      */
writeToParcel(Parcel parcel, int flags)2926     public void writeToParcel(Parcel parcel, int flags) {
2927         // We need to mark all pending intents getting into the notification
2928         // system as being put there to later allow the notification ranker
2929         // to launch them and by doing so add the app to the battery saver white
2930         // list for a short period of time. The problem is that the system
2931         // cannot look into the extras as there may be parcelables there that
2932         // the platform does not know how to handle. To go around that we have
2933         // an explicit list of the pending intents in the extras bundle.
2934         final boolean collectPendingIntents = (allPendingIntents == null);
2935         if (collectPendingIntents) {
2936             PendingIntent.setOnMarshaledListener(
2937                     (PendingIntent intent, Parcel out, int outFlags) -> {
2938                 if (parcel == out) {
2939                     synchronized (this) {
2940                         if (allPendingIntents == null) {
2941                             allPendingIntents = new ArraySet<>();
2942                         }
2943                         allPendingIntents.add(intent);
2944                     }
2945                 }
2946             });
2947         }
2948         try {
2949             // IMPORTANT: Add marshaling code in writeToParcelImpl as we
2950             // want to intercept all pending events written to the parcel.
2951             writeToParcelImpl(parcel, flags);
2952             synchronized (this) {
2953                 // Must be written last!
2954                 parcel.writeArraySet(allPendingIntents);
2955             }
2956         } finally {
2957             if (collectPendingIntents) {
2958                 PendingIntent.setOnMarshaledListener(null);
2959             }
2960         }
2961     }
2962 
writeToParcelImpl(Parcel parcel, int flags)2963     private void writeToParcelImpl(Parcel parcel, int flags) {
2964         parcel.writeInt(1);
2965 
2966         parcel.writeStrongBinder(mAllowlistToken);
2967         parcel.writeLong(when);
2968         parcel.writeLong(creationTime);
2969         if (mSmallIcon == null && icon != 0) {
2970             // you snuck an icon in here without using the builder; let's try to keep it
2971             mSmallIcon = Icon.createWithResource("", icon);
2972         }
2973         if (mSmallIcon != null) {
2974             parcel.writeInt(1);
2975             mSmallIcon.writeToParcel(parcel, 0);
2976         } else {
2977             parcel.writeInt(0);
2978         }
2979         parcel.writeInt(number);
2980         if (contentIntent != null) {
2981             parcel.writeInt(1);
2982             contentIntent.writeToParcel(parcel, 0);
2983         } else {
2984             parcel.writeInt(0);
2985         }
2986         if (deleteIntent != null) {
2987             parcel.writeInt(1);
2988             deleteIntent.writeToParcel(parcel, 0);
2989         } else {
2990             parcel.writeInt(0);
2991         }
2992         if (tickerText != null) {
2993             parcel.writeInt(1);
2994             TextUtils.writeToParcel(tickerText, parcel, flags);
2995         } else {
2996             parcel.writeInt(0);
2997         }
2998         if (tickerView != null) {
2999             parcel.writeInt(1);
3000             tickerView.writeToParcel(parcel, 0);
3001         } else {
3002             parcel.writeInt(0);
3003         }
3004         if (contentView != null) {
3005             parcel.writeInt(1);
3006             contentView.writeToParcel(parcel, 0);
3007         } else {
3008             parcel.writeInt(0);
3009         }
3010         if (mLargeIcon == null && largeIcon != null) {
3011             // you snuck an icon in here without using the builder; let's try to keep it
3012             mLargeIcon = Icon.createWithBitmap(largeIcon);
3013         }
3014         if (mLargeIcon != null) {
3015             parcel.writeInt(1);
3016             mLargeIcon.writeToParcel(parcel, 0);
3017         } else {
3018             parcel.writeInt(0);
3019         }
3020 
3021         parcel.writeInt(defaults);
3022         parcel.writeInt(this.flags);
3023 
3024         if (sound != null) {
3025             parcel.writeInt(1);
3026             sound.writeToParcel(parcel, 0);
3027         } else {
3028             parcel.writeInt(0);
3029         }
3030         parcel.writeInt(audioStreamType);
3031 
3032         if (audioAttributes != null) {
3033             parcel.writeInt(1);
3034             audioAttributes.writeToParcel(parcel, 0);
3035         } else {
3036             parcel.writeInt(0);
3037         }
3038 
3039         parcel.writeLongArray(vibrate);
3040         parcel.writeInt(ledARGB);
3041         parcel.writeInt(ledOnMS);
3042         parcel.writeInt(ledOffMS);
3043         parcel.writeInt(iconLevel);
3044 
3045         if (fullScreenIntent != null) {
3046             parcel.writeInt(1);
3047             fullScreenIntent.writeToParcel(parcel, 0);
3048         } else {
3049             parcel.writeInt(0);
3050         }
3051 
3052         parcel.writeInt(priority);
3053 
3054         parcel.writeString8(category);
3055 
3056         parcel.writeString8(mGroupKey);
3057 
3058         parcel.writeString8(mSortKey);
3059 
3060         parcel.writeBundle(extras); // null ok
3061 
3062         parcel.writeTypedArray(actions, 0); // null ok
3063 
3064         if (bigContentView != null) {
3065             parcel.writeInt(1);
3066             bigContentView.writeToParcel(parcel, 0);
3067         } else {
3068             parcel.writeInt(0);
3069         }
3070 
3071         if (headsUpContentView != null) {
3072             parcel.writeInt(1);
3073             headsUpContentView.writeToParcel(parcel, 0);
3074         } else {
3075             parcel.writeInt(0);
3076         }
3077 
3078         parcel.writeInt(visibility);
3079 
3080         if (publicVersion != null) {
3081             parcel.writeInt(1);
3082             publicVersion.writeToParcel(parcel, 0);
3083         } else {
3084             parcel.writeInt(0);
3085         }
3086 
3087         parcel.writeInt(color);
3088 
3089         if (mChannelId != null) {
3090             parcel.writeInt(1);
3091             parcel.writeString8(mChannelId);
3092         } else {
3093             parcel.writeInt(0);
3094         }
3095         parcel.writeLong(mTimeout);
3096 
3097         if (mShortcutId != null) {
3098             parcel.writeInt(1);
3099             parcel.writeString8(mShortcutId);
3100         } else {
3101             parcel.writeInt(0);
3102         }
3103 
3104         if (mLocusId != null) {
3105             parcel.writeInt(1);
3106             mLocusId.writeToParcel(parcel, 0);
3107         } else {
3108             parcel.writeInt(0);
3109         }
3110 
3111         parcel.writeInt(mBadgeIcon);
3112 
3113         if (mSettingsText != null) {
3114             parcel.writeInt(1);
3115             TextUtils.writeToParcel(mSettingsText, parcel, flags);
3116         } else {
3117             parcel.writeInt(0);
3118         }
3119 
3120         parcel.writeInt(mGroupAlertBehavior);
3121 
3122         if (mBubbleMetadata != null) {
3123             parcel.writeInt(1);
3124             mBubbleMetadata.writeToParcel(parcel, 0);
3125         } else {
3126             parcel.writeInt(0);
3127         }
3128 
3129         parcel.writeBoolean(mAllowSystemGeneratedContextualActions);
3130 
3131         parcel.writeInt(mFgsDeferBehavior);
3132 
3133         // mUsesStandardHeader is not written because it should be recomputed in listeners
3134     }
3135 
3136     /**
3137      * Parcelable.Creator that instantiates Notification objects
3138      */
3139     public static final @android.annotation.NonNull Parcelable.Creator<Notification> CREATOR
3140             = new Parcelable.Creator<Notification>()
3141     {
3142         public Notification createFromParcel(Parcel parcel)
3143         {
3144             return new Notification(parcel);
3145         }
3146 
3147         public Notification[] newArray(int size)
3148         {
3149             return new Notification[size];
3150         }
3151     };
3152 
3153     /**
3154      * @hide
3155      */
areActionsVisiblyDifferent(Notification first, Notification second)3156     public static boolean areActionsVisiblyDifferent(Notification first, Notification second) {
3157         Notification.Action[] firstAs = first.actions;
3158         Notification.Action[] secondAs = second.actions;
3159         if (firstAs == null && secondAs != null || firstAs != null && secondAs == null) {
3160             return true;
3161         }
3162         if (firstAs != null && secondAs != null) {
3163             if (firstAs.length != secondAs.length) {
3164                 return true;
3165             }
3166             for (int i = 0; i < firstAs.length; i++) {
3167                 if (!Objects.equals(String.valueOf(firstAs[i].title),
3168                         String.valueOf(secondAs[i].title))) {
3169                     return true;
3170                 }
3171                 RemoteInput[] firstRs = firstAs[i].getRemoteInputs();
3172                 RemoteInput[] secondRs = secondAs[i].getRemoteInputs();
3173                 if (firstRs == null) {
3174                     firstRs = new RemoteInput[0];
3175                 }
3176                 if (secondRs == null) {
3177                     secondRs = new RemoteInput[0];
3178                 }
3179                 if (firstRs.length != secondRs.length) {
3180                     return true;
3181                 }
3182                 for (int j = 0; j < firstRs.length; j++) {
3183                     if (!Objects.equals(String.valueOf(firstRs[j].getLabel()),
3184                             String.valueOf(secondRs[j].getLabel()))) {
3185                         return true;
3186                     }
3187                 }
3188             }
3189         }
3190         return false;
3191     }
3192 
3193     /**
3194      * @hide
3195      */
areStyledNotificationsVisiblyDifferent(Builder first, Builder second)3196     public static boolean areStyledNotificationsVisiblyDifferent(Builder first, Builder second) {
3197         if (first.getStyle() == null) {
3198             return second.getStyle() != null;
3199         }
3200         if (second.getStyle() == null) {
3201             return true;
3202         }
3203         return first.getStyle().areNotificationsVisiblyDifferent(second.getStyle());
3204     }
3205 
3206     /**
3207      * @hide
3208      */
areRemoteViewsChanged(Builder first, Builder second)3209     public static boolean areRemoteViewsChanged(Builder first, Builder second) {
3210         if (!Objects.equals(first.usesStandardHeader(), second.usesStandardHeader())) {
3211             return true;
3212         }
3213 
3214         if (areRemoteViewsChanged(first.mN.contentView, second.mN.contentView)) {
3215             return true;
3216         }
3217         if (areRemoteViewsChanged(first.mN.bigContentView, second.mN.bigContentView)) {
3218             return true;
3219         }
3220         if (areRemoteViewsChanged(first.mN.headsUpContentView, second.mN.headsUpContentView)) {
3221             return true;
3222         }
3223 
3224         return false;
3225     }
3226 
areRemoteViewsChanged(RemoteViews first, RemoteViews second)3227     private static boolean areRemoteViewsChanged(RemoteViews first, RemoteViews second) {
3228         if (first == null && second == null) {
3229             return false;
3230         }
3231         if (first == null && second != null || first != null && second == null) {
3232             return true;
3233         }
3234 
3235         if (!Objects.equals(first.getLayoutId(), second.getLayoutId())) {
3236             return true;
3237         }
3238 
3239         if (!Objects.equals(first.getSequenceNumber(), second.getSequenceNumber())) {
3240             return true;
3241         }
3242 
3243         return false;
3244     }
3245 
3246     /**
3247      * Parcelling creates multiple copies of objects in {@code extras}. Fix them.
3248      * <p>
3249      * For backwards compatibility {@code extras} holds some references to "real" member data such
3250      * as {@link getLargeIcon()} which is mirrored by {@link #EXTRA_LARGE_ICON}. This is mostly
3251      * fine as long as the object stays in one process.
3252      * <p>
3253      * However, once the notification goes into a parcel each reference gets marshalled separately,
3254      * wasting memory. Especially with large images on Auto and TV, this is worth fixing.
3255      */
fixDuplicateExtras()3256     private void fixDuplicateExtras() {
3257         if (extras != null) {
3258             fixDuplicateExtra(mLargeIcon, EXTRA_LARGE_ICON);
3259         }
3260     }
3261 
3262     /**
3263      * If we find an extra that's exactly the same as one of the "real" fields but refers to a
3264      * separate object, replace it with the field's version to avoid holding duplicate copies.
3265      */
fixDuplicateExtra(@ullable Parcelable original, @NonNull String extraName)3266     private void fixDuplicateExtra(@Nullable Parcelable original, @NonNull String extraName) {
3267         if (original != null && extras.getParcelable(extraName) != null) {
3268             extras.putParcelable(extraName, original);
3269         }
3270     }
3271 
3272     /**
3273      * Sets the {@link #contentView} field to be a view with the standard "Latest Event"
3274      * layout.
3275      *
3276      * <p>Uses the {@link #icon} and {@link #when} fields to set the icon and time fields
3277      * in the view.</p>
3278      * @param context       The context for your application / activity.
3279      * @param contentTitle The title that goes in the expanded entry.
3280      * @param contentText  The text that goes in the expanded entry.
3281      * @param contentIntent The intent to launch when the user clicks the expanded notification.
3282      * If this is an activity, it must include the
3283      * {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK} flag, which requires
3284      * that you take care of task management as described in the
3285      * <a href="{@docRoot}guide/topics/fundamentals/tasks-and-back-stack.html">Tasks and Back
3286      * Stack</a> document.
3287      *
3288      * @deprecated Use {@link Builder} instead.
3289      * @removed
3290      */
3291     @Deprecated
setLatestEventInfo(Context context, CharSequence contentTitle, CharSequence contentText, PendingIntent contentIntent)3292     public void setLatestEventInfo(Context context,
3293             CharSequence contentTitle, CharSequence contentText, PendingIntent contentIntent) {
3294         if (context.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1){
3295             Log.e(TAG, "setLatestEventInfo() is deprecated and you should feel deprecated.",
3296                     new Throwable());
3297         }
3298 
3299         if (context.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N) {
3300             extras.putBoolean(EXTRA_SHOW_WHEN, true);
3301         }
3302 
3303         // ensure that any information already set directly is preserved
3304         final Notification.Builder builder = new Notification.Builder(context, this);
3305 
3306         // now apply the latestEventInfo fields
3307         if (contentTitle != null) {
3308             builder.setContentTitle(contentTitle);
3309         }
3310         if (contentText != null) {
3311             builder.setContentText(contentText);
3312         }
3313         builder.setContentIntent(contentIntent);
3314 
3315         builder.build(); // callers expect this notification to be ready to use
3316     }
3317 
3318     /**
3319      * Sets the token used for background operations for the pending intents associated with this
3320      * notification.
3321      *
3322      * This token is automatically set during deserialization for you, you usually won't need to
3323      * call this unless you want to change the existing token, if any.
3324      *
3325      * @hide
3326      */
setAllowlistToken(@ullable IBinder token)3327     public void setAllowlistToken(@Nullable IBinder token) {
3328         mAllowlistToken = token;
3329     }
3330 
3331     /**
3332      * @hide
3333      */
addFieldsFromContext(Context context, Notification notification)3334     public static void addFieldsFromContext(Context context, Notification notification) {
3335         addFieldsFromContext(context.getApplicationInfo(), notification);
3336     }
3337 
3338     /**
3339      * @hide
3340      */
addFieldsFromContext(ApplicationInfo ai, Notification notification)3341     public static void addFieldsFromContext(ApplicationInfo ai, Notification notification) {
3342         notification.extras.putParcelable(EXTRA_BUILDER_APPLICATION_INFO, ai);
3343     }
3344 
3345     /**
3346      * @hide
3347      */
dumpDebug(ProtoOutputStream proto, long fieldId)3348     public void dumpDebug(ProtoOutputStream proto, long fieldId) {
3349         long token = proto.start(fieldId);
3350         proto.write(NotificationProto.CHANNEL_ID, getChannelId());
3351         proto.write(NotificationProto.HAS_TICKER_TEXT, this.tickerText != null);
3352         proto.write(NotificationProto.FLAGS, this.flags);
3353         proto.write(NotificationProto.COLOR, this.color);
3354         proto.write(NotificationProto.CATEGORY, this.category);
3355         proto.write(NotificationProto.GROUP_KEY, this.mGroupKey);
3356         proto.write(NotificationProto.SORT_KEY, this.mSortKey);
3357         if (this.actions != null) {
3358             proto.write(NotificationProto.ACTION_LENGTH, this.actions.length);
3359         }
3360         if (this.visibility >= VISIBILITY_SECRET && this.visibility <= VISIBILITY_PUBLIC) {
3361             proto.write(NotificationProto.VISIBILITY, this.visibility);
3362         }
3363         if (publicVersion != null) {
3364             publicVersion.dumpDebug(proto, NotificationProto.PUBLIC_VERSION);
3365         }
3366         proto.end(token);
3367     }
3368 
3369     @Override
toString()3370     public String toString() {
3371         StringBuilder sb = new StringBuilder();
3372         sb.append("Notification(channel=");
3373         sb.append(getChannelId());
3374         sb.append(" shortcut=");
3375         sb.append(getShortcutId());
3376         sb.append(" contentView=");
3377         if (contentView != null) {
3378             sb.append(contentView.getPackage());
3379             sb.append("/0x");
3380             sb.append(Integer.toHexString(contentView.getLayoutId()));
3381         } else {
3382             sb.append("null");
3383         }
3384         sb.append(" vibrate=");
3385         if ((this.defaults & DEFAULT_VIBRATE) != 0) {
3386             sb.append("default");
3387         } else if (this.vibrate != null) {
3388             int N = this.vibrate.length-1;
3389             sb.append("[");
3390             for (int i=0; i<N; i++) {
3391                 sb.append(this.vibrate[i]);
3392                 sb.append(',');
3393             }
3394             if (N != -1) {
3395                 sb.append(this.vibrate[N]);
3396             }
3397             sb.append("]");
3398         } else {
3399             sb.append("null");
3400         }
3401         sb.append(" sound=");
3402         if ((this.defaults & DEFAULT_SOUND) != 0) {
3403             sb.append("default");
3404         } else if (this.sound != null) {
3405             sb.append(this.sound.toString());
3406         } else {
3407             sb.append("null");
3408         }
3409         if (this.tickerText != null) {
3410             sb.append(" tick");
3411         }
3412         sb.append(" defaults=0x");
3413         sb.append(Integer.toHexString(this.defaults));
3414         sb.append(" flags=0x");
3415         sb.append(Integer.toHexString(this.flags));
3416         sb.append(String.format(" color=0x%08x", this.color));
3417         if (this.category != null) {
3418             sb.append(" category=");
3419             sb.append(this.category);
3420         }
3421         if (this.mGroupKey != null) {
3422             sb.append(" groupKey=");
3423             sb.append(this.mGroupKey);
3424         }
3425         if (this.mSortKey != null) {
3426             sb.append(" sortKey=");
3427             sb.append(this.mSortKey);
3428         }
3429         if (actions != null) {
3430             sb.append(" actions=");
3431             sb.append(actions.length);
3432         }
3433         sb.append(" vis=");
3434         sb.append(visibilityToString(this.visibility));
3435         if (this.publicVersion != null) {
3436             sb.append(" publicVersion=");
3437             sb.append(publicVersion.toString());
3438         }
3439         if (this.mLocusId != null) {
3440             sb.append(" locusId=");
3441             sb.append(this.mLocusId); // LocusId.toString() is PII safe.
3442         }
3443         sb.append(")");
3444         return sb.toString();
3445     }
3446 
3447     /**
3448      * {@hide}
3449      */
visibilityToString(int vis)3450     public static String visibilityToString(int vis) {
3451         switch (vis) {
3452             case VISIBILITY_PRIVATE:
3453                 return "PRIVATE";
3454             case VISIBILITY_PUBLIC:
3455                 return "PUBLIC";
3456             case VISIBILITY_SECRET:
3457                 return "SECRET";
3458             default:
3459                 return "UNKNOWN(" + String.valueOf(vis) + ")";
3460         }
3461     }
3462 
3463     /**
3464      * {@hide}
3465      */
priorityToString(@riority int pri)3466     public static String priorityToString(@Priority int pri) {
3467         switch (pri) {
3468             case PRIORITY_MIN:
3469                 return "MIN";
3470             case PRIORITY_LOW:
3471                 return "LOW";
3472             case PRIORITY_DEFAULT:
3473                 return "DEFAULT";
3474             case PRIORITY_HIGH:
3475                 return "HIGH";
3476             case PRIORITY_MAX:
3477                 return "MAX";
3478             default:
3479                 return "UNKNOWN(" + String.valueOf(pri) + ")";
3480         }
3481     }
3482 
3483     /**
3484      * @hide
3485      */
hasCompletedProgress()3486     public boolean hasCompletedProgress() {
3487         // not a progress notification; can't be complete
3488         if (!extras.containsKey(EXTRA_PROGRESS)
3489                 || !extras.containsKey(EXTRA_PROGRESS_MAX)) {
3490             return false;
3491         }
3492         // many apps use max 0 for 'indeterminate'; not complete
3493         if (extras.getInt(EXTRA_PROGRESS_MAX) == 0) {
3494             return false;
3495         }
3496         return extras.getInt(EXTRA_PROGRESS) == extras.getInt(EXTRA_PROGRESS_MAX);
3497     }
3498 
3499     /** @removed */
3500     @Deprecated
getChannel()3501     public String getChannel() {
3502         return mChannelId;
3503     }
3504 
3505     /**
3506      * Returns the id of the channel this notification posts to.
3507      */
getChannelId()3508     public String getChannelId() {
3509         return mChannelId;
3510     }
3511 
3512     /** @removed */
3513     @Deprecated
getTimeout()3514     public long getTimeout() {
3515         return mTimeout;
3516     }
3517 
3518     /**
3519      * Returns the duration from posting after which this notification should be canceled by the
3520      * system, if it's not canceled already.
3521      */
getTimeoutAfter()3522     public long getTimeoutAfter() {
3523         return mTimeout;
3524     }
3525 
3526     /**
3527      * Returns what icon should be shown for this notification if it is being displayed in a
3528      * Launcher that supports badging. Will be one of {@link #BADGE_ICON_NONE},
3529      * {@link #BADGE_ICON_SMALL}, or {@link #BADGE_ICON_LARGE}.
3530      */
getBadgeIconType()3531     public int getBadgeIconType() {
3532         return mBadgeIcon;
3533     }
3534 
3535     /**
3536      * Returns the {@link ShortcutInfo#getId() id} that this notification supersedes, if any.
3537      *
3538      * <p>Used by some Launchers that display notification content to hide shortcuts that duplicate
3539      * notifications.
3540      */
getShortcutId()3541     public String getShortcutId() {
3542         return mShortcutId;
3543     }
3544 
3545     /**
3546      * Gets the {@link LocusId} associated with this notification.
3547      *
3548      * <p>Used by the device's intelligence services to correlate objects (such as
3549      * {@link ShortcutInfo} and {@link ContentCaptureContext}) that are correlated.
3550      */
3551     @Nullable
getLocusId()3552     public LocusId getLocusId() {
3553         return mLocusId;
3554     }
3555 
3556     /**
3557      * Returns the settings text provided to {@link Builder#setSettingsText(CharSequence)}.
3558      */
getSettingsText()3559     public CharSequence getSettingsText() {
3560         return mSettingsText;
3561     }
3562 
3563     /**
3564      * Returns which type of notifications in a group are responsible for audibly alerting the
3565      * user. See {@link #GROUP_ALERT_ALL}, {@link #GROUP_ALERT_CHILDREN},
3566      * {@link #GROUP_ALERT_SUMMARY}.
3567      */
getGroupAlertBehavior()3568     public @GroupAlertBehavior int getGroupAlertBehavior() {
3569         return mGroupAlertBehavior;
3570     }
3571 
3572     /**
3573      * Returns the bubble metadata that will be used to display app content in a floating window
3574      * over the existing foreground activity.
3575      */
3576     @Nullable
getBubbleMetadata()3577     public BubbleMetadata getBubbleMetadata() {
3578         return mBubbleMetadata;
3579     }
3580 
3581     /**
3582      * Sets the {@link BubbleMetadata} for this notification.
3583      * @hide
3584      */
setBubbleMetadata(BubbleMetadata data)3585     public void setBubbleMetadata(BubbleMetadata data) {
3586         mBubbleMetadata = data;
3587     }
3588 
3589     /**
3590      * Returns whether the platform is allowed (by the app developer) to generate contextual actions
3591      * for this notification.
3592      */
getAllowSystemGeneratedContextualActions()3593     public boolean getAllowSystemGeneratedContextualActions() {
3594         return mAllowSystemGeneratedContextualActions;
3595     }
3596 
3597     /**
3598      * The small icon representing this notification in the status bar and content view.
3599      *
3600      * @return the small icon representing this notification.
3601      *
3602      * @see Builder#getSmallIcon()
3603      * @see Builder#setSmallIcon(Icon)
3604      */
getSmallIcon()3605     public Icon getSmallIcon() {
3606         return mSmallIcon;
3607     }
3608 
3609     /**
3610      * Used when notifying to clean up legacy small icons.
3611      * @hide
3612      */
3613     @UnsupportedAppUsage
setSmallIcon(Icon icon)3614     public void setSmallIcon(Icon icon) {
3615         mSmallIcon = icon;
3616     }
3617 
3618     /**
3619      * The large icon shown in this notification's content view.
3620      * @see Builder#getLargeIcon()
3621      * @see Builder#setLargeIcon(Icon)
3622      */
getLargeIcon()3623     public Icon getLargeIcon() {
3624         return mLargeIcon;
3625     }
3626 
3627     /**
3628      * @hide
3629      */
3630     @UnsupportedAppUsage
isGroupSummary()3631     public boolean isGroupSummary() {
3632         return mGroupKey != null && (flags & FLAG_GROUP_SUMMARY) != 0;
3633     }
3634 
3635     /**
3636      * @hide
3637      */
3638     @UnsupportedAppUsage
isGroupChild()3639     public boolean isGroupChild() {
3640         return mGroupKey != null && (flags & FLAG_GROUP_SUMMARY) == 0;
3641     }
3642 
3643     /**
3644      * @hide
3645      */
suppressAlertingDueToGrouping()3646     public boolean suppressAlertingDueToGrouping() {
3647         if (isGroupSummary()
3648                 && getGroupAlertBehavior() == Notification.GROUP_ALERT_CHILDREN) {
3649             return true;
3650         } else if (isGroupChild()
3651                 && getGroupAlertBehavior() == Notification.GROUP_ALERT_SUMMARY) {
3652             return true;
3653         }
3654         return false;
3655     }
3656 
3657 
3658     /**
3659      * Finds and returns a remote input and its corresponding action.
3660      *
3661      * @param requiresFreeform requires the remoteinput to allow freeform or not.
3662      * @return the result pair, {@code null} if no result is found.
3663      */
3664     @Nullable
findRemoteInputActionPair(boolean requiresFreeform)3665     public Pair<RemoteInput, Action> findRemoteInputActionPair(boolean requiresFreeform) {
3666         if (actions == null) {
3667             return null;
3668         }
3669         for (Notification.Action action : actions) {
3670             if (action.getRemoteInputs() == null) {
3671                 continue;
3672             }
3673             RemoteInput resultRemoteInput = null;
3674             for (RemoteInput remoteInput : action.getRemoteInputs()) {
3675                 if (remoteInput.getAllowFreeFormInput() || !requiresFreeform) {
3676                     resultRemoteInput = remoteInput;
3677                 }
3678             }
3679             if (resultRemoteInput != null) {
3680                 return Pair.create(resultRemoteInput, action);
3681             }
3682         }
3683         return null;
3684     }
3685 
3686     /**
3687      * Returns the actions that are contextual (that is, suggested because of the content of the
3688      * notification) out of the actions in this notification.
3689      */
getContextualActions()3690     public @NonNull List<Notification.Action> getContextualActions() {
3691         if (actions == null) return Collections.emptyList();
3692 
3693         List<Notification.Action> contextualActions = new ArrayList<>();
3694         for (Notification.Action action : actions) {
3695             if (action.isContextual()) {
3696                 contextualActions.add(action);
3697             }
3698         }
3699         return contextualActions;
3700     }
3701 
3702     /**
3703      * Builder class for {@link Notification} objects.
3704      *
3705      * Provides a convenient way to set the various fields of a {@link Notification} and generate
3706      * content views using the platform's notification layout template. If your app supports
3707      * versions of Android as old as API level 4, you can instead use
3708      * {@link android.support.v4.app.NotificationCompat.Builder NotificationCompat.Builder},
3709      * available in the <a href="{@docRoot}tools/extras/support-library.html">Android Support
3710      * library</a>.
3711      *
3712      * <p>Example:
3713      *
3714      * <pre class="prettyprint">
3715      * Notification noti = new Notification.Builder(mContext)
3716      *         .setContentTitle(&quot;New mail from &quot; + sender.toString())
3717      *         .setContentText(subject)
3718      *         .setSmallIcon(R.drawable.new_mail)
3719      *         .setLargeIcon(aBitmap)
3720      *         .build();
3721      * </pre>
3722      */
3723     public static class Builder {
3724         /**
3725          * @hide
3726          */
3727         public static final String EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT =
3728                 "android.rebuild.contentViewActionCount";
3729         /**
3730          * @hide
3731          */
3732         public static final String EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT
3733                 = "android.rebuild.bigViewActionCount";
3734         /**
3735          * @hide
3736          */
3737         public static final String EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT
3738                 = "android.rebuild.hudViewActionCount";
3739 
3740         private static final boolean USE_ONLY_TITLE_IN_LOW_PRIORITY_SUMMARY =
3741                 SystemProperties.getBoolean("notifications.only_title", true);
3742 
3743         /**
3744          * The lightness difference that has to be added to the primary text color to obtain the
3745          * secondary text color when the background is light.
3746          */
3747         private static final int LIGHTNESS_TEXT_DIFFERENCE_LIGHT = 20;
3748 
3749         /**
3750          * The lightness difference that has to be added to the primary text color to obtain the
3751          * secondary text color when the background is dark.
3752          * A bit less then the above value, since it looks better on dark backgrounds.
3753          */
3754         private static final int LIGHTNESS_TEXT_DIFFERENCE_DARK = -10;
3755 
3756         private Context mContext;
3757         private Notification mN;
3758         private Bundle mUserExtras = new Bundle();
3759         private Style mStyle;
3760         @UnsupportedAppUsage
3761         private ArrayList<Action> mActions = new ArrayList<>(MAX_ACTION_BUTTONS);
3762         private ArrayList<Person> mPersonList = new ArrayList<>();
3763         private ContrastColorUtil mColorUtil;
3764         private boolean mIsLegacy;
3765         private boolean mIsLegacyInitialized;
3766 
3767         /**
3768          * Caches an instance of StandardTemplateParams. Note that this may have been used before,
3769          * so make sure to call {@link StandardTemplateParams#reset()} before using it.
3770          */
3771         StandardTemplateParams mParams = new StandardTemplateParams();
3772         Colors mColors = new Colors();
3773 
3774         private boolean mTintActionButtons;
3775         private boolean mInNightMode;
3776 
3777         /**
3778          * Constructs a new Builder with the defaults:
3779          *
3780          * @param context
3781          *            A {@link Context} that will be used by the Builder to construct the
3782          *            RemoteViews. The Context will not be held past the lifetime of this Builder
3783          *            object.
3784          * @param channelId
3785          *            The constructed Notification will be posted on this
3786          *            {@link NotificationChannel}. To use a NotificationChannel, it must first be
3787          *            created using {@link NotificationManager#createNotificationChannel}.
3788          */
Builder(Context context, String channelId)3789         public Builder(Context context, String channelId) {
3790             this(context, (Notification) null);
3791             mN.mChannelId = channelId;
3792         }
3793 
3794         /**
3795          * @deprecated use {@link #Builder(Context, String)}
3796          * instead. All posted Notifications must specify a NotificationChannel Id.
3797          */
3798         @Deprecated
Builder(Context context)3799         public Builder(Context context) {
3800             this(context, (Notification) null);
3801         }
3802 
3803         /**
3804          * @hide
3805          */
Builder(Context context, Notification toAdopt)3806         public Builder(Context context, Notification toAdopt) {
3807             mContext = context;
3808             Resources res = mContext.getResources();
3809             mTintActionButtons = res.getBoolean(R.bool.config_tintNotificationActionButtons);
3810 
3811             if (res.getBoolean(R.bool.config_enableNightMode)) {
3812                 Configuration currentConfig = res.getConfiguration();
3813                 mInNightMode = (currentConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK)
3814                         == Configuration.UI_MODE_NIGHT_YES;
3815             }
3816 
3817             if (toAdopt == null) {
3818                 mN = new Notification();
3819                 if (context.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N) {
3820                     mN.extras.putBoolean(EXTRA_SHOW_WHEN, true);
3821                 }
3822                 mN.priority = PRIORITY_DEFAULT;
3823                 mN.visibility = VISIBILITY_PRIVATE;
3824             } else {
3825                 mN = toAdopt;
3826                 if (mN.actions != null) {
3827                     Collections.addAll(mActions, mN.actions);
3828                 }
3829 
3830                 if (mN.extras.containsKey(EXTRA_PEOPLE_LIST)) {
3831                     ArrayList<Person> people = mN.extras.getParcelableArrayList(EXTRA_PEOPLE_LIST);
3832                     mPersonList.addAll(people);
3833                 }
3834 
3835                 if (mN.getSmallIcon() == null && mN.icon != 0) {
3836                     setSmallIcon(mN.icon);
3837                 }
3838 
3839                 if (mN.getLargeIcon() == null && mN.largeIcon != null) {
3840                     setLargeIcon(mN.largeIcon);
3841                 }
3842 
3843                 String templateClass = mN.extras.getString(EXTRA_TEMPLATE);
3844                 if (!TextUtils.isEmpty(templateClass)) {
3845                     final Class<? extends Style> styleClass
3846                             = getNotificationStyleClass(templateClass);
3847                     if (styleClass == null) {
3848                         Log.d(TAG, "Unknown style class: " + templateClass);
3849                     } else {
3850                         try {
3851                             final Constructor<? extends Style> ctor =
3852                                     styleClass.getDeclaredConstructor();
3853                             ctor.setAccessible(true);
3854                             final Style style = ctor.newInstance();
3855                             style.restoreFromExtras(mN.extras);
3856 
3857                             if (style != null) {
3858                                 setStyle(style);
3859                             }
3860                         } catch (Throwable t) {
3861                             Log.e(TAG, "Could not create Style", t);
3862                         }
3863                     }
3864                 }
3865             }
3866         }
3867 
getColorUtil()3868         private ContrastColorUtil getColorUtil() {
3869             if (mColorUtil == null) {
3870                 mColorUtil = ContrastColorUtil.getInstance(mContext);
3871             }
3872             return mColorUtil;
3873         }
3874 
3875         /**
3876          * From Android 11, messaging notifications (those that use {@link MessagingStyle}) that
3877          * use this method to link to a published long-lived sharing shortcut may appear in a
3878          * dedicated Conversation section of the shade and may show configuration options that
3879          * are unique to conversations. This behavior should be reserved for person to person(s)
3880          * conversations where there is a likely social obligation for an individual to respond.
3881          * <p>
3882          * For example, the following are some examples of notifications that belong in the
3883          * conversation space:
3884          * <ul>
3885          * <li>1:1 conversations between two individuals</li>
3886          * <li>Group conversations between individuals where everyone can contribute</li>
3887          * </ul>
3888          * And the following are some examples of notifications that do not belong in the
3889          * conversation space:
3890          * <ul>
3891          * <li>Advertisements from a bot (even if personal and contextualized)</li>
3892          * <li>Engagement notifications from a bot</li>
3893          * <li>Directional conversations where there is an active speaker and many passive
3894          * individuals</li>
3895          * <li>Stream / posting updates from other individuals</li>
3896          * <li>Email, document comments, or other conversation types that are not real-time</li>
3897          * </ul>
3898          * </p>
3899          *
3900          * <p>
3901          * Additionally, this method can be used for all types of notifications to mark this
3902          * notification as duplicative of a Launcher shortcut. Launchers that show badges or
3903          * notification content may then suppress the shortcut in favor of the content of this
3904          * notification.
3905          * <p>
3906          * If this notification has {@link BubbleMetadata} attached that was created with
3907          * a shortcutId a check will be performed to ensure the shortcutId supplied to bubble
3908          * metadata matches the shortcutId set here, if one was set. If the shortcutId's were
3909          * specified but do not match, an exception is thrown.
3910          *
3911          * @param shortcutId the {@link ShortcutInfo#getId() id} of the shortcut this notification
3912          *                   is linked to
3913          *
3914          * @see BubbleMetadata.Builder#Builder(String)
3915          */
3916         @NonNull
setShortcutId(String shortcutId)3917         public Builder setShortcutId(String shortcutId) {
3918             mN.mShortcutId = shortcutId;
3919             return this;
3920         }
3921 
3922         /**
3923          * Sets the {@link LocusId} associated with this notification.
3924          *
3925          * <p>This method should be called when the {@link LocusId} is used in other places (such
3926          * as {@link ShortcutInfo} and {@link ContentCaptureContext}) so the device's intelligence
3927          * services can correlate them.
3928          */
3929         @NonNull
setLocusId(@ullable LocusId locusId)3930         public Builder setLocusId(@Nullable LocusId locusId) {
3931             mN.mLocusId = locusId;
3932             return this;
3933         }
3934 
3935         /**
3936          * Sets which icon to display as a badge for this notification.
3937          *
3938          * Must be one of {@link #BADGE_ICON_NONE}, {@link #BADGE_ICON_SMALL},
3939          * {@link #BADGE_ICON_LARGE}.
3940          *
3941          * Note: This value might be ignored, for launchers that don't support badge icons.
3942          */
3943         @NonNull
setBadgeIconType(int icon)3944         public Builder setBadgeIconType(int icon) {
3945             mN.mBadgeIcon = icon;
3946             return this;
3947         }
3948 
3949         /**
3950          * Sets the group alert behavior for this notification. Use this method to mute this
3951          * notification if alerts for this notification's group should be handled by a different
3952          * notification. This is only applicable for notifications that belong to a
3953          * {@link #setGroup(String) group}. This must be called on all notifications you want to
3954          * mute. For example, if you want only the summary of your group to make noise, all
3955          * children in the group should have the group alert behavior {@link #GROUP_ALERT_SUMMARY}.
3956          *
3957          * <p> The default value is {@link #GROUP_ALERT_ALL}.</p>
3958          */
3959         @NonNull
setGroupAlertBehavior(@roupAlertBehavior int groupAlertBehavior)3960         public Builder setGroupAlertBehavior(@GroupAlertBehavior int groupAlertBehavior) {
3961             mN.mGroupAlertBehavior = groupAlertBehavior;
3962             return this;
3963         }
3964 
3965         /**
3966          * Sets the {@link BubbleMetadata} that will be used to display app content in a floating
3967          * window over the existing foreground activity.
3968          *
3969          * <p>This data will be ignored unless the notification is posted to a channel that
3970          * allows {@link NotificationChannel#canBubble() bubbles}.</p>
3971          *
3972          * <p>Notifications allowed to bubble that have valid bubble metadata will display in
3973          * collapsed state outside of the notification shade on unlocked devices. When a user
3974          * interacts with the collapsed state, the bubble intent will be invoked and displayed.</p>
3975          */
3976         @NonNull
setBubbleMetadata(@ullable BubbleMetadata data)3977         public Builder setBubbleMetadata(@Nullable BubbleMetadata data) {
3978             mN.mBubbleMetadata = data;
3979             return this;
3980         }
3981 
3982         /** @removed */
3983         @Deprecated
setChannel(String channelId)3984         public Builder setChannel(String channelId) {
3985             mN.mChannelId = channelId;
3986             return this;
3987         }
3988 
3989         /**
3990          * Specifies the channel the notification should be delivered on.
3991          */
3992         @NonNull
setChannelId(String channelId)3993         public Builder setChannelId(String channelId) {
3994             mN.mChannelId = channelId;
3995             return this;
3996         }
3997 
3998         /** @removed */
3999         @Deprecated
setTimeout(long durationMs)4000         public Builder setTimeout(long durationMs) {
4001             mN.mTimeout = durationMs;
4002             return this;
4003         }
4004 
4005         /**
4006          * Specifies a duration in milliseconds after which this notification should be canceled,
4007          * if it is not already canceled.
4008          */
4009         @NonNull
setTimeoutAfter(long durationMs)4010         public Builder setTimeoutAfter(long durationMs) {
4011             mN.mTimeout = durationMs;
4012             return this;
4013         }
4014 
4015         /**
4016          * Add a timestamp pertaining to the notification (usually the time the event occurred).
4017          *
4018          * For apps targeting {@link android.os.Build.VERSION_CODES#N} and above, this time is not
4019          * shown anymore by default and must be opted into by using
4020          * {@link android.app.Notification.Builder#setShowWhen(boolean)}
4021          *
4022          * @see Notification#when
4023          */
4024         @NonNull
setWhen(long when)4025         public Builder setWhen(long when) {
4026             mN.when = when;
4027             return this;
4028         }
4029 
4030         /**
4031          * Control whether the timestamp set with {@link #setWhen(long) setWhen} is shown
4032          * in the content view.
4033          * For apps targeting {@link android.os.Build.VERSION_CODES#N} and above, this defaults to
4034          * {@code false}. For earlier apps, the default is {@code true}.
4035          */
4036         @NonNull
setShowWhen(boolean show)4037         public Builder setShowWhen(boolean show) {
4038             mN.extras.putBoolean(EXTRA_SHOW_WHEN, show);
4039             return this;
4040         }
4041 
4042         /**
4043          * Show the {@link Notification#when} field as a stopwatch.
4044          *
4045          * Instead of presenting <code>when</code> as a timestamp, the notification will show an
4046          * automatically updating display of the minutes and seconds since <code>when</code>.
4047          *
4048          * Useful when showing an elapsed time (like an ongoing phone call).
4049          *
4050          * The counter can also be set to count down to <code>when</code> when using
4051          * {@link #setChronometerCountDown(boolean)}.
4052          *
4053          * @see android.widget.Chronometer
4054          * @see Notification#when
4055          * @see #setChronometerCountDown(boolean)
4056          */
4057         @NonNull
setUsesChronometer(boolean b)4058         public Builder setUsesChronometer(boolean b) {
4059             mN.extras.putBoolean(EXTRA_SHOW_CHRONOMETER, b);
4060             return this;
4061         }
4062 
4063         /**
4064          * Sets the Chronometer to count down instead of counting up.
4065          *
4066          * <p>This is only relevant if {@link #setUsesChronometer(boolean)} has been set to true.
4067          * If it isn't set the chronometer will count up.
4068          *
4069          * @see #setUsesChronometer(boolean)
4070          */
4071         @NonNull
setChronometerCountDown(boolean countDown)4072         public Builder setChronometerCountDown(boolean countDown) {
4073             mN.extras.putBoolean(EXTRA_CHRONOMETER_COUNT_DOWN, countDown);
4074             return this;
4075         }
4076 
4077         /**
4078          * Set the small icon resource, which will be used to represent the notification in the
4079          * status bar.
4080          *
4081 
4082          * The platform template for the expanded view will draw this icon in the left, unless a
4083          * {@link #setLargeIcon(Bitmap) large icon} has also been specified, in which case the small
4084          * icon will be moved to the right-hand side.
4085          *
4086 
4087          * @param icon
4088          *            A resource ID in the application's package of the drawable to use.
4089          * @see Notification#icon
4090          */
4091         @NonNull
setSmallIcon(@rawableRes int icon)4092         public Builder setSmallIcon(@DrawableRes int icon) {
4093             return setSmallIcon(icon != 0
4094                     ? Icon.createWithResource(mContext, icon)
4095                     : null);
4096         }
4097 
4098         /**
4099          * A variant of {@link #setSmallIcon(int) setSmallIcon(int)} that takes an additional
4100          * level parameter for when the icon is a {@link android.graphics.drawable.LevelListDrawable
4101          * LevelListDrawable}.
4102          *
4103          * @param icon A resource ID in the application's package of the drawable to use.
4104          * @param level The level to use for the icon.
4105          *
4106          * @see Notification#icon
4107          * @see Notification#iconLevel
4108          */
4109         @NonNull
setSmallIcon(@rawableRes int icon, int level)4110         public Builder setSmallIcon(@DrawableRes int icon, int level) {
4111             mN.iconLevel = level;
4112             return setSmallIcon(icon);
4113         }
4114 
4115         /**
4116          * Set the small icon, which will be used to represent the notification in the
4117          * status bar and content view (unless overridden there by a
4118          * {@link #setLargeIcon(Bitmap) large icon}).
4119          *
4120          * @param icon An Icon object to use.
4121          * @see Notification#icon
4122          */
4123         @NonNull
setSmallIcon(Icon icon)4124         public Builder setSmallIcon(Icon icon) {
4125             mN.setSmallIcon(icon);
4126             if (icon != null && icon.getType() == Icon.TYPE_RESOURCE) {
4127                 mN.icon = icon.getResId();
4128             }
4129             return this;
4130         }
4131 
4132         /**
4133          * Set the first line of text in the platform notification template.
4134          */
4135         @NonNull
setContentTitle(CharSequence title)4136         public Builder setContentTitle(CharSequence title) {
4137             mN.extras.putCharSequence(EXTRA_TITLE, safeCharSequence(title));
4138             return this;
4139         }
4140 
4141         /**
4142          * Set the second line of text in the platform notification template.
4143          */
4144         @NonNull
setContentText(CharSequence text)4145         public Builder setContentText(CharSequence text) {
4146             mN.extras.putCharSequence(EXTRA_TEXT, safeCharSequence(text));
4147             return this;
4148         }
4149 
4150         /**
4151          * This provides some additional information that is displayed in the notification. No
4152          * guarantees are given where exactly it is displayed.
4153          *
4154          * <p>This information should only be provided if it provides an essential
4155          * benefit to the understanding of the notification. The more text you provide the
4156          * less readable it becomes. For example, an email client should only provide the account
4157          * name here if more than one email account has been added.</p>
4158          *
4159          * <p>As of {@link android.os.Build.VERSION_CODES#N} this information is displayed in the
4160          * notification header area.
4161          *
4162          * On Android versions before {@link android.os.Build.VERSION_CODES#N}
4163          * this will be shown in the third line of text in the platform notification template.
4164          * You should not be using {@link #setProgress(int, int, boolean)} at the
4165          * same time on those versions; they occupy the same place.
4166          * </p>
4167          */
4168         @NonNull
setSubText(CharSequence text)4169         public Builder setSubText(CharSequence text) {
4170             mN.extras.putCharSequence(EXTRA_SUB_TEXT, safeCharSequence(text));
4171             return this;
4172         }
4173 
4174         /**
4175          * Provides text that will appear as a link to your application's settings.
4176          *
4177          * <p>This text does not appear within notification {@link Style templates} but may
4178          * appear when the user uses an affordance to learn more about the notification.
4179          * Additionally, this text will not appear unless you provide a valid link target by
4180          * handling {@link #INTENT_CATEGORY_NOTIFICATION_PREFERENCES}.
4181          *
4182          * <p>This text is meant to be concise description about what the user can customize
4183          * when they click on this link. The recommended maximum length is 40 characters.
4184          * @param text
4185          * @return
4186          */
4187         @NonNull
setSettingsText(CharSequence text)4188         public Builder setSettingsText(CharSequence text) {
4189             mN.mSettingsText = safeCharSequence(text);
4190             return this;
4191         }
4192 
4193         /**
4194          * Set the remote input history.
4195          *
4196          * This should be set to the most recent inputs that have been sent
4197          * through a {@link RemoteInput} of this Notification and cleared once the it is no
4198          * longer relevant (e.g. for chat notifications once the other party has responded).
4199          *
4200          * The most recent input must be stored at the 0 index, the second most recent at the
4201          * 1 index, etc. Note that the system will limit both how far back the inputs will be shown
4202          * and how much of each individual input is shown.
4203          *
4204          * <p>Note: The reply text will only be shown on notifications that have least one action
4205          * with a {@code RemoteInput}.</p>
4206          */
4207         @NonNull
setRemoteInputHistory(CharSequence[] text)4208         public Builder setRemoteInputHistory(CharSequence[] text) {
4209             if (text == null) {
4210                 mN.extras.putCharSequenceArray(EXTRA_REMOTE_INPUT_HISTORY, null);
4211             } else {
4212                 final int itemCount = Math.min(MAX_REPLY_HISTORY, text.length);
4213                 CharSequence[] safe = new CharSequence[itemCount];
4214                 RemoteInputHistoryItem[] items = new RemoteInputHistoryItem[itemCount];
4215                 for (int i = 0; i < itemCount; i++) {
4216                     safe[i] = safeCharSequence(text[i]);
4217                     items[i] = new RemoteInputHistoryItem(text[i]);
4218                 }
4219                 mN.extras.putCharSequenceArray(EXTRA_REMOTE_INPUT_HISTORY, safe);
4220 
4221                 // Also add these messages as structured history items.
4222                 mN.extras.putParcelableArray(EXTRA_REMOTE_INPUT_HISTORY_ITEMS, items);
4223             }
4224             return this;
4225         }
4226 
4227         /**
4228          * Set the remote input history, with support for embedding URIs and mime types for
4229          * images and other media.
4230          * @hide
4231          */
4232         @NonNull
setRemoteInputHistory(RemoteInputHistoryItem[] items)4233         public Builder setRemoteInputHistory(RemoteInputHistoryItem[] items) {
4234             if (items == null) {
4235                 mN.extras.putParcelableArray(EXTRA_REMOTE_INPUT_HISTORY_ITEMS, null);
4236             } else {
4237                 final int itemCount = Math.min(MAX_REPLY_HISTORY, items.length);
4238                 RemoteInputHistoryItem[] history = new RemoteInputHistoryItem[itemCount];
4239                 for (int i = 0; i < itemCount; i++) {
4240                     history[i] = items[i];
4241                 }
4242                 mN.extras.putParcelableArray(EXTRA_REMOTE_INPUT_HISTORY_ITEMS, history);
4243             }
4244             return this;
4245         }
4246 
4247         /**
4248          * Sets whether remote history entries view should have a spinner.
4249          * @hide
4250          */
4251         @NonNull
setShowRemoteInputSpinner(boolean showSpinner)4252         public Builder setShowRemoteInputSpinner(boolean showSpinner) {
4253             mN.extras.putBoolean(EXTRA_SHOW_REMOTE_INPUT_SPINNER, showSpinner);
4254             return this;
4255         }
4256 
4257         /**
4258          * Sets whether smart reply buttons should be hidden.
4259          * @hide
4260          */
4261         @NonNull
setHideSmartReplies(boolean hideSmartReplies)4262         public Builder setHideSmartReplies(boolean hideSmartReplies) {
4263             mN.extras.putBoolean(EXTRA_HIDE_SMART_REPLIES, hideSmartReplies);
4264             return this;
4265         }
4266 
4267         /**
4268          * Sets the number of items this notification represents. May be displayed as a badge count
4269          * for Launchers that support badging.
4270          */
4271         @NonNull
setNumber(int number)4272         public Builder setNumber(int number) {
4273             mN.number = number;
4274             return this;
4275         }
4276 
4277         /**
4278          * A small piece of additional information pertaining to this notification.
4279          *
4280          * The platform template will draw this on the last line of the notification, at the far
4281          * right (to the right of a smallIcon if it has been placed there).
4282          *
4283          * @deprecated use {@link #setSubText(CharSequence)} instead to set a text in the header.
4284          * For legacy apps targeting a version below {@link android.os.Build.VERSION_CODES#N} this
4285          * field will still show up, but the subtext will take precedence.
4286          */
4287         @Deprecated
setContentInfo(CharSequence info)4288         public Builder setContentInfo(CharSequence info) {
4289             mN.extras.putCharSequence(EXTRA_INFO_TEXT, safeCharSequence(info));
4290             return this;
4291         }
4292 
4293         /**
4294          * Set the progress this notification represents.
4295          *
4296          * The platform template will represent this using a {@link ProgressBar}.
4297          */
4298         @NonNull
setProgress(int max, int progress, boolean indeterminate)4299         public Builder setProgress(int max, int progress, boolean indeterminate) {
4300             mN.extras.putInt(EXTRA_PROGRESS, progress);
4301             mN.extras.putInt(EXTRA_PROGRESS_MAX, max);
4302             mN.extras.putBoolean(EXTRA_PROGRESS_INDETERMINATE, indeterminate);
4303             return this;
4304         }
4305 
4306         /**
4307          * Supply a custom RemoteViews to use instead of the platform template.
4308          *
4309          * Use {@link #setCustomContentView(RemoteViews)} instead.
4310          */
4311         @Deprecated
setContent(RemoteViews views)4312         public Builder setContent(RemoteViews views) {
4313             return setCustomContentView(views);
4314         }
4315 
4316         /**
4317          * Supply custom RemoteViews to use instead of the platform template.
4318          *
4319          * This will override the layout that would otherwise be constructed by this Builder
4320          * object.
4321          */
4322         @NonNull
setCustomContentView(RemoteViews contentView)4323         public Builder setCustomContentView(RemoteViews contentView) {
4324             mN.contentView = contentView;
4325             return this;
4326         }
4327 
4328         /**
4329          * Supply custom RemoteViews to use instead of the platform template in the expanded form.
4330          *
4331          * This will override the expanded layout that would otherwise be constructed by this
4332          * Builder object.
4333          */
4334         @NonNull
setCustomBigContentView(RemoteViews contentView)4335         public Builder setCustomBigContentView(RemoteViews contentView) {
4336             mN.bigContentView = contentView;
4337             return this;
4338         }
4339 
4340         /**
4341          * Supply custom RemoteViews to use instead of the platform template in the heads up dialog.
4342          *
4343          * This will override the heads-up layout that would otherwise be constructed by this
4344          * Builder object.
4345          */
4346         @NonNull
setCustomHeadsUpContentView(RemoteViews contentView)4347         public Builder setCustomHeadsUpContentView(RemoteViews contentView) {
4348             mN.headsUpContentView = contentView;
4349             return this;
4350         }
4351 
4352         /**
4353          * Supply a {@link PendingIntent} to be sent when the notification is clicked.
4354          *
4355          * <p>As of Android {@link android.os.Build.VERSION_CODES#S}, apps targeting API level
4356          * {@link android.os.Build.VERSION_CODES#S} or higher won't be able to start activities
4357          * while processing broadcast receivers or services in response to notification clicks. To
4358          * launch an activity in those cases, provide a {@link PendingIntent} for the activity
4359          * itself.
4360          *
4361          * <p>As of {@link android.os.Build.VERSION_CODES#HONEYCOMB}, if this field is unset and you
4362          * have specified a custom RemoteViews with {@link #setContent(RemoteViews)}, you can use
4363          * {@link RemoteViews#setOnClickPendingIntent RemoteViews.setOnClickPendingIntent(int,PendingIntent)}
4364          * to assign PendingIntents to individual views in that custom layout (i.e., to create
4365          * clickable buttons inside the notification view).
4366          *
4367          * @see Notification#contentIntent Notification.contentIntent
4368          */
4369         @NonNull
setContentIntent(PendingIntent intent)4370         public Builder setContentIntent(PendingIntent intent) {
4371             mN.contentIntent = intent;
4372             return this;
4373         }
4374 
4375         /**
4376          * Supply a {@link PendingIntent} to send when the notification is cleared explicitly by the user.
4377          *
4378          * @see Notification#deleteIntent
4379          */
4380         @NonNull
setDeleteIntent(PendingIntent intent)4381         public Builder setDeleteIntent(PendingIntent intent) {
4382             mN.deleteIntent = intent;
4383             return this;
4384         }
4385 
4386         /**
4387          * An intent to launch instead of posting the notification to the status bar.
4388          * Only for use with extremely high-priority notifications demanding the user's
4389          * <strong>immediate</strong> attention, such as an incoming phone call or
4390          * alarm clock that the user has explicitly set to a particular time.
4391          * If this facility is used for something else, please give the user an option
4392          * to turn it off and use a normal notification, as this can be extremely
4393          * disruptive.
4394          *
4395          * <p>
4396          * The system UI may choose to display a heads-up notification, instead of
4397          * launching this intent, while the user is using the device.
4398          * </p>
4399          * <p>Apps targeting {@link Build.VERSION_CODES#Q} and above will have to request
4400          * a permission ({@link android.Manifest.permission#USE_FULL_SCREEN_INTENT}) in order to
4401          * use full screen intents.</p>
4402          *
4403          * @param intent The pending intent to launch.
4404          * @param highPriority Passing true will cause this notification to be sent
4405          *          even if other notifications are suppressed.
4406          *
4407          * @see Notification#fullScreenIntent
4408          */
4409         @NonNull
setFullScreenIntent(PendingIntent intent, boolean highPriority)4410         public Builder setFullScreenIntent(PendingIntent intent, boolean highPriority) {
4411             mN.fullScreenIntent = intent;
4412             setFlag(FLAG_HIGH_PRIORITY, highPriority);
4413             return this;
4414         }
4415 
4416         /**
4417          * Set the "ticker" text which is sent to accessibility services.
4418          *
4419          * @see Notification#tickerText
4420          */
4421         @NonNull
setTicker(CharSequence tickerText)4422         public Builder setTicker(CharSequence tickerText) {
4423             mN.tickerText = safeCharSequence(tickerText);
4424             return this;
4425         }
4426 
4427         /**
4428          * Obsolete version of {@link #setTicker(CharSequence)}.
4429          *
4430          */
4431         @Deprecated
setTicker(CharSequence tickerText, RemoteViews views)4432         public Builder setTicker(CharSequence tickerText, RemoteViews views) {
4433             setTicker(tickerText);
4434             // views is ignored
4435             return this;
4436         }
4437 
4438         /**
4439          * Add a large icon to the notification content view.
4440          *
4441          * In the platform template, this image will be shown either on the right of the
4442          * notification, with an aspect ratio of up to 16:9, or (when the notification is grouped)
4443          * on the left in place of the {@link #setSmallIcon(Icon) small icon}.
4444          */
4445         @NonNull
setLargeIcon(Bitmap b)4446         public Builder setLargeIcon(Bitmap b) {
4447             return setLargeIcon(b != null ? Icon.createWithBitmap(b) : null);
4448         }
4449 
4450         /**
4451          * Add a large icon to the notification content view.
4452          *
4453          * In the platform template, this image will be shown either on the right of the
4454          * notification, with an aspect ratio of up to 16:9, or (when the notification is grouped)
4455          * on the left in place of the {@link #setSmallIcon(Icon) small icon}.
4456          */
4457         @NonNull
setLargeIcon(Icon icon)4458         public Builder setLargeIcon(Icon icon) {
4459             mN.mLargeIcon = icon;
4460             mN.extras.putParcelable(EXTRA_LARGE_ICON, icon);
4461             return this;
4462         }
4463 
4464         /**
4465          * Set the sound to play.
4466          *
4467          * It will be played using the {@link #AUDIO_ATTRIBUTES_DEFAULT default audio attributes}
4468          * for notifications.
4469          *
4470          * @deprecated use {@link NotificationChannel#setSound(Uri, AudioAttributes)} instead.
4471          */
4472         @Deprecated
setSound(Uri sound)4473         public Builder setSound(Uri sound) {
4474             mN.sound = sound;
4475             mN.audioAttributes = AUDIO_ATTRIBUTES_DEFAULT;
4476             return this;
4477         }
4478 
4479         /**
4480          * Set the sound to play, along with a specific stream on which to play it.
4481          *
4482          * See {@link android.media.AudioManager} for the <code>STREAM_</code> constants.
4483          *
4484          * @deprecated use {@link NotificationChannel#setSound(Uri, AudioAttributes)}.
4485          */
4486         @Deprecated
setSound(Uri sound, int streamType)4487         public Builder setSound(Uri sound, int streamType) {
4488             PlayerBase.deprecateStreamTypeForPlayback(streamType, "Notification", "setSound()");
4489             mN.sound = sound;
4490             mN.audioStreamType = streamType;
4491             return this;
4492         }
4493 
4494         /**
4495          * Set the sound to play, along with specific {@link AudioAttributes audio attributes} to
4496          * use during playback.
4497          *
4498          * @deprecated use {@link NotificationChannel#setSound(Uri, AudioAttributes)} instead.
4499          * @see Notification#sound
4500          */
4501         @Deprecated
setSound(Uri sound, AudioAttributes audioAttributes)4502         public Builder setSound(Uri sound, AudioAttributes audioAttributes) {
4503             mN.sound = sound;
4504             mN.audioAttributes = audioAttributes;
4505             return this;
4506         }
4507 
4508         /**
4509          * Set the vibration pattern to use.
4510          *
4511          * See {@link android.os.Vibrator#vibrate(long[], int)} for a discussion of the
4512          * <code>pattern</code> parameter.
4513          *
4514          * <p>
4515          * A notification that vibrates is more likely to be presented as a heads-up notification.
4516          * </p>
4517          *
4518          * @deprecated use {@link NotificationChannel#setVibrationPattern(long[])} instead.
4519          * @see Notification#vibrate
4520          */
4521         @Deprecated
setVibrate(long[] pattern)4522         public Builder setVibrate(long[] pattern) {
4523             mN.vibrate = pattern;
4524             return this;
4525         }
4526 
4527         /**
4528          * Set the desired color for the indicator LED on the device, as well as the
4529          * blink duty cycle (specified in milliseconds).
4530          *
4531 
4532          * Not all devices will honor all (or even any) of these values.
4533          *
4534          * @deprecated use {@link NotificationChannel#enableLights(boolean)} instead.
4535          * @see Notification#ledARGB
4536          * @see Notification#ledOnMS
4537          * @see Notification#ledOffMS
4538          */
4539         @Deprecated
setLights(@olorInt int argb, int onMs, int offMs)4540         public Builder setLights(@ColorInt int argb, int onMs, int offMs) {
4541             mN.ledARGB = argb;
4542             mN.ledOnMS = onMs;
4543             mN.ledOffMS = offMs;
4544             if (onMs != 0 || offMs != 0) {
4545                 mN.flags |= FLAG_SHOW_LIGHTS;
4546             }
4547             return this;
4548         }
4549 
4550         /**
4551          * Set whether this is an "ongoing" notification.
4552          *
4553 
4554          * Ongoing notifications cannot be dismissed by the user, so your application or service
4555          * must take care of canceling them.
4556          *
4557 
4558          * They are typically used to indicate a background task that the user is actively engaged
4559          * with (e.g., playing music) or is pending in some way and therefore occupying the device
4560          * (e.g., a file download, sync operation, active network connection).
4561          *
4562 
4563          * @see Notification#FLAG_ONGOING_EVENT
4564          */
4565         @NonNull
setOngoing(boolean ongoing)4566         public Builder setOngoing(boolean ongoing) {
4567             setFlag(FLAG_ONGOING_EVENT, ongoing);
4568             return this;
4569         }
4570 
4571         /**
4572          * Set whether this notification should be colorized. When set, the color set with
4573          * {@link #setColor(int)} will be used as the background color of this notification.
4574          * <p>
4575          * This should only be used for high priority ongoing tasks like navigation, an ongoing
4576          * call, or other similarly high-priority events for the user.
4577          * <p>
4578          * For most styles, the coloring will only be applied if the notification is for a
4579          * foreground service notification.
4580          * However, for {@link MediaStyle} and {@link DecoratedMediaCustomViewStyle} notifications
4581          * that have a media session attached there is no such requirement.
4582          *
4583          * @see #setColor(int)
4584          * @see MediaStyle#setMediaSession(MediaSession.Token)
4585          */
4586         @NonNull
setColorized(boolean colorize)4587         public Builder setColorized(boolean colorize) {
4588             mN.extras.putBoolean(EXTRA_COLORIZED, colorize);
4589             return this;
4590         }
4591 
4592         /**
4593          * Set this flag if you would only like the sound, vibrate
4594          * and ticker to be played if the notification is not already showing.
4595          *
4596          * @see Notification#FLAG_ONLY_ALERT_ONCE
4597          */
4598         @NonNull
setOnlyAlertOnce(boolean onlyAlertOnce)4599         public Builder setOnlyAlertOnce(boolean onlyAlertOnce) {
4600             setFlag(FLAG_ONLY_ALERT_ONCE, onlyAlertOnce);
4601             return this;
4602         }
4603 
4604         /**
4605          * Specify a desired visibility policy for a Notification associated with a
4606          * foreground service.  By default, the system can choose to defer
4607          * visibility of the notification for a short time after the service is
4608          * started.  Pass
4609          * {@link Notification#FOREGROUND_SERVICE_IMMEDIATE FOREGROUND_SERVICE_IMMEDIATE}
4610          * to this method in order to guarantee that visibility is never deferred.  Pass
4611          * {@link Notification#FOREGROUND_SERVICE_DEFERRED FOREGROUND_SERVICE_DEFERRED}
4612          * to request that visibility is deferred whenever possible.
4613          *
4614          * <p class="note">Note that deferred visibility is not guaranteed.  There
4615          * may be some circumstances under which the system will show the foreground
4616          * service's associated Notification immediately even when the app has used
4617          * this method to explicitly request deferred display.</p>
4618          * @param behavior One of
4619          * {@link Notification#FOREGROUND_SERVICE_DEFAULT FOREGROUND_SERVICE_DEFAULT},
4620          * {@link Notification#FOREGROUND_SERVICE_IMMEDIATE FOREGROUND_SERVICE_IMMEDIATE},
4621          * or {@link Notification#FOREGROUND_SERVICE_DEFERRED FOREGROUND_SERVICE_DEFERRED}
4622          * @return
4623          */
4624         @NonNull
setForegroundServiceBehavior(@erviceNotificationPolicy int behavior)4625         public Builder setForegroundServiceBehavior(@ServiceNotificationPolicy int behavior) {
4626             mN.mFgsDeferBehavior = behavior;
4627             return this;
4628         }
4629 
4630         /**
4631          * Make this notification automatically dismissed when the user touches it.
4632          *
4633          * @see Notification#FLAG_AUTO_CANCEL
4634          */
4635         @NonNull
setAutoCancel(boolean autoCancel)4636         public Builder setAutoCancel(boolean autoCancel) {
4637             setFlag(FLAG_AUTO_CANCEL, autoCancel);
4638             return this;
4639         }
4640 
4641         /**
4642          * Set whether or not this notification should not bridge to other devices.
4643          *
4644          * <p>Some notifications can be bridged to other devices for remote display.
4645          * This hint can be set to recommend this notification not be bridged.
4646          */
4647         @NonNull
setLocalOnly(boolean localOnly)4648         public Builder setLocalOnly(boolean localOnly) {
4649             setFlag(FLAG_LOCAL_ONLY, localOnly);
4650             return this;
4651         }
4652 
4653         /**
4654          * Set which notification properties will be inherited from system defaults.
4655          * <p>
4656          * The value should be one or more of the following fields combined with
4657          * bitwise-or:
4658          * {@link #DEFAULT_SOUND}, {@link #DEFAULT_VIBRATE}, {@link #DEFAULT_LIGHTS}.
4659          * <p>
4660          * For all default values, use {@link #DEFAULT_ALL}.
4661          *
4662          * @deprecated use {@link NotificationChannel#enableVibration(boolean)} and
4663          * {@link NotificationChannel#enableLights(boolean)} and
4664          * {@link NotificationChannel#setSound(Uri, AudioAttributes)} instead.
4665          */
4666         @Deprecated
setDefaults(int defaults)4667         public Builder setDefaults(int defaults) {
4668             mN.defaults = defaults;
4669             return this;
4670         }
4671 
4672         /**
4673          * Set the priority of this notification.
4674          *
4675          * @see Notification#priority
4676          * @deprecated use {@link NotificationChannel#setImportance(int)} instead.
4677          */
4678         @Deprecated
setPriority(@riority int pri)4679         public Builder setPriority(@Priority int pri) {
4680             mN.priority = pri;
4681             return this;
4682         }
4683 
4684         /**
4685          * Set the notification category.
4686          *
4687          * @see Notification#category
4688          */
4689         @NonNull
setCategory(String category)4690         public Builder setCategory(String category) {
4691             mN.category = category;
4692             return this;
4693         }
4694 
4695         /**
4696          * Add a person that is relevant to this notification.
4697          *
4698          * <P>
4699          * Depending on user preferences, this annotation may allow the notification to pass
4700          * through interruption filters, if this notification is of category {@link #CATEGORY_CALL}
4701          * or {@link #CATEGORY_MESSAGE}. The addition of people may also cause this notification to
4702          * appear more prominently in the user interface.
4703          * </P>
4704          *
4705          * <P>
4706          * The person should be specified by the {@code String} representation of a
4707          * {@link android.provider.ContactsContract.Contacts#CONTENT_LOOKUP_URI}.
4708          * </P>
4709          *
4710          * <P>The system will also attempt to resolve {@code mailto:} and {@code tel:} schema
4711          * URIs.  The path part of these URIs must exist in the contacts database, in the
4712          * appropriate column, or the reference will be discarded as invalid. Telephone schema
4713          * URIs will be resolved by {@link android.provider.ContactsContract.PhoneLookup}.
4714          * It is also possible to provide a URI with the schema {@code name:} in order to uniquely
4715          * identify a person without an entry in the contacts database.
4716          * </P>
4717          *
4718          * @param uri A URI for the person.
4719          * @see Notification#EXTRA_PEOPLE
4720          * @deprecated use {@link #addPerson(Person)}
4721          */
addPerson(String uri)4722         public Builder addPerson(String uri) {
4723             addPerson(new Person.Builder().setUri(uri).build());
4724             return this;
4725         }
4726 
4727         /**
4728          * Add a person that is relevant to this notification.
4729          *
4730          * <P>
4731          * Depending on user preferences, this annotation may allow the notification to pass
4732          * through interruption filters, if this notification is of category {@link #CATEGORY_CALL}
4733          * or {@link #CATEGORY_MESSAGE}. The addition of people may also cause this notification to
4734          * appear more prominently in the user interface.
4735          * </P>
4736          *
4737          * <P>
4738          * A person should usually contain a uri in order to benefit from the ranking boost.
4739          * However, even if no uri is provided, it's beneficial to provide other people in the
4740          * notification, such that listeners and voice only devices can announce and handle them
4741          * properly.
4742          * </P>
4743          *
4744          * @param person the person to add.
4745          * @see Notification#EXTRA_PEOPLE_LIST
4746          */
4747         @NonNull
addPerson(Person person)4748         public Builder addPerson(Person person) {
4749             mPersonList.add(person);
4750             return this;
4751         }
4752 
4753         /**
4754          * Set this notification to be part of a group of notifications sharing the same key.
4755          * Grouped notifications may display in a cluster or stack on devices which
4756          * support such rendering.
4757          *
4758          * <p>To make this notification the summary for its group, also call
4759          * {@link #setGroupSummary}. A sort order can be specified for group members by using
4760          * {@link #setSortKey}.
4761          * @param groupKey The group key of the group.
4762          * @return this object for method chaining
4763          */
4764         @NonNull
setGroup(String groupKey)4765         public Builder setGroup(String groupKey) {
4766             mN.mGroupKey = groupKey;
4767             return this;
4768         }
4769 
4770         /**
4771          * Set this notification to be the group summary for a group of notifications.
4772          * Grouped notifications may display in a cluster or stack on devices which
4773          * support such rendering. If thereRequires a group key also be set using {@link #setGroup}.
4774          * The group summary may be suppressed if too few notifications are included in the group.
4775          * @param isGroupSummary Whether this notification should be a group summary.
4776          * @return this object for method chaining
4777          */
4778         @NonNull
setGroupSummary(boolean isGroupSummary)4779         public Builder setGroupSummary(boolean isGroupSummary) {
4780             setFlag(FLAG_GROUP_SUMMARY, isGroupSummary);
4781             return this;
4782         }
4783 
4784         /**
4785          * Set a sort key that orders this notification among other notifications from the
4786          * same package. This can be useful if an external sort was already applied and an app
4787          * would like to preserve this. Notifications will be sorted lexicographically using this
4788          * value, although providing different priorities in addition to providing sort key may
4789          * cause this value to be ignored.
4790          *
4791          * <p>This sort key can also be used to order members of a notification group. See
4792          * {@link #setGroup}.
4793          *
4794          * @see String#compareTo(String)
4795          */
4796         @NonNull
setSortKey(String sortKey)4797         public Builder setSortKey(String sortKey) {
4798             mN.mSortKey = sortKey;
4799             return this;
4800         }
4801 
4802         /**
4803          * Merge additional metadata into this notification.
4804          *
4805          * <p>Values within the Bundle will replace existing extras values in this Builder.
4806          *
4807          * @see Notification#extras
4808          */
4809         @NonNull
addExtras(Bundle extras)4810         public Builder addExtras(Bundle extras) {
4811             if (extras != null) {
4812                 mUserExtras.putAll(extras);
4813             }
4814             return this;
4815         }
4816 
4817         /**
4818          * Set metadata for this notification.
4819          *
4820          * <p>A reference to the Bundle is held for the lifetime of this Builder, and the Bundle's
4821          * current contents are copied into the Notification each time {@link #build()} is
4822          * called.
4823          *
4824          * <p>Replaces any existing extras values with those from the provided Bundle.
4825          * Use {@link #addExtras} to merge in metadata instead.
4826          *
4827          * @see Notification#extras
4828          */
4829         @NonNull
setExtras(Bundle extras)4830         public Builder setExtras(Bundle extras) {
4831             if (extras != null) {
4832                 mUserExtras = extras;
4833             }
4834             return this;
4835         }
4836 
4837         /**
4838          * Get the current metadata Bundle used by this notification Builder.
4839          *
4840          * <p>The returned Bundle is shared with this Builder.
4841          *
4842          * <p>The current contents of this Bundle are copied into the Notification each time
4843          * {@link #build()} is called.
4844          *
4845          * @see Notification#extras
4846          */
getExtras()4847         public Bundle getExtras() {
4848             return mUserExtras;
4849         }
4850 
getAllExtras()4851         private Bundle getAllExtras() {
4852             final Bundle saveExtras = (Bundle) mUserExtras.clone();
4853             saveExtras.putAll(mN.extras);
4854             return saveExtras;
4855         }
4856 
4857         /**
4858          * Add an action to this notification. Actions are typically displayed by
4859          * the system as a button adjacent to the notification content.
4860          * <p>
4861          * Every action must have an icon (32dp square and matching the
4862          * <a href="{@docRoot}design/style/iconography.html#action-bar">Holo
4863          * Dark action bar</a> visual style), a textual label, and a {@link PendingIntent}.
4864          * <p>
4865          * A notification in its expanded form can display up to 3 actions, from left to right in
4866          * the order they were added. Actions will not be displayed when the notification is
4867          * collapsed, however, so be sure that any essential functions may be accessed by the user
4868          * in some other way (for example, in the Activity pointed to by {@link #contentIntent}).
4869          * <p>
4870          * As of Android {@link android.os.Build.VERSION_CODES#S}, apps targeting API level
4871          * {@link android.os.Build.VERSION_CODES#S} or higher won't be able to start activities
4872          * while processing broadcast receivers or services in response to notification action
4873          * clicks. To launch an activity in those cases, provide a {@link PendingIntent} to the
4874          * activity itself.
4875          *
4876          * @param icon Resource ID of a drawable that represents the action.
4877          * @param title Text describing the action.
4878          * @param intent PendingIntent to be fired when the action is invoked.
4879          *
4880          * @deprecated Use {@link #addAction(Action)} instead.
4881          */
4882         @Deprecated
addAction(int icon, CharSequence title, PendingIntent intent)4883         public Builder addAction(int icon, CharSequence title, PendingIntent intent) {
4884             mActions.add(new Action(icon, safeCharSequence(title), intent));
4885             return this;
4886         }
4887 
4888         /**
4889          * Add an action to this notification. Actions are typically displayed by
4890          * the system as a button adjacent to the notification content.
4891          * <p>
4892          * Every action must have an icon (32dp square and matching the
4893          * <a href="{@docRoot}design/style/iconography.html#action-bar">Holo
4894          * Dark action bar</a> visual style), a textual label, and a {@link PendingIntent}.
4895          * <p>
4896          * A notification in its expanded form can display up to 3 actions, from left to right in
4897          * the order they were added. Actions will not be displayed when the notification is
4898          * collapsed, however, so be sure that any essential functions may be accessed by the user
4899          * in some other way (for example, in the Activity pointed to by {@link #contentIntent}).
4900          *
4901          * @param action The action to add.
4902          */
4903         @NonNull
addAction(Action action)4904         public Builder addAction(Action action) {
4905             if (action != null) {
4906                 mActions.add(action);
4907             }
4908             return this;
4909         }
4910 
4911         /**
4912          * Alter the complete list of actions attached to this notification.
4913          * @see #addAction(Action).
4914          *
4915          * @param actions
4916          * @return
4917          */
4918         @NonNull
setActions(Action... actions)4919         public Builder setActions(Action... actions) {
4920             mActions.clear();
4921             for (int i = 0; i < actions.length; i++) {
4922                 if (actions[i] != null) {
4923                     mActions.add(actions[i]);
4924                 }
4925             }
4926             return this;
4927         }
4928 
4929         /**
4930          * Add a rich notification style to be applied at build time.
4931          *
4932          * @param style Object responsible for modifying the notification style.
4933          */
4934         @NonNull
setStyle(Style style)4935         public Builder setStyle(Style style) {
4936             if (mStyle != style) {
4937                 mStyle = style;
4938                 if (mStyle != null) {
4939                     mStyle.setBuilder(this);
4940                     mN.extras.putString(EXTRA_TEMPLATE, style.getClass().getName());
4941                 }  else {
4942                     mN.extras.remove(EXTRA_TEMPLATE);
4943                 }
4944             }
4945             return this;
4946         }
4947 
4948         /**
4949          * Returns the style set by {@link #setStyle(Style)}.
4950          */
getStyle()4951         public Style getStyle() {
4952             return mStyle;
4953         }
4954 
4955         /**
4956          * Specify the value of {@link #visibility}.
4957          *
4958          * @return The same Builder.
4959          */
4960         @NonNull
setVisibility(@isibility int visibility)4961         public Builder setVisibility(@Visibility int visibility) {
4962             mN.visibility = visibility;
4963             return this;
4964         }
4965 
4966         /**
4967          * Supply a replacement Notification whose contents should be shown in insecure contexts
4968          * (i.e. atop the secure lockscreen). See {@link #visibility} and {@link #VISIBILITY_PUBLIC}.
4969          * @param n A replacement notification, presumably with some or all info redacted.
4970          * @return The same Builder.
4971          */
4972         @NonNull
setPublicVersion(Notification n)4973         public Builder setPublicVersion(Notification n) {
4974             if (n != null) {
4975                 mN.publicVersion = new Notification();
4976                 n.cloneInto(mN.publicVersion, /*heavy=*/ true);
4977             } else {
4978                 mN.publicVersion = null;
4979             }
4980             return this;
4981         }
4982 
4983         /**
4984          * Apply an extender to this notification builder. Extenders may be used to add
4985          * metadata or change options on this builder.
4986          */
4987         @NonNull
extend(Extender extender)4988         public Builder extend(Extender extender) {
4989             extender.extend(this);
4990             return this;
4991         }
4992 
4993         /**
4994          * Set the value for a notification flag
4995          *
4996          * @param mask Bit mask of the flag
4997          * @param value Status (on/off) of the flag
4998          *
4999          * @return The same Builder.
5000          */
5001         @NonNull
setFlag(@otificationFlags int mask, boolean value)5002         public Builder setFlag(@NotificationFlags int mask, boolean value) {
5003             if (value) {
5004                 mN.flags |= mask;
5005             } else {
5006                 mN.flags &= ~mask;
5007             }
5008             return this;
5009         }
5010 
5011         /**
5012          * Sets {@link Notification#color}.
5013          *
5014          * @param argb The accent color to use
5015          *
5016          * @return The same Builder.
5017          */
5018         @NonNull
setColor(@olorInt int argb)5019         public Builder setColor(@ColorInt int argb) {
5020             mN.color = argb;
5021             sanitizeColor();
5022             return this;
5023         }
5024 
bindPhishingAlertIcon(RemoteViews contentView, StandardTemplateParams p)5025         private void bindPhishingAlertIcon(RemoteViews contentView, StandardTemplateParams p) {
5026             contentView.setDrawableTint(
5027                     R.id.phishing_alert,
5028                     false /* targetBackground */,
5029                     getColors(p).getErrorColor(),
5030                     PorterDuff.Mode.SRC_ATOP);
5031         }
5032 
getProfileBadgeDrawable()5033         private Drawable getProfileBadgeDrawable() {
5034             if (mContext.getUserId() == UserHandle.USER_SYSTEM) {
5035                 // This user can never be a badged profile,
5036                 // and also includes USER_ALL system notifications.
5037                 return null;
5038             }
5039             // Note: This assumes that the current user can read the profile badge of the
5040             // originating user.
5041             return mContext.getPackageManager().getUserBadgeForDensityNoBackground(
5042                     new UserHandle(mContext.getUserId()), 0);
5043         }
5044 
getProfileBadge()5045         private Bitmap getProfileBadge() {
5046             Drawable badge = getProfileBadgeDrawable();
5047             if (badge == null) {
5048                 return null;
5049             }
5050             final int size = mContext.getResources().getDimensionPixelSize(
5051                     R.dimen.notification_badge_size);
5052             Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
5053             Canvas canvas = new Canvas(bitmap);
5054             badge.setBounds(0, 0, size, size);
5055             badge.draw(canvas);
5056             return bitmap;
5057         }
5058 
bindProfileBadge(RemoteViews contentView, StandardTemplateParams p)5059         private void bindProfileBadge(RemoteViews contentView, StandardTemplateParams p) {
5060             Bitmap profileBadge = getProfileBadge();
5061 
5062             if (profileBadge != null) {
5063                 contentView.setImageViewBitmap(R.id.profile_badge, profileBadge);
5064                 contentView.setViewVisibility(R.id.profile_badge, View.VISIBLE);
5065                 if (isBackgroundColorized(p)) {
5066                     contentView.setDrawableTint(R.id.profile_badge, false,
5067                             getPrimaryTextColor(p), PorterDuff.Mode.SRC_ATOP);
5068                 }
5069             }
5070         }
5071 
bindAlertedIcon(RemoteViews contentView, StandardTemplateParams p)5072         private void bindAlertedIcon(RemoteViews contentView, StandardTemplateParams p) {
5073             contentView.setDrawableTint(
5074                     R.id.alerted_icon,
5075                     false /* targetBackground */,
5076                     getColors(p).getSecondaryTextColor(),
5077                     PorterDuff.Mode.SRC_IN);
5078         }
5079 
5080         /**
5081          * @hide
5082          */
usesStandardHeader()5083         public boolean usesStandardHeader() {
5084             if (mN.mUsesStandardHeader) {
5085                 return true;
5086             }
5087             if (mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.N) {
5088                 if (mN.contentView == null && mN.bigContentView == null) {
5089                     return true;
5090                 }
5091             }
5092             boolean contentViewUsesHeader = mN.contentView == null
5093                     || STANDARD_LAYOUTS.contains(mN.contentView.getLayoutId());
5094             boolean bigContentViewUsesHeader = mN.bigContentView == null
5095                     || STANDARD_LAYOUTS.contains(mN.bigContentView.getLayoutId());
5096             return contentViewUsesHeader && bigContentViewUsesHeader;
5097         }
5098 
resetStandardTemplate(RemoteViews contentView)5099         private void resetStandardTemplate(RemoteViews contentView) {
5100             resetNotificationHeader(contentView);
5101             contentView.setViewVisibility(R.id.right_icon, View.GONE);
5102             contentView.setViewVisibility(R.id.title, View.GONE);
5103             contentView.setTextViewText(R.id.title, null);
5104             contentView.setViewVisibility(R.id.text, View.GONE);
5105             contentView.setTextViewText(R.id.text, null);
5106         }
5107 
5108         /**
5109          * Resets the notification header to its original state
5110          */
resetNotificationHeader(RemoteViews contentView)5111         private void resetNotificationHeader(RemoteViews contentView) {
5112             // Small icon doesn't need to be reset, as it's always set. Resetting would prevent
5113             // re-using the drawable when the notification is updated.
5114             contentView.setBoolean(R.id.expand_button, "setExpanded", false);
5115             contentView.setViewVisibility(R.id.app_name_text, View.GONE);
5116             contentView.setTextViewText(R.id.app_name_text, null);
5117             contentView.setViewVisibility(R.id.chronometer, View.GONE);
5118             contentView.setViewVisibility(R.id.header_text, View.GONE);
5119             contentView.setTextViewText(R.id.header_text, null);
5120             contentView.setViewVisibility(R.id.header_text_secondary, View.GONE);
5121             contentView.setTextViewText(R.id.header_text_secondary, null);
5122             contentView.setViewVisibility(R.id.header_text_divider, View.GONE);
5123             contentView.setViewVisibility(R.id.header_text_secondary_divider, View.GONE);
5124             contentView.setViewVisibility(R.id.time_divider, View.GONE);
5125             contentView.setViewVisibility(R.id.time, View.GONE);
5126             contentView.setImageViewIcon(R.id.profile_badge, null);
5127             contentView.setViewVisibility(R.id.profile_badge, View.GONE);
5128             mN.mUsesStandardHeader = false;
5129         }
5130 
applyStandardTemplate(int resId, StandardTemplateParams p, TemplateBindResult result)5131         private RemoteViews applyStandardTemplate(int resId, StandardTemplateParams p,
5132                 TemplateBindResult result) {
5133             p.headerless(resId == getBaseLayoutResource()
5134                     || resId == getHeadsUpBaseLayoutResource()
5135                     || resId == getMessagingLayoutResource()
5136                     || resId == R.layout.notification_template_material_media);
5137             RemoteViews contentView = new BuilderRemoteViews(mContext.getApplicationInfo(), resId);
5138 
5139             resetStandardTemplate(contentView);
5140 
5141             final Bundle ex = mN.extras;
5142             updateBackgroundColor(contentView, p);
5143             bindNotificationHeader(contentView, p);
5144             bindLargeIconAndApplyMargin(contentView, p, result);
5145             boolean showProgress = handleProgressBar(contentView, ex, p);
5146             boolean hasSecondLine = showProgress;
5147             if (p.hasTitle()) {
5148                 contentView.setViewVisibility(p.mTitleViewId, View.VISIBLE);
5149                 contentView.setTextViewText(p.mTitleViewId, processTextSpans(p.title));
5150                 setTextViewColorPrimary(contentView, p.mTitleViewId, p);
5151             } else if (p.mTitleViewId != R.id.title) {
5152                 // This alternate title view ID is not cleared by resetStandardTemplate
5153                 contentView.setViewVisibility(p.mTitleViewId, View.GONE);
5154                 contentView.setTextViewText(p.mTitleViewId, null);
5155             }
5156             if (p.text != null && p.text.length() != 0
5157                     && (!showProgress || p.mAllowTextWithProgress)) {
5158                 contentView.setViewVisibility(p.mTextViewId, View.VISIBLE);
5159                 contentView.setTextViewText(p.mTextViewId, processTextSpans(p.text));
5160                 setTextViewColorSecondary(contentView, p.mTextViewId, p);
5161                 hasSecondLine = true;
5162             } else if (p.mTextViewId != R.id.text) {
5163                 // This alternate text view ID is not cleared by resetStandardTemplate
5164                 contentView.setViewVisibility(p.mTextViewId, View.GONE);
5165                 contentView.setTextViewText(p.mTextViewId, null);
5166             }
5167             setHeaderlessVerticalMargins(contentView, p, hasSecondLine);
5168 
5169             return contentView;
5170         }
5171 
setHeaderlessVerticalMargins(RemoteViews contentView, StandardTemplateParams p, boolean hasSecondLine)5172         private static void setHeaderlessVerticalMargins(RemoteViews contentView,
5173                 StandardTemplateParams p, boolean hasSecondLine) {
5174             if (!p.mHeaderless) {
5175                 return;
5176             }
5177             int marginDimen = hasSecondLine
5178                     ? R.dimen.notification_headerless_margin_twoline
5179                     : R.dimen.notification_headerless_margin_oneline;
5180             contentView.setViewLayoutMarginDimen(R.id.notification_headerless_view_column,
5181                     RemoteViews.MARGIN_TOP, marginDimen);
5182             contentView.setViewLayoutMarginDimen(R.id.notification_headerless_view_column,
5183                     RemoteViews.MARGIN_BOTTOM, marginDimen);
5184         }
5185 
processTextSpans(CharSequence text)5186         private CharSequence processTextSpans(CharSequence text) {
5187             if (mInNightMode) {
5188                 return ContrastColorUtil.clearColorSpans(text);
5189             }
5190             return text;
5191         }
5192 
setTextViewColorPrimary(RemoteViews contentView, @IdRes int id, StandardTemplateParams p)5193         private void setTextViewColorPrimary(RemoteViews contentView, @IdRes int id,
5194                 StandardTemplateParams p) {
5195             contentView.setTextColor(id, getPrimaryTextColor(p));
5196         }
5197 
5198         /**
5199          * @param p the template params to inflate this with
5200          * @return the primary text color
5201          * @hide
5202          */
5203         @VisibleForTesting
getPrimaryTextColor(StandardTemplateParams p)5204         public @ColorInt int getPrimaryTextColor(StandardTemplateParams p) {
5205             return getColors(p).getPrimaryTextColor();
5206         }
5207 
5208         /**
5209          * @param p the template params to inflate this with
5210          * @return the secondary text color
5211          * @hide
5212          */
5213         @VisibleForTesting
getSecondaryTextColor(StandardTemplateParams p)5214         public @ColorInt int getSecondaryTextColor(StandardTemplateParams p) {
5215             return getColors(p).getSecondaryTextColor();
5216         }
5217 
setTextViewColorSecondary(RemoteViews contentView, @IdRes int id, StandardTemplateParams p)5218         private void setTextViewColorSecondary(RemoteViews contentView, @IdRes int id,
5219                 StandardTemplateParams p) {
5220             contentView.setTextColor(id, getSecondaryTextColor(p));
5221         }
5222 
getColors(StandardTemplateParams p)5223         private Colors getColors(StandardTemplateParams p) {
5224             mColors.resolvePalette(mContext, mN.color, isBackgroundColorized(p), mInNightMode);
5225             return mColors;
5226         }
5227 
updateBackgroundColor(RemoteViews contentView, StandardTemplateParams p)5228         private void updateBackgroundColor(RemoteViews contentView,
5229                 StandardTemplateParams p) {
5230             if (isBackgroundColorized(p)) {
5231                 contentView.setInt(R.id.status_bar_latest_event_content, "setBackgroundColor",
5232                         getBackgroundColor(p));
5233             } else {
5234                 // Clear it!
5235                 contentView.setInt(R.id.status_bar_latest_event_content, "setBackgroundResource",
5236                         0);
5237             }
5238         }
5239 
handleProgressBar(RemoteViews contentView, Bundle ex, StandardTemplateParams p)5240         private boolean handleProgressBar(RemoteViews contentView, Bundle ex,
5241                 StandardTemplateParams p) {
5242             final int max = ex.getInt(EXTRA_PROGRESS_MAX, 0);
5243             final int progress = ex.getInt(EXTRA_PROGRESS, 0);
5244             final boolean ind = ex.getBoolean(EXTRA_PROGRESS_INDETERMINATE);
5245             if (!p.mHideProgress && (max != 0 || ind)) {
5246                 contentView.setViewVisibility(com.android.internal.R.id.progress, View.VISIBLE);
5247                 contentView.setProgressBar(R.id.progress, max, progress, ind);
5248                 contentView.setProgressBackgroundTintList(R.id.progress,
5249                         mContext.getColorStateList(R.color.notification_progress_background_color));
5250                 ColorStateList progressTint = ColorStateList.valueOf(getPrimaryAccentColor(p));
5251                 contentView.setProgressTintList(R.id.progress, progressTint);
5252                 contentView.setProgressIndeterminateTintList(R.id.progress, progressTint);
5253                 return true;
5254             } else {
5255                 contentView.setViewVisibility(R.id.progress, View.GONE);
5256                 return false;
5257             }
5258         }
5259 
bindLargeIconAndApplyMargin(RemoteViews contentView, @NonNull StandardTemplateParams p, @Nullable TemplateBindResult result)5260         private void bindLargeIconAndApplyMargin(RemoteViews contentView,
5261                 @NonNull StandardTemplateParams p,
5262                 @Nullable TemplateBindResult result) {
5263             if (result == null) {
5264                 result = new TemplateBindResult();
5265             }
5266             bindLargeIcon(contentView, p, result);
5267             if (!p.mHeaderless) {
5268                 // views in states with a header (big states)
5269                 result.mHeadingExtraMarginSet.applyToView(contentView, R.id.notification_header);
5270                 result.mTitleMarginSet.applyToView(contentView, R.id.title);
5271                 // If there is no title, the text (or big_text) needs to wrap around the image
5272                 result.mTitleMarginSet.applyToView(contentView, p.mTextViewId);
5273                 contentView.setInt(p.mTextViewId, "setNumIndentLines", p.hasTitle() ? 0 : 1);
5274             }
5275         }
5276 
5277         // This code is executed on behalf of other apps' notifications, sometimes even by 3p apps,
5278         // a use case that is not supported by the Compat Framework library.  Workarounds to resolve
5279         // the change's state in NotificationManagerService were very complex. These behavior
5280         // changes are entirely visual, and should otherwise be undetectable by apps.
5281         @SuppressWarnings("AndroidFrameworkCompatChange")
calculateRightIconDimens(Icon rightIcon, boolean isPromotedPicture, @NonNull TemplateBindResult result)5282         private void calculateRightIconDimens(Icon rightIcon, boolean isPromotedPicture,
5283                 @NonNull TemplateBindResult result) {
5284             final Resources resources = mContext.getResources();
5285             final float density = resources.getDisplayMetrics().density;
5286             final float iconMarginDp = resources.getDimension(
5287                     R.dimen.notification_right_icon_content_margin) / density;
5288             final float contentMarginDp = resources.getDimension(
5289                     R.dimen.notification_content_margin_end) / density;
5290             final float expanderSizeDp = resources.getDimension(
5291                     R.dimen.notification_header_expand_icon_size) / density - contentMarginDp;
5292             final float viewHeightDp = resources.getDimension(
5293                     R.dimen.notification_right_icon_size) / density;
5294             float viewWidthDp = viewHeightDp;  // icons are 1:1 by default
5295             if (rightIcon != null && (isPromotedPicture
5296                     || mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.S)) {
5297                 Drawable drawable = rightIcon.loadDrawable(mContext);
5298                 if (drawable != null) {
5299                     int iconWidth = drawable.getIntrinsicWidth();
5300                     int iconHeight = drawable.getIntrinsicHeight();
5301                     if (iconWidth > iconHeight && iconHeight > 0) {
5302                         final float maxViewWidthDp = viewHeightDp * MAX_LARGE_ICON_ASPECT_RATIO;
5303                         viewWidthDp = Math.min(viewHeightDp * iconWidth / iconHeight,
5304                                 maxViewWidthDp);
5305                     }
5306                 }
5307             }
5308             final float extraMarginEndDpIfVisible = viewWidthDp + iconMarginDp;
5309             result.setRightIconState(rightIcon != null /* visible */, viewWidthDp,
5310                     viewHeightDp, extraMarginEndDpIfVisible, expanderSizeDp);
5311         }
5312 
5313         /**
5314          * Bind the large icon.
5315          */
bindLargeIcon(RemoteViews contentView, @NonNull StandardTemplateParams p, @NonNull TemplateBindResult result)5316         private void bindLargeIcon(RemoteViews contentView, @NonNull StandardTemplateParams p,
5317                 @NonNull TemplateBindResult result) {
5318             if (mN.mLargeIcon == null && mN.largeIcon != null) {
5319                 mN.mLargeIcon = Icon.createWithBitmap(mN.largeIcon);
5320             }
5321 
5322             // Determine the left and right icons
5323             Icon leftIcon = p.mHideLeftIcon ? null : mN.mLargeIcon;
5324             Icon rightIcon = p.mHideRightIcon ? null
5325                     : (p.mPromotedPicture != null ? p.mPromotedPicture : mN.mLargeIcon);
5326 
5327             // Apply the left icon (without duplicating the bitmap)
5328             if (leftIcon != rightIcon || leftIcon == null) {
5329                 // If the leftIcon is explicitly hidden or different from the rightIcon, then set it
5330                 // explicitly and make sure it won't take the right_icon drawable.
5331                 contentView.setImageViewIcon(R.id.left_icon, leftIcon);
5332                 contentView.setIntTag(R.id.left_icon, R.id.tag_uses_right_icon_drawable, 0);
5333             } else {
5334                 // If the leftIcon equals the rightIcon, just set the flag to use the right_icon
5335                 // drawable.  This avoids the view having two copies of the same bitmap.
5336                 contentView.setIntTag(R.id.left_icon, R.id.tag_uses_right_icon_drawable, 1);
5337             }
5338 
5339             // Always calculate dimens to populate `result` for the GONE case
5340             boolean isPromotedPicture = p.mPromotedPicture != null;
5341             calculateRightIconDimens(rightIcon, isPromotedPicture, result);
5342 
5343             // Bind the right icon
5344             if (rightIcon != null) {
5345                 contentView.setViewLayoutWidth(R.id.right_icon,
5346                         result.mRightIconWidthDp, TypedValue.COMPLEX_UNIT_DIP);
5347                 contentView.setViewLayoutHeight(R.id.right_icon,
5348                         result.mRightIconHeightDp, TypedValue.COMPLEX_UNIT_DIP);
5349                 contentView.setViewVisibility(R.id.right_icon, View.VISIBLE);
5350                 contentView.setImageViewIcon(R.id.right_icon, rightIcon);
5351                 contentView.setIntTag(R.id.right_icon, R.id.tag_keep_when_showing_left_icon,
5352                         isPromotedPicture ? 1 : 0);
5353                 processLargeLegacyIcon(rightIcon, contentView, p);
5354             } else {
5355                 // The "reset" doesn't clear the drawable, so we do it here.  This clear is
5356                 // important because the presence of a drawable in this view (regardless of the
5357                 // visibility) is used by NotificationGroupingUtil to set the visibility.
5358                 contentView.setImageViewIcon(R.id.right_icon, null);
5359                 contentView.setIntTag(R.id.right_icon, R.id.tag_keep_when_showing_left_icon, 0);
5360             }
5361         }
5362 
bindNotificationHeader(RemoteViews contentView, StandardTemplateParams p)5363         private void bindNotificationHeader(RemoteViews contentView, StandardTemplateParams p) {
5364             bindSmallIcon(contentView, p);
5365             // Populate text left-to-right so that separators are only shown between strings
5366             boolean hasTextToLeft = bindHeaderAppName(contentView, p, false /* force */);
5367             hasTextToLeft |= bindHeaderTextSecondary(contentView, p, hasTextToLeft);
5368             hasTextToLeft |= bindHeaderText(contentView, p, hasTextToLeft);
5369             if (!hasTextToLeft) {
5370                 // If there's still no text, force add the app name so there is some text.
5371                 hasTextToLeft |= bindHeaderAppName(contentView, p, true /* force */);
5372             }
5373             bindHeaderChronometerAndTime(contentView, p, hasTextToLeft);
5374             bindPhishingAlertIcon(contentView, p);
5375             bindProfileBadge(contentView, p);
5376             bindAlertedIcon(contentView, p);
5377             bindExpandButton(contentView, p);
5378             mN.mUsesStandardHeader = true;
5379         }
5380 
bindExpandButton(RemoteViews contentView, StandardTemplateParams p)5381         private void bindExpandButton(RemoteViews contentView, StandardTemplateParams p) {
5382             // set default colors
5383             int bgColor = getBackgroundColor(p);
5384             int pillColor = Colors.flattenAlpha(getColors(p).getProtectionColor(), bgColor);
5385             int textColor = Colors.flattenAlpha(getPrimaryTextColor(p), pillColor);
5386             contentView.setInt(R.id.expand_button, "setDefaultTextColor", textColor);
5387             contentView.setInt(R.id.expand_button, "setDefaultPillColor", pillColor);
5388             // Use different highlighted colors for conversations' unread count
5389             if (p.mHighlightExpander) {
5390                 pillColor = Colors.flattenAlpha(getPrimaryAccentColor(p), bgColor);
5391                 textColor = Colors.flattenAlpha(bgColor, pillColor);
5392             }
5393             contentView.setInt(R.id.expand_button, "setHighlightTextColor", textColor);
5394             contentView.setInt(R.id.expand_button, "setHighlightPillColor", pillColor);
5395         }
5396 
bindHeaderChronometerAndTime(RemoteViews contentView, StandardTemplateParams p, boolean hasTextToLeft)5397         private void bindHeaderChronometerAndTime(RemoteViews contentView,
5398                 StandardTemplateParams p, boolean hasTextToLeft) {
5399             if (!p.mHideTime && showsTimeOrChronometer()) {
5400                 if (hasTextToLeft) {
5401                     contentView.setViewVisibility(R.id.time_divider, View.VISIBLE);
5402                     setTextViewColorSecondary(contentView, R.id.time_divider, p);
5403                 }
5404                 if (mN.extras.getBoolean(EXTRA_SHOW_CHRONOMETER)) {
5405                     contentView.setViewVisibility(R.id.chronometer, View.VISIBLE);
5406                     contentView.setLong(R.id.chronometer, "setBase",
5407                             mN.when + (SystemClock.elapsedRealtime() - System.currentTimeMillis()));
5408                     contentView.setBoolean(R.id.chronometer, "setStarted", true);
5409                     boolean countsDown = mN.extras.getBoolean(EXTRA_CHRONOMETER_COUNT_DOWN);
5410                     contentView.setChronometerCountDown(R.id.chronometer, countsDown);
5411                     setTextViewColorSecondary(contentView, R.id.chronometer, p);
5412                 } else {
5413                     contentView.setViewVisibility(R.id.time, View.VISIBLE);
5414                     contentView.setLong(R.id.time, "setTime", mN.when);
5415                     setTextViewColorSecondary(contentView, R.id.time, p);
5416                 }
5417             } else {
5418                 // We still want a time to be set but gone, such that we can show and hide it
5419                 // on demand in case it's a child notification without anything in the header
5420                 contentView.setLong(R.id.time, "setTime", mN.when != 0 ? mN.when : mN.creationTime);
5421                 setTextViewColorSecondary(contentView, R.id.time, p);
5422             }
5423         }
5424 
5425         /**
5426          * @return true if the header text will be visible
5427          */
bindHeaderText(RemoteViews contentView, StandardTemplateParams p, boolean hasTextToLeft)5428         private boolean bindHeaderText(RemoteViews contentView, StandardTemplateParams p,
5429                 boolean hasTextToLeft) {
5430             if (p.mHideSubText) {
5431                 return false;
5432             }
5433             CharSequence summaryText = p.summaryText;
5434             if (summaryText == null && mStyle != null && mStyle.mSummaryTextSet
5435                     && mStyle.hasSummaryInHeader()) {
5436                 summaryText = mStyle.mSummaryText;
5437             }
5438             if (summaryText == null
5439                     && mContext.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N
5440                     && mN.extras.getCharSequence(EXTRA_INFO_TEXT) != null) {
5441                 summaryText = mN.extras.getCharSequence(EXTRA_INFO_TEXT);
5442             }
5443             if (!TextUtils.isEmpty(summaryText)) {
5444                 // TODO: Remove the span entirely to only have the string with propper formating.
5445                 contentView.setTextViewText(R.id.header_text, processTextSpans(
5446                         processLegacyText(summaryText)));
5447                 setTextViewColorSecondary(contentView, R.id.header_text, p);
5448                 contentView.setViewVisibility(R.id.header_text, View.VISIBLE);
5449                 if (hasTextToLeft) {
5450                     contentView.setViewVisibility(R.id.header_text_divider, View.VISIBLE);
5451                     setTextViewColorSecondary(contentView, R.id.header_text_divider, p);
5452                 }
5453                 return true;
5454             }
5455             return false;
5456         }
5457 
5458         /**
5459          * @return true if the secondary header text will be visible
5460          */
bindHeaderTextSecondary(RemoteViews contentView, StandardTemplateParams p, boolean hasTextToLeft)5461         private boolean bindHeaderTextSecondary(RemoteViews contentView, StandardTemplateParams p,
5462                 boolean hasTextToLeft) {
5463             if (p.mHideSubText) {
5464                 return false;
5465             }
5466             if (!TextUtils.isEmpty(p.headerTextSecondary)) {
5467                 contentView.setTextViewText(R.id.header_text_secondary, processTextSpans(
5468                         processLegacyText(p.headerTextSecondary)));
5469                 setTextViewColorSecondary(contentView, R.id.header_text_secondary, p);
5470                 contentView.setViewVisibility(R.id.header_text_secondary, View.VISIBLE);
5471                 if (hasTextToLeft) {
5472                     contentView.setViewVisibility(R.id.header_text_secondary_divider, View.VISIBLE);
5473                     setTextViewColorSecondary(contentView, R.id.header_text_secondary_divider, p);
5474                 }
5475                 return true;
5476             }
5477             return false;
5478         }
5479 
5480         /**
5481          * @hide
5482          */
5483         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
loadHeaderAppName()5484         public String loadHeaderAppName() {
5485             CharSequence name = null;
5486             final PackageManager pm = mContext.getPackageManager();
5487             if (mN.extras.containsKey(EXTRA_SUBSTITUTE_APP_NAME)) {
5488                 // only system packages which lump together a bunch of unrelated stuff
5489                 // may substitute a different name to make the purpose of the
5490                 // notification more clear. the correct package label should always
5491                 // be accessible via SystemUI.
5492                 final String pkg = mContext.getPackageName();
5493                 final String subName = mN.extras.getString(EXTRA_SUBSTITUTE_APP_NAME);
5494                 if (PackageManager.PERMISSION_GRANTED == pm.checkPermission(
5495                         android.Manifest.permission.SUBSTITUTE_NOTIFICATION_APP_NAME, pkg)) {
5496                     name = subName;
5497                 } else {
5498                     Log.w(TAG, "warning: pkg "
5499                             + pkg + " attempting to substitute app name '" + subName
5500                             + "' without holding perm "
5501                             + android.Manifest.permission.SUBSTITUTE_NOTIFICATION_APP_NAME);
5502                 }
5503             }
5504             if (TextUtils.isEmpty(name)) {
5505                 name = pm.getApplicationLabel(mContext.getApplicationInfo());
5506             }
5507             if (TextUtils.isEmpty(name)) {
5508                 // still nothing?
5509                 return null;
5510             }
5511 
5512             return String.valueOf(name);
5513         }
5514 
5515         /**
5516          * @return true if the app name will be visible
5517          */
bindHeaderAppName(RemoteViews contentView, StandardTemplateParams p, boolean force)5518         private boolean bindHeaderAppName(RemoteViews contentView, StandardTemplateParams p,
5519                 boolean force) {
5520             if (p.mViewType == StandardTemplateParams.VIEW_TYPE_MINIMIZED && !force) {
5521                 // unless the force flag is set, don't show the app name in the minimized state.
5522                 return false;
5523             }
5524             if (p.mHeaderless && p.hasTitle()) {
5525                 // the headerless template will have the TITLE in this position; return true to
5526                 // keep the divider visible between that title and the next text element.
5527                 return true;
5528             }
5529             if (p.mHideAppName) {
5530                 // The app name is being hidden, so we definitely want to return here.
5531                 // Assume that there is a title which will replace it in the header.
5532                 return p.hasTitle();
5533             }
5534             contentView.setViewVisibility(R.id.app_name_text, View.VISIBLE);
5535             contentView.setTextViewText(R.id.app_name_text, loadHeaderAppName());
5536             contentView.setTextColor(R.id.app_name_text, getSecondaryTextColor(p));
5537             return true;
5538         }
5539 
5540         /**
5541          * Determines if the notification should be colorized *for the purposes of applying colors*.
5542          * If this is the minimized view of a colorized notification, or if the app did not provide
5543          * a color to colorize with, this will return false so that internal coloring logic can
5544          * still render the notification normally.
5545          */
isBackgroundColorized(StandardTemplateParams p)5546         private boolean isBackgroundColorized(StandardTemplateParams p) {
5547             return p.allowColorization && mN.color != COLOR_DEFAULT && mN.isColorized();
5548         }
5549 
isCallActionColorCustomizable()5550         private boolean isCallActionColorCustomizable() {
5551             // NOTE: this doesn't need to check StandardTemplateParams.allowColorization because
5552             //  that is only used for disallowing colorization of headers for the minimized state,
5553             //  and neither of those conditions applies when showing actions.
5554             //  Not requiring StandardTemplateParams as an argument simplifies the creation process.
5555             return mN.color != COLOR_DEFAULT && mN.isColorized()
5556                     && mContext.getResources().getBoolean(
5557                     R.bool.config_callNotificationActionColorsRequireColorized);
5558         }
5559 
bindSmallIcon(RemoteViews contentView, StandardTemplateParams p)5560         private void bindSmallIcon(RemoteViews contentView, StandardTemplateParams p) {
5561             if (mN.mSmallIcon == null && mN.icon != 0) {
5562                 mN.mSmallIcon = Icon.createWithResource(mContext, mN.icon);
5563             }
5564             contentView.setImageViewIcon(R.id.icon, mN.mSmallIcon);
5565             contentView.setInt(R.id.icon, "setImageLevel", mN.iconLevel);
5566             processSmallIconColor(mN.mSmallIcon, contentView, p);
5567         }
5568 
5569         /**
5570          * @return true if the built notification will show the time or the chronometer; false
5571          *         otherwise
5572          */
showsTimeOrChronometer()5573         private boolean showsTimeOrChronometer() {
5574             return mN.showsTime() || mN.showsChronometer();
5575         }
5576 
resetStandardTemplateWithActions(RemoteViews big)5577         private void resetStandardTemplateWithActions(RemoteViews big) {
5578             // actions_container is only reset when there are no actions to avoid focus issues with
5579             // remote inputs.
5580             big.setViewVisibility(R.id.actions, View.GONE);
5581             big.removeAllViews(R.id.actions);
5582 
5583             big.setViewVisibility(R.id.notification_material_reply_container, View.GONE);
5584             big.setTextViewText(R.id.notification_material_reply_text_1, null);
5585             big.setViewVisibility(R.id.notification_material_reply_text_1_container, View.GONE);
5586             big.setViewVisibility(R.id.notification_material_reply_progress, View.GONE);
5587 
5588             big.setViewVisibility(R.id.notification_material_reply_text_2, View.GONE);
5589             big.setTextViewText(R.id.notification_material_reply_text_2, null);
5590             big.setViewVisibility(R.id.notification_material_reply_text_3, View.GONE);
5591             big.setTextViewText(R.id.notification_material_reply_text_3, null);
5592 
5593             // This may get erased by bindSnoozeAction
5594             big.setViewLayoutMarginDimen(R.id.notification_action_list_margin_target,
5595                     RemoteViews.MARGIN_BOTTOM, R.dimen.notification_content_margin);
5596         }
5597 
bindSnoozeAction(RemoteViews big, StandardTemplateParams p)5598         private void bindSnoozeAction(RemoteViews big, StandardTemplateParams p) {
5599             boolean hideSnoozeButton = mN.isForegroundService() || mN.fullScreenIntent != null
5600                     || isBackgroundColorized(p)
5601                     || p.mViewType != StandardTemplateParams.VIEW_TYPE_BIG;
5602             big.setBoolean(R.id.snooze_button, "setEnabled", !hideSnoozeButton);
5603             if (hideSnoozeButton) {
5604                 // Only hide; NotificationContentView will show it when it adds the click listener
5605                 big.setViewVisibility(R.id.snooze_button, View.GONE);
5606             }
5607 
5608             final boolean snoozeEnabled = !hideSnoozeButton
5609                     && mContext.getContentResolver() != null
5610                     && isSnoozeSettingEnabled();
5611             if (snoozeEnabled) {
5612                 big.setViewLayoutMarginDimen(R.id.notification_action_list_margin_target,
5613                         RemoteViews.MARGIN_BOTTOM, 0);
5614             }
5615         }
5616 
isSnoozeSettingEnabled()5617         private boolean isSnoozeSettingEnabled() {
5618             try {
5619                 return Settings.Secure.getInt(mContext.getContentResolver(),
5620                     Settings.Secure.SHOW_NOTIFICATION_SNOOZE, 0) == 1;
5621             } catch (SecurityException ex) {
5622                 // Most 3p apps can't access this snooze setting, so their NotificationListeners
5623                 // would be unable to create notification views if we propagated this exception.
5624                 return false;
5625             }
5626         }
5627 
5628         /**
5629          * Returns the actions that are not contextual.
5630          */
getNonContextualActions()5631         private @NonNull List<Notification.Action> getNonContextualActions() {
5632             if (mActions == null) return Collections.emptyList();
5633             List<Notification.Action> standardActions = new ArrayList<>();
5634             for (Notification.Action action : mActions) {
5635                 if (!action.isContextual()) {
5636                     standardActions.add(action);
5637                 }
5638             }
5639             return standardActions;
5640         }
5641 
applyStandardTemplateWithActions(int layoutId, StandardTemplateParams p, TemplateBindResult result)5642         private RemoteViews applyStandardTemplateWithActions(int layoutId,
5643                 StandardTemplateParams p, TemplateBindResult result) {
5644             RemoteViews big = applyStandardTemplate(layoutId, p, result);
5645 
5646             resetStandardTemplateWithActions(big);
5647             bindSnoozeAction(big, p);
5648             // color the snooze and bubble actions with the theme color
5649             ColorStateList actionColor = ColorStateList.valueOf(getStandardActionColor(p));
5650             big.setColorStateList(R.id.snooze_button, "setImageTintList", actionColor);
5651             big.setColorStateList(R.id.bubble_button, "setImageTintList", actionColor);
5652 
5653             boolean validRemoteInput = false;
5654 
5655             // In the UI, contextual actions appear separately from the standard actions, so we
5656             // filter them out here.
5657             List<Notification.Action> nonContextualActions = getNonContextualActions();
5658 
5659             int numActions = Math.min(nonContextualActions.size(), MAX_ACTION_BUTTONS);
5660             boolean emphazisedMode = mN.fullScreenIntent != null || p.mCallStyleActions;
5661             if (p.mCallStyleActions) {
5662                 // Clear view padding to allow buttons to start on the left edge.
5663                 // This must be done before 'setEmphasizedMode' which sets top/bottom margins.
5664                 big.setViewPadding(R.id.actions, 0, 0, 0, 0);
5665                 // Add an optional indent that will make buttons start at the correct column when
5666                 // there is enough space to do so (and fall back to the left edge if not).
5667                 big.setInt(R.id.actions, "setCollapsibleIndentDimen",
5668                         R.dimen.call_notification_collapsible_indent);
5669             }
5670             big.setBoolean(R.id.actions, "setEmphasizedMode", emphazisedMode);
5671             if (numActions > 0 && !p.mHideActions) {
5672                 big.setViewVisibility(R.id.actions_container, View.VISIBLE);
5673                 big.setViewVisibility(R.id.actions, View.VISIBLE);
5674                 big.setViewLayoutMarginDimen(R.id.notification_action_list_margin_target,
5675                         RemoteViews.MARGIN_BOTTOM, 0);
5676                 for (int i = 0; i < numActions; i++) {
5677                     Action action = nonContextualActions.get(i);
5678 
5679                     boolean actionHasValidInput = hasValidRemoteInput(action);
5680                     validRemoteInput |= actionHasValidInput;
5681 
5682                     final RemoteViews button = generateActionButton(action, emphazisedMode, p);
5683                     if (actionHasValidInput && !emphazisedMode) {
5684                         // Clear the drawable
5685                         button.setInt(R.id.action0, "setBackgroundResource", 0);
5686                     }
5687                     if (emphazisedMode && i > 0) {
5688                         // Clear start margin from non-first buttons to reduce the gap between them.
5689                         //  (8dp remaining gap is from all buttons' standard 4dp inset).
5690                         button.setViewLayoutMarginDimen(R.id.action0, RemoteViews.MARGIN_START, 0);
5691                     }
5692                     big.addView(R.id.actions, button);
5693                 }
5694             } else {
5695                 big.setViewVisibility(R.id.actions_container, View.GONE);
5696             }
5697 
5698             RemoteInputHistoryItem[] replyText = getParcelableArrayFromBundle(
5699                     mN.extras, EXTRA_REMOTE_INPUT_HISTORY_ITEMS, RemoteInputHistoryItem.class);
5700             if (validRemoteInput && replyText != null && replyText.length > 0
5701                     && !TextUtils.isEmpty(replyText[0].getText())
5702                     && p.maxRemoteInputHistory > 0) {
5703                 boolean showSpinner = mN.extras.getBoolean(EXTRA_SHOW_REMOTE_INPUT_SPINNER);
5704                 big.setViewVisibility(R.id.notification_material_reply_container, View.VISIBLE);
5705                 big.setViewVisibility(R.id.notification_material_reply_text_1_container,
5706                         View.VISIBLE);
5707                 big.setTextViewText(R.id.notification_material_reply_text_1,
5708                         processTextSpans(replyText[0].getText()));
5709                 setTextViewColorSecondary(big, R.id.notification_material_reply_text_1, p);
5710                 big.setViewVisibility(R.id.notification_material_reply_progress,
5711                         showSpinner ? View.VISIBLE : View.GONE);
5712                 big.setProgressIndeterminateTintList(
5713                         R.id.notification_material_reply_progress,
5714                         ColorStateList.valueOf(getPrimaryAccentColor(p)));
5715 
5716                 if (replyText.length > 1 && !TextUtils.isEmpty(replyText[1].getText())
5717                         && p.maxRemoteInputHistory > 1) {
5718                     big.setViewVisibility(R.id.notification_material_reply_text_2, View.VISIBLE);
5719                     big.setTextViewText(R.id.notification_material_reply_text_2,
5720                             processTextSpans(replyText[1].getText()));
5721                     setTextViewColorSecondary(big, R.id.notification_material_reply_text_2, p);
5722 
5723                     if (replyText.length > 2 && !TextUtils.isEmpty(replyText[2].getText())
5724                             && p.maxRemoteInputHistory > 2) {
5725                         big.setViewVisibility(
5726                                 R.id.notification_material_reply_text_3, View.VISIBLE);
5727                         big.setTextViewText(R.id.notification_material_reply_text_3,
5728                                 processTextSpans(replyText[2].getText()));
5729                         setTextViewColorSecondary(big, R.id.notification_material_reply_text_3, p);
5730                     }
5731                 }
5732             }
5733 
5734             return big;
5735         }
5736 
hasValidRemoteInput(Action action)5737         private boolean hasValidRemoteInput(Action action) {
5738             if (TextUtils.isEmpty(action.title) || action.actionIntent == null) {
5739                 // Weird actions
5740                 return false;
5741             }
5742 
5743             RemoteInput[] remoteInputs = action.getRemoteInputs();
5744             if (remoteInputs == null) {
5745                 return false;
5746             }
5747 
5748             for (RemoteInput r : remoteInputs) {
5749                 CharSequence[] choices = r.getChoices();
5750                 if (r.getAllowFreeFormInput() || (choices != null && choices.length != 0)) {
5751                     return true;
5752                 }
5753             }
5754             return false;
5755         }
5756 
5757         /**
5758          * Construct a RemoteViews for the final 1U notification layout. In order:
5759          *   1. Custom contentView from the caller
5760          *   2. Style's proposed content view
5761          *   3. Standard template view
5762          */
createContentView()5763         public RemoteViews createContentView() {
5764             return createContentView(false /* increasedheight */ );
5765         }
5766 
5767         // This code is executed on behalf of other apps' notifications, sometimes even by 3p apps,
5768         // a use case that is not supported by the Compat Framework library.  Workarounds to resolve
5769         // the change's state in NotificationManagerService were very complex. While it's possible
5770         // apps can detect the change, it's most likely that the changes will simply result in
5771         // visual regressions.
5772         @SuppressWarnings("AndroidFrameworkCompatChange")
fullyCustomViewRequiresDecoration(boolean fromStyle)5773         private boolean fullyCustomViewRequiresDecoration(boolean fromStyle) {
5774             // Custom views which come from a platform style class are safe, and thus do not need to
5775             // be wrapped.  Any subclass of those styles has the opportunity to make arbitrary
5776             // changes to the RemoteViews, and thus can't be trusted as a fully vetted view.
5777             if (fromStyle && PLATFORM_STYLE_CLASSES.contains(mStyle.getClass())) {
5778                 return false;
5779             }
5780             return mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.S;
5781         }
5782 
minimallyDecoratedContentView(@onNull RemoteViews customContent)5783         private RemoteViews minimallyDecoratedContentView(@NonNull RemoteViews customContent) {
5784             StandardTemplateParams p = mParams.reset()
5785                     .viewType(StandardTemplateParams.VIEW_TYPE_NORMAL)
5786                     .decorationType(StandardTemplateParams.DECORATION_MINIMAL)
5787                     .fillTextsFrom(this);
5788             TemplateBindResult result = new TemplateBindResult();
5789             RemoteViews standard = applyStandardTemplate(getBaseLayoutResource(), p, result);
5790             buildCustomContentIntoTemplate(mContext, standard, customContent,
5791                     p, result);
5792             return standard;
5793         }
5794 
minimallyDecoratedBigContentView(@onNull RemoteViews customContent)5795         private RemoteViews minimallyDecoratedBigContentView(@NonNull RemoteViews customContent) {
5796             StandardTemplateParams p = mParams.reset()
5797                     .viewType(StandardTemplateParams.VIEW_TYPE_BIG)
5798                     .decorationType(StandardTemplateParams.DECORATION_MINIMAL)
5799                     .fillTextsFrom(this);
5800             TemplateBindResult result = new TemplateBindResult();
5801             RemoteViews standard = applyStandardTemplateWithActions(getBigBaseLayoutResource(),
5802                     p, result);
5803             buildCustomContentIntoTemplate(mContext, standard, customContent,
5804                     p, result);
5805             return standard;
5806         }
5807 
minimallyDecoratedHeadsUpContentView( @onNull RemoteViews customContent)5808         private RemoteViews minimallyDecoratedHeadsUpContentView(
5809                 @NonNull RemoteViews customContent) {
5810             StandardTemplateParams p = mParams.reset()
5811                     .viewType(StandardTemplateParams.VIEW_TYPE_HEADS_UP)
5812                     .decorationType(StandardTemplateParams.DECORATION_MINIMAL)
5813                     .fillTextsFrom(this);
5814             TemplateBindResult result = new TemplateBindResult();
5815             RemoteViews standard = applyStandardTemplateWithActions(getHeadsUpBaseLayoutResource(),
5816                     p, result);
5817             buildCustomContentIntoTemplate(mContext, standard, customContent,
5818                     p, result);
5819             return standard;
5820         }
5821 
5822         /**
5823          * Construct a RemoteViews for the smaller content view.
5824          *
5825          *   @param increasedHeight true if this layout be created with an increased height. Some
5826          *   styles may support showing more then just that basic 1U size
5827          *   and the system may decide to render important notifications
5828          *   slightly bigger even when collapsed.
5829          *
5830          *   @hide
5831          */
createContentView(boolean increasedHeight)5832         public RemoteViews createContentView(boolean increasedHeight) {
5833             if (useExistingRemoteView(mN.contentView)) {
5834                 return fullyCustomViewRequiresDecoration(false /* fromStyle */)
5835                         ? minimallyDecoratedContentView(mN.contentView) : mN.contentView;
5836             } else if (mStyle != null) {
5837                 final RemoteViews styleView = mStyle.makeContentView(increasedHeight);
5838                 if (styleView != null) {
5839                     return fullyCustomViewRequiresDecoration(true /* fromStyle */)
5840                             ? minimallyDecoratedContentView(styleView) : styleView;
5841                 }
5842             }
5843             StandardTemplateParams p = mParams.reset()
5844                     .viewType(StandardTemplateParams.VIEW_TYPE_NORMAL)
5845                     .fillTextsFrom(this);
5846             return applyStandardTemplate(getBaseLayoutResource(), p, null /* result */);
5847         }
5848 
useExistingRemoteView(RemoteViews customContent)5849         private boolean useExistingRemoteView(RemoteViews customContent) {
5850             if (customContent == null) {
5851                 return false;
5852             }
5853             if (styleDisplaysCustomViewInline()) {
5854                 // the provided custom view is intended to be wrapped by the style.
5855                 return false;
5856             }
5857             if (fullyCustomViewRequiresDecoration(false)
5858                     && STANDARD_LAYOUTS.contains(customContent.getLayoutId())) {
5859                 // If the app's custom views are objects returned from Builder.create*ContentView()
5860                 // then the app is most likely attempting to spoof the user.  Even if they are not,
5861                 // the result would be broken (b/189189308) so we will ignore it.
5862                 Log.w(TAG, "For apps targeting S, a custom content view that is a modified "
5863                         + "version of any standard layout is disallowed.");
5864                 return false;
5865             }
5866             return true;
5867         }
5868 
5869         /**
5870          * Construct a RemoteViews for the final big notification layout.
5871          */
createBigContentView()5872         public RemoteViews createBigContentView() {
5873             RemoteViews result = null;
5874             if (useExistingRemoteView(mN.bigContentView)) {
5875                 return fullyCustomViewRequiresDecoration(false /* fromStyle */)
5876                         ? minimallyDecoratedBigContentView(mN.bigContentView) : mN.bigContentView;
5877             }
5878             if (mStyle != null) {
5879                 result = mStyle.makeBigContentView();
5880                 if (fullyCustomViewRequiresDecoration(true /* fromStyle */)) {
5881                     result = minimallyDecoratedBigContentView(result);
5882                 }
5883             }
5884             if (result == null) {
5885                 if (bigContentViewRequired()) {
5886                     StandardTemplateParams p = mParams.reset()
5887                             .viewType(StandardTemplateParams.VIEW_TYPE_BIG)
5888                             .allowTextWithProgress(true)
5889                             .fillTextsFrom(this);
5890                     result = applyStandardTemplateWithActions(getBigBaseLayoutResource(), p,
5891                             null /* result */);
5892                 }
5893             }
5894             makeHeaderExpanded(result);
5895             return result;
5896         }
5897 
5898         // This code is executed on behalf of other apps' notifications, sometimes even by 3p apps,
5899         // a use case that is not supported by the Compat Framework library.  Workarounds to resolve
5900         // the change's state in NotificationManagerService were very complex. While it's possible
5901         // apps can detect the change, it's most likely that the changes will simply result in
5902         // visual regressions.
5903         @SuppressWarnings("AndroidFrameworkCompatChange")
bigContentViewRequired()5904         private boolean bigContentViewRequired() {
5905             if (mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.S) {
5906                 return true;
5907             }
5908             // Notifications with contentView and without a bigContentView, style, or actions would
5909             // not have an expanded state before S, so showing the standard template expanded state
5910             // usually looks wrong, so we keep it simple and don't show the expanded state.
5911             boolean exempt = mN.contentView != null && mN.bigContentView == null
5912                     && mStyle == null && mActions.size() == 0;
5913             return !exempt;
5914         }
5915 
5916         /**
5917          * Construct a RemoteViews for the final notification header only. This will not be
5918          * colorized.
5919          *
5920          * @hide
5921          */
makeNotificationGroupHeader()5922         public RemoteViews makeNotificationGroupHeader() {
5923             return makeNotificationHeader(mParams.reset()
5924                     .viewType(StandardTemplateParams.VIEW_TYPE_GROUP_HEADER)
5925                     .fillTextsFrom(this));
5926         }
5927 
5928         /**
5929          * Construct a RemoteViews for the final notification header only. This will not be
5930          * colorized.
5931          *
5932          * @param p the template params to inflate this with
5933          */
makeNotificationHeader(StandardTemplateParams p)5934         private RemoteViews makeNotificationHeader(StandardTemplateParams p) {
5935             // Headers on their own are never colorized
5936             p.disallowColorization();
5937             RemoteViews header = new BuilderRemoteViews(mContext.getApplicationInfo(),
5938                     R.layout.notification_template_header);
5939             resetNotificationHeader(header);
5940             bindNotificationHeader(header, p);
5941             return header;
5942         }
5943 
5944         /**
5945          * Construct a RemoteViews for the ambient version of the notification.
5946          *
5947          * @hide
5948          */
makeAmbientNotification()5949         public RemoteViews makeAmbientNotification() {
5950             RemoteViews headsUpContentView = createHeadsUpContentView(false /* increasedHeight */);
5951             if (headsUpContentView != null) {
5952                 return headsUpContentView;
5953             }
5954             return createContentView();
5955         }
5956 
5957         /**
5958          * Adapt the Notification header if this view is used as an expanded view.
5959          *
5960          * @hide
5961          */
makeHeaderExpanded(RemoteViews result)5962         public static void makeHeaderExpanded(RemoteViews result) {
5963             if (result != null) {
5964                 result.setBoolean(R.id.expand_button, "setExpanded", true);
5965             }
5966         }
5967 
5968         /**
5969          * Construct a RemoteViews for the final heads-up notification layout.
5970          *
5971          * @param increasedHeight true if this layout be created with an increased height. Some
5972          * styles may support showing more then just that basic 1U size
5973          * and the system may decide to render important notifications
5974          * slightly bigger even when collapsed.
5975          *
5976          * @hide
5977          */
createHeadsUpContentView(boolean increasedHeight)5978         public RemoteViews createHeadsUpContentView(boolean increasedHeight) {
5979             if (useExistingRemoteView(mN.headsUpContentView)) {
5980                 return fullyCustomViewRequiresDecoration(false /* fromStyle */)
5981                         ? minimallyDecoratedHeadsUpContentView(mN.headsUpContentView)
5982                         : mN.headsUpContentView;
5983             } else if (mStyle != null) {
5984                 final RemoteViews styleView = mStyle.makeHeadsUpContentView(increasedHeight);
5985                 if (styleView != null) {
5986                     return fullyCustomViewRequiresDecoration(true /* fromStyle */)
5987                             ? minimallyDecoratedHeadsUpContentView(styleView) : styleView;
5988                 }
5989             } else if (mActions.size() == 0) {
5990                 return null;
5991             }
5992 
5993             // We only want at most a single remote input history to be shown here, otherwise
5994             // the content would become squished.
5995             StandardTemplateParams p = mParams.reset()
5996                     .viewType(StandardTemplateParams.VIEW_TYPE_HEADS_UP)
5997                     .fillTextsFrom(this)
5998                     .setMaxRemoteInputHistory(1);
5999             return applyStandardTemplateWithActions(getHeadsUpBaseLayoutResource(), p,
6000                     null /* result */);
6001         }
6002 
6003         /**
6004          * Construct a RemoteViews for the final heads-up notification layout.
6005          */
createHeadsUpContentView()6006         public RemoteViews createHeadsUpContentView() {
6007             return createHeadsUpContentView(false /* useIncreasedHeight */);
6008         }
6009 
6010         /**
6011          * Construct a RemoteViews for the display in public contexts like on the lockscreen.
6012          *
6013          * @param isLowPriority is this notification low priority
6014          * @hide
6015          */
6016         @UnsupportedAppUsage
makePublicContentView(boolean isLowPriority)6017         public RemoteViews makePublicContentView(boolean isLowPriority) {
6018             if (mN.publicVersion != null) {
6019                 final Builder builder = recoverBuilder(mContext, mN.publicVersion);
6020                 return builder.createContentView();
6021             }
6022             Bundle savedBundle = mN.extras;
6023             Style style = mStyle;
6024             mStyle = null;
6025             Icon largeIcon = mN.mLargeIcon;
6026             mN.mLargeIcon = null;
6027             Bitmap largeIconLegacy = mN.largeIcon;
6028             mN.largeIcon = null;
6029             ArrayList<Action> actions = mActions;
6030             mActions = new ArrayList<>();
6031             Bundle publicExtras = new Bundle();
6032             publicExtras.putBoolean(EXTRA_SHOW_WHEN,
6033                     savedBundle.getBoolean(EXTRA_SHOW_WHEN));
6034             publicExtras.putBoolean(EXTRA_SHOW_CHRONOMETER,
6035                     savedBundle.getBoolean(EXTRA_SHOW_CHRONOMETER));
6036             publicExtras.putBoolean(EXTRA_CHRONOMETER_COUNT_DOWN,
6037                     savedBundle.getBoolean(EXTRA_CHRONOMETER_COUNT_DOWN));
6038             String appName = savedBundle.getString(EXTRA_SUBSTITUTE_APP_NAME);
6039             if (appName != null) {
6040                 publicExtras.putString(EXTRA_SUBSTITUTE_APP_NAME, appName);
6041             }
6042             mN.extras = publicExtras;
6043             RemoteViews view;
6044             StandardTemplateParams params = mParams.reset()
6045                     .viewType(StandardTemplateParams.VIEW_TYPE_PUBLIC)
6046                     .fillTextsFrom(this);
6047             if (isLowPriority) {
6048                 params.highlightExpander(false);
6049             }
6050             view = makeNotificationHeader(params);
6051             view.setBoolean(R.id.notification_header, "setExpandOnlyOnButton", true);
6052             mN.extras = savedBundle;
6053             mN.mLargeIcon = largeIcon;
6054             mN.largeIcon = largeIconLegacy;
6055             mActions = actions;
6056             mStyle = style;
6057             return view;
6058         }
6059 
6060         /**
6061          * Construct a content view for the display when low - priority
6062          *
6063          * @param useRegularSubtext uses the normal subtext set if there is one available. Otherwise
6064          *                          a new subtext is created consisting of the content of the
6065          *                          notification.
6066          * @hide
6067          */
makeLowPriorityContentView(boolean useRegularSubtext)6068         public RemoteViews makeLowPriorityContentView(boolean useRegularSubtext) {
6069             StandardTemplateParams p = mParams.reset()
6070                     .viewType(StandardTemplateParams.VIEW_TYPE_MINIMIZED)
6071                     .highlightExpander(false)
6072                     .fillTextsFrom(this);
6073             if (!useRegularSubtext || TextUtils.isEmpty(p.summaryText)) {
6074                 p.summaryText(createSummaryText());
6075             }
6076             RemoteViews header = makeNotificationHeader(p);
6077             header.setBoolean(R.id.notification_header, "setAcceptAllTouches", true);
6078             // The low priority header has no app name and shows the text
6079             header.setBoolean(R.id.notification_header, "styleTextAsTitle", true);
6080             return header;
6081         }
6082 
createSummaryText()6083         private CharSequence createSummaryText() {
6084             CharSequence titleText = mN.extras.getCharSequence(Notification.EXTRA_TITLE);
6085             if (USE_ONLY_TITLE_IN_LOW_PRIORITY_SUMMARY) {
6086                 return titleText;
6087             }
6088             SpannableStringBuilder summary = new SpannableStringBuilder();
6089             if (titleText == null) {
6090                 titleText = mN.extras.getCharSequence(Notification.EXTRA_TITLE_BIG);
6091             }
6092             BidiFormatter bidi = BidiFormatter.getInstance();
6093             if (titleText != null) {
6094                 summary.append(bidi.unicodeWrap(titleText));
6095             }
6096             CharSequence contentText = mN.extras.getCharSequence(Notification.EXTRA_TEXT);
6097             if (titleText != null && contentText != null) {
6098                 summary.append(bidi.unicodeWrap(mContext.getText(
6099                         R.string.notification_header_divider_symbol_with_spaces)));
6100             }
6101             if (contentText != null) {
6102                 summary.append(bidi.unicodeWrap(contentText));
6103             }
6104             return summary;
6105         }
6106 
generateActionButton(Action action, boolean emphasizedMode, StandardTemplateParams p)6107         private RemoteViews generateActionButton(Action action, boolean emphasizedMode,
6108                 StandardTemplateParams p) {
6109             final boolean tombstone = (action.actionIntent == null);
6110             RemoteViews button = new BuilderRemoteViews(mContext.getApplicationInfo(),
6111                     emphasizedMode ? getEmphasizedActionLayoutResource()
6112                             : tombstone ? getActionTombstoneLayoutResource()
6113                                     : getActionLayoutResource());
6114             if (!tombstone) {
6115                 button.setOnClickPendingIntent(R.id.action0, action.actionIntent);
6116             }
6117             button.setContentDescription(R.id.action0, action.title);
6118             if (action.mRemoteInputs != null) {
6119                 button.setRemoteInputs(R.id.action0, action.mRemoteInputs);
6120             }
6121             if (emphasizedMode) {
6122                 // change the background bgColor
6123                 CharSequence title = action.title;
6124                 ColorStateList[] outResultColor = new ColorStateList[1];
6125                 int buttonFillColor = getColors(p).getSecondaryAccentColor();
6126                 if (isLegacy()) {
6127                     title = ContrastColorUtil.clearColorSpans(title);
6128                 } else {
6129                     int notifBackgroundColor = getColors(p).getBackgroundColor();
6130                     title = ensureColorSpanContrast(title, notifBackgroundColor, outResultColor);
6131                 }
6132                 button.setTextViewText(R.id.action0, processTextSpans(title));
6133                 boolean hasColorOverride = outResultColor[0] != null;
6134                 if (hasColorOverride) {
6135                     // There's a span spanning the full text, let's take it and use it as the
6136                     // background color
6137                     buttonFillColor = outResultColor[0].getDefaultColor();
6138                 }
6139                 final int textColor = ContrastColorUtil.resolvePrimaryColor(mContext,
6140                         buttonFillColor, mInNightMode);
6141                 button.setTextColor(R.id.action0, textColor);
6142                 // We only want about 20% alpha for the ripple
6143                 final int rippleColor = (textColor & 0x00ffffff) | 0x33000000;
6144                 button.setColorStateList(R.id.action0, "setRippleColor",
6145                         ColorStateList.valueOf(rippleColor));
6146                 button.setColorStateList(R.id.action0, "setButtonBackground",
6147                         ColorStateList.valueOf(buttonFillColor));
6148                 if (p.mCallStyleActions) {
6149                     button.setImageViewIcon(R.id.action0, action.getIcon());
6150                     boolean priority = action.getExtras().getBoolean(CallStyle.KEY_ACTION_PRIORITY);
6151                     button.setBoolean(R.id.action0, "setIsPriority", priority);
6152                     int minWidthDimen =
6153                             priority ? R.dimen.call_notification_system_action_min_width : 0;
6154                     button.setIntDimen(R.id.action0, "setMinimumWidth", minWidthDimen);
6155                 }
6156             } else {
6157                 button.setTextViewText(R.id.action0, processTextSpans(
6158                         processLegacyText(action.title)));
6159                 button.setTextColor(R.id.action0, getStandardActionColor(p));
6160             }
6161             // CallStyle notifications add action buttons which don't actually exist in mActions,
6162             //  so we have to omit the index in that case.
6163             int actionIndex = mActions.indexOf(action);
6164             if (actionIndex != -1) {
6165                 button.setIntTag(R.id.action0, R.id.notification_action_index_tag, actionIndex);
6166             }
6167             return button;
6168         }
6169 
6170         /**
6171          * Ensures contrast on color spans against a background color. also returns the color of the
6172          * text if a span was found that spans over the whole text.
6173          *
6174          * @param charSequence the charSequence on which the spans are
6175          * @param background the background color to ensure the contrast against
6176          * @param outResultColor an array in which a color will be returned as the first element if
6177          *                    there exists a full length color span.
6178          * @return the contrasted charSequence
6179          */
ensureColorSpanContrast(CharSequence charSequence, int background, ColorStateList[] outResultColor)6180         private static CharSequence ensureColorSpanContrast(CharSequence charSequence,
6181                 int background, ColorStateList[] outResultColor) {
6182             if (charSequence instanceof Spanned) {
6183                 Spanned ss = (Spanned) charSequence;
6184                 Object[] spans = ss.getSpans(0, ss.length(), Object.class);
6185                 SpannableStringBuilder builder = new SpannableStringBuilder(ss.toString());
6186                 for (Object span : spans) {
6187                     Object resultSpan = span;
6188                     int spanStart = ss.getSpanStart(span);
6189                     int spanEnd = ss.getSpanEnd(span);
6190                     boolean fullLength = (spanEnd - spanStart) == charSequence.length();
6191                     if (resultSpan instanceof CharacterStyle) {
6192                         resultSpan = ((CharacterStyle) span).getUnderlying();
6193                     }
6194                     if (resultSpan instanceof TextAppearanceSpan) {
6195                         TextAppearanceSpan originalSpan = (TextAppearanceSpan) resultSpan;
6196                         ColorStateList textColor = originalSpan.getTextColor();
6197                         if (textColor != null) {
6198                             int[] colors = textColor.getColors();
6199                             int[] newColors = new int[colors.length];
6200                             for (int i = 0; i < newColors.length; i++) {
6201                                 boolean isBgDark = isColorDark(background);
6202                                 newColors[i] = ContrastColorUtil.ensureLargeTextContrast(
6203                                         colors[i], background, isBgDark);
6204                             }
6205                             textColor = new ColorStateList(textColor.getStates().clone(),
6206                                     newColors);
6207                             if (fullLength) {
6208                                 outResultColor[0] = textColor;
6209                                 // Let's drop the color from the span
6210                                 textColor = null;
6211                             }
6212                             resultSpan = new TextAppearanceSpan(
6213                                     originalSpan.getFamily(),
6214                                     originalSpan.getTextStyle(),
6215                                     originalSpan.getTextSize(),
6216                                     textColor,
6217                                     originalSpan.getLinkTextColor());
6218                         }
6219                     } else if (resultSpan instanceof ForegroundColorSpan) {
6220                         ForegroundColorSpan originalSpan = (ForegroundColorSpan) resultSpan;
6221                         int foregroundColor = originalSpan.getForegroundColor();
6222                         boolean isBgDark = isColorDark(background);
6223                         foregroundColor = ContrastColorUtil.ensureLargeTextContrast(
6224                                 foregroundColor, background, isBgDark);
6225                         if (fullLength) {
6226                             outResultColor[0] = ColorStateList.valueOf(foregroundColor);
6227                             resultSpan = null;
6228                         } else {
6229                             resultSpan = new ForegroundColorSpan(foregroundColor);
6230                         }
6231                     } else {
6232                         resultSpan = span;
6233                     }
6234                     if (resultSpan != null) {
6235                         builder.setSpan(resultSpan, spanStart, spanEnd, ss.getSpanFlags(span));
6236                     }
6237                 }
6238                 return builder;
6239             }
6240             return charSequence;
6241         }
6242 
6243         /**
6244          * Determines if the color is light or dark.  Specifically, this is using the same metric as
6245          * {@link ContrastColorUtil#resolvePrimaryColor(Context, int, boolean)} and peers so that
6246          * the direction of color shift is consistent.
6247          *
6248          * @param color the color to check
6249          * @return true if the color has higher contrast with white than black
6250          */
isColorDark(int color)6251         private static boolean isColorDark(int color) {
6252             // as per ContrastColorUtil.shouldUseDark, this uses the color contrast midpoint.
6253             return ContrastColorUtil.calculateLuminance(color) <= 0.17912878474;
6254         }
6255 
6256         /**
6257          * @return Whether we are currently building a notification from a legacy (an app that
6258          *         doesn't create material notifications by itself) app.
6259          */
isLegacy()6260         private boolean isLegacy() {
6261             if (!mIsLegacyInitialized) {
6262                 mIsLegacy = mContext.getApplicationInfo().targetSdkVersion
6263                         < Build.VERSION_CODES.LOLLIPOP;
6264                 mIsLegacyInitialized = true;
6265             }
6266             return mIsLegacy;
6267         }
6268 
6269         private CharSequence processLegacyText(CharSequence charSequence) {
6270             boolean isAlreadyLightText = isLegacy() || textColorsNeedInversion();
6271             if (isAlreadyLightText) {
6272                 return getColorUtil().invertCharSequenceColors(charSequence);
6273             } else {
6274                 return charSequence;
6275             }
6276         }
6277 
6278         /**
6279          * Apply any necessary colors to the small icon
6280          */
6281         private void processSmallIconColor(Icon smallIcon, RemoteViews contentView,
6282                 StandardTemplateParams p) {
6283             boolean colorable = !isLegacy() || getColorUtil().isGrayscaleIcon(mContext, smallIcon);
6284             int color = getSmallIconColor(p);
6285             contentView.setInt(R.id.icon, "setBackgroundColor",
6286                     getBackgroundColor(p));
6287             contentView.setInt(R.id.icon, "setOriginalIconColor",
6288                     colorable ? color : COLOR_INVALID);
6289         }
6290 
6291         /**
6292          * Make the largeIcon dark if it's a fake smallIcon (that is,
6293          * if it's grayscale).
6294          */
6295         // TODO: also check bounds, transparency, that sort of thing.
6296         private void processLargeLegacyIcon(Icon largeIcon, RemoteViews contentView,
6297                 StandardTemplateParams p) {
6298             if (largeIcon != null && isLegacy()
6299                     && getColorUtil().isGrayscaleIcon(mContext, largeIcon)) {
6300                 // resolve color will fall back to the default when legacy
6301                 int color = getSmallIconColor(p);
6302                 contentView.setInt(R.id.icon, "setOriginalIconColor", color);
6303             }
6304         }
6305 
6306         private void sanitizeColor() {
6307             if (mN.color != COLOR_DEFAULT) {
6308                 mN.color |= 0xFF000000; // no alpha for custom colors
6309             }
6310         }
6311 
6312         /**
6313          * Gets the standard action button color
6314          */
6315         private @ColorInt int getStandardActionColor(Notification.StandardTemplateParams p) {
6316             return mTintActionButtons || isBackgroundColorized(p)
6317                     ? getPrimaryAccentColor(p) : getSecondaryTextColor(p);
6318         }
6319 
6320         /**
6321          * Gets the foreground color of the small icon.  If the notification is colorized, this
6322          * is the primary text color, otherwise it's the contrast-adjusted app-provided color.
6323          */
6324         private @ColorInt int getSmallIconColor(StandardTemplateParams p) {
6325             return getColors(p).getContrastColor();
6326         }
6327 
6328         /** @return the theme's accent color for colored UI elements. */
6329         private @ColorInt int getPrimaryAccentColor(StandardTemplateParams p) {
6330             return getColors(p).getPrimaryAccentColor();
6331         }
6332 
6333         /**
6334          * Apply the unstyled operations and return a new {@link Notification} object.
6335          * @hide
6336          */
6337         @NonNull
6338         public Notification buildUnstyled() {
6339             if (mActions.size() > 0) {
6340                 mN.actions = new Action[mActions.size()];
6341                 mActions.toArray(mN.actions);
6342             }
6343             if (!mPersonList.isEmpty()) {
6344                 mN.extras.putParcelableArrayList(EXTRA_PEOPLE_LIST, mPersonList);
6345             }
6346             if (mN.bigContentView != null || mN.contentView != null
6347                     || mN.headsUpContentView != null) {
6348                 mN.extras.putBoolean(EXTRA_CONTAINS_CUSTOM_VIEW, true);
6349             }
6350             return mN;
6351         }
6352 
6353         /**
6354          * Creates a Builder from an existing notification so further changes can be made.
6355          * @param context The context for your application / activity.
6356          * @param n The notification to create a Builder from.
6357          */
6358         @NonNull
recoverBuilder(Context context, Notification n)6359         public static Notification.Builder recoverBuilder(Context context, Notification n) {
6360             // Re-create notification context so we can access app resources.
6361             ApplicationInfo applicationInfo = n.extras.getParcelable(
6362                     EXTRA_BUILDER_APPLICATION_INFO);
6363             Context builderContext;
6364             if (applicationInfo != null) {
6365                 try {
6366                     builderContext = context.createApplicationContext(applicationInfo,
6367                             Context.CONTEXT_RESTRICTED);
6368                 } catch (NameNotFoundException e) {
6369                     Log.e(TAG, "ApplicationInfo " + applicationInfo + " not found");
6370                     builderContext = context;  // try with our context
6371                 }
6372             } else {
6373                 builderContext = context; // try with given context
6374             }
6375 
6376             return new Builder(builderContext, n);
6377         }
6378 
6379         /**
6380          * Determines whether the platform can generate contextual actions for a notification.
6381          * By default this is true.
6382          */
6383         @NonNull
setAllowSystemGeneratedContextualActions(boolean allowed)6384         public Builder setAllowSystemGeneratedContextualActions(boolean allowed) {
6385             mN.mAllowSystemGeneratedContextualActions = allowed;
6386             return this;
6387         }
6388 
6389         /**
6390          * @deprecated Use {@link #build()} instead.
6391          */
6392         @Deprecated
getNotification()6393         public Notification getNotification() {
6394             return build();
6395         }
6396 
6397         /**
6398          * Combine all of the options that have been set and return a new {@link Notification}
6399          * object.
6400          *
6401          * If this notification has {@link BubbleMetadata} attached that was created with
6402          * a shortcutId a check will be performed to ensure the shortcutId supplied to bubble
6403          * metadata matches the shortcutId set on the  notification builder, if one was set.
6404          * If the shortcutId's were specified but do not match, an exception is thrown here.
6405          *
6406          * @see BubbleMetadata.Builder#Builder(String)
6407          * @see #setShortcutId(String)
6408          */
6409         @NonNull
build()6410         public Notification build() {
6411             // Check shortcut id matches
6412             if (mN.mShortcutId != null
6413                     && mN.mBubbleMetadata != null
6414                     && mN.mBubbleMetadata.getShortcutId() != null
6415                     && !mN.mShortcutId.equals(mN.mBubbleMetadata.getShortcutId())) {
6416                 throw new IllegalArgumentException(
6417                         "Notification and BubbleMetadata shortcut id's don't match,"
6418                                 + " notification: " + mN.mShortcutId
6419                                 + " vs bubble: " + mN.mBubbleMetadata.getShortcutId());
6420             }
6421 
6422             // first, add any extras from the calling code
6423             if (mUserExtras != null) {
6424                 mN.extras = getAllExtras();
6425             }
6426 
6427             mN.creationTime = System.currentTimeMillis();
6428 
6429             // lazy stuff from mContext; see comment in Builder(Context, Notification)
6430             Notification.addFieldsFromContext(mContext, mN);
6431 
6432             buildUnstyled();
6433 
6434             if (mStyle != null) {
6435                 mStyle.reduceImageSizes(mContext);
6436                 mStyle.purgeResources();
6437                 mStyle.validate(mContext);
6438                 mStyle.buildStyled(mN);
6439             }
6440             mN.reduceImageSizes(mContext);
6441 
6442             if (mContext.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N
6443                     && !styleDisplaysCustomViewInline()) {
6444                 if (mN.contentView == null) {
6445                     mN.contentView = createContentView();
6446                     mN.extras.putInt(EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT,
6447                             mN.contentView.getSequenceNumber());
6448                 }
6449                 if (mN.bigContentView == null) {
6450                     mN.bigContentView = createBigContentView();
6451                     if (mN.bigContentView != null) {
6452                         mN.extras.putInt(EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT,
6453                                 mN.bigContentView.getSequenceNumber());
6454                     }
6455                 }
6456                 if (mN.headsUpContentView == null) {
6457                     mN.headsUpContentView = createHeadsUpContentView();
6458                     if (mN.headsUpContentView != null) {
6459                         mN.extras.putInt(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT,
6460                                 mN.headsUpContentView.getSequenceNumber());
6461                     }
6462                 }
6463             }
6464 
6465             if ((mN.defaults & DEFAULT_LIGHTS) != 0) {
6466                 mN.flags |= FLAG_SHOW_LIGHTS;
6467             }
6468 
6469             mN.allPendingIntents = null;
6470 
6471             return mN;
6472         }
6473 
styleDisplaysCustomViewInline()6474         private boolean styleDisplaysCustomViewInline() {
6475             return mStyle != null && mStyle.displayCustomViewInline();
6476         }
6477 
6478         /**
6479          * Apply this Builder to an existing {@link Notification} object.
6480          *
6481          * @hide
6482          */
6483         @NonNull
buildInto(@onNull Notification n)6484         public Notification buildInto(@NonNull Notification n) {
6485             build().cloneInto(n, true);
6486             return n;
6487         }
6488 
6489         /**
6490          * Removes RemoteViews that were created for compatibility from {@param n}, if they did not
6491          * change.
6492          *
6493          * @return {@param n}, if no stripping is needed, otherwise a stripped clone of {@param n}.
6494          *
6495          * @hide
6496          */
maybeCloneStrippedForDelivery(Notification n)6497         public static Notification maybeCloneStrippedForDelivery(Notification n) {
6498             String templateClass = n.extras.getString(EXTRA_TEMPLATE);
6499 
6500             // Only strip views for known Styles because we won't know how to
6501             // re-create them otherwise.
6502             if (!TextUtils.isEmpty(templateClass)
6503                     && getNotificationStyleClass(templateClass) == null) {
6504                 return n;
6505             }
6506 
6507             // Only strip unmodified BuilderRemoteViews.
6508             boolean stripContentView = n.contentView instanceof BuilderRemoteViews &&
6509                     n.extras.getInt(EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT, -1) ==
6510                             n.contentView.getSequenceNumber();
6511             boolean stripBigContentView = n.bigContentView instanceof BuilderRemoteViews &&
6512                     n.extras.getInt(EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT, -1) ==
6513                             n.bigContentView.getSequenceNumber();
6514             boolean stripHeadsUpContentView = n.headsUpContentView instanceof BuilderRemoteViews &&
6515                     n.extras.getInt(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT, -1) ==
6516                             n.headsUpContentView.getSequenceNumber();
6517 
6518             // Nothing to do here, no need to clone.
6519             if (!stripContentView && !stripBigContentView && !stripHeadsUpContentView) {
6520                 return n;
6521             }
6522 
6523             Notification clone = n.clone();
6524             if (stripContentView) {
6525                 clone.contentView = null;
6526                 clone.extras.remove(EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT);
6527             }
6528             if (stripBigContentView) {
6529                 clone.bigContentView = null;
6530                 clone.extras.remove(EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT);
6531             }
6532             if (stripHeadsUpContentView) {
6533                 clone.headsUpContentView = null;
6534                 clone.extras.remove(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT);
6535             }
6536             return clone;
6537         }
6538 
6539         @UnsupportedAppUsage
getBaseLayoutResource()6540         private int getBaseLayoutResource() {
6541             return R.layout.notification_template_material_base;
6542         }
6543 
getHeadsUpBaseLayoutResource()6544         private int getHeadsUpBaseLayoutResource() {
6545             return R.layout.notification_template_material_heads_up_base;
6546         }
6547 
getBigBaseLayoutResource()6548         private int getBigBaseLayoutResource() {
6549             return R.layout.notification_template_material_big_base;
6550         }
6551 
getBigPictureLayoutResource()6552         private int getBigPictureLayoutResource() {
6553             return R.layout.notification_template_material_big_picture;
6554         }
6555 
getBigTextLayoutResource()6556         private int getBigTextLayoutResource() {
6557             return R.layout.notification_template_material_big_text;
6558         }
6559 
getInboxLayoutResource()6560         private int getInboxLayoutResource() {
6561             return R.layout.notification_template_material_inbox;
6562         }
6563 
getMessagingLayoutResource()6564         private int getMessagingLayoutResource() {
6565             return R.layout.notification_template_material_messaging;
6566         }
6567 
getBigMessagingLayoutResource()6568         private int getBigMessagingLayoutResource() {
6569             return R.layout.notification_template_material_big_messaging;
6570         }
6571 
getConversationLayoutResource()6572         private int getConversationLayoutResource() {
6573             return R.layout.notification_template_material_conversation;
6574         }
6575 
getActionLayoutResource()6576         private int getActionLayoutResource() {
6577             return R.layout.notification_material_action;
6578         }
6579 
getEmphasizedActionLayoutResource()6580         private int getEmphasizedActionLayoutResource() {
6581             return R.layout.notification_material_action_emphasized;
6582         }
6583 
getActionTombstoneLayoutResource()6584         private int getActionTombstoneLayoutResource() {
6585             return R.layout.notification_material_action_tombstone;
6586         }
6587 
getBackgroundColor(StandardTemplateParams p)6588         private @ColorInt int getBackgroundColor(StandardTemplateParams p) {
6589             return getColors(p).getBackgroundColor();
6590         }
6591 
textColorsNeedInversion()6592         private boolean textColorsNeedInversion() {
6593             if (mStyle == null || !MediaStyle.class.equals(mStyle.getClass())) {
6594                 return false;
6595             }
6596             int targetSdkVersion = mContext.getApplicationInfo().targetSdkVersion;
6597             return targetSdkVersion > Build.VERSION_CODES.M
6598                     && targetSdkVersion < Build.VERSION_CODES.O;
6599         }
6600 
6601         /**
6602          * Get the text that should be displayed in the statusBar when heads upped. This is
6603          * usually just the app name, but may be different depending on the style.
6604          *
6605          * @param publicMode If true, return a text that is safe to display in public.
6606          *
6607          * @hide
6608          */
getHeadsUpStatusBarText(boolean publicMode)6609         public CharSequence getHeadsUpStatusBarText(boolean publicMode) {
6610             if (mStyle != null && !publicMode) {
6611                 CharSequence text = mStyle.getHeadsUpStatusBarText();
6612                 if (!TextUtils.isEmpty(text)) {
6613                     return text;
6614                 }
6615             }
6616             return loadHeaderAppName();
6617         }
6618 
6619         /**
6620          * @return if this builder uses a template
6621          *
6622          * @hide
6623          */
usesTemplate()6624         public boolean usesTemplate() {
6625             return (mN.contentView == null && mN.headsUpContentView == null
6626                     && mN.bigContentView == null)
6627                     || styleDisplaysCustomViewInline();
6628         }
6629     }
6630 
6631     /**
6632      * Reduces the image sizes to conform to a maximum allowed size. This also processes all custom
6633      * remote views.
6634      *
6635      * @hide
6636      */
reduceImageSizes(Context context)6637     void reduceImageSizes(Context context) {
6638         if (extras.getBoolean(EXTRA_REDUCED_IMAGES)) {
6639             return;
6640         }
6641         boolean isLowRam = ActivityManager.isLowRamDeviceStatic();
6642         if (mLargeIcon != null || largeIcon != null) {
6643             Resources resources = context.getResources();
6644             Class<? extends Style> style = getNotificationStyle();
6645             int maxSize = resources.getDimensionPixelSize(isLowRam
6646                     ? R.dimen.notification_right_icon_size_low_ram
6647                     : R.dimen.notification_right_icon_size);
6648             if (mLargeIcon != null) {
6649                 mLargeIcon.scaleDownIfNecessary(maxSize, maxSize);
6650             }
6651             if (largeIcon != null) {
6652                 largeIcon = Icon.scaleDownIfNecessary(largeIcon, maxSize, maxSize);
6653             }
6654         }
6655         reduceImageSizesForRemoteView(contentView, context, isLowRam);
6656         reduceImageSizesForRemoteView(headsUpContentView, context, isLowRam);
6657         reduceImageSizesForRemoteView(bigContentView, context, isLowRam);
6658         extras.putBoolean(EXTRA_REDUCED_IMAGES, true);
6659     }
6660 
reduceImageSizesForRemoteView(RemoteViews remoteView, Context context, boolean isLowRam)6661     private void reduceImageSizesForRemoteView(RemoteViews remoteView, Context context,
6662             boolean isLowRam) {
6663         if (remoteView != null) {
6664             Resources resources = context.getResources();
6665             int maxWidth = resources.getDimensionPixelSize(isLowRam
6666                     ? R.dimen.notification_custom_view_max_image_width_low_ram
6667                     : R.dimen.notification_custom_view_max_image_width);
6668             int maxHeight = resources.getDimensionPixelSize(isLowRam
6669                     ? R.dimen.notification_custom_view_max_image_height_low_ram
6670                     : R.dimen.notification_custom_view_max_image_height);
6671             remoteView.reduceImageSizes(maxWidth, maxHeight);
6672         }
6673     }
6674 
6675     /**
6676      * @return whether this notification is a foreground service notification
6677      * @hide
6678      */
isForegroundService()6679     public boolean isForegroundService() {
6680         return (flags & Notification.FLAG_FOREGROUND_SERVICE) != 0;
6681     }
6682 
6683     /**
6684      * Describe whether this notification's content such that it should always display
6685      * immediately when tied to a foreground service, even if the system might generally
6686      * avoid showing the notifications for short-lived foreground service lifetimes.
6687      *
6688      * Immediate visibility of the Notification is indicated when:
6689      * <ul>
6690      *     <li>The app specifically indicated it with
6691      *         {@link Notification.Builder#setForegroundServiceBehavior(int)
6692      *         setForegroundServiceBehavior(BEHAVIOR_IMMEDIATE_DISPLAY)}</li>
6693      *     <li>It is a media notification or has an associated media session</li>
6694      *     <li>It is a call or navigation notification</li>
6695      *     <li>It provides additional action affordances</li>
6696      * </ul>
6697      *
6698      * If the app has specified
6699      * {@code NotificationBuilder.setForegroundServiceBehavior(BEHAVIOR_DEFERRED_DISPLAY)}
6700      * then this method will return {@code false} and notification visibility will be
6701      * deferred following the service's transition to the foreground state even in the
6702      * circumstances described above.
6703      *
6704      * @return whether this notification should be displayed immediately when
6705      * its associated service transitions to the foreground state
6706      * @hide
6707      */
6708     @TestApi
shouldShowForegroundImmediately()6709     public boolean shouldShowForegroundImmediately() {
6710         // Has the app demanded immediate display?
6711         if (mFgsDeferBehavior == FOREGROUND_SERVICE_IMMEDIATE) {
6712             return true;
6713         }
6714 
6715         // Has the app demanded deferred display?
6716         if (mFgsDeferBehavior == FOREGROUND_SERVICE_DEFERRED) {
6717             return false;
6718         }
6719 
6720         // We show these sorts of notifications immediately in the absence of
6721         // any explicit app declaration
6722         if (isMediaNotification() || hasMediaSession()
6723                     || CATEGORY_CALL.equals(category)
6724                     || CATEGORY_NAVIGATION.equals(category)
6725                     || (actions != null && actions.length > 0)) {
6726             return true;
6727         }
6728 
6729         // No extenuating circumstances: defer visibility
6730         return false;
6731     }
6732 
6733     /**
6734      * Has forced deferral for FGS purposes been specified?
6735      * @hide
6736      */
isForegroundDisplayForceDeferred()6737     public boolean isForegroundDisplayForceDeferred() {
6738         return FOREGROUND_SERVICE_DEFERRED == mFgsDeferBehavior;
6739     }
6740 
6741     /**
6742      * @return whether this notification has a media session attached
6743      * @hide
6744      */
hasMediaSession()6745     public boolean hasMediaSession() {
6746         return extras.getParcelable(Notification.EXTRA_MEDIA_SESSION) != null;
6747     }
6748 
6749     /**
6750      * @return the style class of this notification
6751      * @hide
6752      */
getNotificationStyle()6753     public Class<? extends Notification.Style> getNotificationStyle() {
6754         String templateClass = extras.getString(Notification.EXTRA_TEMPLATE);
6755 
6756         if (!TextUtils.isEmpty(templateClass)) {
6757             return Notification.getNotificationStyleClass(templateClass);
6758         }
6759         return null;
6760     }
6761 
6762     /**
6763      * @return whether the style of this notification is the one provided
6764      * @hide
6765      */
isStyle(@onNull Class<? extends Style> styleClass)6766     public boolean isStyle(@NonNull Class<? extends Style> styleClass) {
6767         String templateClass = extras.getString(Notification.EXTRA_TEMPLATE);
6768         return Objects.equals(templateClass, styleClass.getName());
6769     }
6770 
6771     /**
6772      * @return true if this notification is colorized *for the purposes of ranking*.  If the
6773      * {@link #color} is {@link #COLOR_DEFAULT} this will be true, even though the actual
6774      * appearance of the notification may not be "colorized".
6775      *
6776      * @hide
6777      */
isColorized()6778     public boolean isColorized() {
6779         return extras.getBoolean(EXTRA_COLORIZED)
6780                 && (hasColorizedPermission() || isForegroundService());
6781     }
6782 
6783     /**
6784      * Returns whether an app can colorize due to the android.permission.USE_COLORIZED_NOTIFICATIONS
6785      * permission. The permission is checked when a notification is enqueued.
6786      */
hasColorizedPermission()6787     private boolean hasColorizedPermission() {
6788         return (flags & Notification.FLAG_CAN_COLORIZE) != 0;
6789     }
6790 
6791     /**
6792      * @return true if this is a media notification
6793      *
6794      * @hide
6795      */
isMediaNotification()6796     public boolean isMediaNotification() {
6797         Class<? extends Style> style = getNotificationStyle();
6798         if (MediaStyle.class.equals(style)) {
6799             return true;
6800         } else if (DecoratedMediaCustomViewStyle.class.equals(style)) {
6801             return true;
6802         }
6803         return false;
6804     }
6805 
6806     /**
6807      * @return true if this notification is showing as a bubble
6808      *
6809      * @hide
6810      */
isBubbleNotification()6811     public boolean isBubbleNotification() {
6812         return (flags & Notification.FLAG_BUBBLE) != 0;
6813     }
6814 
hasLargeIcon()6815     private boolean hasLargeIcon() {
6816         return mLargeIcon != null || largeIcon != null;
6817     }
6818 
6819     /**
6820      * @return true if the notification will show the time; false otherwise
6821      * @hide
6822      */
showsTime()6823     public boolean showsTime() {
6824         return when != 0 && extras.getBoolean(EXTRA_SHOW_WHEN);
6825     }
6826 
6827     /**
6828      * @return true if the notification will show a chronometer; false otherwise
6829      * @hide
6830      */
showsChronometer()6831     public boolean showsChronometer() {
6832         return when != 0 && extras.getBoolean(EXTRA_SHOW_CHRONOMETER);
6833     }
6834 
6835     /**
6836      * @return true if the notification has image
6837      */
hasImage()6838     public boolean hasImage() {
6839         if (isStyle(MessagingStyle.class) && extras != null) {
6840             final Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES);
6841             if (!ArrayUtils.isEmpty(messages)) {
6842                 for (MessagingStyle.Message m : MessagingStyle.Message
6843                         .getMessagesFromBundleArray(messages)) {
6844                     if (m.getDataUri() != null
6845                             && m.getDataMimeType() != null
6846                             && m.getDataMimeType().startsWith("image/")) {
6847                         return true;
6848                     }
6849                 }
6850             }
6851         } else if (hasLargeIcon()) {
6852             return true;
6853         } else if (extras.containsKey(EXTRA_BACKGROUND_IMAGE_URI)) {
6854             return true;
6855         }
6856         return false;
6857     }
6858 
6859 
6860     /**
6861      * @removed
6862      */
6863     @SystemApi
getNotificationStyleClass(String templateClass)6864     public static Class<? extends Style> getNotificationStyleClass(String templateClass) {
6865         for (Class<? extends Style> innerClass : PLATFORM_STYLE_CLASSES) {
6866             if (templateClass.equals(innerClass.getName())) {
6867                 return innerClass;
6868             }
6869         }
6870         return null;
6871     }
6872 
buildCustomContentIntoTemplate(@onNull Context context, @NonNull RemoteViews template, @Nullable RemoteViews customContent, @NonNull StandardTemplateParams p, @NonNull TemplateBindResult result)6873     private static void buildCustomContentIntoTemplate(@NonNull Context context,
6874             @NonNull RemoteViews template, @Nullable RemoteViews customContent,
6875             @NonNull StandardTemplateParams p, @NonNull TemplateBindResult result) {
6876         int childIndex = -1;
6877         if (customContent != null) {
6878             // Need to clone customContent before adding, because otherwise it can no longer be
6879             // parceled independently of remoteViews.
6880             customContent = customContent.clone();
6881             if (p.mHeaderless) {
6882                 template.removeFromParent(R.id.notification_top_line);
6883                 // We do not know how many lines ar emote view has, so we presume it has 2;  this
6884                 // ensures that we don't under-pad the content, which could lead to abuse, at the
6885                 // cost of making single-line custom content over-padded.
6886                 Builder.setHeaderlessVerticalMargins(template, p, true /* hasSecondLine */);
6887             } else {
6888                 // also update the end margin to account for the large icon or expander
6889                 Resources resources = context.getResources();
6890                 result.mTitleMarginSet.applyToView(template, R.id.notification_main_column,
6891                         resources.getDimension(R.dimen.notification_content_margin_end)
6892                                 / resources.getDisplayMetrics().density);
6893             }
6894             template.removeAllViewsExceptId(R.id.notification_main_column, R.id.progress);
6895             template.addView(R.id.notification_main_column, customContent, 0 /* index */);
6896             template.addFlags(RemoteViews.FLAG_REAPPLY_DISALLOWED);
6897             childIndex = 0;
6898         }
6899         template.setIntTag(R.id.notification_main_column,
6900                 com.android.internal.R.id.notification_custom_view_index_tag,
6901                 childIndex);
6902     }
6903 
6904     /**
6905      * An object that can apply a rich notification style to a {@link Notification.Builder}
6906      * object.
6907      */
6908     public static abstract class Style {
6909 
6910         /**
6911          * The number of items allowed simulatanously in the remote input history.
6912          * @hide
6913          */
6914         static final int MAX_REMOTE_INPUT_HISTORY_LINES = 3;
6915         private CharSequence mBigContentTitle;
6916 
6917         /**
6918          * @hide
6919          */
6920         protected CharSequence mSummaryText = null;
6921 
6922         /**
6923          * @hide
6924          */
6925         protected boolean mSummaryTextSet = false;
6926 
6927         protected Builder mBuilder;
6928 
6929         /**
6930          * Overrides ContentTitle in the big form of the template.
6931          * This defaults to the value passed to setContentTitle().
6932          */
internalSetBigContentTitle(CharSequence title)6933         protected void internalSetBigContentTitle(CharSequence title) {
6934             mBigContentTitle = title;
6935         }
6936 
6937         /**
6938          * Set the first line of text after the detail section in the big form of the template.
6939          */
internalSetSummaryText(CharSequence cs)6940         protected void internalSetSummaryText(CharSequence cs) {
6941             mSummaryText = cs;
6942             mSummaryTextSet = true;
6943         }
6944 
setBuilder(Builder builder)6945         public void setBuilder(Builder builder) {
6946             if (mBuilder != builder) {
6947                 mBuilder = builder;
6948                 if (mBuilder != null) {
6949                     mBuilder.setStyle(this);
6950                 }
6951             }
6952         }
6953 
checkBuilder()6954         protected void checkBuilder() {
6955             if (mBuilder == null) {
6956                 throw new IllegalArgumentException("Style requires a valid Builder object");
6957             }
6958         }
6959 
getStandardView(int layoutId)6960         protected RemoteViews getStandardView(int layoutId) {
6961             // TODO(jeffdq): set the view type based on the layout resource?
6962             StandardTemplateParams p = mBuilder.mParams.reset()
6963                     .viewType(StandardTemplateParams.VIEW_TYPE_UNSPECIFIED)
6964                     .fillTextsFrom(mBuilder);
6965             return getStandardView(layoutId, p, null);
6966         }
6967 
6968 
6969         /**
6970          * Get the standard view for this style.
6971          *
6972          * @param layoutId The layout id to use.
6973          * @param p the params for this inflation.
6974          * @param result The result where template bind information is saved.
6975          * @return A remoteView for this style.
6976          * @hide
6977          */
getStandardView(int layoutId, StandardTemplateParams p, TemplateBindResult result)6978         protected RemoteViews getStandardView(int layoutId, StandardTemplateParams p,
6979                 TemplateBindResult result) {
6980             checkBuilder();
6981 
6982             if (mBigContentTitle != null) {
6983                 p.title = mBigContentTitle;
6984             }
6985 
6986             return mBuilder.applyStandardTemplateWithActions(layoutId, p, result);
6987         }
6988 
6989         /**
6990          * Construct a Style-specific RemoteViews for the collapsed notification layout.
6991          * The default implementation has nothing additional to add.
6992          *
6993          * @param increasedHeight true if this layout be created with an increased height.
6994          * @hide
6995          */
makeContentView(boolean increasedHeight)6996         public RemoteViews makeContentView(boolean increasedHeight) {
6997             return null;
6998         }
6999 
7000         /**
7001          * Construct a Style-specific RemoteViews for the final big notification layout.
7002          * @hide
7003          */
makeBigContentView()7004         public RemoteViews makeBigContentView() {
7005             return null;
7006         }
7007 
7008         /**
7009          * Construct a Style-specific RemoteViews for the final HUN layout.
7010          *
7011          * @param increasedHeight true if this layout be created with an increased height.
7012          * @hide
7013          */
makeHeadsUpContentView(boolean increasedHeight)7014         public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
7015             return null;
7016         }
7017 
7018         /**
7019          * Apply any style-specific extras to this notification before shipping it out.
7020          * @hide
7021          */
addExtras(Bundle extras)7022         public void addExtras(Bundle extras) {
7023             if (mSummaryTextSet) {
7024                 extras.putCharSequence(EXTRA_SUMMARY_TEXT, mSummaryText);
7025             }
7026             if (mBigContentTitle != null) {
7027                 extras.putCharSequence(EXTRA_TITLE_BIG, mBigContentTitle);
7028             }
7029             extras.putString(EXTRA_TEMPLATE, this.getClass().getName());
7030         }
7031 
7032         /**
7033          * Reconstruct the internal state of this Style object from extras.
7034          * @hide
7035          */
restoreFromExtras(Bundle extras)7036         protected void restoreFromExtras(Bundle extras) {
7037             if (extras.containsKey(EXTRA_SUMMARY_TEXT)) {
7038                 mSummaryText = extras.getCharSequence(EXTRA_SUMMARY_TEXT);
7039                 mSummaryTextSet = true;
7040             }
7041             if (extras.containsKey(EXTRA_TITLE_BIG)) {
7042                 mBigContentTitle = extras.getCharSequence(EXTRA_TITLE_BIG);
7043             }
7044         }
7045 
7046 
7047         /**
7048          * @hide
7049          */
buildStyled(Notification wip)7050         public Notification buildStyled(Notification wip) {
7051             addExtras(wip.extras);
7052             return wip;
7053         }
7054 
7055         /**
7056          * @hide
7057          */
purgeResources()7058         public void purgeResources() {}
7059 
7060         /**
7061          * Calls {@link android.app.Notification.Builder#build()} on the Builder this Style is
7062          * attached to.
7063          *
7064          * @return the fully constructed Notification.
7065          */
build()7066         public Notification build() {
7067             checkBuilder();
7068             return mBuilder.build();
7069         }
7070 
7071         /**
7072          * @hide
7073          * @return Whether we should put the summary be put into the notification header
7074          */
hasSummaryInHeader()7075         public boolean hasSummaryInHeader() {
7076             return true;
7077         }
7078 
7079         /**
7080          * @hide
7081          * @return Whether custom content views are displayed inline in the style
7082          */
displayCustomViewInline()7083         public boolean displayCustomViewInline() {
7084             return false;
7085         }
7086 
7087         /**
7088          * Reduces the image sizes contained in this style.
7089          *
7090          * @hide
7091          */
reduceImageSizes(Context context)7092         public void reduceImageSizes(Context context) {
7093         }
7094 
7095         /**
7096          * Validate that this style was properly composed. This is called at build time.
7097          * @hide
7098          */
validate(Context context)7099         public void validate(Context context) {
7100         }
7101 
7102         /**
7103          * @hide
7104          */
areNotificationsVisiblyDifferent(Style other)7105         public abstract boolean areNotificationsVisiblyDifferent(Style other);
7106 
7107         /**
7108          * @return the text that should be displayed in the statusBar when heads-upped.
7109          * If {@code null} is returned, the default implementation will be used.
7110          *
7111          * @hide
7112          */
getHeadsUpStatusBarText()7113         public CharSequence getHeadsUpStatusBarText() {
7114             return null;
7115         }
7116     }
7117 
7118     /**
7119      * Helper class for generating large-format notifications that include a large image attachment.
7120      *
7121      * Here's how you'd set the <code>BigPictureStyle</code> on a notification:
7122      * <pre class="prettyprint">
7123      * Notification notif = new Notification.Builder(mContext)
7124      *     .setContentTitle(&quot;New photo from &quot; + sender.toString())
7125      *     .setContentText(subject)
7126      *     .setSmallIcon(R.drawable.new_post)
7127      *     .setLargeIcon(aBitmap)
7128      *     .setStyle(new Notification.BigPictureStyle()
7129      *         .bigPicture(aBigBitmap))
7130      *     .build();
7131      * </pre>
7132      *
7133      * @see Notification#bigContentView
7134      */
7135     public static class BigPictureStyle extends Style {
7136         private Icon mPictureIcon;
7137         private Icon mBigLargeIcon;
7138         private boolean mBigLargeIconSet = false;
7139         private CharSequence mPictureContentDescription;
7140         private boolean mShowBigPictureWhenCollapsed;
7141 
BigPictureStyle()7142         public BigPictureStyle() {
7143         }
7144 
7145         /**
7146          * @deprecated use {@code BigPictureStyle()}.
7147          */
7148         @Deprecated
BigPictureStyle(Builder builder)7149         public BigPictureStyle(Builder builder) {
7150             setBuilder(builder);
7151         }
7152 
7153         /**
7154          * Overrides ContentTitle in the big form of the template.
7155          * This defaults to the value passed to setContentTitle().
7156          */
7157         @NonNull
setBigContentTitle(@ullable CharSequence title)7158         public BigPictureStyle setBigContentTitle(@Nullable CharSequence title) {
7159             internalSetBigContentTitle(safeCharSequence(title));
7160             return this;
7161         }
7162 
7163         /**
7164          * Set the first line of text after the detail section in the big form of the template.
7165          */
7166         @NonNull
setSummaryText(@ullable CharSequence cs)7167         public BigPictureStyle setSummaryText(@Nullable CharSequence cs) {
7168             internalSetSummaryText(safeCharSequence(cs));
7169             return this;
7170         }
7171 
7172         /**
7173          * Set the content description of the big picture.
7174          */
7175         @NonNull
setContentDescription( @ullable CharSequence contentDescription)7176         public BigPictureStyle setContentDescription(
7177                 @Nullable CharSequence contentDescription) {
7178             mPictureContentDescription = contentDescription;
7179             return this;
7180         }
7181 
7182         /**
7183          * @hide
7184          */
7185         @Nullable
getBigPicture()7186         public Icon getBigPicture() {
7187             if (mPictureIcon != null) {
7188                 return mPictureIcon;
7189             }
7190             return null;
7191         }
7192 
7193         /**
7194          * Provide the bitmap to be used as the payload for the BigPicture notification.
7195          */
7196         @NonNull
bigPicture(@ullable Bitmap b)7197         public BigPictureStyle bigPicture(@Nullable Bitmap b) {
7198             mPictureIcon = b == null ? null : Icon.createWithBitmap(b);
7199             return this;
7200         }
7201 
7202         /**
7203          * Provide the content Uri to be used as the payload for the BigPicture notification.
7204          */
7205         @NonNull
bigPicture(@ullable Icon icon)7206         public BigPictureStyle bigPicture(@Nullable Icon icon) {
7207             mPictureIcon = icon;
7208             return this;
7209         }
7210 
7211         /**
7212          * When set, the {@link #bigPicture(Bitmap) big picture} of this style will be promoted and
7213          * shown in place of the {@link Builder#setLargeIcon(Icon) large icon} in the collapsed
7214          * state of this notification.
7215          */
7216         @NonNull
showBigPictureWhenCollapsed(boolean show)7217         public BigPictureStyle showBigPictureWhenCollapsed(boolean show) {
7218             mShowBigPictureWhenCollapsed = show;
7219             return this;
7220         }
7221 
7222         /**
7223          * Override the large icon when the big notification is shown.
7224          */
7225         @NonNull
bigLargeIcon(@ullable Bitmap b)7226         public BigPictureStyle bigLargeIcon(@Nullable Bitmap b) {
7227             return bigLargeIcon(b != null ? Icon.createWithBitmap(b) : null);
7228         }
7229 
7230         /**
7231          * Override the large icon when the big notification is shown.
7232          */
7233         @NonNull
bigLargeIcon(@ullable Icon icon)7234         public BigPictureStyle bigLargeIcon(@Nullable Icon icon) {
7235             mBigLargeIconSet = true;
7236             mBigLargeIcon = icon;
7237             return this;
7238         }
7239 
7240         /** @hide */
7241         public static final int MIN_ASHMEM_BITMAP_SIZE = 128 * (1 << 10);
7242 
7243         /**
7244          * @hide
7245          */
7246         @Override
purgeResources()7247         public void purgeResources() {
7248             super.purgeResources();
7249             if (mPictureIcon != null) {
7250                 mPictureIcon.convertToAshmem();
7251             }
7252             if (mBigLargeIcon != null) {
7253                 mBigLargeIcon.convertToAshmem();
7254             }
7255         }
7256 
7257         /**
7258          * @hide
7259          */
7260         @Override
reduceImageSizes(Context context)7261         public void reduceImageSizes(Context context) {
7262             super.reduceImageSizes(context);
7263             Resources resources = context.getResources();
7264             boolean isLowRam = ActivityManager.isLowRamDeviceStatic();
7265             if (mPictureIcon != null) {
7266                 int maxPictureWidth = resources.getDimensionPixelSize(isLowRam
7267                         ? R.dimen.notification_big_picture_max_height_low_ram
7268                         : R.dimen.notification_big_picture_max_height);
7269                 int maxPictureHeight = resources.getDimensionPixelSize(isLowRam
7270                         ? R.dimen.notification_big_picture_max_width_low_ram
7271                         : R.dimen.notification_big_picture_max_width);
7272                 mPictureIcon.scaleDownIfNecessary(maxPictureWidth, maxPictureHeight);
7273             }
7274             if (mBigLargeIcon != null) {
7275                 int rightIconSize = resources.getDimensionPixelSize(isLowRam
7276                         ? R.dimen.notification_right_icon_size_low_ram
7277                         : R.dimen.notification_right_icon_size);
7278                 mBigLargeIcon.scaleDownIfNecessary(rightIconSize, rightIconSize);
7279             }
7280         }
7281 
7282         /**
7283          * @hide
7284          */
7285         @Override
makeContentView(boolean increasedHeight)7286         public RemoteViews makeContentView(boolean increasedHeight) {
7287             if (mPictureIcon == null || !mShowBigPictureWhenCollapsed) {
7288                 return super.makeContentView(increasedHeight);
7289             }
7290 
7291             StandardTemplateParams p = mBuilder.mParams.reset()
7292                     .viewType(StandardTemplateParams.VIEW_TYPE_NORMAL)
7293                     .fillTextsFrom(mBuilder)
7294                     .promotedPicture(mPictureIcon);
7295             return getStandardView(mBuilder.getBaseLayoutResource(), p, null /* result */);
7296         }
7297 
7298         /**
7299          * @hide
7300          */
7301         @Override
makeHeadsUpContentView(boolean increasedHeight)7302         public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
7303             if (mPictureIcon == null || !mShowBigPictureWhenCollapsed) {
7304                 return super.makeHeadsUpContentView(increasedHeight);
7305             }
7306 
7307             StandardTemplateParams p = mBuilder.mParams.reset()
7308                     .viewType(StandardTemplateParams.VIEW_TYPE_HEADS_UP)
7309                     .fillTextsFrom(mBuilder)
7310                     .promotedPicture(mPictureIcon);
7311             return getStandardView(mBuilder.getHeadsUpBaseLayoutResource(), p, null /* result */);
7312         }
7313 
7314         /**
7315          * @hide
7316          */
makeBigContentView()7317         public RemoteViews makeBigContentView() {
7318             // Replace mN.mLargeIcon with mBigLargeIcon if mBigLargeIconSet
7319             // This covers the following cases:
7320             //   1. mBigLargeIconSet -> mBigLargeIcon (null or non-null) applies, overrides
7321             //          mN.mLargeIcon
7322             //   2. !mBigLargeIconSet -> mN.mLargeIcon applies
7323             Icon oldLargeIcon = null;
7324             Bitmap largeIconLegacy = null;
7325             if (mBigLargeIconSet) {
7326                 oldLargeIcon = mBuilder.mN.mLargeIcon;
7327                 mBuilder.mN.mLargeIcon = mBigLargeIcon;
7328                 // The legacy largeIcon might not allow us to clear the image, as it's taken in
7329                 // replacement if the other one is null. Because we're restoring these legacy icons
7330                 // for old listeners, this is in general non-null.
7331                 largeIconLegacy = mBuilder.mN.largeIcon;
7332                 mBuilder.mN.largeIcon = null;
7333             }
7334 
7335             StandardTemplateParams p = mBuilder.mParams.reset()
7336                     .viewType(StandardTemplateParams.VIEW_TYPE_BIG).fillTextsFrom(mBuilder);
7337             RemoteViews contentView = getStandardView(mBuilder.getBigPictureLayoutResource(),
7338                     p, null /* result */);
7339             if (mSummaryTextSet) {
7340                 contentView.setTextViewText(R.id.text, mBuilder.processTextSpans(
7341                         mBuilder.processLegacyText(mSummaryText)));
7342                 mBuilder.setTextViewColorSecondary(contentView, R.id.text, p);
7343                 contentView.setViewVisibility(R.id.text, View.VISIBLE);
7344             }
7345 
7346             if (mBigLargeIconSet) {
7347                 mBuilder.mN.mLargeIcon = oldLargeIcon;
7348                 mBuilder.mN.largeIcon = largeIconLegacy;
7349             }
7350 
7351             contentView.setImageViewIcon(R.id.big_picture, mPictureIcon);
7352 
7353             if (mPictureContentDescription != null) {
7354                 contentView.setContentDescription(R.id.big_picture, mPictureContentDescription);
7355             }
7356 
7357             return contentView;
7358         }
7359 
7360         /**
7361          * @hide
7362          */
addExtras(Bundle extras)7363         public void addExtras(Bundle extras) {
7364             super.addExtras(extras);
7365 
7366             if (mBigLargeIconSet) {
7367                 extras.putParcelable(EXTRA_LARGE_ICON_BIG, mBigLargeIcon);
7368             }
7369             if (mPictureContentDescription != null) {
7370                 extras.putCharSequence(EXTRA_PICTURE_CONTENT_DESCRIPTION,
7371                         mPictureContentDescription);
7372             }
7373             extras.putBoolean(EXTRA_SHOW_BIG_PICTURE_WHEN_COLLAPSED, mShowBigPictureWhenCollapsed);
7374 
7375             // If the icon contains a bitmap, use the old extra so that listeners which look for
7376             // that extra can still find the picture.  Don't include the new extra in that case,
7377             // to avoid duplicating data.
7378             if (mPictureIcon != null && mPictureIcon.getType() == Icon.TYPE_BITMAP) {
7379                 extras.putParcelable(EXTRA_PICTURE, mPictureIcon.getBitmap());
7380                 extras.putParcelable(EXTRA_PICTURE_ICON, null);
7381             } else {
7382                 extras.putParcelable(EXTRA_PICTURE, null);
7383                 extras.putParcelable(EXTRA_PICTURE_ICON, mPictureIcon);
7384             }
7385         }
7386 
7387         /**
7388          * @hide
7389          */
7390         @Override
restoreFromExtras(Bundle extras)7391         protected void restoreFromExtras(Bundle extras) {
7392             super.restoreFromExtras(extras);
7393 
7394             if (extras.containsKey(EXTRA_LARGE_ICON_BIG)) {
7395                 mBigLargeIconSet = true;
7396                 mBigLargeIcon = extras.getParcelable(EXTRA_LARGE_ICON_BIG);
7397             }
7398 
7399             if (extras.containsKey(EXTRA_PICTURE_CONTENT_DESCRIPTION)) {
7400                 mPictureContentDescription =
7401                         extras.getCharSequence(EXTRA_PICTURE_CONTENT_DESCRIPTION);
7402             }
7403 
7404             mShowBigPictureWhenCollapsed = extras.getBoolean(EXTRA_SHOW_BIG_PICTURE_WHEN_COLLAPSED);
7405 
7406             mPictureIcon = getPictureIcon(extras);
7407         }
7408 
7409         /** @hide */
7410         @Nullable
getPictureIcon(@ullable Bundle extras)7411         public static Icon getPictureIcon(@Nullable Bundle extras) {
7412             if (extras == null) return null;
7413             // When this style adds a picture, we only add one of the keys.  If both were added,
7414             // it would most likely be a legacy app trying to override the picture in some way.
7415             // Because of that case it's better to give precedence to the legacy field.
7416             Bitmap bitmapPicture = extras.getParcelable(EXTRA_PICTURE);
7417             if (bitmapPicture != null) {
7418                 return Icon.createWithBitmap(bitmapPicture);
7419             } else {
7420                 return extras.getParcelable(EXTRA_PICTURE_ICON);
7421             }
7422         }
7423 
7424         /**
7425          * @hide
7426          */
7427         @Override
hasSummaryInHeader()7428         public boolean hasSummaryInHeader() {
7429             return false;
7430         }
7431 
7432         /**
7433          * @hide
7434          * Note that we aren't actually comparing the contents of the bitmaps here, so this
7435          * is only doing a cursory inspection. Bitmaps of equal size will appear the same.
7436          */
7437         @Override
areNotificationsVisiblyDifferent(Style other)7438         public boolean areNotificationsVisiblyDifferent(Style other) {
7439             if (other == null || getClass() != other.getClass()) {
7440                 return true;
7441             }
7442             BigPictureStyle otherS = (BigPictureStyle) other;
7443             return areIconsObviouslyDifferent(getBigPicture(), otherS.getBigPicture());
7444         }
7445 
areIconsObviouslyDifferent(Icon a, Icon b)7446         private static boolean areIconsObviouslyDifferent(Icon a, Icon b) {
7447             if (a == b) {
7448                 return false;
7449             }
7450             if (a == null || b == null) {
7451                 return true;
7452             }
7453             if (a.sameAs(b)) {
7454                 return false;
7455             }
7456             final int aType = a.getType();
7457             if (aType != b.getType()) {
7458                 return true;
7459             }
7460             if (aType == Icon.TYPE_BITMAP || aType == Icon.TYPE_ADAPTIVE_BITMAP) {
7461                 final Bitmap aBitmap = a.getBitmap();
7462                 final Bitmap bBitmap = b.getBitmap();
7463                 return aBitmap.getWidth() != bBitmap.getWidth()
7464                         || aBitmap.getHeight() != bBitmap.getHeight()
7465                         || aBitmap.getConfig() != bBitmap.getConfig()
7466                         || aBitmap.getGenerationId() != bBitmap.getGenerationId();
7467             }
7468             return true;
7469         }
7470     }
7471 
7472     /**
7473      * Helper class for generating large-format notifications that include a lot of text.
7474      *
7475      * Here's how you'd set the <code>BigTextStyle</code> on a notification:
7476      * <pre class="prettyprint">
7477      * Notification notif = new Notification.Builder(mContext)
7478      *     .setContentTitle(&quot;New mail from &quot; + sender.toString())
7479      *     .setContentText(subject)
7480      *     .setSmallIcon(R.drawable.new_mail)
7481      *     .setLargeIcon(aBitmap)
7482      *     .setStyle(new Notification.BigTextStyle()
7483      *         .bigText(aVeryLongString))
7484      *     .build();
7485      * </pre>
7486      *
7487      * @see Notification#bigContentView
7488      */
7489     public static class BigTextStyle extends Style {
7490 
7491         private CharSequence mBigText;
7492 
BigTextStyle()7493         public BigTextStyle() {
7494         }
7495 
7496         /**
7497          * @deprecated use {@code BigTextStyle()}.
7498          */
7499         @Deprecated
BigTextStyle(Builder builder)7500         public BigTextStyle(Builder builder) {
7501             setBuilder(builder);
7502         }
7503 
7504         /**
7505          * Overrides ContentTitle in the big form of the template.
7506          * This defaults to the value passed to setContentTitle().
7507          */
setBigContentTitle(CharSequence title)7508         public BigTextStyle setBigContentTitle(CharSequence title) {
7509             internalSetBigContentTitle(safeCharSequence(title));
7510             return this;
7511         }
7512 
7513         /**
7514          * Set the first line of text after the detail section in the big form of the template.
7515          */
setSummaryText(CharSequence cs)7516         public BigTextStyle setSummaryText(CharSequence cs) {
7517             internalSetSummaryText(safeCharSequence(cs));
7518             return this;
7519         }
7520 
7521         /**
7522          * Provide the longer text to be displayed in the big form of the
7523          * template in place of the content text.
7524          */
bigText(CharSequence cs)7525         public BigTextStyle bigText(CharSequence cs) {
7526             mBigText = safeCharSequence(cs);
7527             return this;
7528         }
7529 
7530         /**
7531          * @hide
7532          */
getBigText()7533         public CharSequence getBigText() {
7534             return mBigText;
7535         }
7536 
7537         /**
7538          * @hide
7539          */
addExtras(Bundle extras)7540         public void addExtras(Bundle extras) {
7541             super.addExtras(extras);
7542 
7543             extras.putCharSequence(EXTRA_BIG_TEXT, mBigText);
7544         }
7545 
7546         /**
7547          * @hide
7548          */
7549         @Override
restoreFromExtras(Bundle extras)7550         protected void restoreFromExtras(Bundle extras) {
7551             super.restoreFromExtras(extras);
7552 
7553             mBigText = extras.getCharSequence(EXTRA_BIG_TEXT);
7554         }
7555 
7556         /**
7557          * @param increasedHeight true if this layout be created with an increased height.
7558          *
7559          * @hide
7560          */
7561         @Override
makeContentView(boolean increasedHeight)7562         public RemoteViews makeContentView(boolean increasedHeight) {
7563             if (increasedHeight) {
7564                 ArrayList<Action> originalActions = mBuilder.mActions;
7565                 mBuilder.mActions = new ArrayList<>();
7566                 RemoteViews remoteViews = makeBigContentView();
7567                 mBuilder.mActions = originalActions;
7568                 return remoteViews;
7569             }
7570             return super.makeContentView(increasedHeight);
7571         }
7572 
7573         /**
7574          * @hide
7575          */
7576         @Override
makeHeadsUpContentView(boolean increasedHeight)7577         public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
7578             if (increasedHeight && mBuilder.mActions.size() > 0) {
7579                 // TODO(b/163626038): pass VIEW_TYPE_HEADS_UP?
7580                 return makeBigContentView();
7581             }
7582             return super.makeHeadsUpContentView(increasedHeight);
7583         }
7584 
7585         /**
7586          * @hide
7587          */
makeBigContentView()7588         public RemoteViews makeBigContentView() {
7589             StandardTemplateParams p = mBuilder.mParams.reset()
7590                     .viewType(StandardTemplateParams.VIEW_TYPE_BIG)
7591                     .allowTextWithProgress(true)
7592                     .textViewId(R.id.big_text)
7593                     .fillTextsFrom(mBuilder);
7594 
7595             // Replace the text with the big text, but only if the big text is not empty.
7596             CharSequence bigTextText = mBuilder.processLegacyText(mBigText);
7597             if (!TextUtils.isEmpty(bigTextText)) {
7598                 p.text(bigTextText);
7599             }
7600 
7601             return getStandardView(mBuilder.getBigTextLayoutResource(), p, null /* result */);
7602         }
7603 
7604         /**
7605          * @hide
7606          * Spans are ignored when comparing text for visual difference.
7607          */
7608         @Override
areNotificationsVisiblyDifferent(Style other)7609         public boolean areNotificationsVisiblyDifferent(Style other) {
7610             if (other == null || getClass() != other.getClass()) {
7611                 return true;
7612             }
7613             BigTextStyle newS = (BigTextStyle) other;
7614             return !Objects.equals(String.valueOf(getBigText()), String.valueOf(newS.getBigText()));
7615         }
7616 
7617     }
7618 
7619     /**
7620      * Helper class for generating large-format notifications that include multiple back-and-forth
7621      * messages of varying types between any number of people.
7622      *
7623      * <p>
7624      * If the platform does not provide large-format notifications, this method has no effect. The
7625      * user will always see the normal notification view.
7626      *
7627      * <p>
7628      * If the app is targeting Android P and above, it is required to use the {@link Person}
7629      * class in order to get an optimal rendering of the notification and its avatars. For
7630      * conversations involving multiple people, the app should also make sure that it marks the
7631      * conversation as a group with {@link #setGroupConversation(boolean)}.
7632      *
7633      * <p>
7634      * This class is a "rebuilder": It attaches to a Builder object and modifies its behavior.
7635      * Here's an example of how this may be used:
7636      * <pre class="prettyprint">
7637      *
7638      * Person user = new Person.Builder().setIcon(userIcon).setName(userName).build();
7639      * MessagingStyle style = new MessagingStyle(user)
7640      *      .addMessage(messages[1].getText(), messages[1].getTime(), messages[1].getPerson())
7641      *      .addMessage(messages[2].getText(), messages[2].getTime(), messages[2].getPerson())
7642      *      .setGroupConversation(hasMultiplePeople());
7643      *
7644      * Notification noti = new Notification.Builder()
7645      *     .setContentTitle(&quot;2 new messages with &quot; + sender.toString())
7646      *     .setContentText(subject)
7647      *     .setSmallIcon(R.drawable.new_message)
7648      *     .setLargeIcon(aBitmap)
7649      *     .setStyle(style)
7650      *     .build();
7651      * </pre>
7652      */
7653     public static class MessagingStyle extends Style {
7654 
7655         /**
7656          * The maximum number of messages that will be retained in the Notification itself (the
7657          * number displayed is up to the platform).
7658          */
7659         public static final int MAXIMUM_RETAINED_MESSAGES = 25;
7660 
7661 
7662         /** @hide */
7663         public static final int CONVERSATION_TYPE_LEGACY = 0;
7664         /** @hide */
7665         public static final int CONVERSATION_TYPE_NORMAL = 1;
7666         /** @hide */
7667         public static final int CONVERSATION_TYPE_IMPORTANT = 2;
7668 
7669         /** @hide */
7670         @IntDef(prefix = {"CONVERSATION_TYPE_"}, value = {
7671                 CONVERSATION_TYPE_LEGACY, CONVERSATION_TYPE_NORMAL, CONVERSATION_TYPE_IMPORTANT
7672         })
7673         @Retention(RetentionPolicy.SOURCE)
7674         public @interface ConversationType {}
7675 
7676         @NonNull Person mUser;
7677         @Nullable CharSequence mConversationTitle;
7678         @Nullable Icon mShortcutIcon;
7679         List<Message> mMessages = new ArrayList<>();
7680         List<Message> mHistoricMessages = new ArrayList<>();
7681         boolean mIsGroupConversation;
7682         @ConversationType int mConversationType = CONVERSATION_TYPE_LEGACY;
7683         int mUnreadMessageCount;
7684 
MessagingStyle()7685         MessagingStyle() {
7686         }
7687 
7688         /**
7689          * @param userDisplayName Required - the name to be displayed for any replies sent by the
7690          * user before the posting app reposts the notification with those messages after they've
7691          * been actually sent and in previous messages sent by the user added in
7692          * {@link #addMessage(Notification.MessagingStyle.Message)}
7693          *
7694          * @deprecated use {@code MessagingStyle(Person)}
7695          */
MessagingStyle(@onNull CharSequence userDisplayName)7696         public MessagingStyle(@NonNull CharSequence userDisplayName) {
7697             this(new Person.Builder().setName(userDisplayName).build());
7698         }
7699 
7700         /**
7701          * @param user Required - The person displayed for any messages that are sent by the
7702          * user. Any messages added with {@link #addMessage(Notification.MessagingStyle.Message)}
7703          * who don't have a Person associated with it will be displayed as if they were sent
7704          * by this user. The user also needs to have a valid name associated with it, which will
7705          * be enforced starting in Android P.
7706          */
MessagingStyle(@onNull Person user)7707         public MessagingStyle(@NonNull Person user) {
7708             mUser = user;
7709         }
7710 
7711         /**
7712          * Validate that this style was properly composed. This is called at build time.
7713          * @hide
7714          */
7715         @Override
validate(Context context)7716         public void validate(Context context) {
7717             super.validate(context);
7718             if (context.getApplicationInfo().targetSdkVersion
7719                     >= Build.VERSION_CODES.P && (mUser == null || mUser.getName() == null)) {
7720                 throw new RuntimeException("User must be valid and have a name.");
7721             }
7722         }
7723 
7724         /**
7725          * @return the text that should be displayed in the statusBar when heads upped.
7726          * If {@code null} is returned, the default implementation will be used.
7727          *
7728          * @hide
7729          */
7730         @Override
getHeadsUpStatusBarText()7731         public CharSequence getHeadsUpStatusBarText() {
7732             CharSequence conversationTitle = !TextUtils.isEmpty(super.mBigContentTitle)
7733                     ? super.mBigContentTitle
7734                     : mConversationTitle;
7735             if (mConversationType == CONVERSATION_TYPE_LEGACY
7736                     && !TextUtils.isEmpty(conversationTitle) && !hasOnlyWhiteSpaceSenders()) {
7737                 return conversationTitle;
7738             }
7739             return null;
7740         }
7741 
7742         /**
7743          * @return the user to be displayed for any replies sent by the user
7744          */
7745         @NonNull
getUser()7746         public Person getUser() {
7747             return mUser;
7748         }
7749 
7750         /**
7751          * Returns the name to be displayed for any replies sent by the user
7752          *
7753          * @deprecated use {@link #getUser()} instead
7754          */
getUserDisplayName()7755         public CharSequence getUserDisplayName() {
7756             return mUser.getName();
7757         }
7758 
7759         /**
7760          * Sets the title to be displayed on this conversation. May be set to {@code null}.
7761          *
7762          * <p>Starting in {@link Build.VERSION_CODES#R, this conversation title will be ignored if a
7763          * valid shortcutId is added via {@link Notification.Builder#setShortcutId(String)}. In this
7764          * case, {@link ShortcutInfo#getLongLabel()} (or, if missing,
7765          * {@link ShortcutInfo#getShortLabel()}) will be shown as the conversation title
7766          * instead.
7767          *
7768          * <p>This API's behavior was changed in SDK version {@link Build.VERSION_CODES#P}. If your
7769          * application's target version is less than {@link Build.VERSION_CODES#P}, setting a
7770          * conversation title to a non-null value will make {@link #isGroupConversation()} return
7771          * {@code true} and passing {@code null} will make it return {@code false}. In
7772          * {@link Build.VERSION_CODES#P} and beyond, use {@link #setGroupConversation(boolean)}
7773          * to set group conversation status.
7774          *
7775          * @param conversationTitle Title displayed for this conversation
7776          * @return this object for method chaining
7777          */
setConversationTitle(@ullable CharSequence conversationTitle)7778         public MessagingStyle setConversationTitle(@Nullable CharSequence conversationTitle) {
7779             mConversationTitle = conversationTitle;
7780             return this;
7781         }
7782 
7783         /**
7784          * Return the title to be displayed on this conversation. May return {@code null}.
7785          */
7786         @Nullable
getConversationTitle()7787         public CharSequence getConversationTitle() {
7788             return mConversationTitle;
7789         }
7790 
7791         /**
7792          * Sets the icon to be displayed on the conversation, derived from the shortcutId.
7793          *
7794          * @hide
7795          */
setShortcutIcon(@ullable Icon conversationIcon)7796         public MessagingStyle setShortcutIcon(@Nullable Icon conversationIcon) {
7797             mShortcutIcon = conversationIcon;
7798             return this;
7799         }
7800 
7801         /**
7802          * Return the icon to be displayed on this conversation, derived from the shortcutId. May
7803          * return {@code null}.
7804          *
7805          * @hide
7806          */
7807         @Nullable
getShortcutIcon()7808         public Icon getShortcutIcon() {
7809             return mShortcutIcon;
7810         }
7811 
7812         /**
7813          * Sets the conversation type of this MessageStyle notification.
7814          * {@link #CONVERSATION_TYPE_LEGACY} will use the "older" layout from pre-R,
7815          * {@link #CONVERSATION_TYPE_NORMAL} will use the new "conversation" layout, and
7816          * {@link #CONVERSATION_TYPE_IMPORTANT} will add additional "important" treatments.
7817          *
7818          * @hide
7819          */
setConversationType(@onversationType int conversationType)7820         public MessagingStyle setConversationType(@ConversationType int conversationType) {
7821             mConversationType = conversationType;
7822             return this;
7823         }
7824 
7825         /** @hide */
7826         @ConversationType
getConversationType()7827         public int getConversationType() {
7828             return mConversationType;
7829         }
7830 
7831         /** @hide */
getUnreadMessageCount()7832         public int getUnreadMessageCount() {
7833             return mUnreadMessageCount;
7834         }
7835 
7836         /** @hide */
setUnreadMessageCount(int unreadMessageCount)7837         public MessagingStyle setUnreadMessageCount(int unreadMessageCount) {
7838             mUnreadMessageCount = unreadMessageCount;
7839             return this;
7840         }
7841 
7842         /**
7843          * Adds a message for display by this notification. Convenience call for a simple
7844          * {@link Message} in {@link #addMessage(Notification.MessagingStyle.Message)}.
7845          * @param text A {@link CharSequence} to be displayed as the message content
7846          * @param timestamp Time at which the message arrived
7847          * @param sender A {@link CharSequence} to be used for displaying the name of the
7848          * sender. Should be <code>null</code> for messages by the current user, in which case
7849          * the platform will insert {@link #getUserDisplayName()}.
7850          * Should be unique amongst all individuals in the conversation, and should be
7851          * consistent during re-posts of the notification.
7852          *
7853          * @see Message#Message(CharSequence, long, CharSequence)
7854          *
7855          * @return this object for method chaining
7856          *
7857          * @deprecated use {@link #addMessage(CharSequence, long, Person)}
7858          */
addMessage(CharSequence text, long timestamp, CharSequence sender)7859         public MessagingStyle addMessage(CharSequence text, long timestamp, CharSequence sender) {
7860             return addMessage(text, timestamp,
7861                     sender == null ? null : new Person.Builder().setName(sender).build());
7862         }
7863 
7864         /**
7865          * Adds a message for display by this notification. Convenience call for a simple
7866          * {@link Message} in {@link #addMessage(Notification.MessagingStyle.Message)}.
7867          * @param text A {@link CharSequence} to be displayed as the message content
7868          * @param timestamp Time at which the message arrived
7869          * @param sender The {@link Person} who sent the message.
7870          * Should be <code>null</code> for messages by the current user, in which case
7871          * the platform will insert the user set in {@code MessagingStyle(Person)}.
7872          *
7873          * @see Message#Message(CharSequence, long, CharSequence)
7874          *
7875          * @return this object for method chaining
7876          */
addMessage(@onNull CharSequence text, long timestamp, @Nullable Person sender)7877         public MessagingStyle addMessage(@NonNull CharSequence text, long timestamp,
7878                 @Nullable Person sender) {
7879             return addMessage(new Message(text, timestamp, sender));
7880         }
7881 
7882         /**
7883          * Adds a {@link Message} for display in this notification.
7884          *
7885          * <p>The messages should be added in chronologic order, i.e. the oldest first,
7886          * the newest last.
7887          *
7888          * @param message The {@link Message} to be displayed
7889          * @return this object for method chaining
7890          */
addMessage(Message message)7891         public MessagingStyle addMessage(Message message) {
7892             mMessages.add(message);
7893             if (mMessages.size() > MAXIMUM_RETAINED_MESSAGES) {
7894                 mMessages.remove(0);
7895             }
7896             return this;
7897         }
7898 
7899         /**
7900          * Adds a {@link Message} for historic context in this notification.
7901          *
7902          * <p>Messages should be added as historic if they are not the main subject of the
7903          * notification but may give context to a conversation. The system may choose to present
7904          * them only when relevant, e.g. when replying to a message through a {@link RemoteInput}.
7905          *
7906          * <p>The messages should be added in chronologic order, i.e. the oldest first,
7907          * the newest last.
7908          *
7909          * @param message The historic {@link Message} to be added
7910          * @return this object for method chaining
7911          */
addHistoricMessage(Message message)7912         public MessagingStyle addHistoricMessage(Message message) {
7913             mHistoricMessages.add(message);
7914             if (mHistoricMessages.size() > MAXIMUM_RETAINED_MESSAGES) {
7915                 mHistoricMessages.remove(0);
7916             }
7917             return this;
7918         }
7919 
7920         /**
7921          * Gets the list of {@code Message} objects that represent the notification
7922          */
getMessages()7923         public List<Message> getMessages() {
7924             return mMessages;
7925         }
7926 
7927         /**
7928          * Gets the list of historic {@code Message}s in the notification.
7929          */
getHistoricMessages()7930         public List<Message> getHistoricMessages() {
7931             return mHistoricMessages;
7932         }
7933 
7934         /**
7935          * Sets whether this conversation notification represents a group. If the app is targeting
7936          * Android P, this is required if the app wants to display the largeIcon set with
7937          * {@link Notification.Builder#setLargeIcon(Bitmap)}, otherwise it will be hidden.
7938          *
7939          * @param isGroupConversation {@code true} if the conversation represents a group,
7940          * {@code false} otherwise.
7941          * @return this object for method chaining
7942          */
setGroupConversation(boolean isGroupConversation)7943         public MessagingStyle setGroupConversation(boolean isGroupConversation) {
7944             mIsGroupConversation = isGroupConversation;
7945             return this;
7946         }
7947 
7948         /**
7949          * Returns {@code true} if this notification represents a group conversation, otherwise
7950          * {@code false}.
7951          *
7952          * <p> If the application that generated this {@link MessagingStyle} targets an SDK version
7953          * less than {@link Build.VERSION_CODES#P}, this method becomes dependent on whether or
7954          * not the conversation title is set; returning {@code true} if the conversation title is
7955          * a non-null value, or {@code false} otherwise. From {@link Build.VERSION_CODES#P} forward,
7956          * this method returns what's set by {@link #setGroupConversation(boolean)} allowing for
7957          * named, non-group conversations.
7958          *
7959          * @see #setConversationTitle(CharSequence)
7960          */
isGroupConversation()7961         public boolean isGroupConversation() {
7962             // When target SDK version is < P, a non-null conversation title dictates if this is
7963             // as group conversation.
7964             if (mBuilder != null
7965                     && mBuilder.mContext.getApplicationInfo().targetSdkVersion
7966                             < Build.VERSION_CODES.P) {
7967                 return mConversationTitle != null;
7968             }
7969 
7970             return mIsGroupConversation;
7971         }
7972 
7973         /**
7974          * @hide
7975          */
7976         @Override
addExtras(Bundle extras)7977         public void addExtras(Bundle extras) {
7978             super.addExtras(extras);
7979             if (mUser != null) {
7980                 // For legacy usages
7981                 extras.putCharSequence(EXTRA_SELF_DISPLAY_NAME, mUser.getName());
7982                 extras.putParcelable(EXTRA_MESSAGING_PERSON, mUser);
7983             }
7984             if (mConversationTitle != null) {
7985                 extras.putCharSequence(EXTRA_CONVERSATION_TITLE, mConversationTitle);
7986             }
7987             if (!mMessages.isEmpty()) { extras.putParcelableArray(EXTRA_MESSAGES,
7988                     Message.getBundleArrayForMessages(mMessages));
7989             }
7990             if (!mHistoricMessages.isEmpty()) { extras.putParcelableArray(EXTRA_HISTORIC_MESSAGES,
7991                     Message.getBundleArrayForMessages(mHistoricMessages));
7992             }
7993             if (mShortcutIcon != null) {
7994                 extras.putParcelable(EXTRA_CONVERSATION_ICON, mShortcutIcon);
7995             }
7996             extras.putInt(EXTRA_CONVERSATION_UNREAD_MESSAGE_COUNT, mUnreadMessageCount);
7997 
7998             fixTitleAndTextExtras(extras);
7999             extras.putBoolean(EXTRA_IS_GROUP_CONVERSATION, mIsGroupConversation);
8000         }
8001 
fixTitleAndTextExtras(Bundle extras)8002         private void fixTitleAndTextExtras(Bundle extras) {
8003             Message m = findLatestIncomingMessage();
8004             CharSequence text = (m == null) ? null : m.mText;
8005             CharSequence sender = m == null ? null
8006                     : m.mSender == null || TextUtils.isEmpty(m.mSender.getName())
8007                             ? mUser.getName() : m.mSender.getName();
8008             CharSequence title;
8009             if (!TextUtils.isEmpty(mConversationTitle)) {
8010                 if (!TextUtils.isEmpty(sender)) {
8011                     BidiFormatter bidi = BidiFormatter.getInstance();
8012                     title = mBuilder.mContext.getString(
8013                             com.android.internal.R.string.notification_messaging_title_template,
8014                             bidi.unicodeWrap(mConversationTitle), bidi.unicodeWrap(sender));
8015                 } else {
8016                     title = mConversationTitle;
8017                 }
8018             } else {
8019                 title = sender;
8020             }
8021 
8022             if (title != null) {
8023                 extras.putCharSequence(EXTRA_TITLE, title);
8024             }
8025             if (text != null) {
8026                 extras.putCharSequence(EXTRA_TEXT, text);
8027             }
8028         }
8029 
8030         /**
8031          * @hide
8032          */
8033         @Override
restoreFromExtras(Bundle extras)8034         protected void restoreFromExtras(Bundle extras) {
8035             super.restoreFromExtras(extras);
8036 
8037             mUser = extras.getParcelable(EXTRA_MESSAGING_PERSON);
8038             if (mUser == null) {
8039                 CharSequence displayName = extras.getCharSequence(EXTRA_SELF_DISPLAY_NAME);
8040                 mUser = new Person.Builder().setName(displayName).build();
8041             }
8042             mConversationTitle = extras.getCharSequence(EXTRA_CONVERSATION_TITLE);
8043             Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES);
8044             mMessages = Message.getMessagesFromBundleArray(messages);
8045             Parcelable[] histMessages = extras.getParcelableArray(EXTRA_HISTORIC_MESSAGES);
8046             mHistoricMessages = Message.getMessagesFromBundleArray(histMessages);
8047             mIsGroupConversation = extras.getBoolean(EXTRA_IS_GROUP_CONVERSATION);
8048             mUnreadMessageCount = extras.getInt(EXTRA_CONVERSATION_UNREAD_MESSAGE_COUNT);
8049             mShortcutIcon = extras.getParcelable(EXTRA_CONVERSATION_ICON);
8050         }
8051 
8052         /**
8053          * @hide
8054          */
8055         @Override
makeContentView(boolean increasedHeight)8056         public RemoteViews makeContentView(boolean increasedHeight) {
8057             // All messaging templates contain the actions
8058             ArrayList<Action> originalActions = mBuilder.mActions;
8059             try {
8060                 mBuilder.mActions = new ArrayList<>();
8061                 return makeMessagingView(StandardTemplateParams.VIEW_TYPE_NORMAL);
8062             } finally {
8063                 mBuilder.mActions = originalActions;
8064             }
8065         }
8066 
8067         /**
8068          * @hide
8069          * Spans are ignored when comparing text for visual difference.
8070          */
8071         @Override
areNotificationsVisiblyDifferent(Style other)8072         public boolean areNotificationsVisiblyDifferent(Style other) {
8073             if (other == null || getClass() != other.getClass()) {
8074                 return true;
8075             }
8076             MessagingStyle newS = (MessagingStyle) other;
8077             List<MessagingStyle.Message> oldMs = getMessages();
8078             List<MessagingStyle.Message> newMs = newS.getMessages();
8079 
8080             if (oldMs == null || newMs == null) {
8081                 newMs = new ArrayList<>();
8082             }
8083 
8084             int n = oldMs.size();
8085             if (n != newMs.size()) {
8086                 return true;
8087             }
8088             for (int i = 0; i < n; i++) {
8089                 MessagingStyle.Message oldM = oldMs.get(i);
8090                 MessagingStyle.Message newM = newMs.get(i);
8091                 if (!Objects.equals(
8092                         String.valueOf(oldM.getText()),
8093                         String.valueOf(newM.getText()))) {
8094                     return true;
8095                 }
8096                 if (!Objects.equals(oldM.getDataUri(), newM.getDataUri())) {
8097                     return true;
8098                 }
8099                 String oldSender = String.valueOf(oldM.getSenderPerson() == null
8100                         ? oldM.getSender()
8101                         : oldM.getSenderPerson().getName());
8102                 String newSender = String.valueOf(newM.getSenderPerson() == null
8103                         ? newM.getSender()
8104                         : newM.getSenderPerson().getName());
8105                 if (!Objects.equals(oldSender, newSender)) {
8106                     return true;
8107                 }
8108 
8109                 String oldKey = oldM.getSenderPerson() == null
8110                         ? null : oldM.getSenderPerson().getKey();
8111                 String newKey = newM.getSenderPerson() == null
8112                         ? null : newM.getSenderPerson().getKey();
8113                 if (!Objects.equals(oldKey, newKey)) {
8114                     return true;
8115                 }
8116                 // Other fields (like timestamp) intentionally excluded
8117             }
8118             return false;
8119         }
8120 
findLatestIncomingMessage()8121         private Message findLatestIncomingMessage() {
8122             return findLatestIncomingMessage(mMessages);
8123         }
8124 
8125         /**
8126          * @hide
8127          */
8128         @Nullable
findLatestIncomingMessage( List<Message> messages)8129         public static Message findLatestIncomingMessage(
8130                 List<Message> messages) {
8131             for (int i = messages.size() - 1; i >= 0; i--) {
8132                 Message m = messages.get(i);
8133                 // Incoming messages have a non-empty sender.
8134                 if (m.mSender != null && !TextUtils.isEmpty(m.mSender.getName())) {
8135                     return m;
8136                 }
8137             }
8138             if (!messages.isEmpty()) {
8139                 // No incoming messages, fall back to outgoing message
8140                 return messages.get(messages.size() - 1);
8141             }
8142             return null;
8143         }
8144 
8145         /**
8146          * @hide
8147          */
8148         @Override
makeBigContentView()8149         public RemoteViews makeBigContentView() {
8150             return makeMessagingView(StandardTemplateParams.VIEW_TYPE_BIG);
8151         }
8152 
8153         /**
8154          * Create a messaging layout.
8155          *
8156          * @param viewType one of StandardTemplateParams.VIEW_TYPE_NORMAL, VIEW_TYPE_BIG,
8157          *                VIEW_TYPE_HEADS_UP
8158          * @return the created remoteView.
8159          */
8160         @NonNull
makeMessagingView(int viewType)8161         private RemoteViews makeMessagingView(int viewType) {
8162             boolean isCollapsed = viewType != StandardTemplateParams.VIEW_TYPE_BIG;
8163             boolean hideRightIcons = viewType != StandardTemplateParams.VIEW_TYPE_NORMAL;
8164             boolean isConversationLayout = mConversationType != CONVERSATION_TYPE_LEGACY;
8165             boolean isImportantConversation = mConversationType == CONVERSATION_TYPE_IMPORTANT;
8166             boolean isHeaderless = !isConversationLayout && isCollapsed;
8167 
8168             CharSequence conversationTitle = !TextUtils.isEmpty(super.mBigContentTitle)
8169                     ? super.mBigContentTitle
8170                     : mConversationTitle;
8171             boolean atLeastP = mBuilder.mContext.getApplicationInfo().targetSdkVersion
8172                     >= Build.VERSION_CODES.P;
8173             boolean isOneToOne;
8174             CharSequence nameReplacement = null;
8175             if (!atLeastP) {
8176                 isOneToOne = TextUtils.isEmpty(conversationTitle);
8177                 if (hasOnlyWhiteSpaceSenders()) {
8178                     isOneToOne = true;
8179                     nameReplacement = conversationTitle;
8180                     conversationTitle = null;
8181                 }
8182             } else {
8183                 isOneToOne = !isGroupConversation();
8184             }
8185             if (isHeaderless && isOneToOne && TextUtils.isEmpty(conversationTitle)) {
8186                 conversationTitle = getOtherPersonName();
8187             }
8188 
8189             Icon largeIcon = mBuilder.mN.mLargeIcon;
8190             TemplateBindResult bindResult = new TemplateBindResult();
8191             StandardTemplateParams p = mBuilder.mParams.reset()
8192                     .viewType(viewType)
8193                     .highlightExpander(isConversationLayout)
8194                     .hideProgress(true)
8195                     .title(isHeaderless ? conversationTitle : null)
8196                     .text(null)
8197                     .hideLeftIcon(isOneToOne)
8198                     .hideRightIcon(hideRightIcons || isOneToOne)
8199                     .headerTextSecondary(isHeaderless ? null : conversationTitle);
8200             RemoteViews contentView = mBuilder.applyStandardTemplateWithActions(
8201                     isConversationLayout
8202                             ? mBuilder.getConversationLayoutResource()
8203                             : isCollapsed
8204                                     ? mBuilder.getMessagingLayoutResource()
8205                                     : mBuilder.getBigMessagingLayoutResource(),
8206                     p,
8207                     bindResult);
8208             if (isConversationLayout) {
8209                 mBuilder.setTextViewColorPrimary(contentView, R.id.conversation_text, p);
8210                 mBuilder.setTextViewColorSecondary(contentView, R.id.app_name_divider, p);
8211             }
8212 
8213             addExtras(mBuilder.mN.extras);
8214             contentView.setInt(R.id.status_bar_latest_event_content, "setLayoutColor",
8215                     mBuilder.getSmallIconColor(p));
8216             contentView.setInt(R.id.status_bar_latest_event_content, "setSenderTextColor",
8217                     mBuilder.getPrimaryTextColor(p));
8218             contentView.setInt(R.id.status_bar_latest_event_content, "setMessageTextColor",
8219                     mBuilder.getSecondaryTextColor(p));
8220             contentView.setInt(R.id.status_bar_latest_event_content,
8221                     "setNotificationBackgroundColor",
8222                     mBuilder.getBackgroundColor(p));
8223             contentView.setBoolean(R.id.status_bar_latest_event_content, "setIsCollapsed",
8224                     isCollapsed);
8225             contentView.setIcon(R.id.status_bar_latest_event_content, "setAvatarReplacement",
8226                     mBuilder.mN.mLargeIcon);
8227             contentView.setCharSequence(R.id.status_bar_latest_event_content, "setNameReplacement",
8228                     nameReplacement);
8229             contentView.setBoolean(R.id.status_bar_latest_event_content, "setIsOneToOne",
8230                     isOneToOne);
8231             contentView.setCharSequence(R.id.status_bar_latest_event_content,
8232                     "setConversationTitle", conversationTitle);
8233             if (isConversationLayout) {
8234                 contentView.setIcon(R.id.status_bar_latest_event_content,
8235                         "setShortcutIcon", mShortcutIcon);
8236                 contentView.setBoolean(R.id.status_bar_latest_event_content,
8237                         "setIsImportantConversation", isImportantConversation);
8238             }
8239             if (isHeaderless) {
8240                 // Collapsed legacy messaging style has a 1-line limit.
8241                 contentView.setInt(R.id.notification_messaging, "setMaxDisplayedLines", 1);
8242             }
8243             contentView.setIcon(R.id.status_bar_latest_event_content, "setLargeIcon",
8244                     largeIcon);
8245             contentView.setBundle(R.id.status_bar_latest_event_content, "setData",
8246                     mBuilder.mN.extras);
8247             return contentView;
8248         }
8249 
getKey(Person person)8250         private CharSequence getKey(Person person) {
8251             return person == null ? null
8252                     : person.getKey() == null ? person.getName() : person.getKey();
8253         }
8254 
getOtherPersonName()8255         private CharSequence getOtherPersonName() {
8256             CharSequence userKey = getKey(mUser);
8257             for (int i = mMessages.size() - 1; i >= 0; i--) {
8258                 Person sender = mMessages.get(i).getSenderPerson();
8259                 if (sender != null && !TextUtils.equals(userKey, getKey(sender))) {
8260                     return sender.getName();
8261                 }
8262             }
8263             return null;
8264         }
8265 
hasOnlyWhiteSpaceSenders()8266         private boolean hasOnlyWhiteSpaceSenders() {
8267             for (int i = 0; i < mMessages.size(); i++) {
8268                 Message m = mMessages.get(i);
8269                 Person sender = m.getSenderPerson();
8270                 if (sender != null && !isWhiteSpace(sender.getName())) {
8271                     return false;
8272                 }
8273             }
8274             return true;
8275         }
8276 
isWhiteSpace(CharSequence sender)8277         private boolean isWhiteSpace(CharSequence sender) {
8278             if (TextUtils.isEmpty(sender)) {
8279                 return true;
8280             }
8281             if (sender.toString().matches("^\\s*$")) {
8282                 return true;
8283             }
8284             // Let's check if we only have 0 whitespace chars. Some apps did this as a workaround
8285             // For the presentation that we had.
8286             for (int i = 0; i < sender.length(); i++) {
8287                 char c = sender.charAt(i);
8288                 if (c != '\u200B') {
8289                     return false;
8290                 }
8291             }
8292             return true;
8293         }
8294 
8295         /**
8296          * @hide
8297          */
8298         @Override
makeHeadsUpContentView(boolean increasedHeight)8299         public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
8300             return makeMessagingView(StandardTemplateParams.VIEW_TYPE_HEADS_UP);
8301         }
8302 
8303         public static final class Message {
8304             /** @hide */
8305             public static final String KEY_TEXT = "text";
8306             static final String KEY_TIMESTAMP = "time";
8307             static final String KEY_SENDER = "sender";
8308             static final String KEY_SENDER_PERSON = "sender_person";
8309             static final String KEY_DATA_MIME_TYPE = "type";
8310             static final String KEY_DATA_URI= "uri";
8311             static final String KEY_EXTRAS_BUNDLE = "extras";
8312             static final String KEY_REMOTE_INPUT_HISTORY = "remote_input_history";
8313 
8314             private final CharSequence mText;
8315             private final long mTimestamp;
8316             @Nullable
8317             private final Person mSender;
8318             /** True if this message was generated from the extra
8319              *  {@link Notification#EXTRA_REMOTE_INPUT_HISTORY_ITEMS}
8320              */
8321             private final boolean mRemoteInputHistory;
8322 
8323             private Bundle mExtras = new Bundle();
8324             private String mDataMimeType;
8325             private Uri mDataUri;
8326 
8327             /**
8328              * Constructor
8329              * @param text A {@link CharSequence} to be displayed as the message content
8330              * @param timestamp Time at which the message arrived
8331              * @param sender A {@link CharSequence} to be used for displaying the name of the
8332              * sender. Should be <code>null</code> for messages by the current user, in which case
8333              * the platform will insert {@link MessagingStyle#getUserDisplayName()}.
8334              * Should be unique amongst all individuals in the conversation, and should be
8335              * consistent during re-posts of the notification.
8336              *
8337              *  @deprecated use {@code Message(CharSequence, long, Person)}
8338              */
Message(CharSequence text, long timestamp, CharSequence sender)8339             public Message(CharSequence text, long timestamp, CharSequence sender){
8340                 this(text, timestamp, sender == null ? null
8341                         : new Person.Builder().setName(sender).build());
8342             }
8343 
8344             /**
8345              * Constructor
8346              * @param text A {@link CharSequence} to be displayed as the message content
8347              * @param timestamp Time at which the message arrived
8348              * @param sender The {@link Person} who sent the message.
8349              * Should be <code>null</code> for messages by the current user, in which case
8350              * the platform will insert the user set in {@code MessagingStyle(Person)}.
8351              * <p>
8352              * The person provided should contain an Icon, set with
8353              * {@link Person.Builder#setIcon(Icon)} and also have a name provided
8354              * with {@link Person.Builder#setName(CharSequence)}. If multiple users have the same
8355              * name, consider providing a key with {@link Person.Builder#setKey(String)} in order
8356              * to differentiate between the different users.
8357              * </p>
8358              */
Message(@onNull CharSequence text, long timestamp, @Nullable Person sender)8359             public Message(@NonNull CharSequence text, long timestamp, @Nullable Person sender) {
8360                 this(text, timestamp, sender, false /* remoteHistory */);
8361             }
8362 
8363             /**
8364              * Constructor
8365              * @param text A {@link CharSequence} to be displayed as the message content
8366              * @param timestamp Time at which the message arrived
8367              * @param sender The {@link Person} who sent the message.
8368              * Should be <code>null</code> for messages by the current user, in which case
8369              * the platform will insert the user set in {@code MessagingStyle(Person)}.
8370              * @param remoteInputHistory True if the messages was generated from the extra
8371              * {@link Notification#EXTRA_REMOTE_INPUT_HISTORY_ITEMS}.
8372              * <p>
8373              * The person provided should contain an Icon, set with
8374              * {@link Person.Builder#setIcon(Icon)} and also have a name provided
8375              * with {@link Person.Builder#setName(CharSequence)}. If multiple users have the same
8376              * name, consider providing a key with {@link Person.Builder#setKey(String)} in order
8377              * to differentiate between the different users.
8378              * </p>
8379              * @hide
8380              */
Message(@onNull CharSequence text, long timestamp, @Nullable Person sender, boolean remoteInputHistory)8381             public Message(@NonNull CharSequence text, long timestamp, @Nullable Person sender,
8382                     boolean remoteInputHistory) {
8383                 mText = safeCharSequence(text);
8384                 mTimestamp = timestamp;
8385                 mSender = sender;
8386                 mRemoteInputHistory = remoteInputHistory;
8387             }
8388 
8389             /**
8390              * Sets a binary blob of data and an associated MIME type for a message. In the case
8391              * where the platform doesn't support the MIME type, the original text provided in the
8392              * constructor will be used.
8393              * @param dataMimeType The MIME type of the content. See
8394              * <a href="{@docRoot}notifications/messaging.html"> for the list of supported MIME
8395              * types on Android and Android Wear.
8396              * @param dataUri The uri containing the content whose type is given by the MIME type.
8397              * <p class="note">
8398              * <ol>
8399              *   <li>Notification Listeners including the System UI need permission to access the
8400              *       data the Uri points to. The recommended ways to do this are:</li>
8401              *   <li>Store the data in your own ContentProvider, making sure that other apps have
8402              *       the correct permission to access your provider. The preferred mechanism for
8403              *       providing access is to use per-URI permissions which are temporary and only
8404              *       grant access to the receiving application. An easy way to create a
8405              *       ContentProvider like this is to use the FileProvider helper class.</li>
8406              *   <li>Use the system MediaStore. The MediaStore is primarily aimed at video, audio
8407              *       and image MIME types, however beginning with Android 3.0 (API level 11) it can
8408              *       also store non-media types (see MediaStore.Files for more info). Files can be
8409              *       inserted into the MediaStore using scanFile() after which a content:// style
8410              *       Uri suitable for sharing is passed to the provided onScanCompleted() callback.
8411              *       Note that once added to the system MediaStore the content is accessible to any
8412              *       app on the device.</li>
8413              * </ol>
8414              * @return this object for method chaining
8415              */
setData(String dataMimeType, Uri dataUri)8416             public Message setData(String dataMimeType, Uri dataUri) {
8417                 mDataMimeType = dataMimeType;
8418                 mDataUri = dataUri;
8419                 return this;
8420             }
8421 
8422             /**
8423              * Get the text to be used for this message, or the fallback text if a type and content
8424              * Uri have been set
8425              */
getText()8426             public CharSequence getText() {
8427                 return mText;
8428             }
8429 
8430             /**
8431              * Get the time at which this message arrived
8432              */
getTimestamp()8433             public long getTimestamp() {
8434                 return mTimestamp;
8435             }
8436 
8437             /**
8438              * Get the extras Bundle for this message.
8439              */
getExtras()8440             public Bundle getExtras() {
8441                 return mExtras;
8442             }
8443 
8444             /**
8445              * Get the text used to display the contact's name in the messaging experience
8446              *
8447              * @deprecated use {@link #getSenderPerson()}
8448              */
getSender()8449             public CharSequence getSender() {
8450                 return mSender == null ? null : mSender.getName();
8451             }
8452 
8453             /**
8454              * Get the sender associated with this message.
8455              */
8456             @Nullable
getSenderPerson()8457             public Person getSenderPerson() {
8458                 return mSender;
8459             }
8460 
8461             /**
8462              * Get the MIME type of the data pointed to by the Uri
8463              */
getDataMimeType()8464             public String getDataMimeType() {
8465                 return mDataMimeType;
8466             }
8467 
8468             /**
8469              * Get the Uri pointing to the content of the message. Can be null, in which case
8470              * {@see #getText()} is used.
8471              */
getDataUri()8472             public Uri getDataUri() {
8473                 return mDataUri;
8474             }
8475 
8476             /**
8477              * @return True if the message was generated from
8478              * {@link Notification#EXTRA_REMOTE_INPUT_HISTORY_ITEMS}.
8479              * @hide
8480              */
isRemoteInputHistory()8481             public boolean isRemoteInputHistory() {
8482                 return mRemoteInputHistory;
8483             }
8484 
8485             /**
8486              * @hide
8487              */
8488             @VisibleForTesting
toBundle()8489             public Bundle toBundle() {
8490                 Bundle bundle = new Bundle();
8491                 if (mText != null) {
8492                     bundle.putCharSequence(KEY_TEXT, mText);
8493                 }
8494                 bundle.putLong(KEY_TIMESTAMP, mTimestamp);
8495                 if (mSender != null) {
8496                     // Legacy listeners need this
8497                     bundle.putCharSequence(KEY_SENDER, safeCharSequence(mSender.getName()));
8498                     bundle.putParcelable(KEY_SENDER_PERSON, mSender);
8499                 }
8500                 if (mDataMimeType != null) {
8501                     bundle.putString(KEY_DATA_MIME_TYPE, mDataMimeType);
8502                 }
8503                 if (mDataUri != null) {
8504                     bundle.putParcelable(KEY_DATA_URI, mDataUri);
8505                 }
8506                 if (mExtras != null) {
8507                     bundle.putBundle(KEY_EXTRAS_BUNDLE, mExtras);
8508                 }
8509                 if (mRemoteInputHistory) {
8510                     bundle.putBoolean(KEY_REMOTE_INPUT_HISTORY, mRemoteInputHistory);
8511                 }
8512                 return bundle;
8513             }
8514 
getBundleArrayForMessages(List<Message> messages)8515             static Bundle[] getBundleArrayForMessages(List<Message> messages) {
8516                 Bundle[] bundles = new Bundle[messages.size()];
8517                 final int N = messages.size();
8518                 for (int i = 0; i < N; i++) {
8519                     bundles[i] = messages.get(i).toBundle();
8520                 }
8521                 return bundles;
8522             }
8523 
8524             /**
8525              * Returns a list of messages read from the given bundle list, e.g.
8526              * {@link #EXTRA_MESSAGES} or {@link #EXTRA_HISTORIC_MESSAGES}.
8527              */
8528             @NonNull
getMessagesFromBundleArray(@ullable Parcelable[] bundles)8529             public static List<Message> getMessagesFromBundleArray(@Nullable Parcelable[] bundles) {
8530                 if (bundles == null) {
8531                     return new ArrayList<>();
8532                 }
8533                 List<Message> messages = new ArrayList<>(bundles.length);
8534                 for (int i = 0; i < bundles.length; i++) {
8535                     if (bundles[i] instanceof Bundle) {
8536                         Message message = getMessageFromBundle((Bundle)bundles[i]);
8537                         if (message != null) {
8538                             messages.add(message);
8539                         }
8540                     }
8541                 }
8542                 return messages;
8543             }
8544 
8545             /**
8546              * Returns the message that is stored in the bundle (e.g. one of the values in the lists
8547              * in {@link #EXTRA_MESSAGES} or {@link #EXTRA_HISTORIC_MESSAGES}) or null if the
8548              * message couldn't be resolved.
8549              * @hide
8550              */
8551             @Nullable
getMessageFromBundle(@onNull Bundle bundle)8552             public static Message getMessageFromBundle(@NonNull Bundle bundle) {
8553                 try {
8554                     if (!bundle.containsKey(KEY_TEXT) || !bundle.containsKey(KEY_TIMESTAMP)) {
8555                         return null;
8556                     } else {
8557 
8558                         Person senderPerson = bundle.getParcelable(KEY_SENDER_PERSON);
8559                         if (senderPerson == null) {
8560                             // Legacy apps that use compat don't actually provide the sender objects
8561                             // We need to fix the compat version to provide people / use
8562                             // the native api instead
8563                             CharSequence senderName = bundle.getCharSequence(KEY_SENDER);
8564                             if (senderName != null) {
8565                                 senderPerson = new Person.Builder().setName(senderName).build();
8566                             }
8567                         }
8568                         Message message = new Message(bundle.getCharSequence(KEY_TEXT),
8569                                 bundle.getLong(KEY_TIMESTAMP),
8570                                 senderPerson,
8571                                 bundle.getBoolean(KEY_REMOTE_INPUT_HISTORY, false));
8572                         if (bundle.containsKey(KEY_DATA_MIME_TYPE) &&
8573                                 bundle.containsKey(KEY_DATA_URI)) {
8574                             message.setData(bundle.getString(KEY_DATA_MIME_TYPE),
8575                                     (Uri) bundle.getParcelable(KEY_DATA_URI));
8576                         }
8577                         if (bundle.containsKey(KEY_EXTRAS_BUNDLE)) {
8578                             message.getExtras().putAll(bundle.getBundle(KEY_EXTRAS_BUNDLE));
8579                         }
8580                         return message;
8581                     }
8582                 } catch (ClassCastException e) {
8583                     return null;
8584                 }
8585             }
8586         }
8587     }
8588 
8589     /**
8590      * Helper class for generating large-format notifications that include a list of (up to 5) strings.
8591      *
8592      * Here's how you'd set the <code>InboxStyle</code> on a notification:
8593      * <pre class="prettyprint">
8594      * Notification notif = new Notification.Builder(mContext)
8595      *     .setContentTitle(&quot;5 New mails from &quot; + sender.toString())
8596      *     .setContentText(subject)
8597      *     .setSmallIcon(R.drawable.new_mail)
8598      *     .setLargeIcon(aBitmap)
8599      *     .setStyle(new Notification.InboxStyle()
8600      *         .addLine(str1)
8601      *         .addLine(str2)
8602      *         .setContentTitle(&quot;&quot;)
8603      *         .setSummaryText(&quot;+3 more&quot;))
8604      *     .build();
8605      * </pre>
8606      *
8607      * @see Notification#bigContentView
8608      */
8609     public static class InboxStyle extends Style {
8610 
8611         /**
8612          * The number of lines of remote input history allowed until we start reducing lines.
8613          */
8614         private static final int NUMBER_OF_HISTORY_ALLOWED_UNTIL_REDUCTION = 1;
8615         private ArrayList<CharSequence> mTexts = new ArrayList<CharSequence>(5);
8616 
InboxStyle()8617         public InboxStyle() {
8618         }
8619 
8620         /**
8621          * @deprecated use {@code InboxStyle()}.
8622          */
8623         @Deprecated
InboxStyle(Builder builder)8624         public InboxStyle(Builder builder) {
8625             setBuilder(builder);
8626         }
8627 
8628         /**
8629          * Overrides ContentTitle in the big form of the template.
8630          * This defaults to the value passed to setContentTitle().
8631          */
setBigContentTitle(CharSequence title)8632         public InboxStyle setBigContentTitle(CharSequence title) {
8633             internalSetBigContentTitle(safeCharSequence(title));
8634             return this;
8635         }
8636 
8637         /**
8638          * Set the first line of text after the detail section in the big form of the template.
8639          */
setSummaryText(CharSequence cs)8640         public InboxStyle setSummaryText(CharSequence cs) {
8641             internalSetSummaryText(safeCharSequence(cs));
8642             return this;
8643         }
8644 
8645         /**
8646          * Append a line to the digest section of the Inbox notification.
8647          */
addLine(CharSequence cs)8648         public InboxStyle addLine(CharSequence cs) {
8649             mTexts.add(safeCharSequence(cs));
8650             return this;
8651         }
8652 
8653         /**
8654          * @hide
8655          */
getLines()8656         public ArrayList<CharSequence> getLines() {
8657             return mTexts;
8658         }
8659 
8660         /**
8661          * @hide
8662          */
addExtras(Bundle extras)8663         public void addExtras(Bundle extras) {
8664             super.addExtras(extras);
8665 
8666             CharSequence[] a = new CharSequence[mTexts.size()];
8667             extras.putCharSequenceArray(EXTRA_TEXT_LINES, mTexts.toArray(a));
8668         }
8669 
8670         /**
8671          * @hide
8672          */
8673         @Override
restoreFromExtras(Bundle extras)8674         protected void restoreFromExtras(Bundle extras) {
8675             super.restoreFromExtras(extras);
8676 
8677             mTexts.clear();
8678             if (extras.containsKey(EXTRA_TEXT_LINES)) {
8679                 Collections.addAll(mTexts, extras.getCharSequenceArray(EXTRA_TEXT_LINES));
8680             }
8681         }
8682 
8683         /**
8684          * @hide
8685          */
makeBigContentView()8686         public RemoteViews makeBigContentView() {
8687             StandardTemplateParams p = mBuilder.mParams.reset()
8688                     .viewType(StandardTemplateParams.VIEW_TYPE_BIG)
8689                     .fillTextsFrom(mBuilder).text(null);
8690             TemplateBindResult result = new TemplateBindResult();
8691             RemoteViews contentView = getStandardView(mBuilder.getInboxLayoutResource(), p, result);
8692 
8693             int[] rowIds = {R.id.inbox_text0, R.id.inbox_text1, R.id.inbox_text2, R.id.inbox_text3,
8694                     R.id.inbox_text4, R.id.inbox_text5, R.id.inbox_text6};
8695 
8696             // Make sure all rows are gone in case we reuse a view.
8697             for (int rowId : rowIds) {
8698                 contentView.setViewVisibility(rowId, View.GONE);
8699             }
8700 
8701             int i=0;
8702             int topPadding = mBuilder.mContext.getResources().getDimensionPixelSize(
8703                     R.dimen.notification_inbox_item_top_padding);
8704             boolean first = true;
8705             int onlyViewId = 0;
8706             int maxRows = rowIds.length;
8707             if (mBuilder.mActions.size() > 0) {
8708                 maxRows--;
8709             }
8710             RemoteInputHistoryItem[] remoteInputHistory = getParcelableArrayFromBundle(
8711                     mBuilder.mN.extras, EXTRA_REMOTE_INPUT_HISTORY_ITEMS,
8712                     RemoteInputHistoryItem.class);
8713             if (remoteInputHistory != null
8714                     && remoteInputHistory.length > NUMBER_OF_HISTORY_ALLOWED_UNTIL_REDUCTION) {
8715                 // Let's remove some messages to make room for the remote input history.
8716                 // 1 is always able to fit, but let's remove them if they are 2 or 3
8717                 int numRemoteInputs = Math.min(remoteInputHistory.length,
8718                         MAX_REMOTE_INPUT_HISTORY_LINES);
8719                 int totalNumRows = mTexts.size() + numRemoteInputs
8720                         - NUMBER_OF_HISTORY_ALLOWED_UNTIL_REDUCTION;
8721                 if (totalNumRows > maxRows) {
8722                     int overflow = totalNumRows - maxRows;
8723                     if (mTexts.size() > maxRows) {
8724                         // Heuristic: if the Texts don't fit anyway, we'll rather drop the last
8725                         // few messages, even with the remote input
8726                         maxRows -= overflow;
8727                     } else  {
8728                         // otherwise we drop the first messages
8729                         i = overflow;
8730                     }
8731                 }
8732             }
8733             while (i < mTexts.size() && i < maxRows) {
8734                 CharSequence str = mTexts.get(i);
8735                 if (!TextUtils.isEmpty(str)) {
8736                     contentView.setViewVisibility(rowIds[i], View.VISIBLE);
8737                     contentView.setTextViewText(rowIds[i],
8738                             mBuilder.processTextSpans(mBuilder.processLegacyText(str)));
8739                     mBuilder.setTextViewColorSecondary(contentView, rowIds[i], p);
8740                     contentView.setViewPadding(rowIds[i], 0, topPadding, 0, 0);
8741                     if (first) {
8742                         onlyViewId = rowIds[i];
8743                     } else {
8744                         onlyViewId = 0;
8745                     }
8746                     first = false;
8747                 }
8748                 i++;
8749             }
8750             if (onlyViewId != 0) {
8751                 // We only have 1 entry, lets make it look like the normal Text of a Bigtext
8752                 topPadding = mBuilder.mContext.getResources().getDimensionPixelSize(
8753                         R.dimen.notification_text_margin_top);
8754                 contentView.setViewPadding(onlyViewId, 0, topPadding, 0, 0);
8755             }
8756 
8757             return contentView;
8758         }
8759 
8760         /**
8761          * @hide
8762          */
8763         @Override
areNotificationsVisiblyDifferent(Style other)8764         public boolean areNotificationsVisiblyDifferent(Style other) {
8765             if (other == null || getClass() != other.getClass()) {
8766                 return true;
8767             }
8768             InboxStyle newS = (InboxStyle) other;
8769 
8770             final ArrayList<CharSequence> myLines = getLines();
8771             final ArrayList<CharSequence> newLines = newS.getLines();
8772             final int n = myLines.size();
8773             if (n != newLines.size()) {
8774                 return true;
8775             }
8776 
8777             for (int i = 0; i < n; i++) {
8778                 if (!Objects.equals(
8779                         String.valueOf(myLines.get(i)),
8780                         String.valueOf(newLines.get(i)))) {
8781                     return true;
8782                 }
8783             }
8784             return false;
8785         }
8786     }
8787 
8788     /**
8789      * Notification style for media playback notifications.
8790      *
8791      * In the expanded form, {@link Notification#bigContentView}, up to 5
8792      * {@link Notification.Action}s specified with
8793      * {@link Notification.Builder#addAction(Action) addAction} will be
8794      * shown as icon-only pushbuttons, suitable for transport controls. The Bitmap given to
8795      * {@link Notification.Builder#setLargeIcon(android.graphics.Bitmap) setLargeIcon()} will be
8796      * treated as album artwork.
8797      * <p>
8798      * Unlike the other styles provided here, MediaStyle can also modify the standard-size
8799      * {@link Notification#contentView}; by providing action indices to
8800      * {@link #setShowActionsInCompactView(int...)} you can promote up to 3 actions to be displayed
8801      * in the standard view alongside the usual content.
8802      * <p>
8803      * Notifications created with MediaStyle will have their category set to
8804      * {@link Notification#CATEGORY_TRANSPORT CATEGORY_TRANSPORT} unless you set a different
8805      * category using {@link Notification.Builder#setCategory(String) setCategory()}.
8806      * <p>
8807      * Finally, if you attach a {@link android.media.session.MediaSession.Token} using
8808      * {@link android.app.Notification.MediaStyle#setMediaSession(MediaSession.Token)},
8809      * the System UI can identify this as a notification representing an active media session
8810      * and respond accordingly (by showing album artwork in the lockscreen, for example).
8811      *
8812      * <p>
8813      * Starting at {@link android.os.Build.VERSION_CODES#O Android O} any notification that has a
8814      * media session attached with {@link #setMediaSession(MediaSession.Token)} will be colorized.
8815      * You can opt-out of this behavior by using {@link Notification.Builder#setColorized(boolean)}.
8816      * <p>
8817      *
8818      * To use this style with your Notification, feed it to
8819      * {@link Notification.Builder#setStyle(android.app.Notification.Style)} like so:
8820      * <pre class="prettyprint">
8821      * Notification noti = new Notification.Builder()
8822      *     .setSmallIcon(R.drawable.ic_stat_player)
8823      *     .setContentTitle(&quot;Track title&quot;)
8824      *     .setContentText(&quot;Artist - Album&quot;)
8825      *     .setLargeIcon(albumArtBitmap))
8826      *     .setStyle(<b>new Notification.MediaStyle()</b>
8827      *         .setMediaSession(mySession))
8828      *     .build();
8829      * </pre>
8830      *
8831      * @see Notification#bigContentView
8832      * @see Notification.Builder#setColorized(boolean)
8833      */
8834     public static class MediaStyle extends Style {
8835         // Changing max media buttons requires also changing templates
8836         // (notification_template_material_media and notification_template_material_big_media).
8837         static final int MAX_MEDIA_BUTTONS_IN_COMPACT = 3;
8838         static final int MAX_MEDIA_BUTTONS = 5;
8839         @IdRes private static final int[] MEDIA_BUTTON_IDS = {
8840                 R.id.action0,
8841                 R.id.action1,
8842                 R.id.action2,
8843                 R.id.action3,
8844                 R.id.action4,
8845         };
8846 
8847         private int[] mActionsToShowInCompact = null;
8848         private MediaSession.Token mToken;
8849 
MediaStyle()8850         public MediaStyle() {
8851         }
8852 
8853         /**
8854          * @deprecated use {@code MediaStyle()}.
8855          */
8856         @Deprecated
MediaStyle(Builder builder)8857         public MediaStyle(Builder builder) {
8858             setBuilder(builder);
8859         }
8860 
8861         /**
8862          * Request up to 3 actions (by index in the order of addition) to be shown in the compact
8863          * notification view.
8864          *
8865          * @param actions the indices of the actions to show in the compact notification view
8866          */
setShowActionsInCompactView(int...actions)8867         public MediaStyle setShowActionsInCompactView(int...actions) {
8868             mActionsToShowInCompact = actions;
8869             return this;
8870         }
8871 
8872         /**
8873          * Attach a {@link android.media.session.MediaSession.Token} to this Notification
8874          * to provide additional playback information and control to the SystemUI.
8875          */
setMediaSession(MediaSession.Token token)8876         public MediaStyle setMediaSession(MediaSession.Token token) {
8877             mToken = token;
8878             return this;
8879         }
8880 
8881         /**
8882          * @hide
8883          */
8884         @Override
8885         @UnsupportedAppUsage
buildStyled(Notification wip)8886         public Notification buildStyled(Notification wip) {
8887             super.buildStyled(wip);
8888             if (wip.category == null) {
8889                 wip.category = Notification.CATEGORY_TRANSPORT;
8890             }
8891             return wip;
8892         }
8893 
8894         /**
8895          * @hide
8896          */
8897         @Override
makeContentView(boolean increasedHeight)8898         public RemoteViews makeContentView(boolean increasedHeight) {
8899             return makeMediaContentView(null /* customContent */);
8900         }
8901 
8902         /**
8903          * @hide
8904          */
8905         @Override
makeBigContentView()8906         public RemoteViews makeBigContentView() {
8907             return makeMediaBigContentView(null /* customContent */);
8908         }
8909 
8910         /**
8911          * @hide
8912          */
8913         @Override
makeHeadsUpContentView(boolean increasedHeight)8914         public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
8915             return makeMediaContentView(null /* customContent */);
8916         }
8917 
8918         /** @hide */
8919         @Override
addExtras(Bundle extras)8920         public void addExtras(Bundle extras) {
8921             super.addExtras(extras);
8922 
8923             if (mToken != null) {
8924                 extras.putParcelable(EXTRA_MEDIA_SESSION, mToken);
8925             }
8926             if (mActionsToShowInCompact != null) {
8927                 extras.putIntArray(EXTRA_COMPACT_ACTIONS, mActionsToShowInCompact);
8928             }
8929         }
8930 
8931         /**
8932          * @hide
8933          */
8934         @Override
restoreFromExtras(Bundle extras)8935         protected void restoreFromExtras(Bundle extras) {
8936             super.restoreFromExtras(extras);
8937 
8938             if (extras.containsKey(EXTRA_MEDIA_SESSION)) {
8939                 mToken = extras.getParcelable(EXTRA_MEDIA_SESSION);
8940             }
8941             if (extras.containsKey(EXTRA_COMPACT_ACTIONS)) {
8942                 mActionsToShowInCompact = extras.getIntArray(EXTRA_COMPACT_ACTIONS);
8943             }
8944         }
8945 
8946         /**
8947          * @hide
8948          */
8949         @Override
areNotificationsVisiblyDifferent(Style other)8950         public boolean areNotificationsVisiblyDifferent(Style other) {
8951             if (other == null || getClass() != other.getClass()) {
8952                 return true;
8953             }
8954             // All fields to compare are on the Notification object
8955             return false;
8956         }
8957 
bindMediaActionButton(RemoteViews container, @IdRes int buttonId, Action action, StandardTemplateParams p)8958         private void bindMediaActionButton(RemoteViews container, @IdRes int buttonId,
8959                 Action action, StandardTemplateParams p) {
8960             final boolean tombstone = (action.actionIntent == null);
8961             container.setViewVisibility(buttonId, View.VISIBLE);
8962             container.setImageViewIcon(buttonId, action.getIcon());
8963 
8964             // If the action buttons should not be tinted, then just use the default
8965             // notification color. Otherwise, just use the passed-in color.
8966             int tintColor = mBuilder.getStandardActionColor(p);
8967 
8968             container.setDrawableTint(buttonId, false, tintColor,
8969                     PorterDuff.Mode.SRC_ATOP);
8970 
8971             int rippleAlpha = mBuilder.getColors(p).getRippleAlpha();
8972             int rippleColor = Color.argb(rippleAlpha, Color.red(tintColor), Color.green(tintColor),
8973                     Color.blue(tintColor));
8974             container.setRippleDrawableColor(buttonId, ColorStateList.valueOf(rippleColor));
8975 
8976             if (!tombstone) {
8977                 container.setOnClickPendingIntent(buttonId, action.actionIntent);
8978             }
8979             container.setContentDescription(buttonId, action.title);
8980         }
8981 
8982         /** @hide */
makeMediaContentView(@ullable RemoteViews customContent)8983         protected RemoteViews makeMediaContentView(@Nullable RemoteViews customContent) {
8984             final int numActions = mBuilder.mActions.size();
8985             final int numActionsToShow = Math.min(mActionsToShowInCompact == null
8986                     ? 0 : mActionsToShowInCompact.length, MAX_MEDIA_BUTTONS_IN_COMPACT);
8987             if (numActionsToShow > numActions) {
8988                 throw new IllegalArgumentException(String.format(
8989                         "setShowActionsInCompactView: action %d out of bounds (max %d)",
8990                         numActions, numActions - 1));
8991             }
8992 
8993             StandardTemplateParams p = mBuilder.mParams.reset()
8994                     .viewType(StandardTemplateParams.VIEW_TYPE_NORMAL)
8995                     .hideTime(numActionsToShow > 1)       // hide if actions wider than a right icon
8996                     .hideSubText(numActionsToShow > 1)    // hide if actions wider than a right icon
8997                     .hideLeftIcon(false)                  // allow large icon on left when grouped
8998                     .hideRightIcon(numActionsToShow > 0)  // right icon or actions; not both
8999                     .hideProgress(true)
9000                     .fillTextsFrom(mBuilder);
9001             TemplateBindResult result = new TemplateBindResult();
9002             RemoteViews template = mBuilder.applyStandardTemplate(
9003                     R.layout.notification_template_material_media, p,
9004                     null /* result */);
9005 
9006             for (int i = 0; i < MAX_MEDIA_BUTTONS_IN_COMPACT; i++) {
9007                 if (i < numActionsToShow) {
9008                     final Action action = mBuilder.mActions.get(mActionsToShowInCompact[i]);
9009                     bindMediaActionButton(template, MEDIA_BUTTON_IDS[i], action, p);
9010                 } else {
9011                     template.setViewVisibility(MEDIA_BUTTON_IDS[i], View.GONE);
9012                 }
9013             }
9014             // Prevent a swooping expand animation when there are no actions
9015             boolean hasActions = numActionsToShow != 0;
9016             template.setViewVisibility(R.id.media_actions, hasActions ? View.VISIBLE : View.GONE);
9017 
9018             // Add custom view if provided by subclass.
9019             buildCustomContentIntoTemplate(mBuilder.mContext, template, customContent, p, result);
9020             return template;
9021         }
9022 
9023         /** @hide */
makeMediaBigContentView(@ullable RemoteViews customContent)9024         protected RemoteViews makeMediaBigContentView(@Nullable RemoteViews customContent) {
9025             final int actionCount = Math.min(mBuilder.mActions.size(), MAX_MEDIA_BUTTONS);
9026             StandardTemplateParams p = mBuilder.mParams.reset()
9027                     .viewType(StandardTemplateParams.VIEW_TYPE_BIG)
9028                     .hideProgress(true)
9029                     .fillTextsFrom(mBuilder);
9030             TemplateBindResult result = new TemplateBindResult();
9031             RemoteViews template = mBuilder.applyStandardTemplate(
9032                     R.layout.notification_template_material_big_media, p , result);
9033 
9034             for (int i = 0; i < MAX_MEDIA_BUTTONS; i++) {
9035                 if (i < actionCount) {
9036                     bindMediaActionButton(template,
9037                             MEDIA_BUTTON_IDS[i], mBuilder.mActions.get(i), p);
9038                 } else {
9039                     template.setViewVisibility(MEDIA_BUTTON_IDS[i], View.GONE);
9040                 }
9041             }
9042             buildCustomContentIntoTemplate(mBuilder.mContext, template, customContent, p, result);
9043             return template;
9044         }
9045     }
9046 
9047     /**
9048      * Helper class for generating large-format notifications that include a large image attachment.
9049      *
9050      * Here's how you'd set the <code>CallStyle</code> on a notification:
9051      * <pre class="prettyprint">
9052      * Notification notif = new Notification.Builder(mContext)
9053      *     .setSmallIcon(R.drawable.new_post)
9054      *     .setStyle(Notification.CallStyle.forIncomingCall(caller, declineIntent, answerIntent))
9055      *     .build();
9056      * </pre>
9057      */
9058     public static class CallStyle extends Style {
9059         /** @hide */
9060         public static final int CALL_TYPE_INCOMING = 1;
9061         /** @hide */
9062         public static final int CALL_TYPE_ONGOING = 2;
9063         /** @hide */
9064         public static final int CALL_TYPE_SCREENING = 3;
9065 
9066         /**
9067          * This is a key used privately on the action.extras to give spacing priority
9068          * to the required call actions
9069          */
9070         private static final String KEY_ACTION_PRIORITY = "key_action_priority";
9071 
9072         private int mCallType;
9073         private Person mPerson;
9074         private PendingIntent mAnswerIntent;
9075         private PendingIntent mDeclineIntent;
9076         private PendingIntent mHangUpIntent;
9077         private boolean mIsVideo;
9078         private Integer mAnswerButtonColor;
9079         private Integer mDeclineButtonColor;
9080         private Icon mVerificationIcon;
9081         private CharSequence mVerificationText;
9082 
CallStyle()9083         CallStyle() {
9084         }
9085 
9086         /**
9087          * Create a CallStyle for an incoming call.
9088          * This notification will have a decline and an answer action, will allow a single
9089          * custom {@link Builder#addAction(Action) action}, and will have a default
9090          * {@link Builder#setContentText(CharSequence) content text} for an incoming call.
9091          *
9092          * @param person        The person displayed as the caller.
9093          *                      The person also needs to have a non-empty name associated with it.
9094          * @param declineIntent The intent to be sent when the user taps the decline action
9095          * @param answerIntent  The intent to be sent when the user taps the answer action
9096          */
9097         @NonNull
forIncomingCall(@onNull Person person, @NonNull PendingIntent declineIntent, @NonNull PendingIntent answerIntent)9098         public static CallStyle forIncomingCall(@NonNull Person person,
9099                 @NonNull PendingIntent declineIntent, @NonNull PendingIntent answerIntent) {
9100             return new CallStyle(CALL_TYPE_INCOMING, person,
9101                     null /* hangUpIntent */,
9102                     requireNonNull(declineIntent, "declineIntent is required"),
9103                     requireNonNull(answerIntent, "answerIntent is required")
9104             );
9105         }
9106 
9107         /**
9108          * Create a CallStyle for an ongoing call.
9109          * This notification will have a hang up action, will allow up to two
9110          * custom {@link Builder#addAction(Action) actions}, and will have a default
9111          * {@link Builder#setContentText(CharSequence) content text} for an ongoing call.
9112          *
9113          * @param person       The person displayed as being on the other end of the call.
9114          *                     The person also needs to have a non-empty name associated with it.
9115          * @param hangUpIntent The intent to be sent when the user taps the hang up action
9116          */
9117         @NonNull
forOngoingCall(@onNull Person person, @NonNull PendingIntent hangUpIntent)9118         public static CallStyle forOngoingCall(@NonNull Person person,
9119                 @NonNull PendingIntent hangUpIntent) {
9120             return new CallStyle(CALL_TYPE_ONGOING, person,
9121                     requireNonNull(hangUpIntent, "hangUpIntent is required"),
9122                     null /* declineIntent */,
9123                     null /* answerIntent */
9124             );
9125         }
9126 
9127         /**
9128          * Create a CallStyle for a call that is being screened.
9129          * This notification will have a hang up and an answer action, will allow a single
9130          * custom {@link Builder#addAction(Action) action}, and will have a default
9131          * {@link Builder#setContentText(CharSequence) content text} for a call that is being
9132          * screened.
9133          *
9134          * @param person       The person displayed as the caller.
9135          *                     The person also needs to have a non-empty name associated with it.
9136          * @param hangUpIntent The intent to be sent when the user taps the hang up action
9137          * @param answerIntent The intent to be sent when the user taps the answer action
9138          */
9139         @NonNull
forScreeningCall(@onNull Person person, @NonNull PendingIntent hangUpIntent, @NonNull PendingIntent answerIntent)9140         public static CallStyle forScreeningCall(@NonNull Person person,
9141                 @NonNull PendingIntent hangUpIntent, @NonNull PendingIntent answerIntent) {
9142             return new CallStyle(CALL_TYPE_SCREENING, person,
9143                     requireNonNull(hangUpIntent, "hangUpIntent is required"),
9144                     null /* declineIntent */,
9145                     requireNonNull(answerIntent, "answerIntent is required")
9146             );
9147         }
9148 
9149         /**
9150          * @param person The person displayed for the incoming call.
9151          *             The user also needs to have a non-empty name associated with it.
9152          * @param hangUpIntent The intent to be sent when the user taps the hang up action
9153          * @param declineIntent The intent to be sent when the user taps the decline action
9154          * @param answerIntent The intent to be sent when the user taps the answer action
9155          */
CallStyle(int callType, @NonNull Person person, @Nullable PendingIntent hangUpIntent, @Nullable PendingIntent declineIntent, @Nullable PendingIntent answerIntent)9156         private CallStyle(int callType, @NonNull Person person,
9157                 @Nullable PendingIntent hangUpIntent, @Nullable PendingIntent declineIntent,
9158                 @Nullable PendingIntent answerIntent) {
9159             if (person == null || TextUtils.isEmpty(person.getName())) {
9160                 throw new IllegalArgumentException("person must have a non-empty a name");
9161             }
9162             mCallType = callType;
9163             mPerson = person;
9164             mAnswerIntent = answerIntent;
9165             mDeclineIntent = declineIntent;
9166             mHangUpIntent = hangUpIntent;
9167         }
9168 
9169         /**
9170          * Sets whether the call is a video call, which may affect the icons or text used on the
9171          * required action buttons.
9172          */
9173         @NonNull
setIsVideo(boolean isVideo)9174         public CallStyle setIsVideo(boolean isVideo) {
9175             mIsVideo = isVideo;
9176             return this;
9177         }
9178 
9179         /**
9180          * Optional icon to be displayed with {@link #setVerificationText(CharSequence) text}
9181          * as a verification status of the caller.
9182          */
9183         @NonNull
setVerificationIcon(@ullable Icon verificationIcon)9184         public CallStyle setVerificationIcon(@Nullable Icon verificationIcon) {
9185             mVerificationIcon = verificationIcon;
9186             return this;
9187         }
9188 
9189         /**
9190          * Optional text to be displayed with an {@link #setVerificationIcon(Icon) icon}
9191          * as a verification status of the caller.
9192          */
9193         @NonNull
setVerificationText(@ullable CharSequence verificationText)9194         public CallStyle setVerificationText(@Nullable CharSequence verificationText) {
9195             mVerificationText = safeCharSequence(verificationText);
9196             return this;
9197         }
9198 
9199         /**
9200          * Optional color to be used as a hint for the Answer action button's color.
9201          * The system may change this color to ensure sufficient contrast with the background.
9202          * The system may choose to disregard this hint if the notification is not colorized.
9203          */
9204         @NonNull
setAnswerButtonColorHint(@olorInt int color)9205         public CallStyle setAnswerButtonColorHint(@ColorInt int color) {
9206             mAnswerButtonColor = color;
9207             return this;
9208         }
9209 
9210         /**
9211          * Optional color to be used as a hint for the Decline or Hang Up action button's color.
9212          * The system may change this color to ensure sufficient contrast with the background.
9213          * The system may choose to disregard this hint if the notification is not colorized.
9214          */
9215         @NonNull
setDeclineButtonColorHint(@olorInt int color)9216         public CallStyle setDeclineButtonColorHint(@ColorInt int color) {
9217             mDeclineButtonColor = color;
9218             return this;
9219         }
9220 
9221         /** @hide */
9222         @Override
buildStyled(Notification wip)9223         public Notification buildStyled(Notification wip) {
9224             wip = super.buildStyled(wip);
9225             // ensure that the actions in the builder and notification are corrected.
9226             mBuilder.mActions = getActionsListWithSystemActions();
9227             wip.actions = new Action[mBuilder.mActions.size()];
9228             mBuilder.mActions.toArray(wip.actions);
9229             return wip;
9230         }
9231 
9232         /**
9233          * @hide
9234          */
displayCustomViewInline()9235         public boolean displayCustomViewInline() {
9236             // This is a lie; True is returned to make sure that the custom view is not used
9237             // instead of the template, but it will not actually be included.
9238             return true;
9239         }
9240 
9241         /**
9242          * @hide
9243          */
9244         @Override
purgeResources()9245         public void purgeResources() {
9246             super.purgeResources();
9247             if (mVerificationIcon != null) {
9248                 mVerificationIcon.convertToAshmem();
9249             }
9250         }
9251 
9252         /**
9253          * @hide
9254          */
9255         @Override
reduceImageSizes(Context context)9256         public void reduceImageSizes(Context context) {
9257             super.reduceImageSizes(context);
9258             if (mVerificationIcon != null) {
9259                 int rightIconSize = context.getResources().getDimensionPixelSize(
9260                         ActivityManager.isLowRamDeviceStatic()
9261                                 ? R.dimen.notification_right_icon_size_low_ram
9262                                 : R.dimen.notification_right_icon_size);
9263                 mVerificationIcon.scaleDownIfNecessary(rightIconSize, rightIconSize);
9264             }
9265         }
9266 
9267         /**
9268          * @hide
9269          */
9270         @Override
makeContentView(boolean increasedHeight)9271         public RemoteViews makeContentView(boolean increasedHeight) {
9272             return makeCallLayout(StandardTemplateParams.VIEW_TYPE_NORMAL);
9273         }
9274 
9275         /**
9276          * @hide
9277          */
9278         @Override
makeHeadsUpContentView(boolean increasedHeight)9279         public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
9280             return makeCallLayout(StandardTemplateParams.VIEW_TYPE_HEADS_UP);
9281         }
9282 
9283         /**
9284          * @hide
9285          */
makeBigContentView()9286         public RemoteViews makeBigContentView() {
9287             return makeCallLayout(StandardTemplateParams.VIEW_TYPE_BIG);
9288         }
9289 
9290         @NonNull
makeNegativeAction()9291         private Action makeNegativeAction() {
9292             if (mDeclineIntent == null) {
9293                 return makeAction(R.drawable.ic_call_decline,
9294                         R.string.call_notification_hang_up_action,
9295                         mDeclineButtonColor, R.color.call_notification_decline_color,
9296                         mHangUpIntent);
9297             } else {
9298                 return makeAction(R.drawable.ic_call_decline,
9299                         R.string.call_notification_decline_action,
9300                         mDeclineButtonColor, R.color.call_notification_decline_color,
9301                         mDeclineIntent);
9302             }
9303         }
9304 
9305         @Nullable
makeAnswerAction()9306         private Action makeAnswerAction() {
9307             return mAnswerIntent == null ? null : makeAction(
9308                     mIsVideo ? R.drawable.ic_call_answer_video : R.drawable.ic_call_answer,
9309                     mIsVideo ? R.string.call_notification_answer_video_action
9310                             : R.string.call_notification_answer_action,
9311                     mAnswerButtonColor, R.color.call_notification_answer_color,
9312                     mAnswerIntent);
9313         }
9314 
9315         @NonNull
makeAction(@rawableRes int icon, @StringRes int title, @ColorInt Integer colorInt, @ColorRes int defaultColorRes, PendingIntent intent)9316         private Action makeAction(@DrawableRes int icon, @StringRes int title,
9317                 @ColorInt Integer colorInt, @ColorRes int defaultColorRes, PendingIntent intent) {
9318             if (colorInt == null || !mBuilder.isCallActionColorCustomizable()) {
9319                 colorInt = mBuilder.mContext.getColor(defaultColorRes);
9320             }
9321             Action action = new Action.Builder(Icon.createWithResource("", icon),
9322                     new SpannableStringBuilder().append(mBuilder.mContext.getString(title),
9323                             new ForegroundColorSpan(colorInt),
9324                             SpannableStringBuilder.SPAN_INCLUSIVE_INCLUSIVE),
9325                     intent).build();
9326             action.getExtras().putBoolean(KEY_ACTION_PRIORITY, true);
9327             return action;
9328         }
9329 
isActionAddedByCallStyle(Action action)9330         private boolean isActionAddedByCallStyle(Action action) {
9331             // This is an internal extra added by the style to these actions. If an app were to add
9332             // this extra to the action themselves, the action would be dropped.  :shrug:
9333             return action != null && action.getExtras().getBoolean(KEY_ACTION_PRIORITY);
9334         }
9335 
9336         /**
9337          * Gets the actions list for the call with the answer/decline/hangUp actions inserted in
9338          * the correct place.  This returns the correct result even if the system actions have
9339          * already been added, and even if more actions were added since then.
9340          * @hide
9341          */
9342         @NonNull
getActionsListWithSystemActions()9343         public ArrayList<Action> getActionsListWithSystemActions() {
9344             // Define the system actions we expect to see
9345             final Action negativeAction = makeNegativeAction();
9346             final Action answerAction = makeAnswerAction();
9347             // Sort the expected actions into the correct order:
9348             // * If there's no answer action, put the hang up / decline action at the end
9349             // * Otherwise put the answer action at the end, and put the decline action at start.
9350             final Action firstAction = answerAction == null ? null : negativeAction;
9351             final Action lastAction = answerAction == null ? negativeAction : answerAction;
9352 
9353             // Start creating the result list.
9354             int nonContextualActionSlotsRemaining = MAX_ACTION_BUTTONS;
9355             ArrayList<Action> resultActions = new ArrayList<>(MAX_ACTION_BUTTONS);
9356             if (firstAction != null) {
9357                 resultActions.add(firstAction);
9358                 --nonContextualActionSlotsRemaining;
9359             }
9360 
9361             // Copy actions into the new list, correcting system actions.
9362             if (mBuilder.mActions != null) {
9363                 for (Notification.Action action : mBuilder.mActions) {
9364                     if (action.isContextual()) {
9365                         // Always include all contextual actions
9366                         resultActions.add(action);
9367                     } else if (isActionAddedByCallStyle(action)) {
9368                         // Drop any old versions of system actions
9369                     } else {
9370                         // Copy non-contextual actions; decrement the remaining action slots.
9371                         resultActions.add(action);
9372                         --nonContextualActionSlotsRemaining;
9373                     }
9374                     // If there's exactly one action slot left, fill it with the lastAction.
9375                     if (nonContextualActionSlotsRemaining == 1) {
9376                         resultActions.add(lastAction);
9377                         --nonContextualActionSlotsRemaining;
9378                     }
9379                 }
9380             }
9381             // If there are any action slots left, the lastAction still needs to be added.
9382             if (nonContextualActionSlotsRemaining >= 1) {
9383                 resultActions.add(lastAction);
9384             }
9385             return resultActions;
9386         }
9387 
makeCallLayout(int viewType)9388         private RemoteViews makeCallLayout(int viewType) {
9389             final boolean isCollapsed = viewType == StandardTemplateParams.VIEW_TYPE_NORMAL;
9390             Bundle extras = mBuilder.mN.extras;
9391             CharSequence title = mPerson != null ? mPerson.getName() : null;
9392             CharSequence text = mBuilder.processLegacyText(extras.getCharSequence(EXTRA_TEXT));
9393             if (text == null) {
9394                 text = getDefaultText();
9395             }
9396 
9397             // Bind standard template
9398             StandardTemplateParams p = mBuilder.mParams.reset()
9399                     .viewType(viewType)
9400                     .callStyleActions(true)
9401                     .allowTextWithProgress(true)
9402                     .hideLeftIcon(true)
9403                     .hideRightIcon(true)
9404                     .hideAppName(isCollapsed)
9405                     .titleViewId(R.id.conversation_text)
9406                     .title(title)
9407                     .text(text)
9408                     .summaryText(mBuilder.processLegacyText(mVerificationText));
9409             mBuilder.mActions = getActionsListWithSystemActions();
9410             final RemoteViews contentView;
9411             if (isCollapsed) {
9412                 contentView = mBuilder.applyStandardTemplate(
9413                         R.layout.notification_template_material_call, p, null /* result */);
9414             } else {
9415                 contentView = mBuilder.applyStandardTemplateWithActions(
9416                         R.layout.notification_template_material_big_call, p, null /* result */);
9417             }
9418 
9419             // Bind some extra conversation-specific header fields.
9420             if (!p.mHideAppName) {
9421                 mBuilder.setTextViewColorSecondary(contentView, R.id.app_name_divider, p);
9422                 contentView.setViewVisibility(R.id.app_name_divider, View.VISIBLE);
9423             }
9424             bindCallerVerification(contentView, p);
9425 
9426             // Bind some custom CallLayout properties
9427             contentView.setInt(R.id.status_bar_latest_event_content, "setLayoutColor",
9428                     mBuilder.getSmallIconColor(p));
9429             contentView.setInt(R.id.status_bar_latest_event_content,
9430                     "setNotificationBackgroundColor", mBuilder.getBackgroundColor(p));
9431             contentView.setIcon(R.id.status_bar_latest_event_content, "setLargeIcon",
9432                     mBuilder.mN.mLargeIcon);
9433             contentView.setBundle(R.id.status_bar_latest_event_content, "setData",
9434                     mBuilder.mN.extras);
9435 
9436             return contentView;
9437         }
9438 
bindCallerVerification(RemoteViews contentView, StandardTemplateParams p)9439         private void bindCallerVerification(RemoteViews contentView, StandardTemplateParams p) {
9440             String iconContentDescription = null;
9441             boolean showDivider = true;
9442             if (mVerificationIcon != null) {
9443                 contentView.setImageViewIcon(R.id.verification_icon, mVerificationIcon);
9444                 contentView.setDrawableTint(R.id.verification_icon, false /* targetBackground */,
9445                         mBuilder.getSecondaryTextColor(p), PorterDuff.Mode.SRC_ATOP);
9446                 contentView.setViewVisibility(R.id.verification_icon, View.VISIBLE);
9447                 iconContentDescription = mBuilder.mContext.getString(
9448                         R.string.notification_verified_content_description);
9449                 showDivider = false;  // the icon replaces the divider
9450             } else {
9451                 contentView.setViewVisibility(R.id.verification_icon, View.GONE);
9452             }
9453             if (!TextUtils.isEmpty(mVerificationText)) {
9454                 contentView.setTextViewText(R.id.verification_text, mVerificationText);
9455                 mBuilder.setTextViewColorSecondary(contentView, R.id.verification_text, p);
9456                 contentView.setViewVisibility(R.id.verification_text, View.VISIBLE);
9457                 iconContentDescription = null;  // let the app's text take precedence
9458             } else {
9459                 contentView.setViewVisibility(R.id.verification_text, View.GONE);
9460                 showDivider = false;  // no divider if no text
9461             }
9462             contentView.setContentDescription(R.id.verification_icon, iconContentDescription);
9463             if (showDivider) {
9464                 contentView.setViewVisibility(R.id.verification_divider, View.VISIBLE);
9465                 mBuilder.setTextViewColorSecondary(contentView, R.id.verification_divider, p);
9466             } else {
9467                 contentView.setViewVisibility(R.id.verification_divider, View.GONE);
9468             }
9469         }
9470 
9471         @Nullable
getDefaultText()9472         private String getDefaultText() {
9473             switch (mCallType) {
9474                 case CALL_TYPE_INCOMING:
9475                     return mBuilder.mContext.getString(R.string.call_notification_incoming_text);
9476                 case CALL_TYPE_ONGOING:
9477                     return mBuilder.mContext.getString(R.string.call_notification_ongoing_text);
9478                 case CALL_TYPE_SCREENING:
9479                     return mBuilder.mContext.getString(R.string.call_notification_screening_text);
9480             }
9481             return null;
9482         }
9483 
9484         /**
9485          * @hide
9486          */
addExtras(Bundle extras)9487         public void addExtras(Bundle extras) {
9488             super.addExtras(extras);
9489             extras.putInt(EXTRA_CALL_TYPE, mCallType);
9490             extras.putBoolean(EXTRA_CALL_IS_VIDEO, mIsVideo);
9491             extras.putParcelable(EXTRA_CALL_PERSON, mPerson);
9492             if (mVerificationIcon != null) {
9493                 extras.putParcelable(EXTRA_VERIFICATION_ICON, mVerificationIcon);
9494             }
9495             if (mVerificationText != null) {
9496                 extras.putCharSequence(EXTRA_VERIFICATION_TEXT, mVerificationText);
9497             }
9498             if (mAnswerIntent != null) {
9499                 extras.putParcelable(EXTRA_ANSWER_INTENT, mAnswerIntent);
9500             }
9501             if (mDeclineIntent != null) {
9502                 extras.putParcelable(EXTRA_DECLINE_INTENT, mDeclineIntent);
9503             }
9504             if (mHangUpIntent != null) {
9505                 extras.putParcelable(EXTRA_HANG_UP_INTENT, mHangUpIntent);
9506             }
9507             if (mAnswerButtonColor != null) {
9508                 extras.putInt(EXTRA_ANSWER_COLOR, mAnswerButtonColor);
9509             }
9510             if (mDeclineButtonColor != null) {
9511                 extras.putInt(EXTRA_DECLINE_COLOR, mDeclineButtonColor);
9512             }
9513             fixTitleAndTextExtras(extras);
9514         }
9515 
fixTitleAndTextExtras(Bundle extras)9516         private void fixTitleAndTextExtras(Bundle extras) {
9517             CharSequence sender = mPerson != null ? mPerson.getName() : null;
9518             if (sender != null) {
9519                 extras.putCharSequence(EXTRA_TITLE, sender);
9520             }
9521             if (extras.getCharSequence(EXTRA_TEXT) == null) {
9522                 extras.putCharSequence(EXTRA_TEXT, getDefaultText());
9523             }
9524         }
9525 
9526         /**
9527          * @hide
9528          */
9529         @Override
restoreFromExtras(Bundle extras)9530         protected void restoreFromExtras(Bundle extras) {
9531             super.restoreFromExtras(extras);
9532             mCallType = extras.getInt(EXTRA_CALL_TYPE);
9533             mIsVideo = extras.getBoolean(EXTRA_CALL_IS_VIDEO);
9534             mPerson = extras.getParcelable(EXTRA_CALL_PERSON);
9535             mVerificationIcon = extras.getParcelable(EXTRA_VERIFICATION_ICON);
9536             mVerificationText = extras.getCharSequence(EXTRA_VERIFICATION_TEXT);
9537             mAnswerIntent = extras.getParcelable(EXTRA_ANSWER_INTENT);
9538             mDeclineIntent = extras.getParcelable(EXTRA_DECLINE_INTENT);
9539             mHangUpIntent = extras.getParcelable(EXTRA_HANG_UP_INTENT);
9540             mAnswerButtonColor = extras.containsKey(EXTRA_ANSWER_COLOR)
9541                     ? extras.getInt(EXTRA_ANSWER_COLOR) : null;
9542             mDeclineButtonColor = extras.containsKey(EXTRA_DECLINE_COLOR)
9543                     ? extras.getInt(EXTRA_DECLINE_COLOR) : null;
9544         }
9545 
9546         /**
9547          * @hide
9548          */
9549         @Override
hasSummaryInHeader()9550         public boolean hasSummaryInHeader() {
9551             return false;
9552         }
9553 
9554         /**
9555          * @hide
9556          */
9557         @Override
areNotificationsVisiblyDifferent(Style other)9558         public boolean areNotificationsVisiblyDifferent(Style other) {
9559             if (other == null || getClass() != other.getClass()) {
9560                 return true;
9561             }
9562             CallStyle otherS = (CallStyle) other;
9563             return !Objects.equals(mCallType, otherS.mCallType)
9564                     || !Objects.equals(mPerson, otherS.mPerson)
9565                     || !Objects.equals(mVerificationText, otherS.mVerificationText);
9566         }
9567     }
9568 
9569     /**
9570      * Notification style for custom views that are decorated by the system
9571      *
9572      * <p>Instead of providing a notification that is completely custom, a developer can set this
9573      * style and still obtain system decorations like the notification header with the expand
9574      * affordance and actions.
9575      *
9576      * <p>Use {@link android.app.Notification.Builder#setCustomContentView(RemoteViews)},
9577      * {@link android.app.Notification.Builder#setCustomBigContentView(RemoteViews)} and
9578      * {@link android.app.Notification.Builder#setCustomHeadsUpContentView(RemoteViews)} to set the
9579      * corresponding custom views to display.
9580      *
9581      * To use this style with your Notification, feed it to
9582      * {@link Notification.Builder#setStyle(android.app.Notification.Style)} like so:
9583      * <pre class="prettyprint">
9584      * Notification noti = new Notification.Builder()
9585      *     .setSmallIcon(R.drawable.ic_stat_player)
9586      *     .setLargeIcon(albumArtBitmap))
9587      *     .setCustomContentView(contentView);
9588      *     .setStyle(<b>new Notification.DecoratedCustomViewStyle()</b>)
9589      *     .build();
9590      * </pre>
9591      */
9592     public static class DecoratedCustomViewStyle extends Style {
9593 
DecoratedCustomViewStyle()9594         public DecoratedCustomViewStyle() {
9595         }
9596 
9597         /**
9598          * @hide
9599          */
displayCustomViewInline()9600         public boolean displayCustomViewInline() {
9601             return true;
9602         }
9603 
9604         /**
9605          * @hide
9606          */
9607         @Override
makeContentView(boolean increasedHeight)9608         public RemoteViews makeContentView(boolean increasedHeight) {
9609             return makeStandardTemplateWithCustomContent(mBuilder.mN.contentView);
9610         }
9611 
9612         /**
9613          * @hide
9614          */
9615         @Override
makeBigContentView()9616         public RemoteViews makeBigContentView() {
9617             return makeDecoratedBigContentView();
9618         }
9619 
9620         /**
9621          * @hide
9622          */
9623         @Override
makeHeadsUpContentView(boolean increasedHeight)9624         public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
9625             return makeDecoratedHeadsUpContentView();
9626         }
9627 
makeDecoratedHeadsUpContentView()9628         private RemoteViews makeDecoratedHeadsUpContentView() {
9629             RemoteViews headsUpContentView = mBuilder.mN.headsUpContentView == null
9630                     ? mBuilder.mN.contentView
9631                     : mBuilder.mN.headsUpContentView;
9632             if (headsUpContentView == null) {
9633                 return null;  // no custom view; use the default behavior
9634             }
9635             if (mBuilder.mActions.size() == 0) {
9636                return makeStandardTemplateWithCustomContent(headsUpContentView);
9637             }
9638             TemplateBindResult result = new TemplateBindResult();
9639             StandardTemplateParams p = mBuilder.mParams.reset()
9640                     .viewType(StandardTemplateParams.VIEW_TYPE_HEADS_UP)
9641                     .decorationType(StandardTemplateParams.DECORATION_PARTIAL)
9642                     .fillTextsFrom(mBuilder);
9643             RemoteViews remoteViews = mBuilder.applyStandardTemplateWithActions(
9644                     mBuilder.getHeadsUpBaseLayoutResource(), p, result);
9645             buildCustomContentIntoTemplate(mBuilder.mContext, remoteViews, headsUpContentView,
9646                     p, result);
9647             return remoteViews;
9648         }
9649 
makeStandardTemplateWithCustomContent(RemoteViews customContent)9650         private RemoteViews makeStandardTemplateWithCustomContent(RemoteViews customContent) {
9651             if (customContent == null) {
9652                 return null;  // no custom view; use the default behavior
9653             }
9654             TemplateBindResult result = new TemplateBindResult();
9655             StandardTemplateParams p = mBuilder.mParams.reset()
9656                     .viewType(StandardTemplateParams.VIEW_TYPE_NORMAL)
9657                     .decorationType(StandardTemplateParams.DECORATION_PARTIAL)
9658                     .fillTextsFrom(mBuilder);
9659             RemoteViews remoteViews = mBuilder.applyStandardTemplate(
9660                     mBuilder.getBaseLayoutResource(), p, result);
9661             buildCustomContentIntoTemplate(mBuilder.mContext, remoteViews, customContent,
9662                     p, result);
9663             return remoteViews;
9664         }
9665 
makeDecoratedBigContentView()9666         private RemoteViews makeDecoratedBigContentView() {
9667             RemoteViews bigContentView = mBuilder.mN.bigContentView == null
9668                     ? mBuilder.mN.contentView
9669                     : mBuilder.mN.bigContentView;
9670             if (bigContentView == null) {
9671                 return null;  // no custom view; use the default behavior
9672             }
9673             TemplateBindResult result = new TemplateBindResult();
9674             StandardTemplateParams p = mBuilder.mParams.reset()
9675                     .viewType(StandardTemplateParams.VIEW_TYPE_BIG)
9676                     .decorationType(StandardTemplateParams.DECORATION_PARTIAL)
9677                     .fillTextsFrom(mBuilder);
9678             RemoteViews remoteViews = mBuilder.applyStandardTemplateWithActions(
9679                     mBuilder.getBigBaseLayoutResource(), p, result);
9680             buildCustomContentIntoTemplate(mBuilder.mContext, remoteViews, bigContentView,
9681                     p, result);
9682             return remoteViews;
9683         }
9684 
9685         /**
9686          * @hide
9687          */
9688         @Override
areNotificationsVisiblyDifferent(Style other)9689         public boolean areNotificationsVisiblyDifferent(Style other) {
9690             if (other == null || getClass() != other.getClass()) {
9691                 return true;
9692             }
9693             // Comparison done for all custom RemoteViews, independent of style
9694             return false;
9695         }
9696     }
9697 
9698     /**
9699      * Notification style for media custom views that are decorated by the system
9700      *
9701      * <p>Instead of providing a media notification that is completely custom, a developer can set
9702      * this style and still obtain system decorations like the notification header with the expand
9703      * affordance and actions.
9704      *
9705      * <p>Use {@link android.app.Notification.Builder#setCustomContentView(RemoteViews)},
9706      * {@link android.app.Notification.Builder#setCustomBigContentView(RemoteViews)} and
9707      * {@link android.app.Notification.Builder#setCustomHeadsUpContentView(RemoteViews)} to set the
9708      * corresponding custom views to display.
9709      * <p>
9710      * Contrary to {@link MediaStyle} a developer has to opt-in to the colorizing of the
9711      * notification by using {@link Notification.Builder#setColorized(boolean)}.
9712      * <p>
9713      * To use this style with your Notification, feed it to
9714      * {@link Notification.Builder#setStyle(android.app.Notification.Style)} like so:
9715      * <pre class="prettyprint">
9716      * Notification noti = new Notification.Builder()
9717      *     .setSmallIcon(R.drawable.ic_stat_player)
9718      *     .setLargeIcon(albumArtBitmap))
9719      *     .setCustomContentView(contentView);
9720      *     .setStyle(<b>new Notification.DecoratedMediaCustomViewStyle()</b>
9721      *          .setMediaSession(mySession))
9722      *     .build();
9723      * </pre>
9724      *
9725      * @see android.app.Notification.DecoratedCustomViewStyle
9726      * @see android.app.Notification.MediaStyle
9727      */
9728     public static class DecoratedMediaCustomViewStyle extends MediaStyle {
9729 
DecoratedMediaCustomViewStyle()9730         public DecoratedMediaCustomViewStyle() {
9731         }
9732 
9733         /**
9734          * @hide
9735          */
displayCustomViewInline()9736         public boolean displayCustomViewInline() {
9737             return true;
9738         }
9739 
9740         /**
9741          * @hide
9742          */
9743         @Override
makeContentView(boolean increasedHeight)9744         public RemoteViews makeContentView(boolean increasedHeight) {
9745             return makeMediaContentView(mBuilder.mN.contentView);
9746         }
9747 
9748         /**
9749          * @hide
9750          */
9751         @Override
makeBigContentView()9752         public RemoteViews makeBigContentView() {
9753             RemoteViews customContent = mBuilder.mN.bigContentView != null
9754                     ? mBuilder.mN.bigContentView
9755                     : mBuilder.mN.contentView;
9756             return makeMediaBigContentView(customContent);
9757         }
9758 
9759         /**
9760          * @hide
9761          */
9762         @Override
makeHeadsUpContentView(boolean increasedHeight)9763         public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
9764             RemoteViews customContent = mBuilder.mN.headsUpContentView != null
9765                     ? mBuilder.mN.headsUpContentView
9766                     : mBuilder.mN.contentView;
9767             return makeMediaBigContentView(customContent);
9768         }
9769 
9770         /**
9771          * @hide
9772          */
9773         @Override
areNotificationsVisiblyDifferent(Style other)9774         public boolean areNotificationsVisiblyDifferent(Style other) {
9775             if (other == null || getClass() != other.getClass()) {
9776                 return true;
9777             }
9778             // Comparison done for all custom RemoteViews, independent of style
9779             return false;
9780         }
9781     }
9782 
9783     /**
9784      * Encapsulates the information needed to display a notification as a bubble.
9785      *
9786      * <p>A bubble is used to display app content in a floating window over the existing
9787      * foreground activity. A bubble has a collapsed state represented by an icon and an
9788      * expanded state that displays an activity. These may be defined via
9789      * {@link Builder#Builder(PendingIntent, Icon)} or they may
9790      * be defined via an existing shortcut using {@link Builder#Builder(String)}.
9791      * </p>
9792      *
9793      * <b>Notifications with a valid and allowed bubble will display in collapsed state
9794      * outside of the notification shade on unlocked devices. When a user interacts with the
9795      * collapsed bubble, the bubble activity will be invoked and displayed.</b>
9796      *
9797      * @see Notification.Builder#setBubbleMetadata(BubbleMetadata)
9798      */
9799     public static final class BubbleMetadata implements Parcelable {
9800 
9801         private PendingIntent mPendingIntent;
9802         private PendingIntent mDeleteIntent;
9803         private Icon mIcon;
9804         private int mDesiredHeight;
9805         @DimenRes private int mDesiredHeightResId;
9806         private int mFlags;
9807         private String mShortcutId;
9808 
9809         /**
9810          * If set and the app creating the bubble is in the foreground, the bubble will be posted
9811          * in its expanded state.
9812          *
9813          * <p>This flag has no effect if the app posting the bubble is not in the foreground.
9814          * The app is considered foreground if it is visible and on the screen, note that
9815          * a foreground service does not qualify.
9816          * </p>
9817          *
9818          * <p>Generally this flag should only be set if the user has performed an action to request
9819          * or create a bubble.</p>
9820          *
9821          * @hide
9822          */
9823         public static final int FLAG_AUTO_EXPAND_BUBBLE = 0x00000001;
9824 
9825         /**
9826          * Indicates whether the notification associated with the bubble is being visually
9827          * suppressed from the notification shade. When <code>true</code> the notification is
9828          * hidden, when <code>false</code> the notification shows as normal.
9829          *
9830          * <p>Apps sending bubbles may set this flag so that the bubble is posted <b>without</b>
9831          * the associated notification in the notification shade.</p>
9832          *
9833          * <p>Generally this flag should only be set by the app if the user has performed an
9834          * action to request or create a bubble, or if the user has seen the content in the
9835          * notification and the notification is no longer relevant. </p>
9836          *
9837          * <p>The system will also update this flag with <code>true</code> to hide the notification
9838          * from the user once the bubble has been expanded. </p>
9839          *
9840          * @hide
9841          */
9842         public static final int FLAG_SUPPRESS_NOTIFICATION = 0x00000002;
9843 
9844         /**
9845          * Indicates whether the bubble should be visually suppressed from the bubble stack if the
9846          * user is viewing the same content outside of the bubble. For example, the user has a
9847          * bubble with Alice and then opens up the main app and navigates to Alice's page.
9848          *
9849          * @hide
9850          */
9851         public static final int FLAG_SUPPRESSABLE_BUBBLE = 0x00000004;
9852 
9853         /**
9854          * Indicates whether the bubble is visually suppressed from the bubble stack.
9855          *
9856          * @hide
9857          */
9858         public static final int FLAG_SUPPRESS_BUBBLE = 0x00000008;
9859 
BubbleMetadata(PendingIntent expandIntent, PendingIntent deleteIntent, Icon icon, int height, @DimenRes int heightResId, String shortcutId)9860         private BubbleMetadata(PendingIntent expandIntent, PendingIntent deleteIntent,
9861                 Icon icon, int height, @DimenRes int heightResId, String shortcutId) {
9862             mPendingIntent = expandIntent;
9863             mIcon = icon;
9864             mDesiredHeight = height;
9865             mDesiredHeightResId = heightResId;
9866             mDeleteIntent = deleteIntent;
9867             mShortcutId = shortcutId;
9868         }
9869 
BubbleMetadata(Parcel in)9870         private BubbleMetadata(Parcel in) {
9871             if (in.readInt() != 0) {
9872                 mPendingIntent = PendingIntent.CREATOR.createFromParcel(in);
9873             }
9874             if (in.readInt() != 0) {
9875                 mIcon = Icon.CREATOR.createFromParcel(in);
9876             }
9877             mDesiredHeight = in.readInt();
9878             mFlags = in.readInt();
9879             if (in.readInt() != 0) {
9880                 mDeleteIntent = PendingIntent.CREATOR.createFromParcel(in);
9881             }
9882             mDesiredHeightResId = in.readInt();
9883             if (in.readInt() != 0) {
9884                 mShortcutId = in.readString8();
9885             }
9886         }
9887 
9888         /**
9889          * @return the shortcut id used for this bubble if created via
9890          * {@link Builder#Builder(String)} or null if created
9891          * via {@link Builder#Builder(PendingIntent, Icon)}.
9892          */
9893         @Nullable
getShortcutId()9894         public String getShortcutId() {
9895             return mShortcutId;
9896         }
9897 
9898         /**
9899          * @return the pending intent used to populate the floating window for this bubble, or
9900          * null if this bubble is created via {@link Builder#Builder(String)}.
9901          */
9902         @SuppressLint("InvalidNullConversion")
9903         @Nullable
getIntent()9904         public PendingIntent getIntent() {
9905             return mPendingIntent;
9906         }
9907 
9908         /**
9909          * @deprecated use {@link #getIntent()} instead.
9910          * @removed Removed from the R SDK but was never publicly stable.
9911          */
9912         @Nullable
9913         @Deprecated
getBubbleIntent()9914         public PendingIntent getBubbleIntent() {
9915             return mPendingIntent;
9916         }
9917 
9918         /**
9919          * @return the pending intent to send when the bubble is dismissed by a user, if one exists.
9920          */
9921         @Nullable
getDeleteIntent()9922         public PendingIntent getDeleteIntent() {
9923             return mDeleteIntent;
9924         }
9925 
9926         /**
9927          * @return the icon that will be displayed for this bubble when it is collapsed, or null
9928          * if the bubble is created via {@link Builder#Builder(String)}.
9929          */
9930         @SuppressLint("InvalidNullConversion")
9931         @Nullable
getIcon()9932         public Icon getIcon() {
9933             return mIcon;
9934         }
9935 
9936         /**
9937          * @deprecated use {@link #getIcon()} instead.
9938          * @removed Removed from the R SDK but was never publicly stable.
9939          */
9940         @Nullable
9941         @Deprecated
getBubbleIcon()9942         public Icon getBubbleIcon() {
9943             return mIcon;
9944         }
9945 
9946         /**
9947          * @return the ideal height, in DPs, for the floating window that app content defined by
9948          * {@link #getIntent()} for this bubble. A value of 0 indicates a desired height has
9949          * not been set.
9950          */
9951         @Dimension(unit = DP)
getDesiredHeight()9952         public int getDesiredHeight() {
9953             return mDesiredHeight;
9954         }
9955 
9956         /**
9957          * @return the resId of ideal height for the floating window that app content defined by
9958          * {@link #getIntent()} for this bubble. A value of 0 indicates a res value has not
9959          * been provided for the desired height.
9960          */
9961         @DimenRes
getDesiredHeightResId()9962         public int getDesiredHeightResId() {
9963             return mDesiredHeightResId;
9964         }
9965 
9966         /**
9967          * @return whether this bubble should auto expand when it is posted.
9968          *
9969          * @see BubbleMetadata.Builder#setAutoExpandBubble(boolean)
9970          */
getAutoExpandBubble()9971         public boolean getAutoExpandBubble() {
9972             return (mFlags & FLAG_AUTO_EXPAND_BUBBLE) != 0;
9973         }
9974 
9975         /**
9976          * Indicates whether the notification associated with the bubble is being visually
9977          * suppressed from the notification shade. When <code>true</code> the notification is
9978          * hidden, when <code>false</code> the notification shows as normal.
9979          *
9980          * <p>Apps sending bubbles may set this flag so that the bubble is posted <b>without</b>
9981          * the associated notification in the notification shade.</p>
9982          *
9983          * <p>Generally the app should only set this flag if the user has performed an
9984          * action to request or create a bubble, or if the user has seen the content in the
9985          * notification and the notification is no longer relevant. </p>
9986          *
9987          * <p>The system will update this flag with <code>true</code> to hide the notification
9988          * from the user once the bubble has been expanded.</p>
9989          *
9990          * @return whether this bubble should suppress the notification when it is posted.
9991          *
9992          * @see BubbleMetadata.Builder#setSuppressNotification(boolean)
9993          */
isNotificationSuppressed()9994         public boolean isNotificationSuppressed() {
9995             return (mFlags & FLAG_SUPPRESS_NOTIFICATION) != 0;
9996         }
9997 
9998         /**
9999          * Indicates whether the bubble should be visually suppressed from the bubble stack if the
10000          * user is viewing the same content outside of the bubble. For example, the user has a
10001          * bubble with Alice and then opens up the main app and navigates to Alice's page.
10002          *
10003          * To match the activity and the bubble notification, the bubble notification should
10004          * have a locus id set that matches a locus id set on the activity.
10005          *
10006          * @return whether this bubble should be suppressed when the same content is visible
10007          * outside of the bubble.
10008          *
10009          * @see BubbleMetadata.Builder#setSuppressableBubble(boolean)
10010          */
isBubbleSuppressable()10011         public boolean isBubbleSuppressable() {
10012             return (mFlags & FLAG_SUPPRESSABLE_BUBBLE) != 0;
10013         }
10014 
10015         /**
10016          * Indicates whether the bubble is currently visually suppressed from the bubble stack.
10017          *
10018          * @see BubbleMetadata.Builder#setSuppressableBubble(boolean)
10019          */
isBubbleSuppressed()10020         public boolean isBubbleSuppressed() {
10021             return (mFlags & FLAG_SUPPRESS_BUBBLE) != 0;
10022         }
10023 
10024         /**
10025          * Sets whether the notification associated with the bubble is being visually
10026          * suppressed from the notification shade. When <code>true</code> the notification is
10027          * hidden, when <code>false</code> the notification shows as normal.
10028          *
10029          * @hide
10030          */
setSuppressNotification(boolean suppressed)10031         public void setSuppressNotification(boolean suppressed) {
10032             if (suppressed) {
10033                 mFlags |= Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION;
10034             } else {
10035                 mFlags &= ~Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION;
10036             }
10037         }
10038 
10039         /**
10040          * Sets whether the bubble should be visually suppressed from the bubble stack if the
10041          * user is viewing the same content outside of the bubble. For example, the user has a
10042          * bubble with Alice and then opens up the main app and navigates to Alice's page.
10043          *
10044          * @hide
10045          */
setSuppressBubble(boolean suppressed)10046         public void setSuppressBubble(boolean suppressed) {
10047             if (suppressed) {
10048                 mFlags |= Notification.BubbleMetadata.FLAG_SUPPRESS_BUBBLE;
10049             } else {
10050                 mFlags &= ~Notification.BubbleMetadata.FLAG_SUPPRESS_BUBBLE;
10051             }
10052         }
10053 
10054         /**
10055          * @hide
10056          */
setFlags(int flags)10057         public void setFlags(int flags) {
10058             mFlags = flags;
10059         }
10060 
10061         /**
10062          * @hide
10063          */
getFlags()10064         public int getFlags() {
10065             return mFlags;
10066         }
10067 
10068         public static final @android.annotation.NonNull Parcelable.Creator<BubbleMetadata> CREATOR =
10069                 new Parcelable.Creator<BubbleMetadata>() {
10070 
10071                     @Override
10072                     public BubbleMetadata createFromParcel(Parcel source) {
10073                         return new BubbleMetadata(source);
10074                     }
10075 
10076                     @Override
10077                     public BubbleMetadata[] newArray(int size) {
10078                         return new BubbleMetadata[size];
10079                     }
10080                 };
10081 
10082         @Override
describeContents()10083         public int describeContents() {
10084             return 0;
10085         }
10086 
10087         @Override
writeToParcel(Parcel out, int flags)10088         public void writeToParcel(Parcel out, int flags) {
10089             out.writeInt(mPendingIntent != null ? 1 : 0);
10090             if (mPendingIntent != null) {
10091                 mPendingIntent.writeToParcel(out, 0);
10092             }
10093             out.writeInt(mIcon != null ? 1 : 0);
10094             if (mIcon != null) {
10095                 mIcon.writeToParcel(out, 0);
10096             }
10097             out.writeInt(mDesiredHeight);
10098             out.writeInt(mFlags);
10099             out.writeInt(mDeleteIntent != null ? 1 : 0);
10100             if (mDeleteIntent != null) {
10101                 mDeleteIntent.writeToParcel(out, 0);
10102             }
10103             out.writeInt(mDesiredHeightResId);
10104             out.writeInt(TextUtils.isEmpty(mShortcutId) ? 0 : 1);
10105             if (!TextUtils.isEmpty(mShortcutId)) {
10106                 out.writeString8(mShortcutId);
10107             }
10108         }
10109 
10110         /**
10111          * Builder to construct a {@link BubbleMetadata} object.
10112          */
10113         public static final class Builder {
10114 
10115             private PendingIntent mPendingIntent;
10116             private Icon mIcon;
10117             private int mDesiredHeight;
10118             @DimenRes private int mDesiredHeightResId;
10119             private int mFlags;
10120             private PendingIntent mDeleteIntent;
10121             private String mShortcutId;
10122 
10123             /**
10124              * @deprecated use {@link Builder#Builder(String)} for a bubble created via a
10125              * {@link ShortcutInfo} or {@link Builder#Builder(PendingIntent, Icon)} for a bubble
10126              * created via a {@link PendingIntent}.
10127              */
10128             @Deprecated
Builder()10129             public Builder() {
10130             }
10131 
10132             /**
10133              * Creates a {@link BubbleMetadata.Builder} based on a {@link ShortcutInfo}. To create
10134              * a shortcut bubble, ensure that the shortcut associated with the provided
10135              * {@param shortcutId} is published as a dynamic shortcut that was built with
10136              * {@link ShortcutInfo.Builder#setLongLived(boolean)} being true, otherwise your
10137              * notification will not be able to bubble.
10138              *
10139              * <p>The shortcut icon will be used to represent the bubble when it is collapsed.</p>
10140              *
10141              * <p>The shortcut activity will be used when the bubble is expanded. This will display
10142              * the shortcut activity in a floating window over the existing foreground activity.</p>
10143              *
10144              * <p>When the activity is launched from a bubble,
10145              * {@link Activity#isLaunchedFromBubble()} will return with {@code true}.
10146              * </p>
10147              *
10148              * <p>If the shortcut has not been published when the bubble notification is sent,
10149              * no bubble will be produced. If the shortcut is deleted while the bubble is active,
10150              * the bubble will be removed.</p>
10151              *
10152              * @throws NullPointerException if shortcutId is null.
10153              *
10154              * @see ShortcutInfo
10155              * @see ShortcutInfo.Builder#setLongLived(boolean)
10156              * @see android.content.pm.ShortcutManager#addDynamicShortcuts(List)
10157              */
Builder(@onNull String shortcutId)10158             public Builder(@NonNull String shortcutId) {
10159                 if (TextUtils.isEmpty(shortcutId)) {
10160                     throw new NullPointerException("Bubble requires a non-null shortcut id");
10161                 }
10162                 mShortcutId = shortcutId;
10163             }
10164 
10165             /**
10166              * Creates a {@link BubbleMetadata.Builder} based on the provided intent and icon.
10167              *
10168              * <p>The icon will be used to represent the bubble when it is collapsed. An icon
10169              * should be representative of the content within the bubble. If your app produces
10170              * multiple bubbles, the icon should be unique for each of them.</p>
10171              *
10172              * <p>The intent that will be used when the bubble is expanded. This will display the
10173              * app content in a floating window over the existing foreground activity. The intent
10174              * should point to a resizable activity. </p>
10175              *
10176              * <p>When the activity is launched from a bubble,
10177              * {@link Activity#isLaunchedFromBubble()} will return with {@code true}.
10178              * </p>
10179              *
10180              * Note that the pending intent used here requires PendingIntent.FLAG_MUTABLE.
10181              *
10182              * @throws NullPointerException if intent is null.
10183              * @throws NullPointerException if icon is null.
10184              */
Builder(@onNull PendingIntent intent, @NonNull Icon icon)10185             public Builder(@NonNull PendingIntent intent, @NonNull Icon icon) {
10186                 if (intent == null) {
10187                     throw new NullPointerException("Bubble requires non-null pending intent");
10188                 }
10189                 if (icon == null) {
10190                     throw new NullPointerException("Bubbles require non-null icon");
10191                 }
10192                 if (icon.getType() != TYPE_URI_ADAPTIVE_BITMAP
10193                         && icon.getType() != TYPE_URI) {
10194                     Log.w(TAG, "Bubbles work best with icons of TYPE_URI or "
10195                             + "TYPE_URI_ADAPTIVE_BITMAP. "
10196                             + "In the future, using an icon of this type will be required.");
10197                 }
10198                 mPendingIntent = intent;
10199                 mIcon = icon;
10200             }
10201 
10202             /**
10203              * @deprecated use {@link Builder#Builder(String)} instead.
10204              * @removed Removed from the R SDK but was never publicly stable.
10205              */
10206             @NonNull
10207             @Deprecated
createShortcutBubble(@onNull String shortcutId)10208             public BubbleMetadata.Builder createShortcutBubble(@NonNull String shortcutId) {
10209                 if (!TextUtils.isEmpty(shortcutId)) {
10210                     // If shortcut id is set, we don't use these if they were previously set.
10211                     mPendingIntent = null;
10212                     mIcon = null;
10213                 }
10214                 mShortcutId = shortcutId;
10215                 return this;
10216             }
10217 
10218             /**
10219              * @deprecated use {@link Builder#Builder(PendingIntent, Icon)} instead.
10220              * @removed Removed from the R SDK but was never publicly stable.
10221              */
10222             @NonNull
10223             @Deprecated
createIntentBubble(@onNull PendingIntent intent, @NonNull Icon icon)10224             public BubbleMetadata.Builder createIntentBubble(@NonNull PendingIntent intent,
10225                     @NonNull Icon icon) {
10226                 if (intent == null) {
10227                     throw new IllegalArgumentException("Bubble requires non-null pending intent");
10228                 }
10229                 if (icon == null) {
10230                     throw new IllegalArgumentException("Bubbles require non-null icon");
10231                 }
10232                 if (icon.getType() != TYPE_URI_ADAPTIVE_BITMAP
10233                         && icon.getType() != TYPE_URI) {
10234                     Log.w(TAG, "Bubbles work best with icons of TYPE_URI or "
10235                             + "TYPE_URI_ADAPTIVE_BITMAP. "
10236                             + "In the future, using an icon of this type will be required.");
10237                 }
10238                 mShortcutId = null;
10239                 mPendingIntent = intent;
10240                 mIcon = icon;
10241                 return this;
10242             }
10243 
10244             /**
10245              * Sets the intent for the bubble.
10246              *
10247              * <p>The intent that will be used when the bubble is expanded. This will display the
10248              * app content in a floating window over the existing foreground activity. The intent
10249              * should point to a resizable activity. </p>
10250              *
10251              * @throws NullPointerException  if intent is null.
10252              * @throws IllegalStateException if this builder was created via
10253              *                               {@link Builder#Builder(String)}.
10254              */
10255             @NonNull
setIntent(@onNull PendingIntent intent)10256             public BubbleMetadata.Builder setIntent(@NonNull PendingIntent intent) {
10257                 if (mShortcutId != null) {
10258                     throw new IllegalStateException("Created as a shortcut bubble, cannot set a "
10259                             + "PendingIntent. Consider using "
10260                             + "BubbleMetadata.Builder(PendingIntent,Icon) instead.");
10261                 }
10262                 if (intent == null) {
10263                     throw new NullPointerException("Bubble requires non-null pending intent");
10264                 }
10265                 mPendingIntent = intent;
10266                 return this;
10267             }
10268 
10269             /**
10270              * Sets the icon for the bubble. Can only be used if the bubble was created
10271              * via {@link Builder#Builder(PendingIntent, Icon)}.
10272              *
10273              * <p>The icon will be used to represent the bubble when it is collapsed. An icon
10274              * should be representative of the content within the bubble. If your app produces
10275              * multiple bubbles, the icon should be unique for each of them.</p>
10276              *
10277              * <p>It is recommended to use an {@link Icon} of type {@link Icon#TYPE_URI}
10278              * or {@link Icon#TYPE_URI_ADAPTIVE_BITMAP}</p>
10279              *
10280              * @throws NullPointerException  if icon is null.
10281              * @throws IllegalStateException if this builder was created via
10282              *                               {@link Builder#Builder(String)}.
10283              */
10284             @NonNull
setIcon(@onNull Icon icon)10285             public BubbleMetadata.Builder setIcon(@NonNull Icon icon) {
10286                 if (mShortcutId != null) {
10287                     throw new IllegalStateException("Created as a shortcut bubble, cannot set an "
10288                             + "Icon. Consider using "
10289                             + "BubbleMetadata.Builder(PendingIntent,Icon) instead.");
10290                 }
10291                 if (icon == null) {
10292                     throw new NullPointerException("Bubbles require non-null icon");
10293                 }
10294                 if (icon.getType() != TYPE_URI_ADAPTIVE_BITMAP
10295                         && icon.getType() != TYPE_URI) {
10296                     Log.w(TAG, "Bubbles work best with icons of TYPE_URI or "
10297                             + "TYPE_URI_ADAPTIVE_BITMAP. "
10298                             + "In the future, using an icon of this type will be required.");
10299                 }
10300                 mIcon = icon;
10301                 return this;
10302             }
10303 
10304             /**
10305              * Sets the desired height in DPs for the expanded content of the bubble.
10306              *
10307              * <p>This height may not be respected if there is not enough space on the screen or if
10308              * the provided height is too small to be useful.</p>
10309              *
10310              * <p>If {@link #setDesiredHeightResId(int)} was previously called on this builder, the
10311              * previous value set will be cleared after calling this method, and this value will
10312              * be used instead.</p>
10313              *
10314              * <p>A desired height (in DPs or via resID) is optional.</p>
10315              *
10316              * @see #setDesiredHeightResId(int)
10317              */
10318             @NonNull
setDesiredHeight(@imensionunit = DP) int height)10319             public BubbleMetadata.Builder setDesiredHeight(@Dimension(unit = DP) int height) {
10320                 mDesiredHeight = Math.max(height, 0);
10321                 mDesiredHeightResId = 0;
10322                 return this;
10323             }
10324 
10325 
10326             /**
10327              * Sets the desired height via resId for the expanded content of the bubble.
10328              *
10329              * <p>This height may not be respected if there is not enough space on the screen or if
10330              * the provided height is too small to be useful.</p>
10331              *
10332              * <p>If {@link #setDesiredHeight(int)} was previously called on this builder, the
10333              * previous value set will be cleared after calling this method, and this value will
10334              * be used instead.</p>
10335              *
10336              * <p>A desired height (in DPs or via resID) is optional.</p>
10337              *
10338              * @see #setDesiredHeight(int)
10339              */
10340             @NonNull
setDesiredHeightResId(@imenRes int heightResId)10341             public BubbleMetadata.Builder setDesiredHeightResId(@DimenRes int heightResId) {
10342                 mDesiredHeightResId = heightResId;
10343                 mDesiredHeight = 0;
10344                 return this;
10345             }
10346 
10347             /**
10348              * Sets whether the bubble will be posted in its expanded state.
10349              *
10350              * <p>This flag has no effect if the app posting the bubble is not in the foreground.
10351              * The app is considered foreground if it is visible and on the screen, note that
10352              * a foreground service does not qualify.
10353              * </p>
10354              *
10355              * <p>Generally, this flag should only be set if the user has performed an action to
10356              * request or create a bubble.</p>
10357              *
10358              * <p>Setting this flag is optional; it defaults to false.</p>
10359              */
10360             @NonNull
setAutoExpandBubble(boolean shouldExpand)10361             public BubbleMetadata.Builder setAutoExpandBubble(boolean shouldExpand) {
10362                 setFlag(FLAG_AUTO_EXPAND_BUBBLE, shouldExpand);
10363                 return this;
10364             }
10365 
10366             /**
10367              * Sets whether the bubble will be posted <b>without</b> the associated notification in
10368              * the notification shade.
10369              *
10370              * <p>Generally, this flag should only be set if the user has performed an action to
10371              * request or create a bubble, or if the user has seen the content in the notification
10372              * and the notification is no longer relevant.</p>
10373              *
10374              * <p>Setting this flag is optional; it defaults to false.</p>
10375              */
10376             @NonNull
setSuppressNotification(boolean shouldSuppressNotif)10377             public BubbleMetadata.Builder setSuppressNotification(boolean shouldSuppressNotif) {
10378                 setFlag(FLAG_SUPPRESS_NOTIFICATION, shouldSuppressNotif);
10379                 return this;
10380             }
10381 
10382             /**
10383              * Indicates whether the bubble should be visually suppressed from the bubble stack if
10384              * the user is viewing the same content outside of the bubble. For example, the user has
10385              * a bubble with Alice and then opens up the main app and navigates to Alice's page.
10386              *
10387              * To match the activity and the bubble notification, the bubble notification should
10388              * have a locus id set that matches a locus id set on the activity.
10389              *
10390              * {@link Notification.Builder#setLocusId(LocusId)}
10391              * {@link Activity#setLocusContext(LocusId, Bundle)}
10392              */
10393             @NonNull
setSuppressableBubble(boolean suppressBubble)10394             public BubbleMetadata.Builder setSuppressableBubble(boolean suppressBubble) {
10395                 setFlag(FLAG_SUPPRESSABLE_BUBBLE, suppressBubble);
10396                 return this;
10397             }
10398 
10399             /**
10400              * Sets an intent to send when this bubble is explicitly removed by the user.
10401              *
10402              * <p>Setting a delete intent is optional.</p>
10403              */
10404             @NonNull
setDeleteIntent(@ullable PendingIntent deleteIntent)10405             public BubbleMetadata.Builder setDeleteIntent(@Nullable PendingIntent deleteIntent) {
10406                 mDeleteIntent = deleteIntent;
10407                 return this;
10408             }
10409 
10410             /**
10411              * Creates the {@link BubbleMetadata} defined by this builder.
10412              *
10413              * @throws NullPointerException if required elements have not been set.
10414              */
10415             @NonNull
build()10416             public BubbleMetadata build() {
10417                 if (mShortcutId == null && mPendingIntent == null) {
10418                     throw new NullPointerException(
10419                             "Must supply pending intent or shortcut to bubble");
10420                 }
10421                 if (mShortcutId == null && mIcon == null) {
10422                     throw new NullPointerException(
10423                             "Must supply an icon or shortcut for the bubble");
10424                 }
10425                 BubbleMetadata data = new BubbleMetadata(mPendingIntent, mDeleteIntent,
10426                         mIcon, mDesiredHeight, mDesiredHeightResId, mShortcutId);
10427                 data.setFlags(mFlags);
10428                 return data;
10429             }
10430 
10431             /**
10432              * @hide
10433              */
setFlag(int mask, boolean value)10434             public BubbleMetadata.Builder setFlag(int mask, boolean value) {
10435                 if (value) {
10436                     mFlags |= mask;
10437                 } else {
10438                     mFlags &= ~mask;
10439                 }
10440                 return this;
10441             }
10442         }
10443     }
10444 
10445 
10446     // When adding a new Style subclass here, don't forget to update
10447     // Builder.getNotificationStyleClass.
10448 
10449     /**
10450      * Extender interface for use with {@link Builder#extend}. Extenders may be used to add
10451      * metadata or change options on a notification builder.
10452      */
10453     public interface Extender {
10454         /**
10455          * Apply this extender to a notification builder.
10456          * @param builder the builder to be modified.
10457          * @return the build object for chaining.
10458          */
extend(Builder builder)10459         public Builder extend(Builder builder);
10460     }
10461 
10462     /**
10463      * Helper class to add wearable extensions to notifications.
10464      * <p class="note"> See
10465      * <a href="{@docRoot}wear/notifications/creating.html">Creating Notifications
10466      * for Android Wear</a> for more information on how to use this class.
10467      * <p>
10468      * To create a notification with wearable extensions:
10469      * <ol>
10470      *   <li>Create a {@link android.app.Notification.Builder}, setting any desired
10471      *   properties.
10472      *   <li>Create a {@link android.app.Notification.WearableExtender}.
10473      *   <li>Set wearable-specific properties using the
10474      *   {@code add} and {@code set} methods of {@link android.app.Notification.WearableExtender}.
10475      *   <li>Call {@link android.app.Notification.Builder#extend} to apply the extensions to a
10476      *   notification.
10477      *   <li>Post the notification to the notification system with the
10478      *   {@code NotificationManager.notify(...)} methods.
10479      * </ol>
10480      *
10481      * <pre class="prettyprint">
10482      * Notification notif = new Notification.Builder(mContext)
10483      *         .setContentTitle(&quot;New mail from &quot; + sender.toString())
10484      *         .setContentText(subject)
10485      *         .setSmallIcon(R.drawable.new_mail)
10486      *         .extend(new Notification.WearableExtender()
10487      *                 .setContentIcon(R.drawable.new_mail))
10488      *         .build();
10489      * NotificationManager notificationManger =
10490      *         (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
10491      * notificationManger.notify(0, notif);</pre>
10492      *
10493      * <p>Wearable extensions can be accessed on an existing notification by using the
10494      * {@code WearableExtender(Notification)} constructor,
10495      * and then using the {@code get} methods to access values.
10496      *
10497      * <pre class="prettyprint">
10498      * Notification.WearableExtender wearableExtender = new Notification.WearableExtender(
10499      *         notification);
10500      * List&lt;Notification&gt; pages = wearableExtender.getPages();</pre>
10501      */
10502     public static final class WearableExtender implements Extender {
10503         /**
10504          * Sentinel value for an action index that is unset.
10505          */
10506         public static final int UNSET_ACTION_INDEX = -1;
10507 
10508         /**
10509          * Size value for use with {@link #setCustomSizePreset} to show this notification with
10510          * default sizing.
10511          * <p>For custom display notifications created using {@link #setDisplayIntent},
10512          * the default is {@link #SIZE_MEDIUM}. All other notifications size automatically based
10513          * on their content.
10514          *
10515          * @deprecated Display intents are no longer supported.
10516          */
10517         @Deprecated
10518         public static final int SIZE_DEFAULT = 0;
10519 
10520         /**
10521          * Size value for use with {@link #setCustomSizePreset} to show this notification
10522          * with an extra small size.
10523          * <p>This value is only applicable for custom display notifications created using
10524          * {@link #setDisplayIntent}.
10525          *
10526          * @deprecated Display intents are no longer supported.
10527          */
10528         @Deprecated
10529         public static final int SIZE_XSMALL = 1;
10530 
10531         /**
10532          * Size value for use with {@link #setCustomSizePreset} to show this notification
10533          * with a small size.
10534          * <p>This value is only applicable for custom display notifications created using
10535          * {@link #setDisplayIntent}.
10536          *
10537          * @deprecated Display intents are no longer supported.
10538          */
10539         @Deprecated
10540         public static final int SIZE_SMALL = 2;
10541 
10542         /**
10543          * Size value for use with {@link #setCustomSizePreset} to show this notification
10544          * with a medium size.
10545          * <p>This value is only applicable for custom display notifications created using
10546          * {@link #setDisplayIntent}.
10547          *
10548          * @deprecated Display intents are no longer supported.
10549          */
10550         @Deprecated
10551         public static final int SIZE_MEDIUM = 3;
10552 
10553         /**
10554          * Size value for use with {@link #setCustomSizePreset} to show this notification
10555          * with a large size.
10556          * <p>This value is only applicable for custom display notifications created using
10557          * {@link #setDisplayIntent}.
10558          *
10559          * @deprecated Display intents are no longer supported.
10560          */
10561         @Deprecated
10562         public static final int SIZE_LARGE = 4;
10563 
10564         /**
10565          * Size value for use with {@link #setCustomSizePreset} to show this notification
10566          * full screen.
10567          * <p>This value is only applicable for custom display notifications created using
10568          * {@link #setDisplayIntent}.
10569          *
10570          * @deprecated Display intents are no longer supported.
10571          */
10572         @Deprecated
10573         public static final int SIZE_FULL_SCREEN = 5;
10574 
10575         /**
10576          * Sentinel value for use with {@link #setHintScreenTimeout} to keep the screen on for a
10577          * short amount of time when this notification is displayed on the screen. This
10578          * is the default value.
10579          *
10580          * @deprecated This feature is no longer supported.
10581          */
10582         @Deprecated
10583         public static final int SCREEN_TIMEOUT_SHORT = 0;
10584 
10585         /**
10586          * Sentinel value for use with {@link #setHintScreenTimeout} to keep the screen on
10587          * for a longer amount of time when this notification is displayed on the screen.
10588          *
10589          * @deprecated This feature is no longer supported.
10590          */
10591         @Deprecated
10592         public static final int SCREEN_TIMEOUT_LONG = -1;
10593 
10594         /** Notification extra which contains wearable extensions */
10595         private static final String EXTRA_WEARABLE_EXTENSIONS = "android.wearable.EXTENSIONS";
10596 
10597         // Keys within EXTRA_WEARABLE_EXTENSIONS for wearable options.
10598         private static final String KEY_ACTIONS = "actions";
10599         private static final String KEY_FLAGS = "flags";
10600         private static final String KEY_DISPLAY_INTENT = "displayIntent";
10601         private static final String KEY_PAGES = "pages";
10602         private static final String KEY_BACKGROUND = "background";
10603         private static final String KEY_CONTENT_ICON = "contentIcon";
10604         private static final String KEY_CONTENT_ICON_GRAVITY = "contentIconGravity";
10605         private static final String KEY_CONTENT_ACTION_INDEX = "contentActionIndex";
10606         private static final String KEY_CUSTOM_SIZE_PRESET = "customSizePreset";
10607         private static final String KEY_CUSTOM_CONTENT_HEIGHT = "customContentHeight";
10608         private static final String KEY_GRAVITY = "gravity";
10609         private static final String KEY_HINT_SCREEN_TIMEOUT = "hintScreenTimeout";
10610         private static final String KEY_DISMISSAL_ID = "dismissalId";
10611         private static final String KEY_BRIDGE_TAG = "bridgeTag";
10612 
10613         // Flags bitwise-ored to mFlags
10614         private static final int FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE = 0x1;
10615         private static final int FLAG_HINT_HIDE_ICON = 1 << 1;
10616         private static final int FLAG_HINT_SHOW_BACKGROUND_ONLY = 1 << 2;
10617         private static final int FLAG_START_SCROLL_BOTTOM = 1 << 3;
10618         private static final int FLAG_HINT_AVOID_BACKGROUND_CLIPPING = 1 << 4;
10619         private static final int FLAG_BIG_PICTURE_AMBIENT = 1 << 5;
10620         private static final int FLAG_HINT_CONTENT_INTENT_LAUNCHES_ACTIVITY = 1 << 6;
10621 
10622         // Default value for flags integer
10623         private static final int DEFAULT_FLAGS = FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE;
10624 
10625         private static final int DEFAULT_CONTENT_ICON_GRAVITY = Gravity.END;
10626         private static final int DEFAULT_GRAVITY = Gravity.BOTTOM;
10627 
10628         private ArrayList<Action> mActions = new ArrayList<Action>();
10629         private int mFlags = DEFAULT_FLAGS;
10630         private PendingIntent mDisplayIntent;
10631         private ArrayList<Notification> mPages = new ArrayList<Notification>();
10632         private Bitmap mBackground;
10633         private int mContentIcon;
10634         private int mContentIconGravity = DEFAULT_CONTENT_ICON_GRAVITY;
10635         private int mContentActionIndex = UNSET_ACTION_INDEX;
10636         private int mCustomSizePreset = SIZE_DEFAULT;
10637         private int mCustomContentHeight;
10638         private int mGravity = DEFAULT_GRAVITY;
10639         private int mHintScreenTimeout;
10640         private String mDismissalId;
10641         private String mBridgeTag;
10642 
10643         /**
10644          * Create a {@link android.app.Notification.WearableExtender} with default
10645          * options.
10646          */
WearableExtender()10647         public WearableExtender() {
10648         }
10649 
WearableExtender(Notification notif)10650         public WearableExtender(Notification notif) {
10651             Bundle wearableBundle = notif.extras.getBundle(EXTRA_WEARABLE_EXTENSIONS);
10652             if (wearableBundle != null) {
10653                 List<Action> actions = wearableBundle.getParcelableArrayList(KEY_ACTIONS);
10654                 if (actions != null) {
10655                     mActions.addAll(actions);
10656                 }
10657 
10658                 mFlags = wearableBundle.getInt(KEY_FLAGS, DEFAULT_FLAGS);
10659                 mDisplayIntent = wearableBundle.getParcelable(KEY_DISPLAY_INTENT);
10660 
10661                 Notification[] pages = getParcelableArrayFromBundle(
10662                         wearableBundle, KEY_PAGES, Notification.class);
10663                 if (pages != null) {
10664                     Collections.addAll(mPages, pages);
10665                 }
10666 
10667                 mBackground = wearableBundle.getParcelable(KEY_BACKGROUND);
10668                 mContentIcon = wearableBundle.getInt(KEY_CONTENT_ICON);
10669                 mContentIconGravity = wearableBundle.getInt(KEY_CONTENT_ICON_GRAVITY,
10670                         DEFAULT_CONTENT_ICON_GRAVITY);
10671                 mContentActionIndex = wearableBundle.getInt(KEY_CONTENT_ACTION_INDEX,
10672                         UNSET_ACTION_INDEX);
10673                 mCustomSizePreset = wearableBundle.getInt(KEY_CUSTOM_SIZE_PRESET,
10674                         SIZE_DEFAULT);
10675                 mCustomContentHeight = wearableBundle.getInt(KEY_CUSTOM_CONTENT_HEIGHT);
10676                 mGravity = wearableBundle.getInt(KEY_GRAVITY, DEFAULT_GRAVITY);
10677                 mHintScreenTimeout = wearableBundle.getInt(KEY_HINT_SCREEN_TIMEOUT);
10678                 mDismissalId = wearableBundle.getString(KEY_DISMISSAL_ID);
10679                 mBridgeTag = wearableBundle.getString(KEY_BRIDGE_TAG);
10680             }
10681         }
10682 
10683         /**
10684          * Apply wearable extensions to a notification that is being built. This is typically
10685          * called by the {@link android.app.Notification.Builder#extend} method of
10686          * {@link android.app.Notification.Builder}.
10687          */
10688         @Override
extend(Notification.Builder builder)10689         public Notification.Builder extend(Notification.Builder builder) {
10690             Bundle wearableBundle = new Bundle();
10691 
10692             if (!mActions.isEmpty()) {
10693                 wearableBundle.putParcelableArrayList(KEY_ACTIONS, mActions);
10694             }
10695             if (mFlags != DEFAULT_FLAGS) {
10696                 wearableBundle.putInt(KEY_FLAGS, mFlags);
10697             }
10698             if (mDisplayIntent != null) {
10699                 wearableBundle.putParcelable(KEY_DISPLAY_INTENT, mDisplayIntent);
10700             }
10701             if (!mPages.isEmpty()) {
10702                 wearableBundle.putParcelableArray(KEY_PAGES, mPages.toArray(
10703                         new Notification[mPages.size()]));
10704             }
10705             if (mBackground != null) {
10706                 wearableBundle.putParcelable(KEY_BACKGROUND, mBackground);
10707             }
10708             if (mContentIcon != 0) {
10709                 wearableBundle.putInt(KEY_CONTENT_ICON, mContentIcon);
10710             }
10711             if (mContentIconGravity != DEFAULT_CONTENT_ICON_GRAVITY) {
10712                 wearableBundle.putInt(KEY_CONTENT_ICON_GRAVITY, mContentIconGravity);
10713             }
10714             if (mContentActionIndex != UNSET_ACTION_INDEX) {
10715                 wearableBundle.putInt(KEY_CONTENT_ACTION_INDEX,
10716                         mContentActionIndex);
10717             }
10718             if (mCustomSizePreset != SIZE_DEFAULT) {
10719                 wearableBundle.putInt(KEY_CUSTOM_SIZE_PRESET, mCustomSizePreset);
10720             }
10721             if (mCustomContentHeight != 0) {
10722                 wearableBundle.putInt(KEY_CUSTOM_CONTENT_HEIGHT, mCustomContentHeight);
10723             }
10724             if (mGravity != DEFAULT_GRAVITY) {
10725                 wearableBundle.putInt(KEY_GRAVITY, mGravity);
10726             }
10727             if (mHintScreenTimeout != 0) {
10728                 wearableBundle.putInt(KEY_HINT_SCREEN_TIMEOUT, mHintScreenTimeout);
10729             }
10730             if (mDismissalId != null) {
10731                 wearableBundle.putString(KEY_DISMISSAL_ID, mDismissalId);
10732             }
10733             if (mBridgeTag != null) {
10734                 wearableBundle.putString(KEY_BRIDGE_TAG, mBridgeTag);
10735             }
10736 
10737             builder.getExtras().putBundle(EXTRA_WEARABLE_EXTENSIONS, wearableBundle);
10738             return builder;
10739         }
10740 
10741         @Override
clone()10742         public WearableExtender clone() {
10743             WearableExtender that = new WearableExtender();
10744             that.mActions = new ArrayList<Action>(this.mActions);
10745             that.mFlags = this.mFlags;
10746             that.mDisplayIntent = this.mDisplayIntent;
10747             that.mPages = new ArrayList<Notification>(this.mPages);
10748             that.mBackground = this.mBackground;
10749             that.mContentIcon = this.mContentIcon;
10750             that.mContentIconGravity = this.mContentIconGravity;
10751             that.mContentActionIndex = this.mContentActionIndex;
10752             that.mCustomSizePreset = this.mCustomSizePreset;
10753             that.mCustomContentHeight = this.mCustomContentHeight;
10754             that.mGravity = this.mGravity;
10755             that.mHintScreenTimeout = this.mHintScreenTimeout;
10756             that.mDismissalId = this.mDismissalId;
10757             that.mBridgeTag = this.mBridgeTag;
10758             return that;
10759         }
10760 
10761         /**
10762          * Add a wearable action to this notification.
10763          *
10764          * <p>When wearable actions are added using this method, the set of actions that
10765          * show on a wearable device splits from devices that only show actions added
10766          * using {@link android.app.Notification.Builder#addAction}. This allows for customization
10767          * of which actions display on different devices.
10768          *
10769          * @param action the action to add to this notification
10770          * @return this object for method chaining
10771          * @see android.app.Notification.Action
10772          */
addAction(Action action)10773         public WearableExtender addAction(Action action) {
10774             mActions.add(action);
10775             return this;
10776         }
10777 
10778         /**
10779          * Adds wearable actions to this notification.
10780          *
10781          * <p>When wearable actions are added using this method, the set of actions that
10782          * show on a wearable device splits from devices that only show actions added
10783          * using {@link android.app.Notification.Builder#addAction}. This allows for customization
10784          * of which actions display on different devices.
10785          *
10786          * @param actions the actions to add to this notification
10787          * @return this object for method chaining
10788          * @see android.app.Notification.Action
10789          */
addActions(List<Action> actions)10790         public WearableExtender addActions(List<Action> actions) {
10791             mActions.addAll(actions);
10792             return this;
10793         }
10794 
10795         /**
10796          * Clear all wearable actions present on this builder.
10797          * @return this object for method chaining.
10798          * @see #addAction
10799          */
clearActions()10800         public WearableExtender clearActions() {
10801             mActions.clear();
10802             return this;
10803         }
10804 
10805         /**
10806          * Get the wearable actions present on this notification.
10807          */
getActions()10808         public List<Action> getActions() {
10809             return mActions;
10810         }
10811 
10812         /**
10813          * Set an intent to launch inside of an activity view when displaying
10814          * this notification. The {@link PendingIntent} provided should be for an activity.
10815          *
10816          * <pre class="prettyprint">
10817          * Intent displayIntent = new Intent(context, MyDisplayActivity.class);
10818          * PendingIntent displayPendingIntent = PendingIntent.getActivity(context,
10819          *         0, displayIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
10820          * Notification notif = new Notification.Builder(context)
10821          *         .extend(new Notification.WearableExtender()
10822          *                 .setDisplayIntent(displayPendingIntent)
10823          *                 .setCustomSizePreset(Notification.WearableExtender.SIZE_MEDIUM))
10824          *         .build();</pre>
10825          *
10826          * <p>The activity to launch needs to allow embedding, must be exported, and
10827          * should have an empty task affinity. It is also recommended to use the device
10828          * default light theme.
10829          *
10830          * <p>Example AndroidManifest.xml entry:
10831          * <pre class="prettyprint">
10832          * &lt;activity android:name=&quot;com.example.MyDisplayActivity&quot;
10833          *     android:exported=&quot;true&quot;
10834          *     android:allowEmbedded=&quot;true&quot;
10835          *     android:taskAffinity=&quot;&quot;
10836          *     android:theme=&quot;@android:style/Theme.DeviceDefault.Light&quot; /&gt;</pre>
10837          *
10838          * @param intent the {@link PendingIntent} for an activity
10839          * @return this object for method chaining
10840          * @see android.app.Notification.WearableExtender#getDisplayIntent
10841          * @deprecated Display intents are no longer supported.
10842          */
10843         @Deprecated
setDisplayIntent(PendingIntent intent)10844         public WearableExtender setDisplayIntent(PendingIntent intent) {
10845             mDisplayIntent = intent;
10846             return this;
10847         }
10848 
10849         /**
10850          * Get the intent to launch inside of an activity view when displaying this
10851          * notification. This {@code PendingIntent} should be for an activity.
10852          *
10853          * @deprecated Display intents are no longer supported.
10854          */
10855         @Deprecated
getDisplayIntent()10856         public PendingIntent getDisplayIntent() {
10857             return mDisplayIntent;
10858         }
10859 
10860         /**
10861          * Add an additional page of content to display with this notification. The current
10862          * notification forms the first page, and pages added using this function form
10863          * subsequent pages. This field can be used to separate a notification into multiple
10864          * sections.
10865          *
10866          * @param page the notification to add as another page
10867          * @return this object for method chaining
10868          * @see android.app.Notification.WearableExtender#getPages
10869          * @deprecated Multiple content pages are no longer supported.
10870          */
10871         @Deprecated
addPage(Notification page)10872         public WearableExtender addPage(Notification page) {
10873             mPages.add(page);
10874             return this;
10875         }
10876 
10877         /**
10878          * Add additional pages of content to display with this notification. The current
10879          * notification forms the first page, and pages added using this function form
10880          * subsequent pages. This field can be used to separate a notification into multiple
10881          * sections.
10882          *
10883          * @param pages a list of notifications
10884          * @return this object for method chaining
10885          * @see android.app.Notification.WearableExtender#getPages
10886          * @deprecated Multiple content pages are no longer supported.
10887          */
10888         @Deprecated
addPages(List<Notification> pages)10889         public WearableExtender addPages(List<Notification> pages) {
10890             mPages.addAll(pages);
10891             return this;
10892         }
10893 
10894         /**
10895          * Clear all additional pages present on this builder.
10896          * @return this object for method chaining.
10897          * @see #addPage
10898          * @deprecated Multiple content pages are no longer supported.
10899          */
10900         @Deprecated
clearPages()10901         public WearableExtender clearPages() {
10902             mPages.clear();
10903             return this;
10904         }
10905 
10906         /**
10907          * Get the array of additional pages of content for displaying this notification. The
10908          * current notification forms the first page, and elements within this array form
10909          * subsequent pages. This field can be used to separate a notification into multiple
10910          * sections.
10911          * @return the pages for this notification
10912          * @deprecated Multiple content pages are no longer supported.
10913          */
10914         @Deprecated
getPages()10915         public List<Notification> getPages() {
10916             return mPages;
10917         }
10918 
10919         /**
10920          * Set a background image to be displayed behind the notification content.
10921          * Contrary to the {@link android.app.Notification.BigPictureStyle}, this background
10922          * will work with any notification style.
10923          *
10924          * @param background the background bitmap
10925          * @return this object for method chaining
10926          * @see android.app.Notification.WearableExtender#getBackground
10927          * @deprecated Background images are no longer supported.
10928          */
10929         @Deprecated
setBackground(Bitmap background)10930         public WearableExtender setBackground(Bitmap background) {
10931             mBackground = background;
10932             return this;
10933         }
10934 
10935         /**
10936          * Get a background image to be displayed behind the notification content.
10937          * Contrary to the {@link android.app.Notification.BigPictureStyle}, this background
10938          * will work with any notification style.
10939          *
10940          * @return the background image
10941          * @see android.app.Notification.WearableExtender#setBackground
10942          * @deprecated Background images are no longer supported.
10943          */
10944         @Deprecated
getBackground()10945         public Bitmap getBackground() {
10946             return mBackground;
10947         }
10948 
10949         /**
10950          * Set an icon that goes with the content of this notification.
10951          */
10952         @Deprecated
setContentIcon(int icon)10953         public WearableExtender setContentIcon(int icon) {
10954             mContentIcon = icon;
10955             return this;
10956         }
10957 
10958         /**
10959          * Get an icon that goes with the content of this notification.
10960          */
10961         @Deprecated
getContentIcon()10962         public int getContentIcon() {
10963             return mContentIcon;
10964         }
10965 
10966         /**
10967          * Set the gravity that the content icon should have within the notification display.
10968          * Supported values include {@link android.view.Gravity#START} and
10969          * {@link android.view.Gravity#END}. The default value is {@link android.view.Gravity#END}.
10970          * @see #setContentIcon
10971          */
10972         @Deprecated
setContentIconGravity(int contentIconGravity)10973         public WearableExtender setContentIconGravity(int contentIconGravity) {
10974             mContentIconGravity = contentIconGravity;
10975             return this;
10976         }
10977 
10978         /**
10979          * Get the gravity that the content icon should have within the notification display.
10980          * Supported values include {@link android.view.Gravity#START} and
10981          * {@link android.view.Gravity#END}. The default value is {@link android.view.Gravity#END}.
10982          * @see #getContentIcon
10983          */
10984         @Deprecated
getContentIconGravity()10985         public int getContentIconGravity() {
10986             return mContentIconGravity;
10987         }
10988 
10989         /**
10990          * Set an action from this notification's actions as the primary action. If the action has a
10991          * {@link RemoteInput} associated with it, shortcuts to the options for that input are shown
10992          * directly on the notification.
10993          *
10994          * @param actionIndex The index of the primary action.
10995          *                    If wearable actions were added to the main notification, this index
10996          *                    will apply to that list, otherwise it will apply to the regular
10997          *                    actions list.
10998          */
setContentAction(int actionIndex)10999         public WearableExtender setContentAction(int actionIndex) {
11000             mContentActionIndex = actionIndex;
11001             return this;
11002         }
11003 
11004         /**
11005          * Get the index of the notification action, if any, that was specified as the primary
11006          * action.
11007          *
11008          * <p>If wearable specific actions were added to the main notification, this index will
11009          * apply to that list, otherwise it will apply to the regular actions list.
11010          *
11011          * @return the action index or {@link #UNSET_ACTION_INDEX} if no action was selected.
11012          */
getContentAction()11013         public int getContentAction() {
11014             return mContentActionIndex;
11015         }
11016 
11017         /**
11018          * Set the gravity that this notification should have within the available viewport space.
11019          * Supported values include {@link android.view.Gravity#TOP},
11020          * {@link android.view.Gravity#CENTER_VERTICAL} and {@link android.view.Gravity#BOTTOM}.
11021          * The default value is {@link android.view.Gravity#BOTTOM}.
11022          */
11023         @Deprecated
setGravity(int gravity)11024         public WearableExtender setGravity(int gravity) {
11025             mGravity = gravity;
11026             return this;
11027         }
11028 
11029         /**
11030          * Get the gravity that this notification should have within the available viewport space.
11031          * Supported values include {@link android.view.Gravity#TOP},
11032          * {@link android.view.Gravity#CENTER_VERTICAL} and {@link android.view.Gravity#BOTTOM}.
11033          * The default value is {@link android.view.Gravity#BOTTOM}.
11034          */
11035         @Deprecated
getGravity()11036         public int getGravity() {
11037             return mGravity;
11038         }
11039 
11040         /**
11041          * Set the custom size preset for the display of this notification out of the available
11042          * presets found in {@link android.app.Notification.WearableExtender}, e.g.
11043          * {@link #SIZE_LARGE}.
11044          * <p>Some custom size presets are only applicable for custom display notifications created
11045          * using {@link android.app.Notification.WearableExtender#setDisplayIntent}. Check the
11046          * documentation for the preset in question. See also
11047          * {@link #setCustomContentHeight} and {@link #getCustomSizePreset}.
11048          */
11049         @Deprecated
setCustomSizePreset(int sizePreset)11050         public WearableExtender setCustomSizePreset(int sizePreset) {
11051             mCustomSizePreset = sizePreset;
11052             return this;
11053         }
11054 
11055         /**
11056          * Get the custom size preset for the display of this notification out of the available
11057          * presets found in {@link android.app.Notification.WearableExtender}, e.g.
11058          * {@link #SIZE_LARGE}.
11059          * <p>Some custom size presets are only applicable for custom display notifications created
11060          * using {@link #setDisplayIntent}. Check the documentation for the preset in question.
11061          * See also {@link #setCustomContentHeight} and {@link #setCustomSizePreset}.
11062          */
11063         @Deprecated
getCustomSizePreset()11064         public int getCustomSizePreset() {
11065             return mCustomSizePreset;
11066         }
11067 
11068         /**
11069          * Set the custom height in pixels for the display of this notification's content.
11070          * <p>This option is only available for custom display notifications created
11071          * using {@link android.app.Notification.WearableExtender#setDisplayIntent}. See also
11072          * {@link android.app.Notification.WearableExtender#setCustomSizePreset} and
11073          * {@link #getCustomContentHeight}.
11074          */
11075         @Deprecated
setCustomContentHeight(int height)11076         public WearableExtender setCustomContentHeight(int height) {
11077             mCustomContentHeight = height;
11078             return this;
11079         }
11080 
11081         /**
11082          * Get the custom height in pixels for the display of this notification's content.
11083          * <p>This option is only available for custom display notifications created
11084          * using {@link #setDisplayIntent}. See also {@link #setCustomSizePreset} and
11085          * {@link #setCustomContentHeight}.
11086          */
11087         @Deprecated
getCustomContentHeight()11088         public int getCustomContentHeight() {
11089             return mCustomContentHeight;
11090         }
11091 
11092         /**
11093          * Set whether the scrolling position for the contents of this notification should start
11094          * at the bottom of the contents instead of the top when the contents are too long to
11095          * display within the screen.  Default is false (start scroll at the top).
11096          */
setStartScrollBottom(boolean startScrollBottom)11097         public WearableExtender setStartScrollBottom(boolean startScrollBottom) {
11098             setFlag(FLAG_START_SCROLL_BOTTOM, startScrollBottom);
11099             return this;
11100         }
11101 
11102         /**
11103          * Get whether the scrolling position for the contents of this notification should start
11104          * at the bottom of the contents instead of the top when the contents are too long to
11105          * display within the screen. Default is false (start scroll at the top).
11106          */
getStartScrollBottom()11107         public boolean getStartScrollBottom() {
11108             return (mFlags & FLAG_START_SCROLL_BOTTOM) != 0;
11109         }
11110 
11111         /**
11112          * Set whether the content intent is available when the wearable device is not connected
11113          * to a companion device.  The user can still trigger this intent when the wearable device
11114          * is offline, but a visual hint will indicate that the content intent may not be available.
11115          * Defaults to true.
11116          */
setContentIntentAvailableOffline( boolean contentIntentAvailableOffline)11117         public WearableExtender setContentIntentAvailableOffline(
11118                 boolean contentIntentAvailableOffline) {
11119             setFlag(FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE, contentIntentAvailableOffline);
11120             return this;
11121         }
11122 
11123         /**
11124          * Get whether the content intent is available when the wearable device is not connected
11125          * to a companion device.  The user can still trigger this intent when the wearable device
11126          * is offline, but a visual hint will indicate that the content intent may not be available.
11127          * Defaults to true.
11128          */
getContentIntentAvailableOffline()11129         public boolean getContentIntentAvailableOffline() {
11130             return (mFlags & FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE) != 0;
11131         }
11132 
11133         /**
11134          * Set a hint that this notification's icon should not be displayed.
11135          * @param hintHideIcon {@code true} to hide the icon, {@code false} otherwise.
11136          * @return this object for method chaining
11137          */
11138         @Deprecated
setHintHideIcon(boolean hintHideIcon)11139         public WearableExtender setHintHideIcon(boolean hintHideIcon) {
11140             setFlag(FLAG_HINT_HIDE_ICON, hintHideIcon);
11141             return this;
11142         }
11143 
11144         /**
11145          * Get a hint that this notification's icon should not be displayed.
11146          * @return {@code true} if this icon should not be displayed, false otherwise.
11147          * The default value is {@code false} if this was never set.
11148          */
11149         @Deprecated
getHintHideIcon()11150         public boolean getHintHideIcon() {
11151             return (mFlags & FLAG_HINT_HIDE_ICON) != 0;
11152         }
11153 
11154         /**
11155          * Set a visual hint that only the background image of this notification should be
11156          * displayed, and other semantic content should be hidden. This hint is only applicable
11157          * to sub-pages added using {@link #addPage}.
11158          */
11159         @Deprecated
setHintShowBackgroundOnly(boolean hintShowBackgroundOnly)11160         public WearableExtender setHintShowBackgroundOnly(boolean hintShowBackgroundOnly) {
11161             setFlag(FLAG_HINT_SHOW_BACKGROUND_ONLY, hintShowBackgroundOnly);
11162             return this;
11163         }
11164 
11165         /**
11166          * Get a visual hint that only the background image of this notification should be
11167          * displayed, and other semantic content should be hidden. This hint is only applicable
11168          * to sub-pages added using {@link android.app.Notification.WearableExtender#addPage}.
11169          */
11170         @Deprecated
getHintShowBackgroundOnly()11171         public boolean getHintShowBackgroundOnly() {
11172             return (mFlags & FLAG_HINT_SHOW_BACKGROUND_ONLY) != 0;
11173         }
11174 
11175         /**
11176          * Set a hint that this notification's background should not be clipped if possible,
11177          * and should instead be resized to fully display on the screen, retaining the aspect
11178          * ratio of the image. This can be useful for images like barcodes or qr codes.
11179          * @param hintAvoidBackgroundClipping {@code true} to avoid clipping if possible.
11180          * @return this object for method chaining
11181          */
11182         @Deprecated
setHintAvoidBackgroundClipping( boolean hintAvoidBackgroundClipping)11183         public WearableExtender setHintAvoidBackgroundClipping(
11184                 boolean hintAvoidBackgroundClipping) {
11185             setFlag(FLAG_HINT_AVOID_BACKGROUND_CLIPPING, hintAvoidBackgroundClipping);
11186             return this;
11187         }
11188 
11189         /**
11190          * Get a hint that this notification's background should not be clipped if possible,
11191          * and should instead be resized to fully display on the screen, retaining the aspect
11192          * ratio of the image. This can be useful for images like barcodes or qr codes.
11193          * @return {@code true} if it's ok if the background is clipped on the screen, false
11194          * otherwise. The default value is {@code false} if this was never set.
11195          */
11196         @Deprecated
getHintAvoidBackgroundClipping()11197         public boolean getHintAvoidBackgroundClipping() {
11198             return (mFlags & FLAG_HINT_AVOID_BACKGROUND_CLIPPING) != 0;
11199         }
11200 
11201         /**
11202          * Set a hint that the screen should remain on for at least this duration when
11203          * this notification is displayed on the screen.
11204          * @param timeout The requested screen timeout in milliseconds. Can also be either
11205          *     {@link #SCREEN_TIMEOUT_SHORT} or {@link #SCREEN_TIMEOUT_LONG}.
11206          * @return this object for method chaining
11207          */
11208         @Deprecated
setHintScreenTimeout(int timeout)11209         public WearableExtender setHintScreenTimeout(int timeout) {
11210             mHintScreenTimeout = timeout;
11211             return this;
11212         }
11213 
11214         /**
11215          * Get the duration, in milliseconds, that the screen should remain on for
11216          * when this notification is displayed.
11217          * @return the duration in milliseconds if > 0, or either one of the sentinel values
11218          *     {@link #SCREEN_TIMEOUT_SHORT} or {@link #SCREEN_TIMEOUT_LONG}.
11219          */
11220         @Deprecated
getHintScreenTimeout()11221         public int getHintScreenTimeout() {
11222             return mHintScreenTimeout;
11223         }
11224 
11225         /**
11226          * Set a hint that this notification's {@link BigPictureStyle} (if present) should be
11227          * converted to low-bit and displayed in ambient mode, especially useful for barcodes and
11228          * qr codes, as well as other simple black-and-white tickets.
11229          * @param hintAmbientBigPicture {@code true} to enable converstion and ambient.
11230          * @return this object for method chaining
11231          * @deprecated This feature is no longer supported.
11232          */
11233         @Deprecated
setHintAmbientBigPicture(boolean hintAmbientBigPicture)11234         public WearableExtender setHintAmbientBigPicture(boolean hintAmbientBigPicture) {
11235             setFlag(FLAG_BIG_PICTURE_AMBIENT, hintAmbientBigPicture);
11236             return this;
11237         }
11238 
11239         /**
11240          * Get a hint that this notification's {@link BigPictureStyle} (if present) should be
11241          * converted to low-bit and displayed in ambient mode, especially useful for barcodes and
11242          * qr codes, as well as other simple black-and-white tickets.
11243          * @return {@code true} if it should be displayed in ambient, false otherwise
11244          * otherwise. The default value is {@code false} if this was never set.
11245          * @deprecated This feature is no longer supported.
11246          */
11247         @Deprecated
getHintAmbientBigPicture()11248         public boolean getHintAmbientBigPicture() {
11249             return (mFlags & FLAG_BIG_PICTURE_AMBIENT) != 0;
11250         }
11251 
11252         /**
11253          * Set a hint that this notification's content intent will launch an {@link Activity}
11254          * directly, telling the platform that it can generate the appropriate transitions.
11255          * @param hintContentIntentLaunchesActivity {@code true} if the content intent will launch
11256          * an activity and transitions should be generated, false otherwise.
11257          * @return this object for method chaining
11258          */
setHintContentIntentLaunchesActivity( boolean hintContentIntentLaunchesActivity)11259         public WearableExtender setHintContentIntentLaunchesActivity(
11260                 boolean hintContentIntentLaunchesActivity) {
11261             setFlag(FLAG_HINT_CONTENT_INTENT_LAUNCHES_ACTIVITY, hintContentIntentLaunchesActivity);
11262             return this;
11263         }
11264 
11265         /**
11266          * Get a hint that this notification's content intent will launch an {@link Activity}
11267          * directly, telling the platform that it can generate the appropriate transitions
11268          * @return {@code true} if the content intent will launch an activity and transitions should
11269          * be generated, false otherwise. The default value is {@code false} if this was never set.
11270          */
getHintContentIntentLaunchesActivity()11271         public boolean getHintContentIntentLaunchesActivity() {
11272             return (mFlags & FLAG_HINT_CONTENT_INTENT_LAUNCHES_ACTIVITY) != 0;
11273         }
11274 
11275         /**
11276          * Sets the dismissal id for this notification. If a notification is posted with a
11277          * dismissal id, then when that notification is canceled, notifications on other wearables
11278          * and the paired Android phone having that same dismissal id will also be canceled. See
11279          * <a href="{@docRoot}wear/notifications/index.html">Adding Wearable Features to
11280          * Notifications</a> for more information.
11281          * @param dismissalId the dismissal id of the notification.
11282          * @return this object for method chaining
11283          */
setDismissalId(String dismissalId)11284         public WearableExtender setDismissalId(String dismissalId) {
11285             mDismissalId = dismissalId;
11286             return this;
11287         }
11288 
11289         /**
11290          * Returns the dismissal id of the notification.
11291          * @return the dismissal id of the notification or null if it has not been set.
11292          */
getDismissalId()11293         public String getDismissalId() {
11294             return mDismissalId;
11295         }
11296 
11297         /**
11298          * Sets a bridge tag for this notification. A bridge tag can be set for notifications
11299          * posted from a phone to provide finer-grained control on what notifications are bridged
11300          * to wearables. See <a href="{@docRoot}wear/notifications/index.html">Adding Wearable
11301          * Features to Notifications</a> for more information.
11302          * @param bridgeTag the bridge tag of the notification.
11303          * @return this object for method chaining
11304          */
setBridgeTag(String bridgeTag)11305         public WearableExtender setBridgeTag(String bridgeTag) {
11306             mBridgeTag = bridgeTag;
11307             return this;
11308         }
11309 
11310         /**
11311          * Returns the bridge tag of the notification.
11312          * @return the bridge tag or null if not present.
11313          */
getBridgeTag()11314         public String getBridgeTag() {
11315             return mBridgeTag;
11316         }
11317 
setFlag(int mask, boolean value)11318         private void setFlag(int mask, boolean value) {
11319             if (value) {
11320                 mFlags |= mask;
11321             } else {
11322                 mFlags &= ~mask;
11323             }
11324         }
11325     }
11326 
11327     /**
11328      * <p>Helper class to add Android Auto extensions to notifications. To create a notification
11329      * with car extensions:
11330      *
11331      * <ol>
11332      *  <li>Create an {@link Notification.Builder}, setting any desired
11333      *  properties.
11334      *  <li>Create a {@link CarExtender}.
11335      *  <li>Set car-specific properties using the {@code add} and {@code set} methods of
11336      *  {@link CarExtender}.
11337      *  <li>Call {@link Notification.Builder#extend(Notification.Extender)}
11338      *  to apply the extensions to a notification.
11339      * </ol>
11340      *
11341      * <pre class="prettyprint">
11342      * Notification notification = new Notification.Builder(context)
11343      *         ...
11344      *         .extend(new CarExtender()
11345      *                 .set*(...))
11346      *         .build();
11347      * </pre>
11348      *
11349      * <p>Car extensions can be accessed on an existing notification by using the
11350      * {@code CarExtender(Notification)} constructor, and then using the {@code get} methods
11351      * to access values.
11352      */
11353     public static final class CarExtender implements Extender {
11354         private static final String TAG = "CarExtender";
11355 
11356         private static final String EXTRA_CAR_EXTENDER = "android.car.EXTENSIONS";
11357         private static final String EXTRA_LARGE_ICON = "large_icon";
11358         private static final String EXTRA_CONVERSATION = "car_conversation";
11359         private static final String EXTRA_COLOR = "app_color";
11360 
11361         private Bitmap mLargeIcon;
11362         private UnreadConversation mUnreadConversation;
11363         private int mColor = Notification.COLOR_DEFAULT;
11364 
11365         /**
11366          * Create a {@link CarExtender} with default options.
11367          */
CarExtender()11368         public CarExtender() {
11369         }
11370 
11371         /**
11372          * Create a {@link CarExtender} from the CarExtender options of an existing Notification.
11373          *
11374          * @param notif The notification from which to copy options.
11375          */
CarExtender(Notification notif)11376         public CarExtender(Notification notif) {
11377             Bundle carBundle = notif.extras == null ?
11378                     null : notif.extras.getBundle(EXTRA_CAR_EXTENDER);
11379             if (carBundle != null) {
11380                 mLargeIcon = carBundle.getParcelable(EXTRA_LARGE_ICON);
11381                 mColor = carBundle.getInt(EXTRA_COLOR, Notification.COLOR_DEFAULT);
11382 
11383                 Bundle b = carBundle.getBundle(EXTRA_CONVERSATION);
11384                 mUnreadConversation = UnreadConversation.getUnreadConversationFromBundle(b);
11385             }
11386         }
11387 
11388         /**
11389          * Apply car extensions to a notification that is being built. This is typically called by
11390          * the {@link Notification.Builder#extend(Notification.Extender)}
11391          * method of {@link Notification.Builder}.
11392          */
11393         @Override
extend(Notification.Builder builder)11394         public Notification.Builder extend(Notification.Builder builder) {
11395             Bundle carExtensions = new Bundle();
11396 
11397             if (mLargeIcon != null) {
11398                 carExtensions.putParcelable(EXTRA_LARGE_ICON, mLargeIcon);
11399             }
11400             if (mColor != Notification.COLOR_DEFAULT) {
11401                 carExtensions.putInt(EXTRA_COLOR, mColor);
11402             }
11403 
11404             if (mUnreadConversation != null) {
11405                 Bundle b = mUnreadConversation.getBundleForUnreadConversation();
11406                 carExtensions.putBundle(EXTRA_CONVERSATION, b);
11407             }
11408 
11409             builder.getExtras().putBundle(EXTRA_CAR_EXTENDER, carExtensions);
11410             return builder;
11411         }
11412 
11413         /**
11414          * Sets the accent color to use when Android Auto presents the notification.
11415          *
11416          * Android Auto uses the color set with {@link Notification.Builder#setColor(int)}
11417          * to accent the displayed notification. However, not all colors are acceptable in an
11418          * automotive setting. This method can be used to override the color provided in the
11419          * notification in such a situation.
11420          */
setColor(@olorInt int color)11421         public CarExtender setColor(@ColorInt int color) {
11422             mColor = color;
11423             return this;
11424         }
11425 
11426         /**
11427          * Gets the accent color.
11428          *
11429          * @see #setColor
11430          */
11431         @ColorInt
getColor()11432         public int getColor() {
11433             return mColor;
11434         }
11435 
11436         /**
11437          * Sets the large icon of the car notification.
11438          *
11439          * If no large icon is set in the extender, Android Auto will display the icon
11440          * specified by {@link Notification.Builder#setLargeIcon(android.graphics.Bitmap)}
11441          *
11442          * @param largeIcon The large icon to use in the car notification.
11443          * @return This object for method chaining.
11444          */
setLargeIcon(Bitmap largeIcon)11445         public CarExtender setLargeIcon(Bitmap largeIcon) {
11446             mLargeIcon = largeIcon;
11447             return this;
11448         }
11449 
11450         /**
11451          * Gets the large icon used in this car notification, or null if no icon has been set.
11452          *
11453          * @return The large icon for the car notification.
11454          * @see CarExtender#setLargeIcon
11455          */
getLargeIcon()11456         public Bitmap getLargeIcon() {
11457             return mLargeIcon;
11458         }
11459 
11460         /**
11461          * Sets the unread conversation in a message notification.
11462          *
11463          * @param unreadConversation The unread part of the conversation this notification conveys.
11464          * @return This object for method chaining.
11465          */
setUnreadConversation(UnreadConversation unreadConversation)11466         public CarExtender setUnreadConversation(UnreadConversation unreadConversation) {
11467             mUnreadConversation = unreadConversation;
11468             return this;
11469         }
11470 
11471         /**
11472          * Returns the unread conversation conveyed by this notification.
11473          * @see #setUnreadConversation(UnreadConversation)
11474          */
getUnreadConversation()11475         public UnreadConversation getUnreadConversation() {
11476             return mUnreadConversation;
11477         }
11478 
11479         /**
11480          * A class which holds the unread messages from a conversation.
11481          */
11482         public static class UnreadConversation {
11483             private static final String KEY_AUTHOR = "author";
11484             private static final String KEY_TEXT = "text";
11485             private static final String KEY_MESSAGES = "messages";
11486             private static final String KEY_REMOTE_INPUT = "remote_input";
11487             private static final String KEY_ON_REPLY = "on_reply";
11488             private static final String KEY_ON_READ = "on_read";
11489             private static final String KEY_PARTICIPANTS = "participants";
11490             private static final String KEY_TIMESTAMP = "timestamp";
11491 
11492             private final String[] mMessages;
11493             private final RemoteInput mRemoteInput;
11494             private final PendingIntent mReplyPendingIntent;
11495             private final PendingIntent mReadPendingIntent;
11496             private final String[] mParticipants;
11497             private final long mLatestTimestamp;
11498 
UnreadConversation(String[] messages, RemoteInput remoteInput, PendingIntent replyPendingIntent, PendingIntent readPendingIntent, String[] participants, long latestTimestamp)11499             UnreadConversation(String[] messages, RemoteInput remoteInput,
11500                     PendingIntent replyPendingIntent, PendingIntent readPendingIntent,
11501                     String[] participants, long latestTimestamp) {
11502                 mMessages = messages;
11503                 mRemoteInput = remoteInput;
11504                 mReadPendingIntent = readPendingIntent;
11505                 mReplyPendingIntent = replyPendingIntent;
11506                 mParticipants = participants;
11507                 mLatestTimestamp = latestTimestamp;
11508             }
11509 
11510             /**
11511              * Gets the list of messages conveyed by this notification.
11512              */
getMessages()11513             public String[] getMessages() {
11514                 return mMessages;
11515             }
11516 
11517             /**
11518              * Gets the remote input that will be used to convey the response to a message list, or
11519              * null if no such remote input exists.
11520              */
getRemoteInput()11521             public RemoteInput getRemoteInput() {
11522                 return mRemoteInput;
11523             }
11524 
11525             /**
11526              * Gets the pending intent that will be triggered when the user replies to this
11527              * notification.
11528              */
getReplyPendingIntent()11529             public PendingIntent getReplyPendingIntent() {
11530                 return mReplyPendingIntent;
11531             }
11532 
11533             /**
11534              * Gets the pending intent that Android Auto will send after it reads aloud all messages
11535              * in this object's message list.
11536              */
getReadPendingIntent()11537             public PendingIntent getReadPendingIntent() {
11538                 return mReadPendingIntent;
11539             }
11540 
11541             /**
11542              * Gets the participants in the conversation.
11543              */
getParticipants()11544             public String[] getParticipants() {
11545                 return mParticipants;
11546             }
11547 
11548             /**
11549              * Gets the firs participant in the conversation.
11550              */
getParticipant()11551             public String getParticipant() {
11552                 return mParticipants.length > 0 ? mParticipants[0] : null;
11553             }
11554 
11555             /**
11556              * Gets the timestamp of the conversation.
11557              */
getLatestTimestamp()11558             public long getLatestTimestamp() {
11559                 return mLatestTimestamp;
11560             }
11561 
getBundleForUnreadConversation()11562             Bundle getBundleForUnreadConversation() {
11563                 Bundle b = new Bundle();
11564                 String author = null;
11565                 if (mParticipants != null && mParticipants.length > 1) {
11566                     author = mParticipants[0];
11567                 }
11568                 Parcelable[] messages = new Parcelable[mMessages.length];
11569                 for (int i = 0; i < messages.length; i++) {
11570                     Bundle m = new Bundle();
11571                     m.putString(KEY_TEXT, mMessages[i]);
11572                     m.putString(KEY_AUTHOR, author);
11573                     messages[i] = m;
11574                 }
11575                 b.putParcelableArray(KEY_MESSAGES, messages);
11576                 if (mRemoteInput != null) {
11577                     b.putParcelable(KEY_REMOTE_INPUT, mRemoteInput);
11578                 }
11579                 b.putParcelable(KEY_ON_REPLY, mReplyPendingIntent);
11580                 b.putParcelable(KEY_ON_READ, mReadPendingIntent);
11581                 b.putStringArray(KEY_PARTICIPANTS, mParticipants);
11582                 b.putLong(KEY_TIMESTAMP, mLatestTimestamp);
11583                 return b;
11584             }
11585 
getUnreadConversationFromBundle(Bundle b)11586             static UnreadConversation getUnreadConversationFromBundle(Bundle b) {
11587                 if (b == null) {
11588                     return null;
11589                 }
11590                 Parcelable[] parcelableMessages = b.getParcelableArray(KEY_MESSAGES);
11591                 String[] messages = null;
11592                 if (parcelableMessages != null) {
11593                     String[] tmp = new String[parcelableMessages.length];
11594                     boolean success = true;
11595                     for (int i = 0; i < tmp.length; i++) {
11596                         if (!(parcelableMessages[i] instanceof Bundle)) {
11597                             success = false;
11598                             break;
11599                         }
11600                         tmp[i] = ((Bundle) parcelableMessages[i]).getString(KEY_TEXT);
11601                         if (tmp[i] == null) {
11602                             success = false;
11603                             break;
11604                         }
11605                     }
11606                     if (success) {
11607                         messages = tmp;
11608                     } else {
11609                         return null;
11610                     }
11611                 }
11612 
11613                 PendingIntent onRead = b.getParcelable(KEY_ON_READ);
11614                 PendingIntent onReply = b.getParcelable(KEY_ON_REPLY);
11615 
11616                 RemoteInput remoteInput = b.getParcelable(KEY_REMOTE_INPUT);
11617 
11618                 String[] participants = b.getStringArray(KEY_PARTICIPANTS);
11619                 if (participants == null || participants.length != 1) {
11620                     return null;
11621                 }
11622 
11623                 return new UnreadConversation(messages,
11624                         remoteInput,
11625                         onReply,
11626                         onRead,
11627                         participants, b.getLong(KEY_TIMESTAMP));
11628             }
11629         };
11630 
11631         /**
11632          * Builder class for {@link CarExtender.UnreadConversation} objects.
11633          */
11634         public static class Builder {
11635             private final List<String> mMessages = new ArrayList<String>();
11636             private final String mParticipant;
11637             private RemoteInput mRemoteInput;
11638             private PendingIntent mReadPendingIntent;
11639             private PendingIntent mReplyPendingIntent;
11640             private long mLatestTimestamp;
11641 
11642             /**
11643              * Constructs a new builder for {@link CarExtender.UnreadConversation}.
11644              *
11645              * @param name The name of the other participant in the conversation.
11646              */
Builder(String name)11647             public Builder(String name) {
11648                 mParticipant = name;
11649             }
11650 
11651             /**
11652              * Appends a new unread message to the list of messages for this conversation.
11653              *
11654              * The messages should be added from oldest to newest.
11655              *
11656              * @param message The text of the new unread message.
11657              * @return This object for method chaining.
11658              */
addMessage(String message)11659             public Builder addMessage(String message) {
11660                 mMessages.add(message);
11661                 return this;
11662             }
11663 
11664             /**
11665              * Sets the pending intent and remote input which will convey the reply to this
11666              * notification.
11667              *
11668              * @param pendingIntent The pending intent which will be triggered on a reply.
11669              * @param remoteInput The remote input parcelable which will carry the reply.
11670              * @return This object for method chaining.
11671              *
11672              * @see CarExtender.UnreadConversation#getRemoteInput
11673              * @see CarExtender.UnreadConversation#getReplyPendingIntent
11674              */
setReplyAction( PendingIntent pendingIntent, RemoteInput remoteInput)11675             public Builder setReplyAction(
11676                     PendingIntent pendingIntent, RemoteInput remoteInput) {
11677                 mRemoteInput = remoteInput;
11678                 mReplyPendingIntent = pendingIntent;
11679 
11680                 return this;
11681             }
11682 
11683             /**
11684              * Sets the pending intent that will be sent once the messages in this notification
11685              * are read.
11686              *
11687              * @param pendingIntent The pending intent to use.
11688              * @return This object for method chaining.
11689              */
setReadPendingIntent(PendingIntent pendingIntent)11690             public Builder setReadPendingIntent(PendingIntent pendingIntent) {
11691                 mReadPendingIntent = pendingIntent;
11692                 return this;
11693             }
11694 
11695             /**
11696              * Sets the timestamp of the most recent message in an unread conversation.
11697              *
11698              * If a messaging notification has been posted by your application and has not
11699              * yet been cancelled, posting a later notification with the same id and tag
11700              * but without a newer timestamp may result in Android Auto not displaying a
11701              * heads up notification for the later notification.
11702              *
11703              * @param timestamp The timestamp of the most recent message in the conversation.
11704              * @return This object for method chaining.
11705              */
setLatestTimestamp(long timestamp)11706             public Builder setLatestTimestamp(long timestamp) {
11707                 mLatestTimestamp = timestamp;
11708                 return this;
11709             }
11710 
11711             /**
11712              * Builds a new unread conversation object.
11713              *
11714              * @return The new unread conversation object.
11715              */
build()11716             public UnreadConversation build() {
11717                 String[] messages = mMessages.toArray(new String[mMessages.size()]);
11718                 String[] participants = { mParticipant };
11719                 return new UnreadConversation(messages, mRemoteInput, mReplyPendingIntent,
11720                         mReadPendingIntent, participants, mLatestTimestamp);
11721             }
11722         }
11723     }
11724 
11725     /**
11726      * <p>Helper class to add Android TV extensions to notifications. To create a notification
11727      * with a TV extension:
11728      *
11729      * <ol>
11730      *  <li>Create an {@link Notification.Builder}, setting any desired properties.
11731      *  <li>Create a {@link TvExtender}.
11732      *  <li>Set TV-specific properties using the {@code set} methods of
11733      *  {@link TvExtender}.
11734      *  <li>Call {@link Notification.Builder#extend(Notification.Extender)}
11735      *  to apply the extension to a notification.
11736      * </ol>
11737      *
11738      * <pre class="prettyprint">
11739      * Notification notification = new Notification.Builder(context)
11740      *         ...
11741      *         .extend(new TvExtender()
11742      *                 .set*(...))
11743      *         .build();
11744      * </pre>
11745      *
11746      * <p>TV extensions can be accessed on an existing notification by using the
11747      * {@code TvExtender(Notification)} constructor, and then using the {@code get} methods
11748      * to access values.
11749      *
11750      * @hide
11751      */
11752     @SystemApi
11753     public static final class TvExtender implements Extender {
11754         private static final String TAG = "TvExtender";
11755 
11756         private static final String EXTRA_TV_EXTENDER = "android.tv.EXTENSIONS";
11757         private static final String EXTRA_FLAGS = "flags";
11758         private static final String EXTRA_CONTENT_INTENT = "content_intent";
11759         private static final String EXTRA_DELETE_INTENT = "delete_intent";
11760         private static final String EXTRA_CHANNEL_ID = "channel_id";
11761         private static final String EXTRA_SUPPRESS_SHOW_OVER_APPS = "suppressShowOverApps";
11762 
11763         // Flags bitwise-ored to mFlags
11764         private static final int FLAG_AVAILABLE_ON_TV = 0x1;
11765 
11766         private int mFlags;
11767         private String mChannelId;
11768         private PendingIntent mContentIntent;
11769         private PendingIntent mDeleteIntent;
11770         private boolean mSuppressShowOverApps;
11771 
11772         /**
11773          * Create a {@link TvExtender} with default options.
11774          */
TvExtender()11775         public TvExtender() {
11776             mFlags = FLAG_AVAILABLE_ON_TV;
11777         }
11778 
11779         /**
11780          * Create a {@link TvExtender} from the TvExtender options of an existing Notification.
11781          *
11782          * @param notif The notification from which to copy options.
11783          */
TvExtender(Notification notif)11784         public TvExtender(Notification notif) {
11785             Bundle bundle = notif.extras == null ?
11786                 null : notif.extras.getBundle(EXTRA_TV_EXTENDER);
11787             if (bundle != null) {
11788                 mFlags = bundle.getInt(EXTRA_FLAGS);
11789                 mChannelId = bundle.getString(EXTRA_CHANNEL_ID);
11790                 mSuppressShowOverApps = bundle.getBoolean(EXTRA_SUPPRESS_SHOW_OVER_APPS);
11791                 mContentIntent = bundle.getParcelable(EXTRA_CONTENT_INTENT);
11792                 mDeleteIntent = bundle.getParcelable(EXTRA_DELETE_INTENT);
11793             }
11794         }
11795 
11796         /**
11797          * Apply a TV extension to a notification that is being built. This is typically called by
11798          * the {@link Notification.Builder#extend(Notification.Extender)}
11799          * method of {@link Notification.Builder}.
11800          */
11801         @Override
extend(Notification.Builder builder)11802         public Notification.Builder extend(Notification.Builder builder) {
11803             Bundle bundle = new Bundle();
11804 
11805             bundle.putInt(EXTRA_FLAGS, mFlags);
11806             bundle.putString(EXTRA_CHANNEL_ID, mChannelId);
11807             bundle.putBoolean(EXTRA_SUPPRESS_SHOW_OVER_APPS, mSuppressShowOverApps);
11808             if (mContentIntent != null) {
11809                 bundle.putParcelable(EXTRA_CONTENT_INTENT, mContentIntent);
11810             }
11811 
11812             if (mDeleteIntent != null) {
11813                 bundle.putParcelable(EXTRA_DELETE_INTENT, mDeleteIntent);
11814             }
11815 
11816             builder.getExtras().putBundle(EXTRA_TV_EXTENDER, bundle);
11817             return builder;
11818         }
11819 
11820         /**
11821          * Returns true if this notification should be shown on TV. This method return true
11822          * if the notification was extended with a TvExtender.
11823          */
isAvailableOnTv()11824         public boolean isAvailableOnTv() {
11825             return (mFlags & FLAG_AVAILABLE_ON_TV) != 0;
11826         }
11827 
11828         /**
11829          * Specifies the channel the notification should be delivered on when shown on TV.
11830          * It can be different from the channel that the notification is delivered to when
11831          * posting on a non-TV device.
11832          */
setChannel(String channelId)11833         public TvExtender setChannel(String channelId) {
11834             mChannelId = channelId;
11835             return this;
11836         }
11837 
11838         /**
11839          * Specifies the channel the notification should be delivered on when shown on TV.
11840          * It can be different from the channel that the notification is delivered to when
11841          * posting on a non-TV device.
11842          */
setChannelId(String channelId)11843         public TvExtender setChannelId(String channelId) {
11844             mChannelId = channelId;
11845             return this;
11846         }
11847 
11848         /** @removed */
11849         @Deprecated
getChannel()11850         public String getChannel() {
11851             return mChannelId;
11852         }
11853 
11854         /**
11855          * Returns the id of the channel this notification posts to on TV.
11856          */
getChannelId()11857         public String getChannelId() {
11858             return mChannelId;
11859         }
11860 
11861         /**
11862          * Supplies a {@link PendingIntent} to be sent when the notification is selected on TV.
11863          * If provided, it is used instead of the content intent specified
11864          * at the level of Notification.
11865          */
setContentIntent(PendingIntent intent)11866         public TvExtender setContentIntent(PendingIntent intent) {
11867             mContentIntent = intent;
11868             return this;
11869         }
11870 
11871         /**
11872          * Returns the TV-specific content intent.  If this method returns null, the
11873          * main content intent on the notification should be used.
11874          *
11875          * @see {@link Notification#contentIntent}
11876          */
getContentIntent()11877         public PendingIntent getContentIntent() {
11878             return mContentIntent;
11879         }
11880 
11881         /**
11882          * Supplies a {@link PendingIntent} to send when the notification is cleared explicitly
11883          * by the user on TV.  If provided, it is used instead of the delete intent specified
11884          * at the level of Notification.
11885          */
setDeleteIntent(PendingIntent intent)11886         public TvExtender setDeleteIntent(PendingIntent intent) {
11887             mDeleteIntent = intent;
11888             return this;
11889         }
11890 
11891         /**
11892          * Returns the TV-specific delete intent.  If this method returns null, the
11893          * main delete intent on the notification should be used.
11894          *
11895          * @see {@link Notification#deleteIntent}
11896          */
getDeleteIntent()11897         public PendingIntent getDeleteIntent() {
11898             return mDeleteIntent;
11899         }
11900 
11901         /**
11902          * Specifies whether this notification should suppress showing a message over top of apps
11903          * outside of the launcher.
11904          */
setSuppressShowOverApps(boolean suppress)11905         public TvExtender setSuppressShowOverApps(boolean suppress) {
11906             mSuppressShowOverApps = suppress;
11907             return this;
11908         }
11909 
11910         /**
11911          * Returns true if this notification should not show messages over top of apps
11912          * outside of the launcher.
11913          */
getSuppressShowOverApps()11914         public boolean getSuppressShowOverApps() {
11915             return mSuppressShowOverApps;
11916         }
11917     }
11918 
11919     /**
11920      * Get an array of Parcelable objects from a parcelable array bundle field.
11921      * Update the bundle to have a typed array so fetches in the future don't need
11922      * to do an array copy.
11923      */
11924     @Nullable
getParcelableArrayFromBundle( Bundle bundle, String key, Class<T> itemClass)11925     private static <T extends Parcelable> T[] getParcelableArrayFromBundle(
11926             Bundle bundle, String key, Class<T> itemClass) {
11927         final Parcelable[] array = bundle.getParcelableArray(key);
11928         final Class<?> arrayClass = Array.newInstance(itemClass, 0).getClass();
11929         if (arrayClass.isInstance(array) || array == null) {
11930             return (T[]) array;
11931         }
11932         final T[] typedArray = (T[]) Array.newInstance(itemClass, array.length);
11933         for (int i = 0; i < array.length; i++) {
11934             typedArray[i] = (T) array[i];
11935         }
11936         bundle.putParcelableArray(key, typedArray);
11937         return typedArray;
11938     }
11939 
11940     private static class BuilderRemoteViews extends RemoteViews {
BuilderRemoteViews(Parcel parcel)11941         public BuilderRemoteViews(Parcel parcel) {
11942             super(parcel);
11943         }
11944 
BuilderRemoteViews(ApplicationInfo appInfo, int layoutId)11945         public BuilderRemoteViews(ApplicationInfo appInfo, int layoutId) {
11946             super(appInfo, layoutId);
11947         }
11948 
11949         @Override
clone()11950         public BuilderRemoteViews clone() {
11951             Parcel p = Parcel.obtain();
11952             writeToParcel(p, 0);
11953             p.setDataPosition(0);
11954             BuilderRemoteViews brv = new BuilderRemoteViews(p);
11955             p.recycle();
11956             return brv;
11957         }
11958 
11959         /**
11960          * Override and return true, since {@link RemoteViews#onLoadClass(Class)} is not overridden.
11961          *
11962          * @see RemoteViews#shouldUseStaticFilter()
11963          */
11964         @Override
shouldUseStaticFilter()11965         protected boolean shouldUseStaticFilter() {
11966             return true;
11967         }
11968     }
11969 
11970     /**
11971      * A result object where information about the template that was created is saved.
11972      */
11973     private static class TemplateBindResult {
11974         boolean mRightIconVisible;
11975         float mRightIconWidthDp;
11976         float mRightIconHeightDp;
11977 
11978         /**
11979          * The margin end that needs to be added to the heading so that it won't overlap
11980          * with the large icon.  This value includes the space required to accommodate the large
11981          * icon, but should be added to the space needed to accommodate the expander. This does
11982          * not include the 16dp content margin that all notification views must have.
11983          */
11984         public final MarginSet mHeadingExtraMarginSet = new MarginSet();
11985 
11986         /**
11987          * The margin end that needs to be added to the heading so that it won't overlap
11988          * with the large icon.  This value includes the space required to accommodate the large
11989          * icon as well as the expander.  This does not include the 16dp content margin that all
11990          * notification views must have.
11991          */
11992         public final MarginSet mHeadingFullMarginSet = new MarginSet();
11993 
11994         /**
11995          * The margin end that needs to be added to the title text of the big state
11996          * so that it won't overlap with the large icon, but assuming the text can run under
11997          * the expander when that icon is not visible.
11998          */
11999         public final MarginSet mTitleMarginSet = new MarginSet();
12000 
setRightIconState(boolean visible, float widthDp, float heightDp, float marginEndDpIfVisible, float expanderSizeDp)12001         public void setRightIconState(boolean visible, float widthDp, float heightDp,
12002                 float marginEndDpIfVisible, float expanderSizeDp) {
12003             mRightIconVisible = visible;
12004             mRightIconWidthDp = widthDp;
12005             mRightIconHeightDp = heightDp;
12006             mHeadingExtraMarginSet.setValues(0, marginEndDpIfVisible);
12007             mHeadingFullMarginSet.setValues(expanderSizeDp, marginEndDpIfVisible + expanderSizeDp);
12008             mTitleMarginSet.setValues(0, marginEndDpIfVisible + expanderSizeDp);
12009         }
12010 
12011         /**
12012          * This contains the end margins for a view when the right icon is visible or not.  These
12013          * values are both needed so that NotificationGroupingUtil can 'move' the right_icon to the
12014          * left_icon and adjust the margins, and to undo that change as well.
12015          */
12016         private class MarginSet {
12017             private float mValueIfGone;
12018             private float mValueIfVisible;
12019 
setValues(float valueIfGone, float valueIfVisible)12020             public void setValues(float valueIfGone, float valueIfVisible) {
12021                 mValueIfGone = valueIfGone;
12022                 mValueIfVisible = valueIfVisible;
12023             }
12024 
applyToView(@onNull RemoteViews views, @IdRes int viewId)12025             public void applyToView(@NonNull RemoteViews views, @IdRes int viewId) {
12026                 applyToView(views, viewId, 0);
12027             }
12028 
applyToView(@onNull RemoteViews views, @IdRes int viewId, float extraMarginDp)12029             public void applyToView(@NonNull RemoteViews views, @IdRes int viewId,
12030                     float extraMarginDp) {
12031                 final float marginEndDp = getDpValue() + extraMarginDp;
12032                 if (viewId == R.id.notification_header) {
12033                     views.setFloat(R.id.notification_header,
12034                             "setTopLineExtraMarginEndDp", marginEndDp);
12035                 } else if (viewId == R.id.text || viewId == R.id.big_text) {
12036                     if (mValueIfGone != 0) {
12037                         throw new RuntimeException("Programming error: `text` and `big_text` use "
12038                                 + "ImageFloatingTextView which can either show a margin or not; "
12039                                 + "thus mValueIfGone must be 0, but it was " + mValueIfGone);
12040                     }
12041                     // Note that the caller must set "setNumIndentLines" to a positive int in order
12042                     //  for this margin to do anything at all.
12043                     views.setFloat(viewId, "setImageEndMarginDp", mValueIfVisible);
12044                     views.setBoolean(viewId, "setHasImage", mRightIconVisible);
12045                     // Apply just the *extra* margin as the view layout margin; this will be
12046                     //  unchanged depending on the visibility of the image, but it means that the
12047                     //  extra margin applies to *every* line of text instead of just indented lines.
12048                     views.setViewLayoutMargin(viewId, RemoteViews.MARGIN_END,
12049                             extraMarginDp, TypedValue.COMPLEX_UNIT_DIP);
12050                 } else {
12051                     views.setViewLayoutMargin(viewId, RemoteViews.MARGIN_END,
12052                                     marginEndDp, TypedValue.COMPLEX_UNIT_DIP);
12053                 }
12054                 if (mRightIconVisible) {
12055                     views.setIntTag(viewId, R.id.tag_margin_end_when_icon_visible,
12056                             TypedValue.createComplexDimension(
12057                                     mValueIfVisible + extraMarginDp, TypedValue.COMPLEX_UNIT_DIP));
12058                     views.setIntTag(viewId, R.id.tag_margin_end_when_icon_gone,
12059                             TypedValue.createComplexDimension(
12060                                     mValueIfGone + extraMarginDp, TypedValue.COMPLEX_UNIT_DIP));
12061                 }
12062             }
12063 
getDpValue()12064             public float getDpValue() {
12065                 return mRightIconVisible ? mValueIfVisible : mValueIfGone;
12066             }
12067         }
12068     }
12069 
12070     private static class StandardTemplateParams {
12071         /**
12072          * Notifications will be minimally decorated with ONLY an icon and expander:
12073          * <li>A large icon is never shown.
12074          * <li>A progress bar is never shown.
12075          * <li>The expanded and heads up states do not show actions, even if provided.
12076          */
12077         public static final int DECORATION_MINIMAL = 1;
12078 
12079         /**
12080          * Notifications will be partially decorated with AT LEAST an icon and expander:
12081          * <li>A large icon is shown if provided.
12082          * <li>A progress bar is shown if provided and enough space remains below the content.
12083          * <li>Actions are shown in the expanded and heads up states.
12084          */
12085         public static final int DECORATION_PARTIAL = 2;
12086 
12087         public static int VIEW_TYPE_UNSPECIFIED = 0;
12088         public static int VIEW_TYPE_NORMAL = 1;
12089         public static int VIEW_TYPE_BIG = 2;
12090         public static int VIEW_TYPE_HEADS_UP = 3;
12091         public static int VIEW_TYPE_MINIMIZED = 4;    // header only for minimized state
12092         public static int VIEW_TYPE_PUBLIC = 5;       // header only for automatic public version
12093         public static int VIEW_TYPE_GROUP_HEADER = 6; // header only for top of group
12094 
12095         int mViewType = VIEW_TYPE_UNSPECIFIED;
12096         boolean mHeaderless;
12097         boolean mHideAppName;
12098         boolean mHideTitle;
12099         boolean mHideSubText;
12100         boolean mHideTime;
12101         boolean mHideActions;
12102         boolean mHideProgress;
12103         boolean mHideSnoozeButton;
12104         boolean mHideLeftIcon;
12105         boolean mHideRightIcon;
12106         Icon mPromotedPicture;
12107         boolean mCallStyleActions;
12108         boolean mAllowTextWithProgress;
12109         int mTitleViewId;
12110         int mTextViewId;
12111         CharSequence title;
12112         CharSequence text;
12113         CharSequence headerTextSecondary;
12114         CharSequence summaryText;
12115         int maxRemoteInputHistory = Style.MAX_REMOTE_INPUT_HISTORY_LINES;
12116         boolean allowColorization  = true;
12117         boolean mHighlightExpander = false;
12118 
reset()12119         final StandardTemplateParams reset() {
12120             mViewType = VIEW_TYPE_UNSPECIFIED;
12121             mHeaderless = false;
12122             mHideAppName = false;
12123             mHideTitle = false;
12124             mHideSubText = false;
12125             mHideTime = false;
12126             mHideActions = false;
12127             mHideProgress = false;
12128             mHideSnoozeButton = false;
12129             mHideLeftIcon = false;
12130             mHideRightIcon = false;
12131             mPromotedPicture = null;
12132             mCallStyleActions = false;
12133             mAllowTextWithProgress = false;
12134             mTitleViewId = R.id.title;
12135             mTextViewId = R.id.text;
12136             title = null;
12137             text = null;
12138             summaryText = null;
12139             headerTextSecondary = null;
12140             maxRemoteInputHistory = Style.MAX_REMOTE_INPUT_HISTORY_LINES;
12141             allowColorization = true;
12142             mHighlightExpander = false;
12143             return this;
12144         }
12145 
hasTitle()12146         final boolean hasTitle() {
12147             return !TextUtils.isEmpty(title) && !mHideTitle;
12148         }
12149 
viewType(int viewType)12150         final StandardTemplateParams viewType(int viewType) {
12151             mViewType = viewType;
12152             return this;
12153         }
12154 
headerless(boolean headerless)12155         public StandardTemplateParams headerless(boolean headerless) {
12156             mHeaderless = headerless;
12157             return this;
12158         }
12159 
hideAppName(boolean hideAppName)12160         public StandardTemplateParams hideAppName(boolean hideAppName) {
12161             mHideAppName = hideAppName;
12162             return this;
12163         }
12164 
hideSubText(boolean hideSubText)12165         public StandardTemplateParams hideSubText(boolean hideSubText) {
12166             mHideSubText = hideSubText;
12167             return this;
12168         }
12169 
hideTime(boolean hideTime)12170         public StandardTemplateParams hideTime(boolean hideTime) {
12171             mHideTime = hideTime;
12172             return this;
12173         }
12174 
hideActions(boolean hideActions)12175         final StandardTemplateParams hideActions(boolean hideActions) {
12176             this.mHideActions = hideActions;
12177             return this;
12178         }
12179 
hideProgress(boolean hideProgress)12180         final StandardTemplateParams hideProgress(boolean hideProgress) {
12181             this.mHideProgress = hideProgress;
12182             return this;
12183         }
12184 
hideTitle(boolean hideTitle)12185         final StandardTemplateParams hideTitle(boolean hideTitle) {
12186             this.mHideTitle = hideTitle;
12187             return this;
12188         }
12189 
callStyleActions(boolean callStyleActions)12190         final StandardTemplateParams callStyleActions(boolean callStyleActions) {
12191             this.mCallStyleActions = callStyleActions;
12192             return this;
12193         }
12194 
allowTextWithProgress(boolean allowTextWithProgress)12195         final StandardTemplateParams allowTextWithProgress(boolean allowTextWithProgress) {
12196             this.mAllowTextWithProgress = allowTextWithProgress;
12197             return this;
12198         }
12199 
hideSnoozeButton(boolean hideSnoozeButton)12200         final StandardTemplateParams hideSnoozeButton(boolean hideSnoozeButton) {
12201             this.mHideSnoozeButton = hideSnoozeButton;
12202             return this;
12203         }
12204 
promotedPicture(Icon promotedPicture)12205         final StandardTemplateParams promotedPicture(Icon promotedPicture) {
12206             this.mPromotedPicture = promotedPicture;
12207             return this;
12208         }
12209 
titleViewId(int titleViewId)12210         public StandardTemplateParams titleViewId(int titleViewId) {
12211             mTitleViewId = titleViewId;
12212             return this;
12213         }
12214 
textViewId(int textViewId)12215         public StandardTemplateParams textViewId(int textViewId) {
12216             mTextViewId = textViewId;
12217             return this;
12218         }
12219 
title(CharSequence title)12220         final StandardTemplateParams title(CharSequence title) {
12221             this.title = title;
12222             return this;
12223         }
12224 
text(CharSequence text)12225         final StandardTemplateParams text(CharSequence text) {
12226             this.text = text;
12227             return this;
12228         }
12229 
summaryText(CharSequence text)12230         final StandardTemplateParams summaryText(CharSequence text) {
12231             this.summaryText = text;
12232             return this;
12233         }
12234 
headerTextSecondary(CharSequence text)12235         final StandardTemplateParams headerTextSecondary(CharSequence text) {
12236             this.headerTextSecondary = text;
12237             return this;
12238         }
12239 
12240 
hideLeftIcon(boolean hideLeftIcon)12241         final StandardTemplateParams hideLeftIcon(boolean hideLeftIcon) {
12242             this.mHideLeftIcon = hideLeftIcon;
12243             return this;
12244         }
12245 
hideRightIcon(boolean hideRightIcon)12246         final StandardTemplateParams hideRightIcon(boolean hideRightIcon) {
12247             this.mHideRightIcon = hideRightIcon;
12248             return this;
12249         }
12250 
disallowColorization()12251         final StandardTemplateParams disallowColorization() {
12252             this.allowColorization = false;
12253             return this;
12254         }
12255 
highlightExpander(boolean highlight)12256         final StandardTemplateParams highlightExpander(boolean highlight) {
12257             this.mHighlightExpander = highlight;
12258             return this;
12259         }
12260 
fillTextsFrom(Builder b)12261         final StandardTemplateParams fillTextsFrom(Builder b) {
12262             Bundle extras = b.mN.extras;
12263             this.title = b.processLegacyText(extras.getCharSequence(EXTRA_TITLE));
12264             this.text = b.processLegacyText(extras.getCharSequence(EXTRA_TEXT));
12265             this.summaryText = extras.getCharSequence(EXTRA_SUB_TEXT);
12266             return this;
12267         }
12268 
12269         /**
12270          * Set the maximum lines of remote input history lines allowed.
12271          * @param maxRemoteInputHistory The number of lines.
12272          * @return The builder for method chaining.
12273          */
setMaxRemoteInputHistory(int maxRemoteInputHistory)12274         public StandardTemplateParams setMaxRemoteInputHistory(int maxRemoteInputHistory) {
12275             this.maxRemoteInputHistory = maxRemoteInputHistory;
12276             return this;
12277         }
12278 
decorationType(int decorationType)12279         public StandardTemplateParams decorationType(int decorationType) {
12280             hideTitle(true);
12281             // Minimally decorated custom views do not show certain pieces of chrome that have
12282             // always been shown when using DecoratedCustomViewStyle.
12283             boolean hideOtherFields = decorationType <= DECORATION_MINIMAL;
12284             hideLeftIcon(false);  // The left icon decoration is better than showing nothing.
12285             hideRightIcon(hideOtherFields);
12286             hideProgress(hideOtherFields);
12287             hideActions(hideOtherFields);
12288             return this;
12289         }
12290     }
12291 
12292     /**
12293      * A utility which stores and calculates the palette of colors used to color notifications.
12294      * @hide
12295      */
12296     @VisibleForTesting
12297     public static class Colors {
12298         private int mPaletteIsForRawColor = COLOR_INVALID;
12299         private boolean mPaletteIsForColorized = false;
12300         private boolean mPaletteIsForNightMode = false;
12301         // The following colors are the palette
12302         private int mBackgroundColor = COLOR_INVALID;
12303         private int mProtectionColor = COLOR_INVALID;
12304         private int mPrimaryTextColor = COLOR_INVALID;
12305         private int mSecondaryTextColor = COLOR_INVALID;
12306         private int mPrimaryAccentColor = COLOR_INVALID;
12307         private int mSecondaryAccentColor = COLOR_INVALID;
12308         private int mErrorColor = COLOR_INVALID;
12309         private int mContrastColor = COLOR_INVALID;
12310         private int mRippleAlpha = 0x33;
12311 
12312         /**
12313          * A utility for obtaining a TypedArray of the given DayNight-styled attributes, which
12314          * returns null when the context is a mock with no theme.
12315          *
12316          * NOTE: Calling this method is expensive, as creating a new ContextThemeWrapper
12317          * instances can allocate as much as 5MB of memory, so its important to call this method
12318          * only when necessary, getting as many attributes as possible from each call.
12319          *
12320          * @see Resources.Theme#obtainStyledAttributes(int[])
12321          */
12322         @Nullable
obtainDayNightAttributes(@onNull Context ctx, @NonNull @StyleableRes int[] attrs)12323         private static TypedArray obtainDayNightAttributes(@NonNull Context ctx,
12324                 @NonNull @StyleableRes int[] attrs) {
12325             // when testing, the mock context may have no theme
12326             if (ctx.getTheme() == null) {
12327                 return null;
12328             }
12329             Resources.Theme theme = new ContextThemeWrapper(ctx,
12330                     R.style.Theme_DeviceDefault_DayNight).getTheme();
12331             return theme.obtainStyledAttributes(attrs);
12332         }
12333 
12334         /** A null-safe wrapper of TypedArray.getColor because mocks return null */
getColor(@ullable TypedArray ta, int index, @ColorInt int defValue)12335         private static @ColorInt int getColor(@Nullable TypedArray ta, int index,
12336                 @ColorInt int defValue) {
12337             return ta == null ? defValue : ta.getColor(index, defValue);
12338         }
12339 
12340         /**
12341          * Resolve the palette.  If the inputs have not changed, this will be a no-op.
12342          * This does not handle invalidating the resolved colors when the context itself changes,
12343          * because that case does not happen in the current notification inflation pipeline; we will
12344          * recreate a new builder (and thus a new palette) when reinflating notifications for a new
12345          * theme (admittedly, we do the same for night mode, but that's easy to check).
12346          *
12347          * @param ctx the builder context.
12348          * @param rawColor the notification's color; may be COLOR_DEFAULT, but may never have alpha.
12349          * @param isColorized whether the notification is colorized.
12350          * @param nightMode whether the UI is in night mode.
12351          */
resolvePalette(Context ctx, int rawColor, boolean isColorized, boolean nightMode)12352         public void resolvePalette(Context ctx, int rawColor,
12353                 boolean isColorized, boolean nightMode) {
12354             if (mPaletteIsForRawColor == rawColor
12355                     && mPaletteIsForColorized == isColorized
12356                     && mPaletteIsForNightMode == nightMode) {
12357                 return;
12358             }
12359             mPaletteIsForRawColor = rawColor;
12360             mPaletteIsForColorized = isColorized;
12361             mPaletteIsForNightMode = nightMode;
12362 
12363             if (isColorized) {
12364                 if (rawColor == COLOR_DEFAULT) {
12365                     int[] attrs = {R.attr.colorAccentTertiary};
12366                     try (TypedArray ta = obtainDayNightAttributes(ctx, attrs)) {
12367                         mBackgroundColor = getColor(ta, 0, Color.WHITE);
12368                     }
12369                 } else {
12370                     mBackgroundColor = rawColor;
12371                 }
12372                 mProtectionColor = COLOR_INVALID;  // filled in at the end
12373                 mPrimaryTextColor = ContrastColorUtil.findAlphaToMeetContrast(
12374                         ContrastColorUtil.resolvePrimaryColor(ctx, mBackgroundColor, nightMode),
12375                         mBackgroundColor, 4.5);
12376                 mSecondaryTextColor = ContrastColorUtil.findAlphaToMeetContrast(
12377                         ContrastColorUtil.resolveSecondaryColor(ctx, mBackgroundColor, nightMode),
12378                         mBackgroundColor, 4.5);
12379                 mContrastColor = mPrimaryTextColor;
12380                 mPrimaryAccentColor = mPrimaryTextColor;
12381                 mSecondaryAccentColor = mSecondaryTextColor;
12382                 mErrorColor = mPrimaryTextColor;
12383                 mRippleAlpha = 0x33;
12384             } else {
12385                 int[] attrs = {
12386                         R.attr.colorSurface,
12387                         R.attr.colorBackgroundFloating,
12388                         R.attr.textColorPrimary,
12389                         R.attr.textColorSecondary,
12390                         R.attr.colorAccent,
12391                         R.attr.colorAccentSecondary,
12392                         R.attr.colorError,
12393                         R.attr.colorControlHighlight
12394                 };
12395                 try (TypedArray ta = obtainDayNightAttributes(ctx, attrs)) {
12396                     mBackgroundColor = getColor(ta, 0, nightMode ? Color.BLACK : Color.WHITE);
12397                     mProtectionColor = getColor(ta, 1, COLOR_INVALID);
12398                     mPrimaryTextColor = getColor(ta, 2, COLOR_INVALID);
12399                     mSecondaryTextColor = getColor(ta, 3, COLOR_INVALID);
12400                     mPrimaryAccentColor = getColor(ta, 4, COLOR_INVALID);
12401                     mSecondaryAccentColor = getColor(ta, 5, COLOR_INVALID);
12402                     mErrorColor = getColor(ta, 6, COLOR_INVALID);
12403                     mRippleAlpha = Color.alpha(getColor(ta, 7, 0x33ffffff));
12404                 }
12405                 mContrastColor = calculateContrastColor(ctx, rawColor, mPrimaryAccentColor,
12406                         mBackgroundColor, nightMode);
12407 
12408                 // make sure every color has a valid value
12409                 if (mPrimaryTextColor == COLOR_INVALID) {
12410                     mPrimaryTextColor = ContrastColorUtil.resolvePrimaryColor(
12411                             ctx, mBackgroundColor, nightMode);
12412                 }
12413                 if (mSecondaryTextColor == COLOR_INVALID) {
12414                     mSecondaryTextColor = ContrastColorUtil.resolveSecondaryColor(
12415                             ctx, mBackgroundColor, nightMode);
12416                 }
12417                 if (mPrimaryAccentColor == COLOR_INVALID) {
12418                     mPrimaryAccentColor = mContrastColor;
12419                 }
12420                 if (mSecondaryAccentColor == COLOR_INVALID) {
12421                     mSecondaryAccentColor = mContrastColor;
12422                 }
12423                 if (mErrorColor == COLOR_INVALID) {
12424                     mErrorColor = mPrimaryTextColor;
12425                 }
12426             }
12427             // make sure every color has a valid value
12428             if (mProtectionColor == COLOR_INVALID) {
12429                 mProtectionColor = ColorUtils.blendARGB(mPrimaryTextColor, mBackgroundColor, 0.8f);
12430             }
12431         }
12432 
12433         /** calculates the contrast color for the non-colorized notifications */
calculateContrastColor(Context ctx, @ColorInt int rawColor, @ColorInt int accentColor, @ColorInt int backgroundColor, boolean nightMode)12434         private static @ColorInt int calculateContrastColor(Context ctx, @ColorInt int rawColor,
12435                 @ColorInt int accentColor, @ColorInt int backgroundColor, boolean nightMode) {
12436             int color;
12437             if (rawColor == COLOR_DEFAULT) {
12438                 color = accentColor;
12439                 if (color == COLOR_INVALID) {
12440                     color = ContrastColorUtil.resolveDefaultColor(ctx, backgroundColor, nightMode);
12441                 }
12442             } else {
12443                 color = ContrastColorUtil.resolveContrastColor(ctx, rawColor, backgroundColor,
12444                         nightMode);
12445             }
12446             return flattenAlpha(color, backgroundColor);
12447         }
12448 
12449         /** remove any alpha by manually blending it with the given background. */
flattenAlpha(@olorInt int color, @ColorInt int background)12450         private static @ColorInt int flattenAlpha(@ColorInt int color, @ColorInt int background) {
12451             return Color.alpha(color) == 0xff ? color
12452                     : ContrastColorUtil.compositeColors(color, background);
12453         }
12454 
12455         /** @return the notification's background color */
getBackgroundColor()12456         public @ColorInt int getBackgroundColor() {
12457             return mBackgroundColor;
12458         }
12459 
12460         /**
12461          * @return the "surface protection" color from the theme,
12462          * or a variant of the normal background color when colorized.
12463          */
getProtectionColor()12464         public @ColorInt int getProtectionColor() {
12465             return mProtectionColor;
12466         }
12467 
12468         /** @return the color for the most prominent text */
getPrimaryTextColor()12469         public @ColorInt int getPrimaryTextColor() {
12470             return mPrimaryTextColor;
12471         }
12472 
12473         /** @return the color for less prominent text */
getSecondaryTextColor()12474         public @ColorInt int getSecondaryTextColor() {
12475             return mSecondaryTextColor;
12476         }
12477 
12478         /** @return the theme's accent color for colored UI elements. */
getPrimaryAccentColor()12479         public @ColorInt int getPrimaryAccentColor() {
12480             return mPrimaryAccentColor;
12481         }
12482 
12483         /** @return the theme's secondary accent color for colored UI elements. */
getSecondaryAccentColor()12484         public @ColorInt int getSecondaryAccentColor() {
12485             return mSecondaryAccentColor;
12486         }
12487 
12488         /**
12489          * @return the contrast-adjusted version of the color provided by the app, or the
12490          * primary text color when colorized.
12491          */
getContrastColor()12492         public @ColorInt int getContrastColor() {
12493             return mContrastColor;
12494         }
12495 
12496         /** @return the theme's error color, or the primary text color when colorized. */
getErrorColor()12497         public @ColorInt int getErrorColor() {
12498             return mErrorColor;
12499         }
12500 
12501         /** @return the alpha component of the current theme's control highlight color. */
getRippleAlpha()12502         public int getRippleAlpha() {
12503             return mRippleAlpha;
12504         }
12505     }
12506 }
12507