• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2007 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.app;
18 
19 import static android.annotation.Dimension.DP;
20 import static android.graphics.drawable.Icon.TYPE_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.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     public int flags;
632 
633     /** @hide */
634     @IntDef(prefix = { "PRIORITY_" }, value = {
635             PRIORITY_DEFAULT,
636             PRIORITY_LOW,
637             PRIORITY_MIN,
638             PRIORITY_HIGH,
639             PRIORITY_MAX
640     })
641     @Retention(RetentionPolicy.SOURCE)
642     public @interface Priority {}
643 
644     /**
645      * Default notification {@link #priority}. If your application does not prioritize its own
646      * notifications, use this value for all notifications.
647      *
648      * @deprecated use {@link NotificationManager#IMPORTANCE_DEFAULT} instead.
649      */
650     @Deprecated
651     public static final int PRIORITY_DEFAULT = 0;
652 
653     /**
654      * Lower {@link #priority}, for items that are less important. The UI may choose to show these
655      * items smaller, or at a different position in the list, compared with your app's
656      * {@link #PRIORITY_DEFAULT} items.
657      *
658      * @deprecated use {@link NotificationManager#IMPORTANCE_LOW} instead.
659      */
660     @Deprecated
661     public static final int PRIORITY_LOW = -1;
662 
663     /**
664      * Lowest {@link #priority}; these items might not be shown to the user except under special
665      * circumstances, such as detailed notification logs.
666      *
667      * @deprecated use {@link NotificationManager#IMPORTANCE_MIN} instead.
668      */
669     @Deprecated
670     public static final int PRIORITY_MIN = -2;
671 
672     /**
673      * Higher {@link #priority}, for more important notifications or alerts. The UI may choose to
674      * show these items larger, or at a different position in notification lists, compared with
675      * your app's {@link #PRIORITY_DEFAULT} items.
676      *
677      * @deprecated use {@link NotificationManager#IMPORTANCE_HIGH} instead.
678      */
679     @Deprecated
680     public static final int PRIORITY_HIGH = 1;
681 
682     /**
683      * Highest {@link #priority}, for your application's most important items that require the
684      * user's prompt attention or input.
685      *
686      * @deprecated use {@link NotificationManager#IMPORTANCE_HIGH} instead.
687      */
688     @Deprecated
689     public static final int PRIORITY_MAX = 2;
690 
691     /**
692      * Relative priority for this notification.
693      *
694      * Priority is an indication of how much of the user's valuable attention should be consumed by
695      * this notification. Low-priority notifications may be hidden from the user in certain
696      * situations, while the user might be interrupted for a higher-priority notification. The
697      * system will make a determination about how to interpret this priority when presenting
698      * the notification.
699      *
700      * <p>
701      * A notification that is at least {@link #PRIORITY_HIGH} is more likely to be presented
702      * as a heads-up notification.
703      * </p>
704      *
705      * @deprecated use {@link NotificationChannel#getImportance()} instead.
706      */
707     @Priority
708     @Deprecated
709     public int priority;
710 
711     /**
712      * Accent color (an ARGB integer like the constants in {@link android.graphics.Color})
713      * to be applied by the standard Style templates when presenting this notification.
714      *
715      * The current template design constructs a colorful header image by overlaying the
716      * {@link #icon} image (stenciled in white) atop a field of this color. Alpha components are
717      * ignored.
718      */
719     @ColorInt
720     public int color = COLOR_DEFAULT;
721 
722     /**
723      * Special value of {@link #color} telling the system not to decorate this notification with
724      * any special color but instead use default colors when presenting this notification.
725      */
726     @ColorInt
727     public static final int COLOR_DEFAULT = 0; // AKA Color.TRANSPARENT
728 
729     /**
730      * Special value of {@link #color} used as a place holder for an invalid color.
731      * @hide
732      */
733     @ColorInt
734     public static final int COLOR_INVALID = 1;
735 
736     /**
737      * Sphere of visibility of this notification, which affects how and when the SystemUI reveals
738      * the notification's presence and contents in untrusted situations (namely, on the secure
739      * lockscreen).
740      *
741      * The default level, {@link #VISIBILITY_PRIVATE}, behaves exactly as notifications have always
742      * done on Android: The notification's {@link #icon} and {@link #tickerText} (if available) are
743      * shown in all situations, but the contents are only available if the device is unlocked for
744      * the appropriate user.
745      *
746      * A more permissive policy can be expressed by {@link #VISIBILITY_PUBLIC}; such a notification
747      * can be read even in an "insecure" context (that is, above a secure lockscreen).
748      * To modify the public version of this notification—for example, to redact some portions—see
749      * {@link Builder#setPublicVersion(Notification)}.
750      *
751      * Finally, a notification can be made {@link #VISIBILITY_SECRET}, which will suppress its icon
752      * and ticker until the user has bypassed the lockscreen.
753      */
754     public @Visibility int visibility;
755 
756     /** @hide */
757     @IntDef(prefix = { "VISIBILITY_" }, value = {
758             VISIBILITY_PUBLIC,
759             VISIBILITY_PRIVATE,
760             VISIBILITY_SECRET,
761     })
762     @Retention(RetentionPolicy.SOURCE)
763     public @interface Visibility {}
764 
765     /**
766      * Notification visibility: Show this notification in its entirety on all lockscreens.
767      *
768      * {@see #visibility}
769      */
770     public static final int VISIBILITY_PUBLIC = 1;
771 
772     /**
773      * Notification visibility: Show this notification on all lockscreens, but conceal sensitive or
774      * private information on secure lockscreens.
775      *
776      * {@see #visibility}
777      */
778     public static final int VISIBILITY_PRIVATE = 0;
779 
780     /**
781      * Notification visibility: Do not reveal any part of this notification on a secure lockscreen.
782      *
783      * {@see #visibility}
784      */
785     public static final int VISIBILITY_SECRET = -1;
786 
787     /**
788      * Notification category: incoming call (voice or video) or similar synchronous communication request.
789      */
790     public static final String CATEGORY_CALL = "call";
791 
792     /**
793      * Notification category: map turn-by-turn navigation.
794      */
795     public static final String CATEGORY_NAVIGATION = "navigation";
796 
797     /**
798      * Notification category: incoming direct message (SMS, instant message, etc.).
799      */
800     public static final String CATEGORY_MESSAGE = "msg";
801 
802     /**
803      * Notification category: asynchronous bulk message (email).
804      */
805     public static final String CATEGORY_EMAIL = "email";
806 
807     /**
808      * Notification category: calendar event.
809      */
810     public static final String CATEGORY_EVENT = "event";
811 
812     /**
813      * Notification category: promotion or advertisement.
814      */
815     public static final String CATEGORY_PROMO = "promo";
816 
817     /**
818      * Notification category: alarm or timer.
819      */
820     public static final String CATEGORY_ALARM = "alarm";
821 
822     /**
823      * Notification category: progress of a long-running background operation.
824      */
825     public static final String CATEGORY_PROGRESS = "progress";
826 
827     /**
828      * Notification category: social network or sharing update.
829      */
830     public static final String CATEGORY_SOCIAL = "social";
831 
832     /**
833      * Notification category: error in background operation or authentication status.
834      */
835     public static final String CATEGORY_ERROR = "err";
836 
837     /**
838      * Notification category: media transport control for playback.
839      */
840     public static final String CATEGORY_TRANSPORT = "transport";
841 
842     /**
843      * Notification category: system or device status update.  Reserved for system use.
844      */
845     public static final String CATEGORY_SYSTEM = "sys";
846 
847     /**
848      * Notification category: indication of running background service.
849      */
850     public static final String CATEGORY_SERVICE = "service";
851 
852     /**
853      * Notification category: a specific, timely recommendation for a single thing.
854      * For example, a news app might want to recommend a news story it believes the user will
855      * want to read next.
856      */
857     public static final String CATEGORY_RECOMMENDATION = "recommendation";
858 
859     /**
860      * Notification category: ongoing information about device or contextual status.
861      */
862     public static final String CATEGORY_STATUS = "status";
863 
864     /**
865      * Notification category: user-scheduled reminder.
866      */
867     public static final String CATEGORY_REMINDER = "reminder";
868 
869     /**
870      * Notification category: extreme car emergencies.
871      * @hide
872      */
873     @SystemApi
874     public static final String CATEGORY_CAR_EMERGENCY = "car_emergency";
875 
876     /**
877      * Notification category: car warnings.
878      * @hide
879      */
880     @SystemApi
881     public static final String CATEGORY_CAR_WARNING = "car_warning";
882 
883     /**
884      * Notification category: general car system information.
885      * @hide
886      */
887     @SystemApi
888     public static final String CATEGORY_CAR_INFORMATION = "car_information";
889 
890     /**
891      * One of the predefined notification categories (see the <code>CATEGORY_*</code> constants)
892      * that best describes this Notification.  May be used by the system for ranking and filtering.
893      */
894     public String category;
895 
896     @UnsupportedAppUsage
897     private String mGroupKey;
898 
899     /**
900      * Get the key used to group this notification into a cluster or stack
901      * with other notifications on devices which support such rendering.
902      */
getGroup()903     public String getGroup() {
904         return mGroupKey;
905     }
906 
907     private String mSortKey;
908 
909     /**
910      * Get a sort key that orders this notification among other notifications from the
911      * same package. This can be useful if an external sort was already applied and an app
912      * would like to preserve this. Notifications will be sorted lexicographically using this
913      * value, although providing different priorities in addition to providing sort key may
914      * cause this value to be ignored.
915      *
916      * <p>This sort key can also be used to order members of a notification group. See
917      * {@link Builder#setGroup}.
918      *
919      * @see String#compareTo(String)
920      */
getSortKey()921     public String getSortKey() {
922         return mSortKey;
923     }
924 
925     /**
926      * Additional semantic data to be carried around with this Notification.
927      * <p>
928      * The extras keys defined here are intended to capture the original inputs to {@link Builder}
929      * APIs, and are intended to be used by
930      * {@link android.service.notification.NotificationListenerService} implementations to extract
931      * detailed information from notification objects.
932      */
933     public Bundle extras = new Bundle();
934 
935     /**
936      * All pending intents in the notification as the system needs to be able to access them but
937      * touching the extras bundle in the system process is not safe because the bundle may contain
938      * custom parcelable objects.
939      *
940      * @hide
941      */
942     @UnsupportedAppUsage
943     public ArraySet<PendingIntent> allPendingIntents;
944 
945     /**
946      * Token identifying the notification that is applying doze/bgcheck whitelisting to the
947      * pending intents inside of it, so only those will get the behavior.
948      *
949      * @hide
950      */
951     private IBinder mWhitelistToken;
952 
953     /**
954      * Must be set by a process to start associating tokens with Notification objects
955      * coming in to it.  This is set by NotificationManagerService.
956      *
957      * @hide
958      */
959     static public IBinder processWhitelistToken;
960 
961     /**
962      * {@link #extras} key: this is the title of the notification,
963      * as supplied to {@link Builder#setContentTitle(CharSequence)}.
964      */
965     public static final String EXTRA_TITLE = "android.title";
966 
967     /**
968      * {@link #extras} key: this is the title of the notification when shown in expanded form,
969      * e.g. as supplied to {@link BigTextStyle#setBigContentTitle(CharSequence)}.
970      */
971     public static final String EXTRA_TITLE_BIG = EXTRA_TITLE + ".big";
972 
973     /**
974      * {@link #extras} key: this is the main text payload, as supplied to
975      * {@link Builder#setContentText(CharSequence)}.
976      */
977     public static final String EXTRA_TEXT = "android.text";
978 
979     /**
980      * {@link #extras} key: this is a third line of text, as supplied to
981      * {@link Builder#setSubText(CharSequence)}.
982      */
983     public static final String EXTRA_SUB_TEXT = "android.subText";
984 
985     /**
986      * {@link #extras} key: this is the remote input history, as supplied to
987      * {@link Builder#setRemoteInputHistory(CharSequence[])}.
988      *
989      * Apps can fill this through {@link Builder#setRemoteInputHistory(CharSequence[])}
990      * with the most recent inputs that have been sent through a {@link RemoteInput} of this
991      * Notification and are expected to clear it once the it is no longer relevant (e.g. for chat
992      * notifications once the other party has responded).
993      *
994      * The extra with this key is of type CharSequence[] and contains the most recent entry at
995      * the 0 index, the second most recent at the 1 index, etc.
996      *
997      * @see Builder#setRemoteInputHistory(CharSequence[])
998      */
999     public static final String EXTRA_REMOTE_INPUT_HISTORY = "android.remoteInputHistory";
1000 
1001     /**
1002      * {@link #extras} key: boolean as supplied to
1003      * {@link Builder#setShowRemoteInputSpinner(boolean)}.
1004      *
1005      * If set to true, then the view displaying the remote input history from
1006      * {@link Builder#setRemoteInputHistory(CharSequence[])} will have a progress spinner.
1007      *
1008      * @see Builder#setShowRemoteInputSpinner(boolean)
1009      * @hide
1010      */
1011     public static final String EXTRA_SHOW_REMOTE_INPUT_SPINNER = "android.remoteInputSpinner";
1012 
1013     /**
1014      * {@link #extras} key: boolean as supplied to
1015      * {@link Builder#setHideSmartReplies(boolean)}.
1016      *
1017      * If set to true, then any smart reply buttons will be hidden.
1018      *
1019      * @see Builder#setHideSmartReplies(boolean)
1020      * @hide
1021      */
1022     public static final String EXTRA_HIDE_SMART_REPLIES = "android.hideSmartReplies";
1023 
1024     /**
1025      * {@link #extras} key: this is a small piece of additional text as supplied to
1026      * {@link Builder#setContentInfo(CharSequence)}.
1027      */
1028     public static final String EXTRA_INFO_TEXT = "android.infoText";
1029 
1030     /**
1031      * {@link #extras} key: this is a line of summary information intended to be shown
1032      * alongside expanded notifications, as supplied to (e.g.)
1033      * {@link BigTextStyle#setSummaryText(CharSequence)}.
1034      */
1035     public static final String EXTRA_SUMMARY_TEXT = "android.summaryText";
1036 
1037     /**
1038      * {@link #extras} key: this is the longer text shown in the big form of a
1039      * {@link BigTextStyle} notification, as supplied to
1040      * {@link BigTextStyle#bigText(CharSequence)}.
1041      */
1042     public static final String EXTRA_BIG_TEXT = "android.bigText";
1043 
1044     /**
1045      * {@link #extras} key: this is the resource ID of the notification's main small icon, as
1046      * supplied to {@link Builder#setSmallIcon(int)}.
1047      *
1048      * @deprecated Use {@link #getSmallIcon()}, which supports a wider variety of icon sources.
1049      */
1050     @Deprecated
1051     public static final String EXTRA_SMALL_ICON = "android.icon";
1052 
1053     /**
1054      * {@link #extras} key: this is a bitmap to be used instead of the small icon when showing the
1055      * notification payload, as
1056      * supplied to {@link Builder#setLargeIcon(android.graphics.Bitmap)}.
1057      *
1058      * @deprecated Use {@link #getLargeIcon()}, which supports a wider variety of icon sources.
1059      */
1060     @Deprecated
1061     public static final String EXTRA_LARGE_ICON = "android.largeIcon";
1062 
1063     /**
1064      * {@link #extras} key: this is a bitmap to be used instead of the one from
1065      * {@link Builder#setLargeIcon(android.graphics.Bitmap)} when the notification is
1066      * shown in its expanded form, as supplied to
1067      * {@link BigPictureStyle#bigLargeIcon(android.graphics.Bitmap)}.
1068      */
1069     public static final String EXTRA_LARGE_ICON_BIG = EXTRA_LARGE_ICON + ".big";
1070 
1071     /**
1072      * {@link #extras} key: this is the progress value supplied to
1073      * {@link Builder#setProgress(int, int, boolean)}.
1074      */
1075     public static final String EXTRA_PROGRESS = "android.progress";
1076 
1077     /**
1078      * {@link #extras} key: this is the maximum value supplied to
1079      * {@link Builder#setProgress(int, int, boolean)}.
1080      */
1081     public static final String EXTRA_PROGRESS_MAX = "android.progressMax";
1082 
1083     /**
1084      * {@link #extras} key: whether the progress bar is indeterminate, supplied to
1085      * {@link Builder#setProgress(int, int, boolean)}.
1086      */
1087     public static final String EXTRA_PROGRESS_INDETERMINATE = "android.progressIndeterminate";
1088 
1089     /**
1090      * {@link #extras} key: whether {@link #when} should be shown as a count-up timer (specifically
1091      * a {@link android.widget.Chronometer}) instead of a timestamp, as supplied to
1092      * {@link Builder#setUsesChronometer(boolean)}.
1093      */
1094     public static final String EXTRA_SHOW_CHRONOMETER = "android.showChronometer";
1095 
1096     /**
1097      * {@link #extras} key: whether the chronometer set on the notification should count down
1098      * instead of counting up. Is only relevant if key {@link #EXTRA_SHOW_CHRONOMETER} is present.
1099      * This extra is a boolean. The default is false.
1100      */
1101     public static final String EXTRA_CHRONOMETER_COUNT_DOWN = "android.chronometerCountDown";
1102 
1103     /**
1104      * {@link #extras} key: whether {@link #when} should be shown,
1105      * as supplied to {@link Builder#setShowWhen(boolean)}.
1106      */
1107     public static final String EXTRA_SHOW_WHEN = "android.showWhen";
1108 
1109     /**
1110      * {@link #extras} key: this is a bitmap to be shown in {@link BigPictureStyle} expanded
1111      * notifications, supplied to {@link BigPictureStyle#bigPicture(android.graphics.Bitmap)}.
1112      */
1113     public static final String EXTRA_PICTURE = "android.picture";
1114 
1115     /**
1116      * {@link #extras} key: An array of CharSequences to show in {@link InboxStyle} expanded
1117      * notifications, each of which was supplied to {@link InboxStyle#addLine(CharSequence)}.
1118      */
1119     public static final String EXTRA_TEXT_LINES = "android.textLines";
1120 
1121     /**
1122      * {@link #extras} key: A string representing the name of the specific
1123      * {@link android.app.Notification.Style} used to create this notification.
1124      */
1125     public static final String EXTRA_TEMPLATE = "android.template";
1126 
1127     /**
1128      * {@link #extras} key: A String array containing the people that this notification relates to,
1129      * each of which was supplied to {@link Builder#addPerson(String)}.
1130      *
1131      * @deprecated the actual objects are now in {@link #EXTRA_PEOPLE_LIST}
1132      */
1133     public static final String EXTRA_PEOPLE = "android.people";
1134 
1135     /**
1136      * {@link #extras} key: An arrayList of {@link Person} objects containing the people that
1137      * this notification relates to.
1138      */
1139     public static final String EXTRA_PEOPLE_LIST = "android.people.list";
1140 
1141     /**
1142      * Allow certain system-generated notifications to appear before the device is provisioned.
1143      * Only available to notifications coming from the android package.
1144      * @hide
1145      */
1146     @SystemApi
1147     @RequiresPermission(android.Manifest.permission.NOTIFICATION_DURING_SETUP)
1148     public static final String EXTRA_ALLOW_DURING_SETUP = "android.allowDuringSetup";
1149 
1150     /**
1151      * {@link #extras} key:
1152      * flat {@link String} representation of a {@link android.content.ContentUris content URI}
1153      * pointing to an image that can be displayed in the background when the notification is
1154      * selected. Used on television platforms. The URI must point to an image stream suitable for
1155      * passing into {@link android.graphics.BitmapFactory#decodeStream(java.io.InputStream)
1156      * BitmapFactory.decodeStream}; all other content types will be ignored.
1157      */
1158     public static final String EXTRA_BACKGROUND_IMAGE_URI = "android.backgroundImageUri";
1159 
1160     /**
1161      * {@link #extras} key: A
1162      * {@link android.media.session.MediaSession.Token} associated with a
1163      * {@link android.app.Notification.MediaStyle} notification.
1164      */
1165     public static final String EXTRA_MEDIA_SESSION = "android.mediaSession";
1166 
1167     /**
1168      * {@link #extras} key: the indices of actions to be shown in the compact view,
1169      * as supplied to (e.g.) {@link MediaStyle#setShowActionsInCompactView(int...)}.
1170      */
1171     public static final String EXTRA_COMPACT_ACTIONS = "android.compactActions";
1172 
1173     /**
1174      * {@link #extras} key: the username to be displayed for all messages sent by the user including
1175      * direct replies
1176      * {@link android.app.Notification.MessagingStyle} notification. This extra is a
1177      * {@link CharSequence}
1178      *
1179      * @deprecated use {@link #EXTRA_MESSAGING_PERSON}
1180      */
1181     public static final String EXTRA_SELF_DISPLAY_NAME = "android.selfDisplayName";
1182 
1183     /**
1184      * {@link #extras} key: the person to be displayed for all messages sent by the user including
1185      * direct replies
1186      * {@link android.app.Notification.MessagingStyle} notification. This extra is a
1187      * {@link Person}
1188      */
1189     public static final String EXTRA_MESSAGING_PERSON = "android.messagingUser";
1190 
1191     /**
1192      * {@link #extras} key: a {@link CharSequence} to be displayed as the title to a conversation
1193      * represented by a {@link android.app.Notification.MessagingStyle}
1194      */
1195     public static final String EXTRA_CONVERSATION_TITLE = "android.conversationTitle";
1196 
1197     /**
1198      * {@link #extras} key: an array of {@link android.app.Notification.MessagingStyle.Message}
1199      * bundles provided by a
1200      * {@link android.app.Notification.MessagingStyle} notification. This extra is a parcelable
1201      * array of bundles.
1202      */
1203     public static final String EXTRA_MESSAGES = "android.messages";
1204 
1205     /**
1206      * {@link #extras} key: an array of
1207      * {@link android.app.Notification.MessagingStyle#addHistoricMessage historic}
1208      * {@link android.app.Notification.MessagingStyle.Message} bundles provided by a
1209      * {@link android.app.Notification.MessagingStyle} notification. This extra is a parcelable
1210      * array of bundles.
1211      */
1212     public static final String EXTRA_HISTORIC_MESSAGES = "android.messages.historic";
1213 
1214     /**
1215      * {@link #extras} key: whether the {@link android.app.Notification.MessagingStyle} notification
1216      * represents a group conversation.
1217      */
1218     public static final String EXTRA_IS_GROUP_CONVERSATION = "android.isGroupConversation";
1219 
1220     /**
1221      * {@link #extras} key: whether the notification should be colorized as
1222      * supplied to {@link Builder#setColorized(boolean)}.
1223      */
1224     public static final String EXTRA_COLORIZED = "android.colorized";
1225 
1226     /**
1227      * @hide
1228      */
1229     public static final String EXTRA_BUILDER_APPLICATION_INFO = "android.appInfo";
1230 
1231     /**
1232      * @hide
1233      */
1234     public static final String EXTRA_CONTAINS_CUSTOM_VIEW = "android.contains.customView";
1235 
1236     /**
1237      * @hide
1238      */
1239     public static final String EXTRA_REDUCED_IMAGES = "android.reduced.images";
1240 
1241     /**
1242      * {@link #extras} key: the audio contents of this notification.
1243      *
1244      * This is for use when rendering the notification on an audio-focused interface;
1245      * the audio contents are a complete sound sample that contains the contents/body of the
1246      * notification. This may be used in substitute of a Text-to-Speech reading of the
1247      * notification. For example if the notification represents a voice message this should point
1248      * to the audio of that message.
1249      *
1250      * The data stored under this key should be a String representation of a Uri that contains the
1251      * audio contents in one of the following formats: WAV, PCM 16-bit, AMR-WB.
1252      *
1253      * This extra is unnecessary if you are using {@code MessagingStyle} since each {@code Message}
1254      * has a field for holding data URI. That field can be used for audio.
1255      * See {@code Message#setData}.
1256      *
1257      * Example usage:
1258      * <pre>
1259      * {@code
1260      * Notification.Builder myBuilder = (build your Notification as normal);
1261      * myBuilder.getExtras().putString(EXTRA_AUDIO_CONTENTS_URI, myAudioUri.toString());
1262      * }
1263      * </pre>
1264      */
1265     public static final String EXTRA_AUDIO_CONTENTS_URI = "android.audioContents";
1266 
1267     /** @hide */
1268     @SystemApi
1269     @RequiresPermission(android.Manifest.permission.SUBSTITUTE_NOTIFICATION_APP_NAME)
1270     public static final String EXTRA_SUBSTITUTE_APP_NAME = "android.substName";
1271 
1272     /**
1273      * This is set on the notifications shown by system_server about apps running foreground
1274      * services. It indicates that the notification should be shown
1275      * only if any of the given apps do not already have a properly tagged
1276      * {@link #FLAG_FOREGROUND_SERVICE} notification currently visible to the user.
1277      * This is a string array of all package names of the apps.
1278      * @hide
1279      */
1280     public static final String EXTRA_FOREGROUND_APPS = "android.foregroundApps";
1281 
1282     @UnsupportedAppUsage
1283     private Icon mSmallIcon;
1284     @UnsupportedAppUsage
1285     private Icon mLargeIcon;
1286 
1287     @UnsupportedAppUsage
1288     private String mChannelId;
1289     private long mTimeout;
1290 
1291     private String mShortcutId;
1292     private LocusId mLocusId;
1293     private CharSequence mSettingsText;
1294 
1295     private BubbleMetadata mBubbleMetadata;
1296 
1297     /** @hide */
1298     @IntDef(prefix = { "GROUP_ALERT_" }, value = {
1299             GROUP_ALERT_ALL, GROUP_ALERT_CHILDREN, GROUP_ALERT_SUMMARY
1300     })
1301     @Retention(RetentionPolicy.SOURCE)
1302     public @interface GroupAlertBehavior {}
1303 
1304     /**
1305      * Constant for {@link Builder#setGroupAlertBehavior(int)}, meaning that all notifications in a
1306      * group with sound or vibration ought to make sound or vibrate (respectively), so this
1307      * notification will not be muted when it is in a group.
1308      */
1309     public static final int GROUP_ALERT_ALL = 0;
1310 
1311     /**
1312      * Constant for {@link Builder#setGroupAlertBehavior(int)}, meaning that all children
1313      * notification in a group should be silenced (no sound or vibration) even if they are posted
1314      * to a {@link NotificationChannel} that has sound and/or vibration. Use this constant to
1315      * mute this notification if this notification is a group child. This must be applied to all
1316      * children notifications you want to mute.
1317      *
1318      * <p> For example, you might want to use this constant if you post a number of children
1319      * notifications at once (say, after a periodic sync), and only need to notify the user
1320      * audibly once.
1321      */
1322     public static final int GROUP_ALERT_SUMMARY = 1;
1323 
1324     /**
1325      * Constant for {@link Builder#setGroupAlertBehavior(int)}, meaning that the summary
1326      * notification in a group should be silenced (no sound or vibration) even if they are
1327      * posted to a {@link NotificationChannel} that has sound and/or vibration. Use this constant
1328      * to mute this notification if this notification is a group summary.
1329      *
1330      * <p>For example, you might want to use this constant if only the children notifications
1331      * in your group have content and the summary is only used to visually group notifications
1332      * rather than to alert the user that new information is available.
1333      */
1334     public static final int GROUP_ALERT_CHILDREN = 2;
1335 
1336     private int mGroupAlertBehavior = GROUP_ALERT_ALL;
1337 
1338     /**
1339      * If this notification is being shown as a badge, always show as a number.
1340      */
1341     public static final int BADGE_ICON_NONE = 0;
1342 
1343     /**
1344      * If this notification is being shown as a badge, use the {@link #getSmallIcon()} to
1345      * represent this notification.
1346      */
1347     public static final int BADGE_ICON_SMALL = 1;
1348 
1349     /**
1350      * If this notification is being shown as a badge, use the {@link #getLargeIcon()} to
1351      * represent this notification.
1352      */
1353     public static final int BADGE_ICON_LARGE = 2;
1354     private int mBadgeIcon = BADGE_ICON_NONE;
1355 
1356     /**
1357      * Determines whether the platform can generate contextual actions for a notification.
1358      */
1359     private boolean mAllowSystemGeneratedContextualActions = true;
1360 
1361     /**
1362      * Structure to encapsulate a named action that can be shown as part of this notification.
1363      * It must include an icon, a label, and a {@link PendingIntent} to be fired when the action is
1364      * selected by the user.
1365      * <p>
1366      * Apps should use {@link Notification.Builder#addAction(int, CharSequence, PendingIntent)}
1367      * or {@link Notification.Builder#addAction(Notification.Action)}
1368      * to attach actions.
1369      */
1370     public static class Action implements Parcelable {
1371         /**
1372          * {@link #extras} key: Keys to a {@link Parcelable} {@link ArrayList} of
1373          * {@link RemoteInput}s.
1374          *
1375          * This is intended for {@link RemoteInput}s that only accept data, meaning
1376          * {@link RemoteInput#getAllowFreeFormInput} is false, {@link RemoteInput#getChoices}
1377          * is null or empty, and {@link RemoteInput#getAllowedDataTypes} is non-null and not
1378          * empty. These {@link RemoteInput}s will be ignored by devices that do not
1379          * support non-text-based {@link RemoteInput}s. See {@link Builder#build}.
1380          *
1381          * You can test if a RemoteInput matches these constraints using
1382          * {@link RemoteInput#isDataOnly}.
1383          */
1384         private static final String EXTRA_DATA_ONLY_INPUTS = "android.extra.DATA_ONLY_INPUTS";
1385 
1386         /**
1387          * {@link }: No semantic action defined.
1388          */
1389         public static final int SEMANTIC_ACTION_NONE = 0;
1390 
1391         /**
1392          * {@code SemanticAction}: Reply to a conversation, chat, group, or wherever replies
1393          * may be appropriate.
1394          */
1395         public static final int SEMANTIC_ACTION_REPLY = 1;
1396 
1397         /**
1398          * {@code SemanticAction}: Mark content as read.
1399          */
1400         public static final int SEMANTIC_ACTION_MARK_AS_READ = 2;
1401 
1402         /**
1403          * {@code SemanticAction}: Mark content as unread.
1404          */
1405         public static final int SEMANTIC_ACTION_MARK_AS_UNREAD = 3;
1406 
1407         /**
1408          * {@code SemanticAction}: Delete the content associated with the notification. This
1409          * could mean deleting an email, message, etc.
1410          */
1411         public static final int SEMANTIC_ACTION_DELETE = 4;
1412 
1413         /**
1414          * {@code SemanticAction}: Archive the content associated with the notification. This
1415          * could mean archiving an email, message, etc.
1416          */
1417         public static final int SEMANTIC_ACTION_ARCHIVE = 5;
1418 
1419         /**
1420          * {@code SemanticAction}: Mute the content associated with the notification. This could
1421          * mean silencing a conversation or currently playing media.
1422          */
1423         public static final int SEMANTIC_ACTION_MUTE = 6;
1424 
1425         /**
1426          * {@code SemanticAction}: Unmute the content associated with the notification. This could
1427          * mean un-silencing a conversation or currently playing media.
1428          */
1429         public static final int SEMANTIC_ACTION_UNMUTE = 7;
1430 
1431         /**
1432          * {@code SemanticAction}: Mark content with a thumbs up.
1433          */
1434         public static final int SEMANTIC_ACTION_THUMBS_UP = 8;
1435 
1436         /**
1437          * {@code SemanticAction}: Mark content with a thumbs down.
1438          */
1439         public static final int SEMANTIC_ACTION_THUMBS_DOWN = 9;
1440 
1441         /**
1442          * {@code SemanticAction}: Call a contact, group, etc.
1443          */
1444         public static final int SEMANTIC_ACTION_CALL = 10;
1445 
1446         private final Bundle mExtras;
1447         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
1448         private Icon mIcon;
1449         private final RemoteInput[] mRemoteInputs;
1450         private boolean mAllowGeneratedReplies = true;
1451         private final @SemanticAction int mSemanticAction;
1452         private final boolean mIsContextual;
1453 
1454         /**
1455          * Small icon representing the action.
1456          *
1457          * @deprecated Use {@link Action#getIcon()} instead.
1458          */
1459         @Deprecated
1460         public int icon;
1461 
1462         /**
1463          * Title of the action.
1464          */
1465         public CharSequence title;
1466 
1467         /**
1468          * Intent to send when the user invokes this action. May be null, in which case the action
1469          * may be rendered in a disabled presentation by the system UI.
1470          */
1471         public PendingIntent actionIntent;
1472 
Action(Parcel in)1473         private Action(Parcel in) {
1474             if (in.readInt() != 0) {
1475                 mIcon = Icon.CREATOR.createFromParcel(in);
1476                 if (mIcon.getType() == Icon.TYPE_RESOURCE) {
1477                     icon = mIcon.getResId();
1478                 }
1479             }
1480             title = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
1481             if (in.readInt() == 1) {
1482                 actionIntent = PendingIntent.CREATOR.createFromParcel(in);
1483             }
1484             mExtras = Bundle.setDefusable(in.readBundle(), true);
1485             mRemoteInputs = in.createTypedArray(RemoteInput.CREATOR);
1486             mAllowGeneratedReplies = in.readInt() == 1;
1487             mSemanticAction = in.readInt();
1488             mIsContextual = in.readInt() == 1;
1489         }
1490 
1491         /**
1492          * @deprecated Use {@link android.app.Notification.Action.Builder}.
1493          */
1494         @Deprecated
Action(int icon, CharSequence title, PendingIntent intent)1495         public Action(int icon, CharSequence title, PendingIntent intent) {
1496             this(Icon.createWithResource("", icon), title, intent, new Bundle(), null, true,
1497                     SEMANTIC_ACTION_NONE, false /* isContextual */);
1498         }
1499 
1500         /** 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)1501         private Action(Icon icon, CharSequence title, PendingIntent intent, Bundle extras,
1502                 RemoteInput[] remoteInputs, boolean allowGeneratedReplies,
1503                        @SemanticAction int semanticAction, boolean isContextual) {
1504             this.mIcon = icon;
1505             if (icon != null && icon.getType() == Icon.TYPE_RESOURCE) {
1506                 this.icon = icon.getResId();
1507             }
1508             this.title = title;
1509             this.actionIntent = intent;
1510             this.mExtras = extras != null ? extras : new Bundle();
1511             this.mRemoteInputs = remoteInputs;
1512             this.mAllowGeneratedReplies = allowGeneratedReplies;
1513             this.mSemanticAction = semanticAction;
1514             this.mIsContextual = isContextual;
1515         }
1516 
1517         /**
1518          * Return an icon representing the action.
1519          */
getIcon()1520         public Icon getIcon() {
1521             if (mIcon == null && icon != 0) {
1522                 // you snuck an icon in here without using the builder; let's try to keep it
1523                 mIcon = Icon.createWithResource("", icon);
1524             }
1525             return mIcon;
1526         }
1527 
1528         /**
1529          * Get additional metadata carried around with this Action.
1530          */
getExtras()1531         public Bundle getExtras() {
1532             return mExtras;
1533         }
1534 
1535         /**
1536          * Return whether the platform should automatically generate possible replies for this
1537          * {@link Action}
1538          */
getAllowGeneratedReplies()1539         public boolean getAllowGeneratedReplies() {
1540             return mAllowGeneratedReplies;
1541         }
1542 
1543         /**
1544          * Get the list of inputs to be collected from the user when this action is sent.
1545          * May return null if no remote inputs were added. Only returns inputs which accept
1546          * a text input. For inputs which only accept data use {@link #getDataOnlyRemoteInputs}.
1547          */
getRemoteInputs()1548         public RemoteInput[] getRemoteInputs() {
1549             return mRemoteInputs;
1550         }
1551 
1552         /**
1553          * Returns the {@code SemanticAction} associated with this {@link Action}. A
1554          * {@code SemanticAction} denotes what an {@link Action}'s {@link PendingIntent} will do
1555          * (eg. reply, mark as read, delete, etc).
1556          */
getSemanticAction()1557         public @SemanticAction int getSemanticAction() {
1558             return mSemanticAction;
1559         }
1560 
1561         /**
1562          * Returns whether this is a contextual Action, i.e. whether the action is dependent on the
1563          * notification message body. An example of a contextual action could be an action opening a
1564          * map application with an address shown in the notification.
1565          */
isContextual()1566         public boolean isContextual() {
1567             return mIsContextual;
1568         }
1569 
1570         /**
1571          * Get the list of inputs to be collected from the user that ONLY accept data when this
1572          * action is sent. These remote inputs are guaranteed to return true on a call to
1573          * {@link RemoteInput#isDataOnly}.
1574          *
1575          * Returns null if there are no data-only remote inputs.
1576          *
1577          * This method exists so that legacy RemoteInput collectors that pre-date the addition
1578          * of non-textual RemoteInputs do not access these remote inputs.
1579          */
getDataOnlyRemoteInputs()1580         public RemoteInput[] getDataOnlyRemoteInputs() {
1581             return (RemoteInput[]) mExtras.getParcelableArray(EXTRA_DATA_ONLY_INPUTS);
1582         }
1583 
1584         /**
1585          * Builder class for {@link Action} objects.
1586          */
1587         public static final class Builder {
1588             @Nullable private final Icon mIcon;
1589             @Nullable private final CharSequence mTitle;
1590             @Nullable private final PendingIntent mIntent;
1591             private boolean mAllowGeneratedReplies = true;
1592             @NonNull private final Bundle mExtras;
1593             @Nullable private ArrayList<RemoteInput> mRemoteInputs;
1594             private @SemanticAction int mSemanticAction;
1595             private boolean mIsContextual;
1596 
1597             /**
1598              * Construct a new builder for {@link Action} object.
1599              * @param icon icon to show for this action
1600              * @param title the title of the action
1601              * @param intent the {@link PendingIntent} to fire when users trigger this action
1602              */
1603             @Deprecated
Builder(int icon, CharSequence title, PendingIntent intent)1604             public Builder(int icon, CharSequence title, PendingIntent intent) {
1605                 this(Icon.createWithResource("", icon), title, intent);
1606             }
1607 
1608             /**
1609              * Construct a new builder for {@link Action} object.
1610              * @param icon icon to show for this action
1611              * @param title the title of the action
1612              * @param intent the {@link PendingIntent} to fire when users trigger this action
1613              */
Builder(Icon icon, CharSequence title, PendingIntent intent)1614             public Builder(Icon icon, CharSequence title, PendingIntent intent) {
1615                 this(icon, title, intent, new Bundle(), null, true, SEMANTIC_ACTION_NONE);
1616             }
1617 
1618             /**
1619              * Construct a new builder for {@link Action} object using the fields from an
1620              * {@link Action}.
1621              * @param action the action to read fields from.
1622              */
Builder(Action action)1623             public Builder(Action action) {
1624                 this(action.getIcon(), action.title, action.actionIntent,
1625                         new Bundle(action.mExtras), action.getRemoteInputs(),
1626                         action.getAllowGeneratedReplies(), action.getSemanticAction());
1627             }
1628 
Builder(@ullable Icon icon, @Nullable CharSequence title, @Nullable PendingIntent intent, @NonNull Bundle extras, @Nullable RemoteInput[] remoteInputs, boolean allowGeneratedReplies, @SemanticAction int semanticAction)1629             private Builder(@Nullable Icon icon, @Nullable CharSequence title,
1630                     @Nullable PendingIntent intent, @NonNull Bundle extras,
1631                     @Nullable RemoteInput[] remoteInputs, boolean allowGeneratedReplies,
1632                     @SemanticAction int semanticAction) {
1633                 mIcon = icon;
1634                 mTitle = title;
1635                 mIntent = intent;
1636                 mExtras = extras;
1637                 if (remoteInputs != null) {
1638                     mRemoteInputs = new ArrayList<RemoteInput>(remoteInputs.length);
1639                     Collections.addAll(mRemoteInputs, remoteInputs);
1640                 }
1641                 mAllowGeneratedReplies = allowGeneratedReplies;
1642                 mSemanticAction = semanticAction;
1643             }
1644 
1645             /**
1646              * Merge additional metadata into this builder.
1647              *
1648              * <p>Values within the Bundle will replace existing extras values in this Builder.
1649              *
1650              * @see Notification.Action#extras
1651              */
1652             @NonNull
addExtras(Bundle extras)1653             public Builder addExtras(Bundle extras) {
1654                 if (extras != null) {
1655                     mExtras.putAll(extras);
1656                 }
1657                 return this;
1658             }
1659 
1660             /**
1661              * Get the metadata Bundle used by this Builder.
1662              *
1663              * <p>The returned Bundle is shared with this Builder.
1664              */
1665             @NonNull
getExtras()1666             public Bundle getExtras() {
1667                 return mExtras;
1668             }
1669 
1670             /**
1671              * Add an input to be collected from the user when this action is sent.
1672              * Response values can be retrieved from the fired intent by using the
1673              * {@link RemoteInput#getResultsFromIntent} function.
1674              * @param remoteInput a {@link RemoteInput} to add to the action
1675              * @return this object for method chaining
1676              */
1677             @NonNull
addRemoteInput(RemoteInput remoteInput)1678             public Builder addRemoteInput(RemoteInput remoteInput) {
1679                 if (mRemoteInputs == null) {
1680                     mRemoteInputs = new ArrayList<RemoteInput>();
1681                 }
1682                 mRemoteInputs.add(remoteInput);
1683                 return this;
1684             }
1685 
1686             /**
1687              * Set whether the platform should automatically generate possible replies to add to
1688              * {@link RemoteInput#getChoices()}. If the {@link Action} doesn't have a
1689              * {@link RemoteInput}, this has no effect.
1690              * @param allowGeneratedReplies {@code true} to allow generated replies, {@code false}
1691              * otherwise
1692              * @return this object for method chaining
1693              * The default value is {@code true}
1694              */
1695             @NonNull
setAllowGeneratedReplies(boolean allowGeneratedReplies)1696             public Builder setAllowGeneratedReplies(boolean allowGeneratedReplies) {
1697                 mAllowGeneratedReplies = allowGeneratedReplies;
1698                 return this;
1699             }
1700 
1701             /**
1702              * Sets the {@code SemanticAction} for this {@link Action}. A
1703              * {@code SemanticAction} denotes what an {@link Action}'s
1704              * {@link PendingIntent} will do (eg. reply, mark as read, delete, etc).
1705              * @param semanticAction a SemanticAction defined within {@link Action} with
1706              * {@code SEMANTIC_ACTION_} prefixes
1707              * @return this object for method chaining
1708              */
1709             @NonNull
setSemanticAction(@emanticAction int semanticAction)1710             public Builder setSemanticAction(@SemanticAction int semanticAction) {
1711                 mSemanticAction = semanticAction;
1712                 return this;
1713             }
1714 
1715             /**
1716              * Sets whether this {@link Action} is a contextual action, i.e. whether the action is
1717              * dependent on the notification message body. An example of a contextual action could
1718              * be an action opening a map application with an address shown in the notification.
1719              */
1720             @NonNull
setContextual(boolean isContextual)1721             public Builder setContextual(boolean isContextual) {
1722                 mIsContextual = isContextual;
1723                 return this;
1724             }
1725 
1726             /**
1727              * Apply an extender to this action builder. Extenders may be used to add
1728              * metadata or change options on this builder.
1729              */
1730             @NonNull
extend(Extender extender)1731             public Builder extend(Extender extender) {
1732                 extender.extend(this);
1733                 return this;
1734             }
1735 
1736             /**
1737              * Throws an NPE if we are building a contextual action missing one of the fields
1738              * necessary to display the action.
1739              */
checkContextualActionNullFields()1740             private void checkContextualActionNullFields() {
1741                 if (!mIsContextual) return;
1742 
1743                 if (mIcon == null) {
1744                     throw new NullPointerException("Contextual Actions must contain a valid icon");
1745                 }
1746 
1747                 if (mIntent == null) {
1748                     throw new NullPointerException(
1749                             "Contextual Actions must contain a valid PendingIntent");
1750                 }
1751             }
1752 
1753             /**
1754              * Combine all of the options that have been set and return a new {@link Action}
1755              * object.
1756              * @return the built action
1757              */
1758             @NonNull
build()1759             public Action build() {
1760                 checkContextualActionNullFields();
1761 
1762                 ArrayList<RemoteInput> dataOnlyInputs = new ArrayList<>();
1763                 RemoteInput[] previousDataInputs =
1764                     (RemoteInput[]) mExtras.getParcelableArray(EXTRA_DATA_ONLY_INPUTS);
1765                 if (previousDataInputs != null) {
1766                     for (RemoteInput input : previousDataInputs) {
1767                         dataOnlyInputs.add(input);
1768                     }
1769                 }
1770                 List<RemoteInput> textInputs = new ArrayList<>();
1771                 if (mRemoteInputs != null) {
1772                     for (RemoteInput input : mRemoteInputs) {
1773                         if (input.isDataOnly()) {
1774                             dataOnlyInputs.add(input);
1775                         } else {
1776                             textInputs.add(input);
1777                         }
1778                     }
1779                 }
1780                 if (!dataOnlyInputs.isEmpty()) {
1781                     RemoteInput[] dataInputsArr =
1782                             dataOnlyInputs.toArray(new RemoteInput[dataOnlyInputs.size()]);
1783                     mExtras.putParcelableArray(EXTRA_DATA_ONLY_INPUTS, dataInputsArr);
1784                 }
1785                 RemoteInput[] textInputsArr = textInputs.isEmpty()
1786                         ? null : textInputs.toArray(new RemoteInput[textInputs.size()]);
1787                 return new Action(mIcon, mTitle, mIntent, mExtras, textInputsArr,
1788                         mAllowGeneratedReplies, mSemanticAction, mIsContextual);
1789             }
1790         }
1791 
1792         @Override
clone()1793         public Action clone() {
1794             return new Action(
1795                     getIcon(),
1796                     title,
1797                     actionIntent, // safe to alias
1798                     mExtras == null ? new Bundle() : new Bundle(mExtras),
1799                     getRemoteInputs(),
1800                     getAllowGeneratedReplies(),
1801                     getSemanticAction(),
1802                     isContextual());
1803         }
1804 
1805         @Override
describeContents()1806         public int describeContents() {
1807             return 0;
1808         }
1809 
1810         @Override
writeToParcel(Parcel out, int flags)1811         public void writeToParcel(Parcel out, int flags) {
1812             final Icon ic = getIcon();
1813             if (ic != null) {
1814                 out.writeInt(1);
1815                 ic.writeToParcel(out, 0);
1816             } else {
1817                 out.writeInt(0);
1818             }
1819             TextUtils.writeToParcel(title, out, flags);
1820             if (actionIntent != null) {
1821                 out.writeInt(1);
1822                 actionIntent.writeToParcel(out, flags);
1823             } else {
1824                 out.writeInt(0);
1825             }
1826             out.writeBundle(mExtras);
1827             out.writeTypedArray(mRemoteInputs, flags);
1828             out.writeInt(mAllowGeneratedReplies ? 1 : 0);
1829             out.writeInt(mSemanticAction);
1830             out.writeInt(mIsContextual ? 1 : 0);
1831         }
1832 
1833         public static final @android.annotation.NonNull Parcelable.Creator<Action> CREATOR =
1834                 new Parcelable.Creator<Action>() {
1835             public Action createFromParcel(Parcel in) {
1836                 return new Action(in);
1837             }
1838             public Action[] newArray(int size) {
1839                 return new Action[size];
1840             }
1841         };
1842 
1843         /**
1844          * Extender interface for use with {@link Builder#extend}. Extenders may be used to add
1845          * metadata or change options on an action builder.
1846          */
1847         public interface Extender {
1848             /**
1849              * Apply this extender to a notification action builder.
1850              * @param builder the builder to be modified.
1851              * @return the build object for chaining.
1852              */
extend(Builder builder)1853             public Builder extend(Builder builder);
1854         }
1855 
1856         /**
1857          * Wearable extender for notification actions. To add extensions to an action,
1858          * create a new {@link android.app.Notification.Action.WearableExtender} object using
1859          * the {@code WearableExtender()} constructor and apply it to a
1860          * {@link android.app.Notification.Action.Builder} using
1861          * {@link android.app.Notification.Action.Builder#extend}.
1862          *
1863          * <pre class="prettyprint">
1864          * Notification.Action action = new Notification.Action.Builder(
1865          *         R.drawable.archive_all, "Archive all", actionIntent)
1866          *         .extend(new Notification.Action.WearableExtender()
1867          *                 .setAvailableOffline(false))
1868          *         .build();</pre>
1869          */
1870         public static final class WearableExtender implements Extender {
1871             /** Notification action extra which contains wearable extensions */
1872             private static final String EXTRA_WEARABLE_EXTENSIONS = "android.wearable.EXTENSIONS";
1873 
1874             // Keys within EXTRA_WEARABLE_EXTENSIONS for wearable options.
1875             private static final String KEY_FLAGS = "flags";
1876             private static final String KEY_IN_PROGRESS_LABEL = "inProgressLabel";
1877             private static final String KEY_CONFIRM_LABEL = "confirmLabel";
1878             private static final String KEY_CANCEL_LABEL = "cancelLabel";
1879 
1880             // Flags bitwise-ored to mFlags
1881             private static final int FLAG_AVAILABLE_OFFLINE = 0x1;
1882             private static final int FLAG_HINT_LAUNCHES_ACTIVITY = 1 << 1;
1883             private static final int FLAG_HINT_DISPLAY_INLINE = 1 << 2;
1884 
1885             // Default value for flags integer
1886             private static final int DEFAULT_FLAGS = FLAG_AVAILABLE_OFFLINE;
1887 
1888             private int mFlags = DEFAULT_FLAGS;
1889 
1890             private CharSequence mInProgressLabel;
1891             private CharSequence mConfirmLabel;
1892             private CharSequence mCancelLabel;
1893 
1894             /**
1895              * Create a {@link android.app.Notification.Action.WearableExtender} with default
1896              * options.
1897              */
WearableExtender()1898             public WearableExtender() {
1899             }
1900 
1901             /**
1902              * Create a {@link android.app.Notification.Action.WearableExtender} by reading
1903              * wearable options present in an existing notification action.
1904              * @param action the notification action to inspect.
1905              */
WearableExtender(Action action)1906             public WearableExtender(Action action) {
1907                 Bundle wearableBundle = action.getExtras().getBundle(EXTRA_WEARABLE_EXTENSIONS);
1908                 if (wearableBundle != null) {
1909                     mFlags = wearableBundle.getInt(KEY_FLAGS, DEFAULT_FLAGS);
1910                     mInProgressLabel = wearableBundle.getCharSequence(KEY_IN_PROGRESS_LABEL);
1911                     mConfirmLabel = wearableBundle.getCharSequence(KEY_CONFIRM_LABEL);
1912                     mCancelLabel = wearableBundle.getCharSequence(KEY_CANCEL_LABEL);
1913                 }
1914             }
1915 
1916             /**
1917              * Apply wearable extensions to a notification action that is being built. This is
1918              * typically called by the {@link android.app.Notification.Action.Builder#extend}
1919              * method of {@link android.app.Notification.Action.Builder}.
1920              */
1921             @Override
extend(Action.Builder builder)1922             public Action.Builder extend(Action.Builder builder) {
1923                 Bundle wearableBundle = new Bundle();
1924 
1925                 if (mFlags != DEFAULT_FLAGS) {
1926                     wearableBundle.putInt(KEY_FLAGS, mFlags);
1927                 }
1928                 if (mInProgressLabel != null) {
1929                     wearableBundle.putCharSequence(KEY_IN_PROGRESS_LABEL, mInProgressLabel);
1930                 }
1931                 if (mConfirmLabel != null) {
1932                     wearableBundle.putCharSequence(KEY_CONFIRM_LABEL, mConfirmLabel);
1933                 }
1934                 if (mCancelLabel != null) {
1935                     wearableBundle.putCharSequence(KEY_CANCEL_LABEL, mCancelLabel);
1936                 }
1937 
1938                 builder.getExtras().putBundle(EXTRA_WEARABLE_EXTENSIONS, wearableBundle);
1939                 return builder;
1940             }
1941 
1942             @Override
clone()1943             public WearableExtender clone() {
1944                 WearableExtender that = new WearableExtender();
1945                 that.mFlags = this.mFlags;
1946                 that.mInProgressLabel = this.mInProgressLabel;
1947                 that.mConfirmLabel = this.mConfirmLabel;
1948                 that.mCancelLabel = this.mCancelLabel;
1949                 return that;
1950             }
1951 
1952             /**
1953              * Set whether this action is available when the wearable device is not connected to
1954              * a companion device. The user can still trigger this action when the wearable device is
1955              * offline, but a visual hint will indicate that the action may not be available.
1956              * Defaults to true.
1957              */
setAvailableOffline(boolean availableOffline)1958             public WearableExtender setAvailableOffline(boolean availableOffline) {
1959                 setFlag(FLAG_AVAILABLE_OFFLINE, availableOffline);
1960                 return this;
1961             }
1962 
1963             /**
1964              * Get whether this action is available when the wearable device is not connected to
1965              * a companion device. The user can still trigger this action when the wearable device is
1966              * offline, but a visual hint will indicate that the action may not be available.
1967              * Defaults to true.
1968              */
isAvailableOffline()1969             public boolean isAvailableOffline() {
1970                 return (mFlags & FLAG_AVAILABLE_OFFLINE) != 0;
1971             }
1972 
setFlag(int mask, boolean value)1973             private void setFlag(int mask, boolean value) {
1974                 if (value) {
1975                     mFlags |= mask;
1976                 } else {
1977                     mFlags &= ~mask;
1978                 }
1979             }
1980 
1981             /**
1982              * Set a label to display while the wearable is preparing to automatically execute the
1983              * action. This is usually a 'ing' verb ending in ellipsis like "Sending..."
1984              *
1985              * @param label the label to display while the action is being prepared to execute
1986              * @return this object for method chaining
1987              */
1988             @Deprecated
setInProgressLabel(CharSequence label)1989             public WearableExtender setInProgressLabel(CharSequence label) {
1990                 mInProgressLabel = label;
1991                 return this;
1992             }
1993 
1994             /**
1995              * Get the label to display while the wearable is preparing to automatically execute
1996              * the action. This is usually a 'ing' verb ending in ellipsis like "Sending..."
1997              *
1998              * @return the label to display while the action is being prepared to execute
1999              */
2000             @Deprecated
getInProgressLabel()2001             public CharSequence getInProgressLabel() {
2002                 return mInProgressLabel;
2003             }
2004 
2005             /**
2006              * Set a label to display to confirm that the action should be executed.
2007              * This is usually an imperative verb like "Send".
2008              *
2009              * @param label the label to confirm the action should be executed
2010              * @return this object for method chaining
2011              */
2012             @Deprecated
setConfirmLabel(CharSequence label)2013             public WearableExtender setConfirmLabel(CharSequence label) {
2014                 mConfirmLabel = label;
2015                 return this;
2016             }
2017 
2018             /**
2019              * Get the label to display to confirm that the action should be executed.
2020              * This is usually an imperative verb like "Send".
2021              *
2022              * @return the label to confirm the action should be executed
2023              */
2024             @Deprecated
getConfirmLabel()2025             public CharSequence getConfirmLabel() {
2026                 return mConfirmLabel;
2027             }
2028 
2029             /**
2030              * Set a label to display to cancel the action.
2031              * This is usually an imperative verb, like "Cancel".
2032              *
2033              * @param label the label to display to cancel the action
2034              * @return this object for method chaining
2035              */
2036             @Deprecated
setCancelLabel(CharSequence label)2037             public WearableExtender setCancelLabel(CharSequence label) {
2038                 mCancelLabel = label;
2039                 return this;
2040             }
2041 
2042             /**
2043              * Get the label to display to cancel the action.
2044              * This is usually an imperative verb like "Cancel".
2045              *
2046              * @return the label to display to cancel the action
2047              */
2048             @Deprecated
getCancelLabel()2049             public CharSequence getCancelLabel() {
2050                 return mCancelLabel;
2051             }
2052 
2053             /**
2054              * Set a hint that this Action will launch an {@link Activity} directly, telling the
2055              * platform that it can generate the appropriate transitions.
2056              * @param hintLaunchesActivity {@code true} if the content intent will launch
2057              * an activity and transitions should be generated, false otherwise.
2058              * @return this object for method chaining
2059              */
setHintLaunchesActivity( boolean hintLaunchesActivity)2060             public WearableExtender setHintLaunchesActivity(
2061                     boolean hintLaunchesActivity) {
2062                 setFlag(FLAG_HINT_LAUNCHES_ACTIVITY, hintLaunchesActivity);
2063                 return this;
2064             }
2065 
2066             /**
2067              * Get a hint that this Action will launch an {@link Activity} directly, telling the
2068              * platform that it can generate the appropriate transitions
2069              * @return {@code true} if the content intent will launch an activity and transitions
2070              * should be generated, false otherwise. The default value is {@code false} if this was
2071              * never set.
2072              */
getHintLaunchesActivity()2073             public boolean getHintLaunchesActivity() {
2074                 return (mFlags & FLAG_HINT_LAUNCHES_ACTIVITY) != 0;
2075             }
2076 
2077             /**
2078              * Set a hint that this Action should be displayed inline.
2079              *
2080              * @param hintDisplayInline {@code true} if action should be displayed inline, false
2081              *        otherwise
2082              * @return this object for method chaining
2083              */
setHintDisplayActionInline( boolean hintDisplayInline)2084             public WearableExtender setHintDisplayActionInline(
2085                     boolean hintDisplayInline) {
2086                 setFlag(FLAG_HINT_DISPLAY_INLINE, hintDisplayInline);
2087                 return this;
2088             }
2089 
2090             /**
2091              * Get a hint that this Action should be displayed inline.
2092              *
2093              * @return {@code true} if the Action should be displayed inline, {@code false}
2094              *         otherwise. The default value is {@code false} if this was never set.
2095              */
getHintDisplayActionInline()2096             public boolean getHintDisplayActionInline() {
2097                 return (mFlags & FLAG_HINT_DISPLAY_INLINE) != 0;
2098             }
2099         }
2100 
2101         /**
2102          * Provides meaning to an {@link Action} that hints at what the associated
2103          * {@link PendingIntent} will do. For example, an {@link Action} with a
2104          * {@link PendingIntent} that replies to a text message notification may have the
2105          * {@link #SEMANTIC_ACTION_REPLY} {@code SemanticAction} set within it.
2106          *
2107          * @hide
2108          */
2109         @IntDef(prefix = { "SEMANTIC_ACTION_" }, value = {
2110                 SEMANTIC_ACTION_NONE,
2111                 SEMANTIC_ACTION_REPLY,
2112                 SEMANTIC_ACTION_MARK_AS_READ,
2113                 SEMANTIC_ACTION_MARK_AS_UNREAD,
2114                 SEMANTIC_ACTION_DELETE,
2115                 SEMANTIC_ACTION_ARCHIVE,
2116                 SEMANTIC_ACTION_MUTE,
2117                 SEMANTIC_ACTION_UNMUTE,
2118                 SEMANTIC_ACTION_THUMBS_UP,
2119                 SEMANTIC_ACTION_THUMBS_DOWN,
2120                 SEMANTIC_ACTION_CALL
2121         })
2122         @Retention(RetentionPolicy.SOURCE)
2123         public @interface SemanticAction {}
2124     }
2125 
2126     /**
2127      * Array of all {@link Action} structures attached to this notification by
2128      * {@link Builder#addAction(int, CharSequence, PendingIntent)}. Mostly useful for instances of
2129      * {@link android.service.notification.NotificationListenerService} that provide an alternative
2130      * interface for invoking actions.
2131      */
2132     public Action[] actions;
2133 
2134     /**
2135      * Replacement version of this notification whose content will be shown
2136      * in an insecure context such as atop a secure keyguard. See {@link #visibility}
2137      * and {@link #VISIBILITY_PUBLIC}.
2138      */
2139     public Notification publicVersion;
2140 
2141     /**
2142      * Constructs a Notification object with default values.
2143      * You might want to consider using {@link Builder} instead.
2144      */
Notification()2145     public Notification()
2146     {
2147         this.when = System.currentTimeMillis();
2148         this.creationTime = System.currentTimeMillis();
2149         this.priority = PRIORITY_DEFAULT;
2150     }
2151 
2152     /**
2153      * @hide
2154      */
2155     @UnsupportedAppUsage
Notification(Context context, int icon, CharSequence tickerText, long when, CharSequence contentTitle, CharSequence contentText, Intent contentIntent)2156     public Notification(Context context, int icon, CharSequence tickerText, long when,
2157             CharSequence contentTitle, CharSequence contentText, Intent contentIntent)
2158     {
2159         new Builder(context)
2160                 .setWhen(when)
2161                 .setSmallIcon(icon)
2162                 .setTicker(tickerText)
2163                 .setContentTitle(contentTitle)
2164                 .setContentText(contentText)
2165                 .setContentIntent(PendingIntent.getActivity(context, 0, contentIntent, 0))
2166                 .buildInto(this);
2167     }
2168 
2169     /**
2170      * Constructs a Notification object with the information needed to
2171      * have a status bar icon without the standard expanded view.
2172      *
2173      * @param icon          The resource id of the icon to put in the status bar.
2174      * @param tickerText    The text that flows by in the status bar when the notification first
2175      *                      activates.
2176      * @param when          The time to show in the time field.  In the System.currentTimeMillis
2177      *                      timebase.
2178      *
2179      * @deprecated Use {@link Builder} instead.
2180      */
2181     @Deprecated
Notification(int icon, CharSequence tickerText, long when)2182     public Notification(int icon, CharSequence tickerText, long when)
2183     {
2184         this.icon = icon;
2185         this.tickerText = tickerText;
2186         this.when = when;
2187         this.creationTime = System.currentTimeMillis();
2188     }
2189 
2190     /**
2191      * Unflatten the notification from a parcel.
2192      */
2193     @SuppressWarnings("unchecked")
Notification(Parcel parcel)2194     public Notification(Parcel parcel) {
2195         // IMPORTANT: Add unmarshaling code in readFromParcel as the pending
2196         // intents in extras are always written as the last entry.
2197         readFromParcelImpl(parcel);
2198         // Must be read last!
2199         allPendingIntents = (ArraySet<PendingIntent>) parcel.readArraySet(null);
2200     }
2201 
readFromParcelImpl(Parcel parcel)2202     private void readFromParcelImpl(Parcel parcel)
2203     {
2204         int version = parcel.readInt();
2205 
2206         mWhitelistToken = parcel.readStrongBinder();
2207         if (mWhitelistToken == null) {
2208             mWhitelistToken = processWhitelistToken;
2209         }
2210         // Propagate this token to all pending intents that are unmarshalled from the parcel.
2211         parcel.setClassCookie(PendingIntent.class, mWhitelistToken);
2212 
2213         when = parcel.readLong();
2214         creationTime = parcel.readLong();
2215         if (parcel.readInt() != 0) {
2216             mSmallIcon = Icon.CREATOR.createFromParcel(parcel);
2217             if (mSmallIcon.getType() == Icon.TYPE_RESOURCE) {
2218                 icon = mSmallIcon.getResId();
2219             }
2220         }
2221         number = parcel.readInt();
2222         if (parcel.readInt() != 0) {
2223             contentIntent = PendingIntent.CREATOR.createFromParcel(parcel);
2224         }
2225         if (parcel.readInt() != 0) {
2226             deleteIntent = PendingIntent.CREATOR.createFromParcel(parcel);
2227         }
2228         if (parcel.readInt() != 0) {
2229             tickerText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
2230         }
2231         if (parcel.readInt() != 0) {
2232             tickerView = RemoteViews.CREATOR.createFromParcel(parcel);
2233         }
2234         if (parcel.readInt() != 0) {
2235             contentView = RemoteViews.CREATOR.createFromParcel(parcel);
2236         }
2237         if (parcel.readInt() != 0) {
2238             mLargeIcon = Icon.CREATOR.createFromParcel(parcel);
2239         }
2240         defaults = parcel.readInt();
2241         flags = parcel.readInt();
2242         if (parcel.readInt() != 0) {
2243             sound = Uri.CREATOR.createFromParcel(parcel);
2244         }
2245 
2246         audioStreamType = parcel.readInt();
2247         if (parcel.readInt() != 0) {
2248             audioAttributes = AudioAttributes.CREATOR.createFromParcel(parcel);
2249         }
2250         vibrate = parcel.createLongArray();
2251         ledARGB = parcel.readInt();
2252         ledOnMS = parcel.readInt();
2253         ledOffMS = parcel.readInt();
2254         iconLevel = parcel.readInt();
2255 
2256         if (parcel.readInt() != 0) {
2257             fullScreenIntent = PendingIntent.CREATOR.createFromParcel(parcel);
2258         }
2259 
2260         priority = parcel.readInt();
2261 
2262         category = parcel.readString();
2263 
2264         mGroupKey = parcel.readString();
2265 
2266         mSortKey = parcel.readString();
2267 
2268         extras = Bundle.setDefusable(parcel.readBundle(), true); // may be null
2269         fixDuplicateExtras();
2270 
2271         actions = parcel.createTypedArray(Action.CREATOR); // may be null
2272 
2273         if (parcel.readInt() != 0) {
2274             bigContentView = RemoteViews.CREATOR.createFromParcel(parcel);
2275         }
2276 
2277         if (parcel.readInt() != 0) {
2278             headsUpContentView = RemoteViews.CREATOR.createFromParcel(parcel);
2279         }
2280 
2281         visibility = parcel.readInt();
2282 
2283         if (parcel.readInt() != 0) {
2284             publicVersion = Notification.CREATOR.createFromParcel(parcel);
2285         }
2286 
2287         color = parcel.readInt();
2288 
2289         if (parcel.readInt() != 0) {
2290             mChannelId = parcel.readString();
2291         }
2292         mTimeout = parcel.readLong();
2293 
2294         if (parcel.readInt() != 0) {
2295             mShortcutId = parcel.readString();
2296         }
2297 
2298         if (parcel.readInt() != 0) {
2299             mLocusId = LocusId.CREATOR.createFromParcel(parcel);
2300         }
2301 
2302         mBadgeIcon = parcel.readInt();
2303 
2304         if (parcel.readInt() != 0) {
2305             mSettingsText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
2306         }
2307 
2308         mGroupAlertBehavior = parcel.readInt();
2309         if (parcel.readInt() != 0) {
2310             mBubbleMetadata = BubbleMetadata.CREATOR.createFromParcel(parcel);
2311         }
2312 
2313         mAllowSystemGeneratedContextualActions = parcel.readBoolean();
2314     }
2315 
2316     @Override
clone()2317     public Notification clone() {
2318         Notification that = new Notification();
2319         cloneInto(that, true);
2320         return that;
2321     }
2322 
2323     /**
2324      * Copy all (or if heavy is false, all except Bitmaps and RemoteViews) members
2325      * of this into that.
2326      * @hide
2327      */
cloneInto(Notification that, boolean heavy)2328     public void cloneInto(Notification that, boolean heavy) {
2329         that.mWhitelistToken = this.mWhitelistToken;
2330         that.when = this.when;
2331         that.creationTime = this.creationTime;
2332         that.mSmallIcon = this.mSmallIcon;
2333         that.number = this.number;
2334 
2335         // PendingIntents are global, so there's no reason (or way) to clone them.
2336         that.contentIntent = this.contentIntent;
2337         that.deleteIntent = this.deleteIntent;
2338         that.fullScreenIntent = this.fullScreenIntent;
2339 
2340         if (this.tickerText != null) {
2341             that.tickerText = this.tickerText.toString();
2342         }
2343         if (heavy && this.tickerView != null) {
2344             that.tickerView = this.tickerView.clone();
2345         }
2346         if (heavy && this.contentView != null) {
2347             that.contentView = this.contentView.clone();
2348         }
2349         if (heavy && this.mLargeIcon != null) {
2350             that.mLargeIcon = this.mLargeIcon;
2351         }
2352         that.iconLevel = this.iconLevel;
2353         that.sound = this.sound; // android.net.Uri is immutable
2354         that.audioStreamType = this.audioStreamType;
2355         if (this.audioAttributes != null) {
2356             that.audioAttributes = new AudioAttributes.Builder(this.audioAttributes).build();
2357         }
2358 
2359         final long[] vibrate = this.vibrate;
2360         if (vibrate != null) {
2361             final int N = vibrate.length;
2362             final long[] vib = that.vibrate = new long[N];
2363             System.arraycopy(vibrate, 0, vib, 0, N);
2364         }
2365 
2366         that.ledARGB = this.ledARGB;
2367         that.ledOnMS = this.ledOnMS;
2368         that.ledOffMS = this.ledOffMS;
2369         that.defaults = this.defaults;
2370 
2371         that.flags = this.flags;
2372 
2373         that.priority = this.priority;
2374 
2375         that.category = this.category;
2376 
2377         that.mGroupKey = this.mGroupKey;
2378 
2379         that.mSortKey = this.mSortKey;
2380 
2381         if (this.extras != null) {
2382             try {
2383                 that.extras = new Bundle(this.extras);
2384                 // will unparcel
2385                 that.extras.size();
2386             } catch (BadParcelableException e) {
2387                 Log.e(TAG, "could not unparcel extras from notification: " + this, e);
2388                 that.extras = null;
2389             }
2390         }
2391 
2392         if (!ArrayUtils.isEmpty(allPendingIntents)) {
2393             that.allPendingIntents = new ArraySet<>(allPendingIntents);
2394         }
2395 
2396         if (this.actions != null) {
2397             that.actions = new Action[this.actions.length];
2398             for(int i=0; i<this.actions.length; i++) {
2399                 if ( this.actions[i] != null) {
2400                     that.actions[i] = this.actions[i].clone();
2401                 }
2402             }
2403         }
2404 
2405         if (heavy && this.bigContentView != null) {
2406             that.bigContentView = this.bigContentView.clone();
2407         }
2408 
2409         if (heavy && this.headsUpContentView != null) {
2410             that.headsUpContentView = this.headsUpContentView.clone();
2411         }
2412 
2413         that.visibility = this.visibility;
2414 
2415         if (this.publicVersion != null) {
2416             that.publicVersion = new Notification();
2417             this.publicVersion.cloneInto(that.publicVersion, heavy);
2418         }
2419 
2420         that.color = this.color;
2421 
2422         that.mChannelId = this.mChannelId;
2423         that.mTimeout = this.mTimeout;
2424         that.mShortcutId = this.mShortcutId;
2425         that.mLocusId = this.mLocusId;
2426         that.mBadgeIcon = this.mBadgeIcon;
2427         that.mSettingsText = this.mSettingsText;
2428         that.mGroupAlertBehavior = this.mGroupAlertBehavior;
2429         that.mBubbleMetadata = this.mBubbleMetadata;
2430         that.mAllowSystemGeneratedContextualActions = this.mAllowSystemGeneratedContextualActions;
2431 
2432         if (!heavy) {
2433             that.lightenPayload(); // will clean out extras
2434         }
2435     }
2436 
2437     /**
2438      * Note all {@link Uri} that are referenced internally, with the expectation
2439      * that Uri permission grants will need to be issued to ensure the recipient
2440      * of this object is able to render its contents.
2441      *
2442      * @hide
2443      */
visitUris(@onNull Consumer<Uri> visitor)2444     public void visitUris(@NonNull Consumer<Uri> visitor) {
2445         visitor.accept(sound);
2446 
2447         if (tickerView != null) tickerView.visitUris(visitor);
2448         if (contentView != null) contentView.visitUris(visitor);
2449         if (bigContentView != null) bigContentView.visitUris(visitor);
2450         if (headsUpContentView != null) headsUpContentView.visitUris(visitor);
2451 
2452         if (extras != null) {
2453             visitor.accept(extras.getParcelable(EXTRA_AUDIO_CONTENTS_URI));
2454             if (extras.containsKey(EXTRA_BACKGROUND_IMAGE_URI)) {
2455                 visitor.accept(Uri.parse(extras.getString(EXTRA_BACKGROUND_IMAGE_URI)));
2456             }
2457         }
2458 
2459         if (MessagingStyle.class.equals(getNotificationStyle()) && extras != null) {
2460             final Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES);
2461             if (!ArrayUtils.isEmpty(messages)) {
2462                 for (MessagingStyle.Message message : MessagingStyle.Message
2463                         .getMessagesFromBundleArray(messages)) {
2464                     visitor.accept(message.getDataUri());
2465                 }
2466             }
2467 
2468             final Parcelable[] historic = extras.getParcelableArray(EXTRA_HISTORIC_MESSAGES);
2469             if (!ArrayUtils.isEmpty(historic)) {
2470                 for (MessagingStyle.Message message : MessagingStyle.Message
2471                         .getMessagesFromBundleArray(historic)) {
2472                     visitor.accept(message.getDataUri());
2473                 }
2474             }
2475         }
2476     }
2477 
2478     /**
2479      * Removes heavyweight parts of the Notification object for archival or for sending to
2480      * listeners when the full contents are not necessary.
2481      * @hide
2482      */
lightenPayload()2483     public final void lightenPayload() {
2484         tickerView = null;
2485         contentView = null;
2486         bigContentView = null;
2487         headsUpContentView = null;
2488         mLargeIcon = null;
2489         if (extras != null && !extras.isEmpty()) {
2490             final Set<String> keyset = extras.keySet();
2491             final int N = keyset.size();
2492             final String[] keys = keyset.toArray(new String[N]);
2493             for (int i=0; i<N; i++) {
2494                 final String key = keys[i];
2495                 if (TvExtender.EXTRA_TV_EXTENDER.equals(key)) {
2496                     continue;
2497                 }
2498                 final Object obj = extras.get(key);
2499                 if (obj != null &&
2500                     (  obj instanceof Parcelable
2501                     || obj instanceof Parcelable[]
2502                     || obj instanceof SparseArray
2503                     || obj instanceof ArrayList)) {
2504                     extras.remove(key);
2505                 }
2506             }
2507         }
2508     }
2509 
2510     /**
2511      * Make sure this CharSequence is safe to put into a bundle, which basically
2512      * means it had better not be some custom Parcelable implementation.
2513      * @hide
2514      */
safeCharSequence(CharSequence cs)2515     public static CharSequence safeCharSequence(CharSequence cs) {
2516         if (cs == null) return cs;
2517         if (cs.length() > MAX_CHARSEQUENCE_LENGTH) {
2518             cs = cs.subSequence(0, MAX_CHARSEQUENCE_LENGTH);
2519         }
2520         if (cs instanceof Parcelable) {
2521             Log.e(TAG, "warning: " + cs.getClass().getCanonicalName()
2522                     + " instance is a custom Parcelable and not allowed in Notification");
2523             return cs.toString();
2524         }
2525         return removeTextSizeSpans(cs);
2526     }
2527 
removeTextSizeSpans(CharSequence charSequence)2528     private static CharSequence removeTextSizeSpans(CharSequence charSequence) {
2529         if (charSequence instanceof Spanned) {
2530             Spanned ss = (Spanned) charSequence;
2531             Object[] spans = ss.getSpans(0, ss.length(), Object.class);
2532             SpannableStringBuilder builder = new SpannableStringBuilder(ss.toString());
2533             for (Object span : spans) {
2534                 Object resultSpan = span;
2535                 if (resultSpan instanceof CharacterStyle) {
2536                     resultSpan = ((CharacterStyle) span).getUnderlying();
2537                 }
2538                 if (resultSpan instanceof TextAppearanceSpan) {
2539                     TextAppearanceSpan originalSpan = (TextAppearanceSpan) resultSpan;
2540                     resultSpan = new TextAppearanceSpan(
2541                             originalSpan.getFamily(),
2542                             originalSpan.getTextStyle(),
2543                             -1,
2544                             originalSpan.getTextColor(),
2545                             originalSpan.getLinkTextColor());
2546                 } else if (resultSpan instanceof RelativeSizeSpan
2547                         || resultSpan instanceof AbsoluteSizeSpan) {
2548                     continue;
2549                 } else {
2550                     resultSpan = span;
2551                 }
2552                 builder.setSpan(resultSpan, ss.getSpanStart(span), ss.getSpanEnd(span),
2553                         ss.getSpanFlags(span));
2554             }
2555             return builder;
2556         }
2557         return charSequence;
2558     }
2559 
describeContents()2560     public int describeContents() {
2561         return 0;
2562     }
2563 
2564     /**
2565      * Flatten this notification into a parcel.
2566      */
writeToParcel(Parcel parcel, int flags)2567     public void writeToParcel(Parcel parcel, int flags) {
2568         // We need to mark all pending intents getting into the notification
2569         // system as being put there to later allow the notification ranker
2570         // to launch them and by doing so add the app to the battery saver white
2571         // list for a short period of time. The problem is that the system
2572         // cannot look into the extras as there may be parcelables there that
2573         // the platform does not know how to handle. To go around that we have
2574         // an explicit list of the pending intents in the extras bundle.
2575         final boolean collectPendingIntents = (allPendingIntents == null);
2576         if (collectPendingIntents) {
2577             PendingIntent.setOnMarshaledListener(
2578                     (PendingIntent intent, Parcel out, int outFlags) -> {
2579                 if (parcel == out) {
2580                     if (allPendingIntents == null) {
2581                         allPendingIntents = new ArraySet<>();
2582                     }
2583                     allPendingIntents.add(intent);
2584                 }
2585             });
2586         }
2587         try {
2588             // IMPORTANT: Add marshaling code in writeToParcelImpl as we
2589             // want to intercept all pending events written to the parcel.
2590             writeToParcelImpl(parcel, flags);
2591             // Must be written last!
2592             parcel.writeArraySet(allPendingIntents);
2593         } finally {
2594             if (collectPendingIntents) {
2595                 PendingIntent.setOnMarshaledListener(null);
2596             }
2597         }
2598     }
2599 
writeToParcelImpl(Parcel parcel, int flags)2600     private void writeToParcelImpl(Parcel parcel, int flags) {
2601         parcel.writeInt(1);
2602 
2603         parcel.writeStrongBinder(mWhitelistToken);
2604         parcel.writeLong(when);
2605         parcel.writeLong(creationTime);
2606         if (mSmallIcon == null && icon != 0) {
2607             // you snuck an icon in here without using the builder; let's try to keep it
2608             mSmallIcon = Icon.createWithResource("", icon);
2609         }
2610         if (mSmallIcon != null) {
2611             parcel.writeInt(1);
2612             mSmallIcon.writeToParcel(parcel, 0);
2613         } else {
2614             parcel.writeInt(0);
2615         }
2616         parcel.writeInt(number);
2617         if (contentIntent != null) {
2618             parcel.writeInt(1);
2619             contentIntent.writeToParcel(parcel, 0);
2620         } else {
2621             parcel.writeInt(0);
2622         }
2623         if (deleteIntent != null) {
2624             parcel.writeInt(1);
2625             deleteIntent.writeToParcel(parcel, 0);
2626         } else {
2627             parcel.writeInt(0);
2628         }
2629         if (tickerText != null) {
2630             parcel.writeInt(1);
2631             TextUtils.writeToParcel(tickerText, parcel, flags);
2632         } else {
2633             parcel.writeInt(0);
2634         }
2635         if (tickerView != null) {
2636             parcel.writeInt(1);
2637             tickerView.writeToParcel(parcel, 0);
2638         } else {
2639             parcel.writeInt(0);
2640         }
2641         if (contentView != null) {
2642             parcel.writeInt(1);
2643             contentView.writeToParcel(parcel, 0);
2644         } else {
2645             parcel.writeInt(0);
2646         }
2647         if (mLargeIcon == null && largeIcon != null) {
2648             // you snuck an icon in here without using the builder; let's try to keep it
2649             mLargeIcon = Icon.createWithBitmap(largeIcon);
2650         }
2651         if (mLargeIcon != null) {
2652             parcel.writeInt(1);
2653             mLargeIcon.writeToParcel(parcel, 0);
2654         } else {
2655             parcel.writeInt(0);
2656         }
2657 
2658         parcel.writeInt(defaults);
2659         parcel.writeInt(this.flags);
2660 
2661         if (sound != null) {
2662             parcel.writeInt(1);
2663             sound.writeToParcel(parcel, 0);
2664         } else {
2665             parcel.writeInt(0);
2666         }
2667         parcel.writeInt(audioStreamType);
2668 
2669         if (audioAttributes != null) {
2670             parcel.writeInt(1);
2671             audioAttributes.writeToParcel(parcel, 0);
2672         } else {
2673             parcel.writeInt(0);
2674         }
2675 
2676         parcel.writeLongArray(vibrate);
2677         parcel.writeInt(ledARGB);
2678         parcel.writeInt(ledOnMS);
2679         parcel.writeInt(ledOffMS);
2680         parcel.writeInt(iconLevel);
2681 
2682         if (fullScreenIntent != null) {
2683             parcel.writeInt(1);
2684             fullScreenIntent.writeToParcel(parcel, 0);
2685         } else {
2686             parcel.writeInt(0);
2687         }
2688 
2689         parcel.writeInt(priority);
2690 
2691         parcel.writeString(category);
2692 
2693         parcel.writeString(mGroupKey);
2694 
2695         parcel.writeString(mSortKey);
2696 
2697         parcel.writeBundle(extras); // null ok
2698 
2699         parcel.writeTypedArray(actions, 0); // null ok
2700 
2701         if (bigContentView != null) {
2702             parcel.writeInt(1);
2703             bigContentView.writeToParcel(parcel, 0);
2704         } else {
2705             parcel.writeInt(0);
2706         }
2707 
2708         if (headsUpContentView != null) {
2709             parcel.writeInt(1);
2710             headsUpContentView.writeToParcel(parcel, 0);
2711         } else {
2712             parcel.writeInt(0);
2713         }
2714 
2715         parcel.writeInt(visibility);
2716 
2717         if (publicVersion != null) {
2718             parcel.writeInt(1);
2719             publicVersion.writeToParcel(parcel, 0);
2720         } else {
2721             parcel.writeInt(0);
2722         }
2723 
2724         parcel.writeInt(color);
2725 
2726         if (mChannelId != null) {
2727             parcel.writeInt(1);
2728             parcel.writeString(mChannelId);
2729         } else {
2730             parcel.writeInt(0);
2731         }
2732         parcel.writeLong(mTimeout);
2733 
2734         if (mShortcutId != null) {
2735             parcel.writeInt(1);
2736             parcel.writeString(mShortcutId);
2737         } else {
2738             parcel.writeInt(0);
2739         }
2740 
2741         if (mLocusId != null) {
2742             parcel.writeInt(1);
2743             mLocusId.writeToParcel(parcel, 0);
2744         } else {
2745             parcel.writeInt(0);
2746         }
2747 
2748         parcel.writeInt(mBadgeIcon);
2749 
2750         if (mSettingsText != null) {
2751             parcel.writeInt(1);
2752             TextUtils.writeToParcel(mSettingsText, parcel, flags);
2753         } else {
2754             parcel.writeInt(0);
2755         }
2756 
2757         parcel.writeInt(mGroupAlertBehavior);
2758 
2759         if (mBubbleMetadata != null) {
2760             parcel.writeInt(1);
2761             mBubbleMetadata.writeToParcel(parcel, 0);
2762         } else {
2763             parcel.writeInt(0);
2764         }
2765 
2766         parcel.writeBoolean(mAllowSystemGeneratedContextualActions);
2767 
2768         // mUsesStandardHeader is not written because it should be recomputed in listeners
2769     }
2770 
2771     /**
2772      * Parcelable.Creator that instantiates Notification objects
2773      */
2774     public static final @android.annotation.NonNull Parcelable.Creator<Notification> CREATOR
2775             = new Parcelable.Creator<Notification>()
2776     {
2777         public Notification createFromParcel(Parcel parcel)
2778         {
2779             return new Notification(parcel);
2780         }
2781 
2782         public Notification[] newArray(int size)
2783         {
2784             return new Notification[size];
2785         }
2786     };
2787 
2788     /**
2789      * @hide
2790      */
areActionsVisiblyDifferent(Notification first, Notification second)2791     public static boolean areActionsVisiblyDifferent(Notification first, Notification second) {
2792         Notification.Action[] firstAs = first.actions;
2793         Notification.Action[] secondAs = second.actions;
2794         if (firstAs == null && secondAs != null || firstAs != null && secondAs == null) {
2795             return true;
2796         }
2797         if (firstAs != null && secondAs != null) {
2798             if (firstAs.length != secondAs.length) {
2799                 return true;
2800             }
2801             for (int i = 0; i < firstAs.length; i++) {
2802                 if (!Objects.equals(String.valueOf(firstAs[i].title),
2803                         String.valueOf(secondAs[i].title))) {
2804                     return true;
2805                 }
2806                 RemoteInput[] firstRs = firstAs[i].getRemoteInputs();
2807                 RemoteInput[] secondRs = secondAs[i].getRemoteInputs();
2808                 if (firstRs == null) {
2809                     firstRs = new RemoteInput[0];
2810                 }
2811                 if (secondRs == null) {
2812                     secondRs = new RemoteInput[0];
2813                 }
2814                 if (firstRs.length != secondRs.length) {
2815                     return true;
2816                 }
2817                 for (int j = 0; j < firstRs.length; j++) {
2818                     if (!Objects.equals(String.valueOf(firstRs[j].getLabel()),
2819                             String.valueOf(secondRs[j].getLabel()))) {
2820                         return true;
2821                     }
2822                 }
2823             }
2824         }
2825         return false;
2826     }
2827 
2828     /**
2829      * @hide
2830      */
areStyledNotificationsVisiblyDifferent(Builder first, Builder second)2831     public static boolean areStyledNotificationsVisiblyDifferent(Builder first, Builder second) {
2832         if (first.getStyle() == null) {
2833             return second.getStyle() != null;
2834         }
2835         if (second.getStyle() == null) {
2836             return true;
2837         }
2838         return first.getStyle().areNotificationsVisiblyDifferent(second.getStyle());
2839     }
2840 
2841     /**
2842      * @hide
2843      */
areRemoteViewsChanged(Builder first, Builder second)2844     public static boolean areRemoteViewsChanged(Builder first, Builder second) {
2845         if (!Objects.equals(first.usesStandardHeader(), second.usesStandardHeader())) {
2846             return true;
2847         }
2848 
2849         if (areRemoteViewsChanged(first.mN.contentView, second.mN.contentView)) {
2850             return true;
2851         }
2852         if (areRemoteViewsChanged(first.mN.bigContentView, second.mN.bigContentView)) {
2853             return true;
2854         }
2855         if (areRemoteViewsChanged(first.mN.headsUpContentView, second.mN.headsUpContentView)) {
2856             return true;
2857         }
2858 
2859         return false;
2860     }
2861 
areRemoteViewsChanged(RemoteViews first, RemoteViews second)2862     private static boolean areRemoteViewsChanged(RemoteViews first, RemoteViews second) {
2863         if (first == null && second == null) {
2864             return false;
2865         }
2866         if (first == null && second != null || first != null && second == null) {
2867             return true;
2868         }
2869 
2870         if (!Objects.equals(first.getLayoutId(), second.getLayoutId())) {
2871             return true;
2872         }
2873 
2874         if (!Objects.equals(first.getSequenceNumber(), second.getSequenceNumber())) {
2875             return true;
2876         }
2877 
2878         return false;
2879     }
2880 
2881     /**
2882      * Parcelling creates multiple copies of objects in {@code extras}. Fix them.
2883      * <p>
2884      * For backwards compatibility {@code extras} holds some references to "real" member data such
2885      * as {@link getLargeIcon()} which is mirrored by {@link #EXTRA_LARGE_ICON}. This is mostly
2886      * fine as long as the object stays in one process.
2887      * <p>
2888      * However, once the notification goes into a parcel each reference gets marshalled separately,
2889      * wasting memory. Especially with large images on Auto and TV, this is worth fixing.
2890      */
fixDuplicateExtras()2891     private void fixDuplicateExtras() {
2892         if (extras != null) {
2893             fixDuplicateExtra(mLargeIcon, EXTRA_LARGE_ICON);
2894         }
2895     }
2896 
2897     /**
2898      * If we find an extra that's exactly the same as one of the "real" fields but refers to a
2899      * separate object, replace it with the field's version to avoid holding duplicate copies.
2900      */
fixDuplicateExtra(@ullable Parcelable original, @NonNull String extraName)2901     private void fixDuplicateExtra(@Nullable Parcelable original, @NonNull String extraName) {
2902         if (original != null && extras.getParcelable(extraName) != null) {
2903             extras.putParcelable(extraName, original);
2904         }
2905     }
2906 
2907     /**
2908      * Sets the {@link #contentView} field to be a view with the standard "Latest Event"
2909      * layout.
2910      *
2911      * <p>Uses the {@link #icon} and {@link #when} fields to set the icon and time fields
2912      * in the view.</p>
2913      * @param context       The context for your application / activity.
2914      * @param contentTitle The title that goes in the expanded entry.
2915      * @param contentText  The text that goes in the expanded entry.
2916      * @param contentIntent The intent to launch when the user clicks the expanded notification.
2917      * If this is an activity, it must include the
2918      * {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK} flag, which requires
2919      * that you take care of task management as described in the
2920      * <a href="{@docRoot}guide/topics/fundamentals/tasks-and-back-stack.html">Tasks and Back
2921      * Stack</a> document.
2922      *
2923      * @deprecated Use {@link Builder} instead.
2924      * @removed
2925      */
2926     @Deprecated
setLatestEventInfo(Context context, CharSequence contentTitle, CharSequence contentText, PendingIntent contentIntent)2927     public void setLatestEventInfo(Context context,
2928             CharSequence contentTitle, CharSequence contentText, PendingIntent contentIntent) {
2929         if (context.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1){
2930             Log.e(TAG, "setLatestEventInfo() is deprecated and you should feel deprecated.",
2931                     new Throwable());
2932         }
2933 
2934         if (context.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N) {
2935             extras.putBoolean(EXTRA_SHOW_WHEN, true);
2936         }
2937 
2938         // ensure that any information already set directly is preserved
2939         final Notification.Builder builder = new Notification.Builder(context, this);
2940 
2941         // now apply the latestEventInfo fields
2942         if (contentTitle != null) {
2943             builder.setContentTitle(contentTitle);
2944         }
2945         if (contentText != null) {
2946             builder.setContentText(contentText);
2947         }
2948         builder.setContentIntent(contentIntent);
2949 
2950         builder.build(); // callers expect this notification to be ready to use
2951     }
2952 
2953     /**
2954      * @hide
2955      */
addFieldsFromContext(Context context, Notification notification)2956     public static void addFieldsFromContext(Context context, Notification notification) {
2957         addFieldsFromContext(context.getApplicationInfo(), notification);
2958     }
2959 
2960     /**
2961      * @hide
2962      */
addFieldsFromContext(ApplicationInfo ai, Notification notification)2963     public static void addFieldsFromContext(ApplicationInfo ai, Notification notification) {
2964         notification.extras.putParcelable(EXTRA_BUILDER_APPLICATION_INFO, ai);
2965     }
2966 
2967     /**
2968      * @hide
2969      */
writeToProto(ProtoOutputStream proto, long fieldId)2970     public void writeToProto(ProtoOutputStream proto, long fieldId) {
2971         long token = proto.start(fieldId);
2972         proto.write(NotificationProto.CHANNEL_ID, getChannelId());
2973         proto.write(NotificationProto.HAS_TICKER_TEXT, this.tickerText != null);
2974         proto.write(NotificationProto.FLAGS, this.flags);
2975         proto.write(NotificationProto.COLOR, this.color);
2976         proto.write(NotificationProto.CATEGORY, this.category);
2977         proto.write(NotificationProto.GROUP_KEY, this.mGroupKey);
2978         proto.write(NotificationProto.SORT_KEY, this.mSortKey);
2979         if (this.actions != null) {
2980             proto.write(NotificationProto.ACTION_LENGTH, this.actions.length);
2981         }
2982         if (this.visibility >= VISIBILITY_SECRET && this.visibility <= VISIBILITY_PUBLIC) {
2983             proto.write(NotificationProto.VISIBILITY, this.visibility);
2984         }
2985         if (publicVersion != null) {
2986             publicVersion.writeToProto(proto, NotificationProto.PUBLIC_VERSION);
2987         }
2988         proto.end(token);
2989     }
2990 
2991     @Override
toString()2992     public String toString() {
2993         StringBuilder sb = new StringBuilder();
2994         sb.append("Notification(channel=");
2995         sb.append(getChannelId());
2996         sb.append(" pri=");
2997         sb.append(priority);
2998         sb.append(" contentView=");
2999         if (contentView != null) {
3000             sb.append(contentView.getPackage());
3001             sb.append("/0x");
3002             sb.append(Integer.toHexString(contentView.getLayoutId()));
3003         } else {
3004             sb.append("null");
3005         }
3006         sb.append(" vibrate=");
3007         if ((this.defaults & DEFAULT_VIBRATE) != 0) {
3008             sb.append("default");
3009         } else if (this.vibrate != null) {
3010             int N = this.vibrate.length-1;
3011             sb.append("[");
3012             for (int i=0; i<N; i++) {
3013                 sb.append(this.vibrate[i]);
3014                 sb.append(',');
3015             }
3016             if (N != -1) {
3017                 sb.append(this.vibrate[N]);
3018             }
3019             sb.append("]");
3020         } else {
3021             sb.append("null");
3022         }
3023         sb.append(" sound=");
3024         if ((this.defaults & DEFAULT_SOUND) != 0) {
3025             sb.append("default");
3026         } else if (this.sound != null) {
3027             sb.append(this.sound.toString());
3028         } else {
3029             sb.append("null");
3030         }
3031         if (this.tickerText != null) {
3032             sb.append(" tick");
3033         }
3034         sb.append(" defaults=0x");
3035         sb.append(Integer.toHexString(this.defaults));
3036         sb.append(" flags=0x");
3037         sb.append(Integer.toHexString(this.flags));
3038         sb.append(String.format(" color=0x%08x", this.color));
3039         if (this.category != null) {
3040             sb.append(" category=");
3041             sb.append(this.category);
3042         }
3043         if (this.mGroupKey != null) {
3044             sb.append(" groupKey=");
3045             sb.append(this.mGroupKey);
3046         }
3047         if (this.mSortKey != null) {
3048             sb.append(" sortKey=");
3049             sb.append(this.mSortKey);
3050         }
3051         if (actions != null) {
3052             sb.append(" actions=");
3053             sb.append(actions.length);
3054         }
3055         sb.append(" vis=");
3056         sb.append(visibilityToString(this.visibility));
3057         if (this.publicVersion != null) {
3058             sb.append(" publicVersion=");
3059             sb.append(publicVersion.toString());
3060         }
3061         if (this.mLocusId != null) {
3062             sb.append(" locusId=");
3063             sb.append(this.mLocusId); // LocusId.toString() is PII safe.
3064         }
3065         sb.append(")");
3066         return sb.toString();
3067     }
3068 
3069     /**
3070      * {@hide}
3071      */
visibilityToString(int vis)3072     public static String visibilityToString(int vis) {
3073         switch (vis) {
3074             case VISIBILITY_PRIVATE:
3075                 return "PRIVATE";
3076             case VISIBILITY_PUBLIC:
3077                 return "PUBLIC";
3078             case VISIBILITY_SECRET:
3079                 return "SECRET";
3080             default:
3081                 return "UNKNOWN(" + String.valueOf(vis) + ")";
3082         }
3083     }
3084 
3085     /**
3086      * {@hide}
3087      */
priorityToString(@riority int pri)3088     public static String priorityToString(@Priority int pri) {
3089         switch (pri) {
3090             case PRIORITY_MIN:
3091                 return "MIN";
3092             case PRIORITY_LOW:
3093                 return "LOW";
3094             case PRIORITY_DEFAULT:
3095                 return "DEFAULT";
3096             case PRIORITY_HIGH:
3097                 return "HIGH";
3098             case PRIORITY_MAX:
3099                 return "MAX";
3100             default:
3101                 return "UNKNOWN(" + String.valueOf(pri) + ")";
3102         }
3103     }
3104 
3105     /**
3106      * @hide
3107      */
hasCompletedProgress()3108     public boolean hasCompletedProgress() {
3109         // not a progress notification; can't be complete
3110         if (!extras.containsKey(EXTRA_PROGRESS)
3111                 || !extras.containsKey(EXTRA_PROGRESS_MAX)) {
3112             return false;
3113         }
3114         // many apps use max 0 for 'indeterminate'; not complete
3115         if (extras.getInt(EXTRA_PROGRESS_MAX) == 0) {
3116             return false;
3117         }
3118         return extras.getInt(EXTRA_PROGRESS) == extras.getInt(EXTRA_PROGRESS_MAX);
3119     }
3120 
3121     /** @removed */
3122     @Deprecated
getChannel()3123     public String getChannel() {
3124         return mChannelId;
3125     }
3126 
3127     /**
3128      * Returns the id of the channel this notification posts to.
3129      */
getChannelId()3130     public String getChannelId() {
3131         return mChannelId;
3132     }
3133 
3134     /** @removed */
3135     @Deprecated
getTimeout()3136     public long getTimeout() {
3137         return mTimeout;
3138     }
3139 
3140     /**
3141      * Returns the duration from posting after which this notification should be canceled by the
3142      * system, if it's not canceled already.
3143      */
getTimeoutAfter()3144     public long getTimeoutAfter() {
3145         return mTimeout;
3146     }
3147 
3148     /**
3149      * Returns what icon should be shown for this notification if it is being displayed in a
3150      * Launcher that supports badging. Will be one of {@link #BADGE_ICON_NONE},
3151      * {@link #BADGE_ICON_SMALL}, or {@link #BADGE_ICON_LARGE}.
3152      */
getBadgeIconType()3153     public int getBadgeIconType() {
3154         return mBadgeIcon;
3155     }
3156 
3157     /**
3158      * Returns the {@link ShortcutInfo#getId() id} that this notification supersedes, if any.
3159      *
3160      * <p>Used by some Launchers that display notification content to hide shortcuts that duplicate
3161      * notifications.
3162      */
getShortcutId()3163     public String getShortcutId() {
3164         return mShortcutId;
3165     }
3166 
3167     /**
3168      * Gets the {@link LocusId} associated with this notification.
3169      *
3170      * <p>Used by the Android system to correlate objects (such as
3171      * {@link ShortcutInfo} and {@link ContentCaptureContext}).
3172      */
3173     @Nullable
getLocusId()3174     public LocusId getLocusId() {
3175         return mLocusId;
3176     }
3177 
3178     /**
3179      * Returns the settings text provided to {@link Builder#setSettingsText(CharSequence)}.
3180      */
getSettingsText()3181     public CharSequence getSettingsText() {
3182         return mSettingsText;
3183     }
3184 
3185     /**
3186      * Returns which type of notifications in a group are responsible for audibly alerting the
3187      * user. See {@link #GROUP_ALERT_ALL}, {@link #GROUP_ALERT_CHILDREN},
3188      * {@link #GROUP_ALERT_SUMMARY}.
3189      */
getGroupAlertBehavior()3190     public @GroupAlertBehavior int getGroupAlertBehavior() {
3191         return mGroupAlertBehavior;
3192     }
3193 
3194     /**
3195      * Returns the bubble metadata that will be used to display app content in a floating window
3196      * over the existing foreground activity.
3197      */
3198     @Nullable
getBubbleMetadata()3199     public BubbleMetadata getBubbleMetadata() {
3200         return mBubbleMetadata;
3201     }
3202 
3203     /**
3204      * Returns whether the platform is allowed (by the app developer) to generate contextual actions
3205      * for this notification.
3206      */
getAllowSystemGeneratedContextualActions()3207     public boolean getAllowSystemGeneratedContextualActions() {
3208         return mAllowSystemGeneratedContextualActions;
3209     }
3210 
3211     /**
3212      * The small icon representing this notification in the status bar and content view.
3213      *
3214      * @return the small icon representing this notification.
3215      *
3216      * @see Builder#getSmallIcon()
3217      * @see Builder#setSmallIcon(Icon)
3218      */
getSmallIcon()3219     public Icon getSmallIcon() {
3220         return mSmallIcon;
3221     }
3222 
3223     /**
3224      * Used when notifying to clean up legacy small icons.
3225      * @hide
3226      */
3227     @UnsupportedAppUsage
setSmallIcon(Icon icon)3228     public void setSmallIcon(Icon icon) {
3229         mSmallIcon = icon;
3230     }
3231 
3232     /**
3233      * The large icon shown in this notification's content view.
3234      * @see Builder#getLargeIcon()
3235      * @see Builder#setLargeIcon(Icon)
3236      */
getLargeIcon()3237     public Icon getLargeIcon() {
3238         return mLargeIcon;
3239     }
3240 
3241     /**
3242      * @hide
3243      */
3244     @UnsupportedAppUsage
isGroupSummary()3245     public boolean isGroupSummary() {
3246         return mGroupKey != null && (flags & FLAG_GROUP_SUMMARY) != 0;
3247     }
3248 
3249     /**
3250      * @hide
3251      */
3252     @UnsupportedAppUsage
isGroupChild()3253     public boolean isGroupChild() {
3254         return mGroupKey != null && (flags & FLAG_GROUP_SUMMARY) == 0;
3255     }
3256 
3257     /**
3258      * @hide
3259      */
suppressAlertingDueToGrouping()3260     public boolean suppressAlertingDueToGrouping() {
3261         if (isGroupSummary()
3262                 && getGroupAlertBehavior() == Notification.GROUP_ALERT_CHILDREN) {
3263             return true;
3264         } else if (isGroupChild()
3265                 && getGroupAlertBehavior() == Notification.GROUP_ALERT_SUMMARY) {
3266             return true;
3267         }
3268         return false;
3269     }
3270 
3271 
3272     /**
3273      * Finds and returns a remote input and its corresponding action.
3274      *
3275      * @param requiresFreeform requires the remoteinput to allow freeform or not.
3276      * @return the result pair, {@code null} if no result is found.
3277      *
3278      * @hide
3279      */
3280     @Nullable
findRemoteInputActionPair(boolean requiresFreeform)3281     public Pair<RemoteInput, Action> findRemoteInputActionPair(boolean requiresFreeform) {
3282         if (actions == null) {
3283             return null;
3284         }
3285         for (Notification.Action action : actions) {
3286             if (action.getRemoteInputs() == null) {
3287                 continue;
3288             }
3289             RemoteInput resultRemoteInput = null;
3290             for (RemoteInput remoteInput : action.getRemoteInputs()) {
3291                 if (remoteInput.getAllowFreeFormInput() || !requiresFreeform) {
3292                     resultRemoteInput = remoteInput;
3293                 }
3294             }
3295             if (resultRemoteInput != null) {
3296                 return Pair.create(resultRemoteInput, action);
3297             }
3298         }
3299         return null;
3300     }
3301 
3302     /**
3303      * Returns the actions that are contextual out of the actions in this notification.
3304      *
3305      * @hide
3306      */
getContextualActions()3307     public List<Notification.Action> getContextualActions() {
3308         if (actions == null) return Collections.emptyList();
3309 
3310         List<Notification.Action> contextualActions = new ArrayList<>();
3311         for (Notification.Action action : actions) {
3312             if (action.isContextual()) {
3313                 contextualActions.add(action);
3314             }
3315         }
3316         return contextualActions;
3317     }
3318 
3319     /**
3320      * Builder class for {@link Notification} objects.
3321      *
3322      * Provides a convenient way to set the various fields of a {@link Notification} and generate
3323      * content views using the platform's notification layout template. If your app supports
3324      * versions of Android as old as API level 4, you can instead use
3325      * {@link android.support.v4.app.NotificationCompat.Builder NotificationCompat.Builder},
3326      * available in the <a href="{@docRoot}tools/extras/support-library.html">Android Support
3327      * library</a>.
3328      *
3329      * <p>Example:
3330      *
3331      * <pre class="prettyprint">
3332      * Notification noti = new Notification.Builder(mContext)
3333      *         .setContentTitle(&quot;New mail from &quot; + sender.toString())
3334      *         .setContentText(subject)
3335      *         .setSmallIcon(R.drawable.new_mail)
3336      *         .setLargeIcon(aBitmap)
3337      *         .build();
3338      * </pre>
3339      */
3340     public static class Builder {
3341         /**
3342          * @hide
3343          */
3344         public static final String EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT =
3345                 "android.rebuild.contentViewActionCount";
3346         /**
3347          * @hide
3348          */
3349         public static final String EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT
3350                 = "android.rebuild.bigViewActionCount";
3351         /**
3352          * @hide
3353          */
3354         public static final String EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT
3355                 = "android.rebuild.hudViewActionCount";
3356 
3357         private static final boolean USE_ONLY_TITLE_IN_LOW_PRIORITY_SUMMARY =
3358                 SystemProperties.getBoolean("notifications.only_title", true);
3359 
3360         /**
3361          * The lightness difference that has to be added to the primary text color to obtain the
3362          * secondary text color when the background is light.
3363          */
3364         private static final int LIGHTNESS_TEXT_DIFFERENCE_LIGHT = 20;
3365 
3366         /**
3367          * The lightness difference that has to be added to the primary text color to obtain the
3368          * secondary text color when the background is dark.
3369          * A bit less then the above value, since it looks better on dark backgrounds.
3370          */
3371         private static final int LIGHTNESS_TEXT_DIFFERENCE_DARK = -10;
3372 
3373         private Context mContext;
3374         private Notification mN;
3375         private Bundle mUserExtras = new Bundle();
3376         private Style mStyle;
3377         @UnsupportedAppUsage
3378         private ArrayList<Action> mActions = new ArrayList<Action>(MAX_ACTION_BUTTONS);
3379         private ArrayList<Person> mPersonList = new ArrayList<>();
3380         private ContrastColorUtil mColorUtil;
3381         private boolean mIsLegacy;
3382         private boolean mIsLegacyInitialized;
3383 
3384         /**
3385          * Caches a contrast-enhanced version of {@link #mCachedContrastColorIsFor}.
3386          */
3387         private int mCachedContrastColor = COLOR_INVALID;
3388         private int mCachedContrastColorIsFor = COLOR_INVALID;
3389         /**
3390          * Caches a ambient version of {@link #mCachedAmbientColorIsFor}.
3391          */
3392         private int mCachedAmbientColor = COLOR_INVALID;
3393         private int mCachedAmbientColorIsFor = COLOR_INVALID;
3394         /**
3395          * A neutral color color that can be used for icons.
3396          */
3397         private int mNeutralColor = COLOR_INVALID;
3398 
3399         /**
3400          * Caches an instance of StandardTemplateParams. Note that this may have been used before,
3401          * so make sure to call {@link StandardTemplateParams#reset()} before using it.
3402          */
3403         StandardTemplateParams mParams = new StandardTemplateParams();
3404         private int mTextColorsAreForBackground = COLOR_INVALID;
3405         private int mPrimaryTextColor = COLOR_INVALID;
3406         private int mSecondaryTextColor = COLOR_INVALID;
3407         private int mBackgroundColor = COLOR_INVALID;
3408         private int mForegroundColor = COLOR_INVALID;
3409         /**
3410          * A temporary location where actions are stored. If != null the view originally has action
3411          * but doesn't have any for this inflation.
3412          */
3413         private ArrayList<Action> mOriginalActions;
3414         private boolean mRebuildStyledRemoteViews;
3415 
3416         private boolean mTintActionButtons;
3417         private boolean mInNightMode;
3418 
3419         /**
3420          * Constructs a new Builder with the defaults:
3421          *
3422          * @param context
3423          *            A {@link Context} that will be used by the Builder to construct the
3424          *            RemoteViews. The Context will not be held past the lifetime of this Builder
3425          *            object.
3426          * @param channelId
3427          *            The constructed Notification will be posted on this
3428          *            {@link NotificationChannel}. To use a NotificationChannel, it must first be
3429          *            created using {@link NotificationManager#createNotificationChannel}.
3430          */
Builder(Context context, String channelId)3431         public Builder(Context context, String channelId) {
3432             this(context, (Notification) null);
3433             mN.mChannelId = channelId;
3434         }
3435 
3436         /**
3437          * @deprecated use {@link Notification.Builder#Notification.Builder(Context, String)}
3438          * instead. All posted Notifications must specify a NotificationChannel Id.
3439          */
3440         @Deprecated
Builder(Context context)3441         public Builder(Context context) {
3442             this(context, (Notification) null);
3443         }
3444 
3445         /**
3446          * @hide
3447          */
Builder(Context context, Notification toAdopt)3448         public Builder(Context context, Notification toAdopt) {
3449             mContext = context;
3450             Resources res = mContext.getResources();
3451             mTintActionButtons = res.getBoolean(R.bool.config_tintNotificationActionButtons);
3452 
3453             if (res.getBoolean(R.bool.config_enableNightMode)) {
3454                 Configuration currentConfig = res.getConfiguration();
3455                 mInNightMode = (currentConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK)
3456                         == Configuration.UI_MODE_NIGHT_YES;
3457             }
3458 
3459             if (toAdopt == null) {
3460                 mN = new Notification();
3461                 if (context.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N) {
3462                     mN.extras.putBoolean(EXTRA_SHOW_WHEN, true);
3463                 }
3464                 mN.priority = PRIORITY_DEFAULT;
3465                 mN.visibility = VISIBILITY_PRIVATE;
3466             } else {
3467                 mN = toAdopt;
3468                 if (mN.actions != null) {
3469                     Collections.addAll(mActions, mN.actions);
3470                 }
3471 
3472                 if (mN.extras.containsKey(EXTRA_PEOPLE_LIST)) {
3473                     ArrayList<Person> people = mN.extras.getParcelableArrayList(EXTRA_PEOPLE_LIST);
3474                     mPersonList.addAll(people);
3475                 }
3476 
3477                 if (mN.getSmallIcon() == null && mN.icon != 0) {
3478                     setSmallIcon(mN.icon);
3479                 }
3480 
3481                 if (mN.getLargeIcon() == null && mN.largeIcon != null) {
3482                     setLargeIcon(mN.largeIcon);
3483                 }
3484 
3485                 String templateClass = mN.extras.getString(EXTRA_TEMPLATE);
3486                 if (!TextUtils.isEmpty(templateClass)) {
3487                     final Class<? extends Style> styleClass
3488                             = getNotificationStyleClass(templateClass);
3489                     if (styleClass == null) {
3490                         Log.d(TAG, "Unknown style class: " + templateClass);
3491                     } else {
3492                         try {
3493                             final Constructor<? extends Style> ctor =
3494                                     styleClass.getDeclaredConstructor();
3495                             ctor.setAccessible(true);
3496                             final Style style = ctor.newInstance();
3497                             style.restoreFromExtras(mN.extras);
3498 
3499                             if (style != null) {
3500                                 setStyle(style);
3501                             }
3502                         } catch (Throwable t) {
3503                             Log.e(TAG, "Could not create Style", t);
3504                         }
3505                     }
3506                 }
3507 
3508             }
3509         }
3510 
getColorUtil()3511         private ContrastColorUtil getColorUtil() {
3512             if (mColorUtil == null) {
3513                 mColorUtil = ContrastColorUtil.getInstance(mContext);
3514             }
3515             return mColorUtil;
3516         }
3517 
3518         /**
3519          * If this notification is duplicative of a Launcher shortcut, sets the
3520          * {@link ShortcutInfo#getId() id} of the shortcut, in case the Launcher wants to hide
3521          * the shortcut.
3522          *
3523          * This field will be ignored by Launchers that don't support badging, don't show
3524          * notification content, or don't show {@link android.content.pm.ShortcutManager shortcuts}.
3525          *
3526          * @param shortcutId the {@link ShortcutInfo#getId() id} of the shortcut this notification
3527          *                   supersedes
3528          */
3529         @NonNull
setShortcutId(String shortcutId)3530         public Builder setShortcutId(String shortcutId) {
3531             mN.mShortcutId = shortcutId;
3532             return this;
3533         }
3534 
3535         /**
3536          * Sets the {@link LocusId} associated with this notification.
3537          *
3538          * <p>This method should be called when the {@link LocusId} is used in other places (such
3539          * as {@link ShortcutInfo} and {@link ContentCaptureContext}) so the Android system can
3540          * correlate them.
3541          */
3542         @NonNull
setLocusId(@ullable LocusId locusId)3543         public Builder setLocusId(@Nullable LocusId locusId) {
3544             mN.mLocusId = locusId;
3545             return this;
3546         }
3547 
3548         /**
3549          * Sets which icon to display as a badge for this notification.
3550          *
3551          * Must be one of {@link #BADGE_ICON_NONE}, {@link #BADGE_ICON_SMALL},
3552          * {@link #BADGE_ICON_LARGE}.
3553          *
3554          * Note: This value might be ignored, for launchers that don't support badge icons.
3555          */
3556         @NonNull
setBadgeIconType(int icon)3557         public Builder setBadgeIconType(int icon) {
3558             mN.mBadgeIcon = icon;
3559             return this;
3560         }
3561 
3562         /**
3563          * Sets the group alert behavior for this notification. Use this method to mute this
3564          * notification if alerts for this notification's group should be handled by a different
3565          * notification. This is only applicable for notifications that belong to a
3566          * {@link #setGroup(String) group}. This must be called on all notifications you want to
3567          * mute. For example, if you want only the summary of your group to make noise, all
3568          * children in the group should have the group alert behavior {@link #GROUP_ALERT_SUMMARY}.
3569          *
3570          * <p> The default value is {@link #GROUP_ALERT_ALL}.</p>
3571          */
3572         @NonNull
setGroupAlertBehavior(@roupAlertBehavior int groupAlertBehavior)3573         public Builder setGroupAlertBehavior(@GroupAlertBehavior int groupAlertBehavior) {
3574             mN.mGroupAlertBehavior = groupAlertBehavior;
3575             return this;
3576         }
3577 
3578         /**
3579          * Sets the {@link BubbleMetadata} that will be used to display app content in a floating
3580          * window over the existing foreground activity.
3581          *
3582          * <p>This data will be ignored unless the notification is posted to a channel that
3583          * allows {@link NotificationChannel#canBubble() bubbles}.</p>
3584          *
3585          * <p>Notifications allowed to bubble that have valid bubble metadata will display in
3586          * collapsed state outside of the notification shade on unlocked devices. When a user
3587          * interacts with the collapsed state, the bubble intent will be invoked and displayed.</p>
3588          */
3589         @NonNull
setBubbleMetadata(@ullable BubbleMetadata data)3590         public Builder setBubbleMetadata(@Nullable BubbleMetadata data) {
3591             mN.mBubbleMetadata = data;
3592             return this;
3593         }
3594 
3595         /** @removed */
3596         @Deprecated
setChannel(String channelId)3597         public Builder setChannel(String channelId) {
3598             mN.mChannelId = channelId;
3599             return this;
3600         }
3601 
3602         /**
3603          * Specifies the channel the notification should be delivered on.
3604          */
3605         @NonNull
setChannelId(String channelId)3606         public Builder setChannelId(String channelId) {
3607             mN.mChannelId = channelId;
3608             return this;
3609         }
3610 
3611         /** @removed */
3612         @Deprecated
setTimeout(long durationMs)3613         public Builder setTimeout(long durationMs) {
3614             mN.mTimeout = durationMs;
3615             return this;
3616         }
3617 
3618         /**
3619          * Specifies a duration in milliseconds after which this notification should be canceled,
3620          * if it is not already canceled.
3621          */
3622         @NonNull
setTimeoutAfter(long durationMs)3623         public Builder setTimeoutAfter(long durationMs) {
3624             mN.mTimeout = durationMs;
3625             return this;
3626         }
3627 
3628         /**
3629          * Add a timestamp pertaining to the notification (usually the time the event occurred).
3630          *
3631          * For apps targeting {@link android.os.Build.VERSION_CODES#N} and above, this time is not
3632          * shown anymore by default and must be opted into by using
3633          * {@link android.app.Notification.Builder#setShowWhen(boolean)}
3634          *
3635          * @see Notification#when
3636          */
3637         @NonNull
setWhen(long when)3638         public Builder setWhen(long when) {
3639             mN.when = when;
3640             return this;
3641         }
3642 
3643         /**
3644          * Control whether the timestamp set with {@link #setWhen(long) setWhen} is shown
3645          * in the content view.
3646          * For apps targeting {@link android.os.Build.VERSION_CODES#N} and above, this defaults to
3647          * {@code false}. For earlier apps, the default is {@code true}.
3648          */
3649         @NonNull
setShowWhen(boolean show)3650         public Builder setShowWhen(boolean show) {
3651             mN.extras.putBoolean(EXTRA_SHOW_WHEN, show);
3652             return this;
3653         }
3654 
3655         /**
3656          * Show the {@link Notification#when} field as a stopwatch.
3657          *
3658          * Instead of presenting <code>when</code> as a timestamp, the notification will show an
3659          * automatically updating display of the minutes and seconds since <code>when</code>.
3660          *
3661          * Useful when showing an elapsed time (like an ongoing phone call).
3662          *
3663          * The counter can also be set to count down to <code>when</code> when using
3664          * {@link #setChronometerCountDown(boolean)}.
3665          *
3666          * @see android.widget.Chronometer
3667          * @see Notification#when
3668          * @see #setChronometerCountDown(boolean)
3669          */
3670         @NonNull
setUsesChronometer(boolean b)3671         public Builder setUsesChronometer(boolean b) {
3672             mN.extras.putBoolean(EXTRA_SHOW_CHRONOMETER, b);
3673             return this;
3674         }
3675 
3676         /**
3677          * Sets the Chronometer to count down instead of counting up.
3678          *
3679          * <p>This is only relevant if {@link #setUsesChronometer(boolean)} has been set to true.
3680          * If it isn't set the chronometer will count up.
3681          *
3682          * @see #setUsesChronometer(boolean)
3683          */
3684         @NonNull
setChronometerCountDown(boolean countDown)3685         public Builder setChronometerCountDown(boolean countDown) {
3686             mN.extras.putBoolean(EXTRA_CHRONOMETER_COUNT_DOWN, countDown);
3687             return this;
3688         }
3689 
3690         /**
3691          * Set the small icon resource, which will be used to represent the notification in the
3692          * status bar.
3693          *
3694 
3695          * The platform template for the expanded view will draw this icon in the left, unless a
3696          * {@link #setLargeIcon(Bitmap) large icon} has also been specified, in which case the small
3697          * icon will be moved to the right-hand side.
3698          *
3699 
3700          * @param icon
3701          *            A resource ID in the application's package of the drawable to use.
3702          * @see Notification#icon
3703          */
3704         @NonNull
setSmallIcon(@rawableRes int icon)3705         public Builder setSmallIcon(@DrawableRes int icon) {
3706             return setSmallIcon(icon != 0
3707                     ? Icon.createWithResource(mContext, icon)
3708                     : null);
3709         }
3710 
3711         /**
3712          * A variant of {@link #setSmallIcon(int) setSmallIcon(int)} that takes an additional
3713          * level parameter for when the icon is a {@link android.graphics.drawable.LevelListDrawable
3714          * LevelListDrawable}.
3715          *
3716          * @param icon A resource ID in the application's package of the drawable to use.
3717          * @param level The level to use for the icon.
3718          *
3719          * @see Notification#icon
3720          * @see Notification#iconLevel
3721          */
3722         @NonNull
setSmallIcon(@rawableRes int icon, int level)3723         public Builder setSmallIcon(@DrawableRes int icon, int level) {
3724             mN.iconLevel = level;
3725             return setSmallIcon(icon);
3726         }
3727 
3728         /**
3729          * Set the small icon, which will be used to represent the notification in the
3730          * status bar and content view (unless overridden there by a
3731          * {@link #setLargeIcon(Bitmap) large icon}).
3732          *
3733          * @param icon An Icon object to use.
3734          * @see Notification#icon
3735          */
3736         @NonNull
setSmallIcon(Icon icon)3737         public Builder setSmallIcon(Icon icon) {
3738             mN.setSmallIcon(icon);
3739             if (icon != null && icon.getType() == Icon.TYPE_RESOURCE) {
3740                 mN.icon = icon.getResId();
3741             }
3742             return this;
3743         }
3744 
3745         /**
3746          * Set the first line of text in the platform notification template.
3747          */
3748         @NonNull
setContentTitle(CharSequence title)3749         public Builder setContentTitle(CharSequence title) {
3750             mN.extras.putCharSequence(EXTRA_TITLE, safeCharSequence(title));
3751             return this;
3752         }
3753 
3754         /**
3755          * Set the second line of text in the platform notification template.
3756          */
3757         @NonNull
setContentText(CharSequence text)3758         public Builder setContentText(CharSequence text) {
3759             mN.extras.putCharSequence(EXTRA_TEXT, safeCharSequence(text));
3760             return this;
3761         }
3762 
3763         /**
3764          * This provides some additional information that is displayed in the notification. No
3765          * guarantees are given where exactly it is displayed.
3766          *
3767          * <p>This information should only be provided if it provides an essential
3768          * benefit to the understanding of the notification. The more text you provide the
3769          * less readable it becomes. For example, an email client should only provide the account
3770          * name here if more than one email account has been added.</p>
3771          *
3772          * <p>As of {@link android.os.Build.VERSION_CODES#N} this information is displayed in the
3773          * notification header area.
3774          *
3775          * On Android versions before {@link android.os.Build.VERSION_CODES#N}
3776          * this will be shown in the third line of text in the platform notification template.
3777          * You should not be using {@link #setProgress(int, int, boolean)} at the
3778          * same time on those versions; they occupy the same place.
3779          * </p>
3780          */
3781         @NonNull
setSubText(CharSequence text)3782         public Builder setSubText(CharSequence text) {
3783             mN.extras.putCharSequence(EXTRA_SUB_TEXT, safeCharSequence(text));
3784             return this;
3785         }
3786 
3787         /**
3788          * Provides text that will appear as a link to your application's settings.
3789          *
3790          * <p>This text does not appear within notification {@link Style templates} but may
3791          * appear when the user uses an affordance to learn more about the notification.
3792          * Additionally, this text will not appear unless you provide a valid link target by
3793          * handling {@link #INTENT_CATEGORY_NOTIFICATION_PREFERENCES}.
3794          *
3795          * <p>This text is meant to be concise description about what the user can customize
3796          * when they click on this link. The recommended maximum length is 40 characters.
3797          * @param text
3798          * @return
3799          */
3800         @NonNull
setSettingsText(CharSequence text)3801         public Builder setSettingsText(CharSequence text) {
3802             mN.mSettingsText = safeCharSequence(text);
3803             return this;
3804         }
3805 
3806         /**
3807          * Set the remote input history.
3808          *
3809          * This should be set to the most recent inputs that have been sent
3810          * through a {@link RemoteInput} of this Notification and cleared once the it is no
3811          * longer relevant (e.g. for chat notifications once the other party has responded).
3812          *
3813          * The most recent input must be stored at the 0 index, the second most recent at the
3814          * 1 index, etc. Note that the system will limit both how far back the inputs will be shown
3815          * and how much of each individual input is shown.
3816          *
3817          * <p>Note: The reply text will only be shown on notifications that have least one action
3818          * with a {@code RemoteInput}.</p>
3819          */
3820         @NonNull
setRemoteInputHistory(CharSequence[] text)3821         public Builder setRemoteInputHistory(CharSequence[] text) {
3822             if (text == null) {
3823                 mN.extras.putCharSequenceArray(EXTRA_REMOTE_INPUT_HISTORY, null);
3824             } else {
3825                 final int N = Math.min(MAX_REPLY_HISTORY, text.length);
3826                 CharSequence[] safe = new CharSequence[N];
3827                 for (int i = 0; i < N; i++) {
3828                     safe[i] = safeCharSequence(text[i]);
3829                 }
3830                 mN.extras.putCharSequenceArray(EXTRA_REMOTE_INPUT_HISTORY, safe);
3831             }
3832             return this;
3833         }
3834 
3835         /**
3836          * Sets whether remote history entries view should have a spinner.
3837          * @hide
3838          */
3839         @NonNull
setShowRemoteInputSpinner(boolean showSpinner)3840         public Builder setShowRemoteInputSpinner(boolean showSpinner) {
3841             mN.extras.putBoolean(EXTRA_SHOW_REMOTE_INPUT_SPINNER, showSpinner);
3842             return this;
3843         }
3844 
3845         /**
3846          * Sets whether smart reply buttons should be hidden.
3847          * @hide
3848          */
3849         @NonNull
setHideSmartReplies(boolean hideSmartReplies)3850         public Builder setHideSmartReplies(boolean hideSmartReplies) {
3851             mN.extras.putBoolean(EXTRA_HIDE_SMART_REPLIES, hideSmartReplies);
3852             return this;
3853         }
3854 
3855         /**
3856          * Sets the number of items this notification represents. May be displayed as a badge count
3857          * for Launchers that support badging.
3858          */
3859         @NonNull
setNumber(int number)3860         public Builder setNumber(int number) {
3861             mN.number = number;
3862             return this;
3863         }
3864 
3865         /**
3866          * A small piece of additional information pertaining to this notification.
3867          *
3868          * The platform template will draw this on the last line of the notification, at the far
3869          * right (to the right of a smallIcon if it has been placed there).
3870          *
3871          * @deprecated use {@link #setSubText(CharSequence)} instead to set a text in the header.
3872          * For legacy apps targeting a version below {@link android.os.Build.VERSION_CODES#N} this
3873          * field will still show up, but the subtext will take precedence.
3874          */
3875         @Deprecated
setContentInfo(CharSequence info)3876         public Builder setContentInfo(CharSequence info) {
3877             mN.extras.putCharSequence(EXTRA_INFO_TEXT, safeCharSequence(info));
3878             return this;
3879         }
3880 
3881         /**
3882          * Set the progress this notification represents.
3883          *
3884          * The platform template will represent this using a {@link ProgressBar}.
3885          */
3886         @NonNull
setProgress(int max, int progress, boolean indeterminate)3887         public Builder setProgress(int max, int progress, boolean indeterminate) {
3888             mN.extras.putInt(EXTRA_PROGRESS, progress);
3889             mN.extras.putInt(EXTRA_PROGRESS_MAX, max);
3890             mN.extras.putBoolean(EXTRA_PROGRESS_INDETERMINATE, indeterminate);
3891             return this;
3892         }
3893 
3894         /**
3895          * Supply a custom RemoteViews to use instead of the platform template.
3896          *
3897          * Use {@link #setCustomContentView(RemoteViews)} instead.
3898          */
3899         @Deprecated
setContent(RemoteViews views)3900         public Builder setContent(RemoteViews views) {
3901             return setCustomContentView(views);
3902         }
3903 
3904         /**
3905          * Supply custom RemoteViews to use instead of the platform template.
3906          *
3907          * This will override the layout that would otherwise be constructed by this Builder
3908          * object.
3909          */
3910         @NonNull
setCustomContentView(RemoteViews contentView)3911         public Builder setCustomContentView(RemoteViews contentView) {
3912             mN.contentView = contentView;
3913             return this;
3914         }
3915 
3916         /**
3917          * Supply custom RemoteViews to use instead of the platform template in the expanded form.
3918          *
3919          * This will override the expanded layout that would otherwise be constructed by this
3920          * Builder object.
3921          */
3922         @NonNull
setCustomBigContentView(RemoteViews contentView)3923         public Builder setCustomBigContentView(RemoteViews contentView) {
3924             mN.bigContentView = contentView;
3925             return this;
3926         }
3927 
3928         /**
3929          * Supply custom RemoteViews to use instead of the platform template in the heads up dialog.
3930          *
3931          * This will override the heads-up layout that would otherwise be constructed by this
3932          * Builder object.
3933          */
3934         @NonNull
setCustomHeadsUpContentView(RemoteViews contentView)3935         public Builder setCustomHeadsUpContentView(RemoteViews contentView) {
3936             mN.headsUpContentView = contentView;
3937             return this;
3938         }
3939 
3940         /**
3941          * Supply a {@link PendingIntent} to be sent when the notification is clicked.
3942          *
3943          * As of {@link android.os.Build.VERSION_CODES#HONEYCOMB}, if this field is unset and you
3944          * have specified a custom RemoteViews with {@link #setContent(RemoteViews)}, you can use
3945          * {@link RemoteViews#setOnClickPendingIntent RemoteViews.setOnClickPendingIntent(int,PendingIntent)}
3946          * to assign PendingIntents to individual views in that custom layout (i.e., to create
3947          * clickable buttons inside the notification view).
3948          *
3949          * @see Notification#contentIntent Notification.contentIntent
3950          */
3951         @NonNull
setContentIntent(PendingIntent intent)3952         public Builder setContentIntent(PendingIntent intent) {
3953             mN.contentIntent = intent;
3954             return this;
3955         }
3956 
3957         /**
3958          * Supply a {@link PendingIntent} to send when the notification is cleared explicitly by the user.
3959          *
3960          * @see Notification#deleteIntent
3961          */
3962         @NonNull
setDeleteIntent(PendingIntent intent)3963         public Builder setDeleteIntent(PendingIntent intent) {
3964             mN.deleteIntent = intent;
3965             return this;
3966         }
3967 
3968         /**
3969          * An intent to launch instead of posting the notification to the status bar.
3970          * Only for use with extremely high-priority notifications demanding the user's
3971          * <strong>immediate</strong> attention, such as an incoming phone call or
3972          * alarm clock that the user has explicitly set to a particular time.
3973          * If this facility is used for something else, please give the user an option
3974          * to turn it off and use a normal notification, as this can be extremely
3975          * disruptive.
3976          *
3977          * <p>
3978          * The system UI may choose to display a heads-up notification, instead of
3979          * launching this intent, while the user is using the device.
3980          * </p>
3981          * <p>Apps targeting {@link Build.VERSION_CODES#Q} and above will have to request
3982          * a permission ({@link android.Manifest.permission#USE_FULL_SCREEN_INTENT}) in order to
3983          * use full screen intents.</p>
3984          *
3985          * @param intent The pending intent to launch.
3986          * @param highPriority Passing true will cause this notification to be sent
3987          *          even if other notifications are suppressed.
3988          *
3989          * @see Notification#fullScreenIntent
3990          */
3991         @NonNull
setFullScreenIntent(PendingIntent intent, boolean highPriority)3992         public Builder setFullScreenIntent(PendingIntent intent, boolean highPriority) {
3993             mN.fullScreenIntent = intent;
3994             setFlag(FLAG_HIGH_PRIORITY, highPriority);
3995             return this;
3996         }
3997 
3998         /**
3999          * Set the "ticker" text which is sent to accessibility services.
4000          *
4001          * @see Notification#tickerText
4002          */
4003         @NonNull
setTicker(CharSequence tickerText)4004         public Builder setTicker(CharSequence tickerText) {
4005             mN.tickerText = safeCharSequence(tickerText);
4006             return this;
4007         }
4008 
4009         /**
4010          * Obsolete version of {@link #setTicker(CharSequence)}.
4011          *
4012          */
4013         @Deprecated
setTicker(CharSequence tickerText, RemoteViews views)4014         public Builder setTicker(CharSequence tickerText, RemoteViews views) {
4015             setTicker(tickerText);
4016             // views is ignored
4017             return this;
4018         }
4019 
4020         /**
4021          * Add a large icon to the notification content view.
4022          *
4023          * In the platform template, this image will be shown on the left of the notification view
4024          * in place of the {@link #setSmallIcon(Icon) small icon} (which will be placed in a small
4025          * badge atop the large icon).
4026          */
4027         @NonNull
setLargeIcon(Bitmap b)4028         public Builder setLargeIcon(Bitmap b) {
4029             return setLargeIcon(b != null ? Icon.createWithBitmap(b) : null);
4030         }
4031 
4032         /**
4033          * Add a large icon to the notification content view.
4034          *
4035          * In the platform template, this image will be shown on the left of the notification view
4036          * in place of the {@link #setSmallIcon(Icon) small icon} (which will be placed in a small
4037          * badge atop the large icon).
4038          */
4039         @NonNull
setLargeIcon(Icon icon)4040         public Builder setLargeIcon(Icon icon) {
4041             mN.mLargeIcon = icon;
4042             mN.extras.putParcelable(EXTRA_LARGE_ICON, icon);
4043             return this;
4044         }
4045 
4046         /**
4047          * Set the sound to play.
4048          *
4049          * It will be played using the {@link #AUDIO_ATTRIBUTES_DEFAULT default audio attributes}
4050          * for notifications.
4051          *
4052          * @deprecated use {@link NotificationChannel#setSound(Uri, AudioAttributes)} instead.
4053          */
4054         @Deprecated
setSound(Uri sound)4055         public Builder setSound(Uri sound) {
4056             mN.sound = sound;
4057             mN.audioAttributes = AUDIO_ATTRIBUTES_DEFAULT;
4058             return this;
4059         }
4060 
4061         /**
4062          * Set the sound to play, along with a specific stream on which to play it.
4063          *
4064          * See {@link android.media.AudioManager} for the <code>STREAM_</code> constants.
4065          *
4066          * @deprecated use {@link NotificationChannel#setSound(Uri, AudioAttributes)}.
4067          */
4068         @Deprecated
setSound(Uri sound, int streamType)4069         public Builder setSound(Uri sound, int streamType) {
4070             PlayerBase.deprecateStreamTypeForPlayback(streamType, "Notification", "setSound()");
4071             mN.sound = sound;
4072             mN.audioStreamType = streamType;
4073             return this;
4074         }
4075 
4076         /**
4077          * Set the sound to play, along with specific {@link AudioAttributes audio attributes} to
4078          * use during playback.
4079          *
4080          * @deprecated use {@link NotificationChannel#setSound(Uri, AudioAttributes)} instead.
4081          * @see Notification#sound
4082          */
4083         @Deprecated
setSound(Uri sound, AudioAttributes audioAttributes)4084         public Builder setSound(Uri sound, AudioAttributes audioAttributes) {
4085             mN.sound = sound;
4086             mN.audioAttributes = audioAttributes;
4087             return this;
4088         }
4089 
4090         /**
4091          * Set the vibration pattern to use.
4092          *
4093          * See {@link android.os.Vibrator#vibrate(long[], int)} for a discussion of the
4094          * <code>pattern</code> parameter.
4095          *
4096          * <p>
4097          * A notification that vibrates is more likely to be presented as a heads-up notification.
4098          * </p>
4099          *
4100          * @deprecated use {@link NotificationChannel#setVibrationPattern(long[])} instead.
4101          * @see Notification#vibrate
4102          */
4103         @Deprecated
setVibrate(long[] pattern)4104         public Builder setVibrate(long[] pattern) {
4105             mN.vibrate = pattern;
4106             return this;
4107         }
4108 
4109         /**
4110          * Set the desired color for the indicator LED on the device, as well as the
4111          * blink duty cycle (specified in milliseconds).
4112          *
4113 
4114          * Not all devices will honor all (or even any) of these values.
4115          *
4116          * @deprecated use {@link NotificationChannel#enableLights(boolean)} instead.
4117          * @see Notification#ledARGB
4118          * @see Notification#ledOnMS
4119          * @see Notification#ledOffMS
4120          */
4121         @Deprecated
setLights(@olorInt int argb, int onMs, int offMs)4122         public Builder setLights(@ColorInt int argb, int onMs, int offMs) {
4123             mN.ledARGB = argb;
4124             mN.ledOnMS = onMs;
4125             mN.ledOffMS = offMs;
4126             if (onMs != 0 || offMs != 0) {
4127                 mN.flags |= FLAG_SHOW_LIGHTS;
4128             }
4129             return this;
4130         }
4131 
4132         /**
4133          * Set whether this is an "ongoing" notification.
4134          *
4135 
4136          * Ongoing notifications cannot be dismissed by the user, so your application or service
4137          * must take care of canceling them.
4138          *
4139 
4140          * They are typically used to indicate a background task that the user is actively engaged
4141          * with (e.g., playing music) or is pending in some way and therefore occupying the device
4142          * (e.g., a file download, sync operation, active network connection).
4143          *
4144 
4145          * @see Notification#FLAG_ONGOING_EVENT
4146          */
4147         @NonNull
setOngoing(boolean ongoing)4148         public Builder setOngoing(boolean ongoing) {
4149             setFlag(FLAG_ONGOING_EVENT, ongoing);
4150             return this;
4151         }
4152 
4153         /**
4154          * Set whether this notification should be colorized. When set, the color set with
4155          * {@link #setColor(int)} will be used as the background color of this notification.
4156          * <p>
4157          * This should only be used for high priority ongoing tasks like navigation, an ongoing
4158          * call, or other similarly high-priority events for the user.
4159          * <p>
4160          * For most styles, the coloring will only be applied if the notification is for a
4161          * foreground service notification.
4162          * However, for {@link MediaStyle} and {@link DecoratedMediaCustomViewStyle} notifications
4163          * that have a media session attached there is no such requirement.
4164          *
4165          * @see #setColor(int)
4166          * @see MediaStyle#setMediaSession(MediaSession.Token)
4167          */
4168         @NonNull
setColorized(boolean colorize)4169         public Builder setColorized(boolean colorize) {
4170             mN.extras.putBoolean(EXTRA_COLORIZED, colorize);
4171             return this;
4172         }
4173 
4174         /**
4175          * Set this flag if you would only like the sound, vibrate
4176          * and ticker to be played if the notification is not already showing.
4177          *
4178          * @see Notification#FLAG_ONLY_ALERT_ONCE
4179          */
4180         @NonNull
setOnlyAlertOnce(boolean onlyAlertOnce)4181         public Builder setOnlyAlertOnce(boolean onlyAlertOnce) {
4182             setFlag(FLAG_ONLY_ALERT_ONCE, onlyAlertOnce);
4183             return this;
4184         }
4185 
4186         /**
4187          * Make this notification automatically dismissed when the user touches it.
4188          *
4189          * @see Notification#FLAG_AUTO_CANCEL
4190          */
4191         @NonNull
setAutoCancel(boolean autoCancel)4192         public Builder setAutoCancel(boolean autoCancel) {
4193             setFlag(FLAG_AUTO_CANCEL, autoCancel);
4194             return this;
4195         }
4196 
4197         /**
4198          * Set whether or not this notification should not bridge to other devices.
4199          *
4200          * <p>Some notifications can be bridged to other devices for remote display.
4201          * This hint can be set to recommend this notification not be bridged.
4202          */
4203         @NonNull
setLocalOnly(boolean localOnly)4204         public Builder setLocalOnly(boolean localOnly) {
4205             setFlag(FLAG_LOCAL_ONLY, localOnly);
4206             return this;
4207         }
4208 
4209         /**
4210          * Set which notification properties will be inherited from system defaults.
4211          * <p>
4212          * The value should be one or more of the following fields combined with
4213          * bitwise-or:
4214          * {@link #DEFAULT_SOUND}, {@link #DEFAULT_VIBRATE}, {@link #DEFAULT_LIGHTS}.
4215          * <p>
4216          * For all default values, use {@link #DEFAULT_ALL}.
4217          *
4218          * @deprecated use {@link NotificationChannel#enableVibration(boolean)} and
4219          * {@link NotificationChannel#enableLights(boolean)} and
4220          * {@link NotificationChannel#setSound(Uri, AudioAttributes)} instead.
4221          */
4222         @Deprecated
setDefaults(int defaults)4223         public Builder setDefaults(int defaults) {
4224             mN.defaults = defaults;
4225             return this;
4226         }
4227 
4228         /**
4229          * Set the priority of this notification.
4230          *
4231          * @see Notification#priority
4232          * @deprecated use {@link NotificationChannel#setImportance(int)} instead.
4233          */
4234         @Deprecated
setPriority(@riority int pri)4235         public Builder setPriority(@Priority int pri) {
4236             mN.priority = pri;
4237             return this;
4238         }
4239 
4240         /**
4241          * Set the notification category.
4242          *
4243          * @see Notification#category
4244          */
4245         @NonNull
setCategory(String category)4246         public Builder setCategory(String category) {
4247             mN.category = category;
4248             return this;
4249         }
4250 
4251         /**
4252          * Add a person that is relevant to this notification.
4253          *
4254          * <P>
4255          * Depending on user preferences, this annotation may allow the notification to pass
4256          * through interruption filters, if this notification is of category {@link #CATEGORY_CALL}
4257          * or {@link #CATEGORY_MESSAGE}. The addition of people may also cause this notification to
4258          * appear more prominently in the user interface.
4259          * </P>
4260          *
4261          * <P>
4262          * The person should be specified by the {@code String} representation of a
4263          * {@link android.provider.ContactsContract.Contacts#CONTENT_LOOKUP_URI}.
4264          * </P>
4265          *
4266          * <P>The system will also attempt to resolve {@code mailto:} and {@code tel:} schema
4267          * URIs.  The path part of these URIs must exist in the contacts database, in the
4268          * appropriate column, or the reference will be discarded as invalid. Telephone schema
4269          * URIs will be resolved by {@link android.provider.ContactsContract.PhoneLookup}.
4270          * It is also possible to provide a URI with the schema {@code name:} in order to uniquely
4271          * identify a person without an entry in the contacts database.
4272          * </P>
4273          *
4274          * @param uri A URI for the person.
4275          * @see Notification#EXTRA_PEOPLE
4276          * @deprecated use {@link #addPerson(Person)}
4277          */
addPerson(String uri)4278         public Builder addPerson(String uri) {
4279             addPerson(new Person.Builder().setUri(uri).build());
4280             return this;
4281         }
4282 
4283         /**
4284          * Add a person that is relevant to this notification.
4285          *
4286          * <P>
4287          * Depending on user preferences, this annotation may allow the notification to pass
4288          * through interruption filters, if this notification is of category {@link #CATEGORY_CALL}
4289          * or {@link #CATEGORY_MESSAGE}. The addition of people may also cause this notification to
4290          * appear more prominently in the user interface.
4291          * </P>
4292          *
4293          * <P>
4294          * A person should usually contain a uri in order to benefit from the ranking boost.
4295          * However, even if no uri is provided, it's beneficial to provide other people in the
4296          * notification, such that listeners and voice only devices can announce and handle them
4297          * properly.
4298          * </P>
4299          *
4300          * @param person the person to add.
4301          * @see Notification#EXTRA_PEOPLE_LIST
4302          */
4303         @NonNull
addPerson(Person person)4304         public Builder addPerson(Person person) {
4305             mPersonList.add(person);
4306             return this;
4307         }
4308 
4309         /**
4310          * Set this notification to be part of a group of notifications sharing the same key.
4311          * Grouped notifications may display in a cluster or stack on devices which
4312          * support such rendering.
4313          *
4314          * <p>To make this notification the summary for its group, also call
4315          * {@link #setGroupSummary}. A sort order can be specified for group members by using
4316          * {@link #setSortKey}.
4317          * @param groupKey The group key of the group.
4318          * @return this object for method chaining
4319          */
4320         @NonNull
setGroup(String groupKey)4321         public Builder setGroup(String groupKey) {
4322             mN.mGroupKey = groupKey;
4323             return this;
4324         }
4325 
4326         /**
4327          * Set this notification to be the group summary for a group of notifications.
4328          * Grouped notifications may display in a cluster or stack on devices which
4329          * support such rendering. If thereRequires a group key also be set using {@link #setGroup}.
4330          * The group summary may be suppressed if too few notifications are included in the group.
4331          * @param isGroupSummary Whether this notification should be a group summary.
4332          * @return this object for method chaining
4333          */
4334         @NonNull
setGroupSummary(boolean isGroupSummary)4335         public Builder setGroupSummary(boolean isGroupSummary) {
4336             setFlag(FLAG_GROUP_SUMMARY, isGroupSummary);
4337             return this;
4338         }
4339 
4340         /**
4341          * Set a sort key that orders this notification among other notifications from the
4342          * same package. This can be useful if an external sort was already applied and an app
4343          * would like to preserve this. Notifications will be sorted lexicographically using this
4344          * value, although providing different priorities in addition to providing sort key may
4345          * cause this value to be ignored.
4346          *
4347          * <p>This sort key can also be used to order members of a notification group. See
4348          * {@link #setGroup}.
4349          *
4350          * @see String#compareTo(String)
4351          */
4352         @NonNull
setSortKey(String sortKey)4353         public Builder setSortKey(String sortKey) {
4354             mN.mSortKey = sortKey;
4355             return this;
4356         }
4357 
4358         /**
4359          * Merge additional metadata into this notification.
4360          *
4361          * <p>Values within the Bundle will replace existing extras values in this Builder.
4362          *
4363          * @see Notification#extras
4364          */
4365         @NonNull
addExtras(Bundle extras)4366         public Builder addExtras(Bundle extras) {
4367             if (extras != null) {
4368                 mUserExtras.putAll(extras);
4369             }
4370             return this;
4371         }
4372 
4373         /**
4374          * Set metadata for this notification.
4375          *
4376          * <p>A reference to the Bundle is held for the lifetime of this Builder, and the Bundle's
4377          * current contents are copied into the Notification each time {@link #build()} is
4378          * called.
4379          *
4380          * <p>Replaces any existing extras values with those from the provided Bundle.
4381          * Use {@link #addExtras} to merge in metadata instead.
4382          *
4383          * @see Notification#extras
4384          */
4385         @NonNull
setExtras(Bundle extras)4386         public Builder setExtras(Bundle extras) {
4387             if (extras != null) {
4388                 mUserExtras = extras;
4389             }
4390             return this;
4391         }
4392 
4393         /**
4394          * Get the current metadata Bundle used by this notification Builder.
4395          *
4396          * <p>The returned Bundle is shared with this Builder.
4397          *
4398          * <p>The current contents of this Bundle are copied into the Notification each time
4399          * {@link #build()} is called.
4400          *
4401          * @see Notification#extras
4402          */
getExtras()4403         public Bundle getExtras() {
4404             return mUserExtras;
4405         }
4406 
getAllExtras()4407         private Bundle getAllExtras() {
4408             final Bundle saveExtras = (Bundle) mUserExtras.clone();
4409             saveExtras.putAll(mN.extras);
4410             return saveExtras;
4411         }
4412 
4413         /**
4414          * Add an action to this notification. Actions are typically displayed by
4415          * the system as a button adjacent to the notification content.
4416          * <p>
4417          * Every action must have an icon (32dp square and matching the
4418          * <a href="{@docRoot}design/style/iconography.html#action-bar">Holo
4419          * Dark action bar</a> visual style), a textual label, and a {@link PendingIntent}.
4420          * <p>
4421          * A notification in its expanded form can display up to 3 actions, from left to right in
4422          * the order they were added. Actions will not be displayed when the notification is
4423          * collapsed, however, so be sure that any essential functions may be accessed by the user
4424          * in some other way (for example, in the Activity pointed to by {@link #contentIntent}).
4425          *
4426          * @param icon Resource ID of a drawable that represents the action.
4427          * @param title Text describing the action.
4428          * @param intent PendingIntent to be fired when the action is invoked.
4429          *
4430          * @deprecated Use {@link #addAction(Action)} instead.
4431          */
4432         @Deprecated
addAction(int icon, CharSequence title, PendingIntent intent)4433         public Builder addAction(int icon, CharSequence title, PendingIntent intent) {
4434             mActions.add(new Action(icon, safeCharSequence(title), intent));
4435             return this;
4436         }
4437 
4438         /**
4439          * Add an action to this notification. Actions are typically displayed by
4440          * the system as a button adjacent to the notification content.
4441          * <p>
4442          * Every action must have an icon (32dp square and matching the
4443          * <a href="{@docRoot}design/style/iconography.html#action-bar">Holo
4444          * Dark action bar</a> visual style), a textual label, and a {@link PendingIntent}.
4445          * <p>
4446          * A notification in its expanded form can display up to 3 actions, from left to right in
4447          * the order they were added. Actions will not be displayed when the notification is
4448          * collapsed, however, so be sure that any essential functions may be accessed by the user
4449          * in some other way (for example, in the Activity pointed to by {@link #contentIntent}).
4450          *
4451          * @param action The action to add.
4452          */
4453         @NonNull
addAction(Action action)4454         public Builder addAction(Action action) {
4455             if (action != null) {
4456                 mActions.add(action);
4457             }
4458             return this;
4459         }
4460 
4461         /**
4462          * Alter the complete list of actions attached to this notification.
4463          * @see #addAction(Action).
4464          *
4465          * @param actions
4466          * @return
4467          */
4468         @NonNull
setActions(Action... actions)4469         public Builder setActions(Action... actions) {
4470             mActions.clear();
4471             for (int i = 0; i < actions.length; i++) {
4472                 if (actions[i] != null) {
4473                     mActions.add(actions[i]);
4474                 }
4475             }
4476             return this;
4477         }
4478 
4479         /**
4480          * Add a rich notification style to be applied at build time.
4481          *
4482          * @param style Object responsible for modifying the notification style.
4483          */
4484         @NonNull
setStyle(Style style)4485         public Builder setStyle(Style style) {
4486             if (mStyle != style) {
4487                 mStyle = style;
4488                 if (mStyle != null) {
4489                     mStyle.setBuilder(this);
4490                     mN.extras.putString(EXTRA_TEMPLATE, style.getClass().getName());
4491                 }  else {
4492                     mN.extras.remove(EXTRA_TEMPLATE);
4493                 }
4494             }
4495             return this;
4496         }
4497 
4498         /**
4499          * Returns the style set by {@link #setStyle(Style)}.
4500          */
getStyle()4501         public Style getStyle() {
4502             return mStyle;
4503         }
4504 
4505         /**
4506          * Specify the value of {@link #visibility}.
4507          *
4508          * @return The same Builder.
4509          */
4510         @NonNull
setVisibility(@isibility int visibility)4511         public Builder setVisibility(@Visibility int visibility) {
4512             mN.visibility = visibility;
4513             return this;
4514         }
4515 
4516         /**
4517          * Supply a replacement Notification whose contents should be shown in insecure contexts
4518          * (i.e. atop the secure lockscreen). See {@link #visibility} and {@link #VISIBILITY_PUBLIC}.
4519          * @param n A replacement notification, presumably with some or all info redacted.
4520          * @return The same Builder.
4521          */
4522         @NonNull
setPublicVersion(Notification n)4523         public Builder setPublicVersion(Notification n) {
4524             if (n != null) {
4525                 mN.publicVersion = new Notification();
4526                 n.cloneInto(mN.publicVersion, /*heavy=*/ true);
4527             } else {
4528                 mN.publicVersion = null;
4529             }
4530             return this;
4531         }
4532 
4533         /**
4534          * Apply an extender to this notification builder. Extenders may be used to add
4535          * metadata or change options on this builder.
4536          */
4537         @NonNull
extend(Extender extender)4538         public Builder extend(Extender extender) {
4539             extender.extend(this);
4540             return this;
4541         }
4542 
4543         /**
4544          * @hide
4545          */
4546         @NonNull
setFlag(int mask, boolean value)4547         public Builder setFlag(int mask, boolean value) {
4548             if (value) {
4549                 mN.flags |= mask;
4550             } else {
4551                 mN.flags &= ~mask;
4552             }
4553             return this;
4554         }
4555 
4556         /**
4557          * Sets {@link Notification#color}.
4558          *
4559          * @param argb The accent color to use
4560          *
4561          * @return The same Builder.
4562          */
4563         @NonNull
setColor(@olorInt int argb)4564         public Builder setColor(@ColorInt int argb) {
4565             mN.color = argb;
4566             sanitizeColor();
4567             return this;
4568         }
4569 
getProfileBadgeDrawable()4570         private Drawable getProfileBadgeDrawable() {
4571             if (mContext.getUserId() == UserHandle.USER_SYSTEM) {
4572                 // This user can never be a badged profile,
4573                 // and also includes USER_ALL system notifications.
4574                 return null;
4575             }
4576             // Note: This assumes that the current user can read the profile badge of the
4577             // originating user.
4578             return mContext.getPackageManager().getUserBadgeForDensityNoBackground(
4579                     new UserHandle(mContext.getUserId()), 0);
4580         }
4581 
getProfileBadge()4582         private Bitmap getProfileBadge() {
4583             Drawable badge = getProfileBadgeDrawable();
4584             if (badge == null) {
4585                 return null;
4586             }
4587             final int size = mContext.getResources().getDimensionPixelSize(
4588                     R.dimen.notification_badge_size);
4589             Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
4590             Canvas canvas = new Canvas(bitmap);
4591             badge.setBounds(0, 0, size, size);
4592             badge.draw(canvas);
4593             return bitmap;
4594         }
4595 
bindProfileBadge(RemoteViews contentView, StandardTemplateParams p)4596         private void bindProfileBadge(RemoteViews contentView, StandardTemplateParams p) {
4597             Bitmap profileBadge = getProfileBadge();
4598 
4599             if (profileBadge != null) {
4600                 contentView.setImageViewBitmap(R.id.profile_badge, profileBadge);
4601                 contentView.setViewVisibility(R.id.profile_badge, View.VISIBLE);
4602                 if (isColorized(p)) {
4603                     contentView.setDrawableTint(R.id.profile_badge, false,
4604                             getPrimaryTextColor(p), PorterDuff.Mode.SRC_ATOP);
4605                 }
4606             }
4607         }
4608 
bindAlertedIcon(RemoteViews contentView, StandardTemplateParams p)4609         private void bindAlertedIcon(RemoteViews contentView, StandardTemplateParams p) {
4610             contentView.setDrawableTint(
4611                     R.id.alerted_icon,
4612                     false /* targetBackground */,
4613                     getNeutralColor(p),
4614                     PorterDuff.Mode.SRC_ATOP);
4615         }
4616 
4617         /**
4618          * @hide
4619          */
usesStandardHeader()4620         public boolean usesStandardHeader() {
4621             if (mN.mUsesStandardHeader) {
4622                 return true;
4623             }
4624             if (mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.N) {
4625                 if (mN.contentView == null && mN.bigContentView == null) {
4626                     return true;
4627                 }
4628             }
4629             boolean contentViewUsesHeader = mN.contentView == null
4630                     || STANDARD_LAYOUTS.contains(mN.contentView.getLayoutId());
4631             boolean bigContentViewUsesHeader = mN.bigContentView == null
4632                     || STANDARD_LAYOUTS.contains(mN.bigContentView.getLayoutId());
4633             return contentViewUsesHeader && bigContentViewUsesHeader;
4634         }
4635 
resetStandardTemplate(RemoteViews contentView)4636         private void resetStandardTemplate(RemoteViews contentView) {
4637             resetNotificationHeader(contentView);
4638             contentView.setViewVisibility(R.id.right_icon, View.GONE);
4639             contentView.setViewVisibility(R.id.title, View.GONE);
4640             contentView.setTextViewText(R.id.title, null);
4641             contentView.setViewVisibility(R.id.text, View.GONE);
4642             contentView.setTextViewText(R.id.text, null);
4643             contentView.setViewVisibility(R.id.text_line_1, View.GONE);
4644             contentView.setTextViewText(R.id.text_line_1, null);
4645         }
4646 
4647         /**
4648          * Resets the notification header to its original state
4649          */
resetNotificationHeader(RemoteViews contentView)4650         private void resetNotificationHeader(RemoteViews contentView) {
4651             // Small icon doesn't need to be reset, as it's always set. Resetting would prevent
4652             // re-using the drawable when the notification is updated.
4653             contentView.setBoolean(R.id.notification_header, "setExpanded", false);
4654             contentView.setTextViewText(R.id.app_name_text, null);
4655             contentView.setViewVisibility(R.id.chronometer, View.GONE);
4656             contentView.setViewVisibility(R.id.header_text, View.GONE);
4657             contentView.setTextViewText(R.id.header_text, null);
4658             contentView.setViewVisibility(R.id.header_text_secondary, View.GONE);
4659             contentView.setTextViewText(R.id.header_text_secondary, null);
4660             contentView.setViewVisibility(R.id.header_text_divider, View.GONE);
4661             contentView.setViewVisibility(R.id.header_text_secondary_divider, View.GONE);
4662             contentView.setViewVisibility(R.id.time_divider, View.GONE);
4663             contentView.setViewVisibility(R.id.time, View.GONE);
4664             contentView.setImageViewIcon(R.id.profile_badge, null);
4665             contentView.setViewVisibility(R.id.profile_badge, View.GONE);
4666             contentView.setViewVisibility(R.id.alerted_icon, View.GONE);
4667             mN.mUsesStandardHeader = false;
4668         }
4669 
applyStandardTemplate(int resId, TemplateBindResult result)4670         private RemoteViews applyStandardTemplate(int resId, TemplateBindResult result) {
4671             return applyStandardTemplate(resId, mParams.reset().fillTextsFrom(this),
4672                     result);
4673         }
4674 
applyStandardTemplate(int resId, StandardTemplateParams p, TemplateBindResult result)4675         private RemoteViews applyStandardTemplate(int resId, StandardTemplateParams p,
4676                 TemplateBindResult result) {
4677             RemoteViews contentView = new BuilderRemoteViews(mContext.getApplicationInfo(), resId);
4678 
4679             resetStandardTemplate(contentView);
4680 
4681             final Bundle ex = mN.extras;
4682             updateBackgroundColor(contentView, p);
4683             bindNotificationHeader(contentView, p);
4684             bindLargeIconAndReply(contentView, p, result);
4685             boolean showProgress = handleProgressBar(contentView, ex, p);
4686             if (p.title != null) {
4687                 contentView.setViewVisibility(R.id.title, View.VISIBLE);
4688                 contentView.setTextViewText(R.id.title, processTextSpans(p.title));
4689                 setTextViewColorPrimary(contentView, R.id.title, p);
4690                 contentView.setViewLayoutWidth(R.id.title, showProgress
4691                         ? ViewGroup.LayoutParams.WRAP_CONTENT
4692                         : ViewGroup.LayoutParams.MATCH_PARENT);
4693             }
4694             if (p.text != null) {
4695                 int textId = showProgress ? com.android.internal.R.id.text_line_1
4696                         : com.android.internal.R.id.text;
4697                 contentView.setTextViewText(textId, processTextSpans(p.text));
4698                 setTextViewColorSecondary(contentView, textId, p);
4699                 contentView.setViewVisibility(textId, View.VISIBLE);
4700             }
4701 
4702             setContentMinHeight(contentView, showProgress || mN.hasLargeIcon());
4703 
4704             return contentView;
4705         }
4706 
processTextSpans(CharSequence text)4707         private CharSequence processTextSpans(CharSequence text) {
4708             if (hasForegroundColor() || mInNightMode) {
4709                 return ContrastColorUtil.clearColorSpans(text);
4710             }
4711             return text;
4712         }
4713 
setTextViewColorPrimary(RemoteViews contentView, int id, StandardTemplateParams p)4714         private void setTextViewColorPrimary(RemoteViews contentView, int id,
4715                 StandardTemplateParams p) {
4716             ensureColors(p);
4717             contentView.setTextColor(id, mPrimaryTextColor);
4718         }
4719 
hasForegroundColor()4720         private boolean hasForegroundColor() {
4721             return mForegroundColor != COLOR_INVALID;
4722         }
4723 
4724         /**
4725          * Return the primary text color using the existing template params
4726          * @hide
4727          */
4728         @VisibleForTesting
getPrimaryTextColor()4729         public int getPrimaryTextColor() {
4730             return getPrimaryTextColor(mParams);
4731         }
4732 
4733         /**
4734          * @param p the template params to inflate this with
4735          * @return the primary text color
4736          * @hide
4737          */
4738         @VisibleForTesting
getPrimaryTextColor(StandardTemplateParams p)4739         public int getPrimaryTextColor(StandardTemplateParams p) {
4740             ensureColors(p);
4741             return mPrimaryTextColor;
4742         }
4743 
4744         /**
4745          * Return the secondary text color using the existing template params
4746          * @hide
4747          */
4748         @VisibleForTesting
getSecondaryTextColor()4749         public int getSecondaryTextColor() {
4750             return getSecondaryTextColor(mParams);
4751         }
4752 
4753         /**
4754          * @param p the template params to inflate this with
4755          * @return the secondary text color
4756          * @hide
4757          */
4758         @VisibleForTesting
getSecondaryTextColor(StandardTemplateParams p)4759         public int getSecondaryTextColor(StandardTemplateParams p) {
4760             ensureColors(p);
4761             return mSecondaryTextColor;
4762         }
4763 
setTextViewColorSecondary(RemoteViews contentView, int id, StandardTemplateParams p)4764         private void setTextViewColorSecondary(RemoteViews contentView, int id,
4765                 StandardTemplateParams p) {
4766             ensureColors(p);
4767             contentView.setTextColor(id, mSecondaryTextColor);
4768         }
4769 
ensureColors(StandardTemplateParams p)4770         private void ensureColors(StandardTemplateParams p) {
4771             int backgroundColor = getBackgroundColor(p);
4772             if (mPrimaryTextColor == COLOR_INVALID
4773                     || mSecondaryTextColor == COLOR_INVALID
4774                     || mTextColorsAreForBackground != backgroundColor) {
4775                 mTextColorsAreForBackground = backgroundColor;
4776                 if (!hasForegroundColor() || !isColorized(p)) {
4777                     mPrimaryTextColor = ContrastColorUtil.resolvePrimaryColor(mContext,
4778                             backgroundColor, mInNightMode);
4779                     mSecondaryTextColor = ContrastColorUtil.resolveSecondaryColor(mContext,
4780                             backgroundColor, mInNightMode);
4781                     if (backgroundColor != COLOR_DEFAULT && isColorized(p)) {
4782                         mPrimaryTextColor = ContrastColorUtil.findAlphaToMeetContrast(
4783                                 mPrimaryTextColor, backgroundColor, 4.5);
4784                         mSecondaryTextColor = ContrastColorUtil.findAlphaToMeetContrast(
4785                                 mSecondaryTextColor, backgroundColor, 4.5);
4786                     }
4787                 } else {
4788                     double backLum = ContrastColorUtil.calculateLuminance(backgroundColor);
4789                     double textLum = ContrastColorUtil.calculateLuminance(mForegroundColor);
4790                     double contrast = ContrastColorUtil.calculateContrast(mForegroundColor,
4791                             backgroundColor);
4792                     // We only respect the given colors if worst case Black or White still has
4793                     // contrast
4794                     boolean backgroundLight = backLum > textLum
4795                                     && satisfiesTextContrast(backgroundColor, Color.BLACK)
4796                             || backLum <= textLum
4797                                     && !satisfiesTextContrast(backgroundColor, Color.WHITE);
4798                     if (contrast < 4.5f) {
4799                         if (backgroundLight) {
4800                             mSecondaryTextColor = ContrastColorUtil.findContrastColor(
4801                                     mForegroundColor,
4802                                     backgroundColor,
4803                                     true /* findFG */,
4804                                     4.5f);
4805                             mPrimaryTextColor = ContrastColorUtil.changeColorLightness(
4806                                     mSecondaryTextColor, -LIGHTNESS_TEXT_DIFFERENCE_LIGHT);
4807                         } else {
4808                             mSecondaryTextColor =
4809                                     ContrastColorUtil.findContrastColorAgainstDark(
4810                                     mForegroundColor,
4811                                     backgroundColor,
4812                                     true /* findFG */,
4813                                     4.5f);
4814                             mPrimaryTextColor = ContrastColorUtil.changeColorLightness(
4815                                     mSecondaryTextColor, -LIGHTNESS_TEXT_DIFFERENCE_DARK);
4816                         }
4817                     } else {
4818                         mPrimaryTextColor = mForegroundColor;
4819                         mSecondaryTextColor = ContrastColorUtil.changeColorLightness(
4820                                 mPrimaryTextColor, backgroundLight ? LIGHTNESS_TEXT_DIFFERENCE_LIGHT
4821                                         : LIGHTNESS_TEXT_DIFFERENCE_DARK);
4822                         if (ContrastColorUtil.calculateContrast(mSecondaryTextColor,
4823                                 backgroundColor) < 4.5f) {
4824                             // oh well the secondary is not good enough
4825                             if (backgroundLight) {
4826                                 mSecondaryTextColor = ContrastColorUtil.findContrastColor(
4827                                         mSecondaryTextColor,
4828                                         backgroundColor,
4829                                         true /* findFG */,
4830                                         4.5f);
4831                             } else {
4832                                 mSecondaryTextColor
4833                                         = ContrastColorUtil.findContrastColorAgainstDark(
4834                                         mSecondaryTextColor,
4835                                         backgroundColor,
4836                                         true /* findFG */,
4837                                         4.5f);
4838                             }
4839                             mPrimaryTextColor = ContrastColorUtil.changeColorLightness(
4840                                     mSecondaryTextColor, backgroundLight
4841                                             ? -LIGHTNESS_TEXT_DIFFERENCE_LIGHT
4842                                             : -LIGHTNESS_TEXT_DIFFERENCE_DARK);
4843                         }
4844                     }
4845                 }
4846             }
4847         }
4848 
updateBackgroundColor(RemoteViews contentView, StandardTemplateParams p)4849         private void updateBackgroundColor(RemoteViews contentView,
4850                 StandardTemplateParams p) {
4851             if (isColorized(p)) {
4852                 contentView.setInt(R.id.status_bar_latest_event_content, "setBackgroundColor",
4853                         getBackgroundColor(p));
4854             } else {
4855                 // Clear it!
4856                 contentView.setInt(R.id.status_bar_latest_event_content, "setBackgroundResource",
4857                         0);
4858             }
4859         }
4860 
4861         /**
4862          * @param remoteView the remote view to update the minheight in
4863          * @param hasMinHeight does it have a mimHeight
4864          * @hide
4865          */
setContentMinHeight(RemoteViews remoteView, boolean hasMinHeight)4866         void setContentMinHeight(RemoteViews remoteView, boolean hasMinHeight) {
4867             int minHeight = 0;
4868             if (hasMinHeight) {
4869                 // we need to set the minHeight of the notification
4870                 minHeight = mContext.getResources().getDimensionPixelSize(
4871                         com.android.internal.R.dimen.notification_min_content_height);
4872             }
4873             remoteView.setInt(R.id.notification_main_column, "setMinimumHeight", minHeight);
4874         }
4875 
handleProgressBar(RemoteViews contentView, Bundle ex, StandardTemplateParams p)4876         private boolean handleProgressBar(RemoteViews contentView, Bundle ex,
4877                 StandardTemplateParams p) {
4878             final int max = ex.getInt(EXTRA_PROGRESS_MAX, 0);
4879             final int progress = ex.getInt(EXTRA_PROGRESS, 0);
4880             final boolean ind = ex.getBoolean(EXTRA_PROGRESS_INDETERMINATE);
4881             if (p.hasProgress && (max != 0 || ind)) {
4882                 contentView.setViewVisibility(com.android.internal.R.id.progress, View.VISIBLE);
4883                 contentView.setProgressBar(
4884                         R.id.progress, max, progress, ind);
4885                 contentView.setProgressBackgroundTintList(
4886                         R.id.progress, ColorStateList.valueOf(mContext.getColor(
4887                                 R.color.notification_progress_background_color)));
4888                 if (getRawColor(p) != COLOR_DEFAULT) {
4889                     int color = isColorized(p) ? getPrimaryTextColor(p) : resolveContrastColor(p);
4890                     ColorStateList colorStateList = ColorStateList.valueOf(color);
4891                     contentView.setProgressTintList(R.id.progress, colorStateList);
4892                     contentView.setProgressIndeterminateTintList(R.id.progress, colorStateList);
4893                 }
4894                 return true;
4895             } else {
4896                 contentView.setViewVisibility(R.id.progress, View.GONE);
4897                 return false;
4898             }
4899         }
4900 
bindLargeIconAndReply(RemoteViews contentView, StandardTemplateParams p, TemplateBindResult result)4901         private void bindLargeIconAndReply(RemoteViews contentView, StandardTemplateParams p,
4902                 TemplateBindResult result) {
4903             boolean largeIconShown = bindLargeIcon(contentView, p);
4904             boolean replyIconShown = bindReplyIcon(contentView, p);
4905             boolean iconContainerVisible = largeIconShown || replyIconShown;
4906             contentView.setViewVisibility(R.id.right_icon_container,
4907                     iconContainerVisible ? View.VISIBLE : View.GONE);
4908             int marginEnd = calculateMarginEnd(largeIconShown, replyIconShown);
4909             contentView.setViewLayoutMarginEnd(R.id.line1, marginEnd);
4910             contentView.setViewLayoutMarginEnd(R.id.text, marginEnd);
4911             contentView.setViewLayoutMarginEnd(R.id.progress, marginEnd);
4912             if (result != null) {
4913                 result.setIconMarginEnd(marginEnd);
4914                 result.setRightIconContainerVisible(iconContainerVisible);
4915             }
4916         }
4917 
calculateMarginEnd(boolean largeIconShown, boolean replyIconShown)4918         private int calculateMarginEnd(boolean largeIconShown, boolean replyIconShown) {
4919             int marginEnd = 0;
4920             int contentMargin = mContext.getResources().getDimensionPixelSize(
4921                     R.dimen.notification_content_margin_end);
4922             int iconSize = mContext.getResources().getDimensionPixelSize(
4923                     R.dimen.notification_right_icon_size);
4924             if (replyIconShown) {
4925                 // The size of the reply icon
4926                 marginEnd += iconSize;
4927 
4928                 int replyInset = mContext.getResources().getDimensionPixelSize(
4929                         R.dimen.notification_reply_inset);
4930                 // We're subtracting the inset of the reply icon to make sure it's
4931                 // aligned nicely on the right, and remove it from the following padding
4932                 marginEnd -= replyInset * 2;
4933             }
4934             if (largeIconShown) {
4935                 // adding size of the right icon
4936                 marginEnd += iconSize;
4937 
4938                 if (replyIconShown) {
4939                     // We also add some padding to the reply icon if it's around
4940                     marginEnd += contentMargin;
4941                 }
4942             }
4943             if (replyIconShown || largeIconShown) {
4944                 // The padding to the content
4945                 marginEnd += contentMargin;
4946             }
4947             return marginEnd;
4948         }
4949 
4950         /**
4951          * Bind the large icon.
4952          * @return if the largeIcon is visible
4953          */
bindLargeIcon(RemoteViews contentView, StandardTemplateParams p)4954         private boolean bindLargeIcon(RemoteViews contentView, StandardTemplateParams p) {
4955             if (mN.mLargeIcon == null && mN.largeIcon != null) {
4956                 mN.mLargeIcon = Icon.createWithBitmap(mN.largeIcon);
4957             }
4958             boolean showLargeIcon = mN.mLargeIcon != null && !p.hideLargeIcon;
4959             if (showLargeIcon) {
4960                 contentView.setViewVisibility(R.id.right_icon, View.VISIBLE);
4961                 contentView.setImageViewIcon(R.id.right_icon, mN.mLargeIcon);
4962                 processLargeLegacyIcon(mN.mLargeIcon, contentView, p);
4963             }
4964             return showLargeIcon;
4965         }
4966 
4967         /**
4968          * Bind the reply icon.
4969          * @return if the reply icon is visible
4970          */
bindReplyIcon(RemoteViews contentView, StandardTemplateParams p)4971         private boolean bindReplyIcon(RemoteViews contentView, StandardTemplateParams p) {
4972             boolean actionVisible = !p.hideReplyIcon;
4973             Action action = null;
4974             if (actionVisible) {
4975                 action = findReplyAction();
4976                 actionVisible = action != null;
4977             }
4978             if (actionVisible) {
4979                 contentView.setViewVisibility(R.id.reply_icon_action, View.VISIBLE);
4980                 contentView.setDrawableTint(R.id.reply_icon_action,
4981                         false /* targetBackground */,
4982                         getNeutralColor(p),
4983                         PorterDuff.Mode.SRC_ATOP);
4984                 contentView.setOnClickPendingIntent(R.id.reply_icon_action, action.actionIntent);
4985                 contentView.setRemoteInputs(R.id.reply_icon_action, action.mRemoteInputs);
4986             } else {
4987                 contentView.setRemoteInputs(R.id.reply_icon_action, null);
4988             }
4989             contentView.setViewVisibility(R.id.reply_icon_action,
4990                     actionVisible ? View.VISIBLE : View.GONE);
4991             return actionVisible;
4992         }
4993 
findReplyAction()4994         private Action findReplyAction() {
4995             ArrayList<Action> actions = mActions;
4996             if (mOriginalActions != null) {
4997                 actions = mOriginalActions;
4998             }
4999             int numActions = actions.size();
5000             for (int i = 0; i < numActions; i++) {
5001                 Action action = actions.get(i);
5002                 if (hasValidRemoteInput(action)) {
5003                     return action;
5004                 }
5005             }
5006             return null;
5007         }
5008 
bindNotificationHeader(RemoteViews contentView, StandardTemplateParams p)5009         private void bindNotificationHeader(RemoteViews contentView, StandardTemplateParams p) {
5010             bindSmallIcon(contentView, p);
5011             bindHeaderAppName(contentView, p);
5012             bindHeaderText(contentView, p);
5013             bindHeaderTextSecondary(contentView, p);
5014             bindHeaderChronometerAndTime(contentView, p);
5015             bindProfileBadge(contentView, p);
5016             bindAlertedIcon(contentView, p);
5017             bindActivePermissions(contentView, p);
5018             bindExpandButton(contentView, p);
5019             mN.mUsesStandardHeader = true;
5020         }
5021 
bindActivePermissions(RemoteViews contentView, StandardTemplateParams p)5022         private void bindActivePermissions(RemoteViews contentView, StandardTemplateParams p) {
5023             int color = getNeutralColor(p);
5024             contentView.setDrawableTint(R.id.camera, false, color, PorterDuff.Mode.SRC_ATOP);
5025             contentView.setDrawableTint(R.id.mic, false, color, PorterDuff.Mode.SRC_ATOP);
5026             contentView.setDrawableTint(R.id.overlay, false, color, PorterDuff.Mode.SRC_ATOP);
5027         }
5028 
bindExpandButton(RemoteViews contentView, StandardTemplateParams p)5029         private void bindExpandButton(RemoteViews contentView, StandardTemplateParams p) {
5030             int color = isColorized(p) ? getPrimaryTextColor(p) : getSecondaryTextColor(p);
5031             contentView.setDrawableTint(R.id.expand_button, false, color,
5032                     PorterDuff.Mode.SRC_ATOP);
5033             contentView.setInt(R.id.notification_header, "setOriginalNotificationColor",
5034                     color);
5035         }
5036 
bindHeaderChronometerAndTime(RemoteViews contentView, StandardTemplateParams p)5037         private void bindHeaderChronometerAndTime(RemoteViews contentView,
5038                 StandardTemplateParams p) {
5039             if (showsTimeOrChronometer()) {
5040                 contentView.setViewVisibility(R.id.time_divider, View.VISIBLE);
5041                 setTextViewColorSecondary(contentView, R.id.time_divider, p);
5042                 if (mN.extras.getBoolean(EXTRA_SHOW_CHRONOMETER)) {
5043                     contentView.setViewVisibility(R.id.chronometer, View.VISIBLE);
5044                     contentView.setLong(R.id.chronometer, "setBase",
5045                             mN.when + (SystemClock.elapsedRealtime() - System.currentTimeMillis()));
5046                     contentView.setBoolean(R.id.chronometer, "setStarted", true);
5047                     boolean countsDown = mN.extras.getBoolean(EXTRA_CHRONOMETER_COUNT_DOWN);
5048                     contentView.setChronometerCountDown(R.id.chronometer, countsDown);
5049                     setTextViewColorSecondary(contentView, R.id.chronometer, p);
5050                 } else {
5051                     contentView.setViewVisibility(R.id.time, View.VISIBLE);
5052                     contentView.setLong(R.id.time, "setTime", mN.when);
5053                     setTextViewColorSecondary(contentView, R.id.time, p);
5054                 }
5055             } else {
5056                 // We still want a time to be set but gone, such that we can show and hide it
5057                 // on demand in case it's a child notification without anything in the header
5058                 contentView.setLong(R.id.time, "setTime", mN.when != 0 ? mN.when : mN.creationTime);
5059             }
5060         }
5061 
bindHeaderText(RemoteViews contentView, StandardTemplateParams p)5062         private void bindHeaderText(RemoteViews contentView, StandardTemplateParams p) {
5063             CharSequence summaryText = p.summaryText;
5064             if (summaryText == null && mStyle != null && mStyle.mSummaryTextSet
5065                     && mStyle.hasSummaryInHeader()) {
5066                 summaryText = mStyle.mSummaryText;
5067             }
5068             if (summaryText == null
5069                     && mContext.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N
5070                     && mN.extras.getCharSequence(EXTRA_INFO_TEXT) != null) {
5071                 summaryText = mN.extras.getCharSequence(EXTRA_INFO_TEXT);
5072             }
5073             if (summaryText != null) {
5074                 // TODO: Remove the span entirely to only have the string with propper formating.
5075                 contentView.setTextViewText(R.id.header_text, processTextSpans(
5076                         processLegacyText(summaryText)));
5077                 setTextViewColorSecondary(contentView, R.id.header_text, p);
5078                 contentView.setViewVisibility(R.id.header_text, View.VISIBLE);
5079                 contentView.setViewVisibility(R.id.header_text_divider, View.VISIBLE);
5080                 setTextViewColorSecondary(contentView, R.id.header_text_divider, p);
5081             }
5082         }
5083 
bindHeaderTextSecondary(RemoteViews contentView, StandardTemplateParams p)5084         private void bindHeaderTextSecondary(RemoteViews contentView, StandardTemplateParams p) {
5085             if (!TextUtils.isEmpty(p.headerTextSecondary)) {
5086                 contentView.setTextViewText(R.id.header_text_secondary, processTextSpans(
5087                         processLegacyText(p.headerTextSecondary)));
5088                 setTextViewColorSecondary(contentView, R.id.header_text_secondary, p);
5089                 contentView.setViewVisibility(R.id.header_text_secondary, View.VISIBLE);
5090                 contentView.setViewVisibility(R.id.header_text_secondary_divider, View.VISIBLE);
5091                 setTextViewColorSecondary(contentView, R.id.header_text_secondary_divider, p);
5092             }
5093         }
5094 
5095         /**
5096          * @hide
5097          */
5098         @UnsupportedAppUsage
loadHeaderAppName()5099         public String loadHeaderAppName() {
5100             CharSequence name = null;
5101             final PackageManager pm = mContext.getPackageManager();
5102             if (mN.extras.containsKey(EXTRA_SUBSTITUTE_APP_NAME)) {
5103                 // only system packages which lump together a bunch of unrelated stuff
5104                 // may substitute a different name to make the purpose of the
5105                 // notification more clear. the correct package label should always
5106                 // be accessible via SystemUI.
5107                 final String pkg = mContext.getPackageName();
5108                 final String subName = mN.extras.getString(EXTRA_SUBSTITUTE_APP_NAME);
5109                 if (PackageManager.PERMISSION_GRANTED == pm.checkPermission(
5110                         android.Manifest.permission.SUBSTITUTE_NOTIFICATION_APP_NAME, pkg)) {
5111                     name = subName;
5112                 } else {
5113                     Log.w(TAG, "warning: pkg "
5114                             + pkg + " attempting to substitute app name '" + subName
5115                             + "' without holding perm "
5116                             + android.Manifest.permission.SUBSTITUTE_NOTIFICATION_APP_NAME);
5117                 }
5118             }
5119             if (TextUtils.isEmpty(name)) {
5120                 name = pm.getApplicationLabel(mContext.getApplicationInfo());
5121             }
5122             if (TextUtils.isEmpty(name)) {
5123                 // still nothing?
5124                 return null;
5125             }
5126 
5127             return String.valueOf(name);
5128         }
bindHeaderAppName(RemoteViews contentView, StandardTemplateParams p)5129         private void bindHeaderAppName(RemoteViews contentView, StandardTemplateParams p) {
5130             contentView.setTextViewText(R.id.app_name_text, loadHeaderAppName());
5131             if (isColorized(p)) {
5132                 setTextViewColorPrimary(contentView, R.id.app_name_text, p);
5133             } else {
5134                 contentView.setTextColor(R.id.app_name_text, getSecondaryTextColor(p));
5135             }
5136         }
5137 
isColorized(StandardTemplateParams p)5138         private boolean isColorized(StandardTemplateParams p) {
5139             return p.allowColorization && mN.isColorized();
5140         }
5141 
bindSmallIcon(RemoteViews contentView, StandardTemplateParams p)5142         private void bindSmallIcon(RemoteViews contentView, StandardTemplateParams p) {
5143             if (mN.mSmallIcon == null && mN.icon != 0) {
5144                 mN.mSmallIcon = Icon.createWithResource(mContext, mN.icon);
5145             }
5146             contentView.setImageViewIcon(R.id.icon, mN.mSmallIcon);
5147             contentView.setInt(R.id.icon, "setImageLevel", mN.iconLevel);
5148             processSmallIconColor(mN.mSmallIcon, contentView, p);
5149         }
5150 
5151         /**
5152          * @return true if the built notification will show the time or the chronometer; false
5153          *         otherwise
5154          */
showsTimeOrChronometer()5155         private boolean showsTimeOrChronometer() {
5156             return mN.showsTime() || mN.showsChronometer();
5157         }
5158 
resetStandardTemplateWithActions(RemoteViews big)5159         private void resetStandardTemplateWithActions(RemoteViews big) {
5160             // actions_container is only reset when there are no actions to avoid focus issues with
5161             // remote inputs.
5162             big.setViewVisibility(R.id.actions, View.GONE);
5163             big.removeAllViews(R.id.actions);
5164 
5165             big.setViewVisibility(R.id.notification_material_reply_container, View.GONE);
5166             big.setTextViewText(R.id.notification_material_reply_text_1, null);
5167             big.setViewVisibility(R.id.notification_material_reply_text_1_container, View.GONE);
5168             big.setViewVisibility(R.id.notification_material_reply_progress, View.GONE);
5169 
5170             big.setViewVisibility(R.id.notification_material_reply_text_2, View.GONE);
5171             big.setTextViewText(R.id.notification_material_reply_text_2, null);
5172             big.setViewVisibility(R.id.notification_material_reply_text_3, View.GONE);
5173             big.setTextViewText(R.id.notification_material_reply_text_3, null);
5174 
5175             big.setViewLayoutMarginBottomDimen(R.id.notification_action_list_margin_target,
5176                     R.dimen.notification_content_margin);
5177         }
5178 
applyStandardTemplateWithActions(int layoutId, TemplateBindResult result)5179         private RemoteViews applyStandardTemplateWithActions(int layoutId,
5180                 TemplateBindResult result) {
5181             return applyStandardTemplateWithActions(layoutId, mParams.reset().fillTextsFrom(this),
5182                     result);
5183         }
5184 
filterOutContextualActions( List<Notification.Action> actions)5185         private static List<Notification.Action> filterOutContextualActions(
5186                 List<Notification.Action> actions) {
5187             List<Notification.Action> nonContextualActions = new ArrayList<>();
5188             for (Notification.Action action : actions) {
5189                 if (!action.isContextual()) {
5190                     nonContextualActions.add(action);
5191                 }
5192             }
5193             return nonContextualActions;
5194         }
5195 
applyStandardTemplateWithActions(int layoutId, StandardTemplateParams p, TemplateBindResult result)5196         private RemoteViews applyStandardTemplateWithActions(int layoutId,
5197                 StandardTemplateParams p, TemplateBindResult result) {
5198             RemoteViews big = applyStandardTemplate(layoutId, p, result);
5199 
5200             resetStandardTemplateWithActions(big);
5201 
5202             boolean validRemoteInput = false;
5203 
5204             // In the UI contextual actions appear separately from the standard actions, so we
5205             // filter them out here.
5206             List<Notification.Action> nonContextualActions = filterOutContextualActions(mActions);
5207 
5208             int N = nonContextualActions.size();
5209             boolean emphazisedMode = mN.fullScreenIntent != null;
5210             big.setBoolean(R.id.actions, "setEmphasizedMode", emphazisedMode);
5211             if (N > 0) {
5212                 big.setViewVisibility(R.id.actions_container, View.VISIBLE);
5213                 big.setViewVisibility(R.id.actions, View.VISIBLE);
5214                 big.setViewLayoutMarginBottomDimen(R.id.notification_action_list_margin_target, 0);
5215                 if (N>MAX_ACTION_BUTTONS) N=MAX_ACTION_BUTTONS;
5216                 for (int i=0; i<N; i++) {
5217                     Action action = nonContextualActions.get(i);
5218 
5219                     boolean actionHasValidInput = hasValidRemoteInput(action);
5220                     validRemoteInput |= actionHasValidInput;
5221 
5222                     final RemoteViews button = generateActionButton(action, emphazisedMode, p);
5223                     if (actionHasValidInput && !emphazisedMode) {
5224                         // Clear the drawable
5225                         button.setInt(R.id.action0, "setBackgroundResource", 0);
5226                     }
5227                     big.addView(R.id.actions, button);
5228                 }
5229             } else {
5230                 big.setViewVisibility(R.id.actions_container, View.GONE);
5231             }
5232 
5233             CharSequence[] replyText = mN.extras.getCharSequenceArray(EXTRA_REMOTE_INPUT_HISTORY);
5234             if (validRemoteInput && replyText != null
5235                     && replyText.length > 0 && !TextUtils.isEmpty(replyText[0])
5236                     && p.maxRemoteInputHistory > 0) {
5237                 boolean showSpinner = mN.extras.getBoolean(EXTRA_SHOW_REMOTE_INPUT_SPINNER);
5238                 big.setViewVisibility(R.id.notification_material_reply_container, View.VISIBLE);
5239                 big.setViewVisibility(R.id.notification_material_reply_text_1_container,
5240                         View.VISIBLE);
5241                 big.setTextViewText(R.id.notification_material_reply_text_1,
5242                         processTextSpans(replyText[0]));
5243                 setTextViewColorSecondary(big, R.id.notification_material_reply_text_1, p);
5244                 big.setViewVisibility(R.id.notification_material_reply_progress,
5245                         showSpinner ? View.VISIBLE : View.GONE);
5246                 big.setProgressIndeterminateTintList(
5247                         R.id.notification_material_reply_progress,
5248                         ColorStateList.valueOf(
5249                                 isColorized(p) ? getPrimaryTextColor(p) : resolveContrastColor(p)));
5250 
5251                 if (replyText.length > 1 && !TextUtils.isEmpty(replyText[1])
5252                         && p.maxRemoteInputHistory > 1) {
5253                     big.setViewVisibility(R.id.notification_material_reply_text_2, View.VISIBLE);
5254                     big.setTextViewText(R.id.notification_material_reply_text_2,
5255                             processTextSpans(replyText[1]));
5256                     setTextViewColorSecondary(big, R.id.notification_material_reply_text_2, p);
5257 
5258                     if (replyText.length > 2 && !TextUtils.isEmpty(replyText[2])
5259                             && p.maxRemoteInputHistory > 2) {
5260                         big.setViewVisibility(
5261                                 R.id.notification_material_reply_text_3, View.VISIBLE);
5262                         big.setTextViewText(R.id.notification_material_reply_text_3,
5263                                 processTextSpans(replyText[2]));
5264                         setTextViewColorSecondary(big, R.id.notification_material_reply_text_3, p);
5265                     }
5266                 }
5267             }
5268 
5269             return big;
5270         }
5271 
hasValidRemoteInput(Action action)5272         private boolean hasValidRemoteInput(Action action) {
5273             if (TextUtils.isEmpty(action.title) || action.actionIntent == null) {
5274                 // Weird actions
5275                 return false;
5276             }
5277 
5278             RemoteInput[] remoteInputs = action.getRemoteInputs();
5279             if (remoteInputs == null) {
5280                 return false;
5281             }
5282 
5283             for (RemoteInput r : remoteInputs) {
5284                 CharSequence[] choices = r.getChoices();
5285                 if (r.getAllowFreeFormInput() || (choices != null && choices.length != 0)) {
5286                     return true;
5287                 }
5288             }
5289             return false;
5290         }
5291 
5292         /**
5293          * Construct a RemoteViews for the final 1U notification layout. In order:
5294          *   1. Custom contentView from the caller
5295          *   2. Style's proposed content view
5296          *   3. Standard template view
5297          */
createContentView()5298         public RemoteViews createContentView() {
5299             return createContentView(false /* increasedheight */ );
5300         }
5301 
5302         /**
5303          * Construct a RemoteViews for the smaller content view.
5304          *
5305          *   @param increasedHeight true if this layout be created with an increased height. Some
5306          *   styles may support showing more then just that basic 1U size
5307          *   and the system may decide to render important notifications
5308          *   slightly bigger even when collapsed.
5309          *
5310          *   @hide
5311          */
createContentView(boolean increasedHeight)5312         public RemoteViews createContentView(boolean increasedHeight) {
5313             if (mN.contentView != null && useExistingRemoteView()) {
5314                 return mN.contentView;
5315             } else if (mStyle != null) {
5316                 final RemoteViews styleView = mStyle.makeContentView(increasedHeight);
5317                 if (styleView != null) {
5318                     return styleView;
5319                 }
5320             }
5321             return applyStandardTemplate(getBaseLayoutResource(), null /* result */);
5322         }
5323 
useExistingRemoteView()5324         private boolean useExistingRemoteView() {
5325             return mStyle == null || (!mStyle.displayCustomViewInline()
5326                     && !mRebuildStyledRemoteViews);
5327         }
5328 
5329         /**
5330          * Construct a RemoteViews for the final big notification layout.
5331          */
createBigContentView()5332         public RemoteViews createBigContentView() {
5333             RemoteViews result = null;
5334             if (mN.bigContentView != null && useExistingRemoteView()) {
5335                 return mN.bigContentView;
5336             } else if (mStyle != null) {
5337                 result = mStyle.makeBigContentView();
5338                 hideLine1Text(result);
5339             } else if (mActions.size() != 0) {
5340                 result = applyStandardTemplateWithActions(getBigBaseLayoutResource(),
5341                         null /* result */);
5342             }
5343             makeHeaderExpanded(result);
5344             return result;
5345         }
5346 
5347         /**
5348          * Construct a RemoteViews for the final notification header only. This will not be
5349          * colorized.
5350          *
5351          * @hide
5352          */
makeNotificationHeader()5353         public RemoteViews makeNotificationHeader() {
5354             return makeNotificationHeader(mParams.reset().fillTextsFrom(this));
5355         }
5356 
5357         /**
5358          * Construct a RemoteViews for the final notification header only. This will not be
5359          * colorized.
5360          *
5361          * @param p the template params to inflate this with
5362          */
makeNotificationHeader(StandardTemplateParams p)5363         private RemoteViews makeNotificationHeader(StandardTemplateParams p) {
5364             // Headers on their own are never colorized
5365             p.disallowColorization();
5366             RemoteViews header = new BuilderRemoteViews(mContext.getApplicationInfo(),
5367                     R.layout.notification_template_header);
5368             resetNotificationHeader(header);
5369             bindNotificationHeader(header, p);
5370             return header;
5371         }
5372 
5373         /**
5374          * Construct a RemoteViews for the ambient version of the notification.
5375          *
5376          * @hide
5377          */
makeAmbientNotification()5378         public RemoteViews makeAmbientNotification() {
5379             RemoteViews headsUpContentView = createHeadsUpContentView(false /* increasedHeight */);
5380             if (headsUpContentView != null) {
5381                 return headsUpContentView;
5382             }
5383             return createContentView();
5384         }
5385 
hideLine1Text(RemoteViews result)5386         private void hideLine1Text(RemoteViews result) {
5387             if (result != null) {
5388                 result.setViewVisibility(R.id.text_line_1, View.GONE);
5389             }
5390         }
5391 
5392         /**
5393          * Adapt the Notification header if this view is used as an expanded view.
5394          *
5395          * @hide
5396          */
makeHeaderExpanded(RemoteViews result)5397         public static void makeHeaderExpanded(RemoteViews result) {
5398             if (result != null) {
5399                 result.setBoolean(R.id.notification_header, "setExpanded", true);
5400             }
5401         }
5402 
5403         /**
5404          * Construct a RemoteViews for the final heads-up notification layout.
5405          *
5406          * @param increasedHeight true if this layout be created with an increased height. Some
5407          * styles may support showing more then just that basic 1U size
5408          * and the system may decide to render important notifications
5409          * slightly bigger even when collapsed.
5410          *
5411          * @hide
5412          */
createHeadsUpContentView(boolean increasedHeight)5413         public RemoteViews createHeadsUpContentView(boolean increasedHeight) {
5414             if (mN.headsUpContentView != null && useExistingRemoteView()) {
5415                 return mN.headsUpContentView;
5416             } else if (mStyle != null) {
5417                 final RemoteViews styleView = mStyle.makeHeadsUpContentView(increasedHeight);
5418                 if (styleView != null) {
5419                     return styleView;
5420                 }
5421             } else if (mActions.size() == 0) {
5422                 return null;
5423             }
5424 
5425             // We only want at most a single remote input history to be shown here, otherwise
5426             // the content would become squished.
5427             StandardTemplateParams p = mParams.reset().fillTextsFrom(this)
5428                     .setMaxRemoteInputHistory(1);
5429             return applyStandardTemplateWithActions(getBigBaseLayoutResource(),
5430                     p,
5431                     null /* result */);
5432         }
5433 
5434         /**
5435          * Construct a RemoteViews for the final heads-up notification layout.
5436          */
createHeadsUpContentView()5437         public RemoteViews createHeadsUpContentView() {
5438             return createHeadsUpContentView(false /* useIncreasedHeight */);
5439         }
5440 
5441         /**
5442          * Construct a RemoteViews for the display in public contexts like on the lockscreen.
5443          *
5444          * @hide
5445          */
5446         @UnsupportedAppUsage
makePublicContentView()5447         public RemoteViews makePublicContentView() {
5448             return makePublicView(false /* ambient */);
5449         }
5450 
5451         /**
5452          * Construct a RemoteViews for the display in public contexts like on the lockscreen.
5453          *
5454          * @hide
5455          */
makePublicAmbientNotification()5456         public RemoteViews makePublicAmbientNotification() {
5457             return makePublicView(true /* ambient */);
5458         }
5459 
makePublicView(boolean ambient)5460         private RemoteViews makePublicView(boolean ambient) {
5461             if (mN.publicVersion != null) {
5462                 final Builder builder = recoverBuilder(mContext, mN.publicVersion);
5463                 return ambient ? builder.makeAmbientNotification() : builder.createContentView();
5464             }
5465             Bundle savedBundle = mN.extras;
5466             Style style = mStyle;
5467             mStyle = null;
5468             Icon largeIcon = mN.mLargeIcon;
5469             mN.mLargeIcon = null;
5470             Bitmap largeIconLegacy = mN.largeIcon;
5471             mN.largeIcon = null;
5472             ArrayList<Action> actions = mActions;
5473             mActions = new ArrayList<>();
5474             Bundle publicExtras = new Bundle();
5475             publicExtras.putBoolean(EXTRA_SHOW_WHEN,
5476                     savedBundle.getBoolean(EXTRA_SHOW_WHEN));
5477             publicExtras.putBoolean(EXTRA_SHOW_CHRONOMETER,
5478                     savedBundle.getBoolean(EXTRA_SHOW_CHRONOMETER));
5479             publicExtras.putBoolean(EXTRA_CHRONOMETER_COUNT_DOWN,
5480                     savedBundle.getBoolean(EXTRA_CHRONOMETER_COUNT_DOWN));
5481             String appName = savedBundle.getString(EXTRA_SUBSTITUTE_APP_NAME);
5482             if (appName != null) {
5483                 publicExtras.putString(EXTRA_SUBSTITUTE_APP_NAME, appName);
5484             }
5485             mN.extras = publicExtras;
5486             RemoteViews view;
5487             view = makeNotificationHeader();
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#Notification.MessagingStyle.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#Notification.MessagingStyle.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             RemoteViews expanded = makeMediaBigContentView();
8122             return expanded != null ? expanded : makeMediaContentView();
8123         }
8124 
8125         /** @hide */
8126         @Override
addExtras(Bundle extras)8127         public void addExtras(Bundle extras) {
8128             super.addExtras(extras);
8129 
8130             if (mToken != null) {
8131                 extras.putParcelable(EXTRA_MEDIA_SESSION, mToken);
8132             }
8133             if (mActionsToShowInCompact != null) {
8134                 extras.putIntArray(EXTRA_COMPACT_ACTIONS, mActionsToShowInCompact);
8135             }
8136         }
8137 
8138         /**
8139          * @hide
8140          */
8141         @Override
restoreFromExtras(Bundle extras)8142         protected void restoreFromExtras(Bundle extras) {
8143             super.restoreFromExtras(extras);
8144 
8145             if (extras.containsKey(EXTRA_MEDIA_SESSION)) {
8146                 mToken = extras.getParcelable(EXTRA_MEDIA_SESSION);
8147             }
8148             if (extras.containsKey(EXTRA_COMPACT_ACTIONS)) {
8149                 mActionsToShowInCompact = extras.getIntArray(EXTRA_COMPACT_ACTIONS);
8150             }
8151         }
8152 
8153         /**
8154          * @hide
8155          */
8156         @Override
areNotificationsVisiblyDifferent(Style other)8157         public boolean areNotificationsVisiblyDifferent(Style other) {
8158             if (other == null || getClass() != other.getClass()) {
8159                 return true;
8160             }
8161             // All fields to compare are on the Notification object
8162             return false;
8163         }
8164 
bindMediaActionButton(RemoteViews container, @IdRes int buttonId, Action action, StandardTemplateParams p)8165         private void bindMediaActionButton(RemoteViews container, @IdRes int buttonId,
8166                 Action action, StandardTemplateParams p) {
8167             final boolean tombstone = (action.actionIntent == null);
8168             container.setViewVisibility(buttonId, View.VISIBLE);
8169             container.setImageViewIcon(buttonId, action.getIcon());
8170 
8171             // If the action buttons should not be tinted, then just use the default
8172             // notification color. Otherwise, just use the passed-in color.
8173             Resources resources = mBuilder.mContext.getResources();
8174             Configuration currentConfig = resources.getConfiguration();
8175             boolean inNightMode = (currentConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK)
8176                     == Configuration.UI_MODE_NIGHT_YES;
8177             int tintColor = mBuilder.shouldTintActionButtons() || mBuilder.isColorized(p)
8178                     ? getActionColor(p)
8179                     : ContrastColorUtil.resolveColor(mBuilder.mContext,
8180                             Notification.COLOR_DEFAULT, inNightMode);
8181 
8182             container.setDrawableTint(buttonId, false, tintColor,
8183                     PorterDuff.Mode.SRC_ATOP);
8184 
8185             final TypedArray typedArray = mBuilder.mContext.obtainStyledAttributes(
8186                     new int[]{ android.R.attr.colorControlHighlight });
8187             int rippleAlpha = Color.alpha(typedArray.getColor(0, 0));
8188             typedArray.recycle();
8189             int rippleColor = Color.argb(rippleAlpha, Color.red(tintColor), Color.green(tintColor),
8190                     Color.blue(tintColor));
8191             container.setRippleDrawableColor(buttonId, ColorStateList.valueOf(rippleColor));
8192 
8193             if (!tombstone) {
8194                 container.setOnClickPendingIntent(buttonId, action.actionIntent);
8195             }
8196             container.setContentDescription(buttonId, action.title);
8197         }
8198 
makeMediaContentView()8199         private RemoteViews makeMediaContentView() {
8200             StandardTemplateParams p = mBuilder.mParams.reset().hasProgress(false).fillTextsFrom(
8201                     mBuilder);
8202             RemoteViews view = mBuilder.applyStandardTemplate(
8203                     R.layout.notification_template_material_media, p,
8204                     null /* result */);
8205 
8206             final int numActions = mBuilder.mActions.size();
8207             final int numActionsToShow = mActionsToShowInCompact == null
8208                     ? 0
8209                     : Math.min(mActionsToShowInCompact.length, MAX_MEDIA_BUTTONS_IN_COMPACT);
8210             if (numActionsToShow > numActions) {
8211                 throw new IllegalArgumentException(String.format(
8212                         "setShowActionsInCompactView: action %d out of bounds (max %d)",
8213                         numActions, numActions - 1));
8214             }
8215             for (int i = 0; i < MAX_MEDIA_BUTTONS_IN_COMPACT; i++) {
8216                 if (i < numActionsToShow) {
8217                     final Action action = mBuilder.mActions.get(mActionsToShowInCompact[i]);
8218                     bindMediaActionButton(view, MEDIA_BUTTON_IDS[i], action, p);
8219                 } else {
8220                     view.setViewVisibility(MEDIA_BUTTON_IDS[i], View.GONE);
8221                 }
8222             }
8223             handleImage(view);
8224             // handle the content margin
8225             int endMargin = R.dimen.notification_content_margin_end;
8226             if (mBuilder.mN.hasLargeIcon()) {
8227                 endMargin = R.dimen.notification_media_image_margin_end;
8228             }
8229             view.setViewLayoutMarginEndDimen(R.id.notification_main_column, endMargin);
8230             return view;
8231         }
8232 
getActionColor(StandardTemplateParams p)8233         private int getActionColor(StandardTemplateParams p) {
8234             return mBuilder.isColorized(p) ? mBuilder.getPrimaryTextColor(p)
8235                     : mBuilder.resolveContrastColor(p);
8236         }
8237 
makeMediaBigContentView()8238         private RemoteViews makeMediaBigContentView() {
8239             final int actionCount = Math.min(mBuilder.mActions.size(), MAX_MEDIA_BUTTONS);
8240             // Dont add an expanded view if there is no more content to be revealed
8241             int actionsInCompact = mActionsToShowInCompact == null
8242                     ? 0
8243                     : Math.min(mActionsToShowInCompact.length, MAX_MEDIA_BUTTONS_IN_COMPACT);
8244             if (!mBuilder.mN.hasLargeIcon() && actionCount <= actionsInCompact) {
8245                 return null;
8246             }
8247             StandardTemplateParams p = mBuilder.mParams.reset().hasProgress(false).fillTextsFrom(
8248                     mBuilder);
8249             RemoteViews big = mBuilder.applyStandardTemplate(
8250                     R.layout.notification_template_material_big_media, p , null /* result */);
8251 
8252             for (int i = 0; i < MAX_MEDIA_BUTTONS; i++) {
8253                 if (i < actionCount) {
8254                     bindMediaActionButton(big, MEDIA_BUTTON_IDS[i], mBuilder.mActions.get(i), p);
8255                 } else {
8256                     big.setViewVisibility(MEDIA_BUTTON_IDS[i], View.GONE);
8257                 }
8258             }
8259             bindMediaActionButton(big, R.id.media_seamless, new Action(R.drawable.ic_media_seamless,
8260                     mBuilder.mContext.getString(
8261                             com.android.internal.R.string.ext_media_seamless_action), null), p);
8262             big.setViewVisibility(R.id.media_seamless, View.GONE);
8263             handleImage(big);
8264             return big;
8265         }
8266 
handleImage(RemoteViews contentView)8267         private void handleImage(RemoteViews contentView) {
8268             if (mBuilder.mN.hasLargeIcon()) {
8269                 contentView.setViewLayoutMarginEndDimen(R.id.line1, 0);
8270                 contentView.setViewLayoutMarginEndDimen(R.id.text, 0);
8271             }
8272         }
8273 
8274         /**
8275          * @hide
8276          */
8277         @Override
hasProgress()8278         protected boolean hasProgress() {
8279             return false;
8280         }
8281     }
8282 
8283     /**
8284      * Notification style for custom views that are decorated by the system
8285      *
8286      * <p>Instead of providing a notification that is completely custom, a developer can set this
8287      * style and still obtain system decorations like the notification header with the expand
8288      * affordance and actions.
8289      *
8290      * <p>Use {@link android.app.Notification.Builder#setCustomContentView(RemoteViews)},
8291      * {@link android.app.Notification.Builder#setCustomBigContentView(RemoteViews)} and
8292      * {@link android.app.Notification.Builder#setCustomHeadsUpContentView(RemoteViews)} to set the
8293      * corresponding custom views to display.
8294      *
8295      * To use this style with your Notification, feed it to
8296      * {@link Notification.Builder#setStyle(android.app.Notification.Style)} like so:
8297      * <pre class="prettyprint">
8298      * Notification noti = new Notification.Builder()
8299      *     .setSmallIcon(R.drawable.ic_stat_player)
8300      *     .setLargeIcon(albumArtBitmap))
8301      *     .setCustomContentView(contentView);
8302      *     .setStyle(<b>new Notification.DecoratedCustomViewStyle()</b>)
8303      *     .build();
8304      * </pre>
8305      */
8306     public static class DecoratedCustomViewStyle extends Style {
8307 
DecoratedCustomViewStyle()8308         public DecoratedCustomViewStyle() {
8309         }
8310 
8311         /**
8312          * @hide
8313          */
displayCustomViewInline()8314         public boolean displayCustomViewInline() {
8315             return true;
8316         }
8317 
8318         /**
8319          * @hide
8320          */
8321         @Override
makeContentView(boolean increasedHeight)8322         public RemoteViews makeContentView(boolean increasedHeight) {
8323             return makeStandardTemplateWithCustomContent(mBuilder.mN.contentView);
8324         }
8325 
8326         /**
8327          * @hide
8328          */
8329         @Override
makeBigContentView()8330         public RemoteViews makeBigContentView() {
8331             return makeDecoratedBigContentView();
8332         }
8333 
8334         /**
8335          * @hide
8336          */
8337         @Override
makeHeadsUpContentView(boolean increasedHeight)8338         public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
8339             return makeDecoratedHeadsUpContentView();
8340         }
8341 
makeDecoratedHeadsUpContentView()8342         private RemoteViews makeDecoratedHeadsUpContentView() {
8343             RemoteViews headsUpContentView = mBuilder.mN.headsUpContentView == null
8344                     ? mBuilder.mN.contentView
8345                     : mBuilder.mN.headsUpContentView;
8346             if (mBuilder.mActions.size() == 0) {
8347                return makeStandardTemplateWithCustomContent(headsUpContentView);
8348             }
8349             TemplateBindResult result = new TemplateBindResult();
8350             RemoteViews remoteViews = mBuilder.applyStandardTemplateWithActions(
8351                         mBuilder.getBigBaseLayoutResource(), result);
8352             buildIntoRemoteViewContent(remoteViews, headsUpContentView, result);
8353             return remoteViews;
8354         }
8355 
makeStandardTemplateWithCustomContent(RemoteViews customContent)8356         private RemoteViews makeStandardTemplateWithCustomContent(RemoteViews customContent) {
8357             TemplateBindResult result = new TemplateBindResult();
8358             RemoteViews remoteViews = mBuilder.applyStandardTemplate(
8359                     mBuilder.getBaseLayoutResource(), result);
8360             buildIntoRemoteViewContent(remoteViews, customContent, result);
8361             return remoteViews;
8362         }
8363 
makeDecoratedBigContentView()8364         private RemoteViews makeDecoratedBigContentView() {
8365             RemoteViews bigContentView = mBuilder.mN.bigContentView == null
8366                     ? mBuilder.mN.contentView
8367                     : mBuilder.mN.bigContentView;
8368             if (mBuilder.mActions.size() == 0) {
8369                 return makeStandardTemplateWithCustomContent(bigContentView);
8370             }
8371             TemplateBindResult result = new TemplateBindResult();
8372             RemoteViews remoteViews = mBuilder.applyStandardTemplateWithActions(
8373                     mBuilder.getBigBaseLayoutResource(), result);
8374             buildIntoRemoteViewContent(remoteViews, bigContentView, result);
8375             return remoteViews;
8376         }
8377 
buildIntoRemoteViewContent(RemoteViews remoteViews, RemoteViews customContent, TemplateBindResult result)8378         private void buildIntoRemoteViewContent(RemoteViews remoteViews,
8379                 RemoteViews customContent, TemplateBindResult result) {
8380             int childIndex = -1;
8381             if (customContent != null) {
8382                 // Need to clone customContent before adding, because otherwise it can no longer be
8383                 // parceled independently of remoteViews.
8384                 customContent = customContent.clone();
8385                 remoteViews.removeAllViewsExceptId(R.id.notification_main_column, R.id.progress);
8386                 remoteViews.addView(R.id.notification_main_column, customContent, 0 /* index */);
8387                 remoteViews.addFlags(RemoteViews.FLAG_REAPPLY_DISALLOWED);
8388                 childIndex = 0;
8389             }
8390             remoteViews.setIntTag(R.id.notification_main_column,
8391                     com.android.internal.R.id.notification_custom_view_index_tag,
8392                     childIndex);
8393             // also update the end margin if there is an image
8394             Resources resources = mBuilder.mContext.getResources();
8395             int endMargin = resources.getDimensionPixelSize(
8396                     R.dimen.notification_content_margin_end) + result.getIconMarginEnd();
8397             remoteViews.setViewLayoutMarginEnd(R.id.notification_main_column, endMargin);
8398         }
8399 
8400         /**
8401          * @hide
8402          */
8403         @Override
areNotificationsVisiblyDifferent(Style other)8404         public boolean areNotificationsVisiblyDifferent(Style other) {
8405             if (other == null || getClass() != other.getClass()) {
8406                 return true;
8407             }
8408             // Comparison done for all custom RemoteViews, independent of style
8409             return false;
8410         }
8411     }
8412 
8413     /**
8414      * Notification style for media custom views that are decorated by the system
8415      *
8416      * <p>Instead of providing a media notification that is completely custom, a developer can set
8417      * this style and still obtain system decorations like the notification header with the expand
8418      * affordance and actions.
8419      *
8420      * <p>Use {@link android.app.Notification.Builder#setCustomContentView(RemoteViews)},
8421      * {@link android.app.Notification.Builder#setCustomBigContentView(RemoteViews)} and
8422      * {@link android.app.Notification.Builder#setCustomHeadsUpContentView(RemoteViews)} to set the
8423      * corresponding custom views to display.
8424      * <p>
8425      * Contrary to {@link MediaStyle} a developer has to opt-in to the colorizing of the
8426      * notification by using {@link Notification.Builder#setColorized(boolean)}.
8427      * <p>
8428      * To use this style with your Notification, feed it to
8429      * {@link Notification.Builder#setStyle(android.app.Notification.Style)} like so:
8430      * <pre class="prettyprint">
8431      * Notification noti = new Notification.Builder()
8432      *     .setSmallIcon(R.drawable.ic_stat_player)
8433      *     .setLargeIcon(albumArtBitmap))
8434      *     .setCustomContentView(contentView);
8435      *     .setStyle(<b>new Notification.DecoratedMediaCustomViewStyle()</b>
8436      *          .setMediaSession(mySession))
8437      *     .build();
8438      * </pre>
8439      *
8440      * @see android.app.Notification.DecoratedCustomViewStyle
8441      * @see android.app.Notification.MediaStyle
8442      */
8443     public static class DecoratedMediaCustomViewStyle extends MediaStyle {
8444 
DecoratedMediaCustomViewStyle()8445         public DecoratedMediaCustomViewStyle() {
8446         }
8447 
8448         /**
8449          * @hide
8450          */
displayCustomViewInline()8451         public boolean displayCustomViewInline() {
8452             return true;
8453         }
8454 
8455         /**
8456          * @hide
8457          */
8458         @Override
makeContentView(boolean increasedHeight)8459         public RemoteViews makeContentView(boolean increasedHeight) {
8460             RemoteViews remoteViews = super.makeContentView(false /* increasedHeight */);
8461             return buildIntoRemoteView(remoteViews, R.id.notification_content_container,
8462                     mBuilder.mN.contentView);
8463         }
8464 
8465         /**
8466          * @hide
8467          */
8468         @Override
makeBigContentView()8469         public RemoteViews makeBigContentView() {
8470             RemoteViews customRemoteView = mBuilder.mN.bigContentView != null
8471                     ? mBuilder.mN.bigContentView
8472                     : mBuilder.mN.contentView;
8473             return makeBigContentViewWithCustomContent(customRemoteView);
8474         }
8475 
makeBigContentViewWithCustomContent(RemoteViews customRemoteView)8476         private RemoteViews makeBigContentViewWithCustomContent(RemoteViews customRemoteView) {
8477             RemoteViews remoteViews = super.makeBigContentView();
8478             if (remoteViews != null) {
8479                 return buildIntoRemoteView(remoteViews, R.id.notification_main_column,
8480                         customRemoteView);
8481             } else if (customRemoteView != mBuilder.mN.contentView){
8482                 remoteViews = super.makeContentView(false /* increasedHeight */);
8483                 return buildIntoRemoteView(remoteViews, R.id.notification_content_container,
8484                         customRemoteView);
8485             } else {
8486                 return null;
8487             }
8488         }
8489 
8490         /**
8491          * @hide
8492          */
8493         @Override
makeHeadsUpContentView(boolean increasedHeight)8494         public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
8495             RemoteViews customRemoteView = mBuilder.mN.headsUpContentView != null
8496                     ? mBuilder.mN.headsUpContentView
8497                     : mBuilder.mN.contentView;
8498             return makeBigContentViewWithCustomContent(customRemoteView);
8499         }
8500 
8501         /**
8502          * @hide
8503          */
8504         @Override
areNotificationsVisiblyDifferent(Style other)8505         public boolean areNotificationsVisiblyDifferent(Style other) {
8506             if (other == null || getClass() != other.getClass()) {
8507                 return true;
8508             }
8509             // Comparison done for all custom RemoteViews, independent of style
8510             return false;
8511         }
8512 
buildIntoRemoteView(RemoteViews remoteViews, int id, RemoteViews customContent)8513         private RemoteViews buildIntoRemoteView(RemoteViews remoteViews, int id,
8514                 RemoteViews customContent) {
8515             if (customContent != null) {
8516                 // Need to clone customContent before adding, because otherwise it can no longer be
8517                 // parceled independently of remoteViews.
8518                 customContent = customContent.clone();
8519                 customContent.overrideTextColors(mBuilder.getPrimaryTextColor(mBuilder.mParams));
8520                 remoteViews.removeAllViews(id);
8521                 remoteViews.addView(id, customContent);
8522                 remoteViews.addFlags(RemoteViews.FLAG_REAPPLY_DISALLOWED);
8523             }
8524             return remoteViews;
8525         }
8526     }
8527 
8528     /**
8529      * Encapsulates the information needed to display a notification as a bubble.
8530      *
8531      * <p>A bubble is used to display app content in a floating window over the existing
8532      * foreground activity. A bubble has a collapsed state represented by an icon,
8533      * {@link BubbleMetadata.Builder#setIcon(Icon)} and an expanded state which is populated
8534      * via {@link BubbleMetadata.Builder#setIntent(PendingIntent)}.</p>
8535      *
8536      * <b>Notifications with a valid and allowed bubble will display in collapsed state
8537      * outside of the notification shade on unlocked devices. When a user interacts with the
8538      * collapsed bubble, the bubble intent will be invoked and displayed.</b>
8539      *
8540      * @see Notification.Builder#setBubbleMetadata(BubbleMetadata)
8541      */
8542     public static final class BubbleMetadata implements Parcelable {
8543 
8544         private PendingIntent mPendingIntent;
8545         private PendingIntent mDeleteIntent;
8546         private Icon mIcon;
8547         private int mDesiredHeight;
8548         @DimenRes private int mDesiredHeightResId;
8549         private int mFlags;
8550 
8551         /**
8552          * If set and the app creating the bubble is in the foreground, the bubble will be posted
8553          * in its expanded state, with the contents of {@link #getIntent()} in a floating window.
8554          *
8555          * <p>If the app creating the bubble is not in the foreground this flag has no effect.</p>
8556          *
8557          * <p>Generally this flag should only be set if the user has performed an action to request
8558          * or create a bubble.</p>
8559          */
8560         private static final int FLAG_AUTO_EXPAND_BUBBLE = 0x00000001;
8561 
8562         /**
8563          * If set and the app posting the bubble is in the foreground, the bubble will
8564          * be posted <b>without</b> the associated notification in the notification shade.
8565          *
8566          * <p>If the app posting the bubble is not in the foreground this flag has no effect.</p>
8567          *
8568          * <p>Generally this flag should only be set if the user has performed an action to request
8569          * or create a bubble, or if the user has seen the content in the notification and the
8570          * notification is no longer relevant.</p>
8571          */
8572         private static final int FLAG_SUPPRESS_NOTIFICATION = 0x00000002;
8573 
BubbleMetadata(PendingIntent expandIntent, PendingIntent deleteIntent, Icon icon, int height, @DimenRes int heightResId)8574         private BubbleMetadata(PendingIntent expandIntent, PendingIntent deleteIntent,
8575                 Icon icon, int height, @DimenRes int heightResId) {
8576             mPendingIntent = expandIntent;
8577             mIcon = icon;
8578             mDesiredHeight = height;
8579             mDesiredHeightResId = heightResId;
8580             mDeleteIntent = deleteIntent;
8581         }
8582 
BubbleMetadata(Parcel in)8583         private BubbleMetadata(Parcel in) {
8584             mPendingIntent = PendingIntent.CREATOR.createFromParcel(in);
8585             mIcon = Icon.CREATOR.createFromParcel(in);
8586             mDesiredHeight = in.readInt();
8587             mFlags = in.readInt();
8588             if (in.readInt() != 0) {
8589                 mDeleteIntent = PendingIntent.CREATOR.createFromParcel(in);
8590             }
8591             mDesiredHeightResId = in.readInt();
8592         }
8593 
8594         /**
8595          * @return the pending intent used to populate the floating window for this bubble.
8596          */
8597         @NonNull
getIntent()8598         public PendingIntent getIntent() {
8599             return mPendingIntent;
8600         }
8601 
8602         /**
8603          * @return the pending intent to send when the bubble is dismissed by a user, if one exists.
8604          */
8605         @Nullable
getDeleteIntent()8606         public PendingIntent getDeleteIntent() {
8607             return mDeleteIntent;
8608         }
8609 
8610         /**
8611          * @return the icon that will be displayed for this bubble when it is collapsed.
8612          */
8613         @NonNull
getIcon()8614         public Icon getIcon() {
8615             return mIcon;
8616         }
8617 
8618         /**
8619          * @return the ideal height, in DPs, for the floating window that app content defined by
8620          * {@link #getIntent()} for this bubble. A value of 0 indicates a desired height has not
8621          * been set.
8622          */
8623         @Dimension(unit = DP)
getDesiredHeight()8624         public int getDesiredHeight() {
8625             return mDesiredHeight;
8626         }
8627 
8628         /**
8629          * @return the resId of ideal height for the floating window that app content defined by
8630          * {@link #getIntent()} for this bubble. A value of 0 indicates a res value has not
8631          * been provided for the desired height.
8632          */
8633         @DimenRes
getDesiredHeightResId()8634         public int getDesiredHeightResId() {
8635             return mDesiredHeightResId;
8636         }
8637 
8638         /**
8639          * @return whether this bubble should auto expand when it is posted.
8640          *
8641          * @see BubbleMetadata.Builder#setAutoExpandBubble(boolean)
8642          */
getAutoExpandBubble()8643         public boolean getAutoExpandBubble() {
8644             return (mFlags & FLAG_AUTO_EXPAND_BUBBLE) != 0;
8645         }
8646 
8647         /**
8648          * @return whether this bubble should suppress the notification when it is posted.
8649          *
8650          * @see BubbleMetadata.Builder#setSuppressNotification(boolean)
8651          */
isNotificationSuppressed()8652         public boolean isNotificationSuppressed() {
8653             return (mFlags & FLAG_SUPPRESS_NOTIFICATION) != 0;
8654         }
8655 
8656         public static final @android.annotation.NonNull Parcelable.Creator<BubbleMetadata> CREATOR =
8657                 new Parcelable.Creator<BubbleMetadata>() {
8658 
8659                     @Override
8660                     public BubbleMetadata createFromParcel(Parcel source) {
8661                         return new BubbleMetadata(source);
8662                     }
8663 
8664                     @Override
8665                     public BubbleMetadata[] newArray(int size) {
8666                         return new BubbleMetadata[size];
8667                     }
8668                 };
8669 
8670         @Override
describeContents()8671         public int describeContents() {
8672             return 0;
8673         }
8674 
8675         @Override
writeToParcel(Parcel out, int flags)8676         public void writeToParcel(Parcel out, int flags) {
8677             mPendingIntent.writeToParcel(out, 0);
8678             mIcon.writeToParcel(out, 0);
8679             out.writeInt(mDesiredHeight);
8680             out.writeInt(mFlags);
8681             out.writeInt(mDeleteIntent != null ? 1 : 0);
8682             if (mDeleteIntent != null) {
8683                 mDeleteIntent.writeToParcel(out, 0);
8684             }
8685             out.writeInt(mDesiredHeightResId);
8686         }
8687 
setFlags(int flags)8688         private void setFlags(int flags) {
8689             mFlags = flags;
8690         }
8691 
8692         /**
8693          * Builder to construct a {@link BubbleMetadata} object.
8694          */
8695         public static final class Builder {
8696 
8697             private PendingIntent mPendingIntent;
8698             private Icon mIcon;
8699             private int mDesiredHeight;
8700             @DimenRes private int mDesiredHeightResId;
8701             private int mFlags;
8702             private PendingIntent mDeleteIntent;
8703 
8704             /**
8705              * Constructs a new builder object.
8706              */
Builder()8707             public Builder() {
8708             }
8709 
8710             /**
8711              * Sets the intent that will be used when the bubble is expanded. This will display the
8712              * app content in a floating window over the existing foreground activity.
8713              *
8714              * <p>An intent is required.</p>
8715              *
8716              * @throws IllegalArgumentException if intent is null
8717              */
8718             @NonNull
setIntent(@onNull PendingIntent intent)8719             public BubbleMetadata.Builder setIntent(@NonNull PendingIntent intent) {
8720                 if (intent == null) {
8721                     throw new IllegalArgumentException("Bubble requires non-null pending intent");
8722                 }
8723                 mPendingIntent = intent;
8724                 return this;
8725             }
8726 
8727             /**
8728              * Sets the icon that will represent the bubble when it is collapsed.
8729              *
8730              * <p>An icon is required and should be representative of the content within the bubble.
8731              * If your app produces multiple bubbles, the image should be unique for each of them.
8732              * </p>
8733              *
8734              * <p>The shape of a bubble icon is adaptive and can match the device theme.
8735              *
8736              * If your icon is bitmap-based, you should create it using
8737              * {@link Icon#createWithAdaptiveBitmap(Bitmap)}, otherwise this method will throw.
8738              *
8739              * If your icon is not bitmap-based, you should expect that the icon will be tinted.
8740              * </p>
8741              *
8742              * @throws IllegalArgumentException if icon is null or a non-adaptive bitmap
8743              */
8744             @NonNull
setIcon(@onNull Icon icon)8745             public BubbleMetadata.Builder setIcon(@NonNull Icon icon) {
8746                 if (icon == null) {
8747                     throw new IllegalArgumentException("Bubbles require non-null icon");
8748                 }
8749                 if (icon.getType() == TYPE_BITMAP) {
8750                     throw new IllegalArgumentException("When using bitmap based icons, Bubbles "
8751                             + "require TYPE_ADAPTIVE_BITMAP, please use"
8752                             + " Icon#createWithAdaptiveBitmap instead");
8753                 }
8754                 mIcon = icon;
8755                 return this;
8756             }
8757 
8758             /**
8759              * Sets the desired height in DPs for the app content defined by
8760              * {@link #setIntent(PendingIntent)}.
8761              *
8762              * <p>This height may not be respected if there is not enough space on the screen or if
8763              * the provided height is too small to be useful.</p>
8764              *
8765              * <p>If {@link #setDesiredHeightResId(int)} was previously called on this builder, the
8766              * previous value set will be cleared after calling this method, and this value will
8767              * be used instead.</p>
8768              *
8769              * <p>A desired height (in DPs or via resID) is optional.</p>
8770              *
8771              * @see #setDesiredHeightResId(int)
8772              */
8773             @NonNull
setDesiredHeight(@imensionunit = DP) int height)8774             public BubbleMetadata.Builder setDesiredHeight(@Dimension(unit = DP) int height) {
8775                 mDesiredHeight = Math.max(height, 0);
8776                 mDesiredHeightResId = 0;
8777                 return this;
8778             }
8779 
8780 
8781             /**
8782              * Sets the desired height via resId for the app content defined by
8783              * {@link #setIntent(PendingIntent)}.
8784              *
8785              * <p>This height may not be respected if there is not enough space on the screen or if
8786              * the provided height is too small to be useful.</p>
8787              *
8788              * <p>If {@link #setDesiredHeight(int)} was previously called on this builder, the
8789              * previous value set will be cleared after calling this method, and this value will
8790              * be used instead.</p>
8791              *
8792              * <p>A desired height (in DPs or via resID) is optional.</p>
8793              *
8794              * @see #setDesiredHeight(int)
8795              */
8796             @NonNull
setDesiredHeightResId(@imenRes int heightResId)8797             public BubbleMetadata.Builder setDesiredHeightResId(@DimenRes int heightResId) {
8798                 mDesiredHeightResId = heightResId;
8799                 mDesiredHeight = 0;
8800                 return this;
8801             }
8802 
8803             /**
8804              * Sets whether the bubble will be posted in its expanded state (with the contents of
8805              * {@link #getIntent()} in a floating window).
8806              *
8807              * <p>This flag has no effect if the app posting the bubble is not in the foreground.
8808              * </p>
8809              *
8810              * <p>Generally, this flag should only be set if the user has performed an action to
8811              * request or create a bubble.</p>
8812              *
8813              * <p>Setting this flag is optional; it defaults to false.</p>
8814              */
8815             @NonNull
setAutoExpandBubble(boolean shouldExpand)8816             public BubbleMetadata.Builder setAutoExpandBubble(boolean shouldExpand) {
8817                 setFlag(FLAG_AUTO_EXPAND_BUBBLE, shouldExpand);
8818                 return this;
8819             }
8820 
8821             /**
8822              * Sets whether the bubble will be posted <b>without</b> the associated notification in
8823              * the notification shade.
8824              *
8825              * <p>This flag has no effect if the app posting the bubble is not in the foreground.
8826              * </p>
8827              *
8828              * <p>Generally, this flag should only be set if the user has performed an action to
8829              * request or create a bubble, or if the user has seen the content in the notification
8830              * and the notification is no longer relevant.</p>
8831              *
8832              * <p>Setting this flag is optional; it defaults to false.</p>
8833              */
8834             @NonNull
setSuppressNotification(boolean shouldSuppressNotif)8835             public BubbleMetadata.Builder setSuppressNotification(boolean shouldSuppressNotif) {
8836                 setFlag(FLAG_SUPPRESS_NOTIFICATION, shouldSuppressNotif);
8837                 return this;
8838             }
8839 
8840             /**
8841              * Sets an intent to send when this bubble is explicitly removed by the user.
8842              *
8843              * <p>Setting a delete intent is optional.</p>
8844              */
8845             @NonNull
setDeleteIntent(@ullable PendingIntent deleteIntent)8846             public BubbleMetadata.Builder setDeleteIntent(@Nullable PendingIntent deleteIntent) {
8847                 mDeleteIntent = deleteIntent;
8848                 return this;
8849             }
8850 
8851             /**
8852              * Creates the {@link BubbleMetadata} defined by this builder.
8853              *
8854              * @throws IllegalStateException if {@link #setIntent(PendingIntent)} and/or
8855              *                               {@link #setIcon(Icon)} have not been called on this
8856              *                               builder.
8857              */
8858             @NonNull
build()8859             public BubbleMetadata build() {
8860                 if (mPendingIntent == null) {
8861                     throw new IllegalStateException("Must supply pending intent to bubble");
8862                 }
8863                 if (mIcon == null) {
8864                     throw new IllegalStateException("Must supply an icon for the bubble");
8865                 }
8866                 BubbleMetadata data = new BubbleMetadata(mPendingIntent, mDeleteIntent,
8867                         mIcon, mDesiredHeight, mDesiredHeightResId);
8868                 data.setFlags(mFlags);
8869                 return data;
8870             }
8871 
8872             /**
8873              * @hide
8874              */
setFlag(int mask, boolean value)8875             public BubbleMetadata.Builder setFlag(int mask, boolean value) {
8876                 if (value) {
8877                     mFlags |= mask;
8878                 } else {
8879                     mFlags &= ~mask;
8880                 }
8881                 return this;
8882             }
8883         }
8884     }
8885 
8886 
8887     // When adding a new Style subclass here, don't forget to update
8888     // Builder.getNotificationStyleClass.
8889 
8890     /**
8891      * Extender interface for use with {@link Builder#extend}. Extenders may be used to add
8892      * metadata or change options on a notification builder.
8893      */
8894     public interface Extender {
8895         /**
8896          * Apply this extender to a notification builder.
8897          * @param builder the builder to be modified.
8898          * @return the build object for chaining.
8899          */
extend(Builder builder)8900         public Builder extend(Builder builder);
8901     }
8902 
8903     /**
8904      * Helper class to add wearable extensions to notifications.
8905      * <p class="note"> See
8906      * <a href="{@docRoot}wear/notifications/creating.html">Creating Notifications
8907      * for Android Wear</a> for more information on how to use this class.
8908      * <p>
8909      * To create a notification with wearable extensions:
8910      * <ol>
8911      *   <li>Create a {@link android.app.Notification.Builder}, setting any desired
8912      *   properties.
8913      *   <li>Create a {@link android.app.Notification.WearableExtender}.
8914      *   <li>Set wearable-specific properties using the
8915      *   {@code add} and {@code set} methods of {@link android.app.Notification.WearableExtender}.
8916      *   <li>Call {@link android.app.Notification.Builder#extend} to apply the extensions to a
8917      *   notification.
8918      *   <li>Post the notification to the notification system with the
8919      *   {@code NotificationManager.notify(...)} methods.
8920      * </ol>
8921      *
8922      * <pre class="prettyprint">
8923      * Notification notif = new Notification.Builder(mContext)
8924      *         .setContentTitle(&quot;New mail from &quot; + sender.toString())
8925      *         .setContentText(subject)
8926      *         .setSmallIcon(R.drawable.new_mail)
8927      *         .extend(new Notification.WearableExtender()
8928      *                 .setContentIcon(R.drawable.new_mail))
8929      *         .build();
8930      * NotificationManager notificationManger =
8931      *         (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
8932      * notificationManger.notify(0, notif);</pre>
8933      *
8934      * <p>Wearable extensions can be accessed on an existing notification by using the
8935      * {@code WearableExtender(Notification)} constructor,
8936      * and then using the {@code get} methods to access values.
8937      *
8938      * <pre class="prettyprint">
8939      * Notification.WearableExtender wearableExtender = new Notification.WearableExtender(
8940      *         notification);
8941      * List&lt;Notification&gt; pages = wearableExtender.getPages();</pre>
8942      */
8943     public static final class WearableExtender implements Extender {
8944         /**
8945          * Sentinel value for an action index that is unset.
8946          */
8947         public static final int UNSET_ACTION_INDEX = -1;
8948 
8949         /**
8950          * Size value for use with {@link #setCustomSizePreset} to show this notification with
8951          * default sizing.
8952          * <p>For custom display notifications created using {@link #setDisplayIntent},
8953          * the default is {@link #SIZE_MEDIUM}. All other notifications size automatically based
8954          * on their content.
8955          *
8956          * @deprecated Display intents are no longer supported.
8957          */
8958         @Deprecated
8959         public static final int SIZE_DEFAULT = 0;
8960 
8961         /**
8962          * Size value for use with {@link #setCustomSizePreset} to show this notification
8963          * with an extra small size.
8964          * <p>This value is only applicable for custom display notifications created using
8965          * {@link #setDisplayIntent}.
8966          *
8967          * @deprecated Display intents are no longer supported.
8968          */
8969         @Deprecated
8970         public static final int SIZE_XSMALL = 1;
8971 
8972         /**
8973          * Size value for use with {@link #setCustomSizePreset} to show this notification
8974          * with a small size.
8975          * <p>This value is only applicable for custom display notifications created using
8976          * {@link #setDisplayIntent}.
8977          *
8978          * @deprecated Display intents are no longer supported.
8979          */
8980         @Deprecated
8981         public static final int SIZE_SMALL = 2;
8982 
8983         /**
8984          * Size value for use with {@link #setCustomSizePreset} to show this notification
8985          * with a medium size.
8986          * <p>This value is only applicable for custom display notifications created using
8987          * {@link #setDisplayIntent}.
8988          *
8989          * @deprecated Display intents are no longer supported.
8990          */
8991         @Deprecated
8992         public static final int SIZE_MEDIUM = 3;
8993 
8994         /**
8995          * Size value for use with {@link #setCustomSizePreset} to show this notification
8996          * with a large size.
8997          * <p>This value is only applicable for custom display notifications created using
8998          * {@link #setDisplayIntent}.
8999          *
9000          * @deprecated Display intents are no longer supported.
9001          */
9002         @Deprecated
9003         public static final int SIZE_LARGE = 4;
9004 
9005         /**
9006          * Size value for use with {@link #setCustomSizePreset} to show this notification
9007          * full screen.
9008          * <p>This value is only applicable for custom display notifications created using
9009          * {@link #setDisplayIntent}.
9010          *
9011          * @deprecated Display intents are no longer supported.
9012          */
9013         @Deprecated
9014         public static final int SIZE_FULL_SCREEN = 5;
9015 
9016         /**
9017          * Sentinel value for use with {@link #setHintScreenTimeout} to keep the screen on for a
9018          * short amount of time when this notification is displayed on the screen. This
9019          * is the default value.
9020          *
9021          * @deprecated This feature is no longer supported.
9022          */
9023         @Deprecated
9024         public static final int SCREEN_TIMEOUT_SHORT = 0;
9025 
9026         /**
9027          * Sentinel value for use with {@link #setHintScreenTimeout} to keep the screen on
9028          * for a longer amount of time when this notification is displayed on the screen.
9029          *
9030          * @deprecated This feature is no longer supported.
9031          */
9032         @Deprecated
9033         public static final int SCREEN_TIMEOUT_LONG = -1;
9034 
9035         /** Notification extra which contains wearable extensions */
9036         private static final String EXTRA_WEARABLE_EXTENSIONS = "android.wearable.EXTENSIONS";
9037 
9038         // Keys within EXTRA_WEARABLE_EXTENSIONS for wearable options.
9039         private static final String KEY_ACTIONS = "actions";
9040         private static final String KEY_FLAGS = "flags";
9041         private static final String KEY_DISPLAY_INTENT = "displayIntent";
9042         private static final String KEY_PAGES = "pages";
9043         private static final String KEY_BACKGROUND = "background";
9044         private static final String KEY_CONTENT_ICON = "contentIcon";
9045         private static final String KEY_CONTENT_ICON_GRAVITY = "contentIconGravity";
9046         private static final String KEY_CONTENT_ACTION_INDEX = "contentActionIndex";
9047         private static final String KEY_CUSTOM_SIZE_PRESET = "customSizePreset";
9048         private static final String KEY_CUSTOM_CONTENT_HEIGHT = "customContentHeight";
9049         private static final String KEY_GRAVITY = "gravity";
9050         private static final String KEY_HINT_SCREEN_TIMEOUT = "hintScreenTimeout";
9051         private static final String KEY_DISMISSAL_ID = "dismissalId";
9052         private static final String KEY_BRIDGE_TAG = "bridgeTag";
9053 
9054         // Flags bitwise-ored to mFlags
9055         private static final int FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE = 0x1;
9056         private static final int FLAG_HINT_HIDE_ICON = 1 << 1;
9057         private static final int FLAG_HINT_SHOW_BACKGROUND_ONLY = 1 << 2;
9058         private static final int FLAG_START_SCROLL_BOTTOM = 1 << 3;
9059         private static final int FLAG_HINT_AVOID_BACKGROUND_CLIPPING = 1 << 4;
9060         private static final int FLAG_BIG_PICTURE_AMBIENT = 1 << 5;
9061         private static final int FLAG_HINT_CONTENT_INTENT_LAUNCHES_ACTIVITY = 1 << 6;
9062 
9063         // Default value for flags integer
9064         private static final int DEFAULT_FLAGS = FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE;
9065 
9066         private static final int DEFAULT_CONTENT_ICON_GRAVITY = Gravity.END;
9067         private static final int DEFAULT_GRAVITY = Gravity.BOTTOM;
9068 
9069         private ArrayList<Action> mActions = new ArrayList<Action>();
9070         private int mFlags = DEFAULT_FLAGS;
9071         private PendingIntent mDisplayIntent;
9072         private ArrayList<Notification> mPages = new ArrayList<Notification>();
9073         private Bitmap mBackground;
9074         private int mContentIcon;
9075         private int mContentIconGravity = DEFAULT_CONTENT_ICON_GRAVITY;
9076         private int mContentActionIndex = UNSET_ACTION_INDEX;
9077         private int mCustomSizePreset = SIZE_DEFAULT;
9078         private int mCustomContentHeight;
9079         private int mGravity = DEFAULT_GRAVITY;
9080         private int mHintScreenTimeout;
9081         private String mDismissalId;
9082         private String mBridgeTag;
9083 
9084         /**
9085          * Create a {@link android.app.Notification.WearableExtender} with default
9086          * options.
9087          */
WearableExtender()9088         public WearableExtender() {
9089         }
9090 
WearableExtender(Notification notif)9091         public WearableExtender(Notification notif) {
9092             Bundle wearableBundle = notif.extras.getBundle(EXTRA_WEARABLE_EXTENSIONS);
9093             if (wearableBundle != null) {
9094                 List<Action> actions = wearableBundle.getParcelableArrayList(KEY_ACTIONS);
9095                 if (actions != null) {
9096                     mActions.addAll(actions);
9097                 }
9098 
9099                 mFlags = wearableBundle.getInt(KEY_FLAGS, DEFAULT_FLAGS);
9100                 mDisplayIntent = wearableBundle.getParcelable(KEY_DISPLAY_INTENT);
9101 
9102                 Notification[] pages = getNotificationArrayFromBundle(
9103                         wearableBundle, KEY_PAGES);
9104                 if (pages != null) {
9105                     Collections.addAll(mPages, pages);
9106                 }
9107 
9108                 mBackground = wearableBundle.getParcelable(KEY_BACKGROUND);
9109                 mContentIcon = wearableBundle.getInt(KEY_CONTENT_ICON);
9110                 mContentIconGravity = wearableBundle.getInt(KEY_CONTENT_ICON_GRAVITY,
9111                         DEFAULT_CONTENT_ICON_GRAVITY);
9112                 mContentActionIndex = wearableBundle.getInt(KEY_CONTENT_ACTION_INDEX,
9113                         UNSET_ACTION_INDEX);
9114                 mCustomSizePreset = wearableBundle.getInt(KEY_CUSTOM_SIZE_PRESET,
9115                         SIZE_DEFAULT);
9116                 mCustomContentHeight = wearableBundle.getInt(KEY_CUSTOM_CONTENT_HEIGHT);
9117                 mGravity = wearableBundle.getInt(KEY_GRAVITY, DEFAULT_GRAVITY);
9118                 mHintScreenTimeout = wearableBundle.getInt(KEY_HINT_SCREEN_TIMEOUT);
9119                 mDismissalId = wearableBundle.getString(KEY_DISMISSAL_ID);
9120                 mBridgeTag = wearableBundle.getString(KEY_BRIDGE_TAG);
9121             }
9122         }
9123 
9124         /**
9125          * Apply wearable extensions to a notification that is being built. This is typically
9126          * called by the {@link android.app.Notification.Builder#extend} method of
9127          * {@link android.app.Notification.Builder}.
9128          */
9129         @Override
extend(Notification.Builder builder)9130         public Notification.Builder extend(Notification.Builder builder) {
9131             Bundle wearableBundle = new Bundle();
9132 
9133             if (!mActions.isEmpty()) {
9134                 wearableBundle.putParcelableArrayList(KEY_ACTIONS, mActions);
9135             }
9136             if (mFlags != DEFAULT_FLAGS) {
9137                 wearableBundle.putInt(KEY_FLAGS, mFlags);
9138             }
9139             if (mDisplayIntent != null) {
9140                 wearableBundle.putParcelable(KEY_DISPLAY_INTENT, mDisplayIntent);
9141             }
9142             if (!mPages.isEmpty()) {
9143                 wearableBundle.putParcelableArray(KEY_PAGES, mPages.toArray(
9144                         new Notification[mPages.size()]));
9145             }
9146             if (mBackground != null) {
9147                 wearableBundle.putParcelable(KEY_BACKGROUND, mBackground);
9148             }
9149             if (mContentIcon != 0) {
9150                 wearableBundle.putInt(KEY_CONTENT_ICON, mContentIcon);
9151             }
9152             if (mContentIconGravity != DEFAULT_CONTENT_ICON_GRAVITY) {
9153                 wearableBundle.putInt(KEY_CONTENT_ICON_GRAVITY, mContentIconGravity);
9154             }
9155             if (mContentActionIndex != UNSET_ACTION_INDEX) {
9156                 wearableBundle.putInt(KEY_CONTENT_ACTION_INDEX,
9157                         mContentActionIndex);
9158             }
9159             if (mCustomSizePreset != SIZE_DEFAULT) {
9160                 wearableBundle.putInt(KEY_CUSTOM_SIZE_PRESET, mCustomSizePreset);
9161             }
9162             if (mCustomContentHeight != 0) {
9163                 wearableBundle.putInt(KEY_CUSTOM_CONTENT_HEIGHT, mCustomContentHeight);
9164             }
9165             if (mGravity != DEFAULT_GRAVITY) {
9166                 wearableBundle.putInt(KEY_GRAVITY, mGravity);
9167             }
9168             if (mHintScreenTimeout != 0) {
9169                 wearableBundle.putInt(KEY_HINT_SCREEN_TIMEOUT, mHintScreenTimeout);
9170             }
9171             if (mDismissalId != null) {
9172                 wearableBundle.putString(KEY_DISMISSAL_ID, mDismissalId);
9173             }
9174             if (mBridgeTag != null) {
9175                 wearableBundle.putString(KEY_BRIDGE_TAG, mBridgeTag);
9176             }
9177 
9178             builder.getExtras().putBundle(EXTRA_WEARABLE_EXTENSIONS, wearableBundle);
9179             return builder;
9180         }
9181 
9182         @Override
clone()9183         public WearableExtender clone() {
9184             WearableExtender that = new WearableExtender();
9185             that.mActions = new ArrayList<Action>(this.mActions);
9186             that.mFlags = this.mFlags;
9187             that.mDisplayIntent = this.mDisplayIntent;
9188             that.mPages = new ArrayList<Notification>(this.mPages);
9189             that.mBackground = this.mBackground;
9190             that.mContentIcon = this.mContentIcon;
9191             that.mContentIconGravity = this.mContentIconGravity;
9192             that.mContentActionIndex = this.mContentActionIndex;
9193             that.mCustomSizePreset = this.mCustomSizePreset;
9194             that.mCustomContentHeight = this.mCustomContentHeight;
9195             that.mGravity = this.mGravity;
9196             that.mHintScreenTimeout = this.mHintScreenTimeout;
9197             that.mDismissalId = this.mDismissalId;
9198             that.mBridgeTag = this.mBridgeTag;
9199             return that;
9200         }
9201 
9202         /**
9203          * Add a wearable action to this notification.
9204          *
9205          * <p>When wearable actions are added using this method, the set of actions that
9206          * show on a wearable device splits from devices that only show actions added
9207          * using {@link android.app.Notification.Builder#addAction}. This allows for customization
9208          * of which actions display on different devices.
9209          *
9210          * @param action the action to add to this notification
9211          * @return this object for method chaining
9212          * @see android.app.Notification.Action
9213          */
addAction(Action action)9214         public WearableExtender addAction(Action action) {
9215             mActions.add(action);
9216             return this;
9217         }
9218 
9219         /**
9220          * Adds wearable actions to this notification.
9221          *
9222          * <p>When wearable actions are added using this method, the set of actions that
9223          * show on a wearable device splits from devices that only show actions added
9224          * using {@link android.app.Notification.Builder#addAction}. This allows for customization
9225          * of which actions display on different devices.
9226          *
9227          * @param actions the actions to add to this notification
9228          * @return this object for method chaining
9229          * @see android.app.Notification.Action
9230          */
addActions(List<Action> actions)9231         public WearableExtender addActions(List<Action> actions) {
9232             mActions.addAll(actions);
9233             return this;
9234         }
9235 
9236         /**
9237          * Clear all wearable actions present on this builder.
9238          * @return this object for method chaining.
9239          * @see #addAction
9240          */
clearActions()9241         public WearableExtender clearActions() {
9242             mActions.clear();
9243             return this;
9244         }
9245 
9246         /**
9247          * Get the wearable actions present on this notification.
9248          */
getActions()9249         public List<Action> getActions() {
9250             return mActions;
9251         }
9252 
9253         /**
9254          * Set an intent to launch inside of an activity view when displaying
9255          * this notification. The {@link PendingIntent} provided should be for an activity.
9256          *
9257          * <pre class="prettyprint">
9258          * Intent displayIntent = new Intent(context, MyDisplayActivity.class);
9259          * PendingIntent displayPendingIntent = PendingIntent.getActivity(context,
9260          *         0, displayIntent, PendingIntent.FLAG_UPDATE_CURRENT);
9261          * Notification notif = new Notification.Builder(context)
9262          *         .extend(new Notification.WearableExtender()
9263          *                 .setDisplayIntent(displayPendingIntent)
9264          *                 .setCustomSizePreset(Notification.WearableExtender.SIZE_MEDIUM))
9265          *         .build();</pre>
9266          *
9267          * <p>The activity to launch needs to allow embedding, must be exported, and
9268          * should have an empty task affinity. It is also recommended to use the device
9269          * default light theme.
9270          *
9271          * <p>Example AndroidManifest.xml entry:
9272          * <pre class="prettyprint">
9273          * &lt;activity android:name=&quot;com.example.MyDisplayActivity&quot;
9274          *     android:exported=&quot;true&quot;
9275          *     android:allowEmbedded=&quot;true&quot;
9276          *     android:taskAffinity=&quot;&quot;
9277          *     android:theme=&quot;@android:style/Theme.DeviceDefault.Light&quot; /&gt;</pre>
9278          *
9279          * @param intent the {@link PendingIntent} for an activity
9280          * @return this object for method chaining
9281          * @see android.app.Notification.WearableExtender#getDisplayIntent
9282          * @deprecated Display intents are no longer supported.
9283          */
9284         @Deprecated
setDisplayIntent(PendingIntent intent)9285         public WearableExtender setDisplayIntent(PendingIntent intent) {
9286             mDisplayIntent = intent;
9287             return this;
9288         }
9289 
9290         /**
9291          * Get the intent to launch inside of an activity view when displaying this
9292          * notification. This {@code PendingIntent} should be for an activity.
9293          *
9294          * @deprecated Display intents are no longer supported.
9295          */
9296         @Deprecated
getDisplayIntent()9297         public PendingIntent getDisplayIntent() {
9298             return mDisplayIntent;
9299         }
9300 
9301         /**
9302          * Add an additional page of content to display with this notification. The current
9303          * notification forms the first page, and pages added using this function form
9304          * subsequent pages. This field can be used to separate a notification into multiple
9305          * sections.
9306          *
9307          * @param page the notification to add as another page
9308          * @return this object for method chaining
9309          * @see android.app.Notification.WearableExtender#getPages
9310          * @deprecated Multiple content pages are no longer supported.
9311          */
9312         @Deprecated
addPage(Notification page)9313         public WearableExtender addPage(Notification page) {
9314             mPages.add(page);
9315             return this;
9316         }
9317 
9318         /**
9319          * Add additional pages of content to display with this notification. The current
9320          * notification forms the first page, and pages added using this function form
9321          * subsequent pages. This field can be used to separate a notification into multiple
9322          * sections.
9323          *
9324          * @param pages a list of notifications
9325          * @return this object for method chaining
9326          * @see android.app.Notification.WearableExtender#getPages
9327          * @deprecated Multiple content pages are no longer supported.
9328          */
9329         @Deprecated
addPages(List<Notification> pages)9330         public WearableExtender addPages(List<Notification> pages) {
9331             mPages.addAll(pages);
9332             return this;
9333         }
9334 
9335         /**
9336          * Clear all additional pages present on this builder.
9337          * @return this object for method chaining.
9338          * @see #addPage
9339          * @deprecated Multiple content pages are no longer supported.
9340          */
9341         @Deprecated
clearPages()9342         public WearableExtender clearPages() {
9343             mPages.clear();
9344             return this;
9345         }
9346 
9347         /**
9348          * Get the array of additional pages of content for displaying this notification. The
9349          * current notification forms the first page, and elements within this array form
9350          * subsequent pages. This field can be used to separate a notification into multiple
9351          * sections.
9352          * @return the pages for this notification
9353          * @deprecated Multiple content pages are no longer supported.
9354          */
9355         @Deprecated
getPages()9356         public List<Notification> getPages() {
9357             return mPages;
9358         }
9359 
9360         /**
9361          * Set a background image to be displayed behind the notification content.
9362          * Contrary to the {@link android.app.Notification.BigPictureStyle}, this background
9363          * will work with any notification style.
9364          *
9365          * @param background the background bitmap
9366          * @return this object for method chaining
9367          * @see android.app.Notification.WearableExtender#getBackground
9368          * @deprecated Background images are no longer supported.
9369          */
9370         @Deprecated
setBackground(Bitmap background)9371         public WearableExtender setBackground(Bitmap background) {
9372             mBackground = background;
9373             return this;
9374         }
9375 
9376         /**
9377          * Get a background image to be displayed behind the notification content.
9378          * Contrary to the {@link android.app.Notification.BigPictureStyle}, this background
9379          * will work with any notification style.
9380          *
9381          * @return the background image
9382          * @see android.app.Notification.WearableExtender#setBackground
9383          * @deprecated Background images are no longer supported.
9384          */
9385         @Deprecated
getBackground()9386         public Bitmap getBackground() {
9387             return mBackground;
9388         }
9389 
9390         /**
9391          * Set an icon that goes with the content of this notification.
9392          */
9393         @Deprecated
setContentIcon(int icon)9394         public WearableExtender setContentIcon(int icon) {
9395             mContentIcon = icon;
9396             return this;
9397         }
9398 
9399         /**
9400          * Get an icon that goes with the content of this notification.
9401          */
9402         @Deprecated
getContentIcon()9403         public int getContentIcon() {
9404             return mContentIcon;
9405         }
9406 
9407         /**
9408          * Set the gravity that the content icon should have within the notification display.
9409          * Supported values include {@link android.view.Gravity#START} and
9410          * {@link android.view.Gravity#END}. The default value is {@link android.view.Gravity#END}.
9411          * @see #setContentIcon
9412          */
9413         @Deprecated
setContentIconGravity(int contentIconGravity)9414         public WearableExtender setContentIconGravity(int contentIconGravity) {
9415             mContentIconGravity = contentIconGravity;
9416             return this;
9417         }
9418 
9419         /**
9420          * Get the gravity that the content icon should have within the notification display.
9421          * Supported values include {@link android.view.Gravity#START} and
9422          * {@link android.view.Gravity#END}. The default value is {@link android.view.Gravity#END}.
9423          * @see #getContentIcon
9424          */
9425         @Deprecated
getContentIconGravity()9426         public int getContentIconGravity() {
9427             return mContentIconGravity;
9428         }
9429 
9430         /**
9431          * Set an action from this notification's actions as the primary action. If the action has a
9432          * {@link RemoteInput} associated with it, shortcuts to the options for that input are shown
9433          * directly on the notification.
9434          *
9435          * @param actionIndex The index of the primary action.
9436          *                    If wearable actions were added to the main notification, this index
9437          *                    will apply to that list, otherwise it will apply to the regular
9438          *                    actions list.
9439          */
setContentAction(int actionIndex)9440         public WearableExtender setContentAction(int actionIndex) {
9441             mContentActionIndex = actionIndex;
9442             return this;
9443         }
9444 
9445         /**
9446          * Get the index of the notification action, if any, that was specified as the primary
9447          * action.
9448          *
9449          * <p>If wearable specific actions were added to the main notification, this index will
9450          * apply to that list, otherwise it will apply to the regular actions list.
9451          *
9452          * @return the action index or {@link #UNSET_ACTION_INDEX} if no action was selected.
9453          */
getContentAction()9454         public int getContentAction() {
9455             return mContentActionIndex;
9456         }
9457 
9458         /**
9459          * Set the gravity that this notification should have within the available viewport space.
9460          * Supported values include {@link android.view.Gravity#TOP},
9461          * {@link android.view.Gravity#CENTER_VERTICAL} and {@link android.view.Gravity#BOTTOM}.
9462          * The default value is {@link android.view.Gravity#BOTTOM}.
9463          */
9464         @Deprecated
setGravity(int gravity)9465         public WearableExtender setGravity(int gravity) {
9466             mGravity = gravity;
9467             return this;
9468         }
9469 
9470         /**
9471          * Get the gravity that this notification should have within the available viewport space.
9472          * Supported values include {@link android.view.Gravity#TOP},
9473          * {@link android.view.Gravity#CENTER_VERTICAL} and {@link android.view.Gravity#BOTTOM}.
9474          * The default value is {@link android.view.Gravity#BOTTOM}.
9475          */
9476         @Deprecated
getGravity()9477         public int getGravity() {
9478             return mGravity;
9479         }
9480 
9481         /**
9482          * Set the custom size preset for the display of this notification out of the available
9483          * presets found in {@link android.app.Notification.WearableExtender}, e.g.
9484          * {@link #SIZE_LARGE}.
9485          * <p>Some custom size presets are only applicable for custom display notifications created
9486          * using {@link android.app.Notification.WearableExtender#setDisplayIntent}. Check the
9487          * documentation for the preset in question. See also
9488          * {@link #setCustomContentHeight} and {@link #getCustomSizePreset}.
9489          */
9490         @Deprecated
setCustomSizePreset(int sizePreset)9491         public WearableExtender setCustomSizePreset(int sizePreset) {
9492             mCustomSizePreset = sizePreset;
9493             return this;
9494         }
9495 
9496         /**
9497          * Get the custom size preset for the display of this notification out of the available
9498          * presets found in {@link android.app.Notification.WearableExtender}, e.g.
9499          * {@link #SIZE_LARGE}.
9500          * <p>Some custom size presets are only applicable for custom display notifications created
9501          * using {@link #setDisplayIntent}. Check the documentation for the preset in question.
9502          * See also {@link #setCustomContentHeight} and {@link #setCustomSizePreset}.
9503          */
9504         @Deprecated
getCustomSizePreset()9505         public int getCustomSizePreset() {
9506             return mCustomSizePreset;
9507         }
9508 
9509         /**
9510          * Set the custom height in pixels for the display of this notification's content.
9511          * <p>This option is only available for custom display notifications created
9512          * using {@link android.app.Notification.WearableExtender#setDisplayIntent}. See also
9513          * {@link android.app.Notification.WearableExtender#setCustomSizePreset} and
9514          * {@link #getCustomContentHeight}.
9515          */
9516         @Deprecated
setCustomContentHeight(int height)9517         public WearableExtender setCustomContentHeight(int height) {
9518             mCustomContentHeight = height;
9519             return this;
9520         }
9521 
9522         /**
9523          * Get the custom height in pixels for the display of this notification's content.
9524          * <p>This option is only available for custom display notifications created
9525          * using {@link #setDisplayIntent}. See also {@link #setCustomSizePreset} and
9526          * {@link #setCustomContentHeight}.
9527          */
9528         @Deprecated
getCustomContentHeight()9529         public int getCustomContentHeight() {
9530             return mCustomContentHeight;
9531         }
9532 
9533         /**
9534          * Set whether the scrolling position for the contents of this notification should start
9535          * at the bottom of the contents instead of the top when the contents are too long to
9536          * display within the screen.  Default is false (start scroll at the top).
9537          */
setStartScrollBottom(boolean startScrollBottom)9538         public WearableExtender setStartScrollBottom(boolean startScrollBottom) {
9539             setFlag(FLAG_START_SCROLL_BOTTOM, startScrollBottom);
9540             return this;
9541         }
9542 
9543         /**
9544          * Get whether the scrolling position for the contents of this notification should start
9545          * at the bottom of the contents instead of the top when the contents are too long to
9546          * display within the screen. Default is false (start scroll at the top).
9547          */
getStartScrollBottom()9548         public boolean getStartScrollBottom() {
9549             return (mFlags & FLAG_START_SCROLL_BOTTOM) != 0;
9550         }
9551 
9552         /**
9553          * Set whether the content intent is available when the wearable device is not connected
9554          * to a companion device.  The user can still trigger this intent when the wearable device
9555          * is offline, but a visual hint will indicate that the content intent may not be available.
9556          * Defaults to true.
9557          */
setContentIntentAvailableOffline( boolean contentIntentAvailableOffline)9558         public WearableExtender setContentIntentAvailableOffline(
9559                 boolean contentIntentAvailableOffline) {
9560             setFlag(FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE, contentIntentAvailableOffline);
9561             return this;
9562         }
9563 
9564         /**
9565          * Get whether the content intent is available when the wearable device is not connected
9566          * to a companion device.  The user can still trigger this intent when the wearable device
9567          * is offline, but a visual hint will indicate that the content intent may not be available.
9568          * Defaults to true.
9569          */
getContentIntentAvailableOffline()9570         public boolean getContentIntentAvailableOffline() {
9571             return (mFlags & FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE) != 0;
9572         }
9573 
9574         /**
9575          * Set a hint that this notification's icon should not be displayed.
9576          * @param hintHideIcon {@code true} to hide the icon, {@code false} otherwise.
9577          * @return this object for method chaining
9578          */
9579         @Deprecated
setHintHideIcon(boolean hintHideIcon)9580         public WearableExtender setHintHideIcon(boolean hintHideIcon) {
9581             setFlag(FLAG_HINT_HIDE_ICON, hintHideIcon);
9582             return this;
9583         }
9584 
9585         /**
9586          * Get a hint that this notification's icon should not be displayed.
9587          * @return {@code true} if this icon should not be displayed, false otherwise.
9588          * The default value is {@code false} if this was never set.
9589          */
9590         @Deprecated
getHintHideIcon()9591         public boolean getHintHideIcon() {
9592             return (mFlags & FLAG_HINT_HIDE_ICON) != 0;
9593         }
9594 
9595         /**
9596          * Set a visual hint that only the background image of this notification should be
9597          * displayed, and other semantic content should be hidden. This hint is only applicable
9598          * to sub-pages added using {@link #addPage}.
9599          */
9600         @Deprecated
setHintShowBackgroundOnly(boolean hintShowBackgroundOnly)9601         public WearableExtender setHintShowBackgroundOnly(boolean hintShowBackgroundOnly) {
9602             setFlag(FLAG_HINT_SHOW_BACKGROUND_ONLY, hintShowBackgroundOnly);
9603             return this;
9604         }
9605 
9606         /**
9607          * Get a visual hint that only the background image of this notification should be
9608          * displayed, and other semantic content should be hidden. This hint is only applicable
9609          * to sub-pages added using {@link android.app.Notification.WearableExtender#addPage}.
9610          */
9611         @Deprecated
getHintShowBackgroundOnly()9612         public boolean getHintShowBackgroundOnly() {
9613             return (mFlags & FLAG_HINT_SHOW_BACKGROUND_ONLY) != 0;
9614         }
9615 
9616         /**
9617          * Set a hint that this notification's background should not be clipped if possible,
9618          * and should instead be resized to fully display on the screen, retaining the aspect
9619          * ratio of the image. This can be useful for images like barcodes or qr codes.
9620          * @param hintAvoidBackgroundClipping {@code true} to avoid clipping if possible.
9621          * @return this object for method chaining
9622          */
9623         @Deprecated
setHintAvoidBackgroundClipping( boolean hintAvoidBackgroundClipping)9624         public WearableExtender setHintAvoidBackgroundClipping(
9625                 boolean hintAvoidBackgroundClipping) {
9626             setFlag(FLAG_HINT_AVOID_BACKGROUND_CLIPPING, hintAvoidBackgroundClipping);
9627             return this;
9628         }
9629 
9630         /**
9631          * Get a hint that this notification's background should not be clipped if possible,
9632          * and should instead be resized to fully display on the screen, retaining the aspect
9633          * ratio of the image. This can be useful for images like barcodes or qr codes.
9634          * @return {@code true} if it's ok if the background is clipped on the screen, false
9635          * otherwise. The default value is {@code false} if this was never set.
9636          */
9637         @Deprecated
getHintAvoidBackgroundClipping()9638         public boolean getHintAvoidBackgroundClipping() {
9639             return (mFlags & FLAG_HINT_AVOID_BACKGROUND_CLIPPING) != 0;
9640         }
9641 
9642         /**
9643          * Set a hint that the screen should remain on for at least this duration when
9644          * this notification is displayed on the screen.
9645          * @param timeout The requested screen timeout in milliseconds. Can also be either
9646          *     {@link #SCREEN_TIMEOUT_SHORT} or {@link #SCREEN_TIMEOUT_LONG}.
9647          * @return this object for method chaining
9648          */
9649         @Deprecated
setHintScreenTimeout(int timeout)9650         public WearableExtender setHintScreenTimeout(int timeout) {
9651             mHintScreenTimeout = timeout;
9652             return this;
9653         }
9654 
9655         /**
9656          * Get the duration, in milliseconds, that the screen should remain on for
9657          * when this notification is displayed.
9658          * @return the duration in milliseconds if > 0, or either one of the sentinel values
9659          *     {@link #SCREEN_TIMEOUT_SHORT} or {@link #SCREEN_TIMEOUT_LONG}.
9660          */
9661         @Deprecated
getHintScreenTimeout()9662         public int getHintScreenTimeout() {
9663             return mHintScreenTimeout;
9664         }
9665 
9666         /**
9667          * Set a hint that this notification's {@link BigPictureStyle} (if present) should be
9668          * converted to low-bit and displayed in ambient mode, especially useful for barcodes and
9669          * qr codes, as well as other simple black-and-white tickets.
9670          * @param hintAmbientBigPicture {@code true} to enable converstion and ambient.
9671          * @return this object for method chaining
9672          * @deprecated This feature is no longer supported.
9673          */
9674         @Deprecated
setHintAmbientBigPicture(boolean hintAmbientBigPicture)9675         public WearableExtender setHintAmbientBigPicture(boolean hintAmbientBigPicture) {
9676             setFlag(FLAG_BIG_PICTURE_AMBIENT, hintAmbientBigPicture);
9677             return this;
9678         }
9679 
9680         /**
9681          * Get a hint that this notification's {@link BigPictureStyle} (if present) should be
9682          * converted to low-bit and displayed in ambient mode, especially useful for barcodes and
9683          * qr codes, as well as other simple black-and-white tickets.
9684          * @return {@code true} if it should be displayed in ambient, false otherwise
9685          * otherwise. The default value is {@code false} if this was never set.
9686          * @deprecated This feature is no longer supported.
9687          */
9688         @Deprecated
getHintAmbientBigPicture()9689         public boolean getHintAmbientBigPicture() {
9690             return (mFlags & FLAG_BIG_PICTURE_AMBIENT) != 0;
9691         }
9692 
9693         /**
9694          * Set a hint that this notification's content intent will launch an {@link Activity}
9695          * directly, telling the platform that it can generate the appropriate transitions.
9696          * @param hintContentIntentLaunchesActivity {@code true} if the content intent will launch
9697          * an activity and transitions should be generated, false otherwise.
9698          * @return this object for method chaining
9699          */
setHintContentIntentLaunchesActivity( boolean hintContentIntentLaunchesActivity)9700         public WearableExtender setHintContentIntentLaunchesActivity(
9701                 boolean hintContentIntentLaunchesActivity) {
9702             setFlag(FLAG_HINT_CONTENT_INTENT_LAUNCHES_ACTIVITY, hintContentIntentLaunchesActivity);
9703             return this;
9704         }
9705 
9706         /**
9707          * Get a hint that this notification's content intent will launch an {@link Activity}
9708          * directly, telling the platform that it can generate the appropriate transitions
9709          * @return {@code true} if the content intent will launch an activity and transitions should
9710          * be generated, false otherwise. The default value is {@code false} if this was never set.
9711          */
getHintContentIntentLaunchesActivity()9712         public boolean getHintContentIntentLaunchesActivity() {
9713             return (mFlags & FLAG_HINT_CONTENT_INTENT_LAUNCHES_ACTIVITY) != 0;
9714         }
9715 
9716         /**
9717          * Sets the dismissal id for this notification. If a notification is posted with a
9718          * dismissal id, then when that notification is canceled, notifications on other wearables
9719          * and the paired Android phone having that same dismissal id will also be canceled. See
9720          * <a href="{@docRoot}wear/notifications/index.html">Adding Wearable Features to
9721          * Notifications</a> for more information.
9722          * @param dismissalId the dismissal id of the notification.
9723          * @return this object for method chaining
9724          */
setDismissalId(String dismissalId)9725         public WearableExtender setDismissalId(String dismissalId) {
9726             mDismissalId = dismissalId;
9727             return this;
9728         }
9729 
9730         /**
9731          * Returns the dismissal id of the notification.
9732          * @return the dismissal id of the notification or null if it has not been set.
9733          */
getDismissalId()9734         public String getDismissalId() {
9735             return mDismissalId;
9736         }
9737 
9738         /**
9739          * Sets a bridge tag for this notification. A bridge tag can be set for notifications
9740          * posted from a phone to provide finer-grained control on what notifications are bridged
9741          * to wearables. See <a href="{@docRoot}wear/notifications/index.html">Adding Wearable
9742          * Features to Notifications</a> for more information.
9743          * @param bridgeTag the bridge tag of the notification.
9744          * @return this object for method chaining
9745          */
setBridgeTag(String bridgeTag)9746         public WearableExtender setBridgeTag(String bridgeTag) {
9747             mBridgeTag = bridgeTag;
9748             return this;
9749         }
9750 
9751         /**
9752          * Returns the bridge tag of the notification.
9753          * @return the bridge tag or null if not present.
9754          */
getBridgeTag()9755         public String getBridgeTag() {
9756             return mBridgeTag;
9757         }
9758 
setFlag(int mask, boolean value)9759         private void setFlag(int mask, boolean value) {
9760             if (value) {
9761                 mFlags |= mask;
9762             } else {
9763                 mFlags &= ~mask;
9764             }
9765         }
9766     }
9767 
9768     /**
9769      * <p>Helper class to add Android Auto extensions to notifications. To create a notification
9770      * with car extensions:
9771      *
9772      * <ol>
9773      *  <li>Create an {@link Notification.Builder}, setting any desired
9774      *  properties.
9775      *  <li>Create a {@link CarExtender}.
9776      *  <li>Set car-specific properties using the {@code add} and {@code set} methods of
9777      *  {@link CarExtender}.
9778      *  <li>Call {@link Notification.Builder#extend(Notification.Extender)}
9779      *  to apply the extensions to a notification.
9780      * </ol>
9781      *
9782      * <pre class="prettyprint">
9783      * Notification notification = new Notification.Builder(context)
9784      *         ...
9785      *         .extend(new CarExtender()
9786      *                 .set*(...))
9787      *         .build();
9788      * </pre>
9789      *
9790      * <p>Car extensions can be accessed on an existing notification by using the
9791      * {@code CarExtender(Notification)} constructor, and then using the {@code get} methods
9792      * to access values.
9793      */
9794     public static final class CarExtender implements Extender {
9795         private static final String TAG = "CarExtender";
9796 
9797         private static final String EXTRA_CAR_EXTENDER = "android.car.EXTENSIONS";
9798         private static final String EXTRA_LARGE_ICON = "large_icon";
9799         private static final String EXTRA_CONVERSATION = "car_conversation";
9800         private static final String EXTRA_COLOR = "app_color";
9801 
9802         private Bitmap mLargeIcon;
9803         private UnreadConversation mUnreadConversation;
9804         private int mColor = Notification.COLOR_DEFAULT;
9805 
9806         /**
9807          * Create a {@link CarExtender} with default options.
9808          */
CarExtender()9809         public CarExtender() {
9810         }
9811 
9812         /**
9813          * Create a {@link CarExtender} from the CarExtender options of an existing Notification.
9814          *
9815          * @param notif The notification from which to copy options.
9816          */
CarExtender(Notification notif)9817         public CarExtender(Notification notif) {
9818             Bundle carBundle = notif.extras == null ?
9819                     null : notif.extras.getBundle(EXTRA_CAR_EXTENDER);
9820             if (carBundle != null) {
9821                 mLargeIcon = carBundle.getParcelable(EXTRA_LARGE_ICON);
9822                 mColor = carBundle.getInt(EXTRA_COLOR, Notification.COLOR_DEFAULT);
9823 
9824                 Bundle b = carBundle.getBundle(EXTRA_CONVERSATION);
9825                 mUnreadConversation = UnreadConversation.getUnreadConversationFromBundle(b);
9826             }
9827         }
9828 
9829         /**
9830          * Apply car extensions to a notification that is being built. This is typically called by
9831          * the {@link Notification.Builder#extend(Notification.Extender)}
9832          * method of {@link Notification.Builder}.
9833          */
9834         @Override
extend(Notification.Builder builder)9835         public Notification.Builder extend(Notification.Builder builder) {
9836             Bundle carExtensions = new Bundle();
9837 
9838             if (mLargeIcon != null) {
9839                 carExtensions.putParcelable(EXTRA_LARGE_ICON, mLargeIcon);
9840             }
9841             if (mColor != Notification.COLOR_DEFAULT) {
9842                 carExtensions.putInt(EXTRA_COLOR, mColor);
9843             }
9844 
9845             if (mUnreadConversation != null) {
9846                 Bundle b = mUnreadConversation.getBundleForUnreadConversation();
9847                 carExtensions.putBundle(EXTRA_CONVERSATION, b);
9848             }
9849 
9850             builder.getExtras().putBundle(EXTRA_CAR_EXTENDER, carExtensions);
9851             return builder;
9852         }
9853 
9854         /**
9855          * Sets the accent color to use when Android Auto presents the notification.
9856          *
9857          * Android Auto uses the color set with {@link Notification.Builder#setColor(int)}
9858          * to accent the displayed notification. However, not all colors are acceptable in an
9859          * automotive setting. This method can be used to override the color provided in the
9860          * notification in such a situation.
9861          */
setColor(@olorInt int color)9862         public CarExtender setColor(@ColorInt int color) {
9863             mColor = color;
9864             return this;
9865         }
9866 
9867         /**
9868          * Gets the accent color.
9869          *
9870          * @see #setColor
9871          */
9872         @ColorInt
getColor()9873         public int getColor() {
9874             return mColor;
9875         }
9876 
9877         /**
9878          * Sets the large icon of the car notification.
9879          *
9880          * If no large icon is set in the extender, Android Auto will display the icon
9881          * specified by {@link Notification.Builder#setLargeIcon(android.graphics.Bitmap)}
9882          *
9883          * @param largeIcon The large icon to use in the car notification.
9884          * @return This object for method chaining.
9885          */
setLargeIcon(Bitmap largeIcon)9886         public CarExtender setLargeIcon(Bitmap largeIcon) {
9887             mLargeIcon = largeIcon;
9888             return this;
9889         }
9890 
9891         /**
9892          * Gets the large icon used in this car notification, or null if no icon has been set.
9893          *
9894          * @return The large icon for the car notification.
9895          * @see CarExtender#setLargeIcon
9896          */
getLargeIcon()9897         public Bitmap getLargeIcon() {
9898             return mLargeIcon;
9899         }
9900 
9901         /**
9902          * Sets the unread conversation in a message notification.
9903          *
9904          * @param unreadConversation The unread part of the conversation this notification conveys.
9905          * @return This object for method chaining.
9906          */
setUnreadConversation(UnreadConversation unreadConversation)9907         public CarExtender setUnreadConversation(UnreadConversation unreadConversation) {
9908             mUnreadConversation = unreadConversation;
9909             return this;
9910         }
9911 
9912         /**
9913          * Returns the unread conversation conveyed by this notification.
9914          * @see #setUnreadConversation(UnreadConversation)
9915          */
getUnreadConversation()9916         public UnreadConversation getUnreadConversation() {
9917             return mUnreadConversation;
9918         }
9919 
9920         /**
9921          * A class which holds the unread messages from a conversation.
9922          */
9923         public static class UnreadConversation {
9924             private static final String KEY_AUTHOR = "author";
9925             private static final String KEY_TEXT = "text";
9926             private static final String KEY_MESSAGES = "messages";
9927             private static final String KEY_REMOTE_INPUT = "remote_input";
9928             private static final String KEY_ON_REPLY = "on_reply";
9929             private static final String KEY_ON_READ = "on_read";
9930             private static final String KEY_PARTICIPANTS = "participants";
9931             private static final String KEY_TIMESTAMP = "timestamp";
9932 
9933             private final String[] mMessages;
9934             private final RemoteInput mRemoteInput;
9935             private final PendingIntent mReplyPendingIntent;
9936             private final PendingIntent mReadPendingIntent;
9937             private final String[] mParticipants;
9938             private final long mLatestTimestamp;
9939 
UnreadConversation(String[] messages, RemoteInput remoteInput, PendingIntent replyPendingIntent, PendingIntent readPendingIntent, String[] participants, long latestTimestamp)9940             UnreadConversation(String[] messages, RemoteInput remoteInput,
9941                     PendingIntent replyPendingIntent, PendingIntent readPendingIntent,
9942                     String[] participants, long latestTimestamp) {
9943                 mMessages = messages;
9944                 mRemoteInput = remoteInput;
9945                 mReadPendingIntent = readPendingIntent;
9946                 mReplyPendingIntent = replyPendingIntent;
9947                 mParticipants = participants;
9948                 mLatestTimestamp = latestTimestamp;
9949             }
9950 
9951             /**
9952              * Gets the list of messages conveyed by this notification.
9953              */
getMessages()9954             public String[] getMessages() {
9955                 return mMessages;
9956             }
9957 
9958             /**
9959              * Gets the remote input that will be used to convey the response to a message list, or
9960              * null if no such remote input exists.
9961              */
getRemoteInput()9962             public RemoteInput getRemoteInput() {
9963                 return mRemoteInput;
9964             }
9965 
9966             /**
9967              * Gets the pending intent that will be triggered when the user replies to this
9968              * notification.
9969              */
getReplyPendingIntent()9970             public PendingIntent getReplyPendingIntent() {
9971                 return mReplyPendingIntent;
9972             }
9973 
9974             /**
9975              * Gets the pending intent that Android Auto will send after it reads aloud all messages
9976              * in this object's message list.
9977              */
getReadPendingIntent()9978             public PendingIntent getReadPendingIntent() {
9979                 return mReadPendingIntent;
9980             }
9981 
9982             /**
9983              * Gets the participants in the conversation.
9984              */
getParticipants()9985             public String[] getParticipants() {
9986                 return mParticipants;
9987             }
9988 
9989             /**
9990              * Gets the firs participant in the conversation.
9991              */
getParticipant()9992             public String getParticipant() {
9993                 return mParticipants.length > 0 ? mParticipants[0] : null;
9994             }
9995 
9996             /**
9997              * Gets the timestamp of the conversation.
9998              */
getLatestTimestamp()9999             public long getLatestTimestamp() {
10000                 return mLatestTimestamp;
10001             }
10002 
getBundleForUnreadConversation()10003             Bundle getBundleForUnreadConversation() {
10004                 Bundle b = new Bundle();
10005                 String author = null;
10006                 if (mParticipants != null && mParticipants.length > 1) {
10007                     author = mParticipants[0];
10008                 }
10009                 Parcelable[] messages = new Parcelable[mMessages.length];
10010                 for (int i = 0; i < messages.length; i++) {
10011                     Bundle m = new Bundle();
10012                     m.putString(KEY_TEXT, mMessages[i]);
10013                     m.putString(KEY_AUTHOR, author);
10014                     messages[i] = m;
10015                 }
10016                 b.putParcelableArray(KEY_MESSAGES, messages);
10017                 if (mRemoteInput != null) {
10018                     b.putParcelable(KEY_REMOTE_INPUT, mRemoteInput);
10019                 }
10020                 b.putParcelable(KEY_ON_REPLY, mReplyPendingIntent);
10021                 b.putParcelable(KEY_ON_READ, mReadPendingIntent);
10022                 b.putStringArray(KEY_PARTICIPANTS, mParticipants);
10023                 b.putLong(KEY_TIMESTAMP, mLatestTimestamp);
10024                 return b;
10025             }
10026 
getUnreadConversationFromBundle(Bundle b)10027             static UnreadConversation getUnreadConversationFromBundle(Bundle b) {
10028                 if (b == null) {
10029                     return null;
10030                 }
10031                 Parcelable[] parcelableMessages = b.getParcelableArray(KEY_MESSAGES);
10032                 String[] messages = null;
10033                 if (parcelableMessages != null) {
10034                     String[] tmp = new String[parcelableMessages.length];
10035                     boolean success = true;
10036                     for (int i = 0; i < tmp.length; i++) {
10037                         if (!(parcelableMessages[i] instanceof Bundle)) {
10038                             success = false;
10039                             break;
10040                         }
10041                         tmp[i] = ((Bundle) parcelableMessages[i]).getString(KEY_TEXT);
10042                         if (tmp[i] == null) {
10043                             success = false;
10044                             break;
10045                         }
10046                     }
10047                     if (success) {
10048                         messages = tmp;
10049                     } else {
10050                         return null;
10051                     }
10052                 }
10053 
10054                 PendingIntent onRead = b.getParcelable(KEY_ON_READ);
10055                 PendingIntent onReply = b.getParcelable(KEY_ON_REPLY);
10056 
10057                 RemoteInput remoteInput = b.getParcelable(KEY_REMOTE_INPUT);
10058 
10059                 String[] participants = b.getStringArray(KEY_PARTICIPANTS);
10060                 if (participants == null || participants.length != 1) {
10061                     return null;
10062                 }
10063 
10064                 return new UnreadConversation(messages,
10065                         remoteInput,
10066                         onReply,
10067                         onRead,
10068                         participants, b.getLong(KEY_TIMESTAMP));
10069             }
10070         };
10071 
10072         /**
10073          * Builder class for {@link CarExtender.UnreadConversation} objects.
10074          */
10075         public static class Builder {
10076             private final List<String> mMessages = new ArrayList<String>();
10077             private final String mParticipant;
10078             private RemoteInput mRemoteInput;
10079             private PendingIntent mReadPendingIntent;
10080             private PendingIntent mReplyPendingIntent;
10081             private long mLatestTimestamp;
10082 
10083             /**
10084              * Constructs a new builder for {@link CarExtender.UnreadConversation}.
10085              *
10086              * @param name The name of the other participant in the conversation.
10087              */
Builder(String name)10088             public Builder(String name) {
10089                 mParticipant = name;
10090             }
10091 
10092             /**
10093              * Appends a new unread message to the list of messages for this conversation.
10094              *
10095              * The messages should be added from oldest to newest.
10096              *
10097              * @param message The text of the new unread message.
10098              * @return This object for method chaining.
10099              */
addMessage(String message)10100             public Builder addMessage(String message) {
10101                 mMessages.add(message);
10102                 return this;
10103             }
10104 
10105             /**
10106              * Sets the pending intent and remote input which will convey the reply to this
10107              * notification.
10108              *
10109              * @param pendingIntent The pending intent which will be triggered on a reply.
10110              * @param remoteInput The remote input parcelable which will carry the reply.
10111              * @return This object for method chaining.
10112              *
10113              * @see CarExtender.UnreadConversation#getRemoteInput
10114              * @see CarExtender.UnreadConversation#getReplyPendingIntent
10115              */
setReplyAction( PendingIntent pendingIntent, RemoteInput remoteInput)10116             public Builder setReplyAction(
10117                     PendingIntent pendingIntent, RemoteInput remoteInput) {
10118                 mRemoteInput = remoteInput;
10119                 mReplyPendingIntent = pendingIntent;
10120 
10121                 return this;
10122             }
10123 
10124             /**
10125              * Sets the pending intent that will be sent once the messages in this notification
10126              * are read.
10127              *
10128              * @param pendingIntent The pending intent to use.
10129              * @return This object for method chaining.
10130              */
setReadPendingIntent(PendingIntent pendingIntent)10131             public Builder setReadPendingIntent(PendingIntent pendingIntent) {
10132                 mReadPendingIntent = pendingIntent;
10133                 return this;
10134             }
10135 
10136             /**
10137              * Sets the timestamp of the most recent message in an unread conversation.
10138              *
10139              * If a messaging notification has been posted by your application and has not
10140              * yet been cancelled, posting a later notification with the same id and tag
10141              * but without a newer timestamp may result in Android Auto not displaying a
10142              * heads up notification for the later notification.
10143              *
10144              * @param timestamp The timestamp of the most recent message in the conversation.
10145              * @return This object for method chaining.
10146              */
setLatestTimestamp(long timestamp)10147             public Builder setLatestTimestamp(long timestamp) {
10148                 mLatestTimestamp = timestamp;
10149                 return this;
10150             }
10151 
10152             /**
10153              * Builds a new unread conversation object.
10154              *
10155              * @return The new unread conversation object.
10156              */
build()10157             public UnreadConversation build() {
10158                 String[] messages = mMessages.toArray(new String[mMessages.size()]);
10159                 String[] participants = { mParticipant };
10160                 return new UnreadConversation(messages, mRemoteInput, mReplyPendingIntent,
10161                         mReadPendingIntent, participants, mLatestTimestamp);
10162             }
10163         }
10164     }
10165 
10166     /**
10167      * <p>Helper class to add Android TV extensions to notifications. To create a notification
10168      * with a TV extension:
10169      *
10170      * <ol>
10171      *  <li>Create an {@link Notification.Builder}, setting any desired properties.
10172      *  <li>Create a {@link TvExtender}.
10173      *  <li>Set TV-specific properties using the {@code set} methods of
10174      *  {@link TvExtender}.
10175      *  <li>Call {@link Notification.Builder#extend(Notification.Extender)}
10176      *  to apply the extension to a notification.
10177      * </ol>
10178      *
10179      * <pre class="prettyprint">
10180      * Notification notification = new Notification.Builder(context)
10181      *         ...
10182      *         .extend(new TvExtender()
10183      *                 .set*(...))
10184      *         .build();
10185      * </pre>
10186      *
10187      * <p>TV extensions can be accessed on an existing notification by using the
10188      * {@code TvExtender(Notification)} constructor, and then using the {@code get} methods
10189      * to access values.
10190      *
10191      * @hide
10192      */
10193     @SystemApi
10194     public static final class TvExtender implements Extender {
10195         private static final String TAG = "TvExtender";
10196 
10197         private static final String EXTRA_TV_EXTENDER = "android.tv.EXTENSIONS";
10198         private static final String EXTRA_FLAGS = "flags";
10199         private static final String EXTRA_CONTENT_INTENT = "content_intent";
10200         private static final String EXTRA_DELETE_INTENT = "delete_intent";
10201         private static final String EXTRA_CHANNEL_ID = "channel_id";
10202         private static final String EXTRA_SUPPRESS_SHOW_OVER_APPS = "suppressShowOverApps";
10203 
10204         // Flags bitwise-ored to mFlags
10205         private static final int FLAG_AVAILABLE_ON_TV = 0x1;
10206 
10207         private int mFlags;
10208         private String mChannelId;
10209         private PendingIntent mContentIntent;
10210         private PendingIntent mDeleteIntent;
10211         private boolean mSuppressShowOverApps;
10212 
10213         /**
10214          * Create a {@link TvExtender} with default options.
10215          */
TvExtender()10216         public TvExtender() {
10217             mFlags = FLAG_AVAILABLE_ON_TV;
10218         }
10219 
10220         /**
10221          * Create a {@link TvExtender} from the TvExtender options of an existing Notification.
10222          *
10223          * @param notif The notification from which to copy options.
10224          */
TvExtender(Notification notif)10225         public TvExtender(Notification notif) {
10226             Bundle bundle = notif.extras == null ?
10227                 null : notif.extras.getBundle(EXTRA_TV_EXTENDER);
10228             if (bundle != null) {
10229                 mFlags = bundle.getInt(EXTRA_FLAGS);
10230                 mChannelId = bundle.getString(EXTRA_CHANNEL_ID);
10231                 mSuppressShowOverApps = bundle.getBoolean(EXTRA_SUPPRESS_SHOW_OVER_APPS);
10232                 mContentIntent = bundle.getParcelable(EXTRA_CONTENT_INTENT);
10233                 mDeleteIntent = bundle.getParcelable(EXTRA_DELETE_INTENT);
10234             }
10235         }
10236 
10237         /**
10238          * Apply a TV extension to a notification that is being built. This is typically called by
10239          * the {@link Notification.Builder#extend(Notification.Extender)}
10240          * method of {@link Notification.Builder}.
10241          */
10242         @Override
extend(Notification.Builder builder)10243         public Notification.Builder extend(Notification.Builder builder) {
10244             Bundle bundle = new Bundle();
10245 
10246             bundle.putInt(EXTRA_FLAGS, mFlags);
10247             bundle.putString(EXTRA_CHANNEL_ID, mChannelId);
10248             bundle.putBoolean(EXTRA_SUPPRESS_SHOW_OVER_APPS, mSuppressShowOverApps);
10249             if (mContentIntent != null) {
10250                 bundle.putParcelable(EXTRA_CONTENT_INTENT, mContentIntent);
10251             }
10252 
10253             if (mDeleteIntent != null) {
10254                 bundle.putParcelable(EXTRA_DELETE_INTENT, mDeleteIntent);
10255             }
10256 
10257             builder.getExtras().putBundle(EXTRA_TV_EXTENDER, bundle);
10258             return builder;
10259         }
10260 
10261         /**
10262          * Returns true if this notification should be shown on TV. This method return true
10263          * if the notification was extended with a TvExtender.
10264          */
isAvailableOnTv()10265         public boolean isAvailableOnTv() {
10266             return (mFlags & FLAG_AVAILABLE_ON_TV) != 0;
10267         }
10268 
10269         /**
10270          * Specifies the channel the notification should be delivered on when shown on TV.
10271          * It can be different from the channel that the notification is delivered to when
10272          * posting on a non-TV device.
10273          */
setChannel(String channelId)10274         public TvExtender setChannel(String channelId) {
10275             mChannelId = channelId;
10276             return this;
10277         }
10278 
10279         /**
10280          * Specifies the channel the notification should be delivered on when shown on TV.
10281          * It can be different from the channel that the notification is delivered to when
10282          * posting on a non-TV device.
10283          */
setChannelId(String channelId)10284         public TvExtender setChannelId(String channelId) {
10285             mChannelId = channelId;
10286             return this;
10287         }
10288 
10289         /** @removed */
10290         @Deprecated
getChannel()10291         public String getChannel() {
10292             return mChannelId;
10293         }
10294 
10295         /**
10296          * Returns the id of the channel this notification posts to on TV.
10297          */
getChannelId()10298         public String getChannelId() {
10299             return mChannelId;
10300         }
10301 
10302         /**
10303          * Supplies a {@link PendingIntent} to be sent when the notification is selected on TV.
10304          * If provided, it is used instead of the content intent specified
10305          * at the level of Notification.
10306          */
setContentIntent(PendingIntent intent)10307         public TvExtender setContentIntent(PendingIntent intent) {
10308             mContentIntent = intent;
10309             return this;
10310         }
10311 
10312         /**
10313          * Returns the TV-specific content intent.  If this method returns null, the
10314          * main content intent on the notification should be used.
10315          *
10316          * @see {@link Notification#contentIntent}
10317          */
getContentIntent()10318         public PendingIntent getContentIntent() {
10319             return mContentIntent;
10320         }
10321 
10322         /**
10323          * Supplies a {@link PendingIntent} to send when the notification is cleared explicitly
10324          * by the user on TV.  If provided, it is used instead of the delete intent specified
10325          * at the level of Notification.
10326          */
setDeleteIntent(PendingIntent intent)10327         public TvExtender setDeleteIntent(PendingIntent intent) {
10328             mDeleteIntent = intent;
10329             return this;
10330         }
10331 
10332         /**
10333          * Returns the TV-specific delete intent.  If this method returns null, the
10334          * main delete intent on the notification should be used.
10335          *
10336          * @see {@link Notification#deleteIntent}
10337          */
getDeleteIntent()10338         public PendingIntent getDeleteIntent() {
10339             return mDeleteIntent;
10340         }
10341 
10342         /**
10343          * Specifies whether this notification should suppress showing a message over top of apps
10344          * outside of the launcher.
10345          */
setSuppressShowOverApps(boolean suppress)10346         public TvExtender setSuppressShowOverApps(boolean suppress) {
10347             mSuppressShowOverApps = suppress;
10348             return this;
10349         }
10350 
10351         /**
10352          * Returns true if this notification should not show messages over top of apps
10353          * outside of the launcher.
10354          */
getSuppressShowOverApps()10355         public boolean getSuppressShowOverApps() {
10356             return mSuppressShowOverApps;
10357         }
10358     }
10359 
10360     /**
10361      * Get an array of Notification objects from a parcelable array bundle field.
10362      * Update the bundle to have a typed array so fetches in the future don't need
10363      * to do an array copy.
10364      */
getNotificationArrayFromBundle(Bundle bundle, String key)10365     private static Notification[] getNotificationArrayFromBundle(Bundle bundle, String key) {
10366         Parcelable[] array = bundle.getParcelableArray(key);
10367         if (array instanceof Notification[] || array == null) {
10368             return (Notification[]) array;
10369         }
10370         Notification[] typedArray = Arrays.copyOf(array, array.length,
10371                 Notification[].class);
10372         bundle.putParcelableArray(key, typedArray);
10373         return typedArray;
10374     }
10375 
10376     private static class BuilderRemoteViews extends RemoteViews {
BuilderRemoteViews(Parcel parcel)10377         public BuilderRemoteViews(Parcel parcel) {
10378             super(parcel);
10379         }
10380 
BuilderRemoteViews(ApplicationInfo appInfo, int layoutId)10381         public BuilderRemoteViews(ApplicationInfo appInfo, int layoutId) {
10382             super(appInfo, layoutId);
10383         }
10384 
10385         @Override
clone()10386         public BuilderRemoteViews clone() {
10387             Parcel p = Parcel.obtain();
10388             writeToParcel(p, 0);
10389             p.setDataPosition(0);
10390             BuilderRemoteViews brv = new BuilderRemoteViews(p);
10391             p.recycle();
10392             return brv;
10393         }
10394     }
10395 
10396     /**
10397      * A result object where information about the template that was created is saved.
10398      */
10399     private static class TemplateBindResult {
10400         int mIconMarginEnd;
10401         boolean mRightIconContainerVisible;
10402 
10403         /**
10404          * Get the margin end that needs to be added to any fields that may overlap
10405          * with the right actions.
10406          */
getIconMarginEnd()10407         public int getIconMarginEnd() {
10408             return mIconMarginEnd;
10409         }
10410 
10411         /**
10412          * Is the icon container visible on the right size because of the reply button or the
10413          * right icon.
10414          */
isRightIconContainerVisible()10415         public boolean isRightIconContainerVisible() {
10416             return mRightIconContainerVisible;
10417         }
10418 
setIconMarginEnd(int iconMarginEnd)10419         public void setIconMarginEnd(int iconMarginEnd) {
10420             this.mIconMarginEnd = iconMarginEnd;
10421         }
10422 
setRightIconContainerVisible(boolean iconContainerVisible)10423         public void setRightIconContainerVisible(boolean iconContainerVisible) {
10424             mRightIconContainerVisible = iconContainerVisible;
10425         }
10426     }
10427 
10428     private static class StandardTemplateParams {
10429         boolean hasProgress = true;
10430         CharSequence title;
10431         CharSequence text;
10432         CharSequence headerTextSecondary;
10433         CharSequence summaryText;
10434         int maxRemoteInputHistory = Style.MAX_REMOTE_INPUT_HISTORY_LINES;
10435         boolean hideLargeIcon;
10436         boolean hideReplyIcon;
10437         boolean allowColorization  = true;
10438         boolean forceDefaultColor = false;
10439 
reset()10440         final StandardTemplateParams reset() {
10441             hasProgress = true;
10442             title = null;
10443             text = null;
10444             summaryText = null;
10445             headerTextSecondary = null;
10446             maxRemoteInputHistory = Style.MAX_REMOTE_INPUT_HISTORY_LINES;
10447             allowColorization = true;
10448             forceDefaultColor = false;
10449             return this;
10450         }
10451 
hasProgress(boolean hasProgress)10452         final StandardTemplateParams hasProgress(boolean hasProgress) {
10453             this.hasProgress = hasProgress;
10454             return this;
10455         }
10456 
title(CharSequence title)10457         final StandardTemplateParams title(CharSequence title) {
10458             this.title = title;
10459             return this;
10460         }
10461 
text(CharSequence text)10462         final StandardTemplateParams text(CharSequence text) {
10463             this.text = text;
10464             return this;
10465         }
10466 
summaryText(CharSequence text)10467         final StandardTemplateParams summaryText(CharSequence text) {
10468             this.summaryText = text;
10469             return this;
10470         }
10471 
headerTextSecondary(CharSequence text)10472         final StandardTemplateParams headerTextSecondary(CharSequence text) {
10473             this.headerTextSecondary = text;
10474             return this;
10475         }
10476 
hideLargeIcon(boolean hideLargeIcon)10477         final StandardTemplateParams hideLargeIcon(boolean hideLargeIcon) {
10478             this.hideLargeIcon = hideLargeIcon;
10479             return this;
10480         }
10481 
hideReplyIcon(boolean hideReplyIcon)10482         final StandardTemplateParams hideReplyIcon(boolean hideReplyIcon) {
10483             this.hideReplyIcon = hideReplyIcon;
10484             return this;
10485         }
10486 
disallowColorization()10487         final StandardTemplateParams disallowColorization() {
10488             this.allowColorization = false;
10489             return this;
10490         }
10491 
forceDefaultColor()10492         final StandardTemplateParams forceDefaultColor() {
10493             this.forceDefaultColor = true;
10494             return this;
10495         }
10496 
fillTextsFrom(Builder b)10497         final StandardTemplateParams fillTextsFrom(Builder b) {
10498             Bundle extras = b.mN.extras;
10499             this.title = b.processLegacyText(extras.getCharSequence(EXTRA_TITLE));
10500 
10501             CharSequence text = extras.getCharSequence(EXTRA_BIG_TEXT);
10502             if (TextUtils.isEmpty(text)) {
10503                 text = extras.getCharSequence(EXTRA_TEXT);
10504             }
10505             this.text = b.processLegacyText(text);
10506             this.summaryText = extras.getCharSequence(EXTRA_SUB_TEXT);
10507             return this;
10508         }
10509 
10510         /**
10511          * Set the maximum lines of remote input history lines allowed.
10512          * @param maxRemoteInputHistory The number of lines.
10513          * @return The builder for method chaining.
10514          */
setMaxRemoteInputHistory(int maxRemoteInputHistory)10515         public StandardTemplateParams setMaxRemoteInputHistory(int maxRemoteInputHistory) {
10516             this.maxRemoteInputHistory = maxRemoteInputHistory;
10517             return this;
10518         }
10519     }
10520 }
10521