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