• 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 com.android.internal.util.ContrastColorUtil.satisfiesTextContrast;
24 
25 import android.annotation.ColorInt;
26 import android.annotation.DimenRes;
27 import android.annotation.Dimension;
28 import android.annotation.DrawableRes;
29 import android.annotation.IdRes;
30 import android.annotation.IntDef;
31 import android.annotation.NonNull;
32 import android.annotation.Nullable;
33 import android.annotation.RequiresPermission;
34 import android.annotation.SdkConstant;
35 import android.annotation.SdkConstant.SdkConstantType;
36 import android.annotation.SuppressLint;
37 import android.annotation.SystemApi;
38 import android.compat.annotation.UnsupportedAppUsage;
39 import android.content.Context;
40 import android.content.Intent;
41 import android.content.LocusId;
42 import android.content.pm.ApplicationInfo;
43 import android.content.pm.PackageManager;
44 import android.content.pm.PackageManager.NameNotFoundException;
45 import android.content.pm.ShortcutInfo;
46 import android.content.res.ColorStateList;
47 import android.content.res.Configuration;
48 import android.content.res.Resources;
49 import android.content.res.TypedArray;
50 import android.graphics.Bitmap;
51 import android.graphics.Canvas;
52 import android.graphics.Color;
53 import android.graphics.PorterDuff;
54 import android.graphics.drawable.Drawable;
55 import android.graphics.drawable.Icon;
56 import android.media.AudioAttributes;
57 import android.media.AudioManager;
58 import android.media.PlayerBase;
59 import android.media.session.MediaSession;
60 import android.net.Uri;
61 import android.os.BadParcelableException;
62 import android.os.Build;
63 import android.os.Bundle;
64 import android.os.IBinder;
65 import android.os.Parcel;
66 import android.os.Parcelable;
67 import android.os.SystemClock;
68 import android.os.SystemProperties;
69 import android.os.UserHandle;
70 import android.text.BidiFormatter;
71 import android.text.SpannableStringBuilder;
72 import android.text.Spanned;
73 import android.text.TextUtils;
74 import android.text.style.AbsoluteSizeSpan;
75 import android.text.style.CharacterStyle;
76 import android.text.style.ForegroundColorSpan;
77 import android.text.style.RelativeSizeSpan;
78 import android.text.style.TextAppearanceSpan;
79 import android.util.ArraySet;
80 import android.util.Log;
81 import android.util.Pair;
82 import android.util.SparseArray;
83 import android.util.proto.ProtoOutputStream;
84 import android.view.Gravity;
85 import android.view.NotificationHeaderView;
86 import android.view.View;
87 import android.view.ViewGroup;
88 import android.view.contentcapture.ContentCaptureContext;
89 import android.widget.ProgressBar;
90 import android.widget.RemoteViews;
91 
92 import com.android.internal.R;
93 import com.android.internal.annotations.VisibleForTesting;
94 import com.android.internal.util.ArrayUtils;
95 import com.android.internal.util.ContrastColorUtil;
96 
97 import java.lang.annotation.Retention;
98 import java.lang.annotation.RetentionPolicy;
99 import java.lang.reflect.Array;
100 import java.lang.reflect.Constructor;
101 import java.util.ArrayList;
102 import java.util.Collections;
103 import java.util.List;
104 import java.util.Objects;
105 import java.util.Set;
106 import java.util.function.Consumer;
107 
108 /**
109  * A class that represents how a persistent notification is to be presented to
110  * the user using the {@link android.app.NotificationManager}.
111  *
112  * <p>The {@link Notification.Builder Notification.Builder} has been added to make it
113  * easier to construct Notifications.</p>
114  *
115  * <div class="special reference">
116  * <h3>Developer Guides</h3>
117  * <p>For a guide to creating notifications, read the
118  * <a href="{@docRoot}guide/topics/ui/notifiers/notifications.html">Status Bar Notifications</a>
119  * developer guide.</p>
120  * </div>
121  */
122 public class Notification implements Parcelable
123 {
124     private static final String TAG = "Notification";
125 
126     /**
127      * An activity that provides a user interface for adjusting notification preferences for its
128      * containing application.
129      */
130     @SdkConstant(SdkConstantType.INTENT_CATEGORY)
131     public static final String INTENT_CATEGORY_NOTIFICATION_PREFERENCES
132             = "android.intent.category.NOTIFICATION_PREFERENCES";
133 
134     /**
135      * Optional extra for {@link #INTENT_CATEGORY_NOTIFICATION_PREFERENCES}. If provided, will
136      * contain a {@link NotificationChannel#getId() channel id} that can be used to narrow down
137      * what settings should be shown in the target app.
138      */
139     public static final String EXTRA_CHANNEL_ID = "android.intent.extra.CHANNEL_ID";
140 
141     /**
142      * Optional extra for {@link #INTENT_CATEGORY_NOTIFICATION_PREFERENCES}. If provided, will
143      * contain a {@link NotificationChannelGroup#getId() group id} that can be used to narrow down
144      * what settings should be shown in the target app.
145      */
146     public static final String EXTRA_CHANNEL_GROUP_ID = "android.intent.extra.CHANNEL_GROUP_ID";
147 
148     /**
149      * Optional extra for {@link #INTENT_CATEGORY_NOTIFICATION_PREFERENCES}. If provided, will
150      * contain the tag provided to {@link NotificationManager#notify(String, int, Notification)}
151      * that can be used to narrow down what settings should be shown in the target app.
152      */
153     public static final String EXTRA_NOTIFICATION_TAG = "android.intent.extra.NOTIFICATION_TAG";
154 
155     /**
156      * Optional extra for {@link #INTENT_CATEGORY_NOTIFICATION_PREFERENCES}. If provided, will
157      * contain the id provided to {@link NotificationManager#notify(String, int, Notification)}
158      * that can be used to narrow down what settings should be shown in the target app.
159      */
160     public static final String EXTRA_NOTIFICATION_ID = "android.intent.extra.NOTIFICATION_ID";
161 
162     /**
163      * Use all default values (where applicable).
164      */
165     public static final int DEFAULT_ALL = ~0;
166 
167     /**
168      * Use the default notification sound. This will ignore any given
169      * {@link #sound}.
170      *
171      * <p>
172      * A notification that is noisy is more likely to be presented as a heads-up notification.
173      * </p>
174      *
175      * @see #defaults
176      */
177 
178     public static final int DEFAULT_SOUND = 1;
179 
180     /**
181      * Use the default notification vibrate. This will ignore any given
182      * {@link #vibrate}. Using phone vibration requires the
183      * {@link android.Manifest.permission#VIBRATE VIBRATE} permission.
184      *
185      * <p>
186      * A notification that vibrates is more likely to be presented as a heads-up notification.
187      * </p>
188      *
189      * @see #defaults
190      */
191 
192     public static final int DEFAULT_VIBRATE = 2;
193 
194     /**
195      * Use the default notification lights. This will ignore the
196      * {@link #FLAG_SHOW_LIGHTS} bit, and {@link #ledARGB}, {@link #ledOffMS}, or
197      * {@link #ledOnMS}.
198      *
199      * @see #defaults
200      */
201 
202     public static final int DEFAULT_LIGHTS = 4;
203 
204     /**
205      * Maximum length of CharSequences accepted by Builder and friends.
206      *
207      * <p>
208      * Avoids spamming the system with overly large strings such as full e-mails.
209      */
210     private static final int MAX_CHARSEQUENCE_LENGTH = 1024;
211 
212     /**
213      * Maximum entries of reply text that are accepted by Builder and friends.
214      */
215     private static final int MAX_REPLY_HISTORY = 5;
216 
217     /**
218      * Maximum number of (generic) action buttons in a notification (contextual action buttons are
219      * handled separately).
220      * @hide
221      */
222     public static final int MAX_ACTION_BUTTONS = 3;
223 
224     /**
225      * If the notification contained an unsent draft for a RemoteInput when the user clicked on it,
226      * we're adding the draft as a String extra to the {@link #contentIntent} using this key.
227      *
228      * <p>Apps may use this extra to prepopulate text fields in the app, where the user usually
229      * sends messages.</p>
230      */
231     public static final String EXTRA_REMOTE_INPUT_DRAFT = "android.remoteInputDraft";
232 
233     /**
234      * A timestamp related to this notification, in milliseconds since the epoch.
235      *
236      * Default value: {@link System#currentTimeMillis() Now}.
237      *
238      * Choose a timestamp that will be most relevant to the user. For most finite events, this
239      * corresponds to the time the event happened (or will happen, in the case of events that have
240      * yet to occur but about which the user is being informed). Indefinite events should be
241      * timestamped according to when the activity began.
242      *
243      * Some examples:
244      *
245      * <ul>
246      *   <li>Notification of a new chat message should be stamped when the message was received.</li>
247      *   <li>Notification of an ongoing file download (with a progress bar, for example) should be stamped when the download started.</li>
248      *   <li>Notification of a completed file download should be stamped when the download finished.</li>
249      *   <li>Notification of an upcoming meeting should be stamped with the time the meeting will begin (that is, in the future).</li>
250      *   <li>Notification of an ongoing stopwatch (increasing timer) should be stamped with the watch's start time.
251      *   <li>Notification of an ongoing countdown timer should be stamped with the timer's end time.
252      * </ul>
253      *
254      * For apps targeting {@link android.os.Build.VERSION_CODES#N} and above, this time is not shown
255      * anymore by default and must be opted into by using
256      * {@link android.app.Notification.Builder#setShowWhen(boolean)}
257      */
258     public long when;
259 
260     /**
261      * The creation time of the notification
262      */
263     private long creationTime;
264 
265     /**
266      * The resource id of a drawable to use as the icon in the status bar.
267      *
268      * @deprecated Use {@link Builder#setSmallIcon(Icon)} instead.
269      */
270     @Deprecated
271     @DrawableRes
272     public int icon;
273 
274     /**
275      * If the icon in the status bar is to have more than one level, you can set this.  Otherwise,
276      * leave it at its default value of 0.
277      *
278      * @see android.widget.ImageView#setImageLevel
279      * @see android.graphics.drawable.Drawable#setLevel
280      */
281     public int iconLevel;
282 
283     /**
284      * The number of events that this notification represents. For example, in a new mail
285      * notification, this could be the number of unread messages.
286      *
287      * The system may or may not use this field to modify the appearance of the notification.
288      * Starting with {@link android.os.Build.VERSION_CODES#O}, the number may be displayed as a
289      * badge icon in Launchers that support badging.
290      */
291     public int number = 0;
292 
293     /**
294      * The intent to execute when the expanded status entry is clicked.  If
295      * this is an activity, it must include the
296      * {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK} flag, which requires
297      * that you take care of task management as described in the
298      * <a href="{@docRoot}guide/topics/fundamentals/tasks-and-back-stack.html">Tasks and Back
299      * Stack</a> document.  In particular, make sure to read the notification section
300      * <a href="{@docRoot}guide/topics/ui/notifiers/notifications.html#HandlingNotifications">Handling
301      * Notifications</a> for the correct ways to launch an application from a
302      * notification.
303      */
304     public PendingIntent contentIntent;
305 
306     /**
307      * The intent to execute when the notification is explicitly dismissed by the user, either with
308      * the "Clear All" button or by swiping it away individually.
309      *
310      * This probably shouldn't be launching an activity since several of those will be sent
311      * at the same time.
312      */
313     public PendingIntent deleteIntent;
314 
315     /**
316      * An intent to launch instead of posting the notification to the status bar.
317      *
318      * <p>
319      * The system UI may choose to display a heads-up notification, instead of
320      * launching this intent, while the user is using the device.
321      * </p>
322      *
323      * @see Notification.Builder#setFullScreenIntent
324      */
325     public PendingIntent fullScreenIntent;
326 
327     /**
328      * Text that summarizes this notification for accessibility services.
329      *
330      * As of the L release, this text is no longer shown on screen, but it is still useful to
331      * accessibility services (where it serves as an audible announcement of the notification's
332      * appearance).
333      *
334      * @see #tickerView
335      */
336     public CharSequence tickerText;
337 
338     /**
339      * Formerly, a view showing the {@link #tickerText}.
340      *
341      * No longer displayed in the status bar as of API 21.
342      */
343     @Deprecated
344     public RemoteViews tickerView;
345 
346     /**
347      * The view that will represent this notification in the notification list (which is pulled
348      * down from the status bar).
349      *
350      * As of N, this field may be null. The notification view is determined by the inputs
351      * to {@link Notification.Builder}; a custom RemoteViews can optionally be
352      * supplied with {@link Notification.Builder#setCustomContentView(RemoteViews)}.
353      */
354     @Deprecated
355     public RemoteViews contentView;
356 
357     /**
358      * A large-format version of {@link #contentView}, giving the Notification an
359      * opportunity to show more detail. The system UI may choose to show this
360      * instead of the normal content view at its discretion.
361      *
362      * As of N, this field may be null. The expanded notification view is determined by the
363      * inputs to {@link Notification.Builder}; a custom RemoteViews can optionally be
364      * supplied with {@link Notification.Builder#setCustomBigContentView(RemoteViews)}.
365      */
366     @Deprecated
367     public RemoteViews bigContentView;
368 
369 
370     /**
371      * A medium-format version of {@link #contentView}, providing the Notification an
372      * opportunity to add action buttons to contentView. At its discretion, the system UI may
373      * choose to show this as a heads-up notification, which will pop up so the user can see
374      * it without leaving their current activity.
375      *
376      * As of N, this field may be null. The heads-up notification view is determined by the
377      * inputs to {@link Notification.Builder}; a custom RemoteViews can optionally be
378      * supplied with {@link Notification.Builder#setCustomHeadsUpContentView(RemoteViews)}.
379      */
380     @Deprecated
381     public RemoteViews headsUpContentView;
382 
383     private boolean mUsesStandardHeader;
384 
385     private static final ArraySet<Integer> STANDARD_LAYOUTS = new ArraySet<>();
386     static {
387         STANDARD_LAYOUTS.add(R.layout.notification_template_material_base);
388         STANDARD_LAYOUTS.add(R.layout.notification_template_material_big_base);
389         STANDARD_LAYOUTS.add(R.layout.notification_template_material_big_picture);
390         STANDARD_LAYOUTS.add(R.layout.notification_template_material_big_text);
391         STANDARD_LAYOUTS.add(R.layout.notification_template_material_inbox);
392         STANDARD_LAYOUTS.add(R.layout.notification_template_material_messaging);
393         STANDARD_LAYOUTS.add(R.layout.notification_template_material_conversation);
394         STANDARD_LAYOUTS.add(R.layout.notification_template_material_media);
395         STANDARD_LAYOUTS.add(R.layout.notification_template_material_big_media);
396         STANDARD_LAYOUTS.add(R.layout.notification_template_header);
397     }
398 
399     /**
400      * A large bitmap to be shown in the notification content area.
401      *
402      * @deprecated Use {@link Builder#setLargeIcon(Icon)} instead.
403      */
404     @Deprecated
405     public Bitmap largeIcon;
406 
407     /**
408      * The sound to play.
409      *
410      * <p>
411      * A notification that is noisy is more likely to be presented as a heads-up notification.
412      * </p>
413      *
414      * <p>
415      * To play the default notification sound, see {@link #defaults}.
416      * </p>
417      * @deprecated use {@link NotificationChannel#getSound()}.
418      */
419     @Deprecated
420     public Uri sound;
421 
422     /**
423      * Use this constant as the value for audioStreamType to request that
424      * the default stream type for notifications be used.  Currently the
425      * default stream type is {@link AudioManager#STREAM_NOTIFICATION}.
426      *
427      * @deprecated Use {@link NotificationChannel#getAudioAttributes()} instead.
428      */
429     @Deprecated
430     public static final int STREAM_DEFAULT = -1;
431 
432     /**
433      * The audio stream type to use when playing the sound.
434      * Should be one of the STREAM_ constants from
435      * {@link android.media.AudioManager}.
436      *
437      * @deprecated Use {@link #audioAttributes} instead.
438      */
439     @Deprecated
440     public int audioStreamType = STREAM_DEFAULT;
441 
442     /**
443      * The default value of {@link #audioAttributes}.
444      */
445     public static final AudioAttributes AUDIO_ATTRIBUTES_DEFAULT = new AudioAttributes.Builder()
446             .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
447             .setUsage(AudioAttributes.USAGE_NOTIFICATION)
448             .build();
449 
450     /**
451      * The {@link AudioAttributes audio attributes} to use when playing the sound.
452      *
453      * @deprecated use {@link NotificationChannel#getAudioAttributes()} instead.
454      */
455     @Deprecated
456     public AudioAttributes audioAttributes = AUDIO_ATTRIBUTES_DEFAULT;
457 
458     /**
459      * The pattern with which to vibrate.
460      *
461      * <p>
462      * To vibrate the default pattern, see {@link #defaults}.
463      * </p>
464      *
465      * @see android.os.Vibrator#vibrate(long[],int)
466      * @deprecated use {@link NotificationChannel#getVibrationPattern()}.
467      */
468     @Deprecated
469     public long[] vibrate;
470 
471     /**
472      * The color of the led.  The hardware will do its best approximation.
473      *
474      * @see #FLAG_SHOW_LIGHTS
475      * @see #flags
476      * @deprecated use {@link NotificationChannel#shouldShowLights()}.
477      */
478     @ColorInt
479     @Deprecated
480     public int ledARGB;
481 
482     /**
483      * The number of milliseconds for the LED to be on while it's flashing.
484      * The hardware will do its best approximation.
485      *
486      * @see #FLAG_SHOW_LIGHTS
487      * @see #flags
488      * @deprecated use {@link NotificationChannel#shouldShowLights()}.
489      */
490     @Deprecated
491     public int ledOnMS;
492 
493     /**
494      * The number of milliseconds for the LED to be off while it's flashing.
495      * The hardware will do its best approximation.
496      *
497      * @see #FLAG_SHOW_LIGHTS
498      * @see #flags
499      *
500      * @deprecated use {@link NotificationChannel#shouldShowLights()}.
501      */
502     @Deprecated
503     public int ledOffMS;
504 
505     /**
506      * Specifies which values should be taken from the defaults.
507      * <p>
508      * To set, OR the desired from {@link #DEFAULT_SOUND},
509      * {@link #DEFAULT_VIBRATE}, {@link #DEFAULT_LIGHTS}. For all default
510      * values, use {@link #DEFAULT_ALL}.
511      * </p>
512      *
513      * @deprecated use {@link NotificationChannel#getSound()} and
514      * {@link NotificationChannel#shouldShowLights()} and
515      * {@link NotificationChannel#shouldVibrate()}.
516      */
517     @Deprecated
518     public int defaults;
519 
520     /**
521      * Bit to be bitwise-ored into the {@link #flags} field that should be
522      * set if you want the LED on for this notification.
523      * <ul>
524      * <li>To turn the LED off, pass 0 in the alpha channel for colorARGB
525      *      or 0 for both ledOnMS and ledOffMS.</li>
526      * <li>To turn the LED on, pass 1 for ledOnMS and 0 for ledOffMS.</li>
527      * <li>To flash the LED, pass the number of milliseconds that it should
528      *      be on and off to ledOnMS and ledOffMS.</li>
529      * </ul>
530      * <p>
531      * Since hardware varies, you are not guaranteed that any of the values
532      * you pass are honored exactly.  Use the system defaults if possible
533      * because they will be set to values that work on any given hardware.
534      * <p>
535      * The alpha channel must be set for forward compatibility.
536      *
537      * @deprecated use {@link NotificationChannel#shouldShowLights()}.
538      */
539     @Deprecated
540     public static final int FLAG_SHOW_LIGHTS        = 0x00000001;
541 
542     /**
543      * Bit to be bitwise-ored into the {@link #flags} field that should be
544      * set if this notification is in reference to something that is ongoing,
545      * like a phone call.  It should not be set if this notification is in
546      * reference to something that happened at a particular point in time,
547      * like a missed phone call.
548      */
549     public static final int FLAG_ONGOING_EVENT      = 0x00000002;
550 
551     /**
552      * Bit to be bitwise-ored into the {@link #flags} field that if set,
553      * the audio will be repeated until the notification is
554      * cancelled or the notification window is opened.
555      */
556     public static final int FLAG_INSISTENT          = 0x00000004;
557 
558     /**
559      * Bit to be bitwise-ored into the {@link #flags} field that should be
560      * set if you would only like the sound, vibrate and ticker to be played
561      * if the notification was not already showing.
562      */
563     public static final int FLAG_ONLY_ALERT_ONCE    = 0x00000008;
564 
565     /**
566      * Bit to be bitwise-ored into the {@link #flags} field that should be
567      * set if the notification should be canceled when it is clicked by the
568      * user.
569      */
570     public static final int FLAG_AUTO_CANCEL        = 0x00000010;
571 
572     /**
573      * Bit to be bitwise-ored into the {@link #flags} field that should be
574      * set if the notification should not be canceled when the user clicks
575      * the Clear all button.
576      */
577     public static final int FLAG_NO_CLEAR           = 0x00000020;
578 
579     /**
580      * Bit to be bitwise-ored into the {@link #flags} field that should be
581      * set if this notification represents a currently running service.  This
582      * will normally be set for you by {@link Service#startForeground}.
583      */
584     public static final int FLAG_FOREGROUND_SERVICE = 0x00000040;
585 
586     /**
587      * Obsolete flag indicating high-priority notifications; use the priority field instead.
588      *
589      * @deprecated Use {@link #priority} with a positive value.
590      */
591     @Deprecated
592     public static final int FLAG_HIGH_PRIORITY      = 0x00000080;
593 
594     /**
595      * Bit to be bitswise-ored into the {@link #flags} field that should be
596      * set if this notification is relevant to the current device only
597      * and it is not recommended that it bridge to other devices.
598      */
599     public static final int FLAG_LOCAL_ONLY         = 0x00000100;
600 
601     /**
602      * Bit to be bitswise-ored into the {@link #flags} field that should be
603      * set if this notification is the group summary for a group of notifications.
604      * Grouped notifications may display in a cluster or stack on devices which
605      * support such rendering. Requires a group key also be set using {@link Builder#setGroup}.
606      */
607     public static final int FLAG_GROUP_SUMMARY      = 0x00000200;
608 
609     /**
610      * Bit to be bitswise-ored into the {@link #flags} field that should be
611      * set if this notification is the group summary for an auto-group of notifications.
612      *
613      * @hide
614      */
615     @SystemApi
616     public static final int FLAG_AUTOGROUP_SUMMARY  = 0x00000400;
617 
618     /**
619      * @hide
620      */
621     public static final int FLAG_CAN_COLORIZE = 0x00000800;
622 
623     /**
624      * Bit to be bitswised-ored into the {@link #flags} field that should be
625      * set by the system if this notification is showing as a bubble.
626      *
627      * Applications cannot set this flag directly; they should instead call
628      * {@link Notification.Builder#setBubbleMetadata(BubbleMetadata)} to
629      * request that a notification be displayed as a bubble, and then check
630      * this flag to see whether that request was honored by the system.
631      */
632     public static final int FLAG_BUBBLE = 0x00001000;
633 
634     /** @hide */
635     @IntDef({FLAG_SHOW_LIGHTS, FLAG_ONGOING_EVENT, FLAG_INSISTENT, FLAG_ONLY_ALERT_ONCE,
636             FLAG_AUTO_CANCEL, FLAG_NO_CLEAR, FLAG_FOREGROUND_SERVICE, FLAG_HIGH_PRIORITY,
637             FLAG_LOCAL_ONLY, FLAG_GROUP_SUMMARY, FLAG_AUTOGROUP_SUMMARY, FLAG_BUBBLE})
638     @Retention(RetentionPolicy.SOURCE)
639     public @interface NotificationFlags{};
640 
641     public int flags;
642 
643     /** @hide */
644     @IntDef(prefix = { "PRIORITY_" }, value = {
645             PRIORITY_DEFAULT,
646             PRIORITY_LOW,
647             PRIORITY_MIN,
648             PRIORITY_HIGH,
649             PRIORITY_MAX
650     })
651     @Retention(RetentionPolicy.SOURCE)
652     public @interface Priority {}
653 
654     /**
655      * Default notification {@link #priority}. If your application does not prioritize its own
656      * notifications, use this value for all notifications.
657      *
658      * @deprecated use {@link NotificationManager#IMPORTANCE_DEFAULT} instead.
659      */
660     @Deprecated
661     public static final int PRIORITY_DEFAULT = 0;
662 
663     /**
664      * Lower {@link #priority}, for items that are less important. The UI may choose to show these
665      * items smaller, or at a different position in the list, compared with your app's
666      * {@link #PRIORITY_DEFAULT} items.
667      *
668      * @deprecated use {@link NotificationManager#IMPORTANCE_LOW} instead.
669      */
670     @Deprecated
671     public static final int PRIORITY_LOW = -1;
672 
673     /**
674      * Lowest {@link #priority}; these items might not be shown to the user except under special
675      * circumstances, such as detailed notification logs.
676      *
677      * @deprecated use {@link NotificationManager#IMPORTANCE_MIN} instead.
678      */
679     @Deprecated
680     public static final int PRIORITY_MIN = -2;
681 
682     /**
683      * Higher {@link #priority}, for more important notifications or alerts. The UI may choose to
684      * show these items larger, or at a different position in notification lists, compared with
685      * your app's {@link #PRIORITY_DEFAULT} items.
686      *
687      * @deprecated use {@link NotificationManager#IMPORTANCE_HIGH} instead.
688      */
689     @Deprecated
690     public static final int PRIORITY_HIGH = 1;
691 
692     /**
693      * Highest {@link #priority}, for your application's most important items that require the
694      * user's prompt attention or input.
695      *
696      * @deprecated use {@link NotificationManager#IMPORTANCE_HIGH} instead.
697      */
698     @Deprecated
699     public static final int PRIORITY_MAX = 2;
700 
701     /**
702      * Relative priority for this notification.
703      *
704      * Priority is an indication of how much of the user's valuable attention should be consumed by
705      * this notification. Low-priority notifications may be hidden from the user in certain
706      * situations, while the user might be interrupted for a higher-priority notification. The
707      * system will make a determination about how to interpret this priority when presenting
708      * the notification.
709      *
710      * <p>
711      * A notification that is at least {@link #PRIORITY_HIGH} is more likely to be presented
712      * as a heads-up notification.
713      * </p>
714      *
715      * @deprecated use {@link NotificationChannel#getImportance()} instead.
716      */
717     @Priority
718     @Deprecated
719     public int priority;
720 
721     /**
722      * Accent color (an ARGB integer like the constants in {@link android.graphics.Color})
723      * to be applied by the standard Style templates when presenting this notification.
724      *
725      * The current template design constructs a colorful header image by overlaying the
726      * {@link #icon} image (stenciled in white) atop a field of this color. Alpha components are
727      * ignored.
728      */
729     @ColorInt
730     public int color = COLOR_DEFAULT;
731 
732     /**
733      * Special value of {@link #color} telling the system not to decorate this notification with
734      * any special color but instead use default colors when presenting this notification.
735      */
736     @ColorInt
737     public static final int COLOR_DEFAULT = 0; // AKA Color.TRANSPARENT
738 
739     /**
740      * Special value of {@link #color} used as a place holder for an invalid color.
741      * @hide
742      */
743     @ColorInt
744     public static final int COLOR_INVALID = 1;
745 
746     /**
747      * Sphere of visibility of this notification, which affects how and when the SystemUI reveals
748      * the notification's presence and contents in untrusted situations (namely, on the secure
749      * lockscreen).
750      *
751      * The default level, {@link #VISIBILITY_PRIVATE}, behaves exactly as notifications have always
752      * done on Android: The notification's {@link #icon} and {@link #tickerText} (if available) are
753      * shown in all situations, but the contents are only available if the device is unlocked for
754      * the appropriate user.
755      *
756      * A more permissive policy can be expressed by {@link #VISIBILITY_PUBLIC}; such a notification
757      * can be read even in an "insecure" context (that is, above a secure lockscreen).
758      * To modify the public version of this notification—for example, to redact some portions—see
759      * {@link Builder#setPublicVersion(Notification)}.
760      *
761      * Finally, a notification can be made {@link #VISIBILITY_SECRET}, which will suppress its icon
762      * and ticker until the user has bypassed the lockscreen.
763      */
764     public @Visibility int visibility;
765 
766     /** @hide */
767     @IntDef(prefix = { "VISIBILITY_" }, value = {
768             VISIBILITY_PUBLIC,
769             VISIBILITY_PRIVATE,
770             VISIBILITY_SECRET,
771     })
772     @Retention(RetentionPolicy.SOURCE)
773     public @interface Visibility {}
774 
775     /**
776      * Notification visibility: Show this notification in its entirety on all lockscreens.
777      *
778      * {@see #visibility}
779      */
780     public static final int VISIBILITY_PUBLIC = 1;
781 
782     /**
783      * Notification visibility: Show this notification on all lockscreens, but conceal sensitive or
784      * private information on secure lockscreens.
785      *
786      * {@see #visibility}
787      */
788     public static final int VISIBILITY_PRIVATE = 0;
789 
790     /**
791      * Notification visibility: Do not reveal any part of this notification on a secure lockscreen.
792      *
793      * {@see #visibility}
794      */
795     public static final int VISIBILITY_SECRET = -1;
796 
797     /**
798      * Notification category: incoming call (voice or video) or similar synchronous communication request.
799      */
800     public static final String CATEGORY_CALL = "call";
801 
802     /**
803      * Notification category: map turn-by-turn navigation.
804      */
805     public static final String CATEGORY_NAVIGATION = "navigation";
806 
807     /**
808      * Notification category: incoming direct message (SMS, instant message, etc.).
809      */
810     public static final String CATEGORY_MESSAGE = "msg";
811 
812     /**
813      * Notification category: asynchronous bulk message (email).
814      */
815     public static final String CATEGORY_EMAIL = "email";
816 
817     /**
818      * Notification category: calendar event.
819      */
820     public static final String CATEGORY_EVENT = "event";
821 
822     /**
823      * Notification category: promotion or advertisement.
824      */
825     public static final String CATEGORY_PROMO = "promo";
826 
827     /**
828      * Notification category: alarm or timer.
829      */
830     public static final String CATEGORY_ALARM = "alarm";
831 
832     /**
833      * Notification category: progress of a long-running background operation.
834      */
835     public static final String CATEGORY_PROGRESS = "progress";
836 
837     /**
838      * Notification category: social network or sharing update.
839      */
840     public static final String CATEGORY_SOCIAL = "social";
841 
842     /**
843      * Notification category: error in background operation or authentication status.
844      */
845     public static final String CATEGORY_ERROR = "err";
846 
847     /**
848      * Notification category: media transport control for playback.
849      */
850     public static final String CATEGORY_TRANSPORT = "transport";
851 
852     /**
853      * Notification category: system or device status update.  Reserved for system use.
854      */
855     public static final String CATEGORY_SYSTEM = "sys";
856 
857     /**
858      * Notification category: indication of running background service.
859      */
860     public static final String CATEGORY_SERVICE = "service";
861 
862     /**
863      * Notification category: a specific, timely recommendation for a single thing.
864      * For example, a news app might want to recommend a news story it believes the user will
865      * want to read next.
866      */
867     public static final String CATEGORY_RECOMMENDATION = "recommendation";
868 
869     /**
870      * Notification category: ongoing information about device or contextual status.
871      */
872     public static final String CATEGORY_STATUS = "status";
873 
874     /**
875      * Notification category: user-scheduled reminder.
876      */
877     public static final String CATEGORY_REMINDER = "reminder";
878 
879     /**
880      * Notification category: extreme car emergencies.
881      * @hide
882      */
883     @SystemApi
884     public static final String CATEGORY_CAR_EMERGENCY = "car_emergency";
885 
886     /**
887      * Notification category: car warnings.
888      * @hide
889      */
890     @SystemApi
891     public static final String CATEGORY_CAR_WARNING = "car_warning";
892 
893     /**
894      * Notification category: general car system information.
895      * @hide
896      */
897     @SystemApi
898     public static final String CATEGORY_CAR_INFORMATION = "car_information";
899 
900     /**
901      * One of the predefined notification categories (see the <code>CATEGORY_*</code> constants)
902      * that best describes this Notification.  May be used by the system for ranking and filtering.
903      */
904     public String category;
905 
906     @UnsupportedAppUsage
907     private String mGroupKey;
908 
909     /**
910      * Get the key used to group this notification into a cluster or stack
911      * with other notifications on devices which support such rendering.
912      */
getGroup()913     public String getGroup() {
914         return mGroupKey;
915     }
916 
917     private String mSortKey;
918 
919     /**
920      * Get a sort key that orders this notification among other notifications from the
921      * same package. This can be useful if an external sort was already applied and an app
922      * would like to preserve this. Notifications will be sorted lexicographically using this
923      * value, although providing different priorities in addition to providing sort key may
924      * cause this value to be ignored.
925      *
926      * <p>This sort key can also be used to order members of a notification group. See
927      * {@link Builder#setGroup}.
928      *
929      * @see String#compareTo(String)
930      */
getSortKey()931     public String getSortKey() {
932         return mSortKey;
933     }
934 
935     /**
936      * Additional semantic data to be carried around with this Notification.
937      * <p>
938      * The extras keys defined here are intended to capture the original inputs to {@link Builder}
939      * APIs, and are intended to be used by
940      * {@link android.service.notification.NotificationListenerService} implementations to extract
941      * detailed information from notification objects.
942      */
943     public Bundle extras = new Bundle();
944 
945     /**
946      * All pending intents in the notification as the system needs to be able to access them but
947      * touching the extras bundle in the system process is not safe because the bundle may contain
948      * custom parcelable objects.
949      *
950      * @hide
951      */
952     @UnsupportedAppUsage
953     public ArraySet<PendingIntent> allPendingIntents;
954 
955     /**
956      * Token identifying the notification that is applying doze/bgcheck whitelisting to the
957      * pending intents inside of it, so only those will get the behavior.
958      *
959      * @hide
960      */
961     private IBinder mWhitelistToken;
962 
963     /**
964      * Must be set by a process to start associating tokens with Notification objects
965      * coming in to it.  This is set by NotificationManagerService.
966      *
967      * @hide
968      */
969     static public IBinder processWhitelistToken;
970 
971     /**
972      * {@link #extras} key: this is the title of the notification,
973      * as supplied to {@link Builder#setContentTitle(CharSequence)}.
974      */
975     public static final String EXTRA_TITLE = "android.title";
976 
977     /**
978      * {@link #extras} key: this is the title of the notification when shown in expanded form,
979      * e.g. as supplied to {@link BigTextStyle#setBigContentTitle(CharSequence)}.
980      */
981     public static final String EXTRA_TITLE_BIG = EXTRA_TITLE + ".big";
982 
983     /**
984      * {@link #extras} key: this is the main text payload, as supplied to
985      * {@link Builder#setContentText(CharSequence)}.
986      */
987     public static final String EXTRA_TEXT = "android.text";
988 
989     /**
990      * {@link #extras} key: this is a third line of text, as supplied to
991      * {@link Builder#setSubText(CharSequence)}.
992      */
993     public static final String EXTRA_SUB_TEXT = "android.subText";
994 
995     /**
996      * {@link #extras} key: this is the remote input history, as supplied to
997      * {@link Builder#setRemoteInputHistory(CharSequence[])}.
998      *
999      * Apps can fill this through {@link Builder#setRemoteInputHistory(CharSequence[])}
1000      * with the most recent inputs that have been sent through a {@link RemoteInput} of this
1001      * Notification and are expected to clear it once the it is no longer relevant (e.g. for chat
1002      * notifications once the other party has responded).
1003      *
1004      * The extra with this key is of type CharSequence[] and contains the most recent entry at
1005      * the 0 index, the second most recent at the 1 index, etc.
1006      *
1007      * @see Builder#setRemoteInputHistory(CharSequence[])
1008      */
1009     public static final String EXTRA_REMOTE_INPUT_HISTORY = "android.remoteInputHistory";
1010 
1011 
1012     /**
1013      * {@link #extras} key: this is a remote input history which can include media messages
1014      * in addition to text, as supplied to
1015      * {@link Builder#setRemoteInputHistory(RemoteInputHistoryItem[])} or
1016      * {@link Builder#setRemoteInputHistory(CharSequence[])}.
1017      *
1018      * SystemUI can populate this through
1019      * {@link Builder#setRemoteInputHistory(RemoteInputHistoryItem[])} with the most recent inputs
1020      * that have been sent through a {@link RemoteInput} of this Notification. These items can
1021      * represent either media content (specified by a URI and a MIME type) or a text message
1022      * (described by a CharSequence).
1023      *
1024      * To maintain compatibility, this can also be set by apps with
1025      * {@link Builder#setRemoteInputHistory(CharSequence[])}, which will create a
1026      * {@link RemoteInputHistoryItem} for each of the provided text-only messages.
1027      *
1028      * The extra with this key is of type {@link RemoteInputHistoryItem[]} and contains the most
1029      * recent entry at the 0 index, the second most recent at the 1 index, etc.
1030      *
1031      * @see Builder#setRemoteInputHistory(RemoteInputHistoryItem[])
1032      * @hide
1033      */
1034     public static final String EXTRA_REMOTE_INPUT_HISTORY_ITEMS = "android.remoteInputHistoryItems";
1035 
1036     /**
1037      * {@link #extras} key: boolean as supplied to
1038      * {@link Builder#setShowRemoteInputSpinner(boolean)}.
1039      *
1040      * If set to true, then the view displaying the remote input history from
1041      * {@link Builder#setRemoteInputHistory(CharSequence[])} will have a progress spinner.
1042      *
1043      * @see Builder#setShowRemoteInputSpinner(boolean)
1044      * @hide
1045      */
1046     public static final String EXTRA_SHOW_REMOTE_INPUT_SPINNER = "android.remoteInputSpinner";
1047 
1048     /**
1049      * {@link #extras} key: boolean as supplied to
1050      * {@link Builder#setHideSmartReplies(boolean)}.
1051      *
1052      * If set to true, then any smart reply buttons will be hidden.
1053      *
1054      * @see Builder#setHideSmartReplies(boolean)
1055      * @hide
1056      */
1057     public static final String EXTRA_HIDE_SMART_REPLIES = "android.hideSmartReplies";
1058 
1059     /**
1060      * {@link #extras} key: this is a small piece of additional text as supplied to
1061      * {@link Builder#setContentInfo(CharSequence)}.
1062      */
1063     public static final String EXTRA_INFO_TEXT = "android.infoText";
1064 
1065     /**
1066      * {@link #extras} key: this is a line of summary information intended to be shown
1067      * alongside expanded notifications, as supplied to (e.g.)
1068      * {@link BigTextStyle#setSummaryText(CharSequence)}.
1069      */
1070     public static final String EXTRA_SUMMARY_TEXT = "android.summaryText";
1071 
1072     /**
1073      * {@link #extras} key: this is the longer text shown in the big form of a
1074      * {@link BigTextStyle} notification, as supplied to
1075      * {@link BigTextStyle#bigText(CharSequence)}.
1076      */
1077     public static final String EXTRA_BIG_TEXT = "android.bigText";
1078 
1079     /**
1080      * {@link #extras} key: this is the resource ID of the notification's main small icon, as
1081      * supplied to {@link Builder#setSmallIcon(int)}.
1082      *
1083      * @deprecated Use {@link #getSmallIcon()}, which supports a wider variety of icon sources.
1084      */
1085     @Deprecated
1086     public static final String EXTRA_SMALL_ICON = "android.icon";
1087 
1088     /**
1089      * {@link #extras} key: this is a bitmap to be used instead of the small icon when showing the
1090      * notification payload, as
1091      * supplied to {@link Builder#setLargeIcon(android.graphics.Bitmap)}.
1092      *
1093      * @deprecated Use {@link #getLargeIcon()}, which supports a wider variety of icon sources.
1094      */
1095     @Deprecated
1096     public static final String EXTRA_LARGE_ICON = "android.largeIcon";
1097 
1098     /**
1099      * {@link #extras} key: this is a bitmap to be used instead of the one from
1100      * {@link Builder#setLargeIcon(android.graphics.Bitmap)} when the notification is
1101      * shown in its expanded form, as supplied to
1102      * {@link BigPictureStyle#bigLargeIcon(android.graphics.Bitmap)}.
1103      */
1104     public static final String EXTRA_LARGE_ICON_BIG = EXTRA_LARGE_ICON + ".big";
1105 
1106     /**
1107      * {@link #extras} key: this is the progress value supplied to
1108      * {@link Builder#setProgress(int, int, boolean)}.
1109      */
1110     public static final String EXTRA_PROGRESS = "android.progress";
1111 
1112     /**
1113      * {@link #extras} key: this is the maximum value supplied to
1114      * {@link Builder#setProgress(int, int, boolean)}.
1115      */
1116     public static final String EXTRA_PROGRESS_MAX = "android.progressMax";
1117 
1118     /**
1119      * {@link #extras} key: whether the progress bar is indeterminate, supplied to
1120      * {@link Builder#setProgress(int, int, boolean)}.
1121      */
1122     public static final String EXTRA_PROGRESS_INDETERMINATE = "android.progressIndeterminate";
1123 
1124     /**
1125      * {@link #extras} key: whether {@link #when} should be shown as a count-up timer (specifically
1126      * a {@link android.widget.Chronometer}) instead of a timestamp, as supplied to
1127      * {@link Builder#setUsesChronometer(boolean)}.
1128      */
1129     public static final String EXTRA_SHOW_CHRONOMETER = "android.showChronometer";
1130 
1131     /**
1132      * {@link #extras} key: whether the chronometer set on the notification should count down
1133      * instead of counting up. Is only relevant if key {@link #EXTRA_SHOW_CHRONOMETER} is present.
1134      * This extra is a boolean. The default is false.
1135      */
1136     public static final String EXTRA_CHRONOMETER_COUNT_DOWN = "android.chronometerCountDown";
1137 
1138     /**
1139      * {@link #extras} key: whether {@link #when} should be shown,
1140      * as supplied to {@link Builder#setShowWhen(boolean)}.
1141      */
1142     public static final String EXTRA_SHOW_WHEN = "android.showWhen";
1143 
1144     /**
1145      * {@link #extras} key: this is a bitmap to be shown in {@link BigPictureStyle} expanded
1146      * notifications, supplied to {@link BigPictureStyle#bigPicture(android.graphics.Bitmap)}.
1147      */
1148     public static final String EXTRA_PICTURE = "android.picture";
1149 
1150     /**
1151      * {@link #extras} key: An array of CharSequences to show in {@link InboxStyle} expanded
1152      * notifications, each of which was supplied to {@link InboxStyle#addLine(CharSequence)}.
1153      */
1154     public static final String EXTRA_TEXT_LINES = "android.textLines";
1155 
1156     /**
1157      * {@link #extras} key: A string representing the name of the specific
1158      * {@link android.app.Notification.Style} used to create this notification.
1159      */
1160     public static final String EXTRA_TEMPLATE = "android.template";
1161 
1162     /**
1163      * {@link #extras} key: A String array containing the people that this notification relates to,
1164      * each of which was supplied to {@link Builder#addPerson(String)}.
1165      *
1166      * @deprecated the actual objects are now in {@link #EXTRA_PEOPLE_LIST}
1167      */
1168     public static final String EXTRA_PEOPLE = "android.people";
1169 
1170     /**
1171      * {@link #extras} key: An arrayList of {@link Person} objects containing the people that
1172      * this notification relates to.
1173      */
1174     public static final String EXTRA_PEOPLE_LIST = "android.people.list";
1175 
1176     /**
1177      * Allow certain system-generated notifications to appear before the device is provisioned.
1178      * Only available to notifications coming from the android package.
1179      * @hide
1180      */
1181     @SystemApi
1182     @RequiresPermission(android.Manifest.permission.NOTIFICATION_DURING_SETUP)
1183     public static final String EXTRA_ALLOW_DURING_SETUP = "android.allowDuringSetup";
1184 
1185     /**
1186      * {@link #extras} key:
1187      * flat {@link String} representation of a {@link android.content.ContentUris content URI}
1188      * pointing to an image that can be displayed in the background when the notification is
1189      * selected. Used on television platforms. The URI must point to an image stream suitable for
1190      * passing into {@link android.graphics.BitmapFactory#decodeStream(java.io.InputStream)
1191      * BitmapFactory.decodeStream}; all other content types will be ignored.
1192      */
1193     public static final String EXTRA_BACKGROUND_IMAGE_URI = "android.backgroundImageUri";
1194 
1195     /**
1196      * {@link #extras} key: A
1197      * {@link android.media.session.MediaSession.Token} associated with a
1198      * {@link android.app.Notification.MediaStyle} notification.
1199      */
1200     public static final String EXTRA_MEDIA_SESSION = "android.mediaSession";
1201 
1202     /**
1203      * {@link #extras} key: the indices of actions to be shown in the compact view,
1204      * as supplied to (e.g.) {@link MediaStyle#setShowActionsInCompactView(int...)}.
1205      */
1206     public static final String EXTRA_COMPACT_ACTIONS = "android.compactActions";
1207 
1208     /**
1209      * {@link #extras} key: the username to be displayed for all messages sent by the user including
1210      * direct replies
1211      * {@link android.app.Notification.MessagingStyle} notification. This extra is a
1212      * {@link CharSequence}
1213      *
1214      * @deprecated use {@link #EXTRA_MESSAGING_PERSON}
1215      */
1216     public static final String EXTRA_SELF_DISPLAY_NAME = "android.selfDisplayName";
1217 
1218     /**
1219      * {@link #extras} key: the person to be displayed for all messages sent by the user including
1220      * direct replies
1221      * {@link android.app.Notification.MessagingStyle} notification. This extra is a
1222      * {@link Person}
1223      */
1224     public static final String EXTRA_MESSAGING_PERSON = "android.messagingUser";
1225 
1226     /**
1227      * {@link #extras} key: a {@link CharSequence} to be displayed as the title to a conversation
1228      * represented by a {@link android.app.Notification.MessagingStyle}
1229      */
1230     public static final String EXTRA_CONVERSATION_TITLE = "android.conversationTitle";
1231 
1232     /** @hide */
1233     public static final String EXTRA_CONVERSATION_ICON = "android.conversationIcon";
1234 
1235     /** @hide */
1236     public static final String EXTRA_CONVERSATION_UNREAD_MESSAGE_COUNT =
1237             "android.conversationUnreadMessageCount";
1238 
1239     /**
1240      * {@link #extras} key: an array of {@link android.app.Notification.MessagingStyle.Message}
1241      * bundles provided by a
1242      * {@link android.app.Notification.MessagingStyle} notification. This extra is a parcelable
1243      * array of bundles.
1244      */
1245     public static final String EXTRA_MESSAGES = "android.messages";
1246 
1247     /**
1248      * {@link #extras} key: an array of
1249      * {@link android.app.Notification.MessagingStyle#addHistoricMessage historic}
1250      * {@link android.app.Notification.MessagingStyle.Message} bundles provided by a
1251      * {@link android.app.Notification.MessagingStyle} notification. This extra is a parcelable
1252      * array of bundles.
1253      */
1254     public static final String EXTRA_HISTORIC_MESSAGES = "android.messages.historic";
1255 
1256     /**
1257      * {@link #extras} key: whether the {@link android.app.Notification.MessagingStyle} notification
1258      * represents a group conversation.
1259      */
1260     public static final String EXTRA_IS_GROUP_CONVERSATION = "android.isGroupConversation";
1261 
1262     /**
1263      * {@link #extras} key: whether the notification should be colorized as
1264      * supplied to {@link Builder#setColorized(boolean)}.
1265      */
1266     public static final String EXTRA_COLORIZED = "android.colorized";
1267 
1268     /**
1269      * @hide
1270      */
1271     public static final String EXTRA_BUILDER_APPLICATION_INFO = "android.appInfo";
1272 
1273     /**
1274      * @hide
1275      */
1276     public static final String EXTRA_CONTAINS_CUSTOM_VIEW = "android.contains.customView";
1277 
1278     /**
1279      * @hide
1280      */
1281     public static final String EXTRA_REDUCED_IMAGES = "android.reduced.images";
1282 
1283     /**
1284      * {@link #extras} key: the audio contents of this notification.
1285      *
1286      * This is for use when rendering the notification on an audio-focused interface;
1287      * the audio contents are a complete sound sample that contains the contents/body of the
1288      * notification. This may be used in substitute of a Text-to-Speech reading of the
1289      * notification. For example if the notification represents a voice message this should point
1290      * to the audio of that message.
1291      *
1292      * The data stored under this key should be a String representation of a Uri that contains the
1293      * audio contents in one of the following formats: WAV, PCM 16-bit, AMR-WB.
1294      *
1295      * This extra is unnecessary if you are using {@code MessagingStyle} since each {@code Message}
1296      * has a field for holding data URI. That field can be used for audio.
1297      * See {@code Message#setData}.
1298      *
1299      * Example usage:
1300      * <pre>
1301      * {@code
1302      * Notification.Builder myBuilder = (build your Notification as normal);
1303      * myBuilder.getExtras().putString(EXTRA_AUDIO_CONTENTS_URI, myAudioUri.toString());
1304      * }
1305      * </pre>
1306      */
1307     public static final String EXTRA_AUDIO_CONTENTS_URI = "android.audioContents";
1308 
1309     /** @hide */
1310     @SystemApi
1311     @RequiresPermission(android.Manifest.permission.SUBSTITUTE_NOTIFICATION_APP_NAME)
1312     public static final String EXTRA_SUBSTITUTE_APP_NAME = "android.substName";
1313 
1314     /**
1315      * This is set on the notifications shown by system_server about apps running foreground
1316      * services. It indicates that the notification should be shown
1317      * only if any of the given apps do not already have a properly tagged
1318      * {@link #FLAG_FOREGROUND_SERVICE} notification currently visible to the user.
1319      * This is a string array of all package names of the apps.
1320      * @hide
1321      */
1322     public static final String EXTRA_FOREGROUND_APPS = "android.foregroundApps";
1323 
1324     @UnsupportedAppUsage
1325     private Icon mSmallIcon;
1326     @UnsupportedAppUsage
1327     private Icon mLargeIcon;
1328 
1329     @UnsupportedAppUsage
1330     private String mChannelId;
1331     private long mTimeout;
1332 
1333     private String mShortcutId;
1334     private LocusId mLocusId;
1335     private CharSequence mSettingsText;
1336 
1337     private BubbleMetadata mBubbleMetadata;
1338 
1339     /** @hide */
1340     @IntDef(prefix = { "GROUP_ALERT_" }, value = {
1341             GROUP_ALERT_ALL, GROUP_ALERT_CHILDREN, GROUP_ALERT_SUMMARY
1342     })
1343     @Retention(RetentionPolicy.SOURCE)
1344     public @interface GroupAlertBehavior {}
1345 
1346     /**
1347      * Constant for {@link Builder#setGroupAlertBehavior(int)}, meaning that all notifications in a
1348      * group with sound or vibration ought to make sound or vibrate (respectively), so this
1349      * notification will not be muted when it is in a group.
1350      */
1351     public static final int GROUP_ALERT_ALL = 0;
1352 
1353     /**
1354      * Constant for {@link Builder#setGroupAlertBehavior(int)}, meaning that all children
1355      * notification in a group should be silenced (no sound or vibration) even if they are posted
1356      * to a {@link NotificationChannel} that has sound and/or vibration. Use this constant to
1357      * mute this notification if this notification is a group child. This must be applied to all
1358      * children notifications you want to mute.
1359      *
1360      * <p> For example, you might want to use this constant if you post a number of children
1361      * notifications at once (say, after a periodic sync), and only need to notify the user
1362      * audibly once.
1363      */
1364     public static final int GROUP_ALERT_SUMMARY = 1;
1365 
1366     /**
1367      * Constant for {@link Builder#setGroupAlertBehavior(int)}, meaning that the summary
1368      * notification in a group should be silenced (no sound or vibration) even if they are
1369      * posted to a {@link NotificationChannel} that has sound and/or vibration. Use this constant
1370      * to mute this notification if this notification is a group summary.
1371      *
1372      * <p>For example, you might want to use this constant if only the children notifications
1373      * in your group have content and the summary is only used to visually group notifications
1374      * rather than to alert the user that new information is available.
1375      */
1376     public static final int GROUP_ALERT_CHILDREN = 2;
1377 
1378     private int mGroupAlertBehavior = GROUP_ALERT_ALL;
1379 
1380     /**
1381      * If this notification is being shown as a badge, always show as a number.
1382      */
1383     public static final int BADGE_ICON_NONE = 0;
1384 
1385     /**
1386      * If this notification is being shown as a badge, use the {@link #getSmallIcon()} to
1387      * represent this notification.
1388      */
1389     public static final int BADGE_ICON_SMALL = 1;
1390 
1391     /**
1392      * If this notification is being shown as a badge, use the {@link #getLargeIcon()} to
1393      * represent this notification.
1394      */
1395     public static final int BADGE_ICON_LARGE = 2;
1396     private int mBadgeIcon = BADGE_ICON_NONE;
1397 
1398     /**
1399      * Determines whether the platform can generate contextual actions for a notification.
1400      */
1401     private boolean mAllowSystemGeneratedContextualActions = true;
1402 
1403     /**
1404      * Structure to encapsulate a named action that can be shown as part of this notification.
1405      * It must include an icon, a label, and a {@link PendingIntent} to be fired when the action is
1406      * selected by the user.
1407      * <p>
1408      * Apps should use {@link Notification.Builder#addAction(int, CharSequence, PendingIntent)}
1409      * or {@link Notification.Builder#addAction(Notification.Action)}
1410      * to attach actions.
1411      */
1412     public static class Action implements Parcelable {
1413         /**
1414          * {@link #extras} key: Keys to a {@link Parcelable} {@link ArrayList} of
1415          * {@link RemoteInput}s.
1416          *
1417          * This is intended for {@link RemoteInput}s that only accept data, meaning
1418          * {@link RemoteInput#getAllowFreeFormInput} is false, {@link RemoteInput#getChoices}
1419          * is null or empty, and {@link RemoteInput#getAllowedDataTypes} is non-null and not
1420          * empty. These {@link RemoteInput}s will be ignored by devices that do not
1421          * support non-text-based {@link RemoteInput}s. See {@link Builder#build}.
1422          *
1423          * You can test if a RemoteInput matches these constraints using
1424          * {@link RemoteInput#isDataOnly}.
1425          */
1426         private static final String EXTRA_DATA_ONLY_INPUTS = "android.extra.DATA_ONLY_INPUTS";
1427 
1428         /**
1429          * {@link }: No semantic action defined.
1430          */
1431         public static final int SEMANTIC_ACTION_NONE = 0;
1432 
1433         /**
1434          * {@code SemanticAction}: Reply to a conversation, chat, group, or wherever replies
1435          * may be appropriate.
1436          */
1437         public static final int SEMANTIC_ACTION_REPLY = 1;
1438 
1439         /**
1440          * {@code SemanticAction}: Mark content as read.
1441          */
1442         public static final int SEMANTIC_ACTION_MARK_AS_READ = 2;
1443 
1444         /**
1445          * {@code SemanticAction}: Mark content as unread.
1446          */
1447         public static final int SEMANTIC_ACTION_MARK_AS_UNREAD = 3;
1448 
1449         /**
1450          * {@code SemanticAction}: Delete the content associated with the notification. This
1451          * could mean deleting an email, message, etc.
1452          */
1453         public static final int SEMANTIC_ACTION_DELETE = 4;
1454 
1455         /**
1456          * {@code SemanticAction}: Archive the content associated with the notification. This
1457          * could mean archiving an email, message, etc.
1458          */
1459         public static final int SEMANTIC_ACTION_ARCHIVE = 5;
1460 
1461         /**
1462          * {@code SemanticAction}: Mute the content associated with the notification. This could
1463          * mean silencing a conversation or currently playing media.
1464          */
1465         public static final int SEMANTIC_ACTION_MUTE = 6;
1466 
1467         /**
1468          * {@code SemanticAction}: Unmute the content associated with the notification. This could
1469          * mean un-silencing a conversation or currently playing media.
1470          */
1471         public static final int SEMANTIC_ACTION_UNMUTE = 7;
1472 
1473         /**
1474          * {@code SemanticAction}: Mark content with a thumbs up.
1475          */
1476         public static final int SEMANTIC_ACTION_THUMBS_UP = 8;
1477 
1478         /**
1479          * {@code SemanticAction}: Mark content with a thumbs down.
1480          */
1481         public static final int SEMANTIC_ACTION_THUMBS_DOWN = 9;
1482 
1483         /**
1484          * {@code SemanticAction}: Call a contact, group, etc.
1485          */
1486         public static final int SEMANTIC_ACTION_CALL = 10;
1487 
1488         private final Bundle mExtras;
1489         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
1490         private Icon mIcon;
1491         private final RemoteInput[] mRemoteInputs;
1492         private boolean mAllowGeneratedReplies = true;
1493         private final @SemanticAction int mSemanticAction;
1494         private final boolean mIsContextual;
1495 
1496         /**
1497          * Small icon representing the action.
1498          *
1499          * @deprecated Use {@link Action#getIcon()} instead.
1500          */
1501         @Deprecated
1502         public int icon;
1503 
1504         /**
1505          * Title of the action.
1506          */
1507         public CharSequence title;
1508 
1509         /**
1510          * Intent to send when the user invokes this action. May be null, in which case the action
1511          * may be rendered in a disabled presentation by the system UI.
1512          */
1513         public PendingIntent actionIntent;
1514 
Action(Parcel in)1515         private Action(Parcel in) {
1516             if (in.readInt() != 0) {
1517                 mIcon = Icon.CREATOR.createFromParcel(in);
1518                 if (mIcon.getType() == Icon.TYPE_RESOURCE) {
1519                     icon = mIcon.getResId();
1520                 }
1521             }
1522             title = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
1523             if (in.readInt() == 1) {
1524                 actionIntent = PendingIntent.CREATOR.createFromParcel(in);
1525             }
1526             mExtras = Bundle.setDefusable(in.readBundle(), true);
1527             mRemoteInputs = in.createTypedArray(RemoteInput.CREATOR);
1528             mAllowGeneratedReplies = in.readInt() == 1;
1529             mSemanticAction = in.readInt();
1530             mIsContextual = in.readInt() == 1;
1531         }
1532 
1533         /**
1534          * @deprecated Use {@link android.app.Notification.Action.Builder}.
1535          */
1536         @Deprecated
Action(int icon, CharSequence title, PendingIntent intent)1537         public Action(int icon, CharSequence title, PendingIntent intent) {
1538             this(Icon.createWithResource("", icon), title, intent, new Bundle(), null, true,
1539                     SEMANTIC_ACTION_NONE, false /* isContextual */);
1540         }
1541 
1542         /** 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)1543         private Action(Icon icon, CharSequence title, PendingIntent intent, Bundle extras,
1544                 RemoteInput[] remoteInputs, boolean allowGeneratedReplies,
1545                        @SemanticAction int semanticAction, boolean isContextual) {
1546             this.mIcon = icon;
1547             if (icon != null && icon.getType() == Icon.TYPE_RESOURCE) {
1548                 this.icon = icon.getResId();
1549             }
1550             this.title = title;
1551             this.actionIntent = intent;
1552             this.mExtras = extras != null ? extras : new Bundle();
1553             this.mRemoteInputs = remoteInputs;
1554             this.mAllowGeneratedReplies = allowGeneratedReplies;
1555             this.mSemanticAction = semanticAction;
1556             this.mIsContextual = isContextual;
1557         }
1558 
1559         /**
1560          * Return an icon representing the action.
1561          */
getIcon()1562         public Icon getIcon() {
1563             if (mIcon == null && icon != 0) {
1564                 // you snuck an icon in here without using the builder; let's try to keep it
1565                 mIcon = Icon.createWithResource("", icon);
1566             }
1567             return mIcon;
1568         }
1569 
1570         /**
1571          * Get additional metadata carried around with this Action.
1572          */
getExtras()1573         public Bundle getExtras() {
1574             return mExtras;
1575         }
1576 
1577         /**
1578          * Return whether the platform should automatically generate possible replies for this
1579          * {@link Action}
1580          */
getAllowGeneratedReplies()1581         public boolean getAllowGeneratedReplies() {
1582             return mAllowGeneratedReplies;
1583         }
1584 
1585         /**
1586          * Get the list of inputs to be collected from the user when this action is sent.
1587          * May return null if no remote inputs were added. Only returns inputs which accept
1588          * a text input. For inputs which only accept data use {@link #getDataOnlyRemoteInputs}.
1589          */
getRemoteInputs()1590         public RemoteInput[] getRemoteInputs() {
1591             return mRemoteInputs;
1592         }
1593 
1594         /**
1595          * Returns the {@code SemanticAction} associated with this {@link Action}. A
1596          * {@code SemanticAction} denotes what an {@link Action}'s {@link PendingIntent} will do
1597          * (eg. reply, mark as read, delete, etc).
1598          */
getSemanticAction()1599         public @SemanticAction int getSemanticAction() {
1600             return mSemanticAction;
1601         }
1602 
1603         /**
1604          * Returns whether this is a contextual Action, i.e. whether the action is dependent on the
1605          * notification message body. An example of a contextual action could be an action opening a
1606          * map application with an address shown in the notification.
1607          */
isContextual()1608         public boolean isContextual() {
1609             return mIsContextual;
1610         }
1611 
1612         /**
1613          * Get the list of inputs to be collected from the user that ONLY accept data when this
1614          * action is sent. These remote inputs are guaranteed to return true on a call to
1615          * {@link RemoteInput#isDataOnly}.
1616          *
1617          * Returns null if there are no data-only remote inputs.
1618          *
1619          * This method exists so that legacy RemoteInput collectors that pre-date the addition
1620          * of non-textual RemoteInputs do not access these remote inputs.
1621          */
getDataOnlyRemoteInputs()1622         public RemoteInput[] getDataOnlyRemoteInputs() {
1623             return getParcelableArrayFromBundle(mExtras, EXTRA_DATA_ONLY_INPUTS, RemoteInput.class);
1624         }
1625 
1626         /**
1627          * Builder class for {@link Action} objects.
1628          */
1629         public static final class Builder {
1630             @Nullable private final Icon mIcon;
1631             @Nullable private final CharSequence mTitle;
1632             @Nullable private final PendingIntent mIntent;
1633             private boolean mAllowGeneratedReplies = true;
1634             @NonNull private final Bundle mExtras;
1635             @Nullable private ArrayList<RemoteInput> mRemoteInputs;
1636             private @SemanticAction int mSemanticAction;
1637             private boolean mIsContextual;
1638 
1639             /**
1640              * Construct a new builder for {@link Action} object.
1641              * @param icon icon to show for this action
1642              * @param title the title of the action
1643              * @param intent the {@link PendingIntent} to fire when users trigger this action
1644              */
1645             @Deprecated
Builder(int icon, CharSequence title, PendingIntent intent)1646             public Builder(int icon, CharSequence title, PendingIntent intent) {
1647                 this(Icon.createWithResource("", icon), title, intent);
1648             }
1649 
1650             /**
1651              * Construct a new builder for {@link Action} object.
1652              * @param icon icon to show for this action
1653              * @param title the title of the action
1654              * @param intent the {@link PendingIntent} to fire when users trigger this action
1655              */
Builder(Icon icon, CharSequence title, PendingIntent intent)1656             public Builder(Icon icon, CharSequence title, PendingIntent intent) {
1657                 this(icon, title, intent, new Bundle(), null, true, SEMANTIC_ACTION_NONE);
1658             }
1659 
1660             /**
1661              * Construct a new builder for {@link Action} object using the fields from an
1662              * {@link Action}.
1663              * @param action the action to read fields from.
1664              */
Builder(Action action)1665             public Builder(Action action) {
1666                 this(action.getIcon(), action.title, action.actionIntent,
1667                         new Bundle(action.mExtras), action.getRemoteInputs(),
1668                         action.getAllowGeneratedReplies(), action.getSemanticAction());
1669             }
1670 
Builder(@ullable Icon icon, @Nullable CharSequence title, @Nullable PendingIntent intent, @NonNull Bundle extras, @Nullable RemoteInput[] remoteInputs, boolean allowGeneratedReplies, @SemanticAction int semanticAction)1671             private Builder(@Nullable Icon icon, @Nullable CharSequence title,
1672                     @Nullable PendingIntent intent, @NonNull Bundle extras,
1673                     @Nullable RemoteInput[] remoteInputs, boolean allowGeneratedReplies,
1674                     @SemanticAction int semanticAction) {
1675                 mIcon = icon;
1676                 mTitle = title;
1677                 mIntent = intent;
1678                 mExtras = extras;
1679                 if (remoteInputs != null) {
1680                     mRemoteInputs = new ArrayList<RemoteInput>(remoteInputs.length);
1681                     Collections.addAll(mRemoteInputs, remoteInputs);
1682                 }
1683                 mAllowGeneratedReplies = allowGeneratedReplies;
1684                 mSemanticAction = semanticAction;
1685             }
1686 
1687             /**
1688              * Merge additional metadata into this builder.
1689              *
1690              * <p>Values within the Bundle will replace existing extras values in this Builder.
1691              *
1692              * @see Notification.Action#extras
1693              */
1694             @NonNull
addExtras(Bundle extras)1695             public Builder addExtras(Bundle extras) {
1696                 if (extras != null) {
1697                     mExtras.putAll(extras);
1698                 }
1699                 return this;
1700             }
1701 
1702             /**
1703              * Get the metadata Bundle used by this Builder.
1704              *
1705              * <p>The returned Bundle is shared with this Builder.
1706              */
1707             @NonNull
getExtras()1708             public Bundle getExtras() {
1709                 return mExtras;
1710             }
1711 
1712             /**
1713              * Add an input to be collected from the user when this action is sent.
1714              * Response values can be retrieved from the fired intent by using the
1715              * {@link RemoteInput#getResultsFromIntent} function.
1716              * @param remoteInput a {@link RemoteInput} to add to the action
1717              * @return this object for method chaining
1718              */
1719             @NonNull
addRemoteInput(RemoteInput remoteInput)1720             public Builder addRemoteInput(RemoteInput remoteInput) {
1721                 if (mRemoteInputs == null) {
1722                     mRemoteInputs = new ArrayList<RemoteInput>();
1723                 }
1724                 mRemoteInputs.add(remoteInput);
1725                 return this;
1726             }
1727 
1728             /**
1729              * Set whether the platform should automatically generate possible replies to add to
1730              * {@link RemoteInput#getChoices()}. If the {@link Action} doesn't have a
1731              * {@link RemoteInput}, this has no effect.
1732              * @param allowGeneratedReplies {@code true} to allow generated replies, {@code false}
1733              * otherwise
1734              * @return this object for method chaining
1735              * The default value is {@code true}
1736              */
1737             @NonNull
setAllowGeneratedReplies(boolean allowGeneratedReplies)1738             public Builder setAllowGeneratedReplies(boolean allowGeneratedReplies) {
1739                 mAllowGeneratedReplies = allowGeneratedReplies;
1740                 return this;
1741             }
1742 
1743             /**
1744              * Sets the {@code SemanticAction} for this {@link Action}. A
1745              * {@code SemanticAction} denotes what an {@link Action}'s
1746              * {@link PendingIntent} will do (eg. reply, mark as read, delete, etc).
1747              * @param semanticAction a SemanticAction defined within {@link Action} with
1748              * {@code SEMANTIC_ACTION_} prefixes
1749              * @return this object for method chaining
1750              */
1751             @NonNull
setSemanticAction(@emanticAction int semanticAction)1752             public Builder setSemanticAction(@SemanticAction int semanticAction) {
1753                 mSemanticAction = semanticAction;
1754                 return this;
1755             }
1756 
1757             /**
1758              * Sets whether this {@link Action} is a contextual action, i.e. whether the action is
1759              * dependent on the notification message body. An example of a contextual action could
1760              * be an action opening a map application with an address shown in the notification.
1761              */
1762             @NonNull
setContextual(boolean isContextual)1763             public Builder setContextual(boolean isContextual) {
1764                 mIsContextual = isContextual;
1765                 return this;
1766             }
1767 
1768             /**
1769              * Apply an extender to this action builder. Extenders may be used to add
1770              * metadata or change options on this builder.
1771              */
1772             @NonNull
extend(Extender extender)1773             public Builder extend(Extender extender) {
1774                 extender.extend(this);
1775                 return this;
1776             }
1777 
1778             /**
1779              * Throws an NPE if we are building a contextual action missing one of the fields
1780              * necessary to display the action.
1781              */
checkContextualActionNullFields()1782             private void checkContextualActionNullFields() {
1783                 if (!mIsContextual) return;
1784 
1785                 if (mIcon == null) {
1786                     throw new NullPointerException("Contextual Actions must contain a valid icon");
1787                 }
1788 
1789                 if (mIntent == null) {
1790                     throw new NullPointerException(
1791                             "Contextual Actions must contain a valid PendingIntent");
1792                 }
1793             }
1794 
1795             /**
1796              * Combine all of the options that have been set and return a new {@link Action}
1797              * object.
1798              * @return the built action
1799              */
1800             @NonNull
build()1801             public Action build() {
1802                 checkContextualActionNullFields();
1803 
1804                 ArrayList<RemoteInput> dataOnlyInputs = new ArrayList<>();
1805                 RemoteInput[] previousDataInputs = getParcelableArrayFromBundle(
1806                         mExtras, EXTRA_DATA_ONLY_INPUTS, RemoteInput.class);
1807                 if (previousDataInputs != null) {
1808                     for (RemoteInput input : previousDataInputs) {
1809                         dataOnlyInputs.add(input);
1810                     }
1811                 }
1812                 List<RemoteInput> textInputs = new ArrayList<>();
1813                 if (mRemoteInputs != null) {
1814                     for (RemoteInput input : mRemoteInputs) {
1815                         if (input.isDataOnly()) {
1816                             dataOnlyInputs.add(input);
1817                         } else {
1818                             textInputs.add(input);
1819                         }
1820                     }
1821                 }
1822                 if (!dataOnlyInputs.isEmpty()) {
1823                     RemoteInput[] dataInputsArr =
1824                             dataOnlyInputs.toArray(new RemoteInput[dataOnlyInputs.size()]);
1825                     mExtras.putParcelableArray(EXTRA_DATA_ONLY_INPUTS, dataInputsArr);
1826                 }
1827                 RemoteInput[] textInputsArr = textInputs.isEmpty()
1828                         ? null : textInputs.toArray(new RemoteInput[textInputs.size()]);
1829                 return new Action(mIcon, mTitle, mIntent, mExtras, textInputsArr,
1830                         mAllowGeneratedReplies, mSemanticAction, mIsContextual);
1831             }
1832         }
1833 
1834         @Override
clone()1835         public Action clone() {
1836             return new Action(
1837                     getIcon(),
1838                     title,
1839                     actionIntent, // safe to alias
1840                     mExtras == null ? new Bundle() : new Bundle(mExtras),
1841                     getRemoteInputs(),
1842                     getAllowGeneratedReplies(),
1843                     getSemanticAction(),
1844                     isContextual());
1845         }
1846 
1847         @Override
describeContents()1848         public int describeContents() {
1849             return 0;
1850         }
1851 
1852         @Override
writeToParcel(Parcel out, int flags)1853         public void writeToParcel(Parcel out, int flags) {
1854             final Icon ic = getIcon();
1855             if (ic != null) {
1856                 out.writeInt(1);
1857                 ic.writeToParcel(out, 0);
1858             } else {
1859                 out.writeInt(0);
1860             }
1861             TextUtils.writeToParcel(title, out, flags);
1862             if (actionIntent != null) {
1863                 out.writeInt(1);
1864                 actionIntent.writeToParcel(out, flags);
1865             } else {
1866                 out.writeInt(0);
1867             }
1868             out.writeBundle(mExtras);
1869             out.writeTypedArray(mRemoteInputs, flags);
1870             out.writeInt(mAllowGeneratedReplies ? 1 : 0);
1871             out.writeInt(mSemanticAction);
1872             out.writeInt(mIsContextual ? 1 : 0);
1873         }
1874 
1875         public static final @android.annotation.NonNull Parcelable.Creator<Action> CREATOR =
1876                 new Parcelable.Creator<Action>() {
1877             public Action createFromParcel(Parcel in) {
1878                 return new Action(in);
1879             }
1880             public Action[] newArray(int size) {
1881                 return new Action[size];
1882             }
1883         };
1884 
1885         /**
1886          * Extender interface for use with {@link Builder#extend}. Extenders may be used to add
1887          * metadata or change options on an action builder.
1888          */
1889         public interface Extender {
1890             /**
1891              * Apply this extender to a notification action builder.
1892              * @param builder the builder to be modified.
1893              * @return the build object for chaining.
1894              */
extend(Builder builder)1895             public Builder extend(Builder builder);
1896         }
1897 
1898         /**
1899          * Wearable extender for notification actions. To add extensions to an action,
1900          * create a new {@link android.app.Notification.Action.WearableExtender} object using
1901          * the {@code WearableExtender()} constructor and apply it to a
1902          * {@link android.app.Notification.Action.Builder} using
1903          * {@link android.app.Notification.Action.Builder#extend}.
1904          *
1905          * <pre class="prettyprint">
1906          * Notification.Action action = new Notification.Action.Builder(
1907          *         R.drawable.archive_all, "Archive all", actionIntent)
1908          *         .extend(new Notification.Action.WearableExtender()
1909          *                 .setAvailableOffline(false))
1910          *         .build();</pre>
1911          */
1912         public static final class WearableExtender implements Extender {
1913             /** Notification action extra which contains wearable extensions */
1914             private static final String EXTRA_WEARABLE_EXTENSIONS = "android.wearable.EXTENSIONS";
1915 
1916             // Keys within EXTRA_WEARABLE_EXTENSIONS for wearable options.
1917             private static final String KEY_FLAGS = "flags";
1918             private static final String KEY_IN_PROGRESS_LABEL = "inProgressLabel";
1919             private static final String KEY_CONFIRM_LABEL = "confirmLabel";
1920             private static final String KEY_CANCEL_LABEL = "cancelLabel";
1921 
1922             // Flags bitwise-ored to mFlags
1923             private static final int FLAG_AVAILABLE_OFFLINE = 0x1;
1924             private static final int FLAG_HINT_LAUNCHES_ACTIVITY = 1 << 1;
1925             private static final int FLAG_HINT_DISPLAY_INLINE = 1 << 2;
1926 
1927             // Default value for flags integer
1928             private static final int DEFAULT_FLAGS = FLAG_AVAILABLE_OFFLINE;
1929 
1930             private int mFlags = DEFAULT_FLAGS;
1931 
1932             private CharSequence mInProgressLabel;
1933             private CharSequence mConfirmLabel;
1934             private CharSequence mCancelLabel;
1935 
1936             /**
1937              * Create a {@link android.app.Notification.Action.WearableExtender} with default
1938              * options.
1939              */
WearableExtender()1940             public WearableExtender() {
1941             }
1942 
1943             /**
1944              * Create a {@link android.app.Notification.Action.WearableExtender} by reading
1945              * wearable options present in an existing notification action.
1946              * @param action the notification action to inspect.
1947              */
WearableExtender(Action action)1948             public WearableExtender(Action action) {
1949                 Bundle wearableBundle = action.getExtras().getBundle(EXTRA_WEARABLE_EXTENSIONS);
1950                 if (wearableBundle != null) {
1951                     mFlags = wearableBundle.getInt(KEY_FLAGS, DEFAULT_FLAGS);
1952                     mInProgressLabel = wearableBundle.getCharSequence(KEY_IN_PROGRESS_LABEL);
1953                     mConfirmLabel = wearableBundle.getCharSequence(KEY_CONFIRM_LABEL);
1954                     mCancelLabel = wearableBundle.getCharSequence(KEY_CANCEL_LABEL);
1955                 }
1956             }
1957 
1958             /**
1959              * Apply wearable extensions to a notification action that is being built. This is
1960              * typically called by the {@link android.app.Notification.Action.Builder#extend}
1961              * method of {@link android.app.Notification.Action.Builder}.
1962              */
1963             @Override
extend(Action.Builder builder)1964             public Action.Builder extend(Action.Builder builder) {
1965                 Bundle wearableBundle = new Bundle();
1966 
1967                 if (mFlags != DEFAULT_FLAGS) {
1968                     wearableBundle.putInt(KEY_FLAGS, mFlags);
1969                 }
1970                 if (mInProgressLabel != null) {
1971                     wearableBundle.putCharSequence(KEY_IN_PROGRESS_LABEL, mInProgressLabel);
1972                 }
1973                 if (mConfirmLabel != null) {
1974                     wearableBundle.putCharSequence(KEY_CONFIRM_LABEL, mConfirmLabel);
1975                 }
1976                 if (mCancelLabel != null) {
1977                     wearableBundle.putCharSequence(KEY_CANCEL_LABEL, mCancelLabel);
1978                 }
1979 
1980                 builder.getExtras().putBundle(EXTRA_WEARABLE_EXTENSIONS, wearableBundle);
1981                 return builder;
1982             }
1983 
1984             @Override
clone()1985             public WearableExtender clone() {
1986                 WearableExtender that = new WearableExtender();
1987                 that.mFlags = this.mFlags;
1988                 that.mInProgressLabel = this.mInProgressLabel;
1989                 that.mConfirmLabel = this.mConfirmLabel;
1990                 that.mCancelLabel = this.mCancelLabel;
1991                 return that;
1992             }
1993 
1994             /**
1995              * Set whether this action is available when the wearable device is not connected to
1996              * a companion device. The user can still trigger this action when the wearable device is
1997              * offline, but a visual hint will indicate that the action may not be available.
1998              * Defaults to true.
1999              */
setAvailableOffline(boolean availableOffline)2000             public WearableExtender setAvailableOffline(boolean availableOffline) {
2001                 setFlag(FLAG_AVAILABLE_OFFLINE, availableOffline);
2002                 return this;
2003             }
2004 
2005             /**
2006              * Get whether this action is available when the wearable device is not connected to
2007              * a companion device. The user can still trigger this action when the wearable device is
2008              * offline, but a visual hint will indicate that the action may not be available.
2009              * Defaults to true.
2010              */
isAvailableOffline()2011             public boolean isAvailableOffline() {
2012                 return (mFlags & FLAG_AVAILABLE_OFFLINE) != 0;
2013             }
2014 
setFlag(int mask, boolean value)2015             private void setFlag(int mask, boolean value) {
2016                 if (value) {
2017                     mFlags |= mask;
2018                 } else {
2019                     mFlags &= ~mask;
2020                 }
2021             }
2022 
2023             /**
2024              * Set a label to display while the wearable is preparing to automatically execute the
2025              * action. This is usually a 'ing' verb ending in ellipsis like "Sending..."
2026              *
2027              * @param label the label to display while the action is being prepared to execute
2028              * @return this object for method chaining
2029              */
2030             @Deprecated
setInProgressLabel(CharSequence label)2031             public WearableExtender setInProgressLabel(CharSequence label) {
2032                 mInProgressLabel = label;
2033                 return this;
2034             }
2035 
2036             /**
2037              * Get the label to display while the wearable is preparing to automatically execute
2038              * the action. This is usually a 'ing' verb ending in ellipsis like "Sending..."
2039              *
2040              * @return the label to display while the action is being prepared to execute
2041              */
2042             @Deprecated
getInProgressLabel()2043             public CharSequence getInProgressLabel() {
2044                 return mInProgressLabel;
2045             }
2046 
2047             /**
2048              * Set a label to display to confirm that the action should be executed.
2049              * This is usually an imperative verb like "Send".
2050              *
2051              * @param label the label to confirm the action should be executed
2052              * @return this object for method chaining
2053              */
2054             @Deprecated
setConfirmLabel(CharSequence label)2055             public WearableExtender setConfirmLabel(CharSequence label) {
2056                 mConfirmLabel = label;
2057                 return this;
2058             }
2059 
2060             /**
2061              * Get the label to display to confirm that the action should be executed.
2062              * This is usually an imperative verb like "Send".
2063              *
2064              * @return the label to confirm the action should be executed
2065              */
2066             @Deprecated
getConfirmLabel()2067             public CharSequence getConfirmLabel() {
2068                 return mConfirmLabel;
2069             }
2070 
2071             /**
2072              * Set a label to display to cancel the action.
2073              * This is usually an imperative verb, like "Cancel".
2074              *
2075              * @param label the label to display to cancel the action
2076              * @return this object for method chaining
2077              */
2078             @Deprecated
setCancelLabel(CharSequence label)2079             public WearableExtender setCancelLabel(CharSequence label) {
2080                 mCancelLabel = label;
2081                 return this;
2082             }
2083 
2084             /**
2085              * Get the label to display to cancel the action.
2086              * This is usually an imperative verb like "Cancel".
2087              *
2088              * @return the label to display to cancel the action
2089              */
2090             @Deprecated
getCancelLabel()2091             public CharSequence getCancelLabel() {
2092                 return mCancelLabel;
2093             }
2094 
2095             /**
2096              * Set a hint that this Action will launch an {@link Activity} directly, telling the
2097              * platform that it can generate the appropriate transitions.
2098              * @param hintLaunchesActivity {@code true} if the content intent will launch
2099              * an activity and transitions should be generated, false otherwise.
2100              * @return this object for method chaining
2101              */
setHintLaunchesActivity( boolean hintLaunchesActivity)2102             public WearableExtender setHintLaunchesActivity(
2103                     boolean hintLaunchesActivity) {
2104                 setFlag(FLAG_HINT_LAUNCHES_ACTIVITY, hintLaunchesActivity);
2105                 return this;
2106             }
2107 
2108             /**
2109              * Get a hint that this Action will launch an {@link Activity} directly, telling the
2110              * platform that it can generate the appropriate transitions
2111              * @return {@code true} if the content intent will launch an activity and transitions
2112              * should be generated, false otherwise. The default value is {@code false} if this was
2113              * never set.
2114              */
getHintLaunchesActivity()2115             public boolean getHintLaunchesActivity() {
2116                 return (mFlags & FLAG_HINT_LAUNCHES_ACTIVITY) != 0;
2117             }
2118 
2119             /**
2120              * Set a hint that this Action should be displayed inline.
2121              *
2122              * @param hintDisplayInline {@code true} if action should be displayed inline, false
2123              *        otherwise
2124              * @return this object for method chaining
2125              */
setHintDisplayActionInline( boolean hintDisplayInline)2126             public WearableExtender setHintDisplayActionInline(
2127                     boolean hintDisplayInline) {
2128                 setFlag(FLAG_HINT_DISPLAY_INLINE, hintDisplayInline);
2129                 return this;
2130             }
2131 
2132             /**
2133              * Get a hint that this Action should be displayed inline.
2134              *
2135              * @return {@code true} if the Action should be displayed inline, {@code false}
2136              *         otherwise. The default value is {@code false} if this was never set.
2137              */
getHintDisplayActionInline()2138             public boolean getHintDisplayActionInline() {
2139                 return (mFlags & FLAG_HINT_DISPLAY_INLINE) != 0;
2140             }
2141         }
2142 
2143         /**
2144          * Provides meaning to an {@link Action} that hints at what the associated
2145          * {@link PendingIntent} will do. For example, an {@link Action} with a
2146          * {@link PendingIntent} that replies to a text message notification may have the
2147          * {@link #SEMANTIC_ACTION_REPLY} {@code SemanticAction} set within it.
2148          *
2149          * @hide
2150          */
2151         @IntDef(prefix = { "SEMANTIC_ACTION_" }, value = {
2152                 SEMANTIC_ACTION_NONE,
2153                 SEMANTIC_ACTION_REPLY,
2154                 SEMANTIC_ACTION_MARK_AS_READ,
2155                 SEMANTIC_ACTION_MARK_AS_UNREAD,
2156                 SEMANTIC_ACTION_DELETE,
2157                 SEMANTIC_ACTION_ARCHIVE,
2158                 SEMANTIC_ACTION_MUTE,
2159                 SEMANTIC_ACTION_UNMUTE,
2160                 SEMANTIC_ACTION_THUMBS_UP,
2161                 SEMANTIC_ACTION_THUMBS_DOWN,
2162                 SEMANTIC_ACTION_CALL
2163         })
2164         @Retention(RetentionPolicy.SOURCE)
2165         public @interface SemanticAction {}
2166     }
2167 
2168     /**
2169      * Array of all {@link Action} structures attached to this notification by
2170      * {@link Builder#addAction(int, CharSequence, PendingIntent)}. Mostly useful for instances of
2171      * {@link android.service.notification.NotificationListenerService} that provide an alternative
2172      * interface for invoking actions.
2173      */
2174     public Action[] actions;
2175 
2176     /**
2177      * Replacement version of this notification whose content will be shown
2178      * in an insecure context such as atop a secure keyguard. See {@link #visibility}
2179      * and {@link #VISIBILITY_PUBLIC}.
2180      */
2181     public Notification publicVersion;
2182 
2183     /**
2184      * Constructs a Notification object with default values.
2185      * You might want to consider using {@link Builder} instead.
2186      */
Notification()2187     public Notification()
2188     {
2189         this.when = System.currentTimeMillis();
2190         this.creationTime = System.currentTimeMillis();
2191         this.priority = PRIORITY_DEFAULT;
2192     }
2193 
2194     /**
2195      * @hide
2196      */
2197     @UnsupportedAppUsage
Notification(Context context, int icon, CharSequence tickerText, long when, CharSequence contentTitle, CharSequence contentText, Intent contentIntent)2198     public Notification(Context context, int icon, CharSequence tickerText, long when,
2199             CharSequence contentTitle, CharSequence contentText, Intent contentIntent)
2200     {
2201         new Builder(context)
2202                 .setWhen(when)
2203                 .setSmallIcon(icon)
2204                 .setTicker(tickerText)
2205                 .setContentTitle(contentTitle)
2206                 .setContentText(contentText)
2207                 .setContentIntent(PendingIntent.getActivity(context, 0, contentIntent, 0))
2208                 .buildInto(this);
2209     }
2210 
2211     /**
2212      * Constructs a Notification object with the information needed to
2213      * have a status bar icon without the standard expanded view.
2214      *
2215      * @param icon          The resource id of the icon to put in the status bar.
2216      * @param tickerText    The text that flows by in the status bar when the notification first
2217      *                      activates.
2218      * @param when          The time to show in the time field.  In the System.currentTimeMillis
2219      *                      timebase.
2220      *
2221      * @deprecated Use {@link Builder} instead.
2222      */
2223     @Deprecated
Notification(int icon, CharSequence tickerText, long when)2224     public Notification(int icon, CharSequence tickerText, long when)
2225     {
2226         this.icon = icon;
2227         this.tickerText = tickerText;
2228         this.when = when;
2229         this.creationTime = System.currentTimeMillis();
2230     }
2231 
2232     /**
2233      * Unflatten the notification from a parcel.
2234      */
2235     @SuppressWarnings("unchecked")
Notification(Parcel parcel)2236     public Notification(Parcel parcel) {
2237         // IMPORTANT: Add unmarshaling code in readFromParcel as the pending
2238         // intents in extras are always written as the last entry.
2239         readFromParcelImpl(parcel);
2240         // Must be read last!
2241         allPendingIntents = (ArraySet<PendingIntent>) parcel.readArraySet(null);
2242     }
2243 
readFromParcelImpl(Parcel parcel)2244     private void readFromParcelImpl(Parcel parcel)
2245     {
2246         int version = parcel.readInt();
2247 
2248         mWhitelistToken = parcel.readStrongBinder();
2249         if (mWhitelistToken == null) {
2250             mWhitelistToken = processWhitelistToken;
2251         }
2252         // Propagate this token to all pending intents that are unmarshalled from the parcel.
2253         parcel.setClassCookie(PendingIntent.class, mWhitelistToken);
2254 
2255         when = parcel.readLong();
2256         creationTime = parcel.readLong();
2257         if (parcel.readInt() != 0) {
2258             mSmallIcon = Icon.CREATOR.createFromParcel(parcel);
2259             if (mSmallIcon.getType() == Icon.TYPE_RESOURCE) {
2260                 icon = mSmallIcon.getResId();
2261             }
2262         }
2263         number = parcel.readInt();
2264         if (parcel.readInt() != 0) {
2265             contentIntent = PendingIntent.CREATOR.createFromParcel(parcel);
2266         }
2267         if (parcel.readInt() != 0) {
2268             deleteIntent = PendingIntent.CREATOR.createFromParcel(parcel);
2269         }
2270         if (parcel.readInt() != 0) {
2271             tickerText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
2272         }
2273         if (parcel.readInt() != 0) {
2274             tickerView = RemoteViews.CREATOR.createFromParcel(parcel);
2275         }
2276         if (parcel.readInt() != 0) {
2277             contentView = RemoteViews.CREATOR.createFromParcel(parcel);
2278         }
2279         if (parcel.readInt() != 0) {
2280             mLargeIcon = Icon.CREATOR.createFromParcel(parcel);
2281         }
2282         defaults = parcel.readInt();
2283         flags = parcel.readInt();
2284         if (parcel.readInt() != 0) {
2285             sound = Uri.CREATOR.createFromParcel(parcel);
2286         }
2287 
2288         audioStreamType = parcel.readInt();
2289         if (parcel.readInt() != 0) {
2290             audioAttributes = AudioAttributes.CREATOR.createFromParcel(parcel);
2291         }
2292         vibrate = parcel.createLongArray();
2293         ledARGB = parcel.readInt();
2294         ledOnMS = parcel.readInt();
2295         ledOffMS = parcel.readInt();
2296         iconLevel = parcel.readInt();
2297 
2298         if (parcel.readInt() != 0) {
2299             fullScreenIntent = PendingIntent.CREATOR.createFromParcel(parcel);
2300         }
2301 
2302         priority = parcel.readInt();
2303 
2304         category = parcel.readString8();
2305 
2306         mGroupKey = parcel.readString8();
2307 
2308         mSortKey = parcel.readString8();
2309 
2310         extras = Bundle.setDefusable(parcel.readBundle(), true); // may be null
2311         fixDuplicateExtras();
2312 
2313         actions = parcel.createTypedArray(Action.CREATOR); // may be null
2314 
2315         if (parcel.readInt() != 0) {
2316             bigContentView = RemoteViews.CREATOR.createFromParcel(parcel);
2317         }
2318 
2319         if (parcel.readInt() != 0) {
2320             headsUpContentView = RemoteViews.CREATOR.createFromParcel(parcel);
2321         }
2322 
2323         visibility = parcel.readInt();
2324 
2325         if (parcel.readInt() != 0) {
2326             publicVersion = Notification.CREATOR.createFromParcel(parcel);
2327         }
2328 
2329         color = parcel.readInt();
2330 
2331         if (parcel.readInt() != 0) {
2332             mChannelId = parcel.readString8();
2333         }
2334         mTimeout = parcel.readLong();
2335 
2336         if (parcel.readInt() != 0) {
2337             mShortcutId = parcel.readString8();
2338         }
2339 
2340         if (parcel.readInt() != 0) {
2341             mLocusId = LocusId.CREATOR.createFromParcel(parcel);
2342         }
2343 
2344         mBadgeIcon = parcel.readInt();
2345 
2346         if (parcel.readInt() != 0) {
2347             mSettingsText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
2348         }
2349 
2350         mGroupAlertBehavior = parcel.readInt();
2351         if (parcel.readInt() != 0) {
2352             mBubbleMetadata = BubbleMetadata.CREATOR.createFromParcel(parcel);
2353         }
2354 
2355         mAllowSystemGeneratedContextualActions = parcel.readBoolean();
2356     }
2357 
2358     @Override
clone()2359     public Notification clone() {
2360         Notification that = new Notification();
2361         cloneInto(that, true);
2362         return that;
2363     }
2364 
2365     /**
2366      * Copy all (or if heavy is false, all except Bitmaps and RemoteViews) members
2367      * of this into that.
2368      * @hide
2369      */
cloneInto(Notification that, boolean heavy)2370     public void cloneInto(Notification that, boolean heavy) {
2371         that.mWhitelistToken = this.mWhitelistToken;
2372         that.when = this.when;
2373         that.creationTime = this.creationTime;
2374         that.mSmallIcon = this.mSmallIcon;
2375         that.number = this.number;
2376 
2377         // PendingIntents are global, so there's no reason (or way) to clone them.
2378         that.contentIntent = this.contentIntent;
2379         that.deleteIntent = this.deleteIntent;
2380         that.fullScreenIntent = this.fullScreenIntent;
2381 
2382         if (this.tickerText != null) {
2383             that.tickerText = this.tickerText.toString();
2384         }
2385         if (heavy && this.tickerView != null) {
2386             that.tickerView = this.tickerView.clone();
2387         }
2388         if (heavy && this.contentView != null) {
2389             that.contentView = this.contentView.clone();
2390         }
2391         if (heavy && this.mLargeIcon != null) {
2392             that.mLargeIcon = this.mLargeIcon;
2393         }
2394         that.iconLevel = this.iconLevel;
2395         that.sound = this.sound; // android.net.Uri is immutable
2396         that.audioStreamType = this.audioStreamType;
2397         if (this.audioAttributes != null) {
2398             that.audioAttributes = new AudioAttributes.Builder(this.audioAttributes).build();
2399         }
2400 
2401         final long[] vibrate = this.vibrate;
2402         if (vibrate != null) {
2403             final int N = vibrate.length;
2404             final long[] vib = that.vibrate = new long[N];
2405             System.arraycopy(vibrate, 0, vib, 0, N);
2406         }
2407 
2408         that.ledARGB = this.ledARGB;
2409         that.ledOnMS = this.ledOnMS;
2410         that.ledOffMS = this.ledOffMS;
2411         that.defaults = this.defaults;
2412 
2413         that.flags = this.flags;
2414 
2415         that.priority = this.priority;
2416 
2417         that.category = this.category;
2418 
2419         that.mGroupKey = this.mGroupKey;
2420 
2421         that.mSortKey = this.mSortKey;
2422 
2423         if (this.extras != null) {
2424             try {
2425                 that.extras = new Bundle(this.extras);
2426                 // will unparcel
2427                 that.extras.size();
2428             } catch (BadParcelableException e) {
2429                 Log.e(TAG, "could not unparcel extras from notification: " + this, e);
2430                 that.extras = null;
2431             }
2432         }
2433 
2434         if (!ArrayUtils.isEmpty(allPendingIntents)) {
2435             that.allPendingIntents = new ArraySet<>(allPendingIntents);
2436         }
2437 
2438         if (this.actions != null) {
2439             that.actions = new Action[this.actions.length];
2440             for(int i=0; i<this.actions.length; i++) {
2441                 if ( this.actions[i] != null) {
2442                     that.actions[i] = this.actions[i].clone();
2443                 }
2444             }
2445         }
2446 
2447         if (heavy && this.bigContentView != null) {
2448             that.bigContentView = this.bigContentView.clone();
2449         }
2450 
2451         if (heavy && this.headsUpContentView != null) {
2452             that.headsUpContentView = this.headsUpContentView.clone();
2453         }
2454 
2455         that.visibility = this.visibility;
2456 
2457         if (this.publicVersion != null) {
2458             that.publicVersion = new Notification();
2459             this.publicVersion.cloneInto(that.publicVersion, heavy);
2460         }
2461 
2462         that.color = this.color;
2463 
2464         that.mChannelId = this.mChannelId;
2465         that.mTimeout = this.mTimeout;
2466         that.mShortcutId = this.mShortcutId;
2467         that.mLocusId = this.mLocusId;
2468         that.mBadgeIcon = this.mBadgeIcon;
2469         that.mSettingsText = this.mSettingsText;
2470         that.mGroupAlertBehavior = this.mGroupAlertBehavior;
2471         that.mBubbleMetadata = this.mBubbleMetadata;
2472         that.mAllowSystemGeneratedContextualActions = this.mAllowSystemGeneratedContextualActions;
2473 
2474         if (!heavy) {
2475             that.lightenPayload(); // will clean out extras
2476         }
2477     }
2478 
2479     /**
2480      * Note all {@link Uri} that are referenced internally, with the expectation
2481      * that Uri permission grants will need to be issued to ensure the recipient
2482      * of this object is able to render its contents.
2483      *
2484      * @hide
2485      */
visitUris(@onNull Consumer<Uri> visitor)2486     public void visitUris(@NonNull Consumer<Uri> visitor) {
2487         visitor.accept(sound);
2488 
2489         if (tickerView != null) tickerView.visitUris(visitor);
2490         if (contentView != null) contentView.visitUris(visitor);
2491         if (bigContentView != null) bigContentView.visitUris(visitor);
2492         if (headsUpContentView != null) headsUpContentView.visitUris(visitor);
2493 
2494         if (extras != null) {
2495             visitor.accept(extras.getParcelable(EXTRA_AUDIO_CONTENTS_URI));
2496             if (extras.containsKey(EXTRA_BACKGROUND_IMAGE_URI)) {
2497                 visitor.accept(Uri.parse(extras.getString(EXTRA_BACKGROUND_IMAGE_URI)));
2498             }
2499 
2500             ArrayList<Person> people = extras.getParcelableArrayList(EXTRA_PEOPLE_LIST);
2501             if (people != null && !people.isEmpty()) {
2502                 for (Person p : people) {
2503                     if (p.getIconUri() != null) {
2504                         visitor.accept(p.getIconUri());
2505                     }
2506                 }
2507             }
2508 
2509             final Person person = extras.getParcelable(EXTRA_MESSAGING_PERSON);
2510             if (person != null && person.getIconUri() != null) {
2511                 visitor.accept(person.getIconUri());
2512             }
2513         }
2514 
2515         if (MessagingStyle.class.equals(getNotificationStyle()) && extras != null) {
2516             final Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES);
2517             if (!ArrayUtils.isEmpty(messages)) {
2518                 for (MessagingStyle.Message message : MessagingStyle.Message
2519                         .getMessagesFromBundleArray(messages)) {
2520                     visitor.accept(message.getDataUri());
2521 
2522                     Person senderPerson = message.getSenderPerson();
2523                     if (senderPerson != null && senderPerson.getIconUri() != null) {
2524                         visitor.accept(senderPerson.getIconUri());
2525                     }
2526                 }
2527             }
2528 
2529             final Parcelable[] historic = extras.getParcelableArray(EXTRA_HISTORIC_MESSAGES);
2530             if (!ArrayUtils.isEmpty(historic)) {
2531                 for (MessagingStyle.Message message : MessagingStyle.Message
2532                         .getMessagesFromBundleArray(historic)) {
2533                     visitor.accept(message.getDataUri());
2534 
2535                     Person senderPerson = message.getSenderPerson();
2536                     if (senderPerson != null && senderPerson.getIconUri() != null) {
2537                         visitor.accept(senderPerson.getIconUri());
2538                     }
2539                 }
2540             }
2541         }
2542 
2543         if (mBubbleMetadata != null && mBubbleMetadata.getIcon() != null) {
2544             final Icon icon = mBubbleMetadata.getIcon();
2545             final int iconType = icon.getType();
2546             if (iconType == TYPE_URI_ADAPTIVE_BITMAP || iconType == TYPE_URI) {
2547                 visitor.accept(icon.getUri());
2548             }
2549         }
2550     }
2551 
2552     /**
2553      * Removes heavyweight parts of the Notification object for archival or for sending to
2554      * listeners when the full contents are not necessary.
2555      * @hide
2556      */
lightenPayload()2557     public final void lightenPayload() {
2558         tickerView = null;
2559         contentView = null;
2560         bigContentView = null;
2561         headsUpContentView = null;
2562         mLargeIcon = null;
2563         if (extras != null && !extras.isEmpty()) {
2564             final Set<String> keyset = extras.keySet();
2565             final int N = keyset.size();
2566             final String[] keys = keyset.toArray(new String[N]);
2567             for (int i=0; i<N; i++) {
2568                 final String key = keys[i];
2569                 if (TvExtender.EXTRA_TV_EXTENDER.equals(key)) {
2570                     continue;
2571                 }
2572                 final Object obj = extras.get(key);
2573                 if (obj != null &&
2574                     (  obj instanceof Parcelable
2575                     || obj instanceof Parcelable[]
2576                     || obj instanceof SparseArray
2577                     || obj instanceof ArrayList)) {
2578                     extras.remove(key);
2579                 }
2580             }
2581         }
2582     }
2583 
2584     /**
2585      * Make sure this CharSequence is safe to put into a bundle, which basically
2586      * means it had better not be some custom Parcelable implementation.
2587      * @hide
2588      */
safeCharSequence(CharSequence cs)2589     public static CharSequence safeCharSequence(CharSequence cs) {
2590         if (cs == null) return cs;
2591         if (cs.length() > MAX_CHARSEQUENCE_LENGTH) {
2592             cs = cs.subSequence(0, MAX_CHARSEQUENCE_LENGTH);
2593         }
2594         if (cs instanceof Parcelable) {
2595             Log.e(TAG, "warning: " + cs.getClass().getCanonicalName()
2596                     + " instance is a custom Parcelable and not allowed in Notification");
2597             return cs.toString();
2598         }
2599         return removeTextSizeSpans(cs);
2600     }
2601 
removeTextSizeSpans(CharSequence charSequence)2602     private static CharSequence removeTextSizeSpans(CharSequence charSequence) {
2603         if (charSequence instanceof Spanned) {
2604             Spanned ss = (Spanned) charSequence;
2605             Object[] spans = ss.getSpans(0, ss.length(), Object.class);
2606             SpannableStringBuilder builder = new SpannableStringBuilder(ss.toString());
2607             for (Object span : spans) {
2608                 Object resultSpan = span;
2609                 if (resultSpan instanceof CharacterStyle) {
2610                     resultSpan = ((CharacterStyle) span).getUnderlying();
2611                 }
2612                 if (resultSpan instanceof TextAppearanceSpan) {
2613                     TextAppearanceSpan originalSpan = (TextAppearanceSpan) resultSpan;
2614                     resultSpan = new TextAppearanceSpan(
2615                             originalSpan.getFamily(),
2616                             originalSpan.getTextStyle(),
2617                             -1,
2618                             originalSpan.getTextColor(),
2619                             originalSpan.getLinkTextColor());
2620                 } else if (resultSpan instanceof RelativeSizeSpan
2621                         || resultSpan instanceof AbsoluteSizeSpan) {
2622                     continue;
2623                 } else {
2624                     resultSpan = span;
2625                 }
2626                 builder.setSpan(resultSpan, ss.getSpanStart(span), ss.getSpanEnd(span),
2627                         ss.getSpanFlags(span));
2628             }
2629             return builder;
2630         }
2631         return charSequence;
2632     }
2633 
describeContents()2634     public int describeContents() {
2635         return 0;
2636     }
2637 
2638     /**
2639      * Flatten this notification into a parcel.
2640      */
writeToParcel(Parcel parcel, int flags)2641     public void writeToParcel(Parcel parcel, int flags) {
2642         // We need to mark all pending intents getting into the notification
2643         // system as being put there to later allow the notification ranker
2644         // to launch them and by doing so add the app to the battery saver white
2645         // list for a short period of time. The problem is that the system
2646         // cannot look into the extras as there may be parcelables there that
2647         // the platform does not know how to handle. To go around that we have
2648         // an explicit list of the pending intents in the extras bundle.
2649         final boolean collectPendingIntents = (allPendingIntents == null);
2650         if (collectPendingIntents) {
2651             PendingIntent.setOnMarshaledListener(
2652                     (PendingIntent intent, Parcel out, int outFlags) -> {
2653                 if (parcel == out) {
2654                     synchronized (this) {
2655                         if (allPendingIntents == null) {
2656                             allPendingIntents = new ArraySet<>();
2657                         }
2658                         allPendingIntents.add(intent);
2659                     }
2660                 }
2661             });
2662         }
2663         try {
2664             // IMPORTANT: Add marshaling code in writeToParcelImpl as we
2665             // want to intercept all pending events written to the parcel.
2666             writeToParcelImpl(parcel, flags);
2667             synchronized (this) {
2668                 // Must be written last!
2669                 parcel.writeArraySet(allPendingIntents);
2670             }
2671         } finally {
2672             if (collectPendingIntents) {
2673                 PendingIntent.setOnMarshaledListener(null);
2674             }
2675         }
2676     }
2677 
writeToParcelImpl(Parcel parcel, int flags)2678     private void writeToParcelImpl(Parcel parcel, int flags) {
2679         parcel.writeInt(1);
2680 
2681         parcel.writeStrongBinder(mWhitelistToken);
2682         parcel.writeLong(when);
2683         parcel.writeLong(creationTime);
2684         if (mSmallIcon == null && icon != 0) {
2685             // you snuck an icon in here without using the builder; let's try to keep it
2686             mSmallIcon = Icon.createWithResource("", icon);
2687         }
2688         if (mSmallIcon != null) {
2689             parcel.writeInt(1);
2690             mSmallIcon.writeToParcel(parcel, 0);
2691         } else {
2692             parcel.writeInt(0);
2693         }
2694         parcel.writeInt(number);
2695         if (contentIntent != null) {
2696             parcel.writeInt(1);
2697             contentIntent.writeToParcel(parcel, 0);
2698         } else {
2699             parcel.writeInt(0);
2700         }
2701         if (deleteIntent != null) {
2702             parcel.writeInt(1);
2703             deleteIntent.writeToParcel(parcel, 0);
2704         } else {
2705             parcel.writeInt(0);
2706         }
2707         if (tickerText != null) {
2708             parcel.writeInt(1);
2709             TextUtils.writeToParcel(tickerText, parcel, flags);
2710         } else {
2711             parcel.writeInt(0);
2712         }
2713         if (tickerView != null) {
2714             parcel.writeInt(1);
2715             tickerView.writeToParcel(parcel, 0);
2716         } else {
2717             parcel.writeInt(0);
2718         }
2719         if (contentView != null) {
2720             parcel.writeInt(1);
2721             contentView.writeToParcel(parcel, 0);
2722         } else {
2723             parcel.writeInt(0);
2724         }
2725         if (mLargeIcon == null && largeIcon != null) {
2726             // you snuck an icon in here without using the builder; let's try to keep it
2727             mLargeIcon = Icon.createWithBitmap(largeIcon);
2728         }
2729         if (mLargeIcon != null) {
2730             parcel.writeInt(1);
2731             mLargeIcon.writeToParcel(parcel, 0);
2732         } else {
2733             parcel.writeInt(0);
2734         }
2735 
2736         parcel.writeInt(defaults);
2737         parcel.writeInt(this.flags);
2738 
2739         if (sound != null) {
2740             parcel.writeInt(1);
2741             sound.writeToParcel(parcel, 0);
2742         } else {
2743             parcel.writeInt(0);
2744         }
2745         parcel.writeInt(audioStreamType);
2746 
2747         if (audioAttributes != null) {
2748             parcel.writeInt(1);
2749             audioAttributes.writeToParcel(parcel, 0);
2750         } else {
2751             parcel.writeInt(0);
2752         }
2753 
2754         parcel.writeLongArray(vibrate);
2755         parcel.writeInt(ledARGB);
2756         parcel.writeInt(ledOnMS);
2757         parcel.writeInt(ledOffMS);
2758         parcel.writeInt(iconLevel);
2759 
2760         if (fullScreenIntent != null) {
2761             parcel.writeInt(1);
2762             fullScreenIntent.writeToParcel(parcel, 0);
2763         } else {
2764             parcel.writeInt(0);
2765         }
2766 
2767         parcel.writeInt(priority);
2768 
2769         parcel.writeString8(category);
2770 
2771         parcel.writeString8(mGroupKey);
2772 
2773         parcel.writeString8(mSortKey);
2774 
2775         parcel.writeBundle(extras); // null ok
2776 
2777         parcel.writeTypedArray(actions, 0); // null ok
2778 
2779         if (bigContentView != null) {
2780             parcel.writeInt(1);
2781             bigContentView.writeToParcel(parcel, 0);
2782         } else {
2783             parcel.writeInt(0);
2784         }
2785 
2786         if (headsUpContentView != null) {
2787             parcel.writeInt(1);
2788             headsUpContentView.writeToParcel(parcel, 0);
2789         } else {
2790             parcel.writeInt(0);
2791         }
2792 
2793         parcel.writeInt(visibility);
2794 
2795         if (publicVersion != null) {
2796             parcel.writeInt(1);
2797             publicVersion.writeToParcel(parcel, 0);
2798         } else {
2799             parcel.writeInt(0);
2800         }
2801 
2802         parcel.writeInt(color);
2803 
2804         if (mChannelId != null) {
2805             parcel.writeInt(1);
2806             parcel.writeString8(mChannelId);
2807         } else {
2808             parcel.writeInt(0);
2809         }
2810         parcel.writeLong(mTimeout);
2811 
2812         if (mShortcutId != null) {
2813             parcel.writeInt(1);
2814             parcel.writeString8(mShortcutId);
2815         } else {
2816             parcel.writeInt(0);
2817         }
2818 
2819         if (mLocusId != null) {
2820             parcel.writeInt(1);
2821             mLocusId.writeToParcel(parcel, 0);
2822         } else {
2823             parcel.writeInt(0);
2824         }
2825 
2826         parcel.writeInt(mBadgeIcon);
2827 
2828         if (mSettingsText != null) {
2829             parcel.writeInt(1);
2830             TextUtils.writeToParcel(mSettingsText, parcel, flags);
2831         } else {
2832             parcel.writeInt(0);
2833         }
2834 
2835         parcel.writeInt(mGroupAlertBehavior);
2836 
2837         if (mBubbleMetadata != null) {
2838             parcel.writeInt(1);
2839             mBubbleMetadata.writeToParcel(parcel, 0);
2840         } else {
2841             parcel.writeInt(0);
2842         }
2843 
2844         parcel.writeBoolean(mAllowSystemGeneratedContextualActions);
2845 
2846         // mUsesStandardHeader is not written because it should be recomputed in listeners
2847     }
2848 
2849     /**
2850      * Parcelable.Creator that instantiates Notification objects
2851      */
2852     public static final @android.annotation.NonNull Parcelable.Creator<Notification> CREATOR
2853             = new Parcelable.Creator<Notification>()
2854     {
2855         public Notification createFromParcel(Parcel parcel)
2856         {
2857             return new Notification(parcel);
2858         }
2859 
2860         public Notification[] newArray(int size)
2861         {
2862             return new Notification[size];
2863         }
2864     };
2865 
2866     /**
2867      * @hide
2868      */
areActionsVisiblyDifferent(Notification first, Notification second)2869     public static boolean areActionsVisiblyDifferent(Notification first, Notification second) {
2870         Notification.Action[] firstAs = first.actions;
2871         Notification.Action[] secondAs = second.actions;
2872         if (firstAs == null && secondAs != null || firstAs != null && secondAs == null) {
2873             return true;
2874         }
2875         if (firstAs != null && secondAs != null) {
2876             if (firstAs.length != secondAs.length) {
2877                 return true;
2878             }
2879             for (int i = 0; i < firstAs.length; i++) {
2880                 if (!Objects.equals(String.valueOf(firstAs[i].title),
2881                         String.valueOf(secondAs[i].title))) {
2882                     return true;
2883                 }
2884                 RemoteInput[] firstRs = firstAs[i].getRemoteInputs();
2885                 RemoteInput[] secondRs = secondAs[i].getRemoteInputs();
2886                 if (firstRs == null) {
2887                     firstRs = new RemoteInput[0];
2888                 }
2889                 if (secondRs == null) {
2890                     secondRs = new RemoteInput[0];
2891                 }
2892                 if (firstRs.length != secondRs.length) {
2893                     return true;
2894                 }
2895                 for (int j = 0; j < firstRs.length; j++) {
2896                     if (!Objects.equals(String.valueOf(firstRs[j].getLabel()),
2897                             String.valueOf(secondRs[j].getLabel()))) {
2898                         return true;
2899                     }
2900                 }
2901             }
2902         }
2903         return false;
2904     }
2905 
2906     /**
2907      * @hide
2908      */
areStyledNotificationsVisiblyDifferent(Builder first, Builder second)2909     public static boolean areStyledNotificationsVisiblyDifferent(Builder first, Builder second) {
2910         if (first.getStyle() == null) {
2911             return second.getStyle() != null;
2912         }
2913         if (second.getStyle() == null) {
2914             return true;
2915         }
2916         return first.getStyle().areNotificationsVisiblyDifferent(second.getStyle());
2917     }
2918 
2919     /**
2920      * @hide
2921      */
areRemoteViewsChanged(Builder first, Builder second)2922     public static boolean areRemoteViewsChanged(Builder first, Builder second) {
2923         if (!Objects.equals(first.usesStandardHeader(), second.usesStandardHeader())) {
2924             return true;
2925         }
2926 
2927         if (areRemoteViewsChanged(first.mN.contentView, second.mN.contentView)) {
2928             return true;
2929         }
2930         if (areRemoteViewsChanged(first.mN.bigContentView, second.mN.bigContentView)) {
2931             return true;
2932         }
2933         if (areRemoteViewsChanged(first.mN.headsUpContentView, second.mN.headsUpContentView)) {
2934             return true;
2935         }
2936 
2937         return false;
2938     }
2939 
areRemoteViewsChanged(RemoteViews first, RemoteViews second)2940     private static boolean areRemoteViewsChanged(RemoteViews first, RemoteViews second) {
2941         if (first == null && second == null) {
2942             return false;
2943         }
2944         if (first == null && second != null || first != null && second == null) {
2945             return true;
2946         }
2947 
2948         if (!Objects.equals(first.getLayoutId(), second.getLayoutId())) {
2949             return true;
2950         }
2951 
2952         if (!Objects.equals(first.getSequenceNumber(), second.getSequenceNumber())) {
2953             return true;
2954         }
2955 
2956         return false;
2957     }
2958 
2959     /**
2960      * Parcelling creates multiple copies of objects in {@code extras}. Fix them.
2961      * <p>
2962      * For backwards compatibility {@code extras} holds some references to "real" member data such
2963      * as {@link getLargeIcon()} which is mirrored by {@link #EXTRA_LARGE_ICON}. This is mostly
2964      * fine as long as the object stays in one process.
2965      * <p>
2966      * However, once the notification goes into a parcel each reference gets marshalled separately,
2967      * wasting memory. Especially with large images on Auto and TV, this is worth fixing.
2968      */
fixDuplicateExtras()2969     private void fixDuplicateExtras() {
2970         if (extras != null) {
2971             fixDuplicateExtra(mLargeIcon, EXTRA_LARGE_ICON);
2972         }
2973     }
2974 
2975     /**
2976      * If we find an extra that's exactly the same as one of the "real" fields but refers to a
2977      * separate object, replace it with the field's version to avoid holding duplicate copies.
2978      */
fixDuplicateExtra(@ullable Parcelable original, @NonNull String extraName)2979     private void fixDuplicateExtra(@Nullable Parcelable original, @NonNull String extraName) {
2980         if (original != null && extras.getParcelable(extraName) != null) {
2981             extras.putParcelable(extraName, original);
2982         }
2983     }
2984 
2985     /**
2986      * Sets the {@link #contentView} field to be a view with the standard "Latest Event"
2987      * layout.
2988      *
2989      * <p>Uses the {@link #icon} and {@link #when} fields to set the icon and time fields
2990      * in the view.</p>
2991      * @param context       The context for your application / activity.
2992      * @param contentTitle The title that goes in the expanded entry.
2993      * @param contentText  The text that goes in the expanded entry.
2994      * @param contentIntent The intent to launch when the user clicks the expanded notification.
2995      * If this is an activity, it must include the
2996      * {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK} flag, which requires
2997      * that you take care of task management as described in the
2998      * <a href="{@docRoot}guide/topics/fundamentals/tasks-and-back-stack.html">Tasks and Back
2999      * Stack</a> document.
3000      *
3001      * @deprecated Use {@link Builder} instead.
3002      * @removed
3003      */
3004     @Deprecated
setLatestEventInfo(Context context, CharSequence contentTitle, CharSequence contentText, PendingIntent contentIntent)3005     public void setLatestEventInfo(Context context,
3006             CharSequence contentTitle, CharSequence contentText, PendingIntent contentIntent) {
3007         if (context.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1){
3008             Log.e(TAG, "setLatestEventInfo() is deprecated and you should feel deprecated.",
3009                     new Throwable());
3010         }
3011 
3012         if (context.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N) {
3013             extras.putBoolean(EXTRA_SHOW_WHEN, true);
3014         }
3015 
3016         // ensure that any information already set directly is preserved
3017         final Notification.Builder builder = new Notification.Builder(context, this);
3018 
3019         // now apply the latestEventInfo fields
3020         if (contentTitle != null) {
3021             builder.setContentTitle(contentTitle);
3022         }
3023         if (contentText != null) {
3024             builder.setContentText(contentText);
3025         }
3026         builder.setContentIntent(contentIntent);
3027 
3028         builder.build(); // callers expect this notification to be ready to use
3029     }
3030 
3031     /**
3032      * Sets the token used for background operations for the pending intents associated with this
3033      * notification.
3034      *
3035      * This token is automatically set during deserialization for you, you usually won't need to
3036      * call this unless you want to change the existing token, if any.
3037      *
3038      * @hide
3039      */
setAllowlistToken(@ullable IBinder token)3040     public void setAllowlistToken(@Nullable IBinder token) {
3041         mWhitelistToken = token;
3042     }
3043 
3044     /**
3045      * @hide
3046      */
addFieldsFromContext(Context context, Notification notification)3047     public static void addFieldsFromContext(Context context, Notification notification) {
3048         addFieldsFromContext(context.getApplicationInfo(), notification);
3049     }
3050 
3051     /**
3052      * @hide
3053      */
addFieldsFromContext(ApplicationInfo ai, Notification notification)3054     public static void addFieldsFromContext(ApplicationInfo ai, Notification notification) {
3055         notification.extras.putParcelable(EXTRA_BUILDER_APPLICATION_INFO, ai);
3056     }
3057 
3058     /**
3059      * @hide
3060      */
dumpDebug(ProtoOutputStream proto, long fieldId)3061     public void dumpDebug(ProtoOutputStream proto, long fieldId) {
3062         long token = proto.start(fieldId);
3063         proto.write(NotificationProto.CHANNEL_ID, getChannelId());
3064         proto.write(NotificationProto.HAS_TICKER_TEXT, this.tickerText != null);
3065         proto.write(NotificationProto.FLAGS, this.flags);
3066         proto.write(NotificationProto.COLOR, this.color);
3067         proto.write(NotificationProto.CATEGORY, this.category);
3068         proto.write(NotificationProto.GROUP_KEY, this.mGroupKey);
3069         proto.write(NotificationProto.SORT_KEY, this.mSortKey);
3070         if (this.actions != null) {
3071             proto.write(NotificationProto.ACTION_LENGTH, this.actions.length);
3072         }
3073         if (this.visibility >= VISIBILITY_SECRET && this.visibility <= VISIBILITY_PUBLIC) {
3074             proto.write(NotificationProto.VISIBILITY, this.visibility);
3075         }
3076         if (publicVersion != null) {
3077             publicVersion.dumpDebug(proto, NotificationProto.PUBLIC_VERSION);
3078         }
3079         proto.end(token);
3080     }
3081 
3082     @Override
toString()3083     public String toString() {
3084         StringBuilder sb = new StringBuilder();
3085         sb.append("Notification(channel=");
3086         sb.append(getChannelId());
3087         sb.append(" shortcut=");
3088         sb.append(getShortcutId());
3089         sb.append(" contentView=");
3090         if (contentView != null) {
3091             sb.append(contentView.getPackage());
3092             sb.append("/0x");
3093             sb.append(Integer.toHexString(contentView.getLayoutId()));
3094         } else {
3095             sb.append("null");
3096         }
3097         sb.append(" vibrate=");
3098         if ((this.defaults & DEFAULT_VIBRATE) != 0) {
3099             sb.append("default");
3100         } else if (this.vibrate != null) {
3101             int N = this.vibrate.length-1;
3102             sb.append("[");
3103             for (int i=0; i<N; i++) {
3104                 sb.append(this.vibrate[i]);
3105                 sb.append(',');
3106             }
3107             if (N != -1) {
3108                 sb.append(this.vibrate[N]);
3109             }
3110             sb.append("]");
3111         } else {
3112             sb.append("null");
3113         }
3114         sb.append(" sound=");
3115         if ((this.defaults & DEFAULT_SOUND) != 0) {
3116             sb.append("default");
3117         } else if (this.sound != null) {
3118             sb.append(this.sound.toString());
3119         } else {
3120             sb.append("null");
3121         }
3122         if (this.tickerText != null) {
3123             sb.append(" tick");
3124         }
3125         sb.append(" defaults=0x");
3126         sb.append(Integer.toHexString(this.defaults));
3127         sb.append(" flags=0x");
3128         sb.append(Integer.toHexString(this.flags));
3129         sb.append(String.format(" color=0x%08x", this.color));
3130         if (this.category != null) {
3131             sb.append(" category=");
3132             sb.append(this.category);
3133         }
3134         if (this.mGroupKey != null) {
3135             sb.append(" groupKey=");
3136             sb.append(this.mGroupKey);
3137         }
3138         if (this.mSortKey != null) {
3139             sb.append(" sortKey=");
3140             sb.append(this.mSortKey);
3141         }
3142         if (actions != null) {
3143             sb.append(" actions=");
3144             sb.append(actions.length);
3145         }
3146         sb.append(" vis=");
3147         sb.append(visibilityToString(this.visibility));
3148         if (this.publicVersion != null) {
3149             sb.append(" publicVersion=");
3150             sb.append(publicVersion.toString());
3151         }
3152         if (this.mLocusId != null) {
3153             sb.append(" locusId=");
3154             sb.append(this.mLocusId); // LocusId.toString() is PII safe.
3155         }
3156         sb.append(")");
3157         return sb.toString();
3158     }
3159 
3160     /**
3161      * {@hide}
3162      */
visibilityToString(int vis)3163     public static String visibilityToString(int vis) {
3164         switch (vis) {
3165             case VISIBILITY_PRIVATE:
3166                 return "PRIVATE";
3167             case VISIBILITY_PUBLIC:
3168                 return "PUBLIC";
3169             case VISIBILITY_SECRET:
3170                 return "SECRET";
3171             default:
3172                 return "UNKNOWN(" + String.valueOf(vis) + ")";
3173         }
3174     }
3175 
3176     /**
3177      * {@hide}
3178      */
priorityToString(@riority int pri)3179     public static String priorityToString(@Priority int pri) {
3180         switch (pri) {
3181             case PRIORITY_MIN:
3182                 return "MIN";
3183             case PRIORITY_LOW:
3184                 return "LOW";
3185             case PRIORITY_DEFAULT:
3186                 return "DEFAULT";
3187             case PRIORITY_HIGH:
3188                 return "HIGH";
3189             case PRIORITY_MAX:
3190                 return "MAX";
3191             default:
3192                 return "UNKNOWN(" + String.valueOf(pri) + ")";
3193         }
3194     }
3195 
3196     /**
3197      * @hide
3198      */
hasCompletedProgress()3199     public boolean hasCompletedProgress() {
3200         // not a progress notification; can't be complete
3201         if (!extras.containsKey(EXTRA_PROGRESS)
3202                 || !extras.containsKey(EXTRA_PROGRESS_MAX)) {
3203             return false;
3204         }
3205         // many apps use max 0 for 'indeterminate'; not complete
3206         if (extras.getInt(EXTRA_PROGRESS_MAX) == 0) {
3207             return false;
3208         }
3209         return extras.getInt(EXTRA_PROGRESS) == extras.getInt(EXTRA_PROGRESS_MAX);
3210     }
3211 
3212     /** @removed */
3213     @Deprecated
getChannel()3214     public String getChannel() {
3215         return mChannelId;
3216     }
3217 
3218     /**
3219      * Returns the id of the channel this notification posts to.
3220      */
getChannelId()3221     public String getChannelId() {
3222         return mChannelId;
3223     }
3224 
3225     /** @removed */
3226     @Deprecated
getTimeout()3227     public long getTimeout() {
3228         return mTimeout;
3229     }
3230 
3231     /**
3232      * Returns the duration from posting after which this notification should be canceled by the
3233      * system, if it's not canceled already.
3234      */
getTimeoutAfter()3235     public long getTimeoutAfter() {
3236         return mTimeout;
3237     }
3238 
3239     /**
3240      * Returns what icon should be shown for this notification if it is being displayed in a
3241      * Launcher that supports badging. Will be one of {@link #BADGE_ICON_NONE},
3242      * {@link #BADGE_ICON_SMALL}, or {@link #BADGE_ICON_LARGE}.
3243      */
getBadgeIconType()3244     public int getBadgeIconType() {
3245         return mBadgeIcon;
3246     }
3247 
3248     /**
3249      * Returns the {@link ShortcutInfo#getId() id} that this notification supersedes, if any.
3250      *
3251      * <p>Used by some Launchers that display notification content to hide shortcuts that duplicate
3252      * notifications.
3253      */
getShortcutId()3254     public String getShortcutId() {
3255         return mShortcutId;
3256     }
3257 
3258     /**
3259      * Gets the {@link LocusId} associated with this notification.
3260      *
3261      * <p>Used by the device's intelligence services to correlate objects (such as
3262      * {@link ShortcutInfo} and {@link ContentCaptureContext}) that are correlated.
3263      */
3264     @Nullable
getLocusId()3265     public LocusId getLocusId() {
3266         return mLocusId;
3267     }
3268 
3269     /**
3270      * Returns the settings text provided to {@link Builder#setSettingsText(CharSequence)}.
3271      */
getSettingsText()3272     public CharSequence getSettingsText() {
3273         return mSettingsText;
3274     }
3275 
3276     /**
3277      * Returns which type of notifications in a group are responsible for audibly alerting the
3278      * user. See {@link #GROUP_ALERT_ALL}, {@link #GROUP_ALERT_CHILDREN},
3279      * {@link #GROUP_ALERT_SUMMARY}.
3280      */
getGroupAlertBehavior()3281     public @GroupAlertBehavior int getGroupAlertBehavior() {
3282         return mGroupAlertBehavior;
3283     }
3284 
3285     /**
3286      * Returns the bubble metadata that will be used to display app content in a floating window
3287      * over the existing foreground activity.
3288      */
3289     @Nullable
getBubbleMetadata()3290     public BubbleMetadata getBubbleMetadata() {
3291         return mBubbleMetadata;
3292     }
3293 
3294     /**
3295      * Sets the {@link BubbleMetadata} for this notification.
3296      * @hide
3297      */
setBubbleMetadata(BubbleMetadata data)3298     public void setBubbleMetadata(BubbleMetadata data) {
3299         mBubbleMetadata = data;
3300     }
3301 
3302     /**
3303      * Returns whether the platform is allowed (by the app developer) to generate contextual actions
3304      * for this notification.
3305      */
getAllowSystemGeneratedContextualActions()3306     public boolean getAllowSystemGeneratedContextualActions() {
3307         return mAllowSystemGeneratedContextualActions;
3308     }
3309 
3310     /**
3311      * The small icon representing this notification in the status bar and content view.
3312      *
3313      * @return the small icon representing this notification.
3314      *
3315      * @see Builder#getSmallIcon()
3316      * @see Builder#setSmallIcon(Icon)
3317      */
getSmallIcon()3318     public Icon getSmallIcon() {
3319         return mSmallIcon;
3320     }
3321 
3322     /**
3323      * Used when notifying to clean up legacy small icons.
3324      * @hide
3325      */
3326     @UnsupportedAppUsage
setSmallIcon(Icon icon)3327     public void setSmallIcon(Icon icon) {
3328         mSmallIcon = icon;
3329     }
3330 
3331     /**
3332      * The large icon shown in this notification's content view.
3333      * @see Builder#getLargeIcon()
3334      * @see Builder#setLargeIcon(Icon)
3335      */
getLargeIcon()3336     public Icon getLargeIcon() {
3337         return mLargeIcon;
3338     }
3339 
3340     /**
3341      * @hide
3342      */
3343     @UnsupportedAppUsage
isGroupSummary()3344     public boolean isGroupSummary() {
3345         return mGroupKey != null && (flags & FLAG_GROUP_SUMMARY) != 0;
3346     }
3347 
3348     /**
3349      * @hide
3350      */
3351     @UnsupportedAppUsage
isGroupChild()3352     public boolean isGroupChild() {
3353         return mGroupKey != null && (flags & FLAG_GROUP_SUMMARY) == 0;
3354     }
3355 
3356     /**
3357      * @hide
3358      */
suppressAlertingDueToGrouping()3359     public boolean suppressAlertingDueToGrouping() {
3360         if (isGroupSummary()
3361                 && getGroupAlertBehavior() == Notification.GROUP_ALERT_CHILDREN) {
3362             return true;
3363         } else if (isGroupChild()
3364                 && getGroupAlertBehavior() == Notification.GROUP_ALERT_SUMMARY) {
3365             return true;
3366         }
3367         return false;
3368     }
3369 
3370 
3371     /**
3372      * Finds and returns a remote input and its corresponding action.
3373      *
3374      * @param requiresFreeform requires the remoteinput to allow freeform or not.
3375      * @return the result pair, {@code null} if no result is found.
3376      */
3377     @Nullable
findRemoteInputActionPair(boolean requiresFreeform)3378     public Pair<RemoteInput, Action> findRemoteInputActionPair(boolean requiresFreeform) {
3379         if (actions == null) {
3380             return null;
3381         }
3382         for (Notification.Action action : actions) {
3383             if (action.getRemoteInputs() == null) {
3384                 continue;
3385             }
3386             RemoteInput resultRemoteInput = null;
3387             for (RemoteInput remoteInput : action.getRemoteInputs()) {
3388                 if (remoteInput.getAllowFreeFormInput() || !requiresFreeform) {
3389                     resultRemoteInput = remoteInput;
3390                 }
3391             }
3392             if (resultRemoteInput != null) {
3393                 return Pair.create(resultRemoteInput, action);
3394             }
3395         }
3396         return null;
3397     }
3398 
3399     /**
3400      * Returns the actions that are contextual (that is, suggested because of the content of the
3401      * notification) out of the actions in this notification.
3402      */
getContextualActions()3403     public @NonNull List<Notification.Action> getContextualActions() {
3404         if (actions == null) return Collections.emptyList();
3405 
3406         List<Notification.Action> contextualActions = new ArrayList<>();
3407         for (Notification.Action action : actions) {
3408             if (action.isContextual()) {
3409                 contextualActions.add(action);
3410             }
3411         }
3412         return contextualActions;
3413     }
3414 
3415     /**
3416      * Builder class for {@link Notification} objects.
3417      *
3418      * Provides a convenient way to set the various fields of a {@link Notification} and generate
3419      * content views using the platform's notification layout template. If your app supports
3420      * versions of Android as old as API level 4, you can instead use
3421      * {@link android.support.v4.app.NotificationCompat.Builder NotificationCompat.Builder},
3422      * available in the <a href="{@docRoot}tools/extras/support-library.html">Android Support
3423      * library</a>.
3424      *
3425      * <p>Example:
3426      *
3427      * <pre class="prettyprint">
3428      * Notification noti = new Notification.Builder(mContext)
3429      *         .setContentTitle(&quot;New mail from &quot; + sender.toString())
3430      *         .setContentText(subject)
3431      *         .setSmallIcon(R.drawable.new_mail)
3432      *         .setLargeIcon(aBitmap)
3433      *         .build();
3434      * </pre>
3435      */
3436     public static class Builder {
3437         /**
3438          * @hide
3439          */
3440         public static final String EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT =
3441                 "android.rebuild.contentViewActionCount";
3442         /**
3443          * @hide
3444          */
3445         public static final String EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT
3446                 = "android.rebuild.bigViewActionCount";
3447         /**
3448          * @hide
3449          */
3450         public static final String EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT
3451                 = "android.rebuild.hudViewActionCount";
3452 
3453         private static final boolean USE_ONLY_TITLE_IN_LOW_PRIORITY_SUMMARY =
3454                 SystemProperties.getBoolean("notifications.only_title", true);
3455 
3456         /**
3457          * The lightness difference that has to be added to the primary text color to obtain the
3458          * secondary text color when the background is light.
3459          */
3460         private static final int LIGHTNESS_TEXT_DIFFERENCE_LIGHT = 20;
3461 
3462         /**
3463          * The lightness difference that has to be added to the primary text color to obtain the
3464          * secondary text color when the background is dark.
3465          * A bit less then the above value, since it looks better on dark backgrounds.
3466          */
3467         private static final int LIGHTNESS_TEXT_DIFFERENCE_DARK = -10;
3468 
3469         private Context mContext;
3470         private Notification mN;
3471         private Bundle mUserExtras = new Bundle();
3472         private Style mStyle;
3473         @UnsupportedAppUsage
3474         private ArrayList<Action> mActions = new ArrayList<Action>(MAX_ACTION_BUTTONS);
3475         private ArrayList<Person> mPersonList = new ArrayList<>();
3476         private ContrastColorUtil mColorUtil;
3477         private boolean mIsLegacy;
3478         private boolean mIsLegacyInitialized;
3479 
3480         /**
3481          * Caches a contrast-enhanced version of {@link #mCachedContrastColorIsFor}.
3482          */
3483         private int mCachedContrastColor = COLOR_INVALID;
3484         private int mCachedContrastColorIsFor = COLOR_INVALID;
3485 
3486         /**
3487          * A neutral color color that can be used for icons.
3488          */
3489         private int mNeutralColor = COLOR_INVALID;
3490 
3491         /**
3492          * Caches an instance of StandardTemplateParams. Note that this may have been used before,
3493          * so make sure to call {@link StandardTemplateParams#reset()} before using it.
3494          */
3495         StandardTemplateParams mParams = new StandardTemplateParams();
3496         private int mTextColorsAreForBackground = COLOR_INVALID;
3497         private int mPrimaryTextColor = COLOR_INVALID;
3498         private int mSecondaryTextColor = COLOR_INVALID;
3499         private int mBackgroundColor = COLOR_INVALID;
3500         private int mForegroundColor = COLOR_INVALID;
3501         /**
3502          * A temporary location where actions are stored. If != null the view originally has action
3503          * but doesn't have any for this inflation.
3504          */
3505         private ArrayList<Action> mOriginalActions;
3506         private boolean mRebuildStyledRemoteViews;
3507 
3508         private boolean mTintActionButtons;
3509         private boolean mInNightMode;
3510 
3511         /**
3512          * Constructs a new Builder with the defaults:
3513          *
3514          * @param context
3515          *            A {@link Context} that will be used by the Builder to construct the
3516          *            RemoteViews. The Context will not be held past the lifetime of this Builder
3517          *            object.
3518          * @param channelId
3519          *            The constructed Notification will be posted on this
3520          *            {@link NotificationChannel}. To use a NotificationChannel, it must first be
3521          *            created using {@link NotificationManager#createNotificationChannel}.
3522          */
Builder(Context context, String channelId)3523         public Builder(Context context, String channelId) {
3524             this(context, (Notification) null);
3525             mN.mChannelId = channelId;
3526         }
3527 
3528         /**
3529          * @deprecated use {@link #Builder(Context, String)}
3530          * instead. All posted Notifications must specify a NotificationChannel Id.
3531          */
3532         @Deprecated
Builder(Context context)3533         public Builder(Context context) {
3534             this(context, (Notification) null);
3535         }
3536 
3537         /**
3538          * @hide
3539          */
Builder(Context context, Notification toAdopt)3540         public Builder(Context context, Notification toAdopt) {
3541             mContext = context;
3542             Resources res = mContext.getResources();
3543             mTintActionButtons = res.getBoolean(R.bool.config_tintNotificationActionButtons);
3544 
3545             if (res.getBoolean(R.bool.config_enableNightMode)) {
3546                 Configuration currentConfig = res.getConfiguration();
3547                 mInNightMode = (currentConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK)
3548                         == Configuration.UI_MODE_NIGHT_YES;
3549             }
3550 
3551             if (toAdopt == null) {
3552                 mN = new Notification();
3553                 if (context.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N) {
3554                     mN.extras.putBoolean(EXTRA_SHOW_WHEN, true);
3555                 }
3556                 mN.priority = PRIORITY_DEFAULT;
3557                 mN.visibility = VISIBILITY_PRIVATE;
3558             } else {
3559                 mN = toAdopt;
3560                 if (mN.actions != null) {
3561                     Collections.addAll(mActions, mN.actions);
3562                 }
3563 
3564                 if (mN.extras.containsKey(EXTRA_PEOPLE_LIST)) {
3565                     ArrayList<Person> people = mN.extras.getParcelableArrayList(EXTRA_PEOPLE_LIST);
3566                     mPersonList.addAll(people);
3567                 }
3568 
3569                 if (mN.getSmallIcon() == null && mN.icon != 0) {
3570                     setSmallIcon(mN.icon);
3571                 }
3572 
3573                 if (mN.getLargeIcon() == null && mN.largeIcon != null) {
3574                     setLargeIcon(mN.largeIcon);
3575                 }
3576 
3577                 String templateClass = mN.extras.getString(EXTRA_TEMPLATE);
3578                 if (!TextUtils.isEmpty(templateClass)) {
3579                     final Class<? extends Style> styleClass
3580                             = getNotificationStyleClass(templateClass);
3581                     if (styleClass == null) {
3582                         Log.d(TAG, "Unknown style class: " + templateClass);
3583                     } else {
3584                         try {
3585                             final Constructor<? extends Style> ctor =
3586                                     styleClass.getDeclaredConstructor();
3587                             ctor.setAccessible(true);
3588                             final Style style = ctor.newInstance();
3589                             style.restoreFromExtras(mN.extras);
3590 
3591                             if (style != null) {
3592                                 setStyle(style);
3593                             }
3594                         } catch (Throwable t) {
3595                             Log.e(TAG, "Could not create Style", t);
3596                         }
3597                     }
3598                 }
3599             }
3600         }
3601 
getColorUtil()3602         private ContrastColorUtil getColorUtil() {
3603             if (mColorUtil == null) {
3604                 mColorUtil = ContrastColorUtil.getInstance(mContext);
3605             }
3606             return mColorUtil;
3607         }
3608 
3609         /**
3610          * From Android 11, messaging notifications (those that use {@link MessagingStyle}) that
3611          * use this method to link to a published long-lived sharing shortcut may appear in a
3612          * dedicated Conversation section of the shade and may show configuration options that
3613          * are unique to conversations. This behavior should be reserved for person to person(s)
3614          * conversations where there is a likely social obligation for an individual to respond.
3615          * <p>
3616          * For example, the following are some examples of notifications that belong in the
3617          * conversation space:
3618          * <ul>
3619          * <li>1:1 conversations between two individuals</li>
3620          * <li>Group conversations between individuals where everyone can contribute</li>
3621          * </ul>
3622          * And the following are some examples of notifications that do not belong in the
3623          * conversation space:
3624          * <ul>
3625          * <li>Advertisements from a bot (even if personal and contextualized)</li>
3626          * <li>Engagement notifications from a bot</li>
3627          * <li>Directional conversations where there is an active speaker and many passive
3628          * individuals</li>
3629          * <li>Stream / posting updates from other individuals</li>
3630          * <li>Email, document comments, or other conversation types that are not real-time</li>
3631          * </ul>
3632          * </p>
3633          *
3634          * <p>
3635          * Additionally, this method can be used for all types of notifications to mark this
3636          * notification as duplicative of a Launcher shortcut. Launchers that show badges or
3637          * notification content may then suppress the shortcut in favor of the content of this
3638          * notification.
3639          * <p>
3640          * If this notification has {@link BubbleMetadata} attached that was created with
3641          * a shortcutId a check will be performed to ensure the shortcutId supplied to bubble
3642          * metadata matches the shortcutId set here, if one was set. If the shortcutId's were
3643          * specified but do not match, an exception is thrown.
3644          *
3645          * @param shortcutId the {@link ShortcutInfo#getId() id} of the shortcut this notification
3646          *                   is linked to
3647          *
3648          * @see BubbleMetadata.Builder#Builder(String)
3649          */
3650         @NonNull
setShortcutId(String shortcutId)3651         public Builder setShortcutId(String shortcutId) {
3652             mN.mShortcutId = shortcutId;
3653             return this;
3654         }
3655 
3656         /**
3657          * Sets the {@link LocusId} associated with this notification.
3658          *
3659          * <p>This method should be called when the {@link LocusId} is used in other places (such
3660          * as {@link ShortcutInfo} and {@link ContentCaptureContext}) so the device's intelligence
3661          * services can correlate them.
3662          */
3663         @NonNull
setLocusId(@ullable LocusId locusId)3664         public Builder setLocusId(@Nullable LocusId locusId) {
3665             mN.mLocusId = locusId;
3666             return this;
3667         }
3668 
3669         /**
3670          * Sets which icon to display as a badge for this notification.
3671          *
3672          * Must be one of {@link #BADGE_ICON_NONE}, {@link #BADGE_ICON_SMALL},
3673          * {@link #BADGE_ICON_LARGE}.
3674          *
3675          * Note: This value might be ignored, for launchers that don't support badge icons.
3676          */
3677         @NonNull
setBadgeIconType(int icon)3678         public Builder setBadgeIconType(int icon) {
3679             mN.mBadgeIcon = icon;
3680             return this;
3681         }
3682 
3683         /**
3684          * Sets the group alert behavior for this notification. Use this method to mute this
3685          * notification if alerts for this notification's group should be handled by a different
3686          * notification. This is only applicable for notifications that belong to a
3687          * {@link #setGroup(String) group}. This must be called on all notifications you want to
3688          * mute. For example, if you want only the summary of your group to make noise, all
3689          * children in the group should have the group alert behavior {@link #GROUP_ALERT_SUMMARY}.
3690          *
3691          * <p> The default value is {@link #GROUP_ALERT_ALL}.</p>
3692          */
3693         @NonNull
setGroupAlertBehavior(@roupAlertBehavior int groupAlertBehavior)3694         public Builder setGroupAlertBehavior(@GroupAlertBehavior int groupAlertBehavior) {
3695             mN.mGroupAlertBehavior = groupAlertBehavior;
3696             return this;
3697         }
3698 
3699         /**
3700          * Sets the {@link BubbleMetadata} that will be used to display app content in a floating
3701          * window over the existing foreground activity.
3702          *
3703          * <p>This data will be ignored unless the notification is posted to a channel that
3704          * allows {@link NotificationChannel#canBubble() bubbles}.</p>
3705          *
3706          * <p>Notifications allowed to bubble that have valid bubble metadata will display in
3707          * collapsed state outside of the notification shade on unlocked devices. When a user
3708          * interacts with the collapsed state, the bubble intent will be invoked and displayed.</p>
3709          */
3710         @NonNull
setBubbleMetadata(@ullable BubbleMetadata data)3711         public Builder setBubbleMetadata(@Nullable BubbleMetadata data) {
3712             mN.mBubbleMetadata = data;
3713             return this;
3714         }
3715 
3716         /** @removed */
3717         @Deprecated
setChannel(String channelId)3718         public Builder setChannel(String channelId) {
3719             mN.mChannelId = channelId;
3720             return this;
3721         }
3722 
3723         /**
3724          * Specifies the channel the notification should be delivered on.
3725          */
3726         @NonNull
setChannelId(String channelId)3727         public Builder setChannelId(String channelId) {
3728             mN.mChannelId = channelId;
3729             return this;
3730         }
3731 
3732         /** @removed */
3733         @Deprecated
setTimeout(long durationMs)3734         public Builder setTimeout(long durationMs) {
3735             mN.mTimeout = durationMs;
3736             return this;
3737         }
3738 
3739         /**
3740          * Specifies a duration in milliseconds after which this notification should be canceled,
3741          * if it is not already canceled.
3742          */
3743         @NonNull
setTimeoutAfter(long durationMs)3744         public Builder setTimeoutAfter(long durationMs) {
3745             mN.mTimeout = durationMs;
3746             return this;
3747         }
3748 
3749         /**
3750          * Add a timestamp pertaining to the notification (usually the time the event occurred).
3751          *
3752          * For apps targeting {@link android.os.Build.VERSION_CODES#N} and above, this time is not
3753          * shown anymore by default and must be opted into by using
3754          * {@link android.app.Notification.Builder#setShowWhen(boolean)}
3755          *
3756          * @see Notification#when
3757          */
3758         @NonNull
setWhen(long when)3759         public Builder setWhen(long when) {
3760             mN.when = when;
3761             return this;
3762         }
3763 
3764         /**
3765          * Control whether the timestamp set with {@link #setWhen(long) setWhen} is shown
3766          * in the content view.
3767          * For apps targeting {@link android.os.Build.VERSION_CODES#N} and above, this defaults to
3768          * {@code false}. For earlier apps, the default is {@code true}.
3769          */
3770         @NonNull
setShowWhen(boolean show)3771         public Builder setShowWhen(boolean show) {
3772             mN.extras.putBoolean(EXTRA_SHOW_WHEN, show);
3773             return this;
3774         }
3775 
3776         /**
3777          * Show the {@link Notification#when} field as a stopwatch.
3778          *
3779          * Instead of presenting <code>when</code> as a timestamp, the notification will show an
3780          * automatically updating display of the minutes and seconds since <code>when</code>.
3781          *
3782          * Useful when showing an elapsed time (like an ongoing phone call).
3783          *
3784          * The counter can also be set to count down to <code>when</code> when using
3785          * {@link #setChronometerCountDown(boolean)}.
3786          *
3787          * @see android.widget.Chronometer
3788          * @see Notification#when
3789          * @see #setChronometerCountDown(boolean)
3790          */
3791         @NonNull
setUsesChronometer(boolean b)3792         public Builder setUsesChronometer(boolean b) {
3793             mN.extras.putBoolean(EXTRA_SHOW_CHRONOMETER, b);
3794             return this;
3795         }
3796 
3797         /**
3798          * Sets the Chronometer to count down instead of counting up.
3799          *
3800          * <p>This is only relevant if {@link #setUsesChronometer(boolean)} has been set to true.
3801          * If it isn't set the chronometer will count up.
3802          *
3803          * @see #setUsesChronometer(boolean)
3804          */
3805         @NonNull
setChronometerCountDown(boolean countDown)3806         public Builder setChronometerCountDown(boolean countDown) {
3807             mN.extras.putBoolean(EXTRA_CHRONOMETER_COUNT_DOWN, countDown);
3808             return this;
3809         }
3810 
3811         /**
3812          * Set the small icon resource, which will be used to represent the notification in the
3813          * status bar.
3814          *
3815 
3816          * The platform template for the expanded view will draw this icon in the left, unless a
3817          * {@link #setLargeIcon(Bitmap) large icon} has also been specified, in which case the small
3818          * icon will be moved to the right-hand side.
3819          *
3820 
3821          * @param icon
3822          *            A resource ID in the application's package of the drawable to use.
3823          * @see Notification#icon
3824          */
3825         @NonNull
setSmallIcon(@rawableRes int icon)3826         public Builder setSmallIcon(@DrawableRes int icon) {
3827             return setSmallIcon(icon != 0
3828                     ? Icon.createWithResource(mContext, icon)
3829                     : null);
3830         }
3831 
3832         /**
3833          * A variant of {@link #setSmallIcon(int) setSmallIcon(int)} that takes an additional
3834          * level parameter for when the icon is a {@link android.graphics.drawable.LevelListDrawable
3835          * LevelListDrawable}.
3836          *
3837          * @param icon A resource ID in the application's package of the drawable to use.
3838          * @param level The level to use for the icon.
3839          *
3840          * @see Notification#icon
3841          * @see Notification#iconLevel
3842          */
3843         @NonNull
setSmallIcon(@rawableRes int icon, int level)3844         public Builder setSmallIcon(@DrawableRes int icon, int level) {
3845             mN.iconLevel = level;
3846             return setSmallIcon(icon);
3847         }
3848 
3849         /**
3850          * Set the small icon, which will be used to represent the notification in the
3851          * status bar and content view (unless overridden there by a
3852          * {@link #setLargeIcon(Bitmap) large icon}).
3853          *
3854          * @param icon An Icon object to use.
3855          * @see Notification#icon
3856          */
3857         @NonNull
setSmallIcon(Icon icon)3858         public Builder setSmallIcon(Icon icon) {
3859             mN.setSmallIcon(icon);
3860             if (icon != null && icon.getType() == Icon.TYPE_RESOURCE) {
3861                 mN.icon = icon.getResId();
3862             }
3863             return this;
3864         }
3865 
3866         /**
3867          * Set the first line of text in the platform notification template.
3868          */
3869         @NonNull
setContentTitle(CharSequence title)3870         public Builder setContentTitle(CharSequence title) {
3871             mN.extras.putCharSequence(EXTRA_TITLE, safeCharSequence(title));
3872             return this;
3873         }
3874 
3875         /**
3876          * Set the second line of text in the platform notification template.
3877          */
3878         @NonNull
setContentText(CharSequence text)3879         public Builder setContentText(CharSequence text) {
3880             mN.extras.putCharSequence(EXTRA_TEXT, safeCharSequence(text));
3881             return this;
3882         }
3883 
3884         /**
3885          * This provides some additional information that is displayed in the notification. No
3886          * guarantees are given where exactly it is displayed.
3887          *
3888          * <p>This information should only be provided if it provides an essential
3889          * benefit to the understanding of the notification. The more text you provide the
3890          * less readable it becomes. For example, an email client should only provide the account
3891          * name here if more than one email account has been added.</p>
3892          *
3893          * <p>As of {@link android.os.Build.VERSION_CODES#N} this information is displayed in the
3894          * notification header area.
3895          *
3896          * On Android versions before {@link android.os.Build.VERSION_CODES#N}
3897          * this will be shown in the third line of text in the platform notification template.
3898          * You should not be using {@link #setProgress(int, int, boolean)} at the
3899          * same time on those versions; they occupy the same place.
3900          * </p>
3901          */
3902         @NonNull
setSubText(CharSequence text)3903         public Builder setSubText(CharSequence text) {
3904             mN.extras.putCharSequence(EXTRA_SUB_TEXT, safeCharSequence(text));
3905             return this;
3906         }
3907 
3908         /**
3909          * Provides text that will appear as a link to your application's settings.
3910          *
3911          * <p>This text does not appear within notification {@link Style templates} but may
3912          * appear when the user uses an affordance to learn more about the notification.
3913          * Additionally, this text will not appear unless you provide a valid link target by
3914          * handling {@link #INTENT_CATEGORY_NOTIFICATION_PREFERENCES}.
3915          *
3916          * <p>This text is meant to be concise description about what the user can customize
3917          * when they click on this link. The recommended maximum length is 40 characters.
3918          * @param text
3919          * @return
3920          */
3921         @NonNull
setSettingsText(CharSequence text)3922         public Builder setSettingsText(CharSequence text) {
3923             mN.mSettingsText = safeCharSequence(text);
3924             return this;
3925         }
3926 
3927         /**
3928          * Set the remote input history.
3929          *
3930          * This should be set to the most recent inputs that have been sent
3931          * through a {@link RemoteInput} of this Notification and cleared once the it is no
3932          * longer relevant (e.g. for chat notifications once the other party has responded).
3933          *
3934          * The most recent input must be stored at the 0 index, the second most recent at the
3935          * 1 index, etc. Note that the system will limit both how far back the inputs will be shown
3936          * and how much of each individual input is shown.
3937          *
3938          * <p>Note: The reply text will only be shown on notifications that have least one action
3939          * with a {@code RemoteInput}.</p>
3940          */
3941         @NonNull
setRemoteInputHistory(CharSequence[] text)3942         public Builder setRemoteInputHistory(CharSequence[] text) {
3943             if (text == null) {
3944                 mN.extras.putCharSequenceArray(EXTRA_REMOTE_INPUT_HISTORY, null);
3945             } else {
3946                 final int itemCount = Math.min(MAX_REPLY_HISTORY, text.length);
3947                 CharSequence[] safe = new CharSequence[itemCount];
3948                 RemoteInputHistoryItem[] items = new RemoteInputHistoryItem[itemCount];
3949                 for (int i = 0; i < itemCount; i++) {
3950                     safe[i] = safeCharSequence(text[i]);
3951                     items[i] = new RemoteInputHistoryItem(text[i]);
3952                 }
3953                 mN.extras.putCharSequenceArray(EXTRA_REMOTE_INPUT_HISTORY, safe);
3954 
3955                 // Also add these messages as structured history items.
3956                 mN.extras.putParcelableArray(EXTRA_REMOTE_INPUT_HISTORY_ITEMS, items);
3957             }
3958             return this;
3959         }
3960 
3961         /**
3962          * Set the remote input history, with support for embedding URIs and mime types for
3963          * images and other media.
3964          * @hide
3965          */
3966         @NonNull
setRemoteInputHistory(RemoteInputHistoryItem[] items)3967         public Builder setRemoteInputHistory(RemoteInputHistoryItem[] items) {
3968             if (items == null) {
3969                 mN.extras.putParcelableArray(EXTRA_REMOTE_INPUT_HISTORY_ITEMS, null);
3970             } else {
3971                 final int itemCount = Math.min(MAX_REPLY_HISTORY, items.length);
3972                 RemoteInputHistoryItem[] history = new RemoteInputHistoryItem[itemCount];
3973                 for (int i = 0; i < itemCount; i++) {
3974                     history[i] = items[i];
3975                 }
3976                 mN.extras.putParcelableArray(EXTRA_REMOTE_INPUT_HISTORY_ITEMS, history);
3977             }
3978             return this;
3979         }
3980 
3981         /**
3982          * Sets whether remote history entries view should have a spinner.
3983          * @hide
3984          */
3985         @NonNull
setShowRemoteInputSpinner(boolean showSpinner)3986         public Builder setShowRemoteInputSpinner(boolean showSpinner) {
3987             mN.extras.putBoolean(EXTRA_SHOW_REMOTE_INPUT_SPINNER, showSpinner);
3988             return this;
3989         }
3990 
3991         /**
3992          * Sets whether smart reply buttons should be hidden.
3993          * @hide
3994          */
3995         @NonNull
setHideSmartReplies(boolean hideSmartReplies)3996         public Builder setHideSmartReplies(boolean hideSmartReplies) {
3997             mN.extras.putBoolean(EXTRA_HIDE_SMART_REPLIES, hideSmartReplies);
3998             return this;
3999         }
4000 
4001         /**
4002          * Sets the number of items this notification represents. May be displayed as a badge count
4003          * for Launchers that support badging.
4004          */
4005         @NonNull
setNumber(int number)4006         public Builder setNumber(int number) {
4007             mN.number = number;
4008             return this;
4009         }
4010 
4011         /**
4012          * A small piece of additional information pertaining to this notification.
4013          *
4014          * The platform template will draw this on the last line of the notification, at the far
4015          * right (to the right of a smallIcon if it has been placed there).
4016          *
4017          * @deprecated use {@link #setSubText(CharSequence)} instead to set a text in the header.
4018          * For legacy apps targeting a version below {@link android.os.Build.VERSION_CODES#N} this
4019          * field will still show up, but the subtext will take precedence.
4020          */
4021         @Deprecated
setContentInfo(CharSequence info)4022         public Builder setContentInfo(CharSequence info) {
4023             mN.extras.putCharSequence(EXTRA_INFO_TEXT, safeCharSequence(info));
4024             return this;
4025         }
4026 
4027         /**
4028          * Set the progress this notification represents.
4029          *
4030          * The platform template will represent this using a {@link ProgressBar}.
4031          */
4032         @NonNull
setProgress(int max, int progress, boolean indeterminate)4033         public Builder setProgress(int max, int progress, boolean indeterminate) {
4034             mN.extras.putInt(EXTRA_PROGRESS, progress);
4035             mN.extras.putInt(EXTRA_PROGRESS_MAX, max);
4036             mN.extras.putBoolean(EXTRA_PROGRESS_INDETERMINATE, indeterminate);
4037             return this;
4038         }
4039 
4040         /**
4041          * Supply a custom RemoteViews to use instead of the platform template.
4042          *
4043          * Use {@link #setCustomContentView(RemoteViews)} instead.
4044          */
4045         @Deprecated
setContent(RemoteViews views)4046         public Builder setContent(RemoteViews views) {
4047             return setCustomContentView(views);
4048         }
4049 
4050         /**
4051          * Supply custom RemoteViews to use instead of the platform template.
4052          *
4053          * This will override the layout that would otherwise be constructed by this Builder
4054          * object.
4055          */
4056         @NonNull
setCustomContentView(RemoteViews contentView)4057         public Builder setCustomContentView(RemoteViews contentView) {
4058             mN.contentView = contentView;
4059             return this;
4060         }
4061 
4062         /**
4063          * Supply custom RemoteViews to use instead of the platform template in the expanded form.
4064          *
4065          * This will override the expanded layout that would otherwise be constructed by this
4066          * Builder object.
4067          */
4068         @NonNull
setCustomBigContentView(RemoteViews contentView)4069         public Builder setCustomBigContentView(RemoteViews contentView) {
4070             mN.bigContentView = contentView;
4071             return this;
4072         }
4073 
4074         /**
4075          * Supply custom RemoteViews to use instead of the platform template in the heads up dialog.
4076          *
4077          * This will override the heads-up layout that would otherwise be constructed by this
4078          * Builder object.
4079          */
4080         @NonNull
setCustomHeadsUpContentView(RemoteViews contentView)4081         public Builder setCustomHeadsUpContentView(RemoteViews contentView) {
4082             mN.headsUpContentView = contentView;
4083             return this;
4084         }
4085 
4086         /**
4087          * Supply a {@link PendingIntent} to be sent when the notification is clicked.
4088          *
4089          * As of {@link android.os.Build.VERSION_CODES#HONEYCOMB}, if this field is unset and you
4090          * have specified a custom RemoteViews with {@link #setContent(RemoteViews)}, you can use
4091          * {@link RemoteViews#setOnClickPendingIntent RemoteViews.setOnClickPendingIntent(int,PendingIntent)}
4092          * to assign PendingIntents to individual views in that custom layout (i.e., to create
4093          * clickable buttons inside the notification view).
4094          *
4095          * @see Notification#contentIntent Notification.contentIntent
4096          */
4097         @NonNull
setContentIntent(PendingIntent intent)4098         public Builder setContentIntent(PendingIntent intent) {
4099             mN.contentIntent = intent;
4100             return this;
4101         }
4102 
4103         /**
4104          * Supply a {@link PendingIntent} to send when the notification is cleared explicitly by the user.
4105          *
4106          * @see Notification#deleteIntent
4107          */
4108         @NonNull
setDeleteIntent(PendingIntent intent)4109         public Builder setDeleteIntent(PendingIntent intent) {
4110             mN.deleteIntent = intent;
4111             return this;
4112         }
4113 
4114         /**
4115          * An intent to launch instead of posting the notification to the status bar.
4116          * Only for use with extremely high-priority notifications demanding the user's
4117          * <strong>immediate</strong> attention, such as an incoming phone call or
4118          * alarm clock that the user has explicitly set to a particular time.
4119          * If this facility is used for something else, please give the user an option
4120          * to turn it off and use a normal notification, as this can be extremely
4121          * disruptive.
4122          *
4123          * <p>
4124          * The system UI may choose to display a heads-up notification, instead of
4125          * launching this intent, while the user is using the device.
4126          * </p>
4127          * <p>Apps targeting {@link Build.VERSION_CODES#Q} and above will have to request
4128          * a permission ({@link android.Manifest.permission#USE_FULL_SCREEN_INTENT}) in order to
4129          * use full screen intents.</p>
4130          *
4131          * @param intent The pending intent to launch.
4132          * @param highPriority Passing true will cause this notification to be sent
4133          *          even if other notifications are suppressed.
4134          *
4135          * @see Notification#fullScreenIntent
4136          */
4137         @NonNull
setFullScreenIntent(PendingIntent intent, boolean highPriority)4138         public Builder setFullScreenIntent(PendingIntent intent, boolean highPriority) {
4139             mN.fullScreenIntent = intent;
4140             setFlag(FLAG_HIGH_PRIORITY, highPriority);
4141             return this;
4142         }
4143 
4144         /**
4145          * Set the "ticker" text which is sent to accessibility services.
4146          *
4147          * @see Notification#tickerText
4148          */
4149         @NonNull
setTicker(CharSequence tickerText)4150         public Builder setTicker(CharSequence tickerText) {
4151             mN.tickerText = safeCharSequence(tickerText);
4152             return this;
4153         }
4154 
4155         /**
4156          * Obsolete version of {@link #setTicker(CharSequence)}.
4157          *
4158          */
4159         @Deprecated
setTicker(CharSequence tickerText, RemoteViews views)4160         public Builder setTicker(CharSequence tickerText, RemoteViews views) {
4161             setTicker(tickerText);
4162             // views is ignored
4163             return this;
4164         }
4165 
4166         /**
4167          * Add a large icon to the notification content view.
4168          *
4169          * In the platform template, this image will be shown on the left of the notification view
4170          * in place of the {@link #setSmallIcon(Icon) small icon} (which will be placed in a small
4171          * badge atop the large icon).
4172          */
4173         @NonNull
setLargeIcon(Bitmap b)4174         public Builder setLargeIcon(Bitmap b) {
4175             return setLargeIcon(b != null ? Icon.createWithBitmap(b) : null);
4176         }
4177 
4178         /**
4179          * Add a large icon to the notification content view.
4180          *
4181          * In the platform template, this image will be shown on the left of the notification view
4182          * in place of the {@link #setSmallIcon(Icon) small icon} (which will be placed in a small
4183          * badge atop the large icon).
4184          */
4185         @NonNull
setLargeIcon(Icon icon)4186         public Builder setLargeIcon(Icon icon) {
4187             mN.mLargeIcon = icon;
4188             mN.extras.putParcelable(EXTRA_LARGE_ICON, icon);
4189             return this;
4190         }
4191 
4192         /**
4193          * Set the sound to play.
4194          *
4195          * It will be played using the {@link #AUDIO_ATTRIBUTES_DEFAULT default audio attributes}
4196          * for notifications.
4197          *
4198          * @deprecated use {@link NotificationChannel#setSound(Uri, AudioAttributes)} instead.
4199          */
4200         @Deprecated
setSound(Uri sound)4201         public Builder setSound(Uri sound) {
4202             mN.sound = sound;
4203             mN.audioAttributes = AUDIO_ATTRIBUTES_DEFAULT;
4204             return this;
4205         }
4206 
4207         /**
4208          * Set the sound to play, along with a specific stream on which to play it.
4209          *
4210          * See {@link android.media.AudioManager} for the <code>STREAM_</code> constants.
4211          *
4212          * @deprecated use {@link NotificationChannel#setSound(Uri, AudioAttributes)}.
4213          */
4214         @Deprecated
setSound(Uri sound, int streamType)4215         public Builder setSound(Uri sound, int streamType) {
4216             PlayerBase.deprecateStreamTypeForPlayback(streamType, "Notification", "setSound()");
4217             mN.sound = sound;
4218             mN.audioStreamType = streamType;
4219             return this;
4220         }
4221 
4222         /**
4223          * Set the sound to play, along with specific {@link AudioAttributes audio attributes} to
4224          * use during playback.
4225          *
4226          * @deprecated use {@link NotificationChannel#setSound(Uri, AudioAttributes)} instead.
4227          * @see Notification#sound
4228          */
4229         @Deprecated
setSound(Uri sound, AudioAttributes audioAttributes)4230         public Builder setSound(Uri sound, AudioAttributes audioAttributes) {
4231             mN.sound = sound;
4232             mN.audioAttributes = audioAttributes;
4233             return this;
4234         }
4235 
4236         /**
4237          * Set the vibration pattern to use.
4238          *
4239          * See {@link android.os.Vibrator#vibrate(long[], int)} for a discussion of the
4240          * <code>pattern</code> parameter.
4241          *
4242          * <p>
4243          * A notification that vibrates is more likely to be presented as a heads-up notification.
4244          * </p>
4245          *
4246          * @deprecated use {@link NotificationChannel#setVibrationPattern(long[])} instead.
4247          * @see Notification#vibrate
4248          */
4249         @Deprecated
setVibrate(long[] pattern)4250         public Builder setVibrate(long[] pattern) {
4251             mN.vibrate = pattern;
4252             return this;
4253         }
4254 
4255         /**
4256          * Set the desired color for the indicator LED on the device, as well as the
4257          * blink duty cycle (specified in milliseconds).
4258          *
4259 
4260          * Not all devices will honor all (or even any) of these values.
4261          *
4262          * @deprecated use {@link NotificationChannel#enableLights(boolean)} instead.
4263          * @see Notification#ledARGB
4264          * @see Notification#ledOnMS
4265          * @see Notification#ledOffMS
4266          */
4267         @Deprecated
setLights(@olorInt int argb, int onMs, int offMs)4268         public Builder setLights(@ColorInt int argb, int onMs, int offMs) {
4269             mN.ledARGB = argb;
4270             mN.ledOnMS = onMs;
4271             mN.ledOffMS = offMs;
4272             if (onMs != 0 || offMs != 0) {
4273                 mN.flags |= FLAG_SHOW_LIGHTS;
4274             }
4275             return this;
4276         }
4277 
4278         /**
4279          * Set whether this is an "ongoing" notification.
4280          *
4281 
4282          * Ongoing notifications cannot be dismissed by the user, so your application or service
4283          * must take care of canceling them.
4284          *
4285 
4286          * They are typically used to indicate a background task that the user is actively engaged
4287          * with (e.g., playing music) or is pending in some way and therefore occupying the device
4288          * (e.g., a file download, sync operation, active network connection).
4289          *
4290 
4291          * @see Notification#FLAG_ONGOING_EVENT
4292          */
4293         @NonNull
setOngoing(boolean ongoing)4294         public Builder setOngoing(boolean ongoing) {
4295             setFlag(FLAG_ONGOING_EVENT, ongoing);
4296             return this;
4297         }
4298 
4299         /**
4300          * Set whether this notification should be colorized. When set, the color set with
4301          * {@link #setColor(int)} will be used as the background color of this notification.
4302          * <p>
4303          * This should only be used for high priority ongoing tasks like navigation, an ongoing
4304          * call, or other similarly high-priority events for the user.
4305          * <p>
4306          * For most styles, the coloring will only be applied if the notification is for a
4307          * foreground service notification.
4308          * However, for {@link MediaStyle} and {@link DecoratedMediaCustomViewStyle} notifications
4309          * that have a media session attached there is no such requirement.
4310          *
4311          * @see #setColor(int)
4312          * @see MediaStyle#setMediaSession(MediaSession.Token)
4313          */
4314         @NonNull
setColorized(boolean colorize)4315         public Builder setColorized(boolean colorize) {
4316             mN.extras.putBoolean(EXTRA_COLORIZED, colorize);
4317             return this;
4318         }
4319 
4320         /**
4321          * Set this flag if you would only like the sound, vibrate
4322          * and ticker to be played if the notification is not already showing.
4323          *
4324          * @see Notification#FLAG_ONLY_ALERT_ONCE
4325          */
4326         @NonNull
setOnlyAlertOnce(boolean onlyAlertOnce)4327         public Builder setOnlyAlertOnce(boolean onlyAlertOnce) {
4328             setFlag(FLAG_ONLY_ALERT_ONCE, onlyAlertOnce);
4329             return this;
4330         }
4331 
4332         /**
4333          * Make this notification automatically dismissed when the user touches it.
4334          *
4335          * @see Notification#FLAG_AUTO_CANCEL
4336          */
4337         @NonNull
setAutoCancel(boolean autoCancel)4338         public Builder setAutoCancel(boolean autoCancel) {
4339             setFlag(FLAG_AUTO_CANCEL, autoCancel);
4340             return this;
4341         }
4342 
4343         /**
4344          * Set whether or not this notification should not bridge to other devices.
4345          *
4346          * <p>Some notifications can be bridged to other devices for remote display.
4347          * This hint can be set to recommend this notification not be bridged.
4348          */
4349         @NonNull
setLocalOnly(boolean localOnly)4350         public Builder setLocalOnly(boolean localOnly) {
4351             setFlag(FLAG_LOCAL_ONLY, localOnly);
4352             return this;
4353         }
4354 
4355         /**
4356          * Set which notification properties will be inherited from system defaults.
4357          * <p>
4358          * The value should be one or more of the following fields combined with
4359          * bitwise-or:
4360          * {@link #DEFAULT_SOUND}, {@link #DEFAULT_VIBRATE}, {@link #DEFAULT_LIGHTS}.
4361          * <p>
4362          * For all default values, use {@link #DEFAULT_ALL}.
4363          *
4364          * @deprecated use {@link NotificationChannel#enableVibration(boolean)} and
4365          * {@link NotificationChannel#enableLights(boolean)} and
4366          * {@link NotificationChannel#setSound(Uri, AudioAttributes)} instead.
4367          */
4368         @Deprecated
setDefaults(int defaults)4369         public Builder setDefaults(int defaults) {
4370             mN.defaults = defaults;
4371             return this;
4372         }
4373 
4374         /**
4375          * Set the priority of this notification.
4376          *
4377          * @see Notification#priority
4378          * @deprecated use {@link NotificationChannel#setImportance(int)} instead.
4379          */
4380         @Deprecated
setPriority(@riority int pri)4381         public Builder setPriority(@Priority int pri) {
4382             mN.priority = pri;
4383             return this;
4384         }
4385 
4386         /**
4387          * Set the notification category.
4388          *
4389          * @see Notification#category
4390          */
4391         @NonNull
setCategory(String category)4392         public Builder setCategory(String category) {
4393             mN.category = category;
4394             return this;
4395         }
4396 
4397         /**
4398          * Add a person that is relevant to this notification.
4399          *
4400          * <P>
4401          * Depending on user preferences, this annotation may allow the notification to pass
4402          * through interruption filters, if this notification is of category {@link #CATEGORY_CALL}
4403          * or {@link #CATEGORY_MESSAGE}. The addition of people may also cause this notification to
4404          * appear more prominently in the user interface.
4405          * </P>
4406          *
4407          * <P>
4408          * The person should be specified by the {@code String} representation of a
4409          * {@link android.provider.ContactsContract.Contacts#CONTENT_LOOKUP_URI}.
4410          * </P>
4411          *
4412          * <P>The system will also attempt to resolve {@code mailto:} and {@code tel:} schema
4413          * URIs.  The path part of these URIs must exist in the contacts database, in the
4414          * appropriate column, or the reference will be discarded as invalid. Telephone schema
4415          * URIs will be resolved by {@link android.provider.ContactsContract.PhoneLookup}.
4416          * It is also possible to provide a URI with the schema {@code name:} in order to uniquely
4417          * identify a person without an entry in the contacts database.
4418          * </P>
4419          *
4420          * @param uri A URI for the person.
4421          * @see Notification#EXTRA_PEOPLE
4422          * @deprecated use {@link #addPerson(Person)}
4423          */
addPerson(String uri)4424         public Builder addPerson(String uri) {
4425             addPerson(new Person.Builder().setUri(uri).build());
4426             return this;
4427         }
4428 
4429         /**
4430          * Add a person that is relevant to this notification.
4431          *
4432          * <P>
4433          * Depending on user preferences, this annotation may allow the notification to pass
4434          * through interruption filters, if this notification is of category {@link #CATEGORY_CALL}
4435          * or {@link #CATEGORY_MESSAGE}. The addition of people may also cause this notification to
4436          * appear more prominently in the user interface.
4437          * </P>
4438          *
4439          * <P>
4440          * A person should usually contain a uri in order to benefit from the ranking boost.
4441          * However, even if no uri is provided, it's beneficial to provide other people in the
4442          * notification, such that listeners and voice only devices can announce and handle them
4443          * properly.
4444          * </P>
4445          *
4446          * @param person the person to add.
4447          * @see Notification#EXTRA_PEOPLE_LIST
4448          */
4449         @NonNull
addPerson(Person person)4450         public Builder addPerson(Person person) {
4451             mPersonList.add(person);
4452             return this;
4453         }
4454 
4455         /**
4456          * Set this notification to be part of a group of notifications sharing the same key.
4457          * Grouped notifications may display in a cluster or stack on devices which
4458          * support such rendering.
4459          *
4460          * <p>To make this notification the summary for its group, also call
4461          * {@link #setGroupSummary}. A sort order can be specified for group members by using
4462          * {@link #setSortKey}.
4463          * @param groupKey The group key of the group.
4464          * @return this object for method chaining
4465          */
4466         @NonNull
setGroup(String groupKey)4467         public Builder setGroup(String groupKey) {
4468             mN.mGroupKey = groupKey;
4469             return this;
4470         }
4471 
4472         /**
4473          * Set this notification to be the group summary for a group of notifications.
4474          * Grouped notifications may display in a cluster or stack on devices which
4475          * support such rendering. If thereRequires a group key also be set using {@link #setGroup}.
4476          * The group summary may be suppressed if too few notifications are included in the group.
4477          * @param isGroupSummary Whether this notification should be a group summary.
4478          * @return this object for method chaining
4479          */
4480         @NonNull
setGroupSummary(boolean isGroupSummary)4481         public Builder setGroupSummary(boolean isGroupSummary) {
4482             setFlag(FLAG_GROUP_SUMMARY, isGroupSummary);
4483             return this;
4484         }
4485 
4486         /**
4487          * Set a sort key that orders this notification among other notifications from the
4488          * same package. This can be useful if an external sort was already applied and an app
4489          * would like to preserve this. Notifications will be sorted lexicographically using this
4490          * value, although providing different priorities in addition to providing sort key may
4491          * cause this value to be ignored.
4492          *
4493          * <p>This sort key can also be used to order members of a notification group. See
4494          * {@link #setGroup}.
4495          *
4496          * @see String#compareTo(String)
4497          */
4498         @NonNull
setSortKey(String sortKey)4499         public Builder setSortKey(String sortKey) {
4500             mN.mSortKey = sortKey;
4501             return this;
4502         }
4503 
4504         /**
4505          * Merge additional metadata into this notification.
4506          *
4507          * <p>Values within the Bundle will replace existing extras values in this Builder.
4508          *
4509          * @see Notification#extras
4510          */
4511         @NonNull
addExtras(Bundle extras)4512         public Builder addExtras(Bundle extras) {
4513             if (extras != null) {
4514                 mUserExtras.putAll(extras);
4515             }
4516             return this;
4517         }
4518 
4519         /**
4520          * Set metadata for this notification.
4521          *
4522          * <p>A reference to the Bundle is held for the lifetime of this Builder, and the Bundle's
4523          * current contents are copied into the Notification each time {@link #build()} is
4524          * called.
4525          *
4526          * <p>Replaces any existing extras values with those from the provided Bundle.
4527          * Use {@link #addExtras} to merge in metadata instead.
4528          *
4529          * @see Notification#extras
4530          */
4531         @NonNull
setExtras(Bundle extras)4532         public Builder setExtras(Bundle extras) {
4533             if (extras != null) {
4534                 mUserExtras = extras;
4535             }
4536             return this;
4537         }
4538 
4539         /**
4540          * Get the current metadata Bundle used by this notification Builder.
4541          *
4542          * <p>The returned Bundle is shared with this Builder.
4543          *
4544          * <p>The current contents of this Bundle are copied into the Notification each time
4545          * {@link #build()} is called.
4546          *
4547          * @see Notification#extras
4548          */
getExtras()4549         public Bundle getExtras() {
4550             return mUserExtras;
4551         }
4552 
getAllExtras()4553         private Bundle getAllExtras() {
4554             final Bundle saveExtras = (Bundle) mUserExtras.clone();
4555             saveExtras.putAll(mN.extras);
4556             return saveExtras;
4557         }
4558 
4559         /**
4560          * Add an action to this notification. Actions are typically displayed by
4561          * the system as a button adjacent to the notification content.
4562          * <p>
4563          * Every action must have an icon (32dp square and matching the
4564          * <a href="{@docRoot}design/style/iconography.html#action-bar">Holo
4565          * Dark action bar</a> visual style), a textual label, and a {@link PendingIntent}.
4566          * <p>
4567          * A notification in its expanded form can display up to 3 actions, from left to right in
4568          * the order they were added. Actions will not be displayed when the notification is
4569          * collapsed, however, so be sure that any essential functions may be accessed by the user
4570          * in some other way (for example, in the Activity pointed to by {@link #contentIntent}).
4571          *
4572          * @param icon Resource ID of a drawable that represents the action.
4573          * @param title Text describing the action.
4574          * @param intent PendingIntent to be fired when the action is invoked.
4575          *
4576          * @deprecated Use {@link #addAction(Action)} instead.
4577          */
4578         @Deprecated
addAction(int icon, CharSequence title, PendingIntent intent)4579         public Builder addAction(int icon, CharSequence title, PendingIntent intent) {
4580             mActions.add(new Action(icon, safeCharSequence(title), intent));
4581             return this;
4582         }
4583 
4584         /**
4585          * Add an action to this notification. Actions are typically displayed by
4586          * the system as a button adjacent to the notification content.
4587          * <p>
4588          * Every action must have an icon (32dp square and matching the
4589          * <a href="{@docRoot}design/style/iconography.html#action-bar">Holo
4590          * Dark action bar</a> visual style), a textual label, and a {@link PendingIntent}.
4591          * <p>
4592          * A notification in its expanded form can display up to 3 actions, from left to right in
4593          * the order they were added. Actions will not be displayed when the notification is
4594          * collapsed, however, so be sure that any essential functions may be accessed by the user
4595          * in some other way (for example, in the Activity pointed to by {@link #contentIntent}).
4596          *
4597          * @param action The action to add.
4598          */
4599         @NonNull
addAction(Action action)4600         public Builder addAction(Action action) {
4601             if (action != null) {
4602                 mActions.add(action);
4603             }
4604             return this;
4605         }
4606 
4607         /**
4608          * Alter the complete list of actions attached to this notification.
4609          * @see #addAction(Action).
4610          *
4611          * @param actions
4612          * @return
4613          */
4614         @NonNull
setActions(Action... actions)4615         public Builder setActions(Action... actions) {
4616             mActions.clear();
4617             for (int i = 0; i < actions.length; i++) {
4618                 if (actions[i] != null) {
4619                     mActions.add(actions[i]);
4620                 }
4621             }
4622             return this;
4623         }
4624 
4625         /**
4626          * Add a rich notification style to be applied at build time.
4627          *
4628          * @param style Object responsible for modifying the notification style.
4629          */
4630         @NonNull
setStyle(Style style)4631         public Builder setStyle(Style style) {
4632             if (mStyle != style) {
4633                 mStyle = style;
4634                 if (mStyle != null) {
4635                     mStyle.setBuilder(this);
4636                     mN.extras.putString(EXTRA_TEMPLATE, style.getClass().getName());
4637                 }  else {
4638                     mN.extras.remove(EXTRA_TEMPLATE);
4639                 }
4640             }
4641             return this;
4642         }
4643 
4644         /**
4645          * Returns the style set by {@link #setStyle(Style)}.
4646          */
getStyle()4647         public Style getStyle() {
4648             return mStyle;
4649         }
4650 
4651         /**
4652          * Specify the value of {@link #visibility}.
4653          *
4654          * @return The same Builder.
4655          */
4656         @NonNull
setVisibility(@isibility int visibility)4657         public Builder setVisibility(@Visibility int visibility) {
4658             mN.visibility = visibility;
4659             return this;
4660         }
4661 
4662         /**
4663          * Supply a replacement Notification whose contents should be shown in insecure contexts
4664          * (i.e. atop the secure lockscreen). See {@link #visibility} and {@link #VISIBILITY_PUBLIC}.
4665          * @param n A replacement notification, presumably with some or all info redacted.
4666          * @return The same Builder.
4667          */
4668         @NonNull
setPublicVersion(Notification n)4669         public Builder setPublicVersion(Notification n) {
4670             if (n != null) {
4671                 mN.publicVersion = new Notification();
4672                 n.cloneInto(mN.publicVersion, /*heavy=*/ true);
4673             } else {
4674                 mN.publicVersion = null;
4675             }
4676             return this;
4677         }
4678 
4679         /**
4680          * Apply an extender to this notification builder. Extenders may be used to add
4681          * metadata or change options on this builder.
4682          */
4683         @NonNull
extend(Extender extender)4684         public Builder extend(Extender extender) {
4685             extender.extend(this);
4686             return this;
4687         }
4688 
4689         /**
4690          * Set the value for a notification flag
4691          *
4692          * @param mask Bit mask of the flag
4693          * @param value Status (on/off) of the flag
4694          *
4695          * @return The same Builder.
4696          */
4697         @NonNull
setFlag(@otificationFlags int mask, boolean value)4698         public Builder setFlag(@NotificationFlags int mask, boolean value) {
4699             if (value) {
4700                 mN.flags |= mask;
4701             } else {
4702                 mN.flags &= ~mask;
4703             }
4704             return this;
4705         }
4706 
4707         /**
4708          * Sets {@link Notification#color}.
4709          *
4710          * @param argb The accent color to use
4711          *
4712          * @return The same Builder.
4713          */
4714         @NonNull
setColor(@olorInt int argb)4715         public Builder setColor(@ColorInt int argb) {
4716             mN.color = argb;
4717             sanitizeColor();
4718             return this;
4719         }
4720 
getProfileBadgeDrawable()4721         private Drawable getProfileBadgeDrawable() {
4722             if (mContext.getUserId() == UserHandle.USER_SYSTEM) {
4723                 // This user can never be a badged profile,
4724                 // and also includes USER_ALL system notifications.
4725                 return null;
4726             }
4727             // Note: This assumes that the current user can read the profile badge of the
4728             // originating user.
4729             return mContext.getPackageManager().getUserBadgeForDensityNoBackground(
4730                     new UserHandle(mContext.getUserId()), 0);
4731         }
4732 
getProfileBadge()4733         private Bitmap getProfileBadge() {
4734             Drawable badge = getProfileBadgeDrawable();
4735             if (badge == null) {
4736                 return null;
4737             }
4738             final int size = mContext.getResources().getDimensionPixelSize(
4739                     R.dimen.notification_badge_size);
4740             Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
4741             Canvas canvas = new Canvas(bitmap);
4742             badge.setBounds(0, 0, size, size);
4743             badge.draw(canvas);
4744             return bitmap;
4745         }
4746 
bindProfileBadge(RemoteViews contentView, StandardTemplateParams p)4747         private void bindProfileBadge(RemoteViews contentView, StandardTemplateParams p) {
4748             Bitmap profileBadge = getProfileBadge();
4749 
4750             if (profileBadge != null) {
4751                 contentView.setImageViewBitmap(R.id.profile_badge, profileBadge);
4752                 contentView.setViewVisibility(R.id.profile_badge, View.VISIBLE);
4753                 if (isColorized(p)) {
4754                     contentView.setDrawableTint(R.id.profile_badge, false,
4755                             getPrimaryTextColor(p), PorterDuff.Mode.SRC_ATOP);
4756                 }
4757             }
4758         }
4759 
bindAlertedIcon(RemoteViews contentView, StandardTemplateParams p)4760         private void bindAlertedIcon(RemoteViews contentView, StandardTemplateParams p) {
4761             contentView.setDrawableTint(
4762                     R.id.alerted_icon,
4763                     false /* targetBackground */,
4764                     getNeutralColor(p),
4765                     PorterDuff.Mode.SRC_ATOP);
4766         }
4767 
4768         /**
4769          * @hide
4770          */
usesStandardHeader()4771         public boolean usesStandardHeader() {
4772             if (mN.mUsesStandardHeader) {
4773                 return true;
4774             }
4775             if (mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.N) {
4776                 if (mN.contentView == null && mN.bigContentView == null) {
4777                     return true;
4778                 }
4779             }
4780             boolean contentViewUsesHeader = mN.contentView == null
4781                     || STANDARD_LAYOUTS.contains(mN.contentView.getLayoutId());
4782             boolean bigContentViewUsesHeader = mN.bigContentView == null
4783                     || STANDARD_LAYOUTS.contains(mN.bigContentView.getLayoutId());
4784             return contentViewUsesHeader && bigContentViewUsesHeader;
4785         }
4786 
resetStandardTemplate(RemoteViews contentView)4787         private void resetStandardTemplate(RemoteViews contentView) {
4788             resetNotificationHeader(contentView);
4789             contentView.setViewVisibility(R.id.right_icon, View.GONE);
4790             contentView.setViewVisibility(R.id.title, View.GONE);
4791             contentView.setTextViewText(R.id.title, null);
4792             contentView.setViewVisibility(R.id.text, View.GONE);
4793             contentView.setTextViewText(R.id.text, null);
4794             contentView.setViewVisibility(R.id.text_line_1, View.GONE);
4795             contentView.setTextViewText(R.id.text_line_1, null);
4796         }
4797 
4798         /**
4799          * Resets the notification header to its original state
4800          */
resetNotificationHeader(RemoteViews contentView)4801         private void resetNotificationHeader(RemoteViews contentView) {
4802             // Small icon doesn't need to be reset, as it's always set. Resetting would prevent
4803             // re-using the drawable when the notification is updated.
4804             contentView.setBoolean(R.id.notification_header, "setExpanded", false);
4805             contentView.setTextViewText(R.id.app_name_text, null);
4806             contentView.setViewVisibility(R.id.chronometer, View.GONE);
4807             contentView.setViewVisibility(R.id.header_text, View.GONE);
4808             contentView.setTextViewText(R.id.header_text, null);
4809             contentView.setViewVisibility(R.id.header_text_secondary, View.GONE);
4810             contentView.setTextViewText(R.id.header_text_secondary, null);
4811             contentView.setViewVisibility(R.id.header_text_divider, View.GONE);
4812             contentView.setViewVisibility(R.id.header_text_secondary_divider, View.GONE);
4813             contentView.setViewVisibility(R.id.time_divider, View.GONE);
4814             contentView.setViewVisibility(R.id.time, View.GONE);
4815             contentView.setImageViewIcon(R.id.profile_badge, null);
4816             contentView.setViewVisibility(R.id.profile_badge, View.GONE);
4817             mN.mUsesStandardHeader = false;
4818         }
4819 
applyStandardTemplate(int resId, TemplateBindResult result)4820         private RemoteViews applyStandardTemplate(int resId, TemplateBindResult result) {
4821             return applyStandardTemplate(resId, mParams.reset().fillTextsFrom(this),
4822                     result);
4823         }
4824 
applyStandardTemplate(int resId, StandardTemplateParams p, TemplateBindResult result)4825         private RemoteViews applyStandardTemplate(int resId, StandardTemplateParams p,
4826                 TemplateBindResult result) {
4827             RemoteViews contentView = new BuilderRemoteViews(mContext.getApplicationInfo(), resId);
4828 
4829             resetStandardTemplate(contentView);
4830 
4831             final Bundle ex = mN.extras;
4832             updateBackgroundColor(contentView, p);
4833             bindNotificationHeader(contentView, p);
4834             bindLargeIconAndReply(contentView, p, result);
4835             boolean showProgress = handleProgressBar(contentView, ex, p);
4836             if (p.title != null) {
4837                 contentView.setViewVisibility(R.id.title, View.VISIBLE);
4838                 contentView.setTextViewText(R.id.title, processTextSpans(p.title));
4839                 setTextViewColorPrimary(contentView, R.id.title, p);
4840                 contentView.setViewLayoutWidth(R.id.title, showProgress
4841                         ? ViewGroup.LayoutParams.WRAP_CONTENT
4842                         : ViewGroup.LayoutParams.MATCH_PARENT);
4843             }
4844             if (p.text != null) {
4845                 int textId = showProgress ? com.android.internal.R.id.text_line_1
4846                         : com.android.internal.R.id.text;
4847                 contentView.setTextViewText(textId, processTextSpans(p.text));
4848                 setTextViewColorSecondary(contentView, textId, p);
4849                 contentView.setViewVisibility(textId, View.VISIBLE);
4850             }
4851 
4852             setContentMinHeight(contentView, showProgress || mN.hasLargeIcon());
4853 
4854             return contentView;
4855         }
4856 
processTextSpans(CharSequence text)4857         private CharSequence processTextSpans(CharSequence text) {
4858             if (hasForegroundColor() || mInNightMode) {
4859                 return ContrastColorUtil.clearColorSpans(text);
4860             }
4861             return text;
4862         }
4863 
setTextViewColorPrimary(RemoteViews contentView, int id, StandardTemplateParams p)4864         private void setTextViewColorPrimary(RemoteViews contentView, int id,
4865                 StandardTemplateParams p) {
4866             ensureColors(p);
4867             contentView.setTextColor(id, mPrimaryTextColor);
4868         }
4869 
hasForegroundColor()4870         private boolean hasForegroundColor() {
4871             return mForegroundColor != COLOR_INVALID;
4872         }
4873 
4874         /**
4875          * Return the primary text color using the existing template params
4876          * @hide
4877          */
4878         @VisibleForTesting
getPrimaryTextColor()4879         public int getPrimaryTextColor() {
4880             return getPrimaryTextColor(mParams);
4881         }
4882 
4883         /**
4884          * @param p the template params to inflate this with
4885          * @return the primary text color
4886          * @hide
4887          */
4888         @VisibleForTesting
getPrimaryTextColor(StandardTemplateParams p)4889         public int getPrimaryTextColor(StandardTemplateParams p) {
4890             ensureColors(p);
4891             return mPrimaryTextColor;
4892         }
4893 
4894         /**
4895          * Return the secondary text color using the existing template params
4896          * @hide
4897          */
4898         @VisibleForTesting
getSecondaryTextColor()4899         public int getSecondaryTextColor() {
4900             return getSecondaryTextColor(mParams);
4901         }
4902 
4903         /**
4904          * @param p the template params to inflate this with
4905          * @return the secondary text color
4906          * @hide
4907          */
4908         @VisibleForTesting
getSecondaryTextColor(StandardTemplateParams p)4909         public int getSecondaryTextColor(StandardTemplateParams p) {
4910             ensureColors(p);
4911             return mSecondaryTextColor;
4912         }
4913 
setTextViewColorSecondary(RemoteViews contentView, int id, StandardTemplateParams p)4914         private void setTextViewColorSecondary(RemoteViews contentView, int id,
4915                 StandardTemplateParams p) {
4916             ensureColors(p);
4917             contentView.setTextColor(id, mSecondaryTextColor);
4918         }
4919 
ensureColors(StandardTemplateParams p)4920         private void ensureColors(StandardTemplateParams p) {
4921             int backgroundColor = getBackgroundColor(p);
4922             if (mPrimaryTextColor == COLOR_INVALID
4923                     || mSecondaryTextColor == COLOR_INVALID
4924                     || mTextColorsAreForBackground != backgroundColor) {
4925                 mTextColorsAreForBackground = backgroundColor;
4926                 if (!hasForegroundColor() || !isColorized(p)) {
4927                     mPrimaryTextColor = ContrastColorUtil.resolvePrimaryColor(mContext,
4928                             backgroundColor, mInNightMode);
4929                     mSecondaryTextColor = ContrastColorUtil.resolveSecondaryColor(mContext,
4930                             backgroundColor, mInNightMode);
4931                     if (backgroundColor != COLOR_DEFAULT && isColorized(p)) {
4932                         mPrimaryTextColor = ContrastColorUtil.findAlphaToMeetContrast(
4933                                 mPrimaryTextColor, backgroundColor, 4.5);
4934                         mSecondaryTextColor = ContrastColorUtil.findAlphaToMeetContrast(
4935                                 mSecondaryTextColor, backgroundColor, 4.5);
4936                     }
4937                 } else {
4938                     double backLum = ContrastColorUtil.calculateLuminance(backgroundColor);
4939                     double textLum = ContrastColorUtil.calculateLuminance(mForegroundColor);
4940                     double contrast = ContrastColorUtil.calculateContrast(mForegroundColor,
4941                             backgroundColor);
4942                     // We only respect the given colors if worst case Black or White still has
4943                     // contrast
4944                     boolean backgroundLight = backLum > textLum
4945                                     && satisfiesTextContrast(backgroundColor, Color.BLACK)
4946                             || backLum <= textLum
4947                                     && !satisfiesTextContrast(backgroundColor, Color.WHITE);
4948                     if (contrast < 4.5f) {
4949                         if (backgroundLight) {
4950                             mSecondaryTextColor = ContrastColorUtil.findContrastColor(
4951                                     mForegroundColor,
4952                                     backgroundColor,
4953                                     true /* findFG */,
4954                                     4.5f);
4955                             mPrimaryTextColor = ContrastColorUtil.changeColorLightness(
4956                                     mSecondaryTextColor, -LIGHTNESS_TEXT_DIFFERENCE_LIGHT);
4957                         } else {
4958                             mSecondaryTextColor =
4959                                     ContrastColorUtil.findContrastColorAgainstDark(
4960                                     mForegroundColor,
4961                                     backgroundColor,
4962                                     true /* findFG */,
4963                                     4.5f);
4964                             mPrimaryTextColor = ContrastColorUtil.changeColorLightness(
4965                                     mSecondaryTextColor, -LIGHTNESS_TEXT_DIFFERENCE_DARK);
4966                         }
4967                     } else {
4968                         mPrimaryTextColor = mForegroundColor;
4969                         mSecondaryTextColor = ContrastColorUtil.changeColorLightness(
4970                                 mPrimaryTextColor, backgroundLight ? LIGHTNESS_TEXT_DIFFERENCE_LIGHT
4971                                         : LIGHTNESS_TEXT_DIFFERENCE_DARK);
4972                         if (ContrastColorUtil.calculateContrast(mSecondaryTextColor,
4973                                 backgroundColor) < 4.5f) {
4974                             // oh well the secondary is not good enough
4975                             if (backgroundLight) {
4976                                 mSecondaryTextColor = ContrastColorUtil.findContrastColor(
4977                                         mSecondaryTextColor,
4978                                         backgroundColor,
4979                                         true /* findFG */,
4980                                         4.5f);
4981                             } else {
4982                                 mSecondaryTextColor
4983                                         = ContrastColorUtil.findContrastColorAgainstDark(
4984                                         mSecondaryTextColor,
4985                                         backgroundColor,
4986                                         true /* findFG */,
4987                                         4.5f);
4988                             }
4989                             mPrimaryTextColor = ContrastColorUtil.changeColorLightness(
4990                                     mSecondaryTextColor, backgroundLight
4991                                             ? -LIGHTNESS_TEXT_DIFFERENCE_LIGHT
4992                                             : -LIGHTNESS_TEXT_DIFFERENCE_DARK);
4993                         }
4994                     }
4995                 }
4996             }
4997         }
4998 
updateBackgroundColor(RemoteViews contentView, StandardTemplateParams p)4999         private void updateBackgroundColor(RemoteViews contentView,
5000                 StandardTemplateParams p) {
5001             if (isColorized(p)) {
5002                 contentView.setInt(R.id.status_bar_latest_event_content, "setBackgroundColor",
5003                         getBackgroundColor(p));
5004             } else {
5005                 // Clear it!
5006                 contentView.setInt(R.id.status_bar_latest_event_content, "setBackgroundResource",
5007                         0);
5008             }
5009         }
5010 
5011         /**
5012          * @param remoteView the remote view to update the minheight in
5013          * @param hasMinHeight does it have a mimHeight
5014          * @hide
5015          */
setContentMinHeight(RemoteViews remoteView, boolean hasMinHeight)5016         void setContentMinHeight(RemoteViews remoteView, boolean hasMinHeight) {
5017             int minHeight = 0;
5018             if (hasMinHeight) {
5019                 // we need to set the minHeight of the notification
5020                 minHeight = mContext.getResources().getDimensionPixelSize(
5021                         com.android.internal.R.dimen.notification_min_content_height);
5022             }
5023             remoteView.setInt(R.id.notification_main_column, "setMinimumHeight", minHeight);
5024         }
5025 
handleProgressBar(RemoteViews contentView, Bundle ex, StandardTemplateParams p)5026         private boolean handleProgressBar(RemoteViews contentView, Bundle ex,
5027                 StandardTemplateParams p) {
5028             final int max = ex.getInt(EXTRA_PROGRESS_MAX, 0);
5029             final int progress = ex.getInt(EXTRA_PROGRESS, 0);
5030             final boolean ind = ex.getBoolean(EXTRA_PROGRESS_INDETERMINATE);
5031             if (p.hasProgress && (max != 0 || ind)) {
5032                 contentView.setViewVisibility(com.android.internal.R.id.progress, View.VISIBLE);
5033                 contentView.setProgressBar(
5034                         R.id.progress, max, progress, ind);
5035                 contentView.setProgressBackgroundTintList(
5036                         R.id.progress, ColorStateList.valueOf(mContext.getColor(
5037                                 R.color.notification_progress_background_color)));
5038                 if (getRawColor(p) != COLOR_DEFAULT) {
5039                     int color = isColorized(p) ? getPrimaryTextColor(p) : resolveContrastColor(p);
5040                     ColorStateList colorStateList = ColorStateList.valueOf(color);
5041                     contentView.setProgressTintList(R.id.progress, colorStateList);
5042                     contentView.setProgressIndeterminateTintList(R.id.progress, colorStateList);
5043                 }
5044                 return true;
5045             } else {
5046                 contentView.setViewVisibility(R.id.progress, View.GONE);
5047                 return false;
5048             }
5049         }
5050 
bindLargeIconAndReply(RemoteViews contentView, StandardTemplateParams p, TemplateBindResult result)5051         private void bindLargeIconAndReply(RemoteViews contentView, StandardTemplateParams p,
5052                 TemplateBindResult result) {
5053             boolean largeIconShown = bindLargeIcon(contentView, p);
5054             boolean replyIconShown = bindReplyIcon(contentView, p);
5055             boolean iconContainerVisible = largeIconShown || replyIconShown;
5056             contentView.setViewVisibility(R.id.right_icon_container,
5057                     iconContainerVisible ? View.VISIBLE : View.GONE);
5058             int marginEnd = calculateMarginEnd(largeIconShown, replyIconShown);
5059             contentView.setViewLayoutMarginEnd(R.id.line1, marginEnd);
5060             contentView.setViewLayoutMarginEnd(R.id.text, marginEnd);
5061             contentView.setViewLayoutMarginEnd(R.id.progress, marginEnd);
5062             if (result != null) {
5063                 result.setIconMarginEnd(marginEnd);
5064                 result.setRightIconContainerVisible(iconContainerVisible);
5065             }
5066         }
5067 
calculateMarginEnd(boolean largeIconShown, boolean replyIconShown)5068         private int calculateMarginEnd(boolean largeIconShown, boolean replyIconShown) {
5069             int marginEnd = 0;
5070             int contentMargin = mContext.getResources().getDimensionPixelSize(
5071                     R.dimen.notification_content_margin_end);
5072             int iconSize = mContext.getResources().getDimensionPixelSize(
5073                     R.dimen.notification_right_icon_size);
5074             if (replyIconShown) {
5075                 // The size of the reply icon
5076                 marginEnd += iconSize;
5077 
5078                 int replyInset = mContext.getResources().getDimensionPixelSize(
5079                         R.dimen.notification_reply_inset);
5080                 // We're subtracting the inset of the reply icon to make sure it's
5081                 // aligned nicely on the right, and remove it from the following padding
5082                 marginEnd -= replyInset * 2;
5083             }
5084             if (largeIconShown) {
5085                 // adding size of the right icon
5086                 marginEnd += iconSize;
5087 
5088                 if (replyIconShown) {
5089                     // We also add some padding to the reply icon if it's around
5090                     marginEnd += contentMargin;
5091                 }
5092             }
5093             if (replyIconShown || largeIconShown) {
5094                 // The padding to the content
5095                 marginEnd += contentMargin;
5096             }
5097             return marginEnd;
5098         }
5099 
5100         /**
5101          * Bind the large icon.
5102          * @return if the largeIcon is visible
5103          */
bindLargeIcon(RemoteViews contentView, StandardTemplateParams p)5104         private boolean bindLargeIcon(RemoteViews contentView, StandardTemplateParams p) {
5105             if (mN.mLargeIcon == null && mN.largeIcon != null) {
5106                 mN.mLargeIcon = Icon.createWithBitmap(mN.largeIcon);
5107             }
5108             boolean showLargeIcon = mN.mLargeIcon != null && !p.hideLargeIcon;
5109             if (showLargeIcon) {
5110                 contentView.setViewVisibility(R.id.right_icon, View.VISIBLE);
5111                 contentView.setImageViewIcon(R.id.right_icon, mN.mLargeIcon);
5112                 processLargeLegacyIcon(mN.mLargeIcon, contentView, p);
5113             }
5114             return showLargeIcon;
5115         }
5116 
5117         /**
5118          * Bind the reply icon.
5119          * @return if the reply icon is visible
5120          */
bindReplyIcon(RemoteViews contentView, StandardTemplateParams p)5121         private boolean bindReplyIcon(RemoteViews contentView, StandardTemplateParams p) {
5122             boolean actionVisible = !p.hideReplyIcon;
5123             Action action = null;
5124             if (actionVisible) {
5125                 action = findReplyAction();
5126                 actionVisible = action != null;
5127             }
5128             if (actionVisible) {
5129                 contentView.setViewVisibility(R.id.reply_icon_action, View.VISIBLE);
5130                 contentView.setDrawableTint(R.id.reply_icon_action,
5131                         false /* targetBackground */,
5132                         getNeutralColor(p),
5133                         PorterDuff.Mode.SRC_ATOP);
5134                 contentView.setOnClickPendingIntent(R.id.reply_icon_action, action.actionIntent);
5135                 contentView.setRemoteInputs(R.id.reply_icon_action, action.mRemoteInputs);
5136             } else {
5137                 contentView.setRemoteInputs(R.id.reply_icon_action, null);
5138             }
5139             contentView.setViewVisibility(R.id.reply_icon_action,
5140                     actionVisible ? View.VISIBLE : View.GONE);
5141             return actionVisible;
5142         }
5143 
findReplyAction()5144         private Action findReplyAction() {
5145             ArrayList<Action> actions = mActions;
5146             if (mOriginalActions != null) {
5147                 actions = mOriginalActions;
5148             }
5149             int numActions = actions.size();
5150             for (int i = 0; i < numActions; i++) {
5151                 Action action = actions.get(i);
5152                 if (hasValidRemoteInput(action)) {
5153                     return action;
5154                 }
5155             }
5156             return null;
5157         }
5158 
bindNotificationHeader(RemoteViews contentView, StandardTemplateParams p)5159         private void bindNotificationHeader(RemoteViews contentView, StandardTemplateParams p) {
5160             bindSmallIcon(contentView, p);
5161             bindHeaderAppName(contentView, p);
5162             bindHeaderText(contentView, p);
5163             bindHeaderTextSecondary(contentView, p);
5164             bindHeaderChronometerAndTime(contentView, p);
5165             bindProfileBadge(contentView, p);
5166             bindAlertedIcon(contentView, p);
5167             bindActivePermissions(contentView, p);
5168             bindExpandButton(contentView, p);
5169             mN.mUsesStandardHeader = true;
5170         }
5171 
bindActivePermissions(RemoteViews contentView, StandardTemplateParams p)5172         private void bindActivePermissions(RemoteViews contentView, StandardTemplateParams p) {
5173             int color = getNeutralColor(p);
5174             contentView.setDrawableTint(R.id.camera, false, color, PorterDuff.Mode.SRC_ATOP);
5175             contentView.setDrawableTint(R.id.mic, false, color, PorterDuff.Mode.SRC_ATOP);
5176             contentView.setDrawableTint(R.id.overlay, false, color, PorterDuff.Mode.SRC_ATOP);
5177         }
5178 
bindExpandButton(RemoteViews contentView, StandardTemplateParams p)5179         private void bindExpandButton(RemoteViews contentView, StandardTemplateParams p) {
5180             int color = isColorized(p) ? getPrimaryTextColor(p) : getSecondaryTextColor(p);
5181             contentView.setDrawableTint(R.id.expand_button, false, color,
5182                     PorterDuff.Mode.SRC_ATOP);
5183             contentView.setInt(R.id.expand_button, "setOriginalNotificationColor",
5184                     color);
5185         }
5186 
bindHeaderChronometerAndTime(RemoteViews contentView, StandardTemplateParams p)5187         private void bindHeaderChronometerAndTime(RemoteViews contentView,
5188                 StandardTemplateParams p) {
5189             if (showsTimeOrChronometer()) {
5190                 contentView.setViewVisibility(R.id.time_divider, View.VISIBLE);
5191                 setTextViewColorSecondary(contentView, R.id.time_divider, p);
5192                 if (mN.extras.getBoolean(EXTRA_SHOW_CHRONOMETER)) {
5193                     contentView.setViewVisibility(R.id.chronometer, View.VISIBLE);
5194                     contentView.setLong(R.id.chronometer, "setBase",
5195                             mN.when + (SystemClock.elapsedRealtime() - System.currentTimeMillis()));
5196                     contentView.setBoolean(R.id.chronometer, "setStarted", true);
5197                     boolean countsDown = mN.extras.getBoolean(EXTRA_CHRONOMETER_COUNT_DOWN);
5198                     contentView.setChronometerCountDown(R.id.chronometer, countsDown);
5199                     setTextViewColorSecondary(contentView, R.id.chronometer, p);
5200                 } else {
5201                     contentView.setViewVisibility(R.id.time, View.VISIBLE);
5202                     contentView.setLong(R.id.time, "setTime", mN.when);
5203                     setTextViewColorSecondary(contentView, R.id.time, p);
5204                 }
5205             } else {
5206                 // We still want a time to be set but gone, such that we can show and hide it
5207                 // on demand in case it's a child notification without anything in the header
5208                 contentView.setLong(R.id.time, "setTime", mN.when != 0 ? mN.when : mN.creationTime);
5209             }
5210         }
5211 
bindHeaderText(RemoteViews contentView, StandardTemplateParams p)5212         private void bindHeaderText(RemoteViews contentView, StandardTemplateParams p) {
5213             CharSequence summaryText = p.summaryText;
5214             if (summaryText == null && mStyle != null && mStyle.mSummaryTextSet
5215                     && mStyle.hasSummaryInHeader()) {
5216                 summaryText = mStyle.mSummaryText;
5217             }
5218             if (summaryText == null
5219                     && mContext.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N
5220                     && mN.extras.getCharSequence(EXTRA_INFO_TEXT) != null) {
5221                 summaryText = mN.extras.getCharSequence(EXTRA_INFO_TEXT);
5222             }
5223             if (summaryText != null) {
5224                 // TODO: Remove the span entirely to only have the string with propper formating.
5225                 contentView.setTextViewText(R.id.header_text, processTextSpans(
5226                         processLegacyText(summaryText)));
5227                 setTextViewColorSecondary(contentView, R.id.header_text, p);
5228                 contentView.setViewVisibility(R.id.header_text, View.VISIBLE);
5229                 contentView.setViewVisibility(R.id.header_text_divider, View.VISIBLE);
5230                 setTextViewColorSecondary(contentView, R.id.header_text_divider, p);
5231             }
5232         }
5233 
bindHeaderTextSecondary(RemoteViews contentView, StandardTemplateParams p)5234         private void bindHeaderTextSecondary(RemoteViews contentView, StandardTemplateParams p) {
5235             if (!TextUtils.isEmpty(p.headerTextSecondary)) {
5236                 contentView.setTextViewText(R.id.header_text_secondary, processTextSpans(
5237                         processLegacyText(p.headerTextSecondary)));
5238                 setTextViewColorSecondary(contentView, R.id.header_text_secondary, p);
5239                 contentView.setViewVisibility(R.id.header_text_secondary, View.VISIBLE);
5240                 contentView.setViewVisibility(R.id.header_text_secondary_divider, View.VISIBLE);
5241                 setTextViewColorSecondary(contentView, R.id.header_text_secondary_divider, p);
5242             }
5243         }
5244 
5245         /**
5246          * @hide
5247          */
5248         @UnsupportedAppUsage
loadHeaderAppName()5249         public String loadHeaderAppName() {
5250             CharSequence name = null;
5251             final PackageManager pm = mContext.getPackageManager();
5252             if (mN.extras.containsKey(EXTRA_SUBSTITUTE_APP_NAME)) {
5253                 // only system packages which lump together a bunch of unrelated stuff
5254                 // may substitute a different name to make the purpose of the
5255                 // notification more clear. the correct package label should always
5256                 // be accessible via SystemUI.
5257                 final String pkg = mContext.getPackageName();
5258                 final String subName = mN.extras.getString(EXTRA_SUBSTITUTE_APP_NAME);
5259                 if (PackageManager.PERMISSION_GRANTED == pm.checkPermission(
5260                         android.Manifest.permission.SUBSTITUTE_NOTIFICATION_APP_NAME, pkg)) {
5261                     name = subName;
5262                 } else {
5263                     Log.w(TAG, "warning: pkg "
5264                             + pkg + " attempting to substitute app name '" + subName
5265                             + "' without holding perm "
5266                             + android.Manifest.permission.SUBSTITUTE_NOTIFICATION_APP_NAME);
5267                 }
5268             }
5269             if (TextUtils.isEmpty(name)) {
5270                 name = pm.getApplicationLabel(mContext.getApplicationInfo());
5271             }
5272             if (TextUtils.isEmpty(name)) {
5273                 // still nothing?
5274                 return null;
5275             }
5276 
5277             return String.valueOf(name);
5278         }
bindHeaderAppName(RemoteViews contentView, StandardTemplateParams p)5279         private void bindHeaderAppName(RemoteViews contentView, StandardTemplateParams p) {
5280             contentView.setTextViewText(R.id.app_name_text, loadHeaderAppName());
5281             if (isColorized(p)) {
5282                 setTextViewColorPrimary(contentView, R.id.app_name_text, p);
5283             } else {
5284                 contentView.setTextColor(R.id.app_name_text, getSecondaryTextColor(p));
5285             }
5286         }
5287 
isColorized(StandardTemplateParams p)5288         private boolean isColorized(StandardTemplateParams p) {
5289             return p.allowColorization && mN.isColorized();
5290         }
5291 
bindSmallIcon(RemoteViews contentView, StandardTemplateParams p)5292         private void bindSmallIcon(RemoteViews contentView, StandardTemplateParams p) {
5293             if (mN.mSmallIcon == null && mN.icon != 0) {
5294                 mN.mSmallIcon = Icon.createWithResource(mContext, mN.icon);
5295             }
5296             contentView.setImageViewIcon(R.id.icon, mN.mSmallIcon);
5297             contentView.setInt(R.id.icon, "setImageLevel", mN.iconLevel);
5298             processSmallIconColor(mN.mSmallIcon, contentView, p);
5299         }
5300 
5301         /**
5302          * @return true if the built notification will show the time or the chronometer; false
5303          *         otherwise
5304          */
showsTimeOrChronometer()5305         private boolean showsTimeOrChronometer() {
5306             return mN.showsTime() || mN.showsChronometer();
5307         }
5308 
resetStandardTemplateWithActions(RemoteViews big)5309         private void resetStandardTemplateWithActions(RemoteViews big) {
5310             // actions_container is only reset when there are no actions to avoid focus issues with
5311             // remote inputs.
5312             big.setViewVisibility(R.id.actions, View.GONE);
5313             big.removeAllViews(R.id.actions);
5314 
5315             big.setViewVisibility(R.id.notification_material_reply_container, View.GONE);
5316             big.setTextViewText(R.id.notification_material_reply_text_1, null);
5317             big.setViewVisibility(R.id.notification_material_reply_text_1_container, View.GONE);
5318             big.setViewVisibility(R.id.notification_material_reply_progress, View.GONE);
5319 
5320             big.setViewVisibility(R.id.notification_material_reply_text_2, View.GONE);
5321             big.setTextViewText(R.id.notification_material_reply_text_2, null);
5322             big.setViewVisibility(R.id.notification_material_reply_text_3, View.GONE);
5323             big.setTextViewText(R.id.notification_material_reply_text_3, null);
5324 
5325             big.setViewLayoutMarginBottomDimen(R.id.notification_action_list_margin_target,
5326                     R.dimen.notification_content_margin);
5327         }
5328 
applyStandardTemplateWithActions(int layoutId, TemplateBindResult result)5329         private RemoteViews applyStandardTemplateWithActions(int layoutId,
5330                 TemplateBindResult result) {
5331             return applyStandardTemplateWithActions(layoutId, mParams.reset().fillTextsFrom(this),
5332                     result);
5333         }
5334 
filterOutContextualActions( List<Notification.Action> actions)5335         private static List<Notification.Action> filterOutContextualActions(
5336                 List<Notification.Action> actions) {
5337             List<Notification.Action> nonContextualActions = new ArrayList<>();
5338             for (Notification.Action action : actions) {
5339                 if (!action.isContextual()) {
5340                     nonContextualActions.add(action);
5341                 }
5342             }
5343             return nonContextualActions;
5344         }
5345 
applyStandardTemplateWithActions(int layoutId, StandardTemplateParams p, TemplateBindResult result)5346         private RemoteViews applyStandardTemplateWithActions(int layoutId,
5347                 StandardTemplateParams p, TemplateBindResult result) {
5348             RemoteViews big = applyStandardTemplate(layoutId, p, result);
5349 
5350             resetStandardTemplateWithActions(big);
5351 
5352             boolean validRemoteInput = false;
5353 
5354             // In the UI contextual actions appear separately from the standard actions, so we
5355             // filter them out here.
5356             List<Notification.Action> nonContextualActions = filterOutContextualActions(mActions);
5357 
5358             int N = nonContextualActions.size();
5359             boolean emphazisedMode = mN.fullScreenIntent != null;
5360             big.setBoolean(R.id.actions, "setEmphasizedMode", emphazisedMode);
5361             if (N > 0) {
5362                 big.setViewVisibility(R.id.actions_container, View.VISIBLE);
5363                 big.setViewVisibility(R.id.actions, View.VISIBLE);
5364                 big.setViewLayoutMarginBottomDimen(R.id.notification_action_list_margin_target, 0);
5365                 if (N>MAX_ACTION_BUTTONS) N=MAX_ACTION_BUTTONS;
5366                 for (int i=0; i<N; i++) {
5367                     Action action = nonContextualActions.get(i);
5368 
5369                     boolean actionHasValidInput = hasValidRemoteInput(action);
5370                     validRemoteInput |= actionHasValidInput;
5371 
5372                     final RemoteViews button = generateActionButton(action, emphazisedMode, p);
5373                     if (actionHasValidInput && !emphazisedMode) {
5374                         // Clear the drawable
5375                         button.setInt(R.id.action0, "setBackgroundResource", 0);
5376                     }
5377                     big.addView(R.id.actions, button);
5378                 }
5379             } else {
5380                 big.setViewVisibility(R.id.actions_container, View.GONE);
5381             }
5382 
5383             RemoteInputHistoryItem[] replyText = getParcelableArrayFromBundle(
5384                     mN.extras, EXTRA_REMOTE_INPUT_HISTORY_ITEMS, RemoteInputHistoryItem.class);
5385             if (validRemoteInput && replyText != null && replyText.length > 0
5386                     && !TextUtils.isEmpty(replyText[0].getText())
5387                     && p.maxRemoteInputHistory > 0) {
5388                 boolean showSpinner = mN.extras.getBoolean(EXTRA_SHOW_REMOTE_INPUT_SPINNER);
5389                 big.setViewVisibility(R.id.notification_material_reply_container, View.VISIBLE);
5390                 big.setViewVisibility(R.id.notification_material_reply_text_1_container,
5391                         View.VISIBLE);
5392                 big.setTextViewText(R.id.notification_material_reply_text_1,
5393                         processTextSpans(replyText[0].getText()));
5394                 setTextViewColorSecondary(big, R.id.notification_material_reply_text_1, p);
5395                 big.setViewVisibility(R.id.notification_material_reply_progress,
5396                         showSpinner ? View.VISIBLE : View.GONE);
5397                 big.setProgressIndeterminateTintList(
5398                         R.id.notification_material_reply_progress,
5399                         ColorStateList.valueOf(
5400                                 isColorized(p) ? getPrimaryTextColor(p) : resolveContrastColor(p)));
5401 
5402                 if (replyText.length > 1 && !TextUtils.isEmpty(replyText[1].getText())
5403                         && p.maxRemoteInputHistory > 1) {
5404                     big.setViewVisibility(R.id.notification_material_reply_text_2, View.VISIBLE);
5405                     big.setTextViewText(R.id.notification_material_reply_text_2,
5406                             processTextSpans(replyText[1].getText()));
5407                     setTextViewColorSecondary(big, R.id.notification_material_reply_text_2, p);
5408 
5409                     if (replyText.length > 2 && !TextUtils.isEmpty(replyText[2].getText())
5410                             && p.maxRemoteInputHistory > 2) {
5411                         big.setViewVisibility(
5412                                 R.id.notification_material_reply_text_3, View.VISIBLE);
5413                         big.setTextViewText(R.id.notification_material_reply_text_3,
5414                                 processTextSpans(replyText[2].getText()));
5415                         setTextViewColorSecondary(big, R.id.notification_material_reply_text_3, p);
5416                     }
5417                 }
5418             }
5419 
5420             return big;
5421         }
5422 
hasValidRemoteInput(Action action)5423         private boolean hasValidRemoteInput(Action action) {
5424             if (TextUtils.isEmpty(action.title) || action.actionIntent == null) {
5425                 // Weird actions
5426                 return false;
5427             }
5428 
5429             RemoteInput[] remoteInputs = action.getRemoteInputs();
5430             if (remoteInputs == null) {
5431                 return false;
5432             }
5433 
5434             for (RemoteInput r : remoteInputs) {
5435                 CharSequence[] choices = r.getChoices();
5436                 if (r.getAllowFreeFormInput() || (choices != null && choices.length != 0)) {
5437                     return true;
5438                 }
5439             }
5440             return false;
5441         }
5442 
5443         /**
5444          * Construct a RemoteViews for the final 1U notification layout. In order:
5445          *   1. Custom contentView from the caller
5446          *   2. Style's proposed content view
5447          *   3. Standard template view
5448          */
createContentView()5449         public RemoteViews createContentView() {
5450             return createContentView(false /* increasedheight */ );
5451         }
5452 
5453         /**
5454          * Construct a RemoteViews for the smaller content view.
5455          *
5456          *   @param increasedHeight true if this layout be created with an increased height. Some
5457          *   styles may support showing more then just that basic 1U size
5458          *   and the system may decide to render important notifications
5459          *   slightly bigger even when collapsed.
5460          *
5461          *   @hide
5462          */
createContentView(boolean increasedHeight)5463         public RemoteViews createContentView(boolean increasedHeight) {
5464             if (mN.contentView != null && useExistingRemoteView()) {
5465                 return mN.contentView;
5466             } else if (mStyle != null) {
5467                 final RemoteViews styleView = mStyle.makeContentView(increasedHeight);
5468                 if (styleView != null) {
5469                     return styleView;
5470                 }
5471             }
5472             return applyStandardTemplate(getBaseLayoutResource(), null /* result */);
5473         }
5474 
useExistingRemoteView()5475         private boolean useExistingRemoteView() {
5476             return mStyle == null || (!mStyle.displayCustomViewInline()
5477                     && !mRebuildStyledRemoteViews);
5478         }
5479 
5480         /**
5481          * Construct a RemoteViews for the final big notification layout.
5482          */
createBigContentView()5483         public RemoteViews createBigContentView() {
5484             RemoteViews result = null;
5485             if (mN.bigContentView != null && useExistingRemoteView()) {
5486                 return mN.bigContentView;
5487             } else if (mStyle != null) {
5488                 result = mStyle.makeBigContentView();
5489                 hideLine1Text(result);
5490             } else if (mActions.size() != 0) {
5491                 result = applyStandardTemplateWithActions(getBigBaseLayoutResource(),
5492                         null /* result */);
5493             }
5494             makeHeaderExpanded(result);
5495             return result;
5496         }
5497 
5498         /**
5499          * Construct a RemoteViews for the final notification header only. This will not be
5500          * colorized.
5501          *
5502          * @hide
5503          */
makeNotificationHeader()5504         public RemoteViews makeNotificationHeader() {
5505             return makeNotificationHeader(mParams.reset().fillTextsFrom(this));
5506         }
5507 
5508         /**
5509          * Construct a RemoteViews for the final notification header only. This will not be
5510          * colorized.
5511          *
5512          * @param p the template params to inflate this with
5513          */
makeNotificationHeader(StandardTemplateParams p)5514         private RemoteViews makeNotificationHeader(StandardTemplateParams p) {
5515             // Headers on their own are never colorized
5516             p.disallowColorization();
5517             RemoteViews header = new BuilderRemoteViews(mContext.getApplicationInfo(),
5518                     R.layout.notification_template_header);
5519             resetNotificationHeader(header);
5520             bindNotificationHeader(header, p);
5521             return header;
5522         }
5523 
5524         /**
5525          * Construct a RemoteViews for the ambient version of the notification.
5526          *
5527          * @hide
5528          */
makeAmbientNotification()5529         public RemoteViews makeAmbientNotification() {
5530             RemoteViews headsUpContentView = createHeadsUpContentView(false /* increasedHeight */);
5531             if (headsUpContentView != null) {
5532                 return headsUpContentView;
5533             }
5534             return createContentView();
5535         }
5536 
hideLine1Text(RemoteViews result)5537         private void hideLine1Text(RemoteViews result) {
5538             if (result != null) {
5539                 result.setViewVisibility(R.id.text_line_1, View.GONE);
5540             }
5541         }
5542 
5543         /**
5544          * Adapt the Notification header if this view is used as an expanded view.
5545          *
5546          * @hide
5547          */
makeHeaderExpanded(RemoteViews result)5548         public static void makeHeaderExpanded(RemoteViews result) {
5549             if (result != null) {
5550                 result.setBoolean(R.id.notification_header, "setExpanded", true);
5551             }
5552         }
5553 
5554         /**
5555          * Construct a RemoteViews for the final heads-up notification layout.
5556          *
5557          * @param increasedHeight true if this layout be created with an increased height. Some
5558          * styles may support showing more then just that basic 1U size
5559          * and the system may decide to render important notifications
5560          * slightly bigger even when collapsed.
5561          *
5562          * @hide
5563          */
createHeadsUpContentView(boolean increasedHeight)5564         public RemoteViews createHeadsUpContentView(boolean increasedHeight) {
5565             if (mN.headsUpContentView != null && useExistingRemoteView()) {
5566                 return mN.headsUpContentView;
5567             } else if (mStyle != null) {
5568                 final RemoteViews styleView = mStyle.makeHeadsUpContentView(increasedHeight);
5569                 if (styleView != null) {
5570                     return styleView;
5571                 }
5572             } else if (mActions.size() == 0) {
5573                 return null;
5574             }
5575 
5576             // We only want at most a single remote input history to be shown here, otherwise
5577             // the content would become squished.
5578             StandardTemplateParams p = mParams.reset().fillTextsFrom(this)
5579                     .setMaxRemoteInputHistory(1);
5580             return applyStandardTemplateWithActions(getBigBaseLayoutResource(),
5581                     p,
5582                     null /* result */);
5583         }
5584 
5585         /**
5586          * Construct a RemoteViews for the final heads-up notification layout.
5587          */
createHeadsUpContentView()5588         public RemoteViews createHeadsUpContentView() {
5589             return createHeadsUpContentView(false /* useIncreasedHeight */);
5590         }
5591 
5592         /**
5593          * Construct a RemoteViews for the display in public contexts like on the lockscreen.
5594          *
5595          * @param isLowPriority is this notification low priority
5596          * @hide
5597          */
5598         @UnsupportedAppUsage
makePublicContentView(boolean isLowPriority)5599         public RemoteViews makePublicContentView(boolean isLowPriority) {
5600             if (mN.publicVersion != null) {
5601                 final Builder builder = recoverBuilder(mContext, mN.publicVersion);
5602                 return builder.createContentView();
5603             }
5604             Bundle savedBundle = mN.extras;
5605             Style style = mStyle;
5606             mStyle = null;
5607             Icon largeIcon = mN.mLargeIcon;
5608             mN.mLargeIcon = null;
5609             Bitmap largeIconLegacy = mN.largeIcon;
5610             mN.largeIcon = null;
5611             ArrayList<Action> actions = mActions;
5612             mActions = new ArrayList<>();
5613             Bundle publicExtras = new Bundle();
5614             publicExtras.putBoolean(EXTRA_SHOW_WHEN,
5615                     savedBundle.getBoolean(EXTRA_SHOW_WHEN));
5616             publicExtras.putBoolean(EXTRA_SHOW_CHRONOMETER,
5617                     savedBundle.getBoolean(EXTRA_SHOW_CHRONOMETER));
5618             publicExtras.putBoolean(EXTRA_CHRONOMETER_COUNT_DOWN,
5619                     savedBundle.getBoolean(EXTRA_CHRONOMETER_COUNT_DOWN));
5620             String appName = savedBundle.getString(EXTRA_SUBSTITUTE_APP_NAME);
5621             if (appName != null) {
5622                 publicExtras.putString(EXTRA_SUBSTITUTE_APP_NAME, appName);
5623             }
5624             mN.extras = publicExtras;
5625             RemoteViews view;
5626             StandardTemplateParams params = mParams.reset().fillTextsFrom(this);
5627             if (isLowPriority) {
5628                 params.forceDefaultColor();
5629             }
5630             view = makeNotificationHeader(params);
5631             view.setBoolean(R.id.notification_header, "setExpandOnlyOnButton", true);
5632             mN.extras = savedBundle;
5633             mN.mLargeIcon = largeIcon;
5634             mN.largeIcon = largeIconLegacy;
5635             mActions = actions;
5636             mStyle = style;
5637             return view;
5638         }
5639 
5640         /**
5641          * Construct a content view for the display when low - priority
5642          *
5643          * @param useRegularSubtext uses the normal subtext set if there is one available. Otherwise
5644          *                          a new subtext is created consisting of the content of the
5645          *                          notification.
5646          * @hide
5647          */
makeLowPriorityContentView(boolean useRegularSubtext)5648         public RemoteViews makeLowPriorityContentView(boolean useRegularSubtext) {
5649             StandardTemplateParams p = mParams.reset()
5650                     .forceDefaultColor()
5651                     .fillTextsFrom(this);
5652             if (!useRegularSubtext || TextUtils.isEmpty(mParams.summaryText)) {
5653                 p.summaryText(createSummaryText());
5654             }
5655             RemoteViews header = makeNotificationHeader(p);
5656             header.setBoolean(R.id.notification_header, "setAcceptAllTouches", true);
5657             return header;
5658         }
5659 
createSummaryText()5660         private CharSequence createSummaryText() {
5661             CharSequence titleText = mN.extras.getCharSequence(Notification.EXTRA_TITLE);
5662             if (USE_ONLY_TITLE_IN_LOW_PRIORITY_SUMMARY) {
5663                 return titleText;
5664             }
5665             SpannableStringBuilder summary = new SpannableStringBuilder();
5666             if (titleText == null) {
5667                 titleText = mN.extras.getCharSequence(Notification.EXTRA_TITLE_BIG);
5668             }
5669             BidiFormatter bidi = BidiFormatter.getInstance();
5670             if (titleText != null) {
5671                 summary.append(bidi.unicodeWrap(titleText));
5672             }
5673             CharSequence contentText = mN.extras.getCharSequence(Notification.EXTRA_TEXT);
5674             if (titleText != null && contentText != null) {
5675                 summary.append(bidi.unicodeWrap(mContext.getText(
5676                         R.string.notification_header_divider_symbol_with_spaces)));
5677             }
5678             if (contentText != null) {
5679                 summary.append(bidi.unicodeWrap(contentText));
5680             }
5681             return summary;
5682         }
5683 
generateActionButton(Action action, boolean emphazisedMode, StandardTemplateParams p)5684         private RemoteViews generateActionButton(Action action, boolean emphazisedMode,
5685                 StandardTemplateParams p) {
5686             final boolean tombstone = (action.actionIntent == null);
5687             RemoteViews button = new BuilderRemoteViews(mContext.getApplicationInfo(),
5688                     emphazisedMode ? getEmphasizedActionLayoutResource()
5689                             : tombstone ? getActionTombstoneLayoutResource()
5690                                     : getActionLayoutResource());
5691             if (!tombstone) {
5692                 button.setOnClickPendingIntent(R.id.action0, action.actionIntent);
5693             }
5694             button.setContentDescription(R.id.action0, action.title);
5695             if (action.mRemoteInputs != null) {
5696                 button.setRemoteInputs(R.id.action0, action.mRemoteInputs);
5697             }
5698             if (emphazisedMode) {
5699                 // change the background bgColor
5700                 CharSequence title = action.title;
5701                 ColorStateList[] outResultColor = null;
5702                 int background = resolveBackgroundColor(p);
5703                 if (isLegacy()) {
5704                     title = ContrastColorUtil.clearColorSpans(title);
5705                 } else {
5706                     outResultColor = new ColorStateList[1];
5707                     title = ensureColorSpanContrast(title, background, outResultColor);
5708                 }
5709                 button.setTextViewText(R.id.action0, processTextSpans(title));
5710                 setTextViewColorPrimary(button, R.id.action0, p);
5711                 int rippleColor;
5712                 boolean hasColorOverride = outResultColor != null && outResultColor[0] != null;
5713                 if (hasColorOverride) {
5714                     // There's a span spanning the full text, let's take it and use it as the
5715                     // background color
5716                     background = outResultColor[0].getDefaultColor();
5717                     int textColor = ContrastColorUtil.resolvePrimaryColor(mContext,
5718                             background, mInNightMode);
5719                     button.setTextColor(R.id.action0, textColor);
5720                     rippleColor = textColor;
5721                 } else if (getRawColor(p) != COLOR_DEFAULT && !isColorized(p)
5722                         && mTintActionButtons && !mInNightMode) {
5723                     rippleColor = resolveContrastColor(p);
5724                     button.setTextColor(R.id.action0, rippleColor);
5725                 } else {
5726                     rippleColor = getPrimaryTextColor(p);
5727                 }
5728                 // We only want about 20% alpha for the ripple
5729                 rippleColor = (rippleColor & 0x00ffffff) | 0x33000000;
5730                 button.setColorStateList(R.id.action0, "setRippleColor",
5731                         ColorStateList.valueOf(rippleColor));
5732                 button.setColorStateList(R.id.action0, "setButtonBackground",
5733                         ColorStateList.valueOf(background));
5734                 button.setBoolean(R.id.action0, "setHasStroke", !hasColorOverride);
5735             } else {
5736                 button.setTextViewText(R.id.action0, processTextSpans(
5737                         processLegacyText(action.title)));
5738                 if (isColorized(p)) {
5739                     setTextViewColorPrimary(button, R.id.action0, p);
5740                 } else if (getRawColor(p) != COLOR_DEFAULT && mTintActionButtons) {
5741                     button.setTextColor(R.id.action0, resolveContrastColor(p));
5742                 }
5743             }
5744             button.setIntTag(R.id.action0, R.id.notification_action_index_tag,
5745                     mActions.indexOf(action));
5746             return button;
5747         }
5748 
5749         /**
5750          * Ensures contrast on color spans against a background color. also returns the color of the
5751          * text if a span was found that spans over the whole text.
5752          *
5753          * @param charSequence the charSequence on which the spans are
5754          * @param background the background color to ensure the contrast against
5755          * @param outResultColor an array in which a color will be returned as the first element if
5756          *                    there exists a full length color span.
5757          * @return the contrasted charSequence
5758          */
ensureColorSpanContrast(CharSequence charSequence, int background, ColorStateList[] outResultColor)5759         private CharSequence ensureColorSpanContrast(CharSequence charSequence, int background,
5760                 ColorStateList[] outResultColor) {
5761             if (charSequence instanceof Spanned) {
5762                 Spanned ss = (Spanned) charSequence;
5763                 Object[] spans = ss.getSpans(0, ss.length(), Object.class);
5764                 SpannableStringBuilder builder = new SpannableStringBuilder(ss.toString());
5765                 for (Object span : spans) {
5766                     Object resultSpan = span;
5767                     int spanStart = ss.getSpanStart(span);
5768                     int spanEnd = ss.getSpanEnd(span);
5769                     boolean fullLength = (spanEnd - spanStart) == charSequence.length();
5770                     if (resultSpan instanceof CharacterStyle) {
5771                         resultSpan = ((CharacterStyle) span).getUnderlying();
5772                     }
5773                     if (resultSpan instanceof TextAppearanceSpan) {
5774                         TextAppearanceSpan originalSpan = (TextAppearanceSpan) resultSpan;
5775                         ColorStateList textColor = originalSpan.getTextColor();
5776                         if (textColor != null) {
5777                             int[] colors = textColor.getColors();
5778                             int[] newColors = new int[colors.length];
5779                             for (int i = 0; i < newColors.length; i++) {
5780                                 newColors[i] = ContrastColorUtil.ensureLargeTextContrast(
5781                                         colors[i], background, mInNightMode);
5782                             }
5783                             textColor = new ColorStateList(textColor.getStates().clone(),
5784                                     newColors);
5785                             if (fullLength) {
5786                                 outResultColor[0] = textColor;
5787                                 // Let's drop the color from the span
5788                                 textColor = null;
5789                             }
5790                             resultSpan = new TextAppearanceSpan(
5791                                     originalSpan.getFamily(),
5792                                     originalSpan.getTextStyle(),
5793                                     originalSpan.getTextSize(),
5794                                     textColor,
5795                                     originalSpan.getLinkTextColor());
5796                         }
5797                     } else if (resultSpan instanceof ForegroundColorSpan) {
5798                         ForegroundColorSpan originalSpan = (ForegroundColorSpan) resultSpan;
5799                         int foregroundColor = originalSpan.getForegroundColor();
5800                         foregroundColor = ContrastColorUtil.ensureLargeTextContrast(
5801                                 foregroundColor, background, mInNightMode);
5802                         if (fullLength) {
5803                             outResultColor[0] = ColorStateList.valueOf(foregroundColor);
5804                             resultSpan = null;
5805                         } else {
5806                             resultSpan = new ForegroundColorSpan(foregroundColor);
5807                         }
5808                     } else {
5809                         resultSpan = span;
5810                     }
5811                     if (resultSpan != null) {
5812                         builder.setSpan(resultSpan, spanStart, spanEnd, ss.getSpanFlags(span));
5813                     }
5814                 }
5815                 return builder;
5816             }
5817             return charSequence;
5818         }
5819 
5820         /**
5821          * @return Whether we are currently building a notification from a legacy (an app that
5822          *         doesn't create material notifications by itself) app.
5823          */
isLegacy()5824         private boolean isLegacy() {
5825             if (!mIsLegacyInitialized) {
5826                 mIsLegacy = mContext.getApplicationInfo().targetSdkVersion
5827                         < Build.VERSION_CODES.LOLLIPOP;
5828                 mIsLegacyInitialized = true;
5829             }
5830             return mIsLegacy;
5831         }
5832 
5833         private CharSequence processLegacyText(CharSequence charSequence) {
5834             boolean isAlreadyLightText = isLegacy() || textColorsNeedInversion();
5835             if (isAlreadyLightText) {
5836                 return getColorUtil().invertCharSequenceColors(charSequence);
5837             } else {
5838                 return charSequence;
5839             }
5840         }
5841 
5842         /**
5843          * Apply any necessariy colors to the small icon
5844          */
5845         private void processSmallIconColor(Icon smallIcon, RemoteViews contentView,
5846                 StandardTemplateParams p) {
5847             boolean colorable = !isLegacy() || getColorUtil().isGrayscaleIcon(mContext, smallIcon);
5848             int color;
5849             if (isColorized(p)) {
5850                 color = getPrimaryTextColor(p);
5851             } else {
5852                 color = resolveContrastColor(p);
5853             }
5854             if (colorable) {
5855                 contentView.setDrawableTint(R.id.icon, false, color,
5856                         PorterDuff.Mode.SRC_ATOP);
5857 
5858             }
5859             contentView.setInt(R.id.icon, "setOriginalIconColor",
5860                     colorable ? color : NotificationHeaderView.NO_COLOR);
5861         }
5862 
5863         /**
5864          * Make the largeIcon dark if it's a fake smallIcon (that is,
5865          * if it's grayscale).
5866          */
5867         // TODO: also check bounds, transparency, that sort of thing.
5868         private void processLargeLegacyIcon(Icon largeIcon, RemoteViews contentView,
5869                 StandardTemplateParams p) {
5870             if (largeIcon != null && isLegacy()
5871                     && getColorUtil().isGrayscaleIcon(mContext, largeIcon)) {
5872                 // resolve color will fall back to the default when legacy
5873                 contentView.setDrawableTint(R.id.icon, false, resolveContrastColor(p),
5874                         PorterDuff.Mode.SRC_ATOP);
5875             }
5876         }
5877 
5878         private void sanitizeColor() {
5879             if (mN.color != COLOR_DEFAULT) {
5880                 mN.color |= 0xFF000000; // no alpha for custom colors
5881             }
5882         }
5883 
5884         int resolveContrastColor(StandardTemplateParams p) {
5885             int rawColor = getRawColor(p);
5886             if (mCachedContrastColorIsFor == rawColor && mCachedContrastColor != COLOR_INVALID) {
5887                 return mCachedContrastColor;
5888             }
5889 
5890             int color;
5891             int background = mContext.getColor(
5892                     com.android.internal.R.color.notification_material_background_color);
5893             if (rawColor == COLOR_DEFAULT) {
5894                 ensureColors(p);
5895                 color = ContrastColorUtil.resolveDefaultColor(mContext, background, mInNightMode);
5896             } else {
5897                 color = ContrastColorUtil.resolveContrastColor(mContext, rawColor,
5898                         background, mInNightMode);
5899             }
5900             if (Color.alpha(color) < 255) {
5901                 // alpha doesn't go well for color filters, so let's blend it manually
5902                 color = ContrastColorUtil.compositeColors(color, background);
5903             }
5904             mCachedContrastColorIsFor = rawColor;
5905             return mCachedContrastColor = color;
5906         }
5907 
5908         /**
5909          * Return the raw color of this Notification, which doesn't necessarily satisfy contrast.
5910          *
5911          * @see #resolveContrastColor(StandardTemplateParams) for the contrasted color
5912          * @param p the template params to inflate this with
5913          */
5914         private int getRawColor(StandardTemplateParams p) {
5915             if (p.forceDefaultColor) {
5916                 return COLOR_DEFAULT;
5917             }
5918             return mN.color;
5919         }
5920 
5921         int resolveNeutralColor() {
5922             if (mNeutralColor != COLOR_INVALID) {
5923                 return mNeutralColor;
5924             }
5925             int background = mContext.getColor(
5926                     com.android.internal.R.color.notification_material_background_color);
5927             mNeutralColor = ContrastColorUtil.resolveDefaultColor(mContext, background,
5928                     mInNightMode);
5929             if (Color.alpha(mNeutralColor) < 255) {
5930                 // alpha doesn't go well for color filters, so let's blend it manually
5931                 mNeutralColor = ContrastColorUtil.compositeColors(mNeutralColor, background);
5932             }
5933             return mNeutralColor;
5934         }
5935 
5936         /**
5937          * Apply the unstyled operations and return a new {@link Notification} object.
5938          * @hide
5939          */
5940         @NonNull
5941         public Notification buildUnstyled() {
5942             if (mActions.size() > 0) {
5943                 mN.actions = new Action[mActions.size()];
5944                 mActions.toArray(mN.actions);
5945             }
5946             if (!mPersonList.isEmpty()) {
5947                 mN.extras.putParcelableArrayList(EXTRA_PEOPLE_LIST, mPersonList);
5948             }
5949             if (mN.bigContentView != null || mN.contentView != null
5950                     || mN.headsUpContentView != null) {
5951                 mN.extras.putBoolean(EXTRA_CONTAINS_CUSTOM_VIEW, true);
5952             }
5953             return mN;
5954         }
5955 
5956         /**
5957          * Creates a Builder from an existing notification so further changes can be made.
5958          * @param context The context for your application / activity.
5959          * @param n The notification to create a Builder from.
5960          */
5961         @NonNull
5962         public static Notification.Builder recoverBuilder(Context context, Notification n) {
5963             // Re-create notification context so we can access app resources.
5964             ApplicationInfo applicationInfo = n.extras.getParcelable(
5965                     EXTRA_BUILDER_APPLICATION_INFO);
5966             Context builderContext;
5967             if (applicationInfo != null) {
5968                 try {
5969                     builderContext = context.createApplicationContext(applicationInfo,
5970                             Context.CONTEXT_RESTRICTED);
5971                 } catch (NameNotFoundException e) {
5972                     Log.e(TAG, "ApplicationInfo " + applicationInfo + " not found");
5973                     builderContext = context;  // try with our context
5974                 }
5975             } else {
5976                 builderContext = context; // try with given context
5977             }
5978 
5979             return new Builder(builderContext, n);
5980         }
5981 
5982         /**
5983          * Determines whether the platform can generate contextual actions for a notification.
5984          * By default this is true.
5985          */
5986         @NonNull
5987         public Builder setAllowSystemGeneratedContextualActions(boolean allowed) {
5988             mN.mAllowSystemGeneratedContextualActions = allowed;
5989             return this;
5990         }
5991 
5992         /**
5993          * @deprecated Use {@link #build()} instead.
5994          */
5995         @Deprecated
5996         public Notification getNotification() {
5997             return build();
5998         }
5999 
6000         /**
6001          * Combine all of the options that have been set and return a new {@link Notification}
6002          * object.
6003          *
6004          * If this notification has {@link BubbleMetadata} attached that was created with
6005          * a shortcutId a check will be performed to ensure the shortcutId supplied to bubble
6006          * metadata matches the shortcutId set on the  notification builder, if one was set.
6007          * If the shortcutId's were specified but do not match, an exception is thrown here.
6008          *
6009          * @see BubbleMetadata.Builder#Builder(String)
6010          * @see #setShortcutId(String)
6011          */
6012         @NonNull
6013         public Notification build() {
6014             // Check shortcut id matches
6015             if (mN.mShortcutId != null
6016                     && mN.mBubbleMetadata != null
6017                     && mN.mBubbleMetadata.getShortcutId() != null
6018                     && !mN.mShortcutId.equals(mN.mBubbleMetadata.getShortcutId())) {
6019                 throw new IllegalArgumentException(
6020                         "Notification and BubbleMetadata shortcut id's don't match,"
6021                                 + " notification: " + mN.mShortcutId
6022                                 + " vs bubble: " + mN.mBubbleMetadata.getShortcutId());
6023             }
6024 
6025             // first, add any extras from the calling code
6026             if (mUserExtras != null) {
6027                 mN.extras = getAllExtras();
6028             }
6029 
6030             mN.creationTime = System.currentTimeMillis();
6031 
6032             // lazy stuff from mContext; see comment in Builder(Context, Notification)
6033             Notification.addFieldsFromContext(mContext, mN);
6034 
6035             buildUnstyled();
6036 
6037             if (mStyle != null) {
6038                 mStyle.reduceImageSizes(mContext);
6039                 mStyle.purgeResources();
6040                 mStyle.validate(mContext);
6041                 mStyle.buildStyled(mN);
6042             }
6043             mN.reduceImageSizes(mContext);
6044 
6045             if (mContext.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N
6046                     && (useExistingRemoteView())) {
6047                 if (mN.contentView == null) {
6048                     mN.contentView = createContentView();
6049                     mN.extras.putInt(EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT,
6050                             mN.contentView.getSequenceNumber());
6051                 }
6052                 if (mN.bigContentView == null) {
6053                     mN.bigContentView = createBigContentView();
6054                     if (mN.bigContentView != null) {
6055                         mN.extras.putInt(EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT,
6056                                 mN.bigContentView.getSequenceNumber());
6057                     }
6058                 }
6059                 if (mN.headsUpContentView == null) {
6060                     mN.headsUpContentView = createHeadsUpContentView();
6061                     if (mN.headsUpContentView != null) {
6062                         mN.extras.putInt(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT,
6063                                 mN.headsUpContentView.getSequenceNumber());
6064                     }
6065                 }
6066             }
6067 
6068             if ((mN.defaults & DEFAULT_LIGHTS) != 0) {
6069                 mN.flags |= FLAG_SHOW_LIGHTS;
6070             }
6071 
6072             mN.allPendingIntents = null;
6073 
6074             return mN;
6075         }
6076 
6077         /**
6078          * Apply this Builder to an existing {@link Notification} object.
6079          *
6080          * @hide
6081          */
6082         @NonNull
6083         public Notification buildInto(@NonNull Notification n) {
6084             build().cloneInto(n, true);
6085             return n;
6086         }
6087 
6088         /**
6089          * Removes RemoteViews that were created for compatibility from {@param n}, if they did not
6090          * change.
6091          *
6092          * @return {@param n}, if no stripping is needed, otherwise a stripped clone of {@param n}.
6093          *
6094          * @hide
6095          */
6096         public static Notification maybeCloneStrippedForDelivery(Notification n) {
6097             String templateClass = n.extras.getString(EXTRA_TEMPLATE);
6098 
6099             // Only strip views for known Styles because we won't know how to
6100             // re-create them otherwise.
6101             if (!TextUtils.isEmpty(templateClass)
6102                     && getNotificationStyleClass(templateClass) == null) {
6103                 return n;
6104             }
6105 
6106             // Only strip unmodified BuilderRemoteViews.
6107             boolean stripContentView = n.contentView instanceof BuilderRemoteViews &&
6108                     n.extras.getInt(EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT, -1) ==
6109                             n.contentView.getSequenceNumber();
6110             boolean stripBigContentView = n.bigContentView instanceof BuilderRemoteViews &&
6111                     n.extras.getInt(EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT, -1) ==
6112                             n.bigContentView.getSequenceNumber();
6113             boolean stripHeadsUpContentView = n.headsUpContentView instanceof BuilderRemoteViews &&
6114                     n.extras.getInt(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT, -1) ==
6115                             n.headsUpContentView.getSequenceNumber();
6116 
6117             // Nothing to do here, no need to clone.
6118             if (!stripContentView && !stripBigContentView && !stripHeadsUpContentView) {
6119                 return n;
6120             }
6121 
6122             Notification clone = n.clone();
6123             if (stripContentView) {
6124                 clone.contentView = null;
6125                 clone.extras.remove(EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT);
6126             }
6127             if (stripBigContentView) {
6128                 clone.bigContentView = null;
6129                 clone.extras.remove(EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT);
6130             }
6131             if (stripHeadsUpContentView) {
6132                 clone.headsUpContentView = null;
6133                 clone.extras.remove(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT);
6134             }
6135             return clone;
6136         }
6137 
6138         @UnsupportedAppUsage
6139         private int getBaseLayoutResource() {
6140             return R.layout.notification_template_material_base;
6141         }
6142 
6143         private int getBigBaseLayoutResource() {
6144             return R.layout.notification_template_material_big_base;
6145         }
6146 
6147         private int getBigPictureLayoutResource() {
6148             return R.layout.notification_template_material_big_picture;
6149         }
6150 
6151         private int getBigTextLayoutResource() {
6152             return R.layout.notification_template_material_big_text;
6153         }
6154 
6155         private int getInboxLayoutResource() {
6156             return R.layout.notification_template_material_inbox;
6157         }
6158 
6159         private int getMessagingLayoutResource() {
6160             return R.layout.notification_template_material_messaging;
6161         }
6162 
6163         private int getConversationLayoutResource() {
6164             return R.layout.notification_template_material_conversation;
6165         }
6166 
6167         private int getActionLayoutResource() {
6168             return R.layout.notification_material_action;
6169         }
6170 
6171         private int getEmphasizedActionLayoutResource() {
6172             return R.layout.notification_material_action_emphasized;
6173         }
6174 
6175         private int getActionTombstoneLayoutResource() {
6176             return R.layout.notification_material_action_tombstone;
6177         }
6178 
6179         private int getBackgroundColor(StandardTemplateParams p) {
6180             if (isColorized(p)) {
6181                 return mBackgroundColor != COLOR_INVALID ? mBackgroundColor : getRawColor(p);
6182             } else {
6183                 return COLOR_DEFAULT;
6184             }
6185         }
6186 
6187         /**
6188          * Gets a neutral color that can be used for icons or similar that should not stand out.
6189          * @param p the template params to inflate this with
6190          */
6191         private int getNeutralColor(StandardTemplateParams p) {
6192             if (isColorized(p)) {
6193                 return getSecondaryTextColor(p);
6194             } else {
6195                 return resolveNeutralColor();
6196             }
6197         }
6198 
6199         /**
6200          * Same as getBackgroundColor but also resolved the default color to the background.
6201          * @param p the template params to inflate this with
6202          */
6203         private int resolveBackgroundColor(StandardTemplateParams p) {
6204             int backgroundColor = getBackgroundColor(p);
6205             if (backgroundColor == COLOR_DEFAULT) {
6206                 backgroundColor = mContext.getColor(
6207                         com.android.internal.R.color.notification_material_background_color);
6208             }
6209             return backgroundColor;
6210         }
6211 
6212         private boolean shouldTintActionButtons() {
6213             return mTintActionButtons;
6214         }
6215 
6216         private boolean textColorsNeedInversion() {
6217             if (mStyle == null || !MediaStyle.class.equals(mStyle.getClass())) {
6218                 return false;
6219             }
6220             int targetSdkVersion = mContext.getApplicationInfo().targetSdkVersion;
6221             return targetSdkVersion > Build.VERSION_CODES.M
6222                     && targetSdkVersion < Build.VERSION_CODES.O;
6223         }
6224 
6225         /**
6226          * Set a color palette to be used as the background and textColors
6227          *
6228          * @param backgroundColor the color to be used as the background
6229          * @param foregroundColor the color to be used as the foreground
6230          *
6231          * @hide
6232          */
6233         public void setColorPalette(int backgroundColor, int foregroundColor) {
6234             mBackgroundColor = backgroundColor;
6235             mForegroundColor = foregroundColor;
6236             mTextColorsAreForBackground = COLOR_INVALID;
6237             ensureColors(mParams.reset().fillTextsFrom(this));
6238         }
6239 
6240         /**
6241          * Forces all styled remoteViews to be built from scratch and not use any cached
6242          * RemoteViews.
6243          * This is needed for legacy apps that are baking in their remoteviews into the
6244          * notification.
6245          *
6246          * @hide
6247          */
6248         public void setRebuildStyledRemoteViews(boolean rebuild) {
6249             mRebuildStyledRemoteViews = rebuild;
6250         }
6251 
6252         /**
6253          * Get the text that should be displayed in the statusBar when heads upped. This is
6254          * usually just the app name, but may be different depending on the style.
6255          *
6256          * @param publicMode If true, return a text that is safe to display in public.
6257          *
6258          * @hide
6259          */
6260         public CharSequence getHeadsUpStatusBarText(boolean publicMode) {
6261             if (mStyle != null && !publicMode) {
6262                 CharSequence text = mStyle.getHeadsUpStatusBarText();
6263                 if (!TextUtils.isEmpty(text)) {
6264                     return text;
6265                 }
6266             }
6267             return loadHeaderAppName();
6268         }
6269 
6270         /**
6271          * @return if this builder uses a template
6272          *
6273          * @hide
6274          */
6275         public boolean usesTemplate() {
6276             return (mN.contentView == null && mN.headsUpContentView == null
6277                     && mN.bigContentView == null)
6278                     || (mStyle != null && mStyle.displayCustomViewInline());
6279         }
6280     }
6281 
6282     /**
6283      * Reduces the image sizes to conform to a maximum allowed size. This also processes all custom
6284      * remote views.
6285      *
6286      * @hide
6287      */
6288     void reduceImageSizes(Context context) {
6289         if (extras.getBoolean(EXTRA_REDUCED_IMAGES)) {
6290             return;
6291         }
6292         boolean isLowRam = ActivityManager.isLowRamDeviceStatic();
6293         if (mLargeIcon != null || largeIcon != null) {
6294             Resources resources = context.getResources();
6295             Class<? extends Style> style = getNotificationStyle();
6296             int maxWidth = resources.getDimensionPixelSize(isLowRam
6297                     ? R.dimen.notification_right_icon_size_low_ram
6298                     : R.dimen.notification_right_icon_size);
6299             int maxHeight = maxWidth;
6300             if (MediaStyle.class.equals(style)
6301                     || DecoratedMediaCustomViewStyle.class.equals(style)) {
6302                 maxHeight = resources.getDimensionPixelSize(isLowRam
6303                         ? R.dimen.notification_media_image_max_height_low_ram
6304                         : R.dimen.notification_media_image_max_height);
6305                 maxWidth = resources.getDimensionPixelSize(isLowRam
6306                         ? R.dimen.notification_media_image_max_width_low_ram
6307                         : R.dimen.notification_media_image_max_width);
6308             }
6309             if (mLargeIcon != null) {
6310                 mLargeIcon.scaleDownIfNecessary(maxWidth, maxHeight);
6311             }
6312             if (largeIcon != null) {
6313                 largeIcon = Icon.scaleDownIfNecessary(largeIcon, maxWidth, maxHeight);
6314             }
6315         }
6316         reduceImageSizesForRemoteView(contentView, context, isLowRam);
6317         reduceImageSizesForRemoteView(headsUpContentView, context, isLowRam);
6318         reduceImageSizesForRemoteView(bigContentView, context, isLowRam);
6319         extras.putBoolean(EXTRA_REDUCED_IMAGES, true);
6320     }
6321 
6322     private void reduceImageSizesForRemoteView(RemoteViews remoteView, Context context,
6323             boolean isLowRam) {
6324         if (remoteView != null) {
6325             Resources resources = context.getResources();
6326             int maxWidth = resources.getDimensionPixelSize(isLowRam
6327                     ? R.dimen.notification_custom_view_max_image_width_low_ram
6328                     : R.dimen.notification_custom_view_max_image_width);
6329             int maxHeight = resources.getDimensionPixelSize(isLowRam
6330                     ? R.dimen.notification_custom_view_max_image_height_low_ram
6331                     : R.dimen.notification_custom_view_max_image_height);
6332             remoteView.reduceImageSizes(maxWidth, maxHeight);
6333         }
6334     }
6335 
6336     /**
6337      * @return whether this notification is a foreground service notification
6338      * @hide
6339      */
6340     public boolean isForegroundService() {
6341         return (flags & Notification.FLAG_FOREGROUND_SERVICE) != 0;
6342     }
6343 
6344     /**
6345      * @return whether this notification has a media session attached
6346      * @hide
6347      */
6348     public boolean hasMediaSession() {
6349         return extras.getParcelable(Notification.EXTRA_MEDIA_SESSION) != null;
6350     }
6351 
6352     /**
6353      * @return the style class of this notification
6354      * @hide
6355      */
6356     public Class<? extends Notification.Style> getNotificationStyle() {
6357         String templateClass = extras.getString(Notification.EXTRA_TEMPLATE);
6358 
6359         if (!TextUtils.isEmpty(templateClass)) {
6360             return Notification.getNotificationStyleClass(templateClass);
6361         }
6362         return null;
6363     }
6364 
6365     /**
6366      * @return true if this notification is colorized.
6367      *
6368      * @hide
6369      */
6370     public boolean isColorized() {
6371         if (isColorizedMedia()) {
6372             return true;
6373         }
6374         return extras.getBoolean(EXTRA_COLORIZED)
6375                 && (hasColorizedPermission() || isForegroundService());
6376     }
6377 
6378     /**
6379      * Returns whether an app can colorize due to the android.permission.USE_COLORIZED_NOTIFICATIONS
6380      * permission. The permission is checked when a notification is enqueued.
6381      */
6382     private boolean hasColorizedPermission() {
6383         return (flags & Notification.FLAG_CAN_COLORIZE) != 0;
6384     }
6385 
6386     /**
6387      * @return true if this notification is colorized and it is a media notification
6388      *
6389      * @hide
6390      */
6391     public boolean isColorizedMedia() {
6392         Class<? extends Style> style = getNotificationStyle();
6393         if (MediaStyle.class.equals(style)) {
6394             Boolean colorized = (Boolean) extras.get(EXTRA_COLORIZED);
6395             if ((colorized == null || colorized) && hasMediaSession()) {
6396                 return true;
6397             }
6398         } else if (DecoratedMediaCustomViewStyle.class.equals(style)) {
6399             if (extras.getBoolean(EXTRA_COLORIZED) && hasMediaSession()) {
6400                 return true;
6401             }
6402         }
6403         return false;
6404     }
6405 
6406 
6407     /**
6408      * @return true if this is a media notification
6409      *
6410      * @hide
6411      */
6412     public boolean isMediaNotification() {
6413         Class<? extends Style> style = getNotificationStyle();
6414         if (MediaStyle.class.equals(style)) {
6415             return true;
6416         } else if (DecoratedMediaCustomViewStyle.class.equals(style)) {
6417             return true;
6418         }
6419         return false;
6420     }
6421 
6422     /**
6423      * @return true if this notification is showing as a bubble
6424      *
6425      * @hide
6426      */
6427     public boolean isBubbleNotification() {
6428         return (flags & Notification.FLAG_BUBBLE) != 0;
6429     }
6430 
6431     private boolean hasLargeIcon() {
6432         return mLargeIcon != null || largeIcon != null;
6433     }
6434 
6435     /**
6436      * @return true if the notification will show the time; false otherwise
6437      * @hide
6438      */
6439     public boolean showsTime() {
6440         return when != 0 && extras.getBoolean(EXTRA_SHOW_WHEN);
6441     }
6442 
6443     /**
6444      * @return true if the notification will show a chronometer; false otherwise
6445      * @hide
6446      */
6447     public boolean showsChronometer() {
6448         return when != 0 && extras.getBoolean(EXTRA_SHOW_CHRONOMETER);
6449     }
6450 
6451     /**
6452      * @removed
6453      */
6454     @SystemApi
6455     public static Class<? extends Style> getNotificationStyleClass(String templateClass) {
6456         Class<? extends Style>[] classes = new Class[] {
6457                 BigTextStyle.class, BigPictureStyle.class, InboxStyle.class, MediaStyle.class,
6458                 DecoratedCustomViewStyle.class, DecoratedMediaCustomViewStyle.class,
6459                 MessagingStyle.class };
6460         for (Class<? extends Style> innerClass : classes) {
6461             if (templateClass.equals(innerClass.getName())) {
6462                 return innerClass;
6463             }
6464         }
6465         return null;
6466     }
6467 
6468     /**
6469      * An object that can apply a rich notification style to a {@link Notification.Builder}
6470      * object.
6471      */
6472     public static abstract class Style {
6473 
6474         /**
6475          * The number of items allowed simulatanously in the remote input history.
6476          * @hide
6477          */
6478         static final int MAX_REMOTE_INPUT_HISTORY_LINES = 3;
6479         private CharSequence mBigContentTitle;
6480 
6481         /**
6482          * @hide
6483          */
6484         protected CharSequence mSummaryText = null;
6485 
6486         /**
6487          * @hide
6488          */
6489         protected boolean mSummaryTextSet = false;
6490 
6491         protected Builder mBuilder;
6492 
6493         /**
6494          * Overrides ContentTitle in the big form of the template.
6495          * This defaults to the value passed to setContentTitle().
6496          */
6497         protected void internalSetBigContentTitle(CharSequence title) {
6498             mBigContentTitle = title;
6499         }
6500 
6501         /**
6502          * Set the first line of text after the detail section in the big form of the template.
6503          */
6504         protected void internalSetSummaryText(CharSequence cs) {
6505             mSummaryText = cs;
6506             mSummaryTextSet = true;
6507         }
6508 
6509         public void setBuilder(Builder builder) {
6510             if (mBuilder != builder) {
6511                 mBuilder = builder;
6512                 if (mBuilder != null) {
6513                     mBuilder.setStyle(this);
6514                 }
6515             }
6516         }
6517 
6518         protected void checkBuilder() {
6519             if (mBuilder == null) {
6520                 throw new IllegalArgumentException("Style requires a valid Builder object");
6521             }
6522         }
6523 
6524         protected RemoteViews getStandardView(int layoutId) {
6525             StandardTemplateParams p = mBuilder.mParams.reset().fillTextsFrom(mBuilder);
6526             return getStandardView(layoutId, p, null);
6527         }
6528 
6529 
6530         /**
6531          * Get the standard view for this style.
6532          *
6533          * @param layoutId The layout id to use.
6534          * @param p the params for this inflation.
6535          * @param result The result where template bind information is saved.
6536          * @return A remoteView for this style.
6537          * @hide
6538          */
6539         protected RemoteViews getStandardView(int layoutId, StandardTemplateParams p,
6540                 TemplateBindResult result) {
6541             checkBuilder();
6542 
6543             if (mBigContentTitle != null) {
6544                 p.title = mBigContentTitle;
6545             }
6546 
6547             RemoteViews contentView = mBuilder.applyStandardTemplateWithActions(layoutId, p,
6548                     result);
6549 
6550             if (mBigContentTitle != null && mBigContentTitle.equals("")) {
6551                 contentView.setViewVisibility(R.id.line1, View.GONE);
6552             } else {
6553                 contentView.setViewVisibility(R.id.line1, View.VISIBLE);
6554             }
6555 
6556             return contentView;
6557         }
6558 
6559         /**
6560          * Construct a Style-specific RemoteViews for the collapsed notification layout.
6561          * The default implementation has nothing additional to add.
6562          *
6563          * @param increasedHeight true if this layout be created with an increased height.
6564          * @hide
6565          */
6566         public RemoteViews makeContentView(boolean increasedHeight) {
6567             return null;
6568         }
6569 
6570         /**
6571          * Construct a Style-specific RemoteViews for the final big notification layout.
6572          * @hide
6573          */
6574         public RemoteViews makeBigContentView() {
6575             return null;
6576         }
6577 
6578         /**
6579          * Construct a Style-specific RemoteViews for the final HUN layout.
6580          *
6581          * @param increasedHeight true if this layout be created with an increased height.
6582          * @hide
6583          */
6584         public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
6585             return null;
6586         }
6587 
6588         /**
6589          * Apply any style-specific extras to this notification before shipping it out.
6590          * @hide
6591          */
6592         public void addExtras(Bundle extras) {
6593             if (mSummaryTextSet) {
6594                 extras.putCharSequence(EXTRA_SUMMARY_TEXT, mSummaryText);
6595             }
6596             if (mBigContentTitle != null) {
6597                 extras.putCharSequence(EXTRA_TITLE_BIG, mBigContentTitle);
6598             }
6599             extras.putString(EXTRA_TEMPLATE, this.getClass().getName());
6600         }
6601 
6602         /**
6603          * Reconstruct the internal state of this Style object from extras.
6604          * @hide
6605          */
6606         protected void restoreFromExtras(Bundle extras) {
6607             if (extras.containsKey(EXTRA_SUMMARY_TEXT)) {
6608                 mSummaryText = extras.getCharSequence(EXTRA_SUMMARY_TEXT);
6609                 mSummaryTextSet = true;
6610             }
6611             if (extras.containsKey(EXTRA_TITLE_BIG)) {
6612                 mBigContentTitle = extras.getCharSequence(EXTRA_TITLE_BIG);
6613             }
6614         }
6615 
6616 
6617         /**
6618          * @hide
6619          */
6620         public Notification buildStyled(Notification wip) {
6621             addExtras(wip.extras);
6622             return wip;
6623         }
6624 
6625         /**
6626          * @hide
6627          */
6628         public void purgeResources() {}
6629 
6630         /**
6631          * Calls {@link android.app.Notification.Builder#build()} on the Builder this Style is
6632          * attached to.
6633          *
6634          * @return the fully constructed Notification.
6635          */
6636         public Notification build() {
6637             checkBuilder();
6638             return mBuilder.build();
6639         }
6640 
6641         /**
6642          * @hide
6643          * @return true if the style positions the progress bar on the second line; false if the
6644          *         style hides the progress bar
6645          */
6646         protected boolean hasProgress() {
6647             return true;
6648         }
6649 
6650         /**
6651          * @hide
6652          * @return Whether we should put the summary be put into the notification header
6653          */
6654         public boolean hasSummaryInHeader() {
6655             return true;
6656         }
6657 
6658         /**
6659          * @hide
6660          * @return Whether custom content views are displayed inline in the style
6661          */
6662         public boolean displayCustomViewInline() {
6663             return false;
6664         }
6665 
6666         /**
6667          * Reduces the image sizes contained in this style.
6668          *
6669          * @hide
6670          */
6671         public void reduceImageSizes(Context context) {
6672         }
6673 
6674         /**
6675          * Validate that this style was properly composed. This is called at build time.
6676          * @hide
6677          */
6678         public void validate(Context context) {
6679         }
6680 
6681         /**
6682          * @hide
6683          */
6684         public abstract boolean areNotificationsVisiblyDifferent(Style other);
6685 
6686         /**
6687          * @return the text that should be displayed in the statusBar when heads-upped.
6688          * If {@code null} is returned, the default implementation will be used.
6689          *
6690          * @hide
6691          */
6692         public CharSequence getHeadsUpStatusBarText() {
6693             return null;
6694         }
6695     }
6696 
6697     /**
6698      * Helper class for generating large-format notifications that include a large image attachment.
6699      *
6700      * Here's how you'd set the <code>BigPictureStyle</code> on a notification:
6701      * <pre class="prettyprint">
6702      * Notification notif = new Notification.Builder(mContext)
6703      *     .setContentTitle(&quot;New photo from &quot; + sender.toString())
6704      *     .setContentText(subject)
6705      *     .setSmallIcon(R.drawable.new_post)
6706      *     .setLargeIcon(aBitmap)
6707      *     .setStyle(new Notification.BigPictureStyle()
6708      *         .bigPicture(aBigBitmap))
6709      *     .build();
6710      * </pre>
6711      *
6712      * @see Notification#bigContentView
6713      */
6714     public static class BigPictureStyle extends Style {
6715         private Bitmap mPicture;
6716         private Icon mBigLargeIcon;
6717         private boolean mBigLargeIconSet = false;
6718 
6719         public BigPictureStyle() {
6720         }
6721 
6722         /**
6723          * @deprecated use {@code BigPictureStyle()}.
6724          */
6725         @Deprecated
6726         public BigPictureStyle(Builder builder) {
6727             setBuilder(builder);
6728         }
6729 
6730         /**
6731          * Overrides ContentTitle in the big form of the template.
6732          * This defaults to the value passed to setContentTitle().
6733          */
6734         public BigPictureStyle setBigContentTitle(CharSequence title) {
6735             internalSetBigContentTitle(safeCharSequence(title));
6736             return this;
6737         }
6738 
6739         /**
6740          * Set the first line of text after the detail section in the big form of the template.
6741          */
6742         public BigPictureStyle setSummaryText(CharSequence cs) {
6743             internalSetSummaryText(safeCharSequence(cs));
6744             return this;
6745         }
6746 
6747         /**
6748          * @hide
6749          */
6750         public Bitmap getBigPicture() {
6751             return mPicture;
6752         }
6753 
6754         /**
6755          * Provide the bitmap to be used as the payload for the BigPicture notification.
6756          */
6757         public BigPictureStyle bigPicture(Bitmap b) {
6758             mPicture = b;
6759             return this;
6760         }
6761 
6762         /**
6763          * Override the large icon when the big notification is shown.
6764          */
6765         public BigPictureStyle bigLargeIcon(Bitmap b) {
6766             return bigLargeIcon(b != null ? Icon.createWithBitmap(b) : null);
6767         }
6768 
6769         /**
6770          * Override the large icon when the big notification is shown.
6771          */
6772         public BigPictureStyle bigLargeIcon(Icon icon) {
6773             mBigLargeIconSet = true;
6774             mBigLargeIcon = icon;
6775             return this;
6776         }
6777 
6778         /** @hide */
6779         public static final int MIN_ASHMEM_BITMAP_SIZE = 128 * (1 << 10);
6780 
6781         /**
6782          * @hide
6783          */
6784         @Override
6785         public void purgeResources() {
6786             super.purgeResources();
6787             if (mPicture != null &&
6788                 mPicture.isMutable() &&
6789                 mPicture.getAllocationByteCount() >= MIN_ASHMEM_BITMAP_SIZE) {
6790                 mPicture = mPicture.createAshmemBitmap();
6791             }
6792             if (mBigLargeIcon != null) {
6793                 mBigLargeIcon.convertToAshmem();
6794             }
6795         }
6796 
6797         /**
6798          * @hide
6799          */
6800         @Override
6801         public void reduceImageSizes(Context context) {
6802             super.reduceImageSizes(context);
6803             Resources resources = context.getResources();
6804             boolean isLowRam = ActivityManager.isLowRamDeviceStatic();
6805             if (mPicture != null) {
6806                 int maxPictureWidth = resources.getDimensionPixelSize(isLowRam
6807                         ? R.dimen.notification_big_picture_max_height_low_ram
6808                         : R.dimen.notification_big_picture_max_height);
6809                 int maxPictureHeight = resources.getDimensionPixelSize(isLowRam
6810                         ? R.dimen.notification_big_picture_max_width_low_ram
6811                         : R.dimen.notification_big_picture_max_width);
6812                 mPicture = Icon.scaleDownIfNecessary(mPicture, maxPictureWidth, maxPictureHeight);
6813             }
6814             if (mBigLargeIcon != null) {
6815                 int rightIconSize = resources.getDimensionPixelSize(isLowRam
6816                         ? R.dimen.notification_right_icon_size_low_ram
6817                         : R.dimen.notification_right_icon_size);
6818                 mBigLargeIcon.scaleDownIfNecessary(rightIconSize, rightIconSize);
6819             }
6820         }
6821 
6822         /**
6823          * @hide
6824          */
6825         public RemoteViews makeBigContentView() {
6826             // Replace mN.mLargeIcon with mBigLargeIcon if mBigLargeIconSet
6827             // This covers the following cases:
6828             //   1. mBigLargeIconSet -> mBigLargeIcon (null or non-null) applies, overrides
6829             //          mN.mLargeIcon
6830             //   2. !mBigLargeIconSet -> mN.mLargeIcon applies
6831             Icon oldLargeIcon = null;
6832             Bitmap largeIconLegacy = null;
6833             if (mBigLargeIconSet) {
6834                 oldLargeIcon = mBuilder.mN.mLargeIcon;
6835                 mBuilder.mN.mLargeIcon = mBigLargeIcon;
6836                 // The legacy largeIcon might not allow us to clear the image, as it's taken in
6837                 // replacement if the other one is null. Because we're restoring these legacy icons
6838                 // for old listeners, this is in general non-null.
6839                 largeIconLegacy = mBuilder.mN.largeIcon;
6840                 mBuilder.mN.largeIcon = null;
6841             }
6842 
6843             StandardTemplateParams p = mBuilder.mParams.reset().fillTextsFrom(mBuilder);
6844             RemoteViews contentView = getStandardView(mBuilder.getBigPictureLayoutResource(),
6845                     p, null /* result */);
6846             if (mSummaryTextSet) {
6847                 contentView.setTextViewText(R.id.text, mBuilder.processTextSpans(
6848                         mBuilder.processLegacyText(mSummaryText)));
6849                 mBuilder.setTextViewColorSecondary(contentView, R.id.text, p);
6850                 contentView.setViewVisibility(R.id.text, View.VISIBLE);
6851             }
6852             mBuilder.setContentMinHeight(contentView, mBuilder.mN.hasLargeIcon());
6853 
6854             if (mBigLargeIconSet) {
6855                 mBuilder.mN.mLargeIcon = oldLargeIcon;
6856                 mBuilder.mN.largeIcon = largeIconLegacy;
6857             }
6858 
6859             contentView.setImageViewBitmap(R.id.big_picture, mPicture);
6860             return contentView;
6861         }
6862 
6863         /**
6864          * @hide
6865          */
6866         public void addExtras(Bundle extras) {
6867             super.addExtras(extras);
6868 
6869             if (mBigLargeIconSet) {
6870                 extras.putParcelable(EXTRA_LARGE_ICON_BIG, mBigLargeIcon);
6871             }
6872             extras.putParcelable(EXTRA_PICTURE, mPicture);
6873         }
6874 
6875         /**
6876          * @hide
6877          */
6878         @Override
6879         protected void restoreFromExtras(Bundle extras) {
6880             super.restoreFromExtras(extras);
6881 
6882             if (extras.containsKey(EXTRA_LARGE_ICON_BIG)) {
6883                 mBigLargeIconSet = true;
6884                 mBigLargeIcon = extras.getParcelable(EXTRA_LARGE_ICON_BIG);
6885             }
6886             mPicture = extras.getParcelable(EXTRA_PICTURE);
6887         }
6888 
6889         /**
6890          * @hide
6891          */
6892         @Override
6893         public boolean hasSummaryInHeader() {
6894             return false;
6895         }
6896 
6897         /**
6898          * @hide
6899          * Note that we aren't actually comparing the contents of the bitmaps here, so this
6900          * is only doing a cursory inspection. Bitmaps of equal size will appear the same.
6901          */
6902         @Override
6903         public boolean areNotificationsVisiblyDifferent(Style other) {
6904             if (other == null || getClass() != other.getClass()) {
6905                 return true;
6906             }
6907             BigPictureStyle otherS = (BigPictureStyle) other;
6908             return areBitmapsObviouslyDifferent(getBigPicture(), otherS.getBigPicture());
6909         }
6910 
6911         private static boolean areBitmapsObviouslyDifferent(Bitmap a, Bitmap b) {
6912             if (a == b) {
6913                 return false;
6914             }
6915             if (a == null || b == null) {
6916                 return true;
6917             }
6918             return a.getWidth() != b.getWidth()
6919                     || a.getHeight() != b.getHeight()
6920                     || a.getConfig() != b.getConfig()
6921                     || a.getGenerationId() != b.getGenerationId();
6922         }
6923     }
6924 
6925     /**
6926      * Helper class for generating large-format notifications that include a lot of text.
6927      *
6928      * Here's how you'd set the <code>BigTextStyle</code> on a notification:
6929      * <pre class="prettyprint">
6930      * Notification notif = new Notification.Builder(mContext)
6931      *     .setContentTitle(&quot;New mail from &quot; + sender.toString())
6932      *     .setContentText(subject)
6933      *     .setSmallIcon(R.drawable.new_mail)
6934      *     .setLargeIcon(aBitmap)
6935      *     .setStyle(new Notification.BigTextStyle()
6936      *         .bigText(aVeryLongString))
6937      *     .build();
6938      * </pre>
6939      *
6940      * @see Notification#bigContentView
6941      */
6942     public static class BigTextStyle extends Style {
6943 
6944         private CharSequence mBigText;
6945 
6946         public BigTextStyle() {
6947         }
6948 
6949         /**
6950          * @deprecated use {@code BigTextStyle()}.
6951          */
6952         @Deprecated
6953         public BigTextStyle(Builder builder) {
6954             setBuilder(builder);
6955         }
6956 
6957         /**
6958          * Overrides ContentTitle in the big form of the template.
6959          * This defaults to the value passed to setContentTitle().
6960          */
6961         public BigTextStyle setBigContentTitle(CharSequence title) {
6962             internalSetBigContentTitle(safeCharSequence(title));
6963             return this;
6964         }
6965 
6966         /**
6967          * Set the first line of text after the detail section in the big form of the template.
6968          */
6969         public BigTextStyle setSummaryText(CharSequence cs) {
6970             internalSetSummaryText(safeCharSequence(cs));
6971             return this;
6972         }
6973 
6974         /**
6975          * Provide the longer text to be displayed in the big form of the
6976          * template in place of the content text.
6977          */
6978         public BigTextStyle bigText(CharSequence cs) {
6979             mBigText = safeCharSequence(cs);
6980             return this;
6981         }
6982 
6983         /**
6984          * @hide
6985          */
6986         public CharSequence getBigText() {
6987             return mBigText;
6988         }
6989 
6990         /**
6991          * @hide
6992          */
6993         public void addExtras(Bundle extras) {
6994             super.addExtras(extras);
6995 
6996             extras.putCharSequence(EXTRA_BIG_TEXT, mBigText);
6997         }
6998 
6999         /**
7000          * @hide
7001          */
7002         @Override
7003         protected void restoreFromExtras(Bundle extras) {
7004             super.restoreFromExtras(extras);
7005 
7006             mBigText = extras.getCharSequence(EXTRA_BIG_TEXT);
7007         }
7008 
7009         /**
7010          * @param increasedHeight true if this layout be created with an increased height.
7011          *
7012          * @hide
7013          */
7014         @Override
7015         public RemoteViews makeContentView(boolean increasedHeight) {
7016             if (increasedHeight) {
7017                 mBuilder.mOriginalActions = mBuilder.mActions;
7018                 mBuilder.mActions = new ArrayList<>();
7019                 RemoteViews remoteViews = makeBigContentView();
7020                 mBuilder.mActions = mBuilder.mOriginalActions;
7021                 mBuilder.mOriginalActions = null;
7022                 return remoteViews;
7023             }
7024             return super.makeContentView(increasedHeight);
7025         }
7026 
7027         /**
7028          * @hide
7029          */
7030         @Override
7031         public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
7032             if (increasedHeight && mBuilder.mActions.size() > 0) {
7033                 return makeBigContentView();
7034             }
7035             return super.makeHeadsUpContentView(increasedHeight);
7036         }
7037 
7038         /**
7039          * @hide
7040          */
7041         public RemoteViews makeBigContentView() {
7042             StandardTemplateParams p = mBuilder.mParams.reset().fillTextsFrom(mBuilder).text(null);
7043             TemplateBindResult result = new TemplateBindResult();
7044             RemoteViews contentView = getStandardView(mBuilder.getBigTextLayoutResource(), p,
7045                     result);
7046             contentView.setInt(R.id.big_text, "setImageEndMargin", result.getIconMarginEnd());
7047 
7048             CharSequence bigTextText = mBuilder.processLegacyText(mBigText);
7049             if (TextUtils.isEmpty(bigTextText)) {
7050                 // In case the bigtext is null / empty fall back to the normal text to avoid a weird
7051                 // experience
7052                 bigTextText = mBuilder.processLegacyText(
7053                         mBuilder.getAllExtras().getCharSequence(EXTRA_TEXT));
7054             }
7055             contentView.setTextViewText(R.id.big_text, mBuilder.processTextSpans(bigTextText));
7056             mBuilder.setTextViewColorSecondary(contentView, R.id.big_text, p);
7057             contentView.setViewVisibility(R.id.big_text,
7058                     TextUtils.isEmpty(bigTextText) ? View.GONE : View.VISIBLE);
7059             contentView.setBoolean(R.id.big_text, "setHasImage",
7060                     result.isRightIconContainerVisible());
7061 
7062             return contentView;
7063         }
7064 
7065         /**
7066          * @hide
7067          * Spans are ignored when comparing text for visual difference.
7068          */
7069         @Override
7070         public boolean areNotificationsVisiblyDifferent(Style other) {
7071             if (other == null || getClass() != other.getClass()) {
7072                 return true;
7073             }
7074             BigTextStyle newS = (BigTextStyle) other;
7075             return !Objects.equals(String.valueOf(getBigText()), String.valueOf(newS.getBigText()));
7076         }
7077 
7078     }
7079 
7080     /**
7081      * Helper class for generating large-format notifications that include multiple back-and-forth
7082      * messages of varying types between any number of people.
7083      *
7084      * <p>
7085      * If the platform does not provide large-format notifications, this method has no effect. The
7086      * user will always see the normal notification view.
7087      *
7088      * <p>
7089      * If the app is targeting Android P and above, it is required to use the {@link Person}
7090      * class in order to get an optimal rendering of the notification and its avatars. For
7091      * conversations involving multiple people, the app should also make sure that it marks the
7092      * conversation as a group with {@link #setGroupConversation(boolean)}.
7093      *
7094      * <p>
7095      * This class is a "rebuilder": It attaches to a Builder object and modifies its behavior.
7096      * Here's an example of how this may be used:
7097      * <pre class="prettyprint">
7098      *
7099      * Person user = new Person.Builder().setIcon(userIcon).setName(userName).build();
7100      * MessagingStyle style = new MessagingStyle(user)
7101      *      .addMessage(messages[1].getText(), messages[1].getTime(), messages[1].getPerson())
7102      *      .addMessage(messages[2].getText(), messages[2].getTime(), messages[2].getPerson())
7103      *      .setGroupConversation(hasMultiplePeople());
7104      *
7105      * Notification noti = new Notification.Builder()
7106      *     .setContentTitle(&quot;2 new messages with &quot; + sender.toString())
7107      *     .setContentText(subject)
7108      *     .setSmallIcon(R.drawable.new_message)
7109      *     .setLargeIcon(aBitmap)
7110      *     .setStyle(style)
7111      *     .build();
7112      * </pre>
7113      */
7114     public static class MessagingStyle extends Style {
7115 
7116         /**
7117          * The maximum number of messages that will be retained in the Notification itself (the
7118          * number displayed is up to the platform).
7119          */
7120         public static final int MAXIMUM_RETAINED_MESSAGES = 25;
7121 
7122 
7123         /** @hide */
7124         public static final int CONVERSATION_TYPE_LEGACY = 0;
7125         /** @hide */
7126         public static final int CONVERSATION_TYPE_NORMAL = 1;
7127         /** @hide */
7128         public static final int CONVERSATION_TYPE_IMPORTANT = 2;
7129 
7130         /** @hide */
7131         @IntDef(prefix = {"CONVERSATION_TYPE_"}, value = {
7132                 CONVERSATION_TYPE_LEGACY, CONVERSATION_TYPE_NORMAL, CONVERSATION_TYPE_IMPORTANT
7133         })
7134         @Retention(RetentionPolicy.SOURCE)
7135         public @interface ConversationType {}
7136 
7137         @NonNull Person mUser;
7138         @Nullable CharSequence mConversationTitle;
7139         @Nullable Icon mShortcutIcon;
7140         List<Message> mMessages = new ArrayList<>();
7141         List<Message> mHistoricMessages = new ArrayList<>();
7142         boolean mIsGroupConversation;
7143         @ConversationType int mConversationType = CONVERSATION_TYPE_LEGACY;
7144         int mUnreadMessageCount;
7145 
7146         MessagingStyle() {
7147         }
7148 
7149         /**
7150          * @param userDisplayName Required - the name to be displayed for any replies sent by the
7151          * user before the posting app reposts the notification with those messages after they've
7152          * been actually sent and in previous messages sent by the user added in
7153          * {@link #addMessage(Notification.MessagingStyle.Message)}
7154          *
7155          * @deprecated use {@code MessagingStyle(Person)}
7156          */
7157         public MessagingStyle(@NonNull CharSequence userDisplayName) {
7158             this(new Person.Builder().setName(userDisplayName).build());
7159         }
7160 
7161         /**
7162          * @param user Required - The person displayed for any messages that are sent by the
7163          * user. Any messages added with {@link #addMessage(Notification.MessagingStyle.Message)}
7164          * who don't have a Person associated with it will be displayed as if they were sent
7165          * by this user. The user also needs to have a valid name associated with it, which will
7166          * be enforced starting in Android P.
7167          */
7168         public MessagingStyle(@NonNull Person user) {
7169             mUser = user;
7170         }
7171 
7172         /**
7173          * Validate that this style was properly composed. This is called at build time.
7174          * @hide
7175          */
7176         @Override
7177         public void validate(Context context) {
7178             super.validate(context);
7179             if (context.getApplicationInfo().targetSdkVersion
7180                     >= Build.VERSION_CODES.P && (mUser == null || mUser.getName() == null)) {
7181                 throw new RuntimeException("User must be valid and have a name.");
7182             }
7183         }
7184 
7185         /**
7186          * @return the text that should be displayed in the statusBar when heads upped.
7187          * If {@code null} is returned, the default implementation will be used.
7188          *
7189          * @hide
7190          */
7191         @Override
7192         public CharSequence getHeadsUpStatusBarText() {
7193             CharSequence conversationTitle = !TextUtils.isEmpty(super.mBigContentTitle)
7194                     ? super.mBigContentTitle
7195                     : mConversationTitle;
7196             if (mConversationType == CONVERSATION_TYPE_LEGACY
7197                     && !TextUtils.isEmpty(conversationTitle) && !hasOnlyWhiteSpaceSenders()) {
7198                 return conversationTitle;
7199             }
7200             return null;
7201         }
7202 
7203         /**
7204          * @return the user to be displayed for any replies sent by the user
7205          */
7206         @NonNull
7207         public Person getUser() {
7208             return mUser;
7209         }
7210 
7211         /**
7212          * Returns the name to be displayed for any replies sent by the user
7213          *
7214          * @deprecated use {@link #getUser()} instead
7215          */
7216         public CharSequence getUserDisplayName() {
7217             return mUser.getName();
7218         }
7219 
7220         /**
7221          * Sets the title to be displayed on this conversation. May be set to {@code null}.
7222          *
7223          * <p>Starting in {@link Build.VERSION_CODES#R, this conversation title will be ignored if a
7224          * valid shortcutId is added via {@link Notification.Builder#setShortcutId(String)}. In this
7225          * case, {@link ShortcutInfo#getLongLabel()} (or, if missing,
7226          * {@link ShortcutInfo#getShortLabel()}) will be shown as the conversation title
7227          * instead.
7228          *
7229          * <p>This API's behavior was changed in SDK version {@link Build.VERSION_CODES#P}. If your
7230          * application's target version is less than {@link Build.VERSION_CODES#P}, setting a
7231          * conversation title to a non-null value will make {@link #isGroupConversation()} return
7232          * {@code true} and passing {@code null} will make it return {@code false}. In
7233          * {@link Build.VERSION_CODES#P} and beyond, use {@link #setGroupConversation(boolean)}
7234          * to set group conversation status.
7235          *
7236          * @param conversationTitle Title displayed for this conversation
7237          * @return this object for method chaining
7238          */
7239         public MessagingStyle setConversationTitle(@Nullable CharSequence conversationTitle) {
7240             mConversationTitle = conversationTitle;
7241             return this;
7242         }
7243 
7244         /**
7245          * Return the title to be displayed on this conversation. May return {@code null}.
7246          */
7247         @Nullable
7248         public CharSequence getConversationTitle() {
7249             return mConversationTitle;
7250         }
7251 
7252         /**
7253          * Sets the icon to be displayed on the conversation, derived from the shortcutId.
7254          *
7255          * @hide
7256          */
7257         public MessagingStyle setShortcutIcon(@Nullable Icon conversationIcon) {
7258             mShortcutIcon = conversationIcon;
7259             return this;
7260         }
7261 
7262         /**
7263          * Return the icon to be displayed on this conversation, derived from the shortcutId. May
7264          * return {@code null}.
7265          *
7266          * @hide
7267          */
7268         @Nullable
7269         public Icon getShortcutIcon() {
7270             return mShortcutIcon;
7271         }
7272 
7273         /**
7274          * Sets the conversation type of this MessageStyle notification.
7275          * {@link #CONVERSATION_TYPE_LEGACY} will use the "older" layout from pre-R,
7276          * {@link #CONVERSATION_TYPE_NORMAL} will use the new "conversation" layout, and
7277          * {@link #CONVERSATION_TYPE_IMPORTANT} will add additional "important" treatments.
7278          *
7279          * @hide
7280          */
7281         public MessagingStyle setConversationType(@ConversationType int conversationType) {
7282             mConversationType = conversationType;
7283             return this;
7284         }
7285 
7286         /** @hide */
7287         @ConversationType
7288         public int getConversationType() {
7289             return mConversationType;
7290         }
7291 
7292         /** @hide */
7293         public int getUnreadMessageCount() {
7294             return mUnreadMessageCount;
7295         }
7296 
7297         /** @hide */
7298         public MessagingStyle setUnreadMessageCount(int unreadMessageCount) {
7299             mUnreadMessageCount = unreadMessageCount;
7300             return this;
7301         }
7302 
7303         /**
7304          * Adds a message for display by this notification. Convenience call for a simple
7305          * {@link Message} in {@link #addMessage(Notification.MessagingStyle.Message)}.
7306          * @param text A {@link CharSequence} to be displayed as the message content
7307          * @param timestamp Time at which the message arrived
7308          * @param sender A {@link CharSequence} to be used for displaying the name of the
7309          * sender. Should be <code>null</code> for messages by the current user, in which case
7310          * the platform will insert {@link #getUserDisplayName()}.
7311          * Should be unique amongst all individuals in the conversation, and should be
7312          * consistent during re-posts of the notification.
7313          *
7314          * @see Message#Message(CharSequence, long, CharSequence)
7315          *
7316          * @return this object for method chaining
7317          *
7318          * @deprecated use {@link #addMessage(CharSequence, long, Person)}
7319          */
7320         public MessagingStyle addMessage(CharSequence text, long timestamp, CharSequence sender) {
7321             return addMessage(text, timestamp,
7322                     sender == null ? null : new Person.Builder().setName(sender).build());
7323         }
7324 
7325         /**
7326          * Adds a message for display by this notification. Convenience call for a simple
7327          * {@link Message} in {@link #addMessage(Notification.MessagingStyle.Message)}.
7328          * @param text A {@link CharSequence} to be displayed as the message content
7329          * @param timestamp Time at which the message arrived
7330          * @param sender The {@link Person} who sent the message.
7331          * Should be <code>null</code> for messages by the current user, in which case
7332          * the platform will insert the user set in {@code MessagingStyle(Person)}.
7333          *
7334          * @see Message#Message(CharSequence, long, CharSequence)
7335          *
7336          * @return this object for method chaining
7337          */
7338         public MessagingStyle addMessage(@NonNull CharSequence text, long timestamp,
7339                 @Nullable Person sender) {
7340             return addMessage(new Message(text, timestamp, sender));
7341         }
7342 
7343         /**
7344          * Adds a {@link Message} for display in this notification.
7345          *
7346          * <p>The messages should be added in chronologic order, i.e. the oldest first,
7347          * the newest last.
7348          *
7349          * @param message The {@link Message} to be displayed
7350          * @return this object for method chaining
7351          */
7352         public MessagingStyle addMessage(Message message) {
7353             mMessages.add(message);
7354             if (mMessages.size() > MAXIMUM_RETAINED_MESSAGES) {
7355                 mMessages.remove(0);
7356             }
7357             return this;
7358         }
7359 
7360         /**
7361          * Adds a {@link Message} for historic context in this notification.
7362          *
7363          * <p>Messages should be added as historic if they are not the main subject of the
7364          * notification but may give context to a conversation. The system may choose to present
7365          * them only when relevant, e.g. when replying to a message through a {@link RemoteInput}.
7366          *
7367          * <p>The messages should be added in chronologic order, i.e. the oldest first,
7368          * the newest last.
7369          *
7370          * @param message The historic {@link Message} to be added
7371          * @return this object for method chaining
7372          */
7373         public MessagingStyle addHistoricMessage(Message message) {
7374             mHistoricMessages.add(message);
7375             if (mHistoricMessages.size() > MAXIMUM_RETAINED_MESSAGES) {
7376                 mHistoricMessages.remove(0);
7377             }
7378             return this;
7379         }
7380 
7381         /**
7382          * Gets the list of {@code Message} objects that represent the notification
7383          */
getMessages()7384         public List<Message> getMessages() {
7385             return mMessages;
7386         }
7387 
7388         /**
7389          * Gets the list of historic {@code Message}s in the notification.
7390          */
getHistoricMessages()7391         public List<Message> getHistoricMessages() {
7392             return mHistoricMessages;
7393         }
7394 
7395         /**
7396          * Sets whether this conversation notification represents a group. If the app is targeting
7397          * Android P, this is required if the app wants to display the largeIcon set with
7398          * {@link Notification.Builder#setLargeIcon(Bitmap)}, otherwise it will be hidden.
7399          *
7400          * @param isGroupConversation {@code true} if the conversation represents a group,
7401          * {@code false} otherwise.
7402          * @return this object for method chaining
7403          */
setGroupConversation(boolean isGroupConversation)7404         public MessagingStyle setGroupConversation(boolean isGroupConversation) {
7405             mIsGroupConversation = isGroupConversation;
7406             return this;
7407         }
7408 
7409         /**
7410          * Returns {@code true} if this notification represents a group conversation, otherwise
7411          * {@code false}.
7412          *
7413          * <p> If the application that generated this {@link MessagingStyle} targets an SDK version
7414          * less than {@link Build.VERSION_CODES#P}, this method becomes dependent on whether or
7415          * not the conversation title is set; returning {@code true} if the conversation title is
7416          * a non-null value, or {@code false} otherwise. From {@link Build.VERSION_CODES#P} forward,
7417          * this method returns what's set by {@link #setGroupConversation(boolean)} allowing for
7418          * named, non-group conversations.
7419          *
7420          * @see #setConversationTitle(CharSequence)
7421          */
isGroupConversation()7422         public boolean isGroupConversation() {
7423             // When target SDK version is < P, a non-null conversation title dictates if this is
7424             // as group conversation.
7425             if (mBuilder != null
7426                     && mBuilder.mContext.getApplicationInfo().targetSdkVersion
7427                             < Build.VERSION_CODES.P) {
7428                 return mConversationTitle != null;
7429             }
7430 
7431             return mIsGroupConversation;
7432         }
7433 
7434         /**
7435          * @hide
7436          */
7437         @Override
addExtras(Bundle extras)7438         public void addExtras(Bundle extras) {
7439             super.addExtras(extras);
7440             if (mUser != null) {
7441                 // For legacy usages
7442                 extras.putCharSequence(EXTRA_SELF_DISPLAY_NAME, mUser.getName());
7443                 extras.putParcelable(EXTRA_MESSAGING_PERSON, mUser);
7444             }
7445             if (mConversationTitle != null) {
7446                 extras.putCharSequence(EXTRA_CONVERSATION_TITLE, mConversationTitle);
7447             }
7448             if (!mMessages.isEmpty()) { extras.putParcelableArray(EXTRA_MESSAGES,
7449                     Message.getBundleArrayForMessages(mMessages));
7450             }
7451             if (!mHistoricMessages.isEmpty()) { extras.putParcelableArray(EXTRA_HISTORIC_MESSAGES,
7452                     Message.getBundleArrayForMessages(mHistoricMessages));
7453             }
7454             if (mShortcutIcon != null) {
7455                 extras.putParcelable(EXTRA_CONVERSATION_ICON, mShortcutIcon);
7456             }
7457             extras.putInt(EXTRA_CONVERSATION_UNREAD_MESSAGE_COUNT, mUnreadMessageCount);
7458 
7459             fixTitleAndTextExtras(extras);
7460             extras.putBoolean(EXTRA_IS_GROUP_CONVERSATION, mIsGroupConversation);
7461         }
7462 
fixTitleAndTextExtras(Bundle extras)7463         private void fixTitleAndTextExtras(Bundle extras) {
7464             Message m = findLatestIncomingMessage();
7465             CharSequence text = (m == null) ? null : m.mText;
7466             CharSequence sender = m == null ? null
7467                     : m.mSender == null || TextUtils.isEmpty(m.mSender.getName())
7468                             ? mUser.getName() : m.mSender.getName();
7469             CharSequence title;
7470             if (!TextUtils.isEmpty(mConversationTitle)) {
7471                 if (!TextUtils.isEmpty(sender)) {
7472                     BidiFormatter bidi = BidiFormatter.getInstance();
7473                     title = mBuilder.mContext.getString(
7474                             com.android.internal.R.string.notification_messaging_title_template,
7475                             bidi.unicodeWrap(mConversationTitle), bidi.unicodeWrap(sender));
7476                 } else {
7477                     title = mConversationTitle;
7478                 }
7479             } else {
7480                 title = sender;
7481             }
7482 
7483             if (title != null) {
7484                 extras.putCharSequence(EXTRA_TITLE, title);
7485             }
7486             if (text != null) {
7487                 extras.putCharSequence(EXTRA_TEXT, text);
7488             }
7489         }
7490 
7491         /**
7492          * @hide
7493          */
7494         @Override
restoreFromExtras(Bundle extras)7495         protected void restoreFromExtras(Bundle extras) {
7496             super.restoreFromExtras(extras);
7497 
7498             mUser = extras.getParcelable(EXTRA_MESSAGING_PERSON);
7499             if (mUser == null) {
7500                 CharSequence displayName = extras.getCharSequence(EXTRA_SELF_DISPLAY_NAME);
7501                 mUser = new Person.Builder().setName(displayName).build();
7502             }
7503             mConversationTitle = extras.getCharSequence(EXTRA_CONVERSATION_TITLE);
7504             Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES);
7505             mMessages = Message.getMessagesFromBundleArray(messages);
7506             Parcelable[] histMessages = extras.getParcelableArray(EXTRA_HISTORIC_MESSAGES);
7507             mHistoricMessages = Message.getMessagesFromBundleArray(histMessages);
7508             mIsGroupConversation = extras.getBoolean(EXTRA_IS_GROUP_CONVERSATION);
7509             mUnreadMessageCount = extras.getInt(EXTRA_CONVERSATION_UNREAD_MESSAGE_COUNT);
7510             mShortcutIcon = extras.getParcelable(EXTRA_CONVERSATION_ICON);
7511         }
7512 
7513         /**
7514          * @hide
7515          */
7516         @Override
makeContentView(boolean increasedHeight)7517         public RemoteViews makeContentView(boolean increasedHeight) {
7518             mBuilder.mOriginalActions = mBuilder.mActions;
7519             mBuilder.mActions = new ArrayList<>();
7520             RemoteViews remoteViews = makeMessagingView(true /* isCollapsed */,
7521                     false /* hideLargeIcon */);
7522             mBuilder.mActions = mBuilder.mOriginalActions;
7523             mBuilder.mOriginalActions = null;
7524             return remoteViews;
7525         }
7526 
7527         /**
7528          * @hide
7529          * Spans are ignored when comparing text for visual difference.
7530          */
7531         @Override
areNotificationsVisiblyDifferent(Style other)7532         public boolean areNotificationsVisiblyDifferent(Style other) {
7533             if (other == null || getClass() != other.getClass()) {
7534                 return true;
7535             }
7536             MessagingStyle newS = (MessagingStyle) other;
7537             List<MessagingStyle.Message> oldMs = getMessages();
7538             List<MessagingStyle.Message> newMs = newS.getMessages();
7539 
7540             if (oldMs == null || newMs == null) {
7541                 newMs = new ArrayList<>();
7542             }
7543 
7544             int n = oldMs.size();
7545             if (n != newMs.size()) {
7546                 return true;
7547             }
7548             for (int i = 0; i < n; i++) {
7549                 MessagingStyle.Message oldM = oldMs.get(i);
7550                 MessagingStyle.Message newM = newMs.get(i);
7551                 if (!Objects.equals(
7552                         String.valueOf(oldM.getText()),
7553                         String.valueOf(newM.getText()))) {
7554                     return true;
7555                 }
7556                 if (!Objects.equals(oldM.getDataUri(), newM.getDataUri())) {
7557                     return true;
7558                 }
7559                 String oldSender = String.valueOf(oldM.getSenderPerson() == null
7560                         ? oldM.getSender()
7561                         : oldM.getSenderPerson().getName());
7562                 String newSender = String.valueOf(newM.getSenderPerson() == null
7563                         ? newM.getSender()
7564                         : newM.getSenderPerson().getName());
7565                 if (!Objects.equals(oldSender, newSender)) {
7566                     return true;
7567                 }
7568 
7569                 String oldKey = oldM.getSenderPerson() == null
7570                         ? null : oldM.getSenderPerson().getKey();
7571                 String newKey = newM.getSenderPerson() == null
7572                         ? null : newM.getSenderPerson().getKey();
7573                 if (!Objects.equals(oldKey, newKey)) {
7574                     return true;
7575                 }
7576                 // Other fields (like timestamp) intentionally excluded
7577             }
7578             return false;
7579         }
7580 
findLatestIncomingMessage()7581         private Message findLatestIncomingMessage() {
7582             return findLatestIncomingMessage(mMessages);
7583         }
7584 
7585         /**
7586          * @hide
7587          */
7588         @Nullable
findLatestIncomingMessage( List<Message> messages)7589         public static Message findLatestIncomingMessage(
7590                 List<Message> messages) {
7591             for (int i = messages.size() - 1; i >= 0; i--) {
7592                 Message m = messages.get(i);
7593                 // Incoming messages have a non-empty sender.
7594                 if (m.mSender != null && !TextUtils.isEmpty(m.mSender.getName())) {
7595                     return m;
7596                 }
7597             }
7598             if (!messages.isEmpty()) {
7599                 // No incoming messages, fall back to outgoing message
7600                 return messages.get(messages.size() - 1);
7601             }
7602             return null;
7603         }
7604 
7605         /**
7606          * @hide
7607          */
7608         @Override
makeBigContentView()7609         public RemoteViews makeBigContentView() {
7610             return makeMessagingView(false /* isCollapsed */, true /* hideLargeIcon */);
7611         }
7612 
7613         /**
7614          * Create a messaging layout.
7615          *
7616          * @param isCollapsed Should this use the collapsed layout
7617          * @param hideRightIcons Should the reply affordance be shown at the end of the notification
7618          * @return the created remoteView.
7619          */
7620         @NonNull
makeMessagingView(boolean isCollapsed, boolean hideRightIcons)7621         private RemoteViews makeMessagingView(boolean isCollapsed, boolean hideRightIcons) {
7622             CharSequence conversationTitle = !TextUtils.isEmpty(super.mBigContentTitle)
7623                     ? super.mBigContentTitle
7624                     : mConversationTitle;
7625             boolean atLeastP = mBuilder.mContext.getApplicationInfo().targetSdkVersion
7626                     >= Build.VERSION_CODES.P;
7627             boolean isOneToOne;
7628             CharSequence nameReplacement = null;
7629             if (!atLeastP) {
7630                 isOneToOne = TextUtils.isEmpty(conversationTitle);
7631                 if (hasOnlyWhiteSpaceSenders()) {
7632                     isOneToOne = true;
7633                     nameReplacement = conversationTitle;
7634                     conversationTitle = null;
7635                 }
7636             } else {
7637                 isOneToOne = !isGroupConversation();
7638             }
7639             boolean isConversationLayout = mConversationType != CONVERSATION_TYPE_LEGACY;
7640             boolean isImportantConversation = mConversationType == CONVERSATION_TYPE_IMPORTANT;
7641             Icon largeIcon = mBuilder.mN.mLargeIcon;
7642             TemplateBindResult bindResult = new TemplateBindResult();
7643             StandardTemplateParams p = mBuilder.mParams.reset()
7644                     .hasProgress(false)
7645                     .title(conversationTitle)
7646                     .text(null)
7647                     .hideLargeIcon(hideRightIcons || isOneToOne)
7648                     .hideReplyIcon(hideRightIcons)
7649                     .headerTextSecondary(conversationTitle);
7650             RemoteViews contentView = mBuilder.applyStandardTemplateWithActions(
7651                     isConversationLayout
7652                             ? mBuilder.getConversationLayoutResource()
7653                             : mBuilder.getMessagingLayoutResource(),
7654                     p,
7655                     bindResult);
7656 
7657             addExtras(mBuilder.mN.extras);
7658             if (!isConversationLayout) {
7659                 // also update the end margin if there is an image
7660                 contentView.setViewLayoutMarginEnd(R.id.notification_messaging,
7661                         bindResult.getIconMarginEnd());
7662             }
7663             contentView.setInt(R.id.status_bar_latest_event_content, "setLayoutColor",
7664                     mBuilder.isColorized(p)
7665                             ? mBuilder.getPrimaryTextColor(p)
7666                             : mBuilder.resolveContrastColor(p));
7667             contentView.setInt(R.id.status_bar_latest_event_content, "setSenderTextColor",
7668                     mBuilder.getPrimaryTextColor(p));
7669             contentView.setInt(R.id.status_bar_latest_event_content, "setMessageTextColor",
7670                     mBuilder.getSecondaryTextColor(p));
7671             contentView.setInt(R.id.status_bar_latest_event_content,
7672                     "setNotificationBackgroundColor",
7673                     mBuilder.resolveBackgroundColor(p));
7674             contentView.setBoolean(R.id.status_bar_latest_event_content, "setIsCollapsed",
7675                     isCollapsed);
7676             contentView.setIcon(R.id.status_bar_latest_event_content, "setAvatarReplacement",
7677                     mBuilder.mN.mLargeIcon);
7678             contentView.setCharSequence(R.id.status_bar_latest_event_content, "setNameReplacement",
7679                     nameReplacement);
7680             contentView.setBoolean(R.id.status_bar_latest_event_content, "setIsOneToOne",
7681                     isOneToOne);
7682             contentView.setCharSequence(R.id.status_bar_latest_event_content,
7683                     "setConversationTitle", conversationTitle);
7684             if (isConversationLayout) {
7685                 contentView.setIcon(R.id.status_bar_latest_event_content,
7686                         "setShortcutIcon", mShortcutIcon);
7687                 contentView.setBoolean(R.id.status_bar_latest_event_content,
7688                         "setIsImportantConversation", isImportantConversation);
7689             }
7690             contentView.setIcon(R.id.status_bar_latest_event_content, "setLargeIcon",
7691                     largeIcon);
7692             contentView.setBundle(R.id.status_bar_latest_event_content, "setData",
7693                     mBuilder.mN.extras);
7694             return contentView;
7695         }
7696 
hasOnlyWhiteSpaceSenders()7697         private boolean hasOnlyWhiteSpaceSenders() {
7698             for (int i = 0; i < mMessages.size(); i++) {
7699                 Message m = mMessages.get(i);
7700                 Person sender = m.getSenderPerson();
7701                 if (sender != null && !isWhiteSpace(sender.getName())) {
7702                     return false;
7703                 }
7704             }
7705             return true;
7706         }
7707 
isWhiteSpace(CharSequence sender)7708         private boolean isWhiteSpace(CharSequence sender) {
7709             if (TextUtils.isEmpty(sender)) {
7710                 return true;
7711             }
7712             if (sender.toString().matches("^\\s*$")) {
7713                 return true;
7714             }
7715             // Let's check if we only have 0 whitespace chars. Some apps did this as a workaround
7716             // For the presentation that we had.
7717             for (int i = 0; i < sender.length(); i++) {
7718                 char c = sender.charAt(i);
7719                 if (c != '\u200B') {
7720                     return false;
7721                 }
7722             }
7723             return true;
7724         }
7725 
createConversationTitleFromMessages()7726         private CharSequence createConversationTitleFromMessages() {
7727             ArraySet<CharSequence> names = new ArraySet<>();
7728             for (int i = 0; i < mMessages.size(); i++) {
7729                 Message m = mMessages.get(i);
7730                 Person sender = m.getSenderPerson();
7731                 if (sender != null) {
7732                     names.add(sender.getName());
7733                 }
7734             }
7735             SpannableStringBuilder title = new SpannableStringBuilder();
7736             int size = names.size();
7737             for (int i = 0; i < size; i++) {
7738                 CharSequence name = names.valueAt(i);
7739                 if (!TextUtils.isEmpty(title)) {
7740                     title.append(", ");
7741                 }
7742                 title.append(BidiFormatter.getInstance().unicodeWrap(name));
7743             }
7744             return title;
7745         }
7746 
7747         /**
7748          * @hide
7749          */
7750         @Override
makeHeadsUpContentView(boolean increasedHeight)7751         public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
7752             RemoteViews remoteViews = makeMessagingView(true /* isCollapsed */,
7753                     true /* hideLargeIcon */);
7754             if (mConversationType == CONVERSATION_TYPE_LEGACY) {
7755                 remoteViews.setInt(R.id.notification_messaging, "setMaxDisplayedLines", 1);
7756             }
7757             return remoteViews;
7758         }
7759 
makeFontColorSpan(int color)7760         private static TextAppearanceSpan makeFontColorSpan(int color) {
7761             return new TextAppearanceSpan(null, 0, 0,
7762                     ColorStateList.valueOf(color), null);
7763         }
7764 
7765         public static final class Message {
7766             /** @hide */
7767             public static final String KEY_TEXT = "text";
7768             static final String KEY_TIMESTAMP = "time";
7769             static final String KEY_SENDER = "sender";
7770             static final String KEY_SENDER_PERSON = "sender_person";
7771             static final String KEY_DATA_MIME_TYPE = "type";
7772             static final String KEY_DATA_URI= "uri";
7773             static final String KEY_EXTRAS_BUNDLE = "extras";
7774             static final String KEY_REMOTE_INPUT_HISTORY = "remote_input_history";
7775 
7776             private final CharSequence mText;
7777             private final long mTimestamp;
7778             @Nullable
7779             private final Person mSender;
7780             /** True if this message was generated from the extra
7781              *  {@link Notification#EXTRA_REMOTE_INPUT_HISTORY_ITEMS}
7782              */
7783             private final boolean mRemoteInputHistory;
7784 
7785             private Bundle mExtras = new Bundle();
7786             private String mDataMimeType;
7787             private Uri mDataUri;
7788 
7789             /**
7790              * Constructor
7791              * @param text A {@link CharSequence} to be displayed as the message content
7792              * @param timestamp Time at which the message arrived
7793              * @param sender A {@link CharSequence} to be used for displaying the name of the
7794              * sender. Should be <code>null</code> for messages by the current user, in which case
7795              * the platform will insert {@link MessagingStyle#getUserDisplayName()}.
7796              * Should be unique amongst all individuals in the conversation, and should be
7797              * consistent during re-posts of the notification.
7798              *
7799              *  @deprecated use {@code Message(CharSequence, long, Person)}
7800              */
Message(CharSequence text, long timestamp, CharSequence sender)7801             public Message(CharSequence text, long timestamp, CharSequence sender){
7802                 this(text, timestamp, sender == null ? null
7803                         : new Person.Builder().setName(sender).build());
7804             }
7805 
7806             /**
7807              * Constructor
7808              * @param text A {@link CharSequence} to be displayed as the message content
7809              * @param timestamp Time at which the message arrived
7810              * @param sender The {@link Person} who sent the message.
7811              * Should be <code>null</code> for messages by the current user, in which case
7812              * the platform will insert the user set in {@code MessagingStyle(Person)}.
7813              * <p>
7814              * The person provided should contain an Icon, set with
7815              * {@link Person.Builder#setIcon(Icon)} and also have a name provided
7816              * with {@link Person.Builder#setName(CharSequence)}. If multiple users have the same
7817              * name, consider providing a key with {@link Person.Builder#setKey(String)} in order
7818              * to differentiate between the different users.
7819              * </p>
7820              */
Message(@onNull CharSequence text, long timestamp, @Nullable Person sender)7821             public Message(@NonNull CharSequence text, long timestamp, @Nullable Person sender) {
7822                 this(text, timestamp, sender, false /* remoteHistory */);
7823             }
7824 
7825             /**
7826              * Constructor
7827              * @param text A {@link CharSequence} to be displayed as the message content
7828              * @param timestamp Time at which the message arrived
7829              * @param sender The {@link Person} who sent the message.
7830              * Should be <code>null</code> for messages by the current user, in which case
7831              * the platform will insert the user set in {@code MessagingStyle(Person)}.
7832              * @param remoteInputHistory True if the messages was generated from the extra
7833              * {@link Notification#EXTRA_REMOTE_INPUT_HISTORY_ITEMS}.
7834              * <p>
7835              * The person provided should contain an Icon, set with
7836              * {@link Person.Builder#setIcon(Icon)} and also have a name provided
7837              * with {@link Person.Builder#setName(CharSequence)}. If multiple users have the same
7838              * name, consider providing a key with {@link Person.Builder#setKey(String)} in order
7839              * to differentiate between the different users.
7840              * </p>
7841              * @hide
7842              */
Message(@onNull CharSequence text, long timestamp, @Nullable Person sender, boolean remoteInputHistory)7843             public Message(@NonNull CharSequence text, long timestamp, @Nullable Person sender,
7844                     boolean remoteInputHistory) {
7845                 mText = safeCharSequence(text);
7846                 mTimestamp = timestamp;
7847                 mSender = sender;
7848                 mRemoteInputHistory = remoteInputHistory;
7849             }
7850 
7851             /**
7852              * Sets a binary blob of data and an associated MIME type for a message. In the case
7853              * where the platform doesn't support the MIME type, the original text provided in the
7854              * constructor will be used.
7855              * @param dataMimeType The MIME type of the content. See
7856              * <a href="{@docRoot}notifications/messaging.html"> for the list of supported MIME
7857              * types on Android and Android Wear.
7858              * @param dataUri The uri containing the content whose type is given by the MIME type.
7859              * <p class="note">
7860              * <ol>
7861              *   <li>Notification Listeners including the System UI need permission to access the
7862              *       data the Uri points to. The recommended ways to do this are:</li>
7863              *   <li>Store the data in your own ContentProvider, making sure that other apps have
7864              *       the correct permission to access your provider. The preferred mechanism for
7865              *       providing access is to use per-URI permissions which are temporary and only
7866              *       grant access to the receiving application. An easy way to create a
7867              *       ContentProvider like this is to use the FileProvider helper class.</li>
7868              *   <li>Use the system MediaStore. The MediaStore is primarily aimed at video, audio
7869              *       and image MIME types, however beginning with Android 3.0 (API level 11) it can
7870              *       also store non-media types (see MediaStore.Files for more info). Files can be
7871              *       inserted into the MediaStore using scanFile() after which a content:// style
7872              *       Uri suitable for sharing is passed to the provided onScanCompleted() callback.
7873              *       Note that once added to the system MediaStore the content is accessible to any
7874              *       app on the device.</li>
7875              * </ol>
7876              * @return this object for method chaining
7877              */
setData(String dataMimeType, Uri dataUri)7878             public Message setData(String dataMimeType, Uri dataUri) {
7879                 mDataMimeType = dataMimeType;
7880                 mDataUri = dataUri;
7881                 return this;
7882             }
7883 
7884             /**
7885              * Get the text to be used for this message, or the fallback text if a type and content
7886              * Uri have been set
7887              */
getText()7888             public CharSequence getText() {
7889                 return mText;
7890             }
7891 
7892             /**
7893              * Get the time at which this message arrived
7894              */
getTimestamp()7895             public long getTimestamp() {
7896                 return mTimestamp;
7897             }
7898 
7899             /**
7900              * Get the extras Bundle for this message.
7901              */
getExtras()7902             public Bundle getExtras() {
7903                 return mExtras;
7904             }
7905 
7906             /**
7907              * Get the text used to display the contact's name in the messaging experience
7908              *
7909              * @deprecated use {@link #getSenderPerson()}
7910              */
getSender()7911             public CharSequence getSender() {
7912                 return mSender == null ? null : mSender.getName();
7913             }
7914 
7915             /**
7916              * Get the sender associated with this message.
7917              */
7918             @Nullable
getSenderPerson()7919             public Person getSenderPerson() {
7920                 return mSender;
7921             }
7922 
7923             /**
7924              * Get the MIME type of the data pointed to by the Uri
7925              */
getDataMimeType()7926             public String getDataMimeType() {
7927                 return mDataMimeType;
7928             }
7929 
7930             /**
7931              * Get the Uri pointing to the content of the message. Can be null, in which case
7932              * {@see #getText()} is used.
7933              */
getDataUri()7934             public Uri getDataUri() {
7935                 return mDataUri;
7936             }
7937 
7938             /**
7939              * @return True if the message was generated from
7940              * {@link Notification#EXTRA_REMOTE_INPUT_HISTORY_ITEMS}.
7941              * @hide
7942              */
isRemoteInputHistory()7943             public boolean isRemoteInputHistory() {
7944                 return mRemoteInputHistory;
7945             }
7946 
7947             /**
7948              * @hide
7949              */
7950             @VisibleForTesting
toBundle()7951             public Bundle toBundle() {
7952                 Bundle bundle = new Bundle();
7953                 if (mText != null) {
7954                     bundle.putCharSequence(KEY_TEXT, mText);
7955                 }
7956                 bundle.putLong(KEY_TIMESTAMP, mTimestamp);
7957                 if (mSender != null) {
7958                     // Legacy listeners need this
7959                     bundle.putCharSequence(KEY_SENDER, safeCharSequence(mSender.getName()));
7960                     bundle.putParcelable(KEY_SENDER_PERSON, mSender);
7961                 }
7962                 if (mDataMimeType != null) {
7963                     bundle.putString(KEY_DATA_MIME_TYPE, mDataMimeType);
7964                 }
7965                 if (mDataUri != null) {
7966                     bundle.putParcelable(KEY_DATA_URI, mDataUri);
7967                 }
7968                 if (mExtras != null) {
7969                     bundle.putBundle(KEY_EXTRAS_BUNDLE, mExtras);
7970                 }
7971                 if (mRemoteInputHistory) {
7972                     bundle.putBoolean(KEY_REMOTE_INPUT_HISTORY, mRemoteInputHistory);
7973                 }
7974                 return bundle;
7975             }
7976 
getBundleArrayForMessages(List<Message> messages)7977             static Bundle[] getBundleArrayForMessages(List<Message> messages) {
7978                 Bundle[] bundles = new Bundle[messages.size()];
7979                 final int N = messages.size();
7980                 for (int i = 0; i < N; i++) {
7981                     bundles[i] = messages.get(i).toBundle();
7982                 }
7983                 return bundles;
7984             }
7985 
7986             /**
7987              * Returns a list of messages read from the given bundle list, e.g.
7988              * {@link #EXTRA_MESSAGES} or {@link #EXTRA_HISTORIC_MESSAGES}.
7989              */
7990             @NonNull
getMessagesFromBundleArray(@ullable Parcelable[] bundles)7991             public static List<Message> getMessagesFromBundleArray(@Nullable Parcelable[] bundles) {
7992                 if (bundles == null) {
7993                     return new ArrayList<>();
7994                 }
7995                 List<Message> messages = new ArrayList<>(bundles.length);
7996                 for (int i = 0; i < bundles.length; i++) {
7997                     if (bundles[i] instanceof Bundle) {
7998                         Message message = getMessageFromBundle((Bundle)bundles[i]);
7999                         if (message != null) {
8000                             messages.add(message);
8001                         }
8002                     }
8003                 }
8004                 return messages;
8005             }
8006 
8007             /**
8008              * Returns the message that is stored in the bundle (e.g. one of the values in the lists
8009              * in {@link #EXTRA_MESSAGES} or {@link #EXTRA_HISTORIC_MESSAGES}) or null if the
8010              * message couldn't be resolved.
8011              * @hide
8012              */
8013             @Nullable
getMessageFromBundle(@onNull Bundle bundle)8014             public static Message getMessageFromBundle(@NonNull Bundle bundle) {
8015                 try {
8016                     if (!bundle.containsKey(KEY_TEXT) || !bundle.containsKey(KEY_TIMESTAMP)) {
8017                         return null;
8018                     } else {
8019 
8020                         Person senderPerson = bundle.getParcelable(KEY_SENDER_PERSON);
8021                         if (senderPerson == null) {
8022                             // Legacy apps that use compat don't actually provide the sender objects
8023                             // We need to fix the compat version to provide people / use
8024                             // the native api instead
8025                             CharSequence senderName = bundle.getCharSequence(KEY_SENDER);
8026                             if (senderName != null) {
8027                                 senderPerson = new Person.Builder().setName(senderName).build();
8028                             }
8029                         }
8030                         Message message = new Message(bundle.getCharSequence(KEY_TEXT),
8031                                 bundle.getLong(KEY_TIMESTAMP),
8032                                 senderPerson,
8033                                 bundle.getBoolean(KEY_REMOTE_INPUT_HISTORY, false));
8034                         if (bundle.containsKey(KEY_DATA_MIME_TYPE) &&
8035                                 bundle.containsKey(KEY_DATA_URI)) {
8036                             message.setData(bundle.getString(KEY_DATA_MIME_TYPE),
8037                                     (Uri) bundle.getParcelable(KEY_DATA_URI));
8038                         }
8039                         if (bundle.containsKey(KEY_EXTRAS_BUNDLE)) {
8040                             message.getExtras().putAll(bundle.getBundle(KEY_EXTRAS_BUNDLE));
8041                         }
8042                         return message;
8043                     }
8044                 } catch (ClassCastException e) {
8045                     return null;
8046                 }
8047             }
8048         }
8049     }
8050 
8051     /**
8052      * Helper class for generating large-format notifications that include a list of (up to 5) strings.
8053      *
8054      * Here's how you'd set the <code>InboxStyle</code> on a notification:
8055      * <pre class="prettyprint">
8056      * Notification notif = new Notification.Builder(mContext)
8057      *     .setContentTitle(&quot;5 New mails from &quot; + sender.toString())
8058      *     .setContentText(subject)
8059      *     .setSmallIcon(R.drawable.new_mail)
8060      *     .setLargeIcon(aBitmap)
8061      *     .setStyle(new Notification.InboxStyle()
8062      *         .addLine(str1)
8063      *         .addLine(str2)
8064      *         .setContentTitle(&quot;&quot;)
8065      *         .setSummaryText(&quot;+3 more&quot;))
8066      *     .build();
8067      * </pre>
8068      *
8069      * @see Notification#bigContentView
8070      */
8071     public static class InboxStyle extends Style {
8072 
8073         /**
8074          * The number of lines of remote input history allowed until we start reducing lines.
8075          */
8076         private static final int NUMBER_OF_HISTORY_ALLOWED_UNTIL_REDUCTION = 1;
8077         private ArrayList<CharSequence> mTexts = new ArrayList<CharSequence>(5);
8078 
InboxStyle()8079         public InboxStyle() {
8080         }
8081 
8082         /**
8083          * @deprecated use {@code InboxStyle()}.
8084          */
8085         @Deprecated
InboxStyle(Builder builder)8086         public InboxStyle(Builder builder) {
8087             setBuilder(builder);
8088         }
8089 
8090         /**
8091          * Overrides ContentTitle in the big form of the template.
8092          * This defaults to the value passed to setContentTitle().
8093          */
setBigContentTitle(CharSequence title)8094         public InboxStyle setBigContentTitle(CharSequence title) {
8095             internalSetBigContentTitle(safeCharSequence(title));
8096             return this;
8097         }
8098 
8099         /**
8100          * Set the first line of text after the detail section in the big form of the template.
8101          */
setSummaryText(CharSequence cs)8102         public InboxStyle setSummaryText(CharSequence cs) {
8103             internalSetSummaryText(safeCharSequence(cs));
8104             return this;
8105         }
8106 
8107         /**
8108          * Append a line to the digest section of the Inbox notification.
8109          */
addLine(CharSequence cs)8110         public InboxStyle addLine(CharSequence cs) {
8111             mTexts.add(safeCharSequence(cs));
8112             return this;
8113         }
8114 
8115         /**
8116          * @hide
8117          */
getLines()8118         public ArrayList<CharSequence> getLines() {
8119             return mTexts;
8120         }
8121 
8122         /**
8123          * @hide
8124          */
addExtras(Bundle extras)8125         public void addExtras(Bundle extras) {
8126             super.addExtras(extras);
8127 
8128             CharSequence[] a = new CharSequence[mTexts.size()];
8129             extras.putCharSequenceArray(EXTRA_TEXT_LINES, mTexts.toArray(a));
8130         }
8131 
8132         /**
8133          * @hide
8134          */
8135         @Override
restoreFromExtras(Bundle extras)8136         protected void restoreFromExtras(Bundle extras) {
8137             super.restoreFromExtras(extras);
8138 
8139             mTexts.clear();
8140             if (extras.containsKey(EXTRA_TEXT_LINES)) {
8141                 Collections.addAll(mTexts, extras.getCharSequenceArray(EXTRA_TEXT_LINES));
8142             }
8143         }
8144 
8145         /**
8146          * @hide
8147          */
makeBigContentView()8148         public RemoteViews makeBigContentView() {
8149             StandardTemplateParams p = mBuilder.mParams.reset().fillTextsFrom(mBuilder).text(null);
8150             TemplateBindResult result = new TemplateBindResult();
8151             RemoteViews contentView = getStandardView(mBuilder.getInboxLayoutResource(), p, result);
8152 
8153             int[] rowIds = {R.id.inbox_text0, R.id.inbox_text1, R.id.inbox_text2, R.id.inbox_text3,
8154                     R.id.inbox_text4, R.id.inbox_text5, R.id.inbox_text6};
8155 
8156             // Make sure all rows are gone in case we reuse a view.
8157             for (int rowId : rowIds) {
8158                 contentView.setViewVisibility(rowId, View.GONE);
8159             }
8160 
8161             int i=0;
8162             int topPadding = mBuilder.mContext.getResources().getDimensionPixelSize(
8163                     R.dimen.notification_inbox_item_top_padding);
8164             boolean first = true;
8165             int onlyViewId = 0;
8166             int maxRows = rowIds.length;
8167             if (mBuilder.mActions.size() > 0) {
8168                 maxRows--;
8169             }
8170             RemoteInputHistoryItem[] remoteInputHistory = getParcelableArrayFromBundle(
8171                     mBuilder.mN.extras, EXTRA_REMOTE_INPUT_HISTORY_ITEMS,
8172                     RemoteInputHistoryItem.class);
8173             if (remoteInputHistory != null
8174                     && remoteInputHistory.length > NUMBER_OF_HISTORY_ALLOWED_UNTIL_REDUCTION) {
8175                 // Let's remove some messages to make room for the remote input history.
8176                 // 1 is always able to fit, but let's remove them if they are 2 or 3
8177                 int numRemoteInputs = Math.min(remoteInputHistory.length,
8178                         MAX_REMOTE_INPUT_HISTORY_LINES);
8179                 int totalNumRows = mTexts.size() + numRemoteInputs
8180                         - NUMBER_OF_HISTORY_ALLOWED_UNTIL_REDUCTION;
8181                 if (totalNumRows > maxRows) {
8182                     int overflow = totalNumRows - maxRows;
8183                     if (mTexts.size() > maxRows) {
8184                         // Heuristic: if the Texts don't fit anyway, we'll rather drop the last
8185                         // few messages, even with the remote input
8186                         maxRows -= overflow;
8187                     } else  {
8188                         // otherwise we drop the first messages
8189                         i = overflow;
8190                     }
8191                 }
8192             }
8193             while (i < mTexts.size() && i < maxRows) {
8194                 CharSequence str = mTexts.get(i);
8195                 if (!TextUtils.isEmpty(str)) {
8196                     contentView.setViewVisibility(rowIds[i], View.VISIBLE);
8197                     contentView.setTextViewText(rowIds[i],
8198                             mBuilder.processTextSpans(mBuilder.processLegacyText(str)));
8199                     mBuilder.setTextViewColorSecondary(contentView, rowIds[i], p);
8200                     contentView.setViewPadding(rowIds[i], 0, topPadding, 0, 0);
8201                     handleInboxImageMargin(contentView, rowIds[i], first,
8202                             result.getIconMarginEnd());
8203                     if (first) {
8204                         onlyViewId = rowIds[i];
8205                     } else {
8206                         onlyViewId = 0;
8207                     }
8208                     first = false;
8209                 }
8210                 i++;
8211             }
8212             if (onlyViewId != 0) {
8213                 // We only have 1 entry, lets make it look like the normal Text of a Bigtext
8214                 topPadding = mBuilder.mContext.getResources().getDimensionPixelSize(
8215                         R.dimen.notification_text_margin_top);
8216                 contentView.setViewPadding(onlyViewId, 0, topPadding, 0, 0);
8217             }
8218 
8219             return contentView;
8220         }
8221 
8222         /**
8223          * @hide
8224          */
8225         @Override
areNotificationsVisiblyDifferent(Style other)8226         public boolean areNotificationsVisiblyDifferent(Style other) {
8227             if (other == null || getClass() != other.getClass()) {
8228                 return true;
8229             }
8230             InboxStyle newS = (InboxStyle) other;
8231 
8232             final ArrayList<CharSequence> myLines = getLines();
8233             final ArrayList<CharSequence> newLines = newS.getLines();
8234             final int n = myLines.size();
8235             if (n != newLines.size()) {
8236                 return true;
8237             }
8238 
8239             for (int i = 0; i < n; i++) {
8240                 if (!Objects.equals(
8241                         String.valueOf(myLines.get(i)),
8242                         String.valueOf(newLines.get(i)))) {
8243                     return true;
8244                 }
8245             }
8246             return false;
8247         }
8248 
handleInboxImageMargin(RemoteViews contentView, int id, boolean first, int marginEndValue)8249         private void handleInboxImageMargin(RemoteViews contentView, int id, boolean first,
8250                 int marginEndValue) {
8251             int endMargin = 0;
8252             if (first) {
8253                 final int max = mBuilder.mN.extras.getInt(EXTRA_PROGRESS_MAX, 0);
8254                 final boolean ind = mBuilder.mN.extras.getBoolean(EXTRA_PROGRESS_INDETERMINATE);
8255                 boolean hasProgress = max != 0 || ind;
8256                 if (!hasProgress) {
8257                     endMargin = marginEndValue;
8258                 }
8259             }
8260             contentView.setViewLayoutMarginEnd(id, endMargin);
8261         }
8262     }
8263 
8264     /**
8265      * Notification style for media playback notifications.
8266      *
8267      * In the expanded form, {@link Notification#bigContentView}, up to 5
8268      * {@link Notification.Action}s specified with
8269      * {@link Notification.Builder#addAction(Action) addAction} will be
8270      * shown as icon-only pushbuttons, suitable for transport controls. The Bitmap given to
8271      * {@link Notification.Builder#setLargeIcon(android.graphics.Bitmap) setLargeIcon()} will be
8272      * treated as album artwork.
8273      * <p>
8274      * Unlike the other styles provided here, MediaStyle can also modify the standard-size
8275      * {@link Notification#contentView}; by providing action indices to
8276      * {@link #setShowActionsInCompactView(int...)} you can promote up to 3 actions to be displayed
8277      * in the standard view alongside the usual content.
8278      * <p>
8279      * Notifications created with MediaStyle will have their category set to
8280      * {@link Notification#CATEGORY_TRANSPORT CATEGORY_TRANSPORT} unless you set a different
8281      * category using {@link Notification.Builder#setCategory(String) setCategory()}.
8282      * <p>
8283      * Finally, if you attach a {@link android.media.session.MediaSession.Token} using
8284      * {@link android.app.Notification.MediaStyle#setMediaSession(MediaSession.Token)},
8285      * the System UI can identify this as a notification representing an active media session
8286      * and respond accordingly (by showing album artwork in the lockscreen, for example).
8287      *
8288      * <p>
8289      * Starting at {@link android.os.Build.VERSION_CODES#O Android O} any notification that has a
8290      * media session attached with {@link #setMediaSession(MediaSession.Token)} will be colorized.
8291      * You can opt-out of this behavior by using {@link Notification.Builder#setColorized(boolean)}.
8292      * <p>
8293      *
8294      * To use this style with your Notification, feed it to
8295      * {@link Notification.Builder#setStyle(android.app.Notification.Style)} like so:
8296      * <pre class="prettyprint">
8297      * Notification noti = new Notification.Builder()
8298      *     .setSmallIcon(R.drawable.ic_stat_player)
8299      *     .setContentTitle(&quot;Track title&quot;)
8300      *     .setContentText(&quot;Artist - Album&quot;)
8301      *     .setLargeIcon(albumArtBitmap))
8302      *     .setStyle(<b>new Notification.MediaStyle()</b>
8303      *         .setMediaSession(mySession))
8304      *     .build();
8305      * </pre>
8306      *
8307      * @see Notification#bigContentView
8308      * @see Notification.Builder#setColorized(boolean)
8309      */
8310     public static class MediaStyle extends Style {
8311         // Changing max media buttons requires also changing templates
8312         // (notification_template_material_media and notification_template_material_big_media).
8313         static final int MAX_MEDIA_BUTTONS_IN_COMPACT = 3;
8314         static final int MAX_MEDIA_BUTTONS = 5;
8315         @IdRes private static final int[] MEDIA_BUTTON_IDS = {
8316                 R.id.action0,
8317                 R.id.action1,
8318                 R.id.action2,
8319                 R.id.action3,
8320                 R.id.action4,
8321         };
8322 
8323         private int[] mActionsToShowInCompact = null;
8324         private MediaSession.Token mToken;
8325 
MediaStyle()8326         public MediaStyle() {
8327         }
8328 
8329         /**
8330          * @deprecated use {@code MediaStyle()}.
8331          */
8332         @Deprecated
MediaStyle(Builder builder)8333         public MediaStyle(Builder builder) {
8334             setBuilder(builder);
8335         }
8336 
8337         /**
8338          * Request up to 3 actions (by index in the order of addition) to be shown in the compact
8339          * notification view.
8340          *
8341          * @param actions the indices of the actions to show in the compact notification view
8342          */
setShowActionsInCompactView(int...actions)8343         public MediaStyle setShowActionsInCompactView(int...actions) {
8344             mActionsToShowInCompact = actions;
8345             return this;
8346         }
8347 
8348         /**
8349          * Attach a {@link android.media.session.MediaSession.Token} to this Notification
8350          * to provide additional playback information and control to the SystemUI.
8351          */
setMediaSession(MediaSession.Token token)8352         public MediaStyle setMediaSession(MediaSession.Token token) {
8353             mToken = token;
8354             return this;
8355         }
8356 
8357         /**
8358          * @hide
8359          */
8360         @Override
8361         @UnsupportedAppUsage
buildStyled(Notification wip)8362         public Notification buildStyled(Notification wip) {
8363             super.buildStyled(wip);
8364             if (wip.category == null) {
8365                 wip.category = Notification.CATEGORY_TRANSPORT;
8366             }
8367             return wip;
8368         }
8369 
8370         /**
8371          * @hide
8372          */
8373         @Override
makeContentView(boolean increasedHeight)8374         public RemoteViews makeContentView(boolean increasedHeight) {
8375             return makeMediaContentView();
8376         }
8377 
8378         /**
8379          * @hide
8380          */
8381         @Override
makeBigContentView()8382         public RemoteViews makeBigContentView() {
8383             return makeMediaBigContentView();
8384         }
8385 
8386         /**
8387          * @hide
8388          */
8389         @Override
makeHeadsUpContentView(boolean increasedHeight)8390         public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
8391             return makeMediaContentView();
8392         }
8393 
8394         /** @hide */
8395         @Override
addExtras(Bundle extras)8396         public void addExtras(Bundle extras) {
8397             super.addExtras(extras);
8398 
8399             if (mToken != null) {
8400                 extras.putParcelable(EXTRA_MEDIA_SESSION, mToken);
8401             }
8402             if (mActionsToShowInCompact != null) {
8403                 extras.putIntArray(EXTRA_COMPACT_ACTIONS, mActionsToShowInCompact);
8404             }
8405         }
8406 
8407         /**
8408          * @hide
8409          */
8410         @Override
restoreFromExtras(Bundle extras)8411         protected void restoreFromExtras(Bundle extras) {
8412             super.restoreFromExtras(extras);
8413 
8414             if (extras.containsKey(EXTRA_MEDIA_SESSION)) {
8415                 mToken = extras.getParcelable(EXTRA_MEDIA_SESSION);
8416             }
8417             if (extras.containsKey(EXTRA_COMPACT_ACTIONS)) {
8418                 mActionsToShowInCompact = extras.getIntArray(EXTRA_COMPACT_ACTIONS);
8419             }
8420         }
8421 
8422         /**
8423          * @hide
8424          */
8425         @Override
areNotificationsVisiblyDifferent(Style other)8426         public boolean areNotificationsVisiblyDifferent(Style other) {
8427             if (other == null || getClass() != other.getClass()) {
8428                 return true;
8429             }
8430             // All fields to compare are on the Notification object
8431             return false;
8432         }
8433 
bindMediaActionButton(RemoteViews container, @IdRes int buttonId, Action action, StandardTemplateParams p)8434         private void bindMediaActionButton(RemoteViews container, @IdRes int buttonId,
8435                 Action action, StandardTemplateParams p) {
8436             final boolean tombstone = (action.actionIntent == null);
8437             container.setViewVisibility(buttonId, View.VISIBLE);
8438             if (buttonId != R.id.media_seamless) {
8439                 container.setImageViewIcon(buttonId, action.getIcon());
8440             }
8441 
8442             // If the action buttons should not be tinted, then just use the default
8443             // notification color. Otherwise, just use the passed-in color.
8444             Resources resources = mBuilder.mContext.getResources();
8445             Configuration currentConfig = resources.getConfiguration();
8446             boolean inNightMode = (currentConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK)
8447                     == Configuration.UI_MODE_NIGHT_YES;
8448             int tintColor = mBuilder.shouldTintActionButtons() || mBuilder.isColorized(p)
8449                     ? getActionColor(p)
8450                     : ContrastColorUtil.resolveColor(mBuilder.mContext,
8451                             Notification.COLOR_DEFAULT, inNightMode);
8452 
8453             container.setDrawableTint(buttonId, false, tintColor,
8454                     PorterDuff.Mode.SRC_ATOP);
8455 
8456             final TypedArray typedArray = mBuilder.mContext.obtainStyledAttributes(
8457                     new int[]{ android.R.attr.colorControlHighlight });
8458             int rippleAlpha = Color.alpha(typedArray.getColor(0, 0));
8459             typedArray.recycle();
8460             int rippleColor = Color.argb(rippleAlpha, Color.red(tintColor), Color.green(tintColor),
8461                     Color.blue(tintColor));
8462             container.setRippleDrawableColor(buttonId, ColorStateList.valueOf(rippleColor));
8463 
8464             if (!tombstone) {
8465                 container.setOnClickPendingIntent(buttonId, action.actionIntent);
8466             }
8467             container.setContentDescription(buttonId, action.title);
8468         }
8469 
makeMediaContentView()8470         private RemoteViews makeMediaContentView() {
8471             StandardTemplateParams p = mBuilder.mParams.reset().hasProgress(false).fillTextsFrom(
8472                     mBuilder);
8473             RemoteViews view = mBuilder.applyStandardTemplate(
8474                     R.layout.notification_template_material_media, p,
8475                     null /* result */);
8476 
8477             final int numActions = mBuilder.mActions.size();
8478             final int numActionsToShow = mActionsToShowInCompact == null
8479                     ? 0
8480                     : Math.min(mActionsToShowInCompact.length, MAX_MEDIA_BUTTONS_IN_COMPACT);
8481             if (numActionsToShow > numActions) {
8482                 throw new IllegalArgumentException(String.format(
8483                         "setShowActionsInCompactView: action %d out of bounds (max %d)",
8484                         numActions, numActions - 1));
8485             }
8486             for (int i = 0; i < MAX_MEDIA_BUTTONS_IN_COMPACT; i++) {
8487                 if (i < numActionsToShow) {
8488                     final Action action = mBuilder.mActions.get(mActionsToShowInCompact[i]);
8489                     bindMediaActionButton(view, MEDIA_BUTTON_IDS[i], action, p);
8490                 } else {
8491                     view.setViewVisibility(MEDIA_BUTTON_IDS[i], View.GONE);
8492                 }
8493             }
8494             bindMediaActionButton(view, R.id.media_seamless, new Action(
8495                     R.drawable.ic_media_seamless, mBuilder.mContext.getString(
8496                             com.android.internal.R.string.ext_media_seamless_action), null), p);
8497             view.setViewVisibility(R.id.media_seamless, View.GONE);
8498             handleImage(view);
8499             // handle the content margin
8500             int endMargin = R.dimen.notification_content_margin_end;
8501             if (mBuilder.mN.hasLargeIcon()) {
8502                 endMargin = R.dimen.notification_media_image_margin_end;
8503             }
8504             view.setViewLayoutMarginEndDimen(R.id.notification_main_column, endMargin);
8505             return view;
8506         }
8507 
getActionColor(StandardTemplateParams p)8508         private int getActionColor(StandardTemplateParams p) {
8509             return mBuilder.isColorized(p) ? mBuilder.getPrimaryTextColor(p)
8510                     : mBuilder.resolveContrastColor(p);
8511         }
8512 
makeMediaBigContentView()8513         private RemoteViews makeMediaBigContentView() {
8514             final int actionCount = Math.min(mBuilder.mActions.size(), MAX_MEDIA_BUTTONS);
8515             // Dont add an expanded view if there is no more content to be revealed
8516             int actionsInCompact = mActionsToShowInCompact == null
8517                     ? 0
8518                     : Math.min(mActionsToShowInCompact.length, MAX_MEDIA_BUTTONS_IN_COMPACT);
8519             if (!mBuilder.mN.hasLargeIcon() && actionCount <= actionsInCompact) {
8520                 return null;
8521             }
8522             StandardTemplateParams p = mBuilder.mParams.reset().hasProgress(false).fillTextsFrom(
8523                     mBuilder);
8524             RemoteViews big = mBuilder.applyStandardTemplate(
8525                     R.layout.notification_template_material_big_media, p , null /* result */);
8526 
8527             for (int i = 0; i < MAX_MEDIA_BUTTONS; i++) {
8528                 if (i < actionCount) {
8529                     bindMediaActionButton(big, MEDIA_BUTTON_IDS[i], mBuilder.mActions.get(i), p);
8530                 } else {
8531                     big.setViewVisibility(MEDIA_BUTTON_IDS[i], View.GONE);
8532                 }
8533             }
8534             bindMediaActionButton(big, R.id.media_seamless, new Action(R.drawable.ic_media_seamless,
8535                     mBuilder.mContext.getString(
8536                             com.android.internal.R.string.ext_media_seamless_action), null), p);
8537             big.setViewVisibility(R.id.media_seamless, View.GONE);
8538             handleImage(big);
8539             return big;
8540         }
8541 
handleImage(RemoteViews contentView)8542         private void handleImage(RemoteViews contentView) {
8543             if (mBuilder.mN.hasLargeIcon()) {
8544                 contentView.setViewLayoutMarginEndDimen(R.id.line1, 0);
8545                 contentView.setViewLayoutMarginEndDimen(R.id.text, 0);
8546             }
8547         }
8548 
8549         /**
8550          * @hide
8551          */
8552         @Override
hasProgress()8553         protected boolean hasProgress() {
8554             return false;
8555         }
8556     }
8557 
8558     /**
8559      * Notification style for custom views that are decorated by the system
8560      *
8561      * <p>Instead of providing a notification that is completely custom, a developer can set this
8562      * style and still obtain system decorations like the notification header with the expand
8563      * affordance and actions.
8564      *
8565      * <p>Use {@link android.app.Notification.Builder#setCustomContentView(RemoteViews)},
8566      * {@link android.app.Notification.Builder#setCustomBigContentView(RemoteViews)} and
8567      * {@link android.app.Notification.Builder#setCustomHeadsUpContentView(RemoteViews)} to set the
8568      * corresponding custom views to display.
8569      *
8570      * To use this style with your Notification, feed it to
8571      * {@link Notification.Builder#setStyle(android.app.Notification.Style)} like so:
8572      * <pre class="prettyprint">
8573      * Notification noti = new Notification.Builder()
8574      *     .setSmallIcon(R.drawable.ic_stat_player)
8575      *     .setLargeIcon(albumArtBitmap))
8576      *     .setCustomContentView(contentView);
8577      *     .setStyle(<b>new Notification.DecoratedCustomViewStyle()</b>)
8578      *     .build();
8579      * </pre>
8580      */
8581     public static class DecoratedCustomViewStyle extends Style {
8582 
DecoratedCustomViewStyle()8583         public DecoratedCustomViewStyle() {
8584         }
8585 
8586         /**
8587          * @hide
8588          */
displayCustomViewInline()8589         public boolean displayCustomViewInline() {
8590             return true;
8591         }
8592 
8593         /**
8594          * @hide
8595          */
8596         @Override
makeContentView(boolean increasedHeight)8597         public RemoteViews makeContentView(boolean increasedHeight) {
8598             return makeStandardTemplateWithCustomContent(mBuilder.mN.contentView);
8599         }
8600 
8601         /**
8602          * @hide
8603          */
8604         @Override
makeBigContentView()8605         public RemoteViews makeBigContentView() {
8606             return makeDecoratedBigContentView();
8607         }
8608 
8609         /**
8610          * @hide
8611          */
8612         @Override
makeHeadsUpContentView(boolean increasedHeight)8613         public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
8614             return makeDecoratedHeadsUpContentView();
8615         }
8616 
makeDecoratedHeadsUpContentView()8617         private RemoteViews makeDecoratedHeadsUpContentView() {
8618             RemoteViews headsUpContentView = mBuilder.mN.headsUpContentView == null
8619                     ? mBuilder.mN.contentView
8620                     : mBuilder.mN.headsUpContentView;
8621             if (mBuilder.mActions.size() == 0) {
8622                return makeStandardTemplateWithCustomContent(headsUpContentView);
8623             }
8624             TemplateBindResult result = new TemplateBindResult();
8625             RemoteViews remoteViews = mBuilder.applyStandardTemplateWithActions(
8626                         mBuilder.getBigBaseLayoutResource(), result);
8627             buildIntoRemoteViewContent(remoteViews, headsUpContentView, result);
8628             return remoteViews;
8629         }
8630 
makeStandardTemplateWithCustomContent(RemoteViews customContent)8631         private RemoteViews makeStandardTemplateWithCustomContent(RemoteViews customContent) {
8632             TemplateBindResult result = new TemplateBindResult();
8633             RemoteViews remoteViews = mBuilder.applyStandardTemplate(
8634                     mBuilder.getBaseLayoutResource(), result);
8635             buildIntoRemoteViewContent(remoteViews, customContent, result);
8636             return remoteViews;
8637         }
8638 
makeDecoratedBigContentView()8639         private RemoteViews makeDecoratedBigContentView() {
8640             RemoteViews bigContentView = mBuilder.mN.bigContentView == null
8641                     ? mBuilder.mN.contentView
8642                     : mBuilder.mN.bigContentView;
8643             if (mBuilder.mActions.size() == 0) {
8644                 return makeStandardTemplateWithCustomContent(bigContentView);
8645             }
8646             TemplateBindResult result = new TemplateBindResult();
8647             RemoteViews remoteViews = mBuilder.applyStandardTemplateWithActions(
8648                     mBuilder.getBigBaseLayoutResource(), result);
8649             buildIntoRemoteViewContent(remoteViews, bigContentView, result);
8650             return remoteViews;
8651         }
8652 
buildIntoRemoteViewContent(RemoteViews remoteViews, RemoteViews customContent, TemplateBindResult result)8653         private void buildIntoRemoteViewContent(RemoteViews remoteViews,
8654                 RemoteViews customContent, TemplateBindResult result) {
8655             int childIndex = -1;
8656             if (customContent != null) {
8657                 // Need to clone customContent before adding, because otherwise it can no longer be
8658                 // parceled independently of remoteViews.
8659                 customContent = customContent.clone();
8660                 remoteViews.removeAllViewsExceptId(R.id.notification_main_column, R.id.progress);
8661                 remoteViews.addView(R.id.notification_main_column, customContent, 0 /* index */);
8662                 remoteViews.addFlags(RemoteViews.FLAG_REAPPLY_DISALLOWED);
8663                 childIndex = 0;
8664             }
8665             remoteViews.setIntTag(R.id.notification_main_column,
8666                     com.android.internal.R.id.notification_custom_view_index_tag,
8667                     childIndex);
8668             // also update the end margin if there is an image
8669             Resources resources = mBuilder.mContext.getResources();
8670             int endMargin = resources.getDimensionPixelSize(
8671                     R.dimen.notification_content_margin_end) + result.getIconMarginEnd();
8672             remoteViews.setViewLayoutMarginEnd(R.id.notification_main_column, endMargin);
8673         }
8674 
8675         /**
8676          * @hide
8677          */
8678         @Override
areNotificationsVisiblyDifferent(Style other)8679         public boolean areNotificationsVisiblyDifferent(Style other) {
8680             if (other == null || getClass() != other.getClass()) {
8681                 return true;
8682             }
8683             // Comparison done for all custom RemoteViews, independent of style
8684             return false;
8685         }
8686     }
8687 
8688     /**
8689      * Notification style for media custom views that are decorated by the system
8690      *
8691      * <p>Instead of providing a media notification that is completely custom, a developer can set
8692      * this style and still obtain system decorations like the notification header with the expand
8693      * affordance and actions.
8694      *
8695      * <p>Use {@link android.app.Notification.Builder#setCustomContentView(RemoteViews)},
8696      * {@link android.app.Notification.Builder#setCustomBigContentView(RemoteViews)} and
8697      * {@link android.app.Notification.Builder#setCustomHeadsUpContentView(RemoteViews)} to set the
8698      * corresponding custom views to display.
8699      * <p>
8700      * Contrary to {@link MediaStyle} a developer has to opt-in to the colorizing of the
8701      * notification by using {@link Notification.Builder#setColorized(boolean)}.
8702      * <p>
8703      * To use this style with your Notification, feed it to
8704      * {@link Notification.Builder#setStyle(android.app.Notification.Style)} like so:
8705      * <pre class="prettyprint">
8706      * Notification noti = new Notification.Builder()
8707      *     .setSmallIcon(R.drawable.ic_stat_player)
8708      *     .setLargeIcon(albumArtBitmap))
8709      *     .setCustomContentView(contentView);
8710      *     .setStyle(<b>new Notification.DecoratedMediaCustomViewStyle()</b>
8711      *          .setMediaSession(mySession))
8712      *     .build();
8713      * </pre>
8714      *
8715      * @see android.app.Notification.DecoratedCustomViewStyle
8716      * @see android.app.Notification.MediaStyle
8717      */
8718     public static class DecoratedMediaCustomViewStyle extends MediaStyle {
8719 
DecoratedMediaCustomViewStyle()8720         public DecoratedMediaCustomViewStyle() {
8721         }
8722 
8723         /**
8724          * @hide
8725          */
displayCustomViewInline()8726         public boolean displayCustomViewInline() {
8727             return true;
8728         }
8729 
8730         /**
8731          * @hide
8732          */
8733         @Override
makeContentView(boolean increasedHeight)8734         public RemoteViews makeContentView(boolean increasedHeight) {
8735             RemoteViews remoteViews = super.makeContentView(false /* increasedHeight */);
8736             return buildIntoRemoteView(remoteViews, R.id.notification_content_container,
8737                     mBuilder.mN.contentView);
8738         }
8739 
8740         /**
8741          * @hide
8742          */
8743         @Override
makeBigContentView()8744         public RemoteViews makeBigContentView() {
8745             RemoteViews customRemoteView = mBuilder.mN.bigContentView != null
8746                     ? mBuilder.mN.bigContentView
8747                     : mBuilder.mN.contentView;
8748             return makeBigContentViewWithCustomContent(customRemoteView);
8749         }
8750 
makeBigContentViewWithCustomContent(RemoteViews customRemoteView)8751         private RemoteViews makeBigContentViewWithCustomContent(RemoteViews customRemoteView) {
8752             RemoteViews remoteViews = super.makeBigContentView();
8753             if (remoteViews != null) {
8754                 return buildIntoRemoteView(remoteViews, R.id.notification_main_column,
8755                         customRemoteView);
8756             } else if (customRemoteView != mBuilder.mN.contentView){
8757                 remoteViews = super.makeContentView(false /* increasedHeight */);
8758                 return buildIntoRemoteView(remoteViews, R.id.notification_content_container,
8759                         customRemoteView);
8760             } else {
8761                 return null;
8762             }
8763         }
8764 
8765         /**
8766          * @hide
8767          */
8768         @Override
makeHeadsUpContentView(boolean increasedHeight)8769         public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
8770             RemoteViews customRemoteView = mBuilder.mN.headsUpContentView != null
8771                     ? mBuilder.mN.headsUpContentView
8772                     : mBuilder.mN.contentView;
8773             return makeBigContentViewWithCustomContent(customRemoteView);
8774         }
8775 
8776         /**
8777          * @hide
8778          */
8779         @Override
areNotificationsVisiblyDifferent(Style other)8780         public boolean areNotificationsVisiblyDifferent(Style other) {
8781             if (other == null || getClass() != other.getClass()) {
8782                 return true;
8783             }
8784             // Comparison done for all custom RemoteViews, independent of style
8785             return false;
8786         }
8787 
buildIntoRemoteView(RemoteViews remoteViews, int id, RemoteViews customContent)8788         private RemoteViews buildIntoRemoteView(RemoteViews remoteViews, int id,
8789                 RemoteViews customContent) {
8790             if (customContent != null) {
8791                 // Need to clone customContent before adding, because otherwise it can no longer be
8792                 // parceled independently of remoteViews.
8793                 customContent = customContent.clone();
8794                 customContent.overrideTextColors(mBuilder.getPrimaryTextColor(mBuilder.mParams));
8795                 remoteViews.removeAllViews(id);
8796                 remoteViews.addView(id, customContent);
8797                 remoteViews.addFlags(RemoteViews.FLAG_REAPPLY_DISALLOWED);
8798             }
8799             return remoteViews;
8800         }
8801     }
8802 
8803     /**
8804      * Encapsulates the information needed to display a notification as a bubble.
8805      *
8806      * <p>A bubble is used to display app content in a floating window over the existing
8807      * foreground activity. A bubble has a collapsed state represented by an icon and an
8808      * expanded state that displays an activity. These may be defined via
8809      * {@link Builder#Builder(PendingIntent, Icon)} or they may
8810      * be defined via an existing shortcut using {@link Builder#Builder(String)}.
8811      * </p>
8812      *
8813      * <b>Notifications with a valid and allowed bubble will display in collapsed state
8814      * outside of the notification shade on unlocked devices. When a user interacts with the
8815      * collapsed bubble, the bubble activity will be invoked and displayed.</b>
8816      *
8817      * @see Notification.Builder#setBubbleMetadata(BubbleMetadata)
8818      */
8819     public static final class BubbleMetadata implements Parcelable {
8820 
8821         private PendingIntent mPendingIntent;
8822         private PendingIntent mDeleteIntent;
8823         private Icon mIcon;
8824         private int mDesiredHeight;
8825         @DimenRes private int mDesiredHeightResId;
8826         private int mFlags;
8827         private String mShortcutId;
8828 
8829         /**
8830          * If set and the app creating the bubble is in the foreground, the bubble will be posted
8831          * in its expanded state.
8832          *
8833          * <p>This flag has no effect if the app posting the bubble is not in the foreground.
8834          * The app is considered foreground if it is visible and on the screen, note that
8835          * a foreground service does not qualify.
8836          * </p>
8837          *
8838          * <p>Generally this flag should only be set if the user has performed an action to request
8839          * or create a bubble.</p>
8840          *
8841          * @hide
8842          */
8843         public static final int FLAG_AUTO_EXPAND_BUBBLE = 0x00000001;
8844 
8845         /**
8846          * Indicates whether the notification associated with the bubble is being visually
8847          * suppressed from the notification shade. When <code>true</code> the notification is
8848          * hidden, when <code>false</code> the notification shows as normal.
8849          *
8850          * <p>Apps sending bubbles may set this flag so that the bubble is posted <b>without</b>
8851          * the associated notification in the notification shade.</p>
8852          *
8853          * <p>Apps sending bubbles can only apply this flag when the app is in the foreground,
8854          * otherwise the flag is not respected. The app is considered foreground if it is visible
8855          * and on the screen, note that a foreground service does not qualify.</p>
8856          *
8857          * <p>Generally this flag should only be set by the app if the user has performed an
8858          * action to request or create a bubble, or if the user has seen the content in the
8859          * notification and the notification is no longer relevant. </p>
8860          *
8861          * <p>The system will also update this flag with <code>true</code> to hide the notification
8862          * from the user once the bubble has been expanded. </p>
8863          *
8864          * @hide
8865          */
8866         public static final int FLAG_SUPPRESS_NOTIFICATION = 0x00000002;
8867 
BubbleMetadata(PendingIntent expandIntent, PendingIntent deleteIntent, Icon icon, int height, @DimenRes int heightResId, String shortcutId)8868         private BubbleMetadata(PendingIntent expandIntent, PendingIntent deleteIntent,
8869                 Icon icon, int height, @DimenRes int heightResId, String shortcutId) {
8870             mPendingIntent = expandIntent;
8871             mIcon = icon;
8872             mDesiredHeight = height;
8873             mDesiredHeightResId = heightResId;
8874             mDeleteIntent = deleteIntent;
8875             mShortcutId = shortcutId;
8876         }
8877 
BubbleMetadata(Parcel in)8878         private BubbleMetadata(Parcel in) {
8879             if (in.readInt() != 0) {
8880                 mPendingIntent = PendingIntent.CREATOR.createFromParcel(in);
8881             }
8882             if (in.readInt() != 0) {
8883                 mIcon = Icon.CREATOR.createFromParcel(in);
8884             }
8885             mDesiredHeight = in.readInt();
8886             mFlags = in.readInt();
8887             if (in.readInt() != 0) {
8888                 mDeleteIntent = PendingIntent.CREATOR.createFromParcel(in);
8889             }
8890             mDesiredHeightResId = in.readInt();
8891             if (in.readInt() != 0) {
8892                 mShortcutId = in.readString8();
8893             }
8894         }
8895 
8896         /**
8897          * @return the shortcut id used for this bubble if created via
8898          * {@link Builder#Builder(String)} or null if created
8899          * via {@link Builder#Builder(PendingIntent, Icon)}.
8900          */
8901         @Nullable
getShortcutId()8902         public String getShortcutId() {
8903             return mShortcutId;
8904         }
8905 
8906         /**
8907          * @return the pending intent used to populate the floating window for this bubble, or
8908          * null if this bubble is created via {@link Builder#Builder(String)}.
8909          */
8910         @SuppressLint("InvalidNullConversion")
8911         @Nullable
getIntent()8912         public PendingIntent getIntent() {
8913             return mPendingIntent;
8914         }
8915 
8916         /**
8917          * @deprecated use {@link #getIntent()} instead.
8918          * @removed Removed from the R SDK but was never publicly stable.
8919          */
8920         @Nullable
8921         @Deprecated
getBubbleIntent()8922         public PendingIntent getBubbleIntent() {
8923             return mPendingIntent;
8924         }
8925 
8926         /**
8927          * @return the pending intent to send when the bubble is dismissed by a user, if one exists.
8928          */
8929         @Nullable
getDeleteIntent()8930         public PendingIntent getDeleteIntent() {
8931             return mDeleteIntent;
8932         }
8933 
8934         /**
8935          * @return the icon that will be displayed for this bubble when it is collapsed, or null
8936          * if the bubble is created via {@link Builder#Builder(String)}.
8937          */
8938         @SuppressLint("InvalidNullConversion")
8939         @Nullable
getIcon()8940         public Icon getIcon() {
8941             return mIcon;
8942         }
8943 
8944         /**
8945          * @deprecated use {@link #getIcon()} instead.
8946          * @removed Removed from the R SDK but was never publicly stable.
8947          */
8948         @Nullable
8949         @Deprecated
getBubbleIcon()8950         public Icon getBubbleIcon() {
8951             return mIcon;
8952         }
8953 
8954         /**
8955          * @return the ideal height, in DPs, for the floating window that app content defined by
8956          * {@link #getIntent()} for this bubble. A value of 0 indicates a desired height has
8957          * not been set.
8958          */
8959         @Dimension(unit = DP)
getDesiredHeight()8960         public int getDesiredHeight() {
8961             return mDesiredHeight;
8962         }
8963 
8964         /**
8965          * @return the resId of ideal height for the floating window that app content defined by
8966          * {@link #getIntent()} for this bubble. A value of 0 indicates a res value has not
8967          * been provided for the desired height.
8968          */
8969         @DimenRes
getDesiredHeightResId()8970         public int getDesiredHeightResId() {
8971             return mDesiredHeightResId;
8972         }
8973 
8974         /**
8975          * @return whether this bubble should auto expand when it is posted.
8976          *
8977          * @see BubbleMetadata.Builder#setAutoExpandBubble(boolean)
8978          */
getAutoExpandBubble()8979         public boolean getAutoExpandBubble() {
8980             return (mFlags & FLAG_AUTO_EXPAND_BUBBLE) != 0;
8981         }
8982 
8983         /**
8984          * Indicates whether the notification associated with the bubble is being visually
8985          * suppressed from the notification shade. When <code>true</code> the notification is
8986          * hidden, when <code>false</code> the notification shows as normal.
8987          *
8988          * <p>Apps sending bubbles may set this flag so that the bubble is posted <b>without</b>
8989          * the associated notification in the notification shade.</p>
8990          *
8991          * <p>Apps sending bubbles can only apply this flag when the app is in the foreground,
8992          * otherwise the flag is not respected. The app is considered foreground if it is visible
8993          * and on the screen, note that a foreground service does not qualify.</p>
8994          *
8995          * <p>Generally the app should only set this flag if the user has performed an
8996          * action to request or create a bubble, or if the user has seen the content in the
8997          * notification and the notification is no longer relevant. </p>
8998          *
8999          * <p>The system will update this flag with <code>true</code> to hide the notification
9000          * from the user once the bubble has been expanded.</p>
9001          *
9002          * @return whether this bubble should suppress the notification when it is posted.
9003          *
9004          * @see BubbleMetadata.Builder#setSuppressNotification(boolean)
9005          */
isNotificationSuppressed()9006         public boolean isNotificationSuppressed() {
9007             return (mFlags & FLAG_SUPPRESS_NOTIFICATION) != 0;
9008         }
9009 
9010         public static final @android.annotation.NonNull Parcelable.Creator<BubbleMetadata> CREATOR =
9011                 new Parcelable.Creator<BubbleMetadata>() {
9012 
9013                     @Override
9014                     public BubbleMetadata createFromParcel(Parcel source) {
9015                         return new BubbleMetadata(source);
9016                     }
9017 
9018                     @Override
9019                     public BubbleMetadata[] newArray(int size) {
9020                         return new BubbleMetadata[size];
9021                     }
9022                 };
9023 
9024         @Override
describeContents()9025         public int describeContents() {
9026             return 0;
9027         }
9028 
9029         @Override
writeToParcel(Parcel out, int flags)9030         public void writeToParcel(Parcel out, int flags) {
9031             out.writeInt(mPendingIntent != null ? 1 : 0);
9032             if (mPendingIntent != null) {
9033                 mPendingIntent.writeToParcel(out, 0);
9034             }
9035             out.writeInt(mIcon != null ? 1 : 0);
9036             if (mIcon != null) {
9037                 mIcon.writeToParcel(out, 0);
9038             }
9039             out.writeInt(mDesiredHeight);
9040             out.writeInt(mFlags);
9041             out.writeInt(mDeleteIntent != null ? 1 : 0);
9042             if (mDeleteIntent != null) {
9043                 mDeleteIntent.writeToParcel(out, 0);
9044             }
9045             out.writeInt(mDesiredHeightResId);
9046             out.writeInt(TextUtils.isEmpty(mShortcutId) ? 0 : 1);
9047             if (!TextUtils.isEmpty(mShortcutId)) {
9048                 out.writeString8(mShortcutId);
9049             }
9050         }
9051 
9052         /**
9053          * @hide
9054          */
setFlags(int flags)9055         public void setFlags(int flags) {
9056             mFlags = flags;
9057         }
9058 
9059         /**
9060          * @hide
9061          */
getFlags()9062         public int getFlags() {
9063             return mFlags;
9064         }
9065 
9066         /**
9067          * Builder to construct a {@link BubbleMetadata} object.
9068          */
9069         public static final class Builder {
9070 
9071             private PendingIntent mPendingIntent;
9072             private Icon mIcon;
9073             private int mDesiredHeight;
9074             @DimenRes private int mDesiredHeightResId;
9075             private int mFlags;
9076             private PendingIntent mDeleteIntent;
9077             private String mShortcutId;
9078 
9079             /**
9080              * @deprecated use {@link Builder#Builder(String)} for a bubble created via a
9081              * {@link ShortcutInfo} or {@link Builder#Builder(PendingIntent, Icon)} for a bubble
9082              * created via a {@link PendingIntent}.
9083              */
9084             @Deprecated
Builder()9085             public Builder() {
9086             }
9087 
9088             /**
9089              * Creates a {@link BubbleMetadata.Builder} based on a {@link ShortcutInfo}. To create
9090              * a shortcut bubble, ensure that the shortcut associated with the provided
9091              * {@param shortcutId} is published as a dynamic shortcut that was built with
9092              * {@link ShortcutInfo.Builder#setLongLived(boolean)} being true, otherwise your
9093              * notification will not be able to bubble.
9094              *
9095              * <p>The shortcut icon will be used to represent the bubble when it is collapsed.</p>
9096              *
9097              * <p>The shortcut activity will be used when the bubble is expanded. This will display
9098              * the shortcut activity in a floating window over the existing foreground activity.</p>
9099              *
9100              * <p>If the shortcut has not been published when the bubble notification is sent,
9101              * no bubble will be produced. If the shortcut is deleted while the bubble is active,
9102              * the bubble will be removed.</p>
9103              *
9104              * @throws NullPointerException if shortcutId is null.
9105              *
9106              * @see ShortcutInfo
9107              * @see ShortcutInfo.Builder#setLongLived(boolean)
9108              * @see android.content.pm.ShortcutManager#addDynamicShortcuts(List)
9109              */
Builder(@onNull String shortcutId)9110             public Builder(@NonNull String shortcutId) {
9111                 if (TextUtils.isEmpty(shortcutId)) {
9112                     throw new NullPointerException("Bubble requires a non-null shortcut id");
9113                 }
9114                 mShortcutId = shortcutId;
9115             }
9116 
9117             /**
9118              * Creates a {@link BubbleMetadata.Builder} based on the provided intent and icon.
9119              *
9120              * <p>The icon will be used to represent the bubble when it is collapsed. An icon
9121              * should be representative of the content within the bubble. If your app produces
9122              * multiple bubbles, the icon should be unique for each of them.</p>
9123              *
9124              * <p>The intent that will be used when the bubble is expanded. This will display the
9125              * app content in a floating window over the existing foreground activity. The intent
9126              * should point to a resizable activity. </p>
9127              *
9128              * @throws NullPointerException if intent is null.
9129              * @throws NullPointerException if icon is null.
9130              */
Builder(@onNull PendingIntent intent, @NonNull Icon icon)9131             public Builder(@NonNull PendingIntent intent, @NonNull Icon icon) {
9132                 if (intent == null) {
9133                     throw new NullPointerException("Bubble requires non-null pending intent");
9134                 }
9135                 if (icon == null) {
9136                     throw new NullPointerException("Bubbles require non-null icon");
9137                 }
9138                 if (icon.getType() != TYPE_URI_ADAPTIVE_BITMAP
9139                         && icon.getType() != TYPE_URI) {
9140                     Log.w(TAG, "Bubbles work best with icons of TYPE_URI or "
9141                             + "TYPE_URI_ADAPTIVE_BITMAP. "
9142                             + "In the future, using an icon of this type will be required.");
9143                 }
9144                 mPendingIntent = intent;
9145                 mIcon = icon;
9146             }
9147 
9148             /**
9149              * @deprecated use {@link Builder#Builder(String)} instead.
9150              * @removed Removed from the R SDK but was never publicly stable.
9151              */
9152             @NonNull
9153             @Deprecated
createShortcutBubble(@onNull String shortcutId)9154             public BubbleMetadata.Builder createShortcutBubble(@NonNull String shortcutId) {
9155                 if (!TextUtils.isEmpty(shortcutId)) {
9156                     // If shortcut id is set, we don't use these if they were previously set.
9157                     mPendingIntent = null;
9158                     mIcon = null;
9159                 }
9160                 mShortcutId = shortcutId;
9161                 return this;
9162             }
9163 
9164             /**
9165              * @deprecated use {@link Builder#Builder(PendingIntent, Icon)} instead.
9166              * @removed Removed from the R SDK but was never publicly stable.
9167              */
9168             @NonNull
9169             @Deprecated
createIntentBubble(@onNull PendingIntent intent, @NonNull Icon icon)9170             public BubbleMetadata.Builder createIntentBubble(@NonNull PendingIntent intent,
9171                     @NonNull Icon icon) {
9172                 if (intent == null) {
9173                     throw new IllegalArgumentException("Bubble requires non-null pending intent");
9174                 }
9175                 if (icon == null) {
9176                     throw new IllegalArgumentException("Bubbles require non-null icon");
9177                 }
9178                 if (icon.getType() != TYPE_URI_ADAPTIVE_BITMAP
9179                         && icon.getType() != TYPE_URI) {
9180                     Log.w(TAG, "Bubbles work best with icons of TYPE_URI or "
9181                             + "TYPE_URI_ADAPTIVE_BITMAP. "
9182                             + "In the future, using an icon of this type will be required.");
9183                 }
9184                 mShortcutId = null;
9185                 mPendingIntent = intent;
9186                 mIcon = icon;
9187                 return this;
9188             }
9189 
9190             /**
9191              * Sets the intent for the bubble.
9192              *
9193              * <p>The intent that will be used when the bubble is expanded. This will display the
9194              * app content in a floating window over the existing foreground activity. The intent
9195              * should point to a resizable activity. </p>
9196              *
9197              * @throws NullPointerException  if intent is null.
9198              * @throws IllegalStateException if this builder was created via
9199              *                               {@link Builder#Builder(String)}.
9200              */
9201             @NonNull
setIntent(@onNull PendingIntent intent)9202             public BubbleMetadata.Builder setIntent(@NonNull PendingIntent intent) {
9203                 if (mShortcutId != null) {
9204                     throw new IllegalStateException("Created as a shortcut bubble, cannot set a "
9205                             + "PendingIntent. Consider using "
9206                             + "BubbleMetadata.Builder(PendingIntent,Icon) instead.");
9207                 }
9208                 if (intent == null) {
9209                     throw new NullPointerException("Bubble requires non-null pending intent");
9210                 }
9211                 mPendingIntent = intent;
9212                 return this;
9213             }
9214 
9215             /**
9216              * Sets the icon for the bubble. Can only be used if the bubble was created
9217              * via {@link Builder#Builder(PendingIntent, Icon)}.
9218              *
9219              * <p>The icon will be used to represent the bubble when it is collapsed. An icon
9220              * should be representative of the content within the bubble. If your app produces
9221              * multiple bubbles, the icon should be unique for each of them.</p>
9222              *
9223              * <p>It is recommended to use an {@link Icon} of type {@link Icon#TYPE_URI}
9224              * or {@link Icon#TYPE_URI_ADAPTIVE_BITMAP}</p>
9225              *
9226              * @throws NullPointerException  if icon is null.
9227              * @throws IllegalStateException if this builder was created via
9228              *                               {@link Builder#Builder(String)}.
9229              */
9230             @NonNull
setIcon(@onNull Icon icon)9231             public BubbleMetadata.Builder setIcon(@NonNull Icon icon) {
9232                 if (mShortcutId != null) {
9233                     throw new IllegalStateException("Created as a shortcut bubble, cannot set an "
9234                             + "Icon. Consider using "
9235                             + "BubbleMetadata.Builder(PendingIntent,Icon) instead.");
9236                 }
9237                 if (icon == null) {
9238                     throw new NullPointerException("Bubbles require non-null icon");
9239                 }
9240                 if (icon.getType() != TYPE_URI_ADAPTIVE_BITMAP
9241                         && icon.getType() != TYPE_URI) {
9242                     Log.w(TAG, "Bubbles work best with icons of TYPE_URI or "
9243                             + "TYPE_URI_ADAPTIVE_BITMAP. "
9244                             + "In the future, using an icon of this type will be required.");
9245                 }
9246                 mIcon = icon;
9247                 return this;
9248             }
9249 
9250             /**
9251              * Sets the desired height in DPs for the expanded content of the bubble.
9252              *
9253              * <p>This height may not be respected if there is not enough space on the screen or if
9254              * the provided height is too small to be useful.</p>
9255              *
9256              * <p>If {@link #setDesiredHeightResId(int)} was previously called on this builder, the
9257              * previous value set will be cleared after calling this method, and this value will
9258              * be used instead.</p>
9259              *
9260              * <p>A desired height (in DPs or via resID) is optional.</p>
9261              *
9262              * @see #setDesiredHeightResId(int)
9263              */
9264             @NonNull
setDesiredHeight(@imensionunit = DP) int height)9265             public BubbleMetadata.Builder setDesiredHeight(@Dimension(unit = DP) int height) {
9266                 mDesiredHeight = Math.max(height, 0);
9267                 mDesiredHeightResId = 0;
9268                 return this;
9269             }
9270 
9271 
9272             /**
9273              * Sets the desired height via resId for the expanded content of the bubble.
9274              *
9275              * <p>This height may not be respected if there is not enough space on the screen or if
9276              * the provided height is too small to be useful.</p>
9277              *
9278              * <p>If {@link #setDesiredHeight(int)} was previously called on this builder, the
9279              * previous value set will be cleared after calling this method, and this value will
9280              * be used instead.</p>
9281              *
9282              * <p>A desired height (in DPs or via resID) is optional.</p>
9283              *
9284              * @see #setDesiredHeight(int)
9285              */
9286             @NonNull
setDesiredHeightResId(@imenRes int heightResId)9287             public BubbleMetadata.Builder setDesiredHeightResId(@DimenRes int heightResId) {
9288                 mDesiredHeightResId = heightResId;
9289                 mDesiredHeight = 0;
9290                 return this;
9291             }
9292 
9293             /**
9294              * Sets whether the bubble will be posted in its expanded state.
9295              *
9296              * <p>This flag has no effect if the app posting the bubble is not in the foreground.
9297              * The app is considered foreground if it is visible and on the screen, note that
9298              * a foreground service does not qualify.
9299              * </p>
9300              *
9301              * <p>Generally, this flag should only be set if the user has performed an action to
9302              * request or create a bubble.</p>
9303              *
9304              * <p>Setting this flag is optional; it defaults to false.</p>
9305              */
9306             @NonNull
setAutoExpandBubble(boolean shouldExpand)9307             public BubbleMetadata.Builder setAutoExpandBubble(boolean shouldExpand) {
9308                 setFlag(FLAG_AUTO_EXPAND_BUBBLE, shouldExpand);
9309                 return this;
9310             }
9311 
9312             /**
9313              * Sets whether the bubble will be posted <b>without</b> the associated notification in
9314              * the notification shade.
9315              *
9316              * <p>This flag has no effect if the app posting the bubble is not in the foreground.
9317              * The app is considered foreground if it is visible and on the screen, note that
9318              * a foreground service does not qualify.
9319              * </p>
9320              *
9321              * <p>Generally, this flag should only be set if the user has performed an action to
9322              * request or create a bubble, or if the user has seen the content in the notification
9323              * and the notification is no longer relevant.</p>
9324              *
9325              * <p>Setting this flag is optional; it defaults to false.</p>
9326              */
9327             @NonNull
setSuppressNotification(boolean shouldSuppressNotif)9328             public BubbleMetadata.Builder setSuppressNotification(boolean shouldSuppressNotif) {
9329                 setFlag(FLAG_SUPPRESS_NOTIFICATION, shouldSuppressNotif);
9330                 return this;
9331             }
9332 
9333             /**
9334              * Sets an intent to send when this bubble is explicitly removed by the user.
9335              *
9336              * <p>Setting a delete intent is optional.</p>
9337              */
9338             @NonNull
setDeleteIntent(@ullable PendingIntent deleteIntent)9339             public BubbleMetadata.Builder setDeleteIntent(@Nullable PendingIntent deleteIntent) {
9340                 mDeleteIntent = deleteIntent;
9341                 return this;
9342             }
9343 
9344             /**
9345              * Creates the {@link BubbleMetadata} defined by this builder.
9346              *
9347              * @throws NullPointerException if required elements have not been set.
9348              */
9349             @NonNull
build()9350             public BubbleMetadata build() {
9351                 if (mShortcutId == null && mPendingIntent == null) {
9352                     throw new NullPointerException(
9353                             "Must supply pending intent or shortcut to bubble");
9354                 }
9355                 if (mShortcutId == null && mIcon == null) {
9356                     throw new NullPointerException(
9357                             "Must supply an icon or shortcut for the bubble");
9358                 }
9359                 BubbleMetadata data = new BubbleMetadata(mPendingIntent, mDeleteIntent,
9360                         mIcon, mDesiredHeight, mDesiredHeightResId, mShortcutId);
9361                 data.setFlags(mFlags);
9362                 return data;
9363             }
9364 
9365             /**
9366              * @hide
9367              */
setFlag(int mask, boolean value)9368             public BubbleMetadata.Builder setFlag(int mask, boolean value) {
9369                 if (value) {
9370                     mFlags |= mask;
9371                 } else {
9372                     mFlags &= ~mask;
9373                 }
9374                 return this;
9375             }
9376         }
9377     }
9378 
9379 
9380     // When adding a new Style subclass here, don't forget to update
9381     // Builder.getNotificationStyleClass.
9382 
9383     /**
9384      * Extender interface for use with {@link Builder#extend}. Extenders may be used to add
9385      * metadata or change options on a notification builder.
9386      */
9387     public interface Extender {
9388         /**
9389          * Apply this extender to a notification builder.
9390          * @param builder the builder to be modified.
9391          * @return the build object for chaining.
9392          */
extend(Builder builder)9393         public Builder extend(Builder builder);
9394     }
9395 
9396     /**
9397      * Helper class to add wearable extensions to notifications.
9398      * <p class="note"> See
9399      * <a href="{@docRoot}wear/notifications/creating.html">Creating Notifications
9400      * for Android Wear</a> for more information on how to use this class.
9401      * <p>
9402      * To create a notification with wearable extensions:
9403      * <ol>
9404      *   <li>Create a {@link android.app.Notification.Builder}, setting any desired
9405      *   properties.
9406      *   <li>Create a {@link android.app.Notification.WearableExtender}.
9407      *   <li>Set wearable-specific properties using the
9408      *   {@code add} and {@code set} methods of {@link android.app.Notification.WearableExtender}.
9409      *   <li>Call {@link android.app.Notification.Builder#extend} to apply the extensions to a
9410      *   notification.
9411      *   <li>Post the notification to the notification system with the
9412      *   {@code NotificationManager.notify(...)} methods.
9413      * </ol>
9414      *
9415      * <pre class="prettyprint">
9416      * Notification notif = new Notification.Builder(mContext)
9417      *         .setContentTitle(&quot;New mail from &quot; + sender.toString())
9418      *         .setContentText(subject)
9419      *         .setSmallIcon(R.drawable.new_mail)
9420      *         .extend(new Notification.WearableExtender()
9421      *                 .setContentIcon(R.drawable.new_mail))
9422      *         .build();
9423      * NotificationManager notificationManger =
9424      *         (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
9425      * notificationManger.notify(0, notif);</pre>
9426      *
9427      * <p>Wearable extensions can be accessed on an existing notification by using the
9428      * {@code WearableExtender(Notification)} constructor,
9429      * and then using the {@code get} methods to access values.
9430      *
9431      * <pre class="prettyprint">
9432      * Notification.WearableExtender wearableExtender = new Notification.WearableExtender(
9433      *         notification);
9434      * List&lt;Notification&gt; pages = wearableExtender.getPages();</pre>
9435      */
9436     public static final class WearableExtender implements Extender {
9437         /**
9438          * Sentinel value for an action index that is unset.
9439          */
9440         public static final int UNSET_ACTION_INDEX = -1;
9441 
9442         /**
9443          * Size value for use with {@link #setCustomSizePreset} to show this notification with
9444          * default sizing.
9445          * <p>For custom display notifications created using {@link #setDisplayIntent},
9446          * the default is {@link #SIZE_MEDIUM}. All other notifications size automatically based
9447          * on their content.
9448          *
9449          * @deprecated Display intents are no longer supported.
9450          */
9451         @Deprecated
9452         public static final int SIZE_DEFAULT = 0;
9453 
9454         /**
9455          * Size value for use with {@link #setCustomSizePreset} to show this notification
9456          * with an extra small size.
9457          * <p>This value is only applicable for custom display notifications created using
9458          * {@link #setDisplayIntent}.
9459          *
9460          * @deprecated Display intents are no longer supported.
9461          */
9462         @Deprecated
9463         public static final int SIZE_XSMALL = 1;
9464 
9465         /**
9466          * Size value for use with {@link #setCustomSizePreset} to show this notification
9467          * with a small size.
9468          * <p>This value is only applicable for custom display notifications created using
9469          * {@link #setDisplayIntent}.
9470          *
9471          * @deprecated Display intents are no longer supported.
9472          */
9473         @Deprecated
9474         public static final int SIZE_SMALL = 2;
9475 
9476         /**
9477          * Size value for use with {@link #setCustomSizePreset} to show this notification
9478          * with a medium size.
9479          * <p>This value is only applicable for custom display notifications created using
9480          * {@link #setDisplayIntent}.
9481          *
9482          * @deprecated Display intents are no longer supported.
9483          */
9484         @Deprecated
9485         public static final int SIZE_MEDIUM = 3;
9486 
9487         /**
9488          * Size value for use with {@link #setCustomSizePreset} to show this notification
9489          * with a large size.
9490          * <p>This value is only applicable for custom display notifications created using
9491          * {@link #setDisplayIntent}.
9492          *
9493          * @deprecated Display intents are no longer supported.
9494          */
9495         @Deprecated
9496         public static final int SIZE_LARGE = 4;
9497 
9498         /**
9499          * Size value for use with {@link #setCustomSizePreset} to show this notification
9500          * full screen.
9501          * <p>This value is only applicable for custom display notifications created using
9502          * {@link #setDisplayIntent}.
9503          *
9504          * @deprecated Display intents are no longer supported.
9505          */
9506         @Deprecated
9507         public static final int SIZE_FULL_SCREEN = 5;
9508 
9509         /**
9510          * Sentinel value for use with {@link #setHintScreenTimeout} to keep the screen on for a
9511          * short amount of time when this notification is displayed on the screen. This
9512          * is the default value.
9513          *
9514          * @deprecated This feature is no longer supported.
9515          */
9516         @Deprecated
9517         public static final int SCREEN_TIMEOUT_SHORT = 0;
9518 
9519         /**
9520          * Sentinel value for use with {@link #setHintScreenTimeout} to keep the screen on
9521          * for a longer amount of time when this notification is displayed on the screen.
9522          *
9523          * @deprecated This feature is no longer supported.
9524          */
9525         @Deprecated
9526         public static final int SCREEN_TIMEOUT_LONG = -1;
9527 
9528         /** Notification extra which contains wearable extensions */
9529         private static final String EXTRA_WEARABLE_EXTENSIONS = "android.wearable.EXTENSIONS";
9530 
9531         // Keys within EXTRA_WEARABLE_EXTENSIONS for wearable options.
9532         private static final String KEY_ACTIONS = "actions";
9533         private static final String KEY_FLAGS = "flags";
9534         private static final String KEY_DISPLAY_INTENT = "displayIntent";
9535         private static final String KEY_PAGES = "pages";
9536         private static final String KEY_BACKGROUND = "background";
9537         private static final String KEY_CONTENT_ICON = "contentIcon";
9538         private static final String KEY_CONTENT_ICON_GRAVITY = "contentIconGravity";
9539         private static final String KEY_CONTENT_ACTION_INDEX = "contentActionIndex";
9540         private static final String KEY_CUSTOM_SIZE_PRESET = "customSizePreset";
9541         private static final String KEY_CUSTOM_CONTENT_HEIGHT = "customContentHeight";
9542         private static final String KEY_GRAVITY = "gravity";
9543         private static final String KEY_HINT_SCREEN_TIMEOUT = "hintScreenTimeout";
9544         private static final String KEY_DISMISSAL_ID = "dismissalId";
9545         private static final String KEY_BRIDGE_TAG = "bridgeTag";
9546 
9547         // Flags bitwise-ored to mFlags
9548         private static final int FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE = 0x1;
9549         private static final int FLAG_HINT_HIDE_ICON = 1 << 1;
9550         private static final int FLAG_HINT_SHOW_BACKGROUND_ONLY = 1 << 2;
9551         private static final int FLAG_START_SCROLL_BOTTOM = 1 << 3;
9552         private static final int FLAG_HINT_AVOID_BACKGROUND_CLIPPING = 1 << 4;
9553         private static final int FLAG_BIG_PICTURE_AMBIENT = 1 << 5;
9554         private static final int FLAG_HINT_CONTENT_INTENT_LAUNCHES_ACTIVITY = 1 << 6;
9555 
9556         // Default value for flags integer
9557         private static final int DEFAULT_FLAGS = FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE;
9558 
9559         private static final int DEFAULT_CONTENT_ICON_GRAVITY = Gravity.END;
9560         private static final int DEFAULT_GRAVITY = Gravity.BOTTOM;
9561 
9562         private ArrayList<Action> mActions = new ArrayList<Action>();
9563         private int mFlags = DEFAULT_FLAGS;
9564         private PendingIntent mDisplayIntent;
9565         private ArrayList<Notification> mPages = new ArrayList<Notification>();
9566         private Bitmap mBackground;
9567         private int mContentIcon;
9568         private int mContentIconGravity = DEFAULT_CONTENT_ICON_GRAVITY;
9569         private int mContentActionIndex = UNSET_ACTION_INDEX;
9570         private int mCustomSizePreset = SIZE_DEFAULT;
9571         private int mCustomContentHeight;
9572         private int mGravity = DEFAULT_GRAVITY;
9573         private int mHintScreenTimeout;
9574         private String mDismissalId;
9575         private String mBridgeTag;
9576 
9577         /**
9578          * Create a {@link android.app.Notification.WearableExtender} with default
9579          * options.
9580          */
WearableExtender()9581         public WearableExtender() {
9582         }
9583 
WearableExtender(Notification notif)9584         public WearableExtender(Notification notif) {
9585             Bundle wearableBundle = notif.extras.getBundle(EXTRA_WEARABLE_EXTENSIONS);
9586             if (wearableBundle != null) {
9587                 List<Action> actions = wearableBundle.getParcelableArrayList(KEY_ACTIONS);
9588                 if (actions != null) {
9589                     mActions.addAll(actions);
9590                 }
9591 
9592                 mFlags = wearableBundle.getInt(KEY_FLAGS, DEFAULT_FLAGS);
9593                 mDisplayIntent = wearableBundle.getParcelable(KEY_DISPLAY_INTENT);
9594 
9595                 Notification[] pages = getParcelableArrayFromBundle(
9596                         wearableBundle, KEY_PAGES, Notification.class);
9597                 if (pages != null) {
9598                     Collections.addAll(mPages, pages);
9599                 }
9600 
9601                 mBackground = wearableBundle.getParcelable(KEY_BACKGROUND);
9602                 mContentIcon = wearableBundle.getInt(KEY_CONTENT_ICON);
9603                 mContentIconGravity = wearableBundle.getInt(KEY_CONTENT_ICON_GRAVITY,
9604                         DEFAULT_CONTENT_ICON_GRAVITY);
9605                 mContentActionIndex = wearableBundle.getInt(KEY_CONTENT_ACTION_INDEX,
9606                         UNSET_ACTION_INDEX);
9607                 mCustomSizePreset = wearableBundle.getInt(KEY_CUSTOM_SIZE_PRESET,
9608                         SIZE_DEFAULT);
9609                 mCustomContentHeight = wearableBundle.getInt(KEY_CUSTOM_CONTENT_HEIGHT);
9610                 mGravity = wearableBundle.getInt(KEY_GRAVITY, DEFAULT_GRAVITY);
9611                 mHintScreenTimeout = wearableBundle.getInt(KEY_HINT_SCREEN_TIMEOUT);
9612                 mDismissalId = wearableBundle.getString(KEY_DISMISSAL_ID);
9613                 mBridgeTag = wearableBundle.getString(KEY_BRIDGE_TAG);
9614             }
9615         }
9616 
9617         /**
9618          * Apply wearable extensions to a notification that is being built. This is typically
9619          * called by the {@link android.app.Notification.Builder#extend} method of
9620          * {@link android.app.Notification.Builder}.
9621          */
9622         @Override
extend(Notification.Builder builder)9623         public Notification.Builder extend(Notification.Builder builder) {
9624             Bundle wearableBundle = new Bundle();
9625 
9626             if (!mActions.isEmpty()) {
9627                 wearableBundle.putParcelableArrayList(KEY_ACTIONS, mActions);
9628             }
9629             if (mFlags != DEFAULT_FLAGS) {
9630                 wearableBundle.putInt(KEY_FLAGS, mFlags);
9631             }
9632             if (mDisplayIntent != null) {
9633                 wearableBundle.putParcelable(KEY_DISPLAY_INTENT, mDisplayIntent);
9634             }
9635             if (!mPages.isEmpty()) {
9636                 wearableBundle.putParcelableArray(KEY_PAGES, mPages.toArray(
9637                         new Notification[mPages.size()]));
9638             }
9639             if (mBackground != null) {
9640                 wearableBundle.putParcelable(KEY_BACKGROUND, mBackground);
9641             }
9642             if (mContentIcon != 0) {
9643                 wearableBundle.putInt(KEY_CONTENT_ICON, mContentIcon);
9644             }
9645             if (mContentIconGravity != DEFAULT_CONTENT_ICON_GRAVITY) {
9646                 wearableBundle.putInt(KEY_CONTENT_ICON_GRAVITY, mContentIconGravity);
9647             }
9648             if (mContentActionIndex != UNSET_ACTION_INDEX) {
9649                 wearableBundle.putInt(KEY_CONTENT_ACTION_INDEX,
9650                         mContentActionIndex);
9651             }
9652             if (mCustomSizePreset != SIZE_DEFAULT) {
9653                 wearableBundle.putInt(KEY_CUSTOM_SIZE_PRESET, mCustomSizePreset);
9654             }
9655             if (mCustomContentHeight != 0) {
9656                 wearableBundle.putInt(KEY_CUSTOM_CONTENT_HEIGHT, mCustomContentHeight);
9657             }
9658             if (mGravity != DEFAULT_GRAVITY) {
9659                 wearableBundle.putInt(KEY_GRAVITY, mGravity);
9660             }
9661             if (mHintScreenTimeout != 0) {
9662                 wearableBundle.putInt(KEY_HINT_SCREEN_TIMEOUT, mHintScreenTimeout);
9663             }
9664             if (mDismissalId != null) {
9665                 wearableBundle.putString(KEY_DISMISSAL_ID, mDismissalId);
9666             }
9667             if (mBridgeTag != null) {
9668                 wearableBundle.putString(KEY_BRIDGE_TAG, mBridgeTag);
9669             }
9670 
9671             builder.getExtras().putBundle(EXTRA_WEARABLE_EXTENSIONS, wearableBundle);
9672             return builder;
9673         }
9674 
9675         @Override
clone()9676         public WearableExtender clone() {
9677             WearableExtender that = new WearableExtender();
9678             that.mActions = new ArrayList<Action>(this.mActions);
9679             that.mFlags = this.mFlags;
9680             that.mDisplayIntent = this.mDisplayIntent;
9681             that.mPages = new ArrayList<Notification>(this.mPages);
9682             that.mBackground = this.mBackground;
9683             that.mContentIcon = this.mContentIcon;
9684             that.mContentIconGravity = this.mContentIconGravity;
9685             that.mContentActionIndex = this.mContentActionIndex;
9686             that.mCustomSizePreset = this.mCustomSizePreset;
9687             that.mCustomContentHeight = this.mCustomContentHeight;
9688             that.mGravity = this.mGravity;
9689             that.mHintScreenTimeout = this.mHintScreenTimeout;
9690             that.mDismissalId = this.mDismissalId;
9691             that.mBridgeTag = this.mBridgeTag;
9692             return that;
9693         }
9694 
9695         /**
9696          * Add a wearable action to this notification.
9697          *
9698          * <p>When wearable actions are added using this method, the set of actions that
9699          * show on a wearable device splits from devices that only show actions added
9700          * using {@link android.app.Notification.Builder#addAction}. This allows for customization
9701          * of which actions display on different devices.
9702          *
9703          * @param action the action to add to this notification
9704          * @return this object for method chaining
9705          * @see android.app.Notification.Action
9706          */
addAction(Action action)9707         public WearableExtender addAction(Action action) {
9708             mActions.add(action);
9709             return this;
9710         }
9711 
9712         /**
9713          * Adds wearable actions to this notification.
9714          *
9715          * <p>When wearable actions are added using this method, the set of actions that
9716          * show on a wearable device splits from devices that only show actions added
9717          * using {@link android.app.Notification.Builder#addAction}. This allows for customization
9718          * of which actions display on different devices.
9719          *
9720          * @param actions the actions to add to this notification
9721          * @return this object for method chaining
9722          * @see android.app.Notification.Action
9723          */
addActions(List<Action> actions)9724         public WearableExtender addActions(List<Action> actions) {
9725             mActions.addAll(actions);
9726             return this;
9727         }
9728 
9729         /**
9730          * Clear all wearable actions present on this builder.
9731          * @return this object for method chaining.
9732          * @see #addAction
9733          */
clearActions()9734         public WearableExtender clearActions() {
9735             mActions.clear();
9736             return this;
9737         }
9738 
9739         /**
9740          * Get the wearable actions present on this notification.
9741          */
getActions()9742         public List<Action> getActions() {
9743             return mActions;
9744         }
9745 
9746         /**
9747          * Set an intent to launch inside of an activity view when displaying
9748          * this notification. The {@link PendingIntent} provided should be for an activity.
9749          *
9750          * <pre class="prettyprint">
9751          * Intent displayIntent = new Intent(context, MyDisplayActivity.class);
9752          * PendingIntent displayPendingIntent = PendingIntent.getActivity(context,
9753          *         0, displayIntent, PendingIntent.FLAG_UPDATE_CURRENT);
9754          * Notification notif = new Notification.Builder(context)
9755          *         .extend(new Notification.WearableExtender()
9756          *                 .setDisplayIntent(displayPendingIntent)
9757          *                 .setCustomSizePreset(Notification.WearableExtender.SIZE_MEDIUM))
9758          *         .build();</pre>
9759          *
9760          * <p>The activity to launch needs to allow embedding, must be exported, and
9761          * should have an empty task affinity. It is also recommended to use the device
9762          * default light theme.
9763          *
9764          * <p>Example AndroidManifest.xml entry:
9765          * <pre class="prettyprint">
9766          * &lt;activity android:name=&quot;com.example.MyDisplayActivity&quot;
9767          *     android:exported=&quot;true&quot;
9768          *     android:allowEmbedded=&quot;true&quot;
9769          *     android:taskAffinity=&quot;&quot;
9770          *     android:theme=&quot;@android:style/Theme.DeviceDefault.Light&quot; /&gt;</pre>
9771          *
9772          * @param intent the {@link PendingIntent} for an activity
9773          * @return this object for method chaining
9774          * @see android.app.Notification.WearableExtender#getDisplayIntent
9775          * @deprecated Display intents are no longer supported.
9776          */
9777         @Deprecated
setDisplayIntent(PendingIntent intent)9778         public WearableExtender setDisplayIntent(PendingIntent intent) {
9779             mDisplayIntent = intent;
9780             return this;
9781         }
9782 
9783         /**
9784          * Get the intent to launch inside of an activity view when displaying this
9785          * notification. This {@code PendingIntent} should be for an activity.
9786          *
9787          * @deprecated Display intents are no longer supported.
9788          */
9789         @Deprecated
getDisplayIntent()9790         public PendingIntent getDisplayIntent() {
9791             return mDisplayIntent;
9792         }
9793 
9794         /**
9795          * Add an additional page of content to display with this notification. The current
9796          * notification forms the first page, and pages added using this function form
9797          * subsequent pages. This field can be used to separate a notification into multiple
9798          * sections.
9799          *
9800          * @param page the notification to add as another page
9801          * @return this object for method chaining
9802          * @see android.app.Notification.WearableExtender#getPages
9803          * @deprecated Multiple content pages are no longer supported.
9804          */
9805         @Deprecated
addPage(Notification page)9806         public WearableExtender addPage(Notification page) {
9807             mPages.add(page);
9808             return this;
9809         }
9810 
9811         /**
9812          * Add additional pages of content to display with this notification. The current
9813          * notification forms the first page, and pages added using this function form
9814          * subsequent pages. This field can be used to separate a notification into multiple
9815          * sections.
9816          *
9817          * @param pages a list of notifications
9818          * @return this object for method chaining
9819          * @see android.app.Notification.WearableExtender#getPages
9820          * @deprecated Multiple content pages are no longer supported.
9821          */
9822         @Deprecated
addPages(List<Notification> pages)9823         public WearableExtender addPages(List<Notification> pages) {
9824             mPages.addAll(pages);
9825             return this;
9826         }
9827 
9828         /**
9829          * Clear all additional pages present on this builder.
9830          * @return this object for method chaining.
9831          * @see #addPage
9832          * @deprecated Multiple content pages are no longer supported.
9833          */
9834         @Deprecated
clearPages()9835         public WearableExtender clearPages() {
9836             mPages.clear();
9837             return this;
9838         }
9839 
9840         /**
9841          * Get the array of additional pages of content for displaying this notification. The
9842          * current notification forms the first page, and elements within this array form
9843          * subsequent pages. This field can be used to separate a notification into multiple
9844          * sections.
9845          * @return the pages for this notification
9846          * @deprecated Multiple content pages are no longer supported.
9847          */
9848         @Deprecated
getPages()9849         public List<Notification> getPages() {
9850             return mPages;
9851         }
9852 
9853         /**
9854          * Set a background image to be displayed behind the notification content.
9855          * Contrary to the {@link android.app.Notification.BigPictureStyle}, this background
9856          * will work with any notification style.
9857          *
9858          * @param background the background bitmap
9859          * @return this object for method chaining
9860          * @see android.app.Notification.WearableExtender#getBackground
9861          * @deprecated Background images are no longer supported.
9862          */
9863         @Deprecated
setBackground(Bitmap background)9864         public WearableExtender setBackground(Bitmap background) {
9865             mBackground = background;
9866             return this;
9867         }
9868 
9869         /**
9870          * Get a background image to be displayed behind the notification content.
9871          * Contrary to the {@link android.app.Notification.BigPictureStyle}, this background
9872          * will work with any notification style.
9873          *
9874          * @return the background image
9875          * @see android.app.Notification.WearableExtender#setBackground
9876          * @deprecated Background images are no longer supported.
9877          */
9878         @Deprecated
getBackground()9879         public Bitmap getBackground() {
9880             return mBackground;
9881         }
9882 
9883         /**
9884          * Set an icon that goes with the content of this notification.
9885          */
9886         @Deprecated
setContentIcon(int icon)9887         public WearableExtender setContentIcon(int icon) {
9888             mContentIcon = icon;
9889             return this;
9890         }
9891 
9892         /**
9893          * Get an icon that goes with the content of this notification.
9894          */
9895         @Deprecated
getContentIcon()9896         public int getContentIcon() {
9897             return mContentIcon;
9898         }
9899 
9900         /**
9901          * Set the gravity that the content icon should have within the notification display.
9902          * Supported values include {@link android.view.Gravity#START} and
9903          * {@link android.view.Gravity#END}. The default value is {@link android.view.Gravity#END}.
9904          * @see #setContentIcon
9905          */
9906         @Deprecated
setContentIconGravity(int contentIconGravity)9907         public WearableExtender setContentIconGravity(int contentIconGravity) {
9908             mContentIconGravity = contentIconGravity;
9909             return this;
9910         }
9911 
9912         /**
9913          * Get the gravity that the content icon should have within the notification display.
9914          * Supported values include {@link android.view.Gravity#START} and
9915          * {@link android.view.Gravity#END}. The default value is {@link android.view.Gravity#END}.
9916          * @see #getContentIcon
9917          */
9918         @Deprecated
getContentIconGravity()9919         public int getContentIconGravity() {
9920             return mContentIconGravity;
9921         }
9922 
9923         /**
9924          * Set an action from this notification's actions as the primary action. If the action has a
9925          * {@link RemoteInput} associated with it, shortcuts to the options for that input are shown
9926          * directly on the notification.
9927          *
9928          * @param actionIndex The index of the primary action.
9929          *                    If wearable actions were added to the main notification, this index
9930          *                    will apply to that list, otherwise it will apply to the regular
9931          *                    actions list.
9932          */
setContentAction(int actionIndex)9933         public WearableExtender setContentAction(int actionIndex) {
9934             mContentActionIndex = actionIndex;
9935             return this;
9936         }
9937 
9938         /**
9939          * Get the index of the notification action, if any, that was specified as the primary
9940          * action.
9941          *
9942          * <p>If wearable specific actions were added to the main notification, this index will
9943          * apply to that list, otherwise it will apply to the regular actions list.
9944          *
9945          * @return the action index or {@link #UNSET_ACTION_INDEX} if no action was selected.
9946          */
getContentAction()9947         public int getContentAction() {
9948             return mContentActionIndex;
9949         }
9950 
9951         /**
9952          * Set the gravity that this notification should have within the available viewport space.
9953          * Supported values include {@link android.view.Gravity#TOP},
9954          * {@link android.view.Gravity#CENTER_VERTICAL} and {@link android.view.Gravity#BOTTOM}.
9955          * The default value is {@link android.view.Gravity#BOTTOM}.
9956          */
9957         @Deprecated
setGravity(int gravity)9958         public WearableExtender setGravity(int gravity) {
9959             mGravity = gravity;
9960             return this;
9961         }
9962 
9963         /**
9964          * Get the gravity that this notification should have within the available viewport space.
9965          * Supported values include {@link android.view.Gravity#TOP},
9966          * {@link android.view.Gravity#CENTER_VERTICAL} and {@link android.view.Gravity#BOTTOM}.
9967          * The default value is {@link android.view.Gravity#BOTTOM}.
9968          */
9969         @Deprecated
getGravity()9970         public int getGravity() {
9971             return mGravity;
9972         }
9973 
9974         /**
9975          * Set the custom size preset for the display of this notification out of the available
9976          * presets found in {@link android.app.Notification.WearableExtender}, e.g.
9977          * {@link #SIZE_LARGE}.
9978          * <p>Some custom size presets are only applicable for custom display notifications created
9979          * using {@link android.app.Notification.WearableExtender#setDisplayIntent}. Check the
9980          * documentation for the preset in question. See also
9981          * {@link #setCustomContentHeight} and {@link #getCustomSizePreset}.
9982          */
9983         @Deprecated
setCustomSizePreset(int sizePreset)9984         public WearableExtender setCustomSizePreset(int sizePreset) {
9985             mCustomSizePreset = sizePreset;
9986             return this;
9987         }
9988 
9989         /**
9990          * Get the custom size preset for the display of this notification out of the available
9991          * presets found in {@link android.app.Notification.WearableExtender}, e.g.
9992          * {@link #SIZE_LARGE}.
9993          * <p>Some custom size presets are only applicable for custom display notifications created
9994          * using {@link #setDisplayIntent}. Check the documentation for the preset in question.
9995          * See also {@link #setCustomContentHeight} and {@link #setCustomSizePreset}.
9996          */
9997         @Deprecated
getCustomSizePreset()9998         public int getCustomSizePreset() {
9999             return mCustomSizePreset;
10000         }
10001 
10002         /**
10003          * Set the custom height in pixels for the display of this notification's content.
10004          * <p>This option is only available for custom display notifications created
10005          * using {@link android.app.Notification.WearableExtender#setDisplayIntent}. See also
10006          * {@link android.app.Notification.WearableExtender#setCustomSizePreset} and
10007          * {@link #getCustomContentHeight}.
10008          */
10009         @Deprecated
setCustomContentHeight(int height)10010         public WearableExtender setCustomContentHeight(int height) {
10011             mCustomContentHeight = height;
10012             return this;
10013         }
10014 
10015         /**
10016          * Get the custom height in pixels for the display of this notification's content.
10017          * <p>This option is only available for custom display notifications created
10018          * using {@link #setDisplayIntent}. See also {@link #setCustomSizePreset} and
10019          * {@link #setCustomContentHeight}.
10020          */
10021         @Deprecated
getCustomContentHeight()10022         public int getCustomContentHeight() {
10023             return mCustomContentHeight;
10024         }
10025 
10026         /**
10027          * Set whether the scrolling position for the contents of this notification should start
10028          * at the bottom of the contents instead of the top when the contents are too long to
10029          * display within the screen.  Default is false (start scroll at the top).
10030          */
setStartScrollBottom(boolean startScrollBottom)10031         public WearableExtender setStartScrollBottom(boolean startScrollBottom) {
10032             setFlag(FLAG_START_SCROLL_BOTTOM, startScrollBottom);
10033             return this;
10034         }
10035 
10036         /**
10037          * Get whether the scrolling position for the contents of this notification should start
10038          * at the bottom of the contents instead of the top when the contents are too long to
10039          * display within the screen. Default is false (start scroll at the top).
10040          */
getStartScrollBottom()10041         public boolean getStartScrollBottom() {
10042             return (mFlags & FLAG_START_SCROLL_BOTTOM) != 0;
10043         }
10044 
10045         /**
10046          * Set whether the content intent is available when the wearable device is not connected
10047          * to a companion device.  The user can still trigger this intent when the wearable device
10048          * is offline, but a visual hint will indicate that the content intent may not be available.
10049          * Defaults to true.
10050          */
setContentIntentAvailableOffline( boolean contentIntentAvailableOffline)10051         public WearableExtender setContentIntentAvailableOffline(
10052                 boolean contentIntentAvailableOffline) {
10053             setFlag(FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE, contentIntentAvailableOffline);
10054             return this;
10055         }
10056 
10057         /**
10058          * Get whether the content intent is available when the wearable device is not connected
10059          * to a companion device.  The user can still trigger this intent when the wearable device
10060          * is offline, but a visual hint will indicate that the content intent may not be available.
10061          * Defaults to true.
10062          */
getContentIntentAvailableOffline()10063         public boolean getContentIntentAvailableOffline() {
10064             return (mFlags & FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE) != 0;
10065         }
10066 
10067         /**
10068          * Set a hint that this notification's icon should not be displayed.
10069          * @param hintHideIcon {@code true} to hide the icon, {@code false} otherwise.
10070          * @return this object for method chaining
10071          */
10072         @Deprecated
setHintHideIcon(boolean hintHideIcon)10073         public WearableExtender setHintHideIcon(boolean hintHideIcon) {
10074             setFlag(FLAG_HINT_HIDE_ICON, hintHideIcon);
10075             return this;
10076         }
10077 
10078         /**
10079          * Get a hint that this notification's icon should not be displayed.
10080          * @return {@code true} if this icon should not be displayed, false otherwise.
10081          * The default value is {@code false} if this was never set.
10082          */
10083         @Deprecated
getHintHideIcon()10084         public boolean getHintHideIcon() {
10085             return (mFlags & FLAG_HINT_HIDE_ICON) != 0;
10086         }
10087 
10088         /**
10089          * Set a visual hint that only the background image of this notification should be
10090          * displayed, and other semantic content should be hidden. This hint is only applicable
10091          * to sub-pages added using {@link #addPage}.
10092          */
10093         @Deprecated
setHintShowBackgroundOnly(boolean hintShowBackgroundOnly)10094         public WearableExtender setHintShowBackgroundOnly(boolean hintShowBackgroundOnly) {
10095             setFlag(FLAG_HINT_SHOW_BACKGROUND_ONLY, hintShowBackgroundOnly);
10096             return this;
10097         }
10098 
10099         /**
10100          * Get a visual hint that only the background image of this notification should be
10101          * displayed, and other semantic content should be hidden. This hint is only applicable
10102          * to sub-pages added using {@link android.app.Notification.WearableExtender#addPage}.
10103          */
10104         @Deprecated
getHintShowBackgroundOnly()10105         public boolean getHintShowBackgroundOnly() {
10106             return (mFlags & FLAG_HINT_SHOW_BACKGROUND_ONLY) != 0;
10107         }
10108 
10109         /**
10110          * Set a hint that this notification's background should not be clipped if possible,
10111          * and should instead be resized to fully display on the screen, retaining the aspect
10112          * ratio of the image. This can be useful for images like barcodes or qr codes.
10113          * @param hintAvoidBackgroundClipping {@code true} to avoid clipping if possible.
10114          * @return this object for method chaining
10115          */
10116         @Deprecated
setHintAvoidBackgroundClipping( boolean hintAvoidBackgroundClipping)10117         public WearableExtender setHintAvoidBackgroundClipping(
10118                 boolean hintAvoidBackgroundClipping) {
10119             setFlag(FLAG_HINT_AVOID_BACKGROUND_CLIPPING, hintAvoidBackgroundClipping);
10120             return this;
10121         }
10122 
10123         /**
10124          * Get a hint that this notification's background should not be clipped if possible,
10125          * and should instead be resized to fully display on the screen, retaining the aspect
10126          * ratio of the image. This can be useful for images like barcodes or qr codes.
10127          * @return {@code true} if it's ok if the background is clipped on the screen, false
10128          * otherwise. The default value is {@code false} if this was never set.
10129          */
10130         @Deprecated
getHintAvoidBackgroundClipping()10131         public boolean getHintAvoidBackgroundClipping() {
10132             return (mFlags & FLAG_HINT_AVOID_BACKGROUND_CLIPPING) != 0;
10133         }
10134 
10135         /**
10136          * Set a hint that the screen should remain on for at least this duration when
10137          * this notification is displayed on the screen.
10138          * @param timeout The requested screen timeout in milliseconds. Can also be either
10139          *     {@link #SCREEN_TIMEOUT_SHORT} or {@link #SCREEN_TIMEOUT_LONG}.
10140          * @return this object for method chaining
10141          */
10142         @Deprecated
setHintScreenTimeout(int timeout)10143         public WearableExtender setHintScreenTimeout(int timeout) {
10144             mHintScreenTimeout = timeout;
10145             return this;
10146         }
10147 
10148         /**
10149          * Get the duration, in milliseconds, that the screen should remain on for
10150          * when this notification is displayed.
10151          * @return the duration in milliseconds if > 0, or either one of the sentinel values
10152          *     {@link #SCREEN_TIMEOUT_SHORT} or {@link #SCREEN_TIMEOUT_LONG}.
10153          */
10154         @Deprecated
getHintScreenTimeout()10155         public int getHintScreenTimeout() {
10156             return mHintScreenTimeout;
10157         }
10158 
10159         /**
10160          * Set a hint that this notification's {@link BigPictureStyle} (if present) should be
10161          * converted to low-bit and displayed in ambient mode, especially useful for barcodes and
10162          * qr codes, as well as other simple black-and-white tickets.
10163          * @param hintAmbientBigPicture {@code true} to enable converstion and ambient.
10164          * @return this object for method chaining
10165          * @deprecated This feature is no longer supported.
10166          */
10167         @Deprecated
setHintAmbientBigPicture(boolean hintAmbientBigPicture)10168         public WearableExtender setHintAmbientBigPicture(boolean hintAmbientBigPicture) {
10169             setFlag(FLAG_BIG_PICTURE_AMBIENT, hintAmbientBigPicture);
10170             return this;
10171         }
10172 
10173         /**
10174          * Get a hint that this notification's {@link BigPictureStyle} (if present) should be
10175          * converted to low-bit and displayed in ambient mode, especially useful for barcodes and
10176          * qr codes, as well as other simple black-and-white tickets.
10177          * @return {@code true} if it should be displayed in ambient, false otherwise
10178          * otherwise. The default value is {@code false} if this was never set.
10179          * @deprecated This feature is no longer supported.
10180          */
10181         @Deprecated
getHintAmbientBigPicture()10182         public boolean getHintAmbientBigPicture() {
10183             return (mFlags & FLAG_BIG_PICTURE_AMBIENT) != 0;
10184         }
10185 
10186         /**
10187          * Set a hint that this notification's content intent will launch an {@link Activity}
10188          * directly, telling the platform that it can generate the appropriate transitions.
10189          * @param hintContentIntentLaunchesActivity {@code true} if the content intent will launch
10190          * an activity and transitions should be generated, false otherwise.
10191          * @return this object for method chaining
10192          */
setHintContentIntentLaunchesActivity( boolean hintContentIntentLaunchesActivity)10193         public WearableExtender setHintContentIntentLaunchesActivity(
10194                 boolean hintContentIntentLaunchesActivity) {
10195             setFlag(FLAG_HINT_CONTENT_INTENT_LAUNCHES_ACTIVITY, hintContentIntentLaunchesActivity);
10196             return this;
10197         }
10198 
10199         /**
10200          * Get a hint that this notification's content intent will launch an {@link Activity}
10201          * directly, telling the platform that it can generate the appropriate transitions
10202          * @return {@code true} if the content intent will launch an activity and transitions should
10203          * be generated, false otherwise. The default value is {@code false} if this was never set.
10204          */
getHintContentIntentLaunchesActivity()10205         public boolean getHintContentIntentLaunchesActivity() {
10206             return (mFlags & FLAG_HINT_CONTENT_INTENT_LAUNCHES_ACTIVITY) != 0;
10207         }
10208 
10209         /**
10210          * Sets the dismissal id for this notification. If a notification is posted with a
10211          * dismissal id, then when that notification is canceled, notifications on other wearables
10212          * and the paired Android phone having that same dismissal id will also be canceled. See
10213          * <a href="{@docRoot}wear/notifications/index.html">Adding Wearable Features to
10214          * Notifications</a> for more information.
10215          * @param dismissalId the dismissal id of the notification.
10216          * @return this object for method chaining
10217          */
setDismissalId(String dismissalId)10218         public WearableExtender setDismissalId(String dismissalId) {
10219             mDismissalId = dismissalId;
10220             return this;
10221         }
10222 
10223         /**
10224          * Returns the dismissal id of the notification.
10225          * @return the dismissal id of the notification or null if it has not been set.
10226          */
getDismissalId()10227         public String getDismissalId() {
10228             return mDismissalId;
10229         }
10230 
10231         /**
10232          * Sets a bridge tag for this notification. A bridge tag can be set for notifications
10233          * posted from a phone to provide finer-grained control on what notifications are bridged
10234          * to wearables. See <a href="{@docRoot}wear/notifications/index.html">Adding Wearable
10235          * Features to Notifications</a> for more information.
10236          * @param bridgeTag the bridge tag of the notification.
10237          * @return this object for method chaining
10238          */
setBridgeTag(String bridgeTag)10239         public WearableExtender setBridgeTag(String bridgeTag) {
10240             mBridgeTag = bridgeTag;
10241             return this;
10242         }
10243 
10244         /**
10245          * Returns the bridge tag of the notification.
10246          * @return the bridge tag or null if not present.
10247          */
getBridgeTag()10248         public String getBridgeTag() {
10249             return mBridgeTag;
10250         }
10251 
setFlag(int mask, boolean value)10252         private void setFlag(int mask, boolean value) {
10253             if (value) {
10254                 mFlags |= mask;
10255             } else {
10256                 mFlags &= ~mask;
10257             }
10258         }
10259     }
10260 
10261     /**
10262      * <p>Helper class to add Android Auto extensions to notifications. To create a notification
10263      * with car extensions:
10264      *
10265      * <ol>
10266      *  <li>Create an {@link Notification.Builder}, setting any desired
10267      *  properties.
10268      *  <li>Create a {@link CarExtender}.
10269      *  <li>Set car-specific properties using the {@code add} and {@code set} methods of
10270      *  {@link CarExtender}.
10271      *  <li>Call {@link Notification.Builder#extend(Notification.Extender)}
10272      *  to apply the extensions to a notification.
10273      * </ol>
10274      *
10275      * <pre class="prettyprint">
10276      * Notification notification = new Notification.Builder(context)
10277      *         ...
10278      *         .extend(new CarExtender()
10279      *                 .set*(...))
10280      *         .build();
10281      * </pre>
10282      *
10283      * <p>Car extensions can be accessed on an existing notification by using the
10284      * {@code CarExtender(Notification)} constructor, and then using the {@code get} methods
10285      * to access values.
10286      */
10287     public static final class CarExtender implements Extender {
10288         private static final String TAG = "CarExtender";
10289 
10290         private static final String EXTRA_CAR_EXTENDER = "android.car.EXTENSIONS";
10291         private static final String EXTRA_LARGE_ICON = "large_icon";
10292         private static final String EXTRA_CONVERSATION = "car_conversation";
10293         private static final String EXTRA_COLOR = "app_color";
10294 
10295         private Bitmap mLargeIcon;
10296         private UnreadConversation mUnreadConversation;
10297         private int mColor = Notification.COLOR_DEFAULT;
10298 
10299         /**
10300          * Create a {@link CarExtender} with default options.
10301          */
CarExtender()10302         public CarExtender() {
10303         }
10304 
10305         /**
10306          * Create a {@link CarExtender} from the CarExtender options of an existing Notification.
10307          *
10308          * @param notif The notification from which to copy options.
10309          */
CarExtender(Notification notif)10310         public CarExtender(Notification notif) {
10311             Bundle carBundle = notif.extras == null ?
10312                     null : notif.extras.getBundle(EXTRA_CAR_EXTENDER);
10313             if (carBundle != null) {
10314                 mLargeIcon = carBundle.getParcelable(EXTRA_LARGE_ICON);
10315                 mColor = carBundle.getInt(EXTRA_COLOR, Notification.COLOR_DEFAULT);
10316 
10317                 Bundle b = carBundle.getBundle(EXTRA_CONVERSATION);
10318                 mUnreadConversation = UnreadConversation.getUnreadConversationFromBundle(b);
10319             }
10320         }
10321 
10322         /**
10323          * Apply car extensions to a notification that is being built. This is typically called by
10324          * the {@link Notification.Builder#extend(Notification.Extender)}
10325          * method of {@link Notification.Builder}.
10326          */
10327         @Override
extend(Notification.Builder builder)10328         public Notification.Builder extend(Notification.Builder builder) {
10329             Bundle carExtensions = new Bundle();
10330 
10331             if (mLargeIcon != null) {
10332                 carExtensions.putParcelable(EXTRA_LARGE_ICON, mLargeIcon);
10333             }
10334             if (mColor != Notification.COLOR_DEFAULT) {
10335                 carExtensions.putInt(EXTRA_COLOR, mColor);
10336             }
10337 
10338             if (mUnreadConversation != null) {
10339                 Bundle b = mUnreadConversation.getBundleForUnreadConversation();
10340                 carExtensions.putBundle(EXTRA_CONVERSATION, b);
10341             }
10342 
10343             builder.getExtras().putBundle(EXTRA_CAR_EXTENDER, carExtensions);
10344             return builder;
10345         }
10346 
10347         /**
10348          * Sets the accent color to use when Android Auto presents the notification.
10349          *
10350          * Android Auto uses the color set with {@link Notification.Builder#setColor(int)}
10351          * to accent the displayed notification. However, not all colors are acceptable in an
10352          * automotive setting. This method can be used to override the color provided in the
10353          * notification in such a situation.
10354          */
setColor(@olorInt int color)10355         public CarExtender setColor(@ColorInt int color) {
10356             mColor = color;
10357             return this;
10358         }
10359 
10360         /**
10361          * Gets the accent color.
10362          *
10363          * @see #setColor
10364          */
10365         @ColorInt
getColor()10366         public int getColor() {
10367             return mColor;
10368         }
10369 
10370         /**
10371          * Sets the large icon of the car notification.
10372          *
10373          * If no large icon is set in the extender, Android Auto will display the icon
10374          * specified by {@link Notification.Builder#setLargeIcon(android.graphics.Bitmap)}
10375          *
10376          * @param largeIcon The large icon to use in the car notification.
10377          * @return This object for method chaining.
10378          */
setLargeIcon(Bitmap largeIcon)10379         public CarExtender setLargeIcon(Bitmap largeIcon) {
10380             mLargeIcon = largeIcon;
10381             return this;
10382         }
10383 
10384         /**
10385          * Gets the large icon used in this car notification, or null if no icon has been set.
10386          *
10387          * @return The large icon for the car notification.
10388          * @see CarExtender#setLargeIcon
10389          */
getLargeIcon()10390         public Bitmap getLargeIcon() {
10391             return mLargeIcon;
10392         }
10393 
10394         /**
10395          * Sets the unread conversation in a message notification.
10396          *
10397          * @param unreadConversation The unread part of the conversation this notification conveys.
10398          * @return This object for method chaining.
10399          */
setUnreadConversation(UnreadConversation unreadConversation)10400         public CarExtender setUnreadConversation(UnreadConversation unreadConversation) {
10401             mUnreadConversation = unreadConversation;
10402             return this;
10403         }
10404 
10405         /**
10406          * Returns the unread conversation conveyed by this notification.
10407          * @see #setUnreadConversation(UnreadConversation)
10408          */
getUnreadConversation()10409         public UnreadConversation getUnreadConversation() {
10410             return mUnreadConversation;
10411         }
10412 
10413         /**
10414          * A class which holds the unread messages from a conversation.
10415          */
10416         public static class UnreadConversation {
10417             private static final String KEY_AUTHOR = "author";
10418             private static final String KEY_TEXT = "text";
10419             private static final String KEY_MESSAGES = "messages";
10420             private static final String KEY_REMOTE_INPUT = "remote_input";
10421             private static final String KEY_ON_REPLY = "on_reply";
10422             private static final String KEY_ON_READ = "on_read";
10423             private static final String KEY_PARTICIPANTS = "participants";
10424             private static final String KEY_TIMESTAMP = "timestamp";
10425 
10426             private final String[] mMessages;
10427             private final RemoteInput mRemoteInput;
10428             private final PendingIntent mReplyPendingIntent;
10429             private final PendingIntent mReadPendingIntent;
10430             private final String[] mParticipants;
10431             private final long mLatestTimestamp;
10432 
UnreadConversation(String[] messages, RemoteInput remoteInput, PendingIntent replyPendingIntent, PendingIntent readPendingIntent, String[] participants, long latestTimestamp)10433             UnreadConversation(String[] messages, RemoteInput remoteInput,
10434                     PendingIntent replyPendingIntent, PendingIntent readPendingIntent,
10435                     String[] participants, long latestTimestamp) {
10436                 mMessages = messages;
10437                 mRemoteInput = remoteInput;
10438                 mReadPendingIntent = readPendingIntent;
10439                 mReplyPendingIntent = replyPendingIntent;
10440                 mParticipants = participants;
10441                 mLatestTimestamp = latestTimestamp;
10442             }
10443 
10444             /**
10445              * Gets the list of messages conveyed by this notification.
10446              */
getMessages()10447             public String[] getMessages() {
10448                 return mMessages;
10449             }
10450 
10451             /**
10452              * Gets the remote input that will be used to convey the response to a message list, or
10453              * null if no such remote input exists.
10454              */
getRemoteInput()10455             public RemoteInput getRemoteInput() {
10456                 return mRemoteInput;
10457             }
10458 
10459             /**
10460              * Gets the pending intent that will be triggered when the user replies to this
10461              * notification.
10462              */
getReplyPendingIntent()10463             public PendingIntent getReplyPendingIntent() {
10464                 return mReplyPendingIntent;
10465             }
10466 
10467             /**
10468              * Gets the pending intent that Android Auto will send after it reads aloud all messages
10469              * in this object's message list.
10470              */
getReadPendingIntent()10471             public PendingIntent getReadPendingIntent() {
10472                 return mReadPendingIntent;
10473             }
10474 
10475             /**
10476              * Gets the participants in the conversation.
10477              */
getParticipants()10478             public String[] getParticipants() {
10479                 return mParticipants;
10480             }
10481 
10482             /**
10483              * Gets the firs participant in the conversation.
10484              */
getParticipant()10485             public String getParticipant() {
10486                 return mParticipants.length > 0 ? mParticipants[0] : null;
10487             }
10488 
10489             /**
10490              * Gets the timestamp of the conversation.
10491              */
getLatestTimestamp()10492             public long getLatestTimestamp() {
10493                 return mLatestTimestamp;
10494             }
10495 
getBundleForUnreadConversation()10496             Bundle getBundleForUnreadConversation() {
10497                 Bundle b = new Bundle();
10498                 String author = null;
10499                 if (mParticipants != null && mParticipants.length > 1) {
10500                     author = mParticipants[0];
10501                 }
10502                 Parcelable[] messages = new Parcelable[mMessages.length];
10503                 for (int i = 0; i < messages.length; i++) {
10504                     Bundle m = new Bundle();
10505                     m.putString(KEY_TEXT, mMessages[i]);
10506                     m.putString(KEY_AUTHOR, author);
10507                     messages[i] = m;
10508                 }
10509                 b.putParcelableArray(KEY_MESSAGES, messages);
10510                 if (mRemoteInput != null) {
10511                     b.putParcelable(KEY_REMOTE_INPUT, mRemoteInput);
10512                 }
10513                 b.putParcelable(KEY_ON_REPLY, mReplyPendingIntent);
10514                 b.putParcelable(KEY_ON_READ, mReadPendingIntent);
10515                 b.putStringArray(KEY_PARTICIPANTS, mParticipants);
10516                 b.putLong(KEY_TIMESTAMP, mLatestTimestamp);
10517                 return b;
10518             }
10519 
getUnreadConversationFromBundle(Bundle b)10520             static UnreadConversation getUnreadConversationFromBundle(Bundle b) {
10521                 if (b == null) {
10522                     return null;
10523                 }
10524                 Parcelable[] parcelableMessages = b.getParcelableArray(KEY_MESSAGES);
10525                 String[] messages = null;
10526                 if (parcelableMessages != null) {
10527                     String[] tmp = new String[parcelableMessages.length];
10528                     boolean success = true;
10529                     for (int i = 0; i < tmp.length; i++) {
10530                         if (!(parcelableMessages[i] instanceof Bundle)) {
10531                             success = false;
10532                             break;
10533                         }
10534                         tmp[i] = ((Bundle) parcelableMessages[i]).getString(KEY_TEXT);
10535                         if (tmp[i] == null) {
10536                             success = false;
10537                             break;
10538                         }
10539                     }
10540                     if (success) {
10541                         messages = tmp;
10542                     } else {
10543                         return null;
10544                     }
10545                 }
10546 
10547                 PendingIntent onRead = b.getParcelable(KEY_ON_READ);
10548                 PendingIntent onReply = b.getParcelable(KEY_ON_REPLY);
10549 
10550                 RemoteInput remoteInput = b.getParcelable(KEY_REMOTE_INPUT);
10551 
10552                 String[] participants = b.getStringArray(KEY_PARTICIPANTS);
10553                 if (participants == null || participants.length != 1) {
10554                     return null;
10555                 }
10556 
10557                 return new UnreadConversation(messages,
10558                         remoteInput,
10559                         onReply,
10560                         onRead,
10561                         participants, b.getLong(KEY_TIMESTAMP));
10562             }
10563         };
10564 
10565         /**
10566          * Builder class for {@link CarExtender.UnreadConversation} objects.
10567          */
10568         public static class Builder {
10569             private final List<String> mMessages = new ArrayList<String>();
10570             private final String mParticipant;
10571             private RemoteInput mRemoteInput;
10572             private PendingIntent mReadPendingIntent;
10573             private PendingIntent mReplyPendingIntent;
10574             private long mLatestTimestamp;
10575 
10576             /**
10577              * Constructs a new builder for {@link CarExtender.UnreadConversation}.
10578              *
10579              * @param name The name of the other participant in the conversation.
10580              */
Builder(String name)10581             public Builder(String name) {
10582                 mParticipant = name;
10583             }
10584 
10585             /**
10586              * Appends a new unread message to the list of messages for this conversation.
10587              *
10588              * The messages should be added from oldest to newest.
10589              *
10590              * @param message The text of the new unread message.
10591              * @return This object for method chaining.
10592              */
addMessage(String message)10593             public Builder addMessage(String message) {
10594                 mMessages.add(message);
10595                 return this;
10596             }
10597 
10598             /**
10599              * Sets the pending intent and remote input which will convey the reply to this
10600              * notification.
10601              *
10602              * @param pendingIntent The pending intent which will be triggered on a reply.
10603              * @param remoteInput The remote input parcelable which will carry the reply.
10604              * @return This object for method chaining.
10605              *
10606              * @see CarExtender.UnreadConversation#getRemoteInput
10607              * @see CarExtender.UnreadConversation#getReplyPendingIntent
10608              */
setReplyAction( PendingIntent pendingIntent, RemoteInput remoteInput)10609             public Builder setReplyAction(
10610                     PendingIntent pendingIntent, RemoteInput remoteInput) {
10611                 mRemoteInput = remoteInput;
10612                 mReplyPendingIntent = pendingIntent;
10613 
10614                 return this;
10615             }
10616 
10617             /**
10618              * Sets the pending intent that will be sent once the messages in this notification
10619              * are read.
10620              *
10621              * @param pendingIntent The pending intent to use.
10622              * @return This object for method chaining.
10623              */
setReadPendingIntent(PendingIntent pendingIntent)10624             public Builder setReadPendingIntent(PendingIntent pendingIntent) {
10625                 mReadPendingIntent = pendingIntent;
10626                 return this;
10627             }
10628 
10629             /**
10630              * Sets the timestamp of the most recent message in an unread conversation.
10631              *
10632              * If a messaging notification has been posted by your application and has not
10633              * yet been cancelled, posting a later notification with the same id and tag
10634              * but without a newer timestamp may result in Android Auto not displaying a
10635              * heads up notification for the later notification.
10636              *
10637              * @param timestamp The timestamp of the most recent message in the conversation.
10638              * @return This object for method chaining.
10639              */
setLatestTimestamp(long timestamp)10640             public Builder setLatestTimestamp(long timestamp) {
10641                 mLatestTimestamp = timestamp;
10642                 return this;
10643             }
10644 
10645             /**
10646              * Builds a new unread conversation object.
10647              *
10648              * @return The new unread conversation object.
10649              */
build()10650             public UnreadConversation build() {
10651                 String[] messages = mMessages.toArray(new String[mMessages.size()]);
10652                 String[] participants = { mParticipant };
10653                 return new UnreadConversation(messages, mRemoteInput, mReplyPendingIntent,
10654                         mReadPendingIntent, participants, mLatestTimestamp);
10655             }
10656         }
10657     }
10658 
10659     /**
10660      * <p>Helper class to add Android TV extensions to notifications. To create a notification
10661      * with a TV extension:
10662      *
10663      * <ol>
10664      *  <li>Create an {@link Notification.Builder}, setting any desired properties.
10665      *  <li>Create a {@link TvExtender}.
10666      *  <li>Set TV-specific properties using the {@code set} methods of
10667      *  {@link TvExtender}.
10668      *  <li>Call {@link Notification.Builder#extend(Notification.Extender)}
10669      *  to apply the extension to a notification.
10670      * </ol>
10671      *
10672      * <pre class="prettyprint">
10673      * Notification notification = new Notification.Builder(context)
10674      *         ...
10675      *         .extend(new TvExtender()
10676      *                 .set*(...))
10677      *         .build();
10678      * </pre>
10679      *
10680      * <p>TV extensions can be accessed on an existing notification by using the
10681      * {@code TvExtender(Notification)} constructor, and then using the {@code get} methods
10682      * to access values.
10683      *
10684      * @hide
10685      */
10686     @SystemApi
10687     public static final class TvExtender implements Extender {
10688         private static final String TAG = "TvExtender";
10689 
10690         private static final String EXTRA_TV_EXTENDER = "android.tv.EXTENSIONS";
10691         private static final String EXTRA_FLAGS = "flags";
10692         private static final String EXTRA_CONTENT_INTENT = "content_intent";
10693         private static final String EXTRA_DELETE_INTENT = "delete_intent";
10694         private static final String EXTRA_CHANNEL_ID = "channel_id";
10695         private static final String EXTRA_SUPPRESS_SHOW_OVER_APPS = "suppressShowOverApps";
10696 
10697         // Flags bitwise-ored to mFlags
10698         private static final int FLAG_AVAILABLE_ON_TV = 0x1;
10699 
10700         private int mFlags;
10701         private String mChannelId;
10702         private PendingIntent mContentIntent;
10703         private PendingIntent mDeleteIntent;
10704         private boolean mSuppressShowOverApps;
10705 
10706         /**
10707          * Create a {@link TvExtender} with default options.
10708          */
TvExtender()10709         public TvExtender() {
10710             mFlags = FLAG_AVAILABLE_ON_TV;
10711         }
10712 
10713         /**
10714          * Create a {@link TvExtender} from the TvExtender options of an existing Notification.
10715          *
10716          * @param notif The notification from which to copy options.
10717          */
TvExtender(Notification notif)10718         public TvExtender(Notification notif) {
10719             Bundle bundle = notif.extras == null ?
10720                 null : notif.extras.getBundle(EXTRA_TV_EXTENDER);
10721             if (bundle != null) {
10722                 mFlags = bundle.getInt(EXTRA_FLAGS);
10723                 mChannelId = bundle.getString(EXTRA_CHANNEL_ID);
10724                 mSuppressShowOverApps = bundle.getBoolean(EXTRA_SUPPRESS_SHOW_OVER_APPS);
10725                 mContentIntent = bundle.getParcelable(EXTRA_CONTENT_INTENT);
10726                 mDeleteIntent = bundle.getParcelable(EXTRA_DELETE_INTENT);
10727             }
10728         }
10729 
10730         /**
10731          * Apply a TV extension to a notification that is being built. This is typically called by
10732          * the {@link Notification.Builder#extend(Notification.Extender)}
10733          * method of {@link Notification.Builder}.
10734          */
10735         @Override
extend(Notification.Builder builder)10736         public Notification.Builder extend(Notification.Builder builder) {
10737             Bundle bundle = new Bundle();
10738 
10739             bundle.putInt(EXTRA_FLAGS, mFlags);
10740             bundle.putString(EXTRA_CHANNEL_ID, mChannelId);
10741             bundle.putBoolean(EXTRA_SUPPRESS_SHOW_OVER_APPS, mSuppressShowOverApps);
10742             if (mContentIntent != null) {
10743                 bundle.putParcelable(EXTRA_CONTENT_INTENT, mContentIntent);
10744             }
10745 
10746             if (mDeleteIntent != null) {
10747                 bundle.putParcelable(EXTRA_DELETE_INTENT, mDeleteIntent);
10748             }
10749 
10750             builder.getExtras().putBundle(EXTRA_TV_EXTENDER, bundle);
10751             return builder;
10752         }
10753 
10754         /**
10755          * Returns true if this notification should be shown on TV. This method return true
10756          * if the notification was extended with a TvExtender.
10757          */
isAvailableOnTv()10758         public boolean isAvailableOnTv() {
10759             return (mFlags & FLAG_AVAILABLE_ON_TV) != 0;
10760         }
10761 
10762         /**
10763          * Specifies the channel the notification should be delivered on when shown on TV.
10764          * It can be different from the channel that the notification is delivered to when
10765          * posting on a non-TV device.
10766          */
setChannel(String channelId)10767         public TvExtender setChannel(String channelId) {
10768             mChannelId = channelId;
10769             return this;
10770         }
10771 
10772         /**
10773          * Specifies the channel the notification should be delivered on when shown on TV.
10774          * It can be different from the channel that the notification is delivered to when
10775          * posting on a non-TV device.
10776          */
setChannelId(String channelId)10777         public TvExtender setChannelId(String channelId) {
10778             mChannelId = channelId;
10779             return this;
10780         }
10781 
10782         /** @removed */
10783         @Deprecated
getChannel()10784         public String getChannel() {
10785             return mChannelId;
10786         }
10787 
10788         /**
10789          * Returns the id of the channel this notification posts to on TV.
10790          */
getChannelId()10791         public String getChannelId() {
10792             return mChannelId;
10793         }
10794 
10795         /**
10796          * Supplies a {@link PendingIntent} to be sent when the notification is selected on TV.
10797          * If provided, it is used instead of the content intent specified
10798          * at the level of Notification.
10799          */
setContentIntent(PendingIntent intent)10800         public TvExtender setContentIntent(PendingIntent intent) {
10801             mContentIntent = intent;
10802             return this;
10803         }
10804 
10805         /**
10806          * Returns the TV-specific content intent.  If this method returns null, the
10807          * main content intent on the notification should be used.
10808          *
10809          * @see {@link Notification#contentIntent}
10810          */
getContentIntent()10811         public PendingIntent getContentIntent() {
10812             return mContentIntent;
10813         }
10814 
10815         /**
10816          * Supplies a {@link PendingIntent} to send when the notification is cleared explicitly
10817          * by the user on TV.  If provided, it is used instead of the delete intent specified
10818          * at the level of Notification.
10819          */
setDeleteIntent(PendingIntent intent)10820         public TvExtender setDeleteIntent(PendingIntent intent) {
10821             mDeleteIntent = intent;
10822             return this;
10823         }
10824 
10825         /**
10826          * Returns the TV-specific delete intent.  If this method returns null, the
10827          * main delete intent on the notification should be used.
10828          *
10829          * @see {@link Notification#deleteIntent}
10830          */
getDeleteIntent()10831         public PendingIntent getDeleteIntent() {
10832             return mDeleteIntent;
10833         }
10834 
10835         /**
10836          * Specifies whether this notification should suppress showing a message over top of apps
10837          * outside of the launcher.
10838          */
setSuppressShowOverApps(boolean suppress)10839         public TvExtender setSuppressShowOverApps(boolean suppress) {
10840             mSuppressShowOverApps = suppress;
10841             return this;
10842         }
10843 
10844         /**
10845          * Returns true if this notification should not show messages over top of apps
10846          * outside of the launcher.
10847          */
getSuppressShowOverApps()10848         public boolean getSuppressShowOverApps() {
10849             return mSuppressShowOverApps;
10850         }
10851     }
10852 
10853     /**
10854      * Get an array of Parcelable objects from a parcelable array bundle field.
10855      * Update the bundle to have a typed array so fetches in the future don't need
10856      * to do an array copy.
10857      */
10858     @Nullable
getParcelableArrayFromBundle( Bundle bundle, String key, Class<T> itemClass)10859     private static <T extends Parcelable> T[] getParcelableArrayFromBundle(
10860             Bundle bundle, String key, Class<T> itemClass) {
10861         final Parcelable[] array = bundle.getParcelableArray(key);
10862         final Class<?> arrayClass = Array.newInstance(itemClass, 0).getClass();
10863         if (arrayClass.isInstance(array) || array == null) {
10864             return (T[]) array;
10865         }
10866         final T[] typedArray = (T[]) Array.newInstance(itemClass, array.length);
10867         for (int i = 0; i < array.length; i++) {
10868             typedArray[i] = (T) array[i];
10869         }
10870         bundle.putParcelableArray(key, typedArray);
10871         return typedArray;
10872     }
10873 
10874     private static class BuilderRemoteViews extends RemoteViews {
BuilderRemoteViews(Parcel parcel)10875         public BuilderRemoteViews(Parcel parcel) {
10876             super(parcel);
10877         }
10878 
BuilderRemoteViews(ApplicationInfo appInfo, int layoutId)10879         public BuilderRemoteViews(ApplicationInfo appInfo, int layoutId) {
10880             super(appInfo, layoutId);
10881         }
10882 
10883         @Override
clone()10884         public BuilderRemoteViews clone() {
10885             Parcel p = Parcel.obtain();
10886             writeToParcel(p, 0);
10887             p.setDataPosition(0);
10888             BuilderRemoteViews brv = new BuilderRemoteViews(p);
10889             p.recycle();
10890             return brv;
10891         }
10892 
10893         /**
10894          * Override and return true, since {@link RemoteViews#onLoadClass(Class)} is not overridden.
10895          *
10896          * @see RemoteViews#shouldUseStaticFilter()
10897          */
10898         @Override
shouldUseStaticFilter()10899         protected boolean shouldUseStaticFilter() {
10900             return true;
10901         }
10902     }
10903 
10904     /**
10905      * A result object where information about the template that was created is saved.
10906      */
10907     private static class TemplateBindResult {
10908         int mIconMarginEnd;
10909         boolean mRightIconContainerVisible;
10910 
10911         /**
10912          * Get the margin end that needs to be added to any fields that may overlap
10913          * with the right actions.
10914          */
getIconMarginEnd()10915         public int getIconMarginEnd() {
10916             return mIconMarginEnd;
10917         }
10918 
10919         /**
10920          * Is the icon container visible on the right size because of the reply button or the
10921          * right icon.
10922          */
isRightIconContainerVisible()10923         public boolean isRightIconContainerVisible() {
10924             return mRightIconContainerVisible;
10925         }
10926 
setIconMarginEnd(int iconMarginEnd)10927         public void setIconMarginEnd(int iconMarginEnd) {
10928             this.mIconMarginEnd = iconMarginEnd;
10929         }
10930 
setRightIconContainerVisible(boolean iconContainerVisible)10931         public void setRightIconContainerVisible(boolean iconContainerVisible) {
10932             mRightIconContainerVisible = iconContainerVisible;
10933         }
10934     }
10935 
10936     private static class StandardTemplateParams {
10937         boolean hasProgress = true;
10938         CharSequence title;
10939         CharSequence text;
10940         CharSequence headerTextSecondary;
10941         CharSequence summaryText;
10942         int maxRemoteInputHistory = Style.MAX_REMOTE_INPUT_HISTORY_LINES;
10943         boolean hideLargeIcon;
10944         boolean hideReplyIcon;
10945         boolean allowColorization  = true;
10946         boolean forceDefaultColor = false;
10947 
reset()10948         final StandardTemplateParams reset() {
10949             hasProgress = true;
10950             title = null;
10951             text = null;
10952             summaryText = null;
10953             headerTextSecondary = null;
10954             maxRemoteInputHistory = Style.MAX_REMOTE_INPUT_HISTORY_LINES;
10955             allowColorization = true;
10956             forceDefaultColor = false;
10957             return this;
10958         }
10959 
hasProgress(boolean hasProgress)10960         final StandardTemplateParams hasProgress(boolean hasProgress) {
10961             this.hasProgress = hasProgress;
10962             return this;
10963         }
10964 
title(CharSequence title)10965         final StandardTemplateParams title(CharSequence title) {
10966             this.title = title;
10967             return this;
10968         }
10969 
text(CharSequence text)10970         final StandardTemplateParams text(CharSequence text) {
10971             this.text = text;
10972             return this;
10973         }
10974 
summaryText(CharSequence text)10975         final StandardTemplateParams summaryText(CharSequence text) {
10976             this.summaryText = text;
10977             return this;
10978         }
10979 
headerTextSecondary(CharSequence text)10980         final StandardTemplateParams headerTextSecondary(CharSequence text) {
10981             this.headerTextSecondary = text;
10982             return this;
10983         }
10984 
hideLargeIcon(boolean hideLargeIcon)10985         final StandardTemplateParams hideLargeIcon(boolean hideLargeIcon) {
10986             this.hideLargeIcon = hideLargeIcon;
10987             return this;
10988         }
10989 
hideReplyIcon(boolean hideReplyIcon)10990         final StandardTemplateParams hideReplyIcon(boolean hideReplyIcon) {
10991             this.hideReplyIcon = hideReplyIcon;
10992             return this;
10993         }
10994 
disallowColorization()10995         final StandardTemplateParams disallowColorization() {
10996             this.allowColorization = false;
10997             return this;
10998         }
10999 
forceDefaultColor()11000         final StandardTemplateParams forceDefaultColor() {
11001             this.forceDefaultColor = true;
11002             return this;
11003         }
11004 
fillTextsFrom(Builder b)11005         final StandardTemplateParams fillTextsFrom(Builder b) {
11006             Bundle extras = b.mN.extras;
11007             this.title = b.processLegacyText(extras.getCharSequence(EXTRA_TITLE));
11008             this.text = b.processLegacyText(extras.getCharSequence(EXTRA_TEXT));
11009             this.summaryText = extras.getCharSequence(EXTRA_SUB_TEXT);
11010             return this;
11011         }
11012 
11013         /**
11014          * Set the maximum lines of remote input history lines allowed.
11015          * @param maxRemoteInputHistory The number of lines.
11016          * @return The builder for method chaining.
11017          */
setMaxRemoteInputHistory(int maxRemoteInputHistory)11018         public StandardTemplateParams setMaxRemoteInputHistory(int maxRemoteInputHistory) {
11019             this.maxRemoteInputHistory = maxRemoteInputHistory;
11020             return this;
11021         }
11022     }
11023 }
11024