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