2 * Copyright (C) 2007 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
19 import static com.android.internal.util.NotificationColorUtil.satisfiesTextContrast;
21 import android.annotation.ColorInt;
22 import android.annotation.DrawableRes;
23 import android.annotation.IntDef;
24 import android.annotation.NonNull;
25 import android.annotation.RequiresPermission;
26 import android.annotation.SdkConstant;
27 import android.annotation.SdkConstant.SdkConstantType;
28 import android.annotation.SystemApi;
29 import android.content.Context;
30 import android.content.Intent;
31 import android.content.pm.ApplicationInfo;
32 import android.content.pm.PackageManager;
33 import android.content.pm.PackageManager.NameNotFoundException;
34 import android.content.pm.ShortcutInfo;
35 import android.content.res.ColorStateList;
36 import android.graphics.Bitmap;
37 import android.graphics.Canvas;
38 import android.graphics.Color;
39 import android.graphics.PorterDuff;
40 import android.graphics.drawable.Drawable;
41 import android.graphics.drawable.Icon;
42 import android.media.AudioAttributes;
43 import android.media.AudioManager;
44 import android.media.PlayerBase;
45 import android.media.session.MediaSession;
46 import android.net.Uri;
47 import android.os.BadParcelableException;
48 import android.os.Build;
49 import android.os.Bundle;
50 import android.os.IBinder;
51 import android.os.Parcel;
52 import android.os.Parcelable;
53 import android.os.SystemClock;
54 import android.os.SystemProperties;
55 import android.os.UserHandle;
56 import android.text.BidiFormatter;
57 import android.text.SpannableStringBuilder;
58 import android.text.Spanned;
59 import android.text.TextUtils;
60 import android.text.style.AbsoluteSizeSpan;
61 import android.text.style.BackgroundColorSpan;
62 import android.text.style.CharacterStyle;
63 import android.text.style.ForegroundColorSpan;
64 import android.text.style.RelativeSizeSpan;
65 import android.text.style.TextAppearanceSpan;
66 import android.util.ArraySet;
67 import android.util.Log;
68 import android.util.SparseArray;
69 import android.view.Gravity;
70 import android.view.NotificationHeaderView;
71 import android.view.View;
72 import android.view.ViewGroup;
73 import android.widget.ProgressBar;
74 import android.widget.RemoteViews;
76 import com.android.internal.R;
77 import com.android.internal.annotations.VisibleForTesting;
78 import com.android.internal.util.ArrayUtils;
79 import com.android.internal.util.NotificationColorUtil;
80 import com.android.internal.util.Preconditions;
82 import java.lang.annotation.Retention;
83 import java.lang.annotation.RetentionPolicy;
84 import java.lang.reflect.Constructor;
85 import java.util.ArrayList;
86 import java.util.Arrays;
87 import java.util.Collections;
88 import java.util.List;
92 * A class that represents how a persistent notification is to be presented to
93 * the user using the {@link android.app.NotificationManager}.
95 * <p>The {@link Notification.Builder Notification.Builder} has been added to make it
96 * easier to construct Notifications.</p>
98 * <div class="special reference">
99 * <h3>Developer Guides</h3>
100 * <p>For a guide to creating notifications, read the
101 * <a href="{@docRoot}guide/topics/ui/notifiers/notifications.html">Status Bar Notifications</a>
102 * developer guide.</p>
105 public class Notification implements Parcelable
107 private static final String TAG = "Notification";
110 * An activity that provides a user interface for adjusting notification preferences for its
111 * containing application.
113 @SdkConstant(SdkConstantType.INTENT_CATEGORY)
114 public static final String INTENT_CATEGORY_NOTIFICATION_PREFERENCES
115 = "android.intent.category.NOTIFICATION_PREFERENCES";
118 * Optional extra for {@link #INTENT_CATEGORY_NOTIFICATION_PREFERENCES}. If provided, will
119 * contain a {@link NotificationChannel#getId() channel id} that can be used to narrow down
120 * what settings should be shown in the target app.
122 public static final String EXTRA_CHANNEL_ID = "android.intent.extra.CHANNEL_ID";
125 * Optional extra for {@link #INTENT_CATEGORY_NOTIFICATION_PREFERENCES}. If provided, will
126 * contain the tag provided to {@link NotificationManager#notify(String, int, Notification)}
127 * that can be used to narrow down what settings should be shown in the target app.
129 public static final String EXTRA_NOTIFICATION_TAG = "android.intent.extra.NOTIFICATION_TAG";
132 * Optional extra for {@link #INTENT_CATEGORY_NOTIFICATION_PREFERENCES}. If provided, will
133 * contain the id provided to {@link NotificationManager#notify(String, int, Notification)}
134 * that can be used to narrow down what settings should be shown in the target app.
136 public static final String EXTRA_NOTIFICATION_ID = "android.intent.extra.NOTIFICATION_ID";
139 * Use all default values (where applicable).
141 public static final int DEFAULT_ALL = ~0;
144 * Use the default notification sound. This will ignore any given
148 * A notification that is noisy is more likely to be presented as a heads-up notification.
154 public static final int DEFAULT_SOUND = 1;
157 * Use the default notification vibrate. This will ignore any given
158 * {@link #vibrate}. Using phone vibration requires the
159 * {@link android.Manifest.permission#VIBRATE VIBRATE} permission.
162 * A notification that vibrates is more likely to be presented as a heads-up notification.
168 public static final int DEFAULT_VIBRATE = 2;
171 * Use the default notification lights. This will ignore the
172 * {@link #FLAG_SHOW_LIGHTS} bit, and {@link #ledARGB}, {@link #ledOffMS}, or
178 public static final int DEFAULT_LIGHTS = 4;
181 * Maximum length of CharSequences accepted by Builder and friends.
184 * Avoids spamming the system with overly large strings such as full e-mails.
186 private static final int MAX_CHARSEQUENCE_LENGTH = 5 * 1024;
189 * Maximum entries of reply text that are accepted by Builder and friends.
191 private static final int MAX_REPLY_HISTORY = 5;
194 * A timestamp related to this notification, in milliseconds since the epoch.
196 * Default value: {@link System#currentTimeMillis() Now}.
198 * Choose a timestamp that will be most relevant to the user. For most finite events, this
199 * corresponds to the time the event happened (or will happen, in the case of events that have
200 * yet to occur but about which the user is being informed). Indefinite events should be
201 * timestamped according to when the activity began.
206 * <li>Notification of a new chat message should be stamped when the message was received.</li>
207 * <li>Notification of an ongoing file download (with a progress bar, for example) should be stamped when the download started.</li>
208 * <li>Notification of a completed file download should be stamped when the download finished.</li>
209 * <li>Notification of an upcoming meeting should be stamped with the time the meeting will begin (that is, in the future).</li>
210 * <li>Notification of an ongoing stopwatch (increasing timer) should be stamped with the watch's start time.
211 * <li>Notification of an ongoing countdown timer should be stamped with the timer's end time.
214 * For apps targeting {@link android.os.Build.VERSION_CODES#N} and above, this time is not shown
215 * anymore by default and must be opted into by using
216 * {@link android.app.Notification.Builder#setShowWhen(boolean)}
221 * The creation time of the notification
223 private long creationTime;
226 * The resource id of a drawable to use as the icon in the status bar.
228 * @deprecated Use {@link Builder#setSmallIcon(Icon)} instead.
235 * If the icon in the status bar is to have more than one level, you can set this. Otherwise,
236 * leave it at its default value of 0.
238 * @see android.widget.ImageView#setImageLevel
239 * @see android.graphics.drawable.Drawable#setLevel
241 public int iconLevel;
244 * The number of events that this notification represents. For example, in a new mail
245 * notification, this could be the number of unread messages.
247 * The system may or may not use this field to modify the appearance of the notification.
248 * Starting with {@link android.os.Build.VERSION_CODES#O}, the number may be displayed as a
249 * badge icon in Launchers that support badging.
251 public int number = 0;
254 * The intent to execute when the expanded status entry is clicked. If
255 * this is an activity, it must include the
256 * {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK} flag, which requires
257 * that you take care of task management as described in the
258 * <a href="{@docRoot}guide/topics/fundamentals/tasks-and-back-stack.html">Tasks and Back
259 * Stack</a> document. In particular, make sure to read the notification section
260 * <a href="{@docRoot}guide/topics/ui/notifiers/notifications.html#HandlingNotifications">Handling
261 * Notifications</a> for the correct ways to launch an application from a
264 public PendingIntent contentIntent;
267 * The intent to execute when the notification is explicitly dismissed by the user, either with
268 * the "Clear All" button or by swiping it away individually.
270 * This probably shouldn't be launching an activity since several of those will be sent
273 public PendingIntent deleteIntent;
276 * An intent to launch instead of posting the notification to the status bar.
279 * The system UI may choose to display a heads-up notification, instead of
280 * launching this intent, while the user is using the device.
283 * @see Notification.Builder#setFullScreenIntent
285 public PendingIntent fullScreenIntent;
288 * Text that summarizes this notification for accessibility services.
290 * As of the L release, this text is no longer shown on screen, but it is still useful to
291 * accessibility services (where it serves as an audible announcement of the notification's
296 public CharSequence tickerText;
299 * Formerly, a view showing the {@link #tickerText}.
301 * No longer displayed in the status bar as of API 21.
304 public RemoteViews tickerView;
307 * The view that will represent this notification in the notification list (which is pulled
308 * down from the status bar).
310 * As of N, this field may be null. The notification view is determined by the inputs
311 * to {@link Notification.Builder}; a custom RemoteViews can optionally be
312 * supplied with {@link Notification.Builder#setCustomContentView(RemoteViews)}.
315 public RemoteViews contentView;
318 * A large-format version of {@link #contentView}, giving the Notification an
319 * opportunity to show more detail. The system UI may choose to show this
320 * instead of the normal content view at its discretion.
322 * As of N, this field may be null. The expanded notification view is determined by the
323 * inputs to {@link Notification.Builder}; a custom RemoteViews can optionally be
324 * supplied with {@link Notification.Builder#setCustomBigContentView(RemoteViews)}.
327 public RemoteViews bigContentView;
331 * A medium-format version of {@link #contentView}, providing the Notification an
332 * opportunity to add action buttons to contentView. At its discretion, the system UI may
333 * choose to show this as a heads-up notification, which will pop up so the user can see
334 * it without leaving their current activity.
336 * As of N, this field may be null. The heads-up notification view is determined by the
337 * inputs to {@link Notification.Builder}; a custom RemoteViews can optionally be
338 * supplied with {@link Notification.Builder#setCustomHeadsUpContentView(RemoteViews)}.
341 public RemoteViews headsUpContentView;
344 * A large bitmap to be shown in the notification content area.
346 * @deprecated Use {@link Builder#setLargeIcon(Icon)} instead.
349 public Bitmap largeIcon;
355 * A notification that is noisy is more likely to be presented as a heads-up notification.
359 * To play the default notification sound, see {@link #defaults}.
361 * @deprecated use {@link NotificationChannel#getSound()}.
367 * Use this constant as the value for audioStreamType to request that
368 * the default stream type for notifications be used. Currently the
369 * default stream type is {@link AudioManager#STREAM_NOTIFICATION}.
371 * @deprecated Use {@link NotificationChannel#getAudioAttributes()} instead.
374 public static final int STREAM_DEFAULT = -1;
377 * The audio stream type to use when playing the sound.
378 * Should be one of the STREAM_ constants from
379 * {@link android.media.AudioManager}.
381 * @deprecated Use {@link #audioAttributes} instead.
384 public int audioStreamType = STREAM_DEFAULT;
387 * The default value of {@link #audioAttributes}.
389 public static final AudioAttributes AUDIO_ATTRIBUTES_DEFAULT = new AudioAttributes.Builder()
390 .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
391 .setUsage(AudioAttributes.USAGE_NOTIFICATION)
395 * The {@link AudioAttributes audio attributes} to use when playing the sound.
397 * @deprecated use {@link NotificationChannel#getAudioAttributes()} instead.
400 public AudioAttributes audioAttributes = AUDIO_ATTRIBUTES_DEFAULT;
403 * The pattern with which to vibrate.
406 * To vibrate the default pattern, see {@link #defaults}.
409 * @see android.os.Vibrator#vibrate(long[],int)
410 * @deprecated use {@link NotificationChannel#getVibrationPattern()}.
413 public long[] vibrate;
416 * The color of the led. The hardware will do its best approximation.
418 * @see #FLAG_SHOW_LIGHTS
420 * @deprecated use {@link NotificationChannel#shouldShowLights()}.
427 * The number of milliseconds for the LED to be on while it's flashing.
428 * The hardware will do its best approximation.
430 * @see #FLAG_SHOW_LIGHTS
432 * @deprecated use {@link NotificationChannel#shouldShowLights()}.
438 * The number of milliseconds for the LED to be off while it's flashing.
439 * The hardware will do its best approximation.
441 * @see #FLAG_SHOW_LIGHTS
444 * @deprecated use {@link NotificationChannel#shouldShowLights()}.
450 * Specifies which values should be taken from the defaults.
452 * To set, OR the desired from {@link #DEFAULT_SOUND},
453 * {@link #DEFAULT_VIBRATE}, {@link #DEFAULT_LIGHTS}. For all default
454 * values, use {@link #DEFAULT_ALL}.
457 * @deprecated use {@link NotificationChannel#getSound()} and
458 * {@link NotificationChannel#shouldShowLights()} and
459 * {@link NotificationChannel#shouldVibrate()}.
465 * Bit to be bitwise-ored into the {@link #flags} field that should be
466 * set if you want the LED on for this notification.
468 * <li>To turn the LED off, pass 0 in the alpha channel for colorARGB
469 * or 0 for both ledOnMS and ledOffMS.</li>
470 * <li>To turn the LED on, pass 1 for ledOnMS and 0 for ledOffMS.</li>
471 * <li>To flash the LED, pass the number of milliseconds that it should
472 * be on and off to ledOnMS and ledOffMS.</li>
475 * Since hardware varies, you are not guaranteed that any of the values
476 * you pass are honored exactly. Use the system defaults (TODO) if possible
477 * because they will be set to values that work on any given hardware.
479 * The alpha channel must be set for forward compatibility.
481 * @deprecated use {@link NotificationChannel#shouldShowLights()}.
484 public static final int FLAG_SHOW_LIGHTS = 0x00000001;
487 * Bit to be bitwise-ored into the {@link #flags} field that should be
488 * set if this notification is in reference to something that is ongoing,
489 * like a phone call. It should not be set if this notification is in
490 * reference to something that happened at a particular point in time,
491 * like a missed phone call.
493 public static final int FLAG_ONGOING_EVENT = 0x00000002;
496 * Bit to be bitwise-ored into the {@link #flags} field that if set,
497 * the audio will be repeated until the notification is
498 * cancelled or the notification window is opened.
500 public static final int FLAG_INSISTENT = 0x00000004;
503 * Bit to be bitwise-ored into the {@link #flags} field that should be
504 * set if you would only like the sound, vibrate and ticker to be played
505 * if the notification was not already showing.
507 public static final int FLAG_ONLY_ALERT_ONCE = 0x00000008;
510 * Bit to be bitwise-ored into the {@link #flags} field that should be
511 * set if the notification should be canceled when it is clicked by the
514 public static final int FLAG_AUTO_CANCEL = 0x00000010;
517 * Bit to be bitwise-ored into the {@link #flags} field that should be
518 * set if the notification should not be canceled when the user clicks
519 * the Clear all button.
521 public static final int FLAG_NO_CLEAR = 0x00000020;
524 * Bit to be bitwise-ored into the {@link #flags} field that should be
525 * set if this notification represents a currently running service. This
526 * will normally be set for you by {@link Service#startForeground}.
528 public static final int FLAG_FOREGROUND_SERVICE = 0x00000040;
531 * Obsolete flag indicating high-priority notifications; use the priority field instead.
533 * @deprecated Use {@link #priority} with a positive value.
536 public static final int FLAG_HIGH_PRIORITY = 0x00000080;
539 * Bit to be bitswise-ored into the {@link #flags} field that should be
540 * set if this notification is relevant to the current device only
541 * and it is not recommended that it bridge to other devices.
543 public static final int FLAG_LOCAL_ONLY = 0x00000100;
546 * Bit to be bitswise-ored into the {@link #flags} field that should be
547 * set if this notification is the group summary for a group of notifications.
548 * Grouped notifications may display in a cluster or stack on devices which
549 * support such rendering. Requires a group key also be set using {@link Builder#setGroup}.
551 public static final int FLAG_GROUP_SUMMARY = 0x00000200;
554 * Bit to be bitswise-ored into the {@link #flags} field that should be
555 * set if this notification is the group summary for an auto-group of notifications.
560 public static final int FLAG_AUTOGROUP_SUMMARY = 0x00000400;
565 @IntDef({PRIORITY_DEFAULT,PRIORITY_LOW,PRIORITY_MIN,PRIORITY_HIGH,PRIORITY_MAX})
566 @Retention(RetentionPolicy.SOURCE)
567 public @interface Priority {}
570 * Default notification {@link #priority}. If your application does not prioritize its own
571 * notifications, use this value for all notifications.
573 * @deprecated use {@link NotificationManager#IMPORTANCE_DEFAULT} instead.
576 public static final int PRIORITY_DEFAULT = 0;
579 * Lower {@link #priority}, for items that are less important. The UI may choose to show these
580 * items smaller, or at a different position in the list, compared with your app's
581 * {@link #PRIORITY_DEFAULT} items.
583 * @deprecated use {@link NotificationManager#IMPORTANCE_LOW} instead.
586 public static final int PRIORITY_LOW = -1;
589 * Lowest {@link #priority}; these items might not be shown to the user except under special
590 * circumstances, such as detailed notification logs.
592 * @deprecated use {@link NotificationManager#IMPORTANCE_MIN} instead.
595 public static final int PRIORITY_MIN = -2;
598 * Higher {@link #priority}, for more important notifications or alerts. The UI may choose to
599 * show these items larger, or at a different position in notification lists, compared with
600 * your app's {@link #PRIORITY_DEFAULT} items.
602 * @deprecated use {@link NotificationManager#IMPORTANCE_HIGH} instead.
605 public static final int PRIORITY_HIGH = 1;
608 * Highest {@link #priority}, for your application's most important items that require the
609 * user's prompt attention or input.
611 * @deprecated use {@link NotificationManager#IMPORTANCE_HIGH} instead.
614 public static final int PRIORITY_MAX = 2;
617 * Relative priority for this notification.
619 * Priority is an indication of how much of the user's valuable attention should be consumed by
620 * this notification. Low-priority notifications may be hidden from the user in certain
621 * situations, while the user might be interrupted for a higher-priority notification. The
622 * system will make a determination about how to interpret this priority when presenting
626 * A notification that is at least {@link #PRIORITY_HIGH} is more likely to be presented
627 * as a heads-up notification.
630 * @deprecated use {@link NotificationChannel#getImportance()} instead.
637 * Accent color (an ARGB integer like the constants in {@link android.graphics.Color})
638 * to be applied by the standard Style templates when presenting this notification.
640 * The current template design constructs a colorful header image by overlaying the
641 * {@link #icon} image (stenciled in white) atop a field of this color. Alpha components are
645 public int color = COLOR_DEFAULT;
648 * Special value of {@link #color} telling the system not to decorate this notification with
649 * any special color but instead use default colors when presenting this notification.
652 public static final int COLOR_DEFAULT = 0; // AKA Color.TRANSPARENT
655 * Special value of {@link #color} used as a place holder for an invalid color.
659 public static final int COLOR_INVALID = 1;
662 * Sphere of visibility of this notification, which affects how and when the SystemUI reveals
663 * the notification's presence and contents in untrusted situations (namely, on the secure
666 * The default level, {@link #VISIBILITY_PRIVATE}, behaves exactly as notifications have always
667 * done on Android: The notification's {@link #icon} and {@link #tickerText} (if available) are
668 * shown in all situations, but the contents are only available if the device is unlocked for
669 * the appropriate user.
671 * A more permissive policy can be expressed by {@link #VISIBILITY_PUBLIC}; such a notification
672 * can be read even in an "insecure" context (that is, above a secure lockscreen).
673 * To modify the public version of this notification—for example, to redact some portions—see
674 * {@link Builder#setPublicVersion(Notification)}.
676 * Finally, a notification can be made {@link #VISIBILITY_SECRET}, which will suppress its icon
677 * and ticker until the user has bypassed the lockscreen.
679 public @Visibility int visibility;
682 @IntDef(prefix = { "VISIBILITY_" }, value = {
687 @Retention(RetentionPolicy.SOURCE)
688 public @interface Visibility {}
691 * Notification visibility: Show this notification in its entirety on all lockscreens.
695 public static final int VISIBILITY_PUBLIC = 1;
698 * Notification visibility: Show this notification on all lockscreens, but conceal sensitive or
699 * private information on secure lockscreens.
703 public static final int VISIBILITY_PRIVATE = 0;
706 * Notification visibility: Do not reveal any part of this notification on a secure lockscreen.
710 public static final int VISIBILITY_SECRET = -1;
713 * Notification category: incoming call (voice or video) or similar synchronous communication request.
715 public static final String CATEGORY_CALL = "call";
718 * Notification category: incoming direct message (SMS, instant message, etc.).
720 public static final String CATEGORY_MESSAGE = "msg";
723 * Notification category: asynchronous bulk message (email).
725 public static final String CATEGORY_EMAIL = "email";
728 * Notification category: calendar event.
730 public static final String CATEGORY_EVENT = "event";
733 * Notification category: promotion or advertisement.
735 public static final String CATEGORY_PROMO = "promo";
738 * Notification category: alarm or timer.
740 public static final String CATEGORY_ALARM = "alarm";
743 * Notification category: progress of a long-running background operation.
745 public static final String CATEGORY_PROGRESS = "progress";
748 * Notification category: social network or sharing update.
750 public static final String CATEGORY_SOCIAL = "social";
753 * Notification category: error in background operation or authentication status.
755 public static final String CATEGORY_ERROR = "err";
758 * Notification category: media transport control for playback.
760 public static final String CATEGORY_TRANSPORT = "transport";
763 * Notification category: system or device status update. Reserved for system use.
765 public static final String CATEGORY_SYSTEM = "sys";
768 * Notification category: indication of running background service.
770 public static final String CATEGORY_SERVICE = "service";
773 * Notification category: a specific, timely recommendation for a single thing.
774 * For example, a news app might want to recommend a news story it believes the user will
777 public static final String CATEGORY_RECOMMENDATION = "recommendation";
780 * Notification category: ongoing information about device or contextual status.
782 public static final String CATEGORY_STATUS = "status";
785 * Notification category: user-scheduled reminder.
787 public static final String CATEGORY_REMINDER = "reminder";
790 * One of the predefined notification categories (see the <code>CATEGORY_*</code> constants)
791 * that best describes this Notification. May be used by the system for ranking and filtering.
793 public String category;
795 private String mGroupKey;
798 * Get the key used to group this notification into a cluster or stack
799 * with other notifications on devices which support such rendering.
801 public String getGroup() {
805 private String mSortKey;
808 * Get a sort key that orders this notification among other notifications from the
809 * same package. This can be useful if an external sort was already applied and an app
810 * would like to preserve this. Notifications will be sorted lexicographically using this
811 * value, although providing different priorities in addition to providing sort key may
812 * cause this value to be ignored.
814 * <p>This sort key can also be used to order members of a notification group. See
815 * {@link Builder#setGroup}.
817 * @see String#compareTo(String)
819 public String getSortKey() {
824 * Additional semantic data to be carried around with this Notification.
826 * The extras keys defined here are intended to capture the original inputs to {@link Builder}
827 * APIs, and are intended to be used by
828 * {@link android.service.notification.NotificationListenerService} implementations to extract
829 * detailed information from notification objects.
831 public Bundle extras = new Bundle();
834 * All pending intents in the notification as the system needs to be able to access them but
835 * touching the extras bundle in the system process is not safe because the bundle may contain
836 * custom parcelable objects.
840 public ArraySet<PendingIntent> allPendingIntents;
843 * Token identifying the notification that is applying doze/bgcheck whitelisting to the
844 * pending intents inside of it, so only those will get the behavior.
848 static public IBinder whitelistToken;
851 * Must be set by a process to start associating tokens with Notification objects
852 * coming in to it. This is set by NotificationManagerService.
856 static public IBinder processWhitelistToken;
859 * {@link #extras} key: this is the title of the notification,
860 * as supplied to {@link Builder#setContentTitle(CharSequence)}.
862 public static final String EXTRA_TITLE = "android.title";
865 * {@link #extras} key: this is the title of the notification when shown in expanded form,
866 * e.g. as supplied to {@link BigTextStyle#setBigContentTitle(CharSequence)}.
868 public static final String EXTRA_TITLE_BIG = EXTRA_TITLE + ".big";
871 * {@link #extras} key: this is the main text payload, as supplied to
872 * {@link Builder#setContentText(CharSequence)}.
874 public static final String EXTRA_TEXT = "android.text";
877 * {@link #extras} key: this is a third line of text, as supplied to
878 * {@link Builder#setSubText(CharSequence)}.
880 public static final String EXTRA_SUB_TEXT = "android.subText";
883 * {@link #extras} key: this is the remote input history, as supplied to
884 * {@link Builder#setRemoteInputHistory(CharSequence[])}.
886 * Apps can fill this through {@link Builder#setRemoteInputHistory(CharSequence[])}
887 * with the most recent inputs that have been sent through a {@link RemoteInput} of this
888 * Notification and are expected to clear it once the it is no longer relevant (e.g. for chat
889 * notifications once the other party has responded).
891 * The extra with this key is of type CharSequence[] and contains the most recent entry at
892 * the 0 index, the second most recent at the 1 index, etc.
894 * @see Builder#setRemoteInputHistory(CharSequence[])
896 public static final String EXTRA_REMOTE_INPUT_HISTORY = "android.remoteInputHistory";
899 * {@link #extras} key: this is a small piece of additional text as supplied to
900 * {@link Builder#setContentInfo(CharSequence)}.
902 public static final String EXTRA_INFO_TEXT = "android.infoText";
905 * {@link #extras} key: this is a line of summary information intended to be shown
906 * alongside expanded notifications, as supplied to (e.g.)
907 * {@link BigTextStyle#setSummaryText(CharSequence)}.
909 public static final String EXTRA_SUMMARY_TEXT = "android.summaryText";
912 * {@link #extras} key: this is the longer text shown in the big form of a
913 * {@link BigTextStyle} notification, as supplied to
914 * {@link BigTextStyle#bigText(CharSequence)}.
916 public static final String EXTRA_BIG_TEXT = "android.bigText";
919 * {@link #extras} key: this is the resource ID of the notification's main small icon, as
920 * supplied to {@link Builder#setSmallIcon(int)}.
922 * @deprecated Use {@link #getSmallIcon()}, which supports a wider variety of icon sources.
925 public static final String EXTRA_SMALL_ICON = "android.icon";
928 * {@link #extras} key: this is a bitmap to be used instead of the small icon when showing the
929 * notification payload, as
930 * supplied to {@link Builder#setLargeIcon(android.graphics.Bitmap)}.
932 * @deprecated Use {@link #getLargeIcon()}, which supports a wider variety of icon sources.
935 public static final String EXTRA_LARGE_ICON = "android.largeIcon";
938 * {@link #extras} key: this is a bitmap to be used instead of the one from
939 * {@link Builder#setLargeIcon(android.graphics.Bitmap)} when the notification is
940 * shown in its expanded form, as supplied to
941 * {@link BigPictureStyle#bigLargeIcon(android.graphics.Bitmap)}.
943 public static final String EXTRA_LARGE_ICON_BIG = EXTRA_LARGE_ICON + ".big";
946 * {@link #extras} key: this is the progress value supplied to
947 * {@link Builder#setProgress(int, int, boolean)}.
949 public static final String EXTRA_PROGRESS = "android.progress";
952 * {@link #extras} key: this is the maximum value supplied to
953 * {@link Builder#setProgress(int, int, boolean)}.
955 public static final String EXTRA_PROGRESS_MAX = "android.progressMax";
958 * {@link #extras} key: whether the progress bar is indeterminate, supplied to
959 * {@link Builder#setProgress(int, int, boolean)}.
961 public static final String EXTRA_PROGRESS_INDETERMINATE = "android.progressIndeterminate";
964 * {@link #extras} key: whether {@link #when} should be shown as a count-up timer (specifically
965 * a {@link android.widget.Chronometer}) instead of a timestamp, as supplied to
966 * {@link Builder#setUsesChronometer(boolean)}.
968 public static final String EXTRA_SHOW_CHRONOMETER = "android.showChronometer";
971 * {@link #extras} key: whether the chronometer set on the notification should count down
972 * instead of counting up. Is only relevant if key {@link #EXTRA_SHOW_CHRONOMETER} is present.
973 * This extra is a boolean. The default is false.
975 public static final String EXTRA_CHRONOMETER_COUNT_DOWN = "android.chronometerCountDown";
978 * {@link #extras} key: whether {@link #when} should be shown,
979 * as supplied to {@link Builder#setShowWhen(boolean)}.
981 public static final String EXTRA_SHOW_WHEN = "android.showWhen";
984 * {@link #extras} key: this is a bitmap to be shown in {@link BigPictureStyle} expanded
985 * notifications, supplied to {@link BigPictureStyle#bigPicture(android.graphics.Bitmap)}.
987 public static final String EXTRA_PICTURE = "android.picture";
990 * {@link #extras} key: An array of CharSequences to show in {@link InboxStyle} expanded
991 * notifications, each of which was supplied to {@link InboxStyle#addLine(CharSequence)}.
993 public static final String EXTRA_TEXT_LINES = "android.textLines";
996 * {@link #extras} key: A string representing the name of the specific
997 * {@link android.app.Notification.Style} used to create this notification.
999 public static final String EXTRA_TEMPLATE = "android.template";
1002 * {@link #extras} key: A String array containing the people that this notification relates to,
1003 * each of which was supplied to {@link Builder#addPerson(String)}.
1005 public static final String EXTRA_PEOPLE = "android.people";
1008 * Allow certain system-generated notifications to appear before the device is provisioned.
1009 * Only available to notifications coming from the android package.
1013 @RequiresPermission(android.Manifest.permission.NOTIFICATION_DURING_SETUP)
1014 public static final String EXTRA_ALLOW_DURING_SETUP = "android.allowDuringSetup";
1017 * {@link #extras} key: A
1018 * {@link android.content.ContentUris content URI} pointing to an image that can be displayed
1019 * in the background when the notification is selected. The URI must point to an image stream
1020 * suitable for passing into
1021 * {@link android.graphics.BitmapFactory#decodeStream(java.io.InputStream)
1022 * BitmapFactory.decodeStream}; all other content types will be ignored. The content provider
1023 * URI used for this purpose must require no permissions to read the image data.
1025 public static final String EXTRA_BACKGROUND_IMAGE_URI = "android.backgroundImageUri";
1028 * {@link #extras} key: A
1029 * {@link android.media.session.MediaSession.Token} associated with a
1030 * {@link android.app.Notification.MediaStyle} notification.
1032 public static final String EXTRA_MEDIA_SESSION = "android.mediaSession";
1035 * {@link #extras} key: the indices of actions to be shown in the compact view,
1036 * as supplied to (e.g.) {@link MediaStyle#setShowActionsInCompactView(int...)}.
1038 public static final String EXTRA_COMPACT_ACTIONS = "android.compactActions";
1041 * {@link #extras} key: the username to be displayed for all messages sent by the user including
1043 * {@link android.app.Notification.MessagingStyle} notification. This extra is a
1044 * {@link CharSequence}
1046 public static final String EXTRA_SELF_DISPLAY_NAME = "android.selfDisplayName";
1049 * {@link #extras} key: a {@link CharSequence} to be displayed as the title to a conversation
1050 * represented by a {@link android.app.Notification.MessagingStyle}
1052 public static final String EXTRA_CONVERSATION_TITLE = "android.conversationTitle";
1055 * {@link #extras} key: an array of {@link android.app.Notification.MessagingStyle.Message}
1056 * bundles provided by a
1057 * {@link android.app.Notification.MessagingStyle} notification. This extra is a parcelable
1060 public static final String EXTRA_MESSAGES = "android.messages";
1063 * {@link #extras} key: an array of
1064 * {@link android.app.Notification.MessagingStyle#addHistoricMessage historic}
1065 * {@link android.app.Notification.MessagingStyle.Message} bundles provided by a
1066 * {@link android.app.Notification.MessagingStyle} notification. This extra is a parcelable
1069 public static final String EXTRA_HISTORIC_MESSAGES = "android.messages.historic";
1072 * {@link #extras} key: whether the notification should be colorized as
1073 * supplied to {@link Builder#setColorized(boolean)}}.
1075 public static final String EXTRA_COLORIZED = "android.colorized";
1080 public static final String EXTRA_BUILDER_APPLICATION_INFO = "android.appInfo";
1085 public static final String EXTRA_CONTAINS_CUSTOM_VIEW = "android.contains.customView";
1088 * {@link #extras} key: the audio contents of this notification.
1090 * This is for use when rendering the notification on an audio-focused interface;
1091 * the audio contents are a complete sound sample that contains the contents/body of the
1092 * notification. This may be used in substitute of a Text-to-Speech reading of the
1093 * notification. For example if the notification represents a voice message this should point
1094 * to the audio of that message.
1096 * The data stored under this key should be a String representation of a Uri that contains the
1097 * audio contents in one of the following formats: WAV, PCM 16-bit, AMR-WB.
1099 * This extra is unnecessary if you are using {@code MessagingStyle} since each {@code Message}
1100 * has a field for holding data URI. That field can be used for audio.
1101 * See {@code Message#setData}.
1106 * Notification.Builder myBuilder = (build your Notification as normal);
1107 * myBuilder.getExtras().putString(EXTRA_AUDIO_CONTENTS_URI, myAudioUri.toString());
1111 public static final String EXTRA_AUDIO_CONTENTS_URI = "android.audioContents";
1115 @RequiresPermission(android.Manifest.permission.SUBSTITUTE_NOTIFICATION_APP_NAME)
1116 public static final String EXTRA_SUBSTITUTE_APP_NAME = "android.substName";
1119 * This is set on the notification shown by the activity manager about all apps
1120 * running in the background. It indicates that the notification should be shown
1121 * only if any of the given apps do not already have a {@link #FLAG_FOREGROUND_SERVICE}
1122 * notification currently visible to the user. This is a string array of all
1123 * package names of the apps.
1126 public static final String EXTRA_FOREGROUND_APPS = "android.foregroundApps";
1128 private Icon mSmallIcon;
1129 private Icon mLargeIcon;
1131 private String mChannelId;
1132 private long mTimeout;
1134 private String mShortcutId;
1135 private CharSequence mSettingsText;
1138 @IntDef(prefix = { "GROUP_ALERT_" }, value = {
1139 GROUP_ALERT_ALL, GROUP_ALERT_CHILDREN, GROUP_ALERT_SUMMARY
1141 @Retention(RetentionPolicy.SOURCE)
1142 public @interface GroupAlertBehavior {}
1145 * Constant for {@link Builder#setGroupAlertBehavior(int)}, meaning that all notifications in a
1146 * group with sound or vibration ought to make sound or vibrate (respectively), so this
1147 * notification will not be muted when it is in a group.
1149 public static final int GROUP_ALERT_ALL = 0;
1152 * Constant for {@link Builder#setGroupAlertBehavior(int)}, meaning that all children
1153 * notification in a group should be silenced (no sound or vibration) even if they are posted
1154 * to a {@link NotificationChannel} that has sound and/or vibration. Use this constant to
1155 * mute this notification if this notification is a group child.
1157 * <p> For example, you might want to use this constant if you post a number of children
1158 * notifications at once (say, after a periodic sync), and only need to notify the user
1161 public static final int GROUP_ALERT_SUMMARY = 1;
1164 * Constant for {@link Builder#setGroupAlertBehavior(int)}, meaning that the summary
1165 * notification in a group should be silenced (no sound or vibration) even if they are
1166 * posted to a {@link NotificationChannel} that has sound and/or vibration. Use this constant
1167 * to mute this notification if this notification is a group summary.
1169 * <p>For example, you might want to use this constant if only the children notifications
1170 * in your group have content and the summary is only used to visually group notifications.
1172 public static final int GROUP_ALERT_CHILDREN = 2;
1174 private int mGroupAlertBehavior = GROUP_ALERT_CHILDREN;
1177 * If this notification is being shown as a badge, always show as a number.
1179 public static final int BADGE_ICON_NONE = 0;
1182 * If this notification is being shown as a badge, use the {@link #getSmallIcon()} to
1183 * represent this notification.
1185 public static final int BADGE_ICON_SMALL = 1;
1188 * If this notification is being shown as a badge, use the {@link #getLargeIcon()} to
1189 * represent this notification.
1191 public static final int BADGE_ICON_LARGE = 2;
1192 private int mBadgeIcon = BADGE_ICON_NONE;
1195 * Structure to encapsulate a named action that can be shown as part of this notification.
1196 * It must include an icon, a label, and a {@link PendingIntent} to be fired when the action is
1197 * selected by the user.
1199 * Apps should use {@link Notification.Builder#addAction(int, CharSequence, PendingIntent)}
1200 * or {@link Notification.Builder#addAction(Notification.Action)}
1201 * to attach actions.
1203 public static class Action implements Parcelable {
1205 * {@link #extras} key: Keys to a {@link Parcelable} {@link ArrayList} of
1206 * {@link RemoteInput}s.
1208 * This is intended for {@link RemoteInput}s that only accept data, meaning
1209 * {@link RemoteInput#getAllowFreeFormInput} is false, {@link RemoteInput#getChoices}
1210 * is null or empty, and {@link RemoteInput#getAllowedDataTypes is non-null and not
1211 * empty. These {@link RemoteInput}s will be ignored by devices that do not
1212 * support non-text-based {@link RemoteInput}s. See {@link Builder#build}.
1214 * You can test if a RemoteInput matches these constraints using
1215 * {@link RemoteInput#isDataOnly}.
1217 private static final String EXTRA_DATA_ONLY_INPUTS = "android.extra.DATA_ONLY_INPUTS";
1219 private final Bundle mExtras;
1221 private final RemoteInput[] mRemoteInputs;
1222 private boolean mAllowGeneratedReplies = true;
1225 * Small icon representing the action.
1227 * @deprecated Use {@link Action#getIcon()} instead.
1233 * Title of the action.
1235 public CharSequence title;
1238 * Intent to send when the user invokes this action. May be null, in which case the action
1239 * may be rendered in a disabled presentation by the system UI.
1241 public PendingIntent actionIntent;
1243 private Action(Parcel in) {
1244 if (in.readInt() != 0) {
1245 mIcon = Icon.CREATOR.createFromParcel(in);
1246 if (mIcon.getType() == Icon.TYPE_RESOURCE) {
1247 icon = mIcon.getResId();
1250 title = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
1251 if (in.readInt() == 1) {
1252 actionIntent = PendingIntent.CREATOR.createFromParcel(in);
1254 mExtras = Bundle.setDefusable(in.readBundle(), true);
1255 mRemoteInputs = in.createTypedArray(RemoteInput.CREATOR);
1256 mAllowGeneratedReplies = in.readInt() == 1;
1260 * @deprecated Use {@link android.app.Notification.Action.Builder}.
1263 public Action(int icon, CharSequence title, PendingIntent intent) {
1264 this(Icon.createWithResource("", icon), title, intent, new Bundle(), null, true);
1267 /** Keep in sync with {@link Notification.Action.Builder#Builder(Action)}! */
1268 private Action(Icon icon, CharSequence title, PendingIntent intent, Bundle extras,
1269 RemoteInput[] remoteInputs, boolean allowGeneratedReplies) {
1271 if (icon != null && icon.getType() == Icon.TYPE_RESOURCE) {
1272 this.icon = icon.getResId();
1275 this.actionIntent = intent;
1276 this.mExtras = extras != null ? extras : new Bundle();
1277 this.mRemoteInputs = remoteInputs;
1278 this.mAllowGeneratedReplies = allowGeneratedReplies;
1282 * Return an icon representing the action.
1284 public Icon getIcon() {
1285 if (mIcon == null && icon != 0) {
1286 // you snuck an icon in here without using the builder; let's try to keep it
1287 mIcon = Icon.createWithResource("", icon);
1293 * Get additional metadata carried around with this Action.
1295 public Bundle getExtras() {
1300 * Return whether the platform should automatically generate possible replies for this
1303 public boolean getAllowGeneratedReplies() {
1304 return mAllowGeneratedReplies;
1308 * Get the list of inputs to be collected from the user when this action is sent.
1309 * May return null if no remote inputs were added. Only returns inputs which accept
1310 * a text input. For inputs which only accept data use {@link #getDataOnlyRemoteInputs}.
1312 public RemoteInput[] getRemoteInputs() {
1313 return mRemoteInputs;
1317 * Get the list of inputs to be collected from the user that ONLY accept data when this
1318 * action is sent. These remote inputs are guaranteed to return true on a call to
1319 * {@link RemoteInput#isDataOnly}.
1321 * Returns null if there are no data-only remote inputs.
1323 * This method exists so that legacy RemoteInput collectors that pre-date the addition
1324 * of non-textual RemoteInputs do not access these remote inputs.
1326 public RemoteInput[] getDataOnlyRemoteInputs() {
1327 return (RemoteInput[]) mExtras.getParcelableArray(EXTRA_DATA_ONLY_INPUTS);
1331 * Builder class for {@link Action} objects.
1333 public static final class Builder {
1334 private final Icon mIcon;
1335 private final CharSequence mTitle;
1336 private final PendingIntent mIntent;
1337 private boolean mAllowGeneratedReplies = true;
1338 private final Bundle mExtras;
1339 private ArrayList<RemoteInput> mRemoteInputs;
1342 * Construct a new builder for {@link Action} object.
1343 * @param icon icon to show for this action
1344 * @param title the title of the action
1345 * @param intent the {@link PendingIntent} to fire when users trigger this action
1348 public Builder(int icon, CharSequence title, PendingIntent intent) {
1349 this(Icon.createWithResource("", icon), title, intent);
1353 * Construct a new builder for {@link Action} object.
1354 * @param icon icon to show for this action
1355 * @param title the title of the action
1356 * @param intent the {@link PendingIntent} to fire when users trigger this action
1358 public Builder(Icon icon, CharSequence title, PendingIntent intent) {
1359 this(icon, title, intent, new Bundle(), null, true);
1363 * Construct a new builder for {@link Action} object using the fields from an
1365 * @param action the action to read fields from.
1367 public Builder(Action action) {
1368 this(action.getIcon(), action.title, action.actionIntent,
1369 new Bundle(action.mExtras), action.getRemoteInputs(),
1370 action.getAllowGeneratedReplies());
1373 private Builder(Icon icon, CharSequence title, PendingIntent intent, Bundle extras,
1374 RemoteInput[] remoteInputs, boolean allowGeneratedReplies) {
1379 if (remoteInputs != null) {
1380 mRemoteInputs = new ArrayList<RemoteInput>(remoteInputs.length);
1381 Collections.addAll(mRemoteInputs, remoteInputs);
1383 mAllowGeneratedReplies = allowGeneratedReplies;
1387 * Merge additional metadata into this builder.
1389 * <p>Values within the Bundle will replace existing extras values in this Builder.
1391 * @see Notification.Action#extras
1393 public Builder addExtras(Bundle extras) {
1394 if (extras != null) {
1395 mExtras.putAll(extras);
1401 * Get the metadata Bundle used by this Builder.
1403 * <p>The returned Bundle is shared with this Builder.
1405 public Bundle getExtras() {
1410 * Add an input to be collected from the user when this action is sent.
1411 * Response values can be retrieved from the fired intent by using the
1412 * {@link RemoteInput#getResultsFromIntent} function.
1413 * @param remoteInput a {@link RemoteInput} to add to the action
1414 * @return this object for method chaining
1416 public Builder addRemoteInput(RemoteInput remoteInput) {
1417 if (mRemoteInputs == null) {
1418 mRemoteInputs = new ArrayList<RemoteInput>();
1420 mRemoteInputs.add(remoteInput);
1425 * Set whether the platform should automatically generate possible replies to add to
1426 * {@link RemoteInput#getChoices()}. If the {@link Action} doesn't have a
1427 * {@link RemoteInput}, this has no effect.
1428 * @param allowGeneratedReplies {@code true} to allow generated replies, {@code false}
1430 * @return this object for method chaining
1431 * The default value is {@code true}
1433 public Builder setAllowGeneratedReplies(boolean allowGeneratedReplies) {
1434 mAllowGeneratedReplies = allowGeneratedReplies;
1439 * Apply an extender to this action builder. Extenders may be used to add
1440 * metadata or change options on this builder.
1442 public Builder extend(Extender extender) {
1443 extender.extend(this);
1448 * Combine all of the options that have been set and return a new {@link Action}
1450 * @return the built action
1452 public Action build() {
1453 ArrayList<RemoteInput> dataOnlyInputs = new ArrayList<>();
1454 RemoteInput[] previousDataInputs =
1455 (RemoteInput[]) mExtras.getParcelableArray(EXTRA_DATA_ONLY_INPUTS);
1456 if (previousDataInputs != null) {
1457 for (RemoteInput input : previousDataInputs) {
1458 dataOnlyInputs.add(input);
1461 List<RemoteInput> textInputs = new ArrayList<>();
1462 if (mRemoteInputs != null) {
1463 for (RemoteInput input : mRemoteInputs) {
1464 if (input.isDataOnly()) {
1465 dataOnlyInputs.add(input);
1467 textInputs.add(input);
1471 if (!dataOnlyInputs.isEmpty()) {
1472 RemoteInput[] dataInputsArr =
1473 dataOnlyInputs.toArray(new RemoteInput[dataOnlyInputs.size()]);
1474 mExtras.putParcelableArray(EXTRA_DATA_ONLY_INPUTS, dataInputsArr);
1476 RemoteInput[] textInputsArr = textInputs.isEmpty()
1477 ? null : textInputs.toArray(new RemoteInput[textInputs.size()]);
1478 return new Action(mIcon, mTitle, mIntent, mExtras, textInputsArr,
1479 mAllowGeneratedReplies);
1484 public Action clone() {
1488 actionIntent, // safe to alias
1489 mExtras == null ? new Bundle() : new Bundle(mExtras),
1491 getAllowGeneratedReplies());
1494 public int describeContents() {
1498 public void writeToParcel(Parcel out, int flags) {
1499 final Icon ic = getIcon();
1502 ic.writeToParcel(out, 0);
1506 TextUtils.writeToParcel(title, out, flags);
1507 if (actionIntent != null) {
1509 actionIntent.writeToParcel(out, flags);
1513 out.writeBundle(mExtras);
1514 out.writeTypedArray(mRemoteInputs, flags);
1515 out.writeInt(mAllowGeneratedReplies ? 1 : 0);
1517 public static final Parcelable.Creator<Action> CREATOR =
1518 new Parcelable.Creator<Action>() {
1519 public Action createFromParcel(Parcel in) {
1520 return new Action(in);
1522 public Action[] newArray(int size) {
1523 return new Action[size];
1528 * Extender interface for use with {@link Builder#extend}. Extenders may be used to add
1529 * metadata or change options on an action builder.
1531 public interface Extender {
1533 * Apply this extender to a notification action builder.
1534 * @param builder the builder to be modified.
1535 * @return the build object for chaining.
1537 public Builder extend(Builder builder);
1541 * Wearable extender for notification actions. To add extensions to an action,
1542 * create a new {@link android.app.Notification.Action.WearableExtender} object using
1543 * the {@code WearableExtender()} constructor and apply it to a
1544 * {@link android.app.Notification.Action.Builder} using
1545 * {@link android.app.Notification.Action.Builder#extend}.
1547 * <pre class="prettyprint">
1548 * Notification.Action action = new Notification.Action.Builder(
1549 * R.drawable.archive_all, "Archive all", actionIntent)
1550 * .extend(new Notification.Action.WearableExtender()
1551 * .setAvailableOffline(false))
1554 public static final class WearableExtender implements Extender {
1555 /** Notification action extra which contains wearable extensions */
1556 private static final String EXTRA_WEARABLE_EXTENSIONS = "android.wearable.EXTENSIONS";
1558 // Keys within EXTRA_WEARABLE_EXTENSIONS for wearable options.
1559 private static final String KEY_FLAGS = "flags";
1560 private static final String KEY_IN_PROGRESS_LABEL = "inProgressLabel";
1561 private static final String KEY_CONFIRM_LABEL = "confirmLabel";
1562 private static final String KEY_CANCEL_LABEL = "cancelLabel";
1564 // Flags bitwise-ored to mFlags
1565 private static final int FLAG_AVAILABLE_OFFLINE = 0x1;
1566 private static final int FLAG_HINT_LAUNCHES_ACTIVITY = 1 << 1;
1567 private static final int FLAG_HINT_DISPLAY_INLINE = 1 << 2;
1569 // Default value for flags integer
1570 private static final int DEFAULT_FLAGS = FLAG_AVAILABLE_OFFLINE;
1572 private int mFlags = DEFAULT_FLAGS;
1574 private CharSequence mInProgressLabel;
1575 private CharSequence mConfirmLabel;
1576 private CharSequence mCancelLabel;
1579 * Create a {@link android.app.Notification.Action.WearableExtender} with default
1582 public WearableExtender() {
1586 * Create a {@link android.app.Notification.Action.WearableExtender} by reading
1587 * wearable options present in an existing notification action.
1588 * @param action the notification action to inspect.
1590 public WearableExtender(Action action) {
1591 Bundle wearableBundle = action.getExtras().getBundle(EXTRA_WEARABLE_EXTENSIONS);
1592 if (wearableBundle != null) {
1593 mFlags = wearableBundle.getInt(KEY_FLAGS, DEFAULT_FLAGS);
1594 mInProgressLabel = wearableBundle.getCharSequence(KEY_IN_PROGRESS_LABEL);
1595 mConfirmLabel = wearableBundle.getCharSequence(KEY_CONFIRM_LABEL);
1596 mCancelLabel = wearableBundle.getCharSequence(KEY_CANCEL_LABEL);
1601 * Apply wearable extensions to a notification action that is being built. This is
1602 * typically called by the {@link android.app.Notification.Action.Builder#extend}
1603 * method of {@link android.app.Notification.Action.Builder}.
1606 public Action.Builder extend(Action.Builder builder) {
1607 Bundle wearableBundle = new Bundle();
1609 if (mFlags != DEFAULT_FLAGS) {
1610 wearableBundle.putInt(KEY_FLAGS, mFlags);
1612 if (mInProgressLabel != null) {
1613 wearableBundle.putCharSequence(KEY_IN_PROGRESS_LABEL, mInProgressLabel);
1615 if (mConfirmLabel != null) {
1616 wearableBundle.putCharSequence(KEY_CONFIRM_LABEL, mConfirmLabel);
1618 if (mCancelLabel != null) {
1619 wearableBundle.putCharSequence(KEY_CANCEL_LABEL, mCancelLabel);
1622 builder.getExtras().putBundle(EXTRA_WEARABLE_EXTENSIONS, wearableBundle);
1627 public WearableExtender clone() {
1628 WearableExtender that = new WearableExtender();
1629 that.mFlags = this.mFlags;
1630 that.mInProgressLabel = this.mInProgressLabel;
1631 that.mConfirmLabel = this.mConfirmLabel;
1632 that.mCancelLabel = this.mCancelLabel;
1637 * Set whether this action is available when the wearable device is not connected to
1638 * a companion device. The user can still trigger this action when the wearable device is
1639 * offline, but a visual hint will indicate that the action may not be available.
1642 public WearableExtender setAvailableOffline(boolean availableOffline) {
1643 setFlag(FLAG_AVAILABLE_OFFLINE, availableOffline);
1648 * Get whether this action is available when the wearable device is not connected to
1649 * a companion device. The user can still trigger this action when the wearable device is
1650 * offline, but a visual hint will indicate that the action may not be available.
1653 public boolean isAvailableOffline() {
1654 return (mFlags & FLAG_AVAILABLE_OFFLINE) != 0;
1657 private void setFlag(int mask, boolean value) {
1666 * Set a label to display while the wearable is preparing to automatically execute the
1667 * action. This is usually a 'ing' verb ending in ellipsis like "Sending..."
1669 * @param label the label to display while the action is being prepared to execute
1670 * @return this object for method chaining
1672 public WearableExtender setInProgressLabel(CharSequence label) {
1673 mInProgressLabel = label;
1678 * Get the label to display while the wearable is preparing to automatically execute
1679 * the action. This is usually a 'ing' verb ending in ellipsis like "Sending..."
1681 * @return the label to display while the action is being prepared to execute
1683 public CharSequence getInProgressLabel() {
1684 return mInProgressLabel;
1688 * Set a label to display to confirm that the action should be executed.
1689 * This is usually an imperative verb like "Send".
1691 * @param label the label to confirm the action should be executed
1692 * @return this object for method chaining
1694 public WearableExtender setConfirmLabel(CharSequence label) {
1695 mConfirmLabel = label;
1700 * Get the label to display to confirm that the action should be executed.
1701 * This is usually an imperative verb like "Send".
1703 * @return the label to confirm the action should be executed
1705 public CharSequence getConfirmLabel() {
1706 return mConfirmLabel;
1710 * Set a label to display to cancel the action.
1711 * This is usually an imperative verb, like "Cancel".
1713 * @param label the label to display to cancel the action
1714 * @return this object for method chaining
1716 public WearableExtender setCancelLabel(CharSequence label) {
1717 mCancelLabel = label;
1722 * Get the label to display to cancel the action.
1723 * This is usually an imperative verb like "Cancel".
1725 * @return the label to display to cancel the action
1727 public CharSequence getCancelLabel() {
1728 return mCancelLabel;
1732 * Set a hint that this Action will launch an {@link Activity} directly, telling the
1733 * platform that it can generate the appropriate transitions.
1734 * @param hintLaunchesActivity {@code true} if the content intent will launch
1735 * an activity and transitions should be generated, false otherwise.
1736 * @return this object for method chaining
1738 public WearableExtender setHintLaunchesActivity(
1739 boolean hintLaunchesActivity) {
1740 setFlag(FLAG_HINT_LAUNCHES_ACTIVITY, hintLaunchesActivity);
1745 * Get a hint that this Action will launch an {@link Activity} directly, telling the
1746 * platform that it can generate the appropriate transitions
1747 * @return {@code true} if the content intent will launch an activity and transitions
1748 * should be generated, false otherwise. The default value is {@code false} if this was
1751 public boolean getHintLaunchesActivity() {
1752 return (mFlags & FLAG_HINT_LAUNCHES_ACTIVITY) != 0;
1756 * Set a hint that this Action should be displayed inline.
1758 * @param hintDisplayInline {@code true} if action should be displayed inline, false
1760 * @return this object for method chaining
1762 public WearableExtender setHintDisplayActionInline(
1763 boolean hintDisplayInline) {
1764 setFlag(FLAG_HINT_DISPLAY_INLINE, hintDisplayInline);
1769 * Get a hint that this Action should be displayed inline.
1771 * @return {@code true} if the Action should be displayed inline, {@code false}
1772 * otherwise. The default value is {@code false} if this was never set.
1774 public boolean getHintDisplayActionInline() {
1775 return (mFlags & FLAG_HINT_DISPLAY_INLINE) != 0;
1781 * Array of all {@link Action} structures attached to this notification by
1782 * {@link Builder#addAction(int, CharSequence, PendingIntent)}. Mostly useful for instances of
1783 * {@link android.service.notification.NotificationListenerService} that provide an alternative
1784 * interface for invoking actions.
1786 public Action[] actions;
1789 * Replacement version of this notification whose content will be shown
1790 * in an insecure context such as atop a secure keyguard. See {@link #visibility}
1791 * and {@link #VISIBILITY_PUBLIC}.
1793 public Notification publicVersion;
1796 * Constructs a Notification object with default values.
1797 * You might want to consider using {@link Builder} instead.
1799 public Notification()
1801 this.when = System.currentTimeMillis();
1802 this.creationTime = System.currentTimeMillis();
1803 this.priority = PRIORITY_DEFAULT;
1809 public Notification(Context context, int icon, CharSequence tickerText, long when,
1810 CharSequence contentTitle, CharSequence contentText, Intent contentIntent)
1812 new Builder(context)
1815 .setTicker(tickerText)
1816 .setContentTitle(contentTitle)
1817 .setContentText(contentText)
1818 .setContentIntent(PendingIntent.getActivity(context, 0, contentIntent, 0))
1823 * Constructs a Notification object with the information needed to
1824 * have a status bar icon without the standard expanded view.
1826 * @param icon The resource id of the icon to put in the status bar.
1827 * @param tickerText The text that flows by in the status bar when the notification first
1829 * @param when The time to show in the time field. In the System.currentTimeMillis
1832 * @deprecated Use {@link Builder} instead.
1835 public Notification(int icon, CharSequence tickerText, long when)
1838 this.tickerText = tickerText;
1840 this.creationTime = System.currentTimeMillis();
1844 * Unflatten the notification from a parcel.
1846 @SuppressWarnings("unchecked")
1847 public Notification(Parcel parcel) {
1848 // IMPORTANT: Add unmarshaling code in readFromParcel as the pending
1849 // intents in extras are always written as the last entry.
1850 readFromParcelImpl(parcel);
1851 // Must be read last!
1852 allPendingIntents = (ArraySet<PendingIntent>) parcel.readArraySet(null);
1855 private void readFromParcelImpl(Parcel parcel)
1857 int version = parcel.readInt();
1859 whitelistToken = parcel.readStrongBinder();
1860 if (whitelistToken == null) {
1861 whitelistToken = processWhitelistToken;
1863 // Propagate this token to all pending intents that are unmarshalled from the parcel.
1864 parcel.setClassCookie(PendingIntent.class, whitelistToken);
1866 when = parcel.readLong();
1867 creationTime = parcel.readLong();
1868 if (parcel.readInt() != 0) {
1869 mSmallIcon = Icon.CREATOR.createFromParcel(parcel);
1870 if (mSmallIcon.getType() == Icon.TYPE_RESOURCE) {
1871 icon = mSmallIcon.getResId();
1874 number = parcel.readInt();
1875 if (parcel.readInt() != 0) {
1876 contentIntent = PendingIntent.CREATOR.createFromParcel(parcel);
1878 if (parcel.readInt() != 0) {
1879 deleteIntent = PendingIntent.CREATOR.createFromParcel(parcel);
1881 if (parcel.readInt() != 0) {
1882 tickerText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
1884 if (parcel.readInt() != 0) {
1885 tickerView = RemoteViews.CREATOR.createFromParcel(parcel);
1887 if (parcel.readInt() != 0) {
1888 contentView = RemoteViews.CREATOR.createFromParcel(parcel);
1890 if (parcel.readInt() != 0) {
1891 mLargeIcon = Icon.CREATOR.createFromParcel(parcel);
1893 defaults = parcel.readInt();
1894 flags = parcel.readInt();
1895 if (parcel.readInt() != 0) {
1896 sound = Uri.CREATOR.createFromParcel(parcel);
1899 audioStreamType = parcel.readInt();
1900 if (parcel.readInt() != 0) {
1901 audioAttributes = AudioAttributes.CREATOR.createFromParcel(parcel);
1903 vibrate = parcel.createLongArray();
1904 ledARGB = parcel.readInt();
1905 ledOnMS = parcel.readInt();
1906 ledOffMS = parcel.readInt();
1907 iconLevel = parcel.readInt();
1909 if (parcel.readInt() != 0) {
1910 fullScreenIntent = PendingIntent.CREATOR.createFromParcel(parcel);
1913 priority = parcel.readInt();
1915 category = parcel.readString();
1917 mGroupKey = parcel.readString();
1919 mSortKey = parcel.readString();
1921 extras = Bundle.setDefusable(parcel.readBundle(), true); // may be null
1923 actions = parcel.createTypedArray(Action.CREATOR); // may be null
1925 if (parcel.readInt() != 0) {
1926 bigContentView = RemoteViews.CREATOR.createFromParcel(parcel);
1929 if (parcel.readInt() != 0) {
1930 headsUpContentView = RemoteViews.CREATOR.createFromParcel(parcel);
1933 visibility = parcel.readInt();
1935 if (parcel.readInt() != 0) {
1936 publicVersion = Notification.CREATOR.createFromParcel(parcel);
1939 color = parcel.readInt();
1941 if (parcel.readInt() != 0) {
1942 mChannelId = parcel.readString();
1944 mTimeout = parcel.readLong();
1946 if (parcel.readInt() != 0) {
1947 mShortcutId = parcel.readString();
1950 mBadgeIcon = parcel.readInt();
1952 if (parcel.readInt() != 0) {
1953 mSettingsText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
1956 mGroupAlertBehavior = parcel.readInt();
1960 public Notification clone() {
1961 Notification that = new Notification();
1962 cloneInto(that, true);
1967 * Copy all (or if heavy is false, all except Bitmaps and RemoteViews) members
1968 * of this into that.
1971 public void cloneInto(Notification that, boolean heavy) {
1972 that.whitelistToken = this.whitelistToken;
1973 that.when = this.when;
1974 that.creationTime = this.creationTime;
1975 that.mSmallIcon = this.mSmallIcon;
1976 that.number = this.number;
1978 // PendingIntents are global, so there's no reason (or way) to clone them.
1979 that.contentIntent = this.contentIntent;
1980 that.deleteIntent = this.deleteIntent;
1981 that.fullScreenIntent = this.fullScreenIntent;
1983 if (this.tickerText != null) {
1984 that.tickerText = this.tickerText.toString();
1986 if (heavy && this.tickerView != null) {
1987 that.tickerView = this.tickerView.clone();
1989 if (heavy && this.contentView != null) {
1990 that.contentView = this.contentView.clone();
1992 if (heavy && this.mLargeIcon != null) {
1993 that.mLargeIcon = this.mLargeIcon;
1995 that.iconLevel = this.iconLevel;
1996 that.sound = this.sound; // android.net.Uri is immutable
1997 that.audioStreamType = this.audioStreamType;
1998 if (this.audioAttributes != null) {
1999 that.audioAttributes = new AudioAttributes.Builder(this.audioAttributes).build();
2002 final long[] vibrate = this.vibrate;
2003 if (vibrate != null) {
2004 final int N = vibrate.length;
2005 final long[] vib = that.vibrate = new long[N];
2006 System.arraycopy(vibrate, 0, vib, 0, N);
2009 that.ledARGB = this.ledARGB;
2010 that.ledOnMS = this.ledOnMS;
2011 that.ledOffMS = this.ledOffMS;
2012 that.defaults = this.defaults;
2014 that.flags = this.flags;
2016 that.priority = this.priority;
2018 that.category = this.category;
2020 that.mGroupKey = this.mGroupKey;
2022 that.mSortKey = this.mSortKey;
2024 if (this.extras != null) {
2026 that.extras = new Bundle(this.extras);
2029 } catch (BadParcelableException e) {
2030 Log.e(TAG, "could not unparcel extras from notification: " + this, e);
2035 if (!ArrayUtils.isEmpty(allPendingIntents)) {
2036 that.allPendingIntents = new ArraySet<>(allPendingIntents);
2039 if (this.actions != null) {
2040 that.actions = new Action[this.actions.length];
2041 for(int i=0; i<this.actions.length; i++) {
2042 if ( this.actions[i] != null) {
2043 that.actions[i] = this.actions[i].clone();
2048 if (heavy && this.bigContentView != null) {
2049 that.bigContentView = this.bigContentView.clone();
2052 if (heavy && this.headsUpContentView != null) {
2053 that.headsUpContentView = this.headsUpContentView.clone();
2056 that.visibility = this.visibility;
2058 if (this.publicVersion != null) {
2059 that.publicVersion = new Notification();
2060 this.publicVersion.cloneInto(that.publicVersion, heavy);
2063 that.color = this.color;
2065 that.mChannelId = this.mChannelId;
2066 that.mTimeout = this.mTimeout;
2067 that.mShortcutId = this.mShortcutId;
2068 that.mBadgeIcon = this.mBadgeIcon;
2069 that.mSettingsText = this.mSettingsText;
2070 that.mGroupAlertBehavior = this.mGroupAlertBehavior;
2073 that.lightenPayload(); // will clean out extras
2078 * Removes heavyweight parts of the Notification object for archival or for sending to
2079 * listeners when the full contents are not necessary.
2082 public final void lightenPayload() {
2085 bigContentView = null;
2086 headsUpContentView = null;
2088 if (extras != null && !extras.isEmpty()) {
2089 final Set<String> keyset = extras.keySet();
2090 final int N = keyset.size();
2091 final String[] keys = keyset.toArray(new String[N]);
2092 for (int i=0; i<N; i++) {
2093 final String key = keys[i];
2094 if (TvExtender.EXTRA_TV_EXTENDER.equals(key)) {
2097 final Object obj = extras.get(key);
2099 ( obj instanceof Parcelable
2100 || obj instanceof Parcelable[]
2101 || obj instanceof SparseArray
2102 || obj instanceof ArrayList)) {
2110 * Make sure this CharSequence is safe to put into a bundle, which basically
2111 * means it had better not be some custom Parcelable implementation.
2114 public static CharSequence safeCharSequence(CharSequence cs) {
2115 if (cs == null) return cs;
2116 if (cs.length() > MAX_CHARSEQUENCE_LENGTH) {
2117 cs = cs.subSequence(0, MAX_CHARSEQUENCE_LENGTH);
2119 if (cs instanceof Parcelable) {
2120 Log.e(TAG, "warning: " + cs.getClass().getCanonicalName()
2121 + " instance is a custom Parcelable and not allowed in Notification");
2122 return cs.toString();
2124 return removeTextSizeSpans(cs);
2127 private static CharSequence removeTextSizeSpans(CharSequence charSequence) {
2128 if (charSequence instanceof Spanned) {
2129 Spanned ss = (Spanned) charSequence;
2130 Object[] spans = ss.getSpans(0, ss.length(), Object.class);
2131 SpannableStringBuilder builder = new SpannableStringBuilder(ss.toString());
2132 for (Object span : spans) {
2133 Object resultSpan = span;
2134 if (resultSpan instanceof CharacterStyle) {
2135 resultSpan = ((CharacterStyle) span).getUnderlying();
2137 if (resultSpan instanceof TextAppearanceSpan) {
2138 TextAppearanceSpan originalSpan = (TextAppearanceSpan) resultSpan;
2139 resultSpan = new TextAppearanceSpan(
2140 originalSpan.getFamily(),
2141 originalSpan.getTextStyle(),
2143 originalSpan.getTextColor(),
2144 originalSpan.getLinkTextColor());
2145 } else if (resultSpan instanceof RelativeSizeSpan
2146 || resultSpan instanceof AbsoluteSizeSpan) {
2151 builder.setSpan(resultSpan, ss.getSpanStart(span), ss.getSpanEnd(span),
2152 ss.getSpanFlags(span));
2156 return charSequence;
2159 public int describeContents() {
2164 * Flatten this notification into a parcel.
2166 public void writeToParcel(Parcel parcel, int flags) {
2167 // We need to mark all pending intents getting into the notification
2168 // system as being put there to later allow the notification ranker
2169 // to launch them and by doing so add the app to the battery saver white
2170 // list for a short period of time. The problem is that the system
2171 // cannot look into the extras as there may be parcelables there that
2172 // the platform does not know how to handle. To go around that we have
2173 // an explicit list of the pending intents in the extras bundle.
2174 final boolean collectPendingIntents = (allPendingIntents == null);
2175 if (collectPendingIntents) {
2176 PendingIntent.setOnMarshaledListener(
2177 (PendingIntent intent, Parcel out, int outFlags) -> {
2178 if (parcel == out) {
2179 if (allPendingIntents == null) {
2180 allPendingIntents = new ArraySet<>();
2182 allPendingIntents.add(intent);
2187 // IMPORTANT: Add marshaling code in writeToParcelImpl as we
2188 // want to intercept all pending events written to the parcel.
2189 writeToParcelImpl(parcel, flags);
2190 // Must be written last!
2191 parcel.writeArraySet(allPendingIntents);
2193 if (collectPendingIntents) {
2194 PendingIntent.setOnMarshaledListener(null);
2199 private void writeToParcelImpl(Parcel parcel, int flags) {
2202 parcel.writeStrongBinder(whitelistToken);
2203 parcel.writeLong(when);
2204 parcel.writeLong(creationTime);
2205 if (mSmallIcon == null && icon != 0) {
2206 // you snuck an icon in here without using the builder; let's try to keep it
2207 mSmallIcon = Icon.createWithResource("", icon);
2209 if (mSmallIcon != null) {
2211 mSmallIcon.writeToParcel(parcel, 0);
2215 parcel.writeInt(number);
2216 if (contentIntent != null) {
2218 contentIntent.writeToParcel(parcel, 0);
2222 if (deleteIntent != null) {
2224 deleteIntent.writeToParcel(parcel, 0);
2228 if (tickerText != null) {
2230 TextUtils.writeToParcel(tickerText, parcel, flags);
2234 if (tickerView != null) {
2236 tickerView.writeToParcel(parcel, 0);
2240 if (contentView != null) {
2242 contentView.writeToParcel(parcel, 0);
2246 if (mLargeIcon == null && largeIcon != null) {
2247 // you snuck an icon in here without using the builder; let's try to keep it
2248 mLargeIcon = Icon.createWithBitmap(largeIcon);
2250 if (mLargeIcon != null) {
2252 mLargeIcon.writeToParcel(parcel, 0);
2257 parcel.writeInt(defaults);
2258 parcel.writeInt(this.flags);
2260 if (sound != null) {
2262 sound.writeToParcel(parcel, 0);
2266 parcel.writeInt(audioStreamType);
2268 if (audioAttributes != null) {
2270 audioAttributes.writeToParcel(parcel, 0);
2275 parcel.writeLongArray(vibrate);
2276 parcel.writeInt(ledARGB);
2277 parcel.writeInt(ledOnMS);
2278 parcel.writeInt(ledOffMS);
2279 parcel.writeInt(iconLevel);
2281 if (fullScreenIntent != null) {
2283 fullScreenIntent.writeToParcel(parcel, 0);
2288 parcel.writeInt(priority);
2290 parcel.writeString(category);
2292 parcel.writeString(mGroupKey);
2294 parcel.writeString(mSortKey);
2296 parcel.writeBundle(extras); // null ok
2298 parcel.writeTypedArray(actions, 0); // null ok
2300 if (bigContentView != null) {
2302 bigContentView.writeToParcel(parcel, 0);
2307 if (headsUpContentView != null) {
2309 headsUpContentView.writeToParcel(parcel, 0);
2314 parcel.writeInt(visibility);
2316 if (publicVersion != null) {
2318 publicVersion.writeToParcel(parcel, 0);
2323 parcel.writeInt(color);
2325 if (mChannelId != null) {
2327 parcel.writeString(mChannelId);
2331 parcel.writeLong(mTimeout);
2333 if (mShortcutId != null) {
2335 parcel.writeString(mShortcutId);
2340 parcel.writeInt(mBadgeIcon);
2342 if (mSettingsText != null) {
2344 TextUtils.writeToParcel(mSettingsText, parcel, flags);
2349 parcel.writeInt(mGroupAlertBehavior);
2353 * Parcelable.Creator that instantiates Notification objects
2355 public static final Parcelable.Creator<Notification> CREATOR
2356 = new Parcelable.Creator<Notification>()
2358 public Notification createFromParcel(Parcel parcel)
2360 return new Notification(parcel);
2363 public Notification[] newArray(int size)
2365 return new Notification[size];
2370 * Sets the {@link #contentView} field to be a view with the standard "Latest Event"
2373 * <p>Uses the {@link #icon} and {@link #when} fields to set the icon and time fields
2375 * @param context The context for your application / activity.
2376 * @param contentTitle The title that goes in the expanded entry.
2377 * @param contentText The text that goes in the expanded entry.
2378 * @param contentIntent The intent to launch when the user clicks the expanded notification.
2379 * If this is an activity, it must include the
2380 * {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK} flag, which requires
2381 * that you take care of task management as described in the
2382 * <a href="{@docRoot}guide/topics/fundamentals/tasks-and-back-stack.html">Tasks and Back
2383 * Stack</a> document.
2385 * @deprecated Use {@link Builder} instead.
2389 public void setLatestEventInfo(Context context,
2390 CharSequence contentTitle, CharSequence contentText, PendingIntent contentIntent) {
2391 if (context.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1){
2392 Log.e(TAG, "setLatestEventInfo() is deprecated and you should feel deprecated.",
2396 if (context.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N) {
2397 extras.putBoolean(EXTRA_SHOW_WHEN, true);
2400 // ensure that any information already set directly is preserved
2401 final Notification.Builder builder = new Notification.Builder(context, this);
2403 // now apply the latestEventInfo fields
2404 if (contentTitle != null) {
2405 builder.setContentTitle(contentTitle);
2407 if (contentText != null) {
2408 builder.setContentText(contentText);
2410 builder.setContentIntent(contentIntent);
2412 builder.build(); // callers expect this notification to be ready to use
2418 public static void addFieldsFromContext(Context context, Notification notification) {
2419 addFieldsFromContext(context.getApplicationInfo(), notification);
2425 public static void addFieldsFromContext(ApplicationInfo ai, Notification notification) {
2426 notification.extras.putParcelable(EXTRA_BUILDER_APPLICATION_INFO, ai);
2430 public String toString() {
2431 StringBuilder sb = new StringBuilder();
2432 sb.append("Notification(channel=");
2433 sb.append(getChannelId());
2435 sb.append(priority);
2436 sb.append(" contentView=");
2437 if (contentView != null) {
2438 sb.append(contentView.getPackage());
2440 sb.append(Integer.toHexString(contentView.getLayoutId()));
2444 sb.append(" vibrate=");
2445 if ((this.defaults & DEFAULT_VIBRATE) != 0) {
2446 sb.append("default");
2447 } else if (this.vibrate != null) {
2448 int N = this.vibrate.length-1;
2450 for (int i=0; i<N; i++) {
2451 sb.append(this.vibrate[i]);
2455 sb.append(this.vibrate[N]);
2461 sb.append(" sound=");
2462 if ((this.defaults & DEFAULT_SOUND) != 0) {
2463 sb.append("default");
2464 } else if (this.sound != null) {
2465 sb.append(this.sound.toString());
2469 if (this.tickerText != null) {
2472 sb.append(" defaults=0x");
2473 sb.append(Integer.toHexString(this.defaults));
2474 sb.append(" flags=0x");
2475 sb.append(Integer.toHexString(this.flags));
2476 sb.append(String.format(" color=0x%08x", this.color));
2477 if (this.category != null) {
2478 sb.append(" category=");
2479 sb.append(this.category);
2481 if (this.mGroupKey != null) {
2482 sb.append(" groupKey=");
2483 sb.append(this.mGroupKey);
2485 if (this.mSortKey != null) {
2486 sb.append(" sortKey=");
2487 sb.append(this.mSortKey);
2489 if (actions != null) {
2490 sb.append(" actions=");
2491 sb.append(actions.length);
2494 sb.append(visibilityToString(this.visibility));
2495 if (this.publicVersion != null) {
2496 sb.append(" publicVersion=");
2497 sb.append(publicVersion.toString());
2500 return sb.toString();
2506 public static String visibilityToString(int vis) {
2508 case VISIBILITY_PRIVATE:
2510 case VISIBILITY_PUBLIC:
2512 case VISIBILITY_SECRET:
2515 return "UNKNOWN(" + String.valueOf(vis) + ")";
2522 public static String priorityToString(@Priority int pri) {
2528 case PRIORITY_DEFAULT:
2535 return "UNKNOWN(" + String.valueOf(pri) + ")";
2541 public String getChannel() {
2546 * Returns the id of the channel this notification posts to.
2548 public String getChannelId() {
2554 public long getTimeout() {
2559 * Returns the duration from posting after which this notification should be canceled by the
2560 * system, if it's not canceled already.
2562 public long getTimeoutAfter() {
2567 * Returns what icon should be shown for this notification if it is being displayed in a
2568 * Launcher that supports badging. Will be one of {@link #BADGE_ICON_NONE},
2569 * {@link #BADGE_ICON_SMALL}, or {@link #BADGE_ICON_LARGE}.
2571 public int getBadgeIconType() {
2576 * Returns the {@link ShortcutInfo#getId() id} that this notification supersedes, if any.
2578 * <p>Used by some Launchers that display notification content to hide shortcuts that duplicate
2581 public String getShortcutId() {
2587 * Returns the settings text provided to {@link Builder#setSettingsText(CharSequence)}.
2589 public CharSequence getSettingsText() {
2590 return mSettingsText;
2594 * Returns which type of notifications in a group are responsible for audibly alerting the
2595 * user. See {@link #GROUP_ALERT_ALL}, {@link #GROUP_ALERT_CHILDREN},
2596 * {@link #GROUP_ALERT_SUMMARY}.
2598 public @GroupAlertBehavior int getGroupAlertBehavior() {
2599 return mGroupAlertBehavior;
2603 * The small icon representing this notification in the status bar and content view.
2605 * @return the small icon representing this notification.
2607 * @see Builder#getSmallIcon()
2608 * @see Builder#setSmallIcon(Icon)
2610 public Icon getSmallIcon() {
2615 * Used when notifying to clean up legacy small icons.
2618 public void setSmallIcon(Icon icon) {
2623 * The large icon shown in this notification's content view.
2624 * @see Builder#getLargeIcon()
2625 * @see Builder#setLargeIcon(Icon)
2627 public Icon getLargeIcon() {
2634 public boolean isGroupSummary() {
2635 return mGroupKey != null && (flags & FLAG_GROUP_SUMMARY) != 0;
2641 public boolean isGroupChild() {
2642 return mGroupKey != null && (flags & FLAG_GROUP_SUMMARY) == 0;
2648 public boolean suppressAlertingDueToGrouping() {
2649 if (isGroupSummary()
2650 && getGroupAlertBehavior() == Notification.GROUP_ALERT_CHILDREN) {
2652 } else if (isGroupChild()
2653 && getGroupAlertBehavior() == Notification.GROUP_ALERT_SUMMARY) {
2660 * Builder class for {@link Notification} objects.
2662 * Provides a convenient way to set the various fields of a {@link Notification} and generate
2663 * content views using the platform's notification layout template. If your app supports
2664 * versions of Android as old as API level 4, you can instead use
2665 * {@link android.support.v4.app.NotificationCompat.Builder NotificationCompat.Builder},
2666 * available in the <a href="{@docRoot}tools/extras/support-library.html">Android Support
2671 * <pre class="prettyprint">
2672 * Notification noti = new Notification.Builder(mContext)
2673 * .setContentTitle("New mail from " + sender.toString())
2674 * .setContentText(subject)
2675 * .setSmallIcon(R.drawable.new_mail)
2676 * .setLargeIcon(aBitmap)
2680 public static class Builder {
2684 public static final String EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT =
2685 "android.rebuild.contentViewActionCount";
2689 public static final String EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT
2690 = "android.rebuild.bigViewActionCount";
2694 public static final String EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT
2695 = "android.rebuild.hudViewActionCount";
2697 private static final int MAX_ACTION_BUTTONS = 3;
2699 private static final boolean USE_ONLY_TITLE_IN_LOW_PRIORITY_SUMMARY =
2700 SystemProperties.getBoolean("notifications.only_title", true);
2703 * The lightness difference that has to be added to the primary text color to obtain the
2704 * secondary text color when the background is light.
2706 private static final int LIGHTNESS_TEXT_DIFFERENCE_LIGHT = 20;
2709 * The lightness difference that has to be added to the primary text color to obtain the
2710 * secondary text color when the background is dark.
2711 * A bit less then the above value, since it looks better on dark backgrounds.
2713 private static final int LIGHTNESS_TEXT_DIFFERENCE_DARK = -10;
2715 private Context mContext;
2716 private Notification mN;
2717 private Bundle mUserExtras = new Bundle();
2718 private Style mStyle;
2719 private ArrayList<Action> mActions = new ArrayList<Action>(MAX_ACTION_BUTTONS);
2720 private ArrayList<String> mPersonList = new ArrayList<String>();
2721 private NotificationColorUtil mColorUtil;
2722 private boolean mIsLegacy;
2723 private boolean mIsLegacyInitialized;
2726 * Caches a contrast-enhanced version of {@link #mCachedContrastColorIsFor}.
2728 private int mCachedContrastColor = COLOR_INVALID;
2729 private int mCachedContrastColorIsFor = COLOR_INVALID;
2731 * Caches a ambient version of {@link #mCachedContrastColorIsFor}.
2733 private int mCachedAmbientColor = COLOR_INVALID;
2734 private int mCachedAmbientColorIsFor = COLOR_INVALID;
2737 * Caches an instance of StandardTemplateParams. Note that this may have been used before,
2738 * so make sure to call {@link StandardTemplateParams#reset()} before using it.
2740 StandardTemplateParams mParams = new StandardTemplateParams();
2741 private int mTextColorsAreForBackground = COLOR_INVALID;
2742 private int mPrimaryTextColor = COLOR_INVALID;
2743 private int mSecondaryTextColor = COLOR_INVALID;
2744 private int mActionBarColor = COLOR_INVALID;
2745 private int mBackgroundColor = COLOR_INVALID;
2746 private int mForegroundColor = COLOR_INVALID;
2747 private int mBackgroundColorHint = COLOR_INVALID;
2748 private boolean mRebuildStyledRemoteViews;
2751 * Constructs a new Builder with the defaults:
2754 * A {@link Context} that will be used by the Builder to construct the
2755 * RemoteViews. The Context will not be held past the lifetime of this Builder
2758 * The constructed Notification will be posted on this
2759 * {@link NotificationChannel}. To use a NotificationChannel, it must first be
2760 * created using {@link NotificationManager#createNotificationChannel}.
2762 public Builder(Context context, String channelId) {
2763 this(context, (Notification) null);
2764 mN.mChannelId = channelId;
2768 * @deprecated use {@link Notification.Builder#Notification.Builder(Context, String)}
2769 * instead. All posted Notifications must specify a NotificationChannel Id.
2772 public Builder(Context context) {
2773 this(context, (Notification) null);
2779 public Builder(Context context, Notification toAdopt) {
2782 if (toAdopt == null) {
2783 mN = new Notification();
2784 if (context.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N) {
2785 mN.extras.putBoolean(EXTRA_SHOW_WHEN, true);
2787 mN.priority = PRIORITY_DEFAULT;
2788 mN.visibility = VISIBILITY_PRIVATE;
2791 if (mN.actions != null) {
2792 Collections.addAll(mActions, mN.actions);
2795 if (mN.extras.containsKey(EXTRA_PEOPLE)) {
2796 Collections.addAll(mPersonList, mN.extras.getStringArray(EXTRA_PEOPLE));
2799 if (mN.getSmallIcon() == null && mN.icon != 0) {
2800 setSmallIcon(mN.icon);
2803 if (mN.getLargeIcon() == null && mN.largeIcon != null) {
2804 setLargeIcon(mN.largeIcon);
2807 String templateClass = mN.extras.getString(EXTRA_TEMPLATE);
2808 if (!TextUtils.isEmpty(templateClass)) {
2809 final Class<? extends Style> styleClass
2810 = getNotificationStyleClass(templateClass);
2811 if (styleClass == null) {
2812 Log.d(TAG, "Unknown style class: " + templateClass);
2815 final Constructor<? extends Style> ctor =
2816 styleClass.getDeclaredConstructor();
2817 ctor.setAccessible(true);
2818 final Style style = ctor.newInstance();
2819 style.restoreFromExtras(mN.extras);
2821 if (style != null) {
2824 } catch (Throwable t) {
2825 Log.e(TAG, "Could not create Style", t);
2833 private NotificationColorUtil getColorUtil() {
2834 if (mColorUtil == null) {
2835 mColorUtil = NotificationColorUtil.getInstance(mContext);
2841 * If this notification is duplicative of a Launcher shortcut, sets the
2842 * {@link ShortcutInfo#getId() id} of the shortcut, in case the Launcher wants to hide
2845 * This field will be ignored by Launchers that don't support badging, don't show
2846 * notification content, or don't show {@link android.content.pm.ShortcutManager shortcuts}.
2848 * @param shortcutId the {@link ShortcutInfo#getId() id} of the shortcut this notification
2851 public Builder setShortcutId(String shortcutId) {
2852 mN.mShortcutId = shortcutId;
2857 * Sets which icon to display as a badge for this notification.
2859 * Must be one of {@link #BADGE_ICON_NONE}, {@link #BADGE_ICON_SMALL},
2860 * {@link #BADGE_ICON_LARGE}.
2862 * Note: This value might be ignored, for launchers that don't support badge icons.
2864 public Builder setBadgeIconType(int icon) {
2865 mN.mBadgeIcon = icon;
2870 * Sets the group alert behavior for this notification. Use this method to mute this
2871 * notification if alerts for this notification's group should be handled by a different
2872 * notification. This is only applicable for notifications that belong to a
2873 * {@link #setGroup(String) group}.
2875 * <p> The default value is {@link #GROUP_ALERT_ALL}.</p>
2877 public Builder setGroupAlertBehavior(@GroupAlertBehavior int groupAlertBehavior) {
2878 mN.mGroupAlertBehavior = groupAlertBehavior;
2884 public Builder setChannel(String channelId) {
2885 mN.mChannelId = channelId;
2890 * Specifies the channel the notification should be delivered on.
2892 public Builder setChannelId(String channelId) {
2893 mN.mChannelId = channelId;
2899 public Builder setTimeout(long durationMs) {
2900 mN.mTimeout = durationMs;
2905 * Specifies a duration in milliseconds after which this notification should be canceled,
2906 * if it is not already canceled.
2908 public Builder setTimeoutAfter(long durationMs) {
2909 mN.mTimeout = durationMs;
2914 * Add a timestamp pertaining to the notification (usually the time the event occurred).
2916 * For apps targeting {@link android.os.Build.VERSION_CODES#N} and above, this time is not
2917 * shown anymore by default and must be opted into by using
2918 * {@link android.app.Notification.Builder#setShowWhen(boolean)}
2920 * @see Notification#when
2922 public Builder setWhen(long when) {
2928 * Control whether the timestamp set with {@link #setWhen(long) setWhen} is shown
2929 * in the content view.
2930 * For apps targeting {@link android.os.Build.VERSION_CODES#N} and above, this defaults to
2931 * {@code false}. For earlier apps, the default is {@code true}.
2933 public Builder setShowWhen(boolean show) {
2934 mN.extras.putBoolean(EXTRA_SHOW_WHEN, show);
2939 * Show the {@link Notification#when} field as a stopwatch.
2941 * Instead of presenting <code>when</code> as a timestamp, the notification will show an
2942 * automatically updating display of the minutes and seconds since <code>when</code>.
2944 * Useful when showing an elapsed time (like an ongoing phone call).
2946 * The counter can also be set to count down to <code>when</code> when using
2947 * {@link #setChronometerCountDown(boolean)}.
2949 * @see android.widget.Chronometer
2950 * @see Notification#when
2951 * @see #setChronometerCountDown(boolean)
2953 public Builder setUsesChronometer(boolean b) {
2954 mN.extras.putBoolean(EXTRA_SHOW_CHRONOMETER, b);
2959 * Sets the Chronometer to count down instead of counting up.
2961 * <p>This is only relevant if {@link #setUsesChronometer(boolean)} has been set to true.
2962 * If it isn't set the chronometer will count up.
2964 * @see #setUsesChronometer(boolean)
2966 public Builder setChronometerCountDown(boolean countDown) {
2967 mN.extras.putBoolean(EXTRA_CHRONOMETER_COUNT_DOWN, countDown);
2972 * Set the small icon resource, which will be used to represent the notification in the
2976 * The platform template for the expanded view will draw this icon in the left, unless a
2977 * {@link #setLargeIcon(Bitmap) large icon} has also been specified, in which case the small
2978 * icon will be moved to the right-hand side.
2982 * A resource ID in the application's package of the drawable to use.
2983 * @see Notification#icon
2985 public Builder setSmallIcon(@DrawableRes int icon) {
2986 return setSmallIcon(icon != 0
2987 ? Icon.createWithResource(mContext, icon)
2992 * A variant of {@link #setSmallIcon(int) setSmallIcon(int)} that takes an additional
2993 * level parameter for when the icon is a {@link android.graphics.drawable.LevelListDrawable
2994 * LevelListDrawable}.
2996 * @param icon A resource ID in the application's package of the drawable to use.
2997 * @param level The level to use for the icon.
2999 * @see Notification#icon
3000 * @see Notification#iconLevel
3002 public Builder setSmallIcon(@DrawableRes int icon, int level) {
3003 mN.iconLevel = level;
3004 return setSmallIcon(icon);
3008 * Set the small icon, which will be used to represent the notification in the
3009 * status bar and content view (unless overriden there by a
3010 * {@link #setLargeIcon(Bitmap) large icon}).
3012 * @param icon An Icon object to use.
3013 * @see Notification#icon
3015 public Builder setSmallIcon(Icon icon) {
3016 mN.setSmallIcon(icon);
3017 if (icon != null && icon.getType() == Icon.TYPE_RESOURCE) {
3018 mN.icon = icon.getResId();
3024 * Set the first line of text in the platform notification template.
3026 public Builder setContentTitle(CharSequence title) {
3027 mN.extras.putCharSequence(EXTRA_TITLE, safeCharSequence(title));
3032 * Set the second line of text in the platform notification template.
3034 public Builder setContentText(CharSequence text) {
3035 mN.extras.putCharSequence(EXTRA_TEXT, safeCharSequence(text));
3040 * This provides some additional information that is displayed in the notification. No
3041 * guarantees are given where exactly it is displayed.
3043 * <p>This information should only be provided if it provides an essential
3044 * benefit to the understanding of the notification. The more text you provide the
3045 * less readable it becomes. For example, an email client should only provide the account
3046 * name here if more than one email account has been added.</p>
3048 * <p>As of {@link android.os.Build.VERSION_CODES#N} this information is displayed in the
3049 * notification header area.
3051 * On Android versions before {@link android.os.Build.VERSION_CODES#N}
3052 * this will be shown in the third line of text in the platform notification template.
3053 * You should not be using {@link #setProgress(int, int, boolean)} at the
3054 * same time on those versions; they occupy the same place.
3057 public Builder setSubText(CharSequence text) {
3058 mN.extras.putCharSequence(EXTRA_SUB_TEXT, safeCharSequence(text));
3063 * Provides text that will appear as a link to your application's settings.
3065 * <p>This text does not appear within notification {@link Style templates} but may
3066 * appear when the user uses an affordance to learn more about the notification.
3067 * Additionally, this text will not appear unless you provide a valid link target by
3068 * handling {@link #INTENT_CATEGORY_NOTIFICATION_PREFERENCES}.
3070 * <p>This text is meant to be concise description about what the user can customize
3071 * when they click on this link. The recommended maximum length is 40 characters.
3075 public Builder setSettingsText(CharSequence text) {
3076 mN.mSettingsText = safeCharSequence(text);
3081 * Set the remote input history.
3083 * This should be set to the most recent inputs that have been sent
3084 * through a {@link RemoteInput} of this Notification and cleared once the it is no
3085 * longer relevant (e.g. for chat notifications once the other party has responded).
3087 * The most recent input must be stored at the 0 index, the second most recent at the
3088 * 1 index, etc. Note that the system will limit both how far back the inputs will be shown
3089 * and how much of each individual input is shown.
3091 * <p>Note: The reply text will only be shown on notifications that have least one action
3092 * with a {@code RemoteInput}.</p>
3094 public Builder setRemoteInputHistory(CharSequence[] text) {
3096 mN.extras.putCharSequenceArray(EXTRA_REMOTE_INPUT_HISTORY, null);
3098 final int N = Math.min(MAX_REPLY_HISTORY, text.length);
3099 CharSequence[] safe = new CharSequence[N];
3100 for (int i = 0; i < N; i++) {
3101 safe[i] = safeCharSequence(text[i]);
3103 mN.extras.putCharSequenceArray(EXTRA_REMOTE_INPUT_HISTORY, safe);
3109 * Sets the number of items this notification represents. May be displayed as a badge count
3110 * for Launchers that support badging.
3112 public Builder setNumber(int number) {
3118 * A small piece of additional information pertaining to this notification.
3120 * The platform template will draw this on the last line of the notification, at the far
3121 * right (to the right of a smallIcon if it has been placed there).
3123 * @deprecated use {@link #setSubText(CharSequence)} instead to set a text in the header.
3124 * For legacy apps targeting a version below {@link android.os.Build.VERSION_CODES#N} this
3125 * field will still show up, but the subtext will take precedence.
3128 public Builder setContentInfo(CharSequence info) {
3129 mN.extras.putCharSequence(EXTRA_INFO_TEXT, safeCharSequence(info));
3134 * Set the progress this notification represents.
3136 * The platform template will represent this using a {@link ProgressBar}.
3138 public Builder setProgress(int max, int progress, boolean indeterminate) {
3139 mN.extras.putInt(EXTRA_PROGRESS, progress);
3140 mN.extras.putInt(EXTRA_PROGRESS_MAX, max);
3141 mN.extras.putBoolean(EXTRA_PROGRESS_INDETERMINATE, indeterminate);
3146 * Supply a custom RemoteViews to use instead of the platform template.
3148 * Use {@link #setCustomContentView(RemoteViews)} instead.
3151 public Builder setContent(RemoteViews views) {
3152 return setCustomContentView(views);
3156 * Supply custom RemoteViews to use instead of the platform template.
3158 * This will override the layout that would otherwise be constructed by this Builder
3161 public Builder setCustomContentView(RemoteViews contentView) {
3162 mN.contentView = contentView;
3167 * Supply custom RemoteViews to use instead of the platform template in the expanded form.
3169 * This will override the expanded layout that would otherwise be constructed by this
3172 public Builder setCustomBigContentView(RemoteViews contentView) {
3173 mN.bigContentView = contentView;
3178 * Supply custom RemoteViews to use instead of the platform template in the heads up dialog.
3180 * This will override the heads-up layout that would otherwise be constructed by this
3183 public Builder setCustomHeadsUpContentView(RemoteViews contentView) {
3184 mN.headsUpContentView = contentView;
3189 * Supply a {@link PendingIntent} to be sent when the notification is clicked.
3191 * As of {@link android.os.Build.VERSION_CODES#HONEYCOMB}, if this field is unset and you
3192 * have specified a custom RemoteViews with {@link #setContent(RemoteViews)}, you can use
3193 * {@link RemoteViews#setOnClickPendingIntent RemoteViews.setOnClickPendingIntent(int,PendingIntent)}
3194 * to assign PendingIntents to individual views in that custom layout (i.e., to create
3195 * clickable buttons inside the notification view).
3197 * @see Notification#contentIntent Notification.contentIntent
3199 public Builder setContentIntent(PendingIntent intent) {
3200 mN.contentIntent = intent;
3205 * Supply a {@link PendingIntent} to send when the notification is cleared explicitly by the user.
3207 * @see Notification#deleteIntent
3209 public Builder setDeleteIntent(PendingIntent intent) {
3210 mN.deleteIntent = intent;
3215 * An intent to launch instead of posting the notification to the status bar.
3216 * Only for use with extremely high-priority notifications demanding the user's
3217 * <strong>immediate</strong> attention, such as an incoming phone call or
3218 * alarm clock that the user has explicitly set to a particular time.
3219 * If this facility is used for something else, please give the user an option
3220 * to turn it off and use a normal notification, as this can be extremely
3224 * The system UI may choose to display a heads-up notification, instead of
3225 * launching this intent, while the user is using the device.
3228 * @param intent The pending intent to launch.
3229 * @param highPriority Passing true will cause this notification to be sent
3230 * even if other notifications are suppressed.
3232 * @see Notification#fullScreenIntent
3234 public Builder setFullScreenIntent(PendingIntent intent, boolean highPriority) {
3235 mN.fullScreenIntent = intent;
3236 setFlag(FLAG_HIGH_PRIORITY, highPriority);
3241 * Set the "ticker" text which is sent to accessibility services.
3243 * @see Notification#tickerText
3245 public Builder setTicker(CharSequence tickerText) {
3246 mN.tickerText = safeCharSequence(tickerText);
3251 * Obsolete version of {@link #setTicker(CharSequence)}.
3255 public Builder setTicker(CharSequence tickerText, RemoteViews views) {
3256 setTicker(tickerText);
3262 * Add a large icon to the notification content view.
3264 * In the platform template, this image will be shown on the left of the notification view
3265 * in place of the {@link #setSmallIcon(Icon) small icon} (which will be placed in a small
3266 * badge atop the large icon).
3268 public Builder setLargeIcon(Bitmap b) {
3269 return setLargeIcon(b != null ? Icon.createWithBitmap(b) : null);
3273 * Add a large icon to the notification content view.
3275 * In the platform template, this image will be shown on the left of the notification view
3276 * in place of the {@link #setSmallIcon(Icon) small icon} (which will be placed in a small
3277 * badge atop the large icon).
3279 public Builder setLargeIcon(Icon icon) {
3280 mN.mLargeIcon = icon;
3281 mN.extras.putParcelable(EXTRA_LARGE_ICON, icon);
3286 * Set the sound to play.
3288 * It will be played using the {@link #AUDIO_ATTRIBUTES_DEFAULT default audio attributes}
3289 * for notifications.
3291 * @deprecated use {@link NotificationChannel#setSound(Uri, AudioAttributes)} instead.
3294 public Builder setSound(Uri sound) {
3296 mN.audioAttributes = AUDIO_ATTRIBUTES_DEFAULT;
3301 * Set the sound to play, along with a specific stream on which to play it.
3303 * See {@link android.media.AudioManager} for the <code>STREAM_</code> constants.
3305 * @deprecated use {@link NotificationChannel#setSound(Uri, AudioAttributes)}.
3308 public Builder setSound(Uri sound, int streamType) {
3309 PlayerBase.deprecateStreamTypeForPlayback(streamType, "Notification", "setSound()");
3311 mN.audioStreamType = streamType;
3316 * Set the sound to play, along with specific {@link AudioAttributes audio attributes} to
3317 * use during playback.
3319 * @deprecated use {@link NotificationChannel#setSound(Uri, AudioAttributes)} instead.
3320 * @see Notification#sound
3323 public Builder setSound(Uri sound, AudioAttributes audioAttributes) {
3325 mN.audioAttributes = audioAttributes;
3330 * Set the vibration pattern to use.
3332 * See {@link android.os.Vibrator#vibrate(long[], int)} for a discussion of the
3333 * <code>pattern</code> parameter.
3336 * A notification that vibrates is more likely to be presented as a heads-up notification.
3339 * @deprecated use {@link NotificationChannel#setVibrationPattern(long[])} instead.
3340 * @see Notification#vibrate
3343 public Builder setVibrate(long[] pattern) {
3344 mN.vibrate = pattern;
3349 * Set the desired color for the indicator LED on the device, as well as the
3350 * blink duty cycle (specified in milliseconds).
3353 * Not all devices will honor all (or even any) of these values.
3355 * @deprecated use {@link NotificationChannel#enableLights(boolean)} instead.
3356 * @see Notification#ledARGB
3357 * @see Notification#ledOnMS
3358 * @see Notification#ledOffMS
3361 public Builder setLights(@ColorInt int argb, int onMs, int offMs) {
3364 mN.ledOffMS = offMs;
3365 if (onMs != 0 || offMs != 0) {
3366 mN.flags |= FLAG_SHOW_LIGHTS;
3372 * Set whether this is an "ongoing" notification.
3375 * Ongoing notifications cannot be dismissed by the user, so your application or service
3376 * must take care of canceling them.
3379 * They are typically used to indicate a background task that the user is actively engaged
3380 * with (e.g., playing music) or is pending in some way and therefore occupying the device
3381 * (e.g., a file download, sync operation, active network connection).
3384 * @see Notification#FLAG_ONGOING_EVENT
3385 * @see Service#setForeground(boolean)
3387 public Builder setOngoing(boolean ongoing) {
3388 setFlag(FLAG_ONGOING_EVENT, ongoing);
3393 * Set whether this notification should be colorized. When set, the color set with
3394 * {@link #setColor(int)} will be used as the background color of this notification.
3396 * This should only be used for high priority ongoing tasks like navigation, an ongoing
3397 * call, or other similarly high-priority events for the user.
3399 * For most styles, the coloring will only be applied if the notification is for a
3400 * foreground service notification.
3401 * However, for {@link MediaStyle} and {@link DecoratedMediaCustomViewStyle} notifications
3402 * that have a media session attached there is no such requirement.
3404 * @see Builder#setColor(int)
3405 * @see MediaStyle#setMediaSession(MediaSession.Token)
3407 public Builder setColorized(boolean colorize) {
3408 mN.extras.putBoolean(EXTRA_COLORIZED, colorize);
3413 * Set this flag if you would only like the sound, vibrate
3414 * and ticker to be played if the notification is not already showing.
3416 * @see Notification#FLAG_ONLY_ALERT_ONCE
3418 public Builder setOnlyAlertOnce(boolean onlyAlertOnce) {
3419 setFlag(FLAG_ONLY_ALERT_ONCE, onlyAlertOnce);
3424 * Make this notification automatically dismissed when the user touches it.
3426 * @see Notification#FLAG_AUTO_CANCEL
3428 public Builder setAutoCancel(boolean autoCancel) {
3429 setFlag(FLAG_AUTO_CANCEL, autoCancel);
3434 * Set whether or not this notification should not bridge to other devices.
3436 * <p>Some notifications can be bridged to other devices for remote display.
3437 * This hint can be set to recommend this notification not be bridged.
3439 public Builder setLocalOnly(boolean localOnly) {
3440 setFlag(FLAG_LOCAL_ONLY, localOnly);
3445 * Set which notification properties will be inherited from system defaults.
3447 * The value should be one or more of the following fields combined with
3449 * {@link #DEFAULT_SOUND}, {@link #DEFAULT_VIBRATE}, {@link #DEFAULT_LIGHTS}.
3451 * For all default values, use {@link #DEFAULT_ALL}.
3453 * @deprecated use {@link NotificationChannel#enableVibration(boolean)} and
3454 * {@link NotificationChannel#enableLights(boolean)} and
3455 * {@link NotificationChannel#setSound(Uri, AudioAttributes)} instead.
3458 public Builder setDefaults(int defaults) {
3459 mN.defaults = defaults;
3464 * Set the priority of this notification.
3466 * @see Notification#priority
3467 * @deprecated use {@link NotificationChannel#setImportance(int)} instead.
3470 public Builder setPriority(@Priority int pri) {
3476 * Set the notification category.
3478 * @see Notification#category
3480 public Builder setCategory(String category) {
3481 mN.category = category;
3486 * Add a person that is relevant to this notification.
3489 * Depending on user preferences, this annotation may allow the notification to pass
3490 * through interruption filters, if this notification is of category {@link #CATEGORY_CALL}
3491 * or {@link #CATEGORY_MESSAGE}. The addition of people may also cause this notification to
3492 * appear more prominently in the user interface.
3496 * The person should be specified by the {@code String} representation of a
3497 * {@link android.provider.ContactsContract.Contacts#CONTENT_LOOKUP_URI}.
3500 * <P>The system will also attempt to resolve {@code mailto:} and {@code tel:} schema
3501 * URIs. The path part of these URIs must exist in the contacts database, in the
3502 * appropriate column, or the reference will be discarded as invalid. Telephone schema
3503 * URIs will be resolved by {@link android.provider.ContactsContract.PhoneLookup}.
3506 * @param uri A URI for the person.
3507 * @see Notification#EXTRA_PEOPLE
3509 public Builder addPerson(String uri) {
3510 mPersonList.add(uri);
3515 * Set this notification to be part of a group of notifications sharing the same key.
3516 * Grouped notifications may display in a cluster or stack on devices which
3517 * support such rendering.
3519 * <p>To make this notification the summary for its group, also call
3520 * {@link #setGroupSummary}. A sort order can be specified for group members by using
3521 * {@link #setSortKey}.
3522 * @param groupKey The group key of the group.
3523 * @return this object for method chaining
3525 public Builder setGroup(String groupKey) {
3526 mN.mGroupKey = groupKey;
3531 * Set this notification to be the group summary for a group of notifications.
3532 * Grouped notifications may display in a cluster or stack on devices which
3533 * support such rendering. If thereRequires a group key also be set using {@link #setGroup}.
3534 * The group summary may be suppressed if too few notifications are included in the group.
3535 * @param isGroupSummary Whether this notification should be a group summary.
3536 * @return this object for method chaining
3538 public Builder setGroupSummary(boolean isGroupSummary) {
3539 setFlag(FLAG_GROUP_SUMMARY, isGroupSummary);
3544 * Set a sort key that orders this notification among other notifications from the
3545 * same package. This can be useful if an external sort was already applied and an app
3546 * would like to preserve this. Notifications will be sorted lexicographically using this
3547 * value, although providing different priorities in addition to providing sort key may
3548 * cause this value to be ignored.
3550 * <p>This sort key can also be used to order members of a notification group. See
3551 * {@link #setGroup}.
3553 * @see String#compareTo(String)
3555 public Builder setSortKey(String sortKey) {
3556 mN.mSortKey = sortKey;
3561 * Merge additional metadata into this notification.
3563 * <p>Values within the Bundle will replace existing extras values in this Builder.
3565 * @see Notification#extras
3567 public Builder addExtras(Bundle extras) {
3568 if (extras != null) {
3569 mUserExtras.putAll(extras);
3575 * Set metadata for this notification.
3577 * <p>A reference to the Bundle is held for the lifetime of this Builder, and the Bundle's
3578 * current contents are copied into the Notification each time {@link #build()} is
3581 * <p>Replaces any existing extras values with those from the provided Bundle.
3582 * Use {@link #addExtras} to merge in metadata instead.
3584 * @see Notification#extras
3586 public Builder setExtras(Bundle extras) {
3587 if (extras != null) {
3588 mUserExtras = extras;
3594 * Get the current metadata Bundle used by this notification Builder.
3596 * <p>The returned Bundle is shared with this Builder.
3598 * <p>The current contents of this Bundle are copied into the Notification each time
3599 * {@link #build()} is called.
3601 * @see Notification#extras
3603 public Bundle getExtras() {
3607 private Bundle getAllExtras() {
3608 final Bundle saveExtras = (Bundle) mUserExtras.clone();
3609 saveExtras.putAll(mN.extras);
3614 * Add an action to this notification. Actions are typically displayed by
3615 * the system as a button adjacent to the notification content.
3617 * Every action must have an icon (32dp square and matching the
3618 * <a href="{@docRoot}design/style/iconography.html#action-bar">Holo
3619 * Dark action bar</a> visual style), a textual label, and a {@link PendingIntent}.
3621 * A notification in its expanded form can display up to 3 actions, from left to right in
3622 * the order they were added. Actions will not be displayed when the notification is
3623 * collapsed, however, so be sure that any essential functions may be accessed by the user
3624 * in some other way (for example, in the Activity pointed to by {@link #contentIntent}).
3626 * @param icon Resource ID of a drawable that represents the action.
3627 * @param title Text describing the action.
3628 * @param intent PendingIntent to be fired when the action is invoked.
3630 * @deprecated Use {@link #addAction(Action)} instead.
3633 public Builder addAction(int icon, CharSequence title, PendingIntent intent) {
3634 mActions.add(new Action(icon, safeCharSequence(title), intent));
3639 * Add an action to this notification. Actions are typically displayed by
3640 * the system as a button adjacent to the notification content.
3642 * Every action must have an icon (32dp square and matching the
3643 * <a href="{@docRoot}design/style/iconography.html#action-bar">Holo
3644 * Dark action bar</a> visual style), a textual label, and a {@link PendingIntent}.
3646 * A notification in its expanded form can display up to 3 actions, from left to right in
3647 * the order they were added. Actions will not be displayed when the notification is
3648 * collapsed, however, so be sure that any essential functions may be accessed by the user
3649 * in some other way (for example, in the Activity pointed to by {@link #contentIntent}).
3651 * @param action The action to add.
3653 public Builder addAction(Action action) {
3654 if (action != null) {
3655 mActions.add(action);
3661 * Alter the complete list of actions attached to this notification.
3662 * @see #addAction(Action).
3667 public Builder setActions(Action... actions) {
3669 for (int i = 0; i < actions.length; i++) {
3670 if (actions[i] != null) {
3671 mActions.add(actions[i]);
3678 * Add a rich notification style to be applied at build time.
3680 * @param style Object responsible for modifying the notification style.
3682 public Builder setStyle(Style style) {
3683 if (mStyle != style) {
3685 if (mStyle != null) {
3686 mStyle.setBuilder(this);
3687 mN.extras.putString(EXTRA_TEMPLATE, style.getClass().getName());
3689 mN.extras.remove(EXTRA_TEMPLATE);
3696 * Specify the value of {@link #visibility}.
3698 * @return The same Builder.
3700 public Builder setVisibility(@Visibility int visibility) {
3701 mN.visibility = visibility;
3706 * Supply a replacement Notification whose contents should be shown in insecure contexts
3707 * (i.e. atop the secure lockscreen). See {@link #visibility} and {@link #VISIBILITY_PUBLIC}.
3708 * @param n A replacement notification, presumably with some or all info redacted.
3709 * @return The same Builder.
3711 public Builder setPublicVersion(Notification n) {
3713 mN.publicVersion = new Notification();
3714 n.cloneInto(mN.publicVersion, /*heavy=*/ true);
3716 mN.publicVersion = null;
3722 * Apply an extender to this notification builder. Extenders may be used to add
3723 * metadata or change options on this builder.
3725 public Builder extend(Extender extender) {
3726 extender.extend(this);
3733 public Builder setFlag(int mask, boolean value) {
3743 * Sets {@link Notification#color}.
3745 * @param argb The accent color to use
3747 * @return The same Builder.
3749 public Builder setColor(@ColorInt int argb) {
3755 private Drawable getProfileBadgeDrawable() {
3756 if (mContext.getUserId() == UserHandle.USER_SYSTEM) {
3757 // This user can never be a badged profile,
3758 // and also includes USER_ALL system notifications.
3761 // Note: This assumes that the current user can read the profile badge of the
3762 // originating user.
3763 return mContext.getPackageManager().getUserBadgeForDensityNoBackground(
3764 new UserHandle(mContext.getUserId()), 0);
3767 private Bitmap getProfileBadge() {
3768 Drawable badge = getProfileBadgeDrawable();
3769 if (badge == null) {
3772 final int size = mContext.getResources().getDimensionPixelSize(
3773 R.dimen.notification_badge_size);
3774 Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
3775 Canvas canvas = new Canvas(bitmap);
3776 badge.setBounds(0, 0, size, size);
3781 private void bindProfileBadge(RemoteViews contentView) {
3782 Bitmap profileBadge = getProfileBadge();
3784 if (profileBadge != null) {
3785 contentView.setImageViewBitmap(R.id.profile_badge, profileBadge);
3786 contentView.setViewVisibility(R.id.profile_badge, View.VISIBLE);
3787 if (isColorized()) {
3788 contentView.setDrawableParameters(R.id.profile_badge, false, -1,
3789 getPrimaryTextColor(), PorterDuff.Mode.SRC_ATOP, -1);
3794 private void resetStandardTemplate(RemoteViews contentView) {
3795 resetNotificationHeader(contentView);
3796 resetContentMargins(contentView);
3797 contentView.setViewVisibility(R.id.right_icon, View.GONE);
3798 contentView.setViewVisibility(R.id.title, View.GONE);
3799 contentView.setTextViewText(R.id.title, null);
3800 contentView.setViewVisibility(R.id.text, View.GONE);
3801 contentView.setTextViewText(R.id.text, null);
3802 contentView.setViewVisibility(R.id.text_line_1, View.GONE);
3803 contentView.setTextViewText(R.id.text_line_1, null);
3807 * Resets the notification header to its original state
3809 private void resetNotificationHeader(RemoteViews contentView) {
3810 // Small icon doesn't need to be reset, as it's always set. Resetting would prevent
3811 // re-using the drawable when the notification is updated.
3812 contentView.setBoolean(R.id.notification_header, "setExpanded", false);
3813 contentView.setTextViewText(R.id.app_name_text, null);
3814 contentView.setViewVisibility(R.id.chronometer, View.GONE);
3815 contentView.setViewVisibility(R.id.header_text, View.GONE);
3816 contentView.setTextViewText(R.id.header_text, null);
3817 contentView.setViewVisibility(R.id.header_text_divider, View.GONE);
3818 contentView.setViewVisibility(R.id.time_divider, View.GONE);
3819 contentView.setViewVisibility(R.id.time, View.GONE);
3820 contentView.setImageViewIcon(R.id.profile_badge, null);
3821 contentView.setViewVisibility(R.id.profile_badge, View.GONE);
3824 private void resetContentMargins(RemoteViews contentView) {
3825 contentView.setViewLayoutMarginEndDimen(R.id.line1, 0);
3826 contentView.setViewLayoutMarginEndDimen(R.id.text, 0);
3829 private RemoteViews applyStandardTemplate(int resId) {
3830 return applyStandardTemplate(resId, mParams.reset().fillTextsFrom(this));
3834 * @param hasProgress whether the progress bar should be shown and set
3836 private RemoteViews applyStandardTemplate(int resId, boolean hasProgress) {
3837 return applyStandardTemplate(resId, mParams.reset().hasProgress(hasProgress)
3838 .fillTextsFrom(this));
3841 private RemoteViews applyStandardTemplate(int resId, StandardTemplateParams p) {
3842 RemoteViews contentView = new BuilderRemoteViews(mContext.getApplicationInfo(), resId);
3844 resetStandardTemplate(contentView);
3846 final Bundle ex = mN.extras;
3847 updateBackgroundColor(contentView);
3848 bindNotificationHeader(contentView, p.ambient);
3849 bindLargeIcon(contentView);
3850 boolean showProgress = handleProgressBar(p.hasProgress, contentView, ex);
3851 if (p.title != null) {
3852 contentView.setViewVisibility(R.id.title, View.VISIBLE);
3853 contentView.setTextViewText(R.id.title, p.title);
3855 setTextViewColorPrimary(contentView, R.id.title);
3857 contentView.setViewLayoutWidth(R.id.title, showProgress
3858 ? ViewGroup.LayoutParams.WRAP_CONTENT
3859 : ViewGroup.LayoutParams.MATCH_PARENT);
3861 if (p.text != null) {
3862 int textId = showProgress ? com.android.internal.R.id.text_line_1
3863 : com.android.internal.R.id.text;
3864 contentView.setTextViewText(textId, p.text);
3866 setTextViewColorSecondary(contentView, textId);
3868 contentView.setViewVisibility(textId, View.VISIBLE);
3871 setContentMinHeight(contentView, showProgress || mN.hasLargeIcon());
3876 private void setTextViewColorPrimary(RemoteViews contentView, int id) {
3878 contentView.setTextColor(id, mPrimaryTextColor);
3882 * @return the primary text color
3886 public int getPrimaryTextColor() {
3888 return mPrimaryTextColor;
3892 * @return the secondary text color
3896 public int getSecondaryTextColor() {
3898 return mSecondaryTextColor;
3901 private int getActionBarColor() {
3903 return mActionBarColor;
3906 private int getActionBarColorDeEmphasized() {
3907 int backgroundColor = getBackgroundColor();
3908 return NotificationColorUtil.getShiftedColor(backgroundColor, 12);
3911 private void setTextViewColorSecondary(RemoteViews contentView, int id) {
3913 contentView.setTextColor(id, mSecondaryTextColor);
3916 private void ensureColors() {
3917 int backgroundColor = getBackgroundColor();
3918 if (mPrimaryTextColor == COLOR_INVALID
3919 || mSecondaryTextColor == COLOR_INVALID
3920 || mActionBarColor == COLOR_INVALID
3921 || mTextColorsAreForBackground != backgroundColor) {
3922 mTextColorsAreForBackground = backgroundColor;
3923 if (mForegroundColor == COLOR_INVALID || !isColorized()) {
3924 mPrimaryTextColor = NotificationColorUtil.resolvePrimaryColor(mContext,
3926 mSecondaryTextColor = NotificationColorUtil.resolveSecondaryColor(mContext,
3928 if (backgroundColor != COLOR_DEFAULT
3929 && (mBackgroundColorHint != COLOR_INVALID || isColorized())) {
3930 mPrimaryTextColor = NotificationColorUtil.findAlphaToMeetContrast(
3931 mPrimaryTextColor, backgroundColor, 4.5);
3932 mSecondaryTextColor = NotificationColorUtil.findAlphaToMeetContrast(
3933 mSecondaryTextColor, backgroundColor, 4.5);
3936 double backLum = NotificationColorUtil.calculateLuminance(backgroundColor);
3937 double textLum = NotificationColorUtil.calculateLuminance(mForegroundColor);
3938 double contrast = NotificationColorUtil.calculateContrast(mForegroundColor,
3940 // We only respect the given colors if worst case Black or White still has
3942 boolean backgroundLight = backLum > textLum
3943 && satisfiesTextContrast(backgroundColor, Color.BLACK)
3944 || backLum <= textLum
3945 && !satisfiesTextContrast(backgroundColor, Color.WHITE);
3946 if (contrast < 4.5f) {
3947 if (backgroundLight) {
3948 mSecondaryTextColor = NotificationColorUtil.findContrastColor(
3953 mPrimaryTextColor = NotificationColorUtil.changeColorLightness(
3954 mSecondaryTextColor, -LIGHTNESS_TEXT_DIFFERENCE_LIGHT);
3956 mSecondaryTextColor =
3957 NotificationColorUtil.findContrastColorAgainstDark(
3962 mPrimaryTextColor = NotificationColorUtil.changeColorLightness(
3963 mSecondaryTextColor, -LIGHTNESS_TEXT_DIFFERENCE_DARK);
3966 mPrimaryTextColor = mForegroundColor;
3967 mSecondaryTextColor = NotificationColorUtil.changeColorLightness(
3968 mPrimaryTextColor, backgroundLight ? LIGHTNESS_TEXT_DIFFERENCE_LIGHT
3969 : LIGHTNESS_TEXT_DIFFERENCE_DARK);
3970 if (NotificationColorUtil.calculateContrast(mSecondaryTextColor,
3971 backgroundColor) < 4.5f) {
3972 // oh well the secondary is not good enough
3973 if (backgroundLight) {
3974 mSecondaryTextColor = NotificationColorUtil.findContrastColor(
3975 mSecondaryTextColor,
3981 = NotificationColorUtil.findContrastColorAgainstDark(
3982 mSecondaryTextColor,
3987 mPrimaryTextColor = NotificationColorUtil.changeColorLightness(
3988 mSecondaryTextColor, backgroundLight
3989 ? -LIGHTNESS_TEXT_DIFFERENCE_LIGHT
3990 : -LIGHTNESS_TEXT_DIFFERENCE_DARK);
3994 mActionBarColor = NotificationColorUtil.resolveActionBarColor(mContext,
3999 private void updateBackgroundColor(RemoteViews contentView) {
4000 if (isColorized()) {
4001 contentView.setInt(R.id.status_bar_latest_event_content, "setBackgroundColor",
4002 getBackgroundColor());
4005 contentView.setInt(R.id.status_bar_latest_event_content, "setBackgroundResource",
4011 * @param remoteView the remote view to update the minheight in
4012 * @param hasMinHeight does it have a mimHeight
4015 void setContentMinHeight(RemoteViews remoteView, boolean hasMinHeight) {
4018 // we need to set the minHeight of the notification
4019 minHeight = mContext.getResources().getDimensionPixelSize(
4020 com.android.internal.R.dimen.notification_min_content_height);
4022 remoteView.setInt(R.id.notification_main_column, "setMinimumHeight", minHeight);
4025 private boolean handleProgressBar(boolean hasProgress, RemoteViews contentView, Bundle ex) {
4026 final int max = ex.getInt(EXTRA_PROGRESS_MAX, 0);
4027 final int progress = ex.getInt(EXTRA_PROGRESS, 0);
4028 final boolean ind = ex.getBoolean(EXTRA_PROGRESS_INDETERMINATE);
4029 if (hasProgress && (max != 0 || ind)) {
4030 contentView.setViewVisibility(com.android.internal.R.id.progress, View.VISIBLE);
4031 contentView.setProgressBar(
4032 R.id.progress, max, progress, ind);
4033 contentView.setProgressBackgroundTintList(
4034 R.id.progress, ColorStateList.valueOf(mContext.getColor(
4035 R.color.notification_progress_background_color)));
4036 if (mN.color != COLOR_DEFAULT) {
4037 ColorStateList colorStateList = ColorStateList.valueOf(resolveContrastColor());
4038 contentView.setProgressTintList(R.id.progress, colorStateList);
4039 contentView.setProgressIndeterminateTintList(R.id.progress, colorStateList);
4043 contentView.setViewVisibility(R.id.progress, View.GONE);
4048 private void bindLargeIcon(RemoteViews contentView) {
4049 if (mN.mLargeIcon == null && mN.largeIcon != null) {
4050 mN.mLargeIcon = Icon.createWithBitmap(mN.largeIcon);
4052 if (mN.mLargeIcon != null) {
4053 contentView.setViewVisibility(R.id.right_icon, View.VISIBLE);
4054 contentView.setImageViewIcon(R.id.right_icon, mN.mLargeIcon);
4055 processLargeLegacyIcon(mN.mLargeIcon, contentView);
4056 int endMargin = R.dimen.notification_content_picture_margin;
4057 contentView.setViewLayoutMarginEndDimen(R.id.line1, endMargin);
4058 contentView.setViewLayoutMarginEndDimen(R.id.text, endMargin);
4059 contentView.setViewLayoutMarginEndDimen(R.id.progress, endMargin);
4063 private void bindNotificationHeader(RemoteViews contentView, boolean ambient) {
4064 bindSmallIcon(contentView, ambient);
4065 bindHeaderAppName(contentView, ambient);
4067 // Ambient view does not have these
4068 bindHeaderText(contentView);
4069 bindHeaderChronometerAndTime(contentView);
4070 bindProfileBadge(contentView);
4072 bindExpandButton(contentView);
4075 private void bindExpandButton(RemoteViews contentView) {
4076 int color = getPrimaryHighlightColor();
4077 contentView.setDrawableParameters(R.id.expand_button, false, -1, color,
4078 PorterDuff.Mode.SRC_ATOP, -1);
4079 contentView.setInt(R.id.notification_header, "setOriginalNotificationColor",
4084 * @return the color that is used as the first primary highlight color. This is applied
4085 * in several places like the action buttons or the app name in the header.
4087 private int getPrimaryHighlightColor() {
4088 return isColorized() ? getPrimaryTextColor() : resolveContrastColor();
4091 private void bindHeaderChronometerAndTime(RemoteViews contentView) {
4092 if (showsTimeOrChronometer()) {
4093 contentView.setViewVisibility(R.id.time_divider, View.VISIBLE);
4094 setTextViewColorSecondary(contentView, R.id.time_divider);
4095 if (mN.extras.getBoolean(EXTRA_SHOW_CHRONOMETER)) {
4096 contentView.setViewVisibility(R.id.chronometer, View.VISIBLE);
4097 contentView.setLong(R.id.chronometer, "setBase",
4098 mN.when + (SystemClock.elapsedRealtime() - System.currentTimeMillis()));
4099 contentView.setBoolean(R.id.chronometer, "setStarted", true);
4100 boolean countsDown = mN.extras.getBoolean(EXTRA_CHRONOMETER_COUNT_DOWN);
4101 contentView.setChronometerCountDown(R.id.chronometer, countsDown);
4102 setTextViewColorSecondary(contentView, R.id.chronometer);
4104 contentView.setViewVisibility(R.id.time, View.VISIBLE);
4105 contentView.setLong(R.id.time, "setTime", mN.when);
4106 setTextViewColorSecondary(contentView, R.id.time);
4109 // We still want a time to be set but gone, such that we can show and hide it
4110 // on demand in case it's a child notification without anything in the header
4111 contentView.setLong(R.id.time, "setTime", mN.when != 0 ? mN.when : mN.creationTime);
4115 private void bindHeaderText(RemoteViews contentView) {
4116 CharSequence headerText = mN.extras.getCharSequence(EXTRA_SUB_TEXT);
4117 if (headerText == null && mStyle != null && mStyle.mSummaryTextSet
4118 && mStyle.hasSummaryInHeader()) {
4119 headerText = mStyle.mSummaryText;
4121 if (headerText == null
4122 && mContext.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N
4123 && mN.extras.getCharSequence(EXTRA_INFO_TEXT) != null) {
4124 headerText = mN.extras.getCharSequence(EXTRA_INFO_TEXT);
4126 if (headerText != null) {
4127 // TODO: Remove the span entirely to only have the string with propper formating.
4128 contentView.setTextViewText(R.id.header_text, processLegacyText(headerText));
4129 setTextViewColorSecondary(contentView, R.id.header_text);
4130 contentView.setViewVisibility(R.id.header_text, View.VISIBLE);
4131 contentView.setViewVisibility(R.id.header_text_divider, View.VISIBLE);
4132 setTextViewColorSecondary(contentView, R.id.header_text_divider);
4139 public String loadHeaderAppName() {
4140 CharSequence name = null;
4141 final PackageManager pm = mContext.getPackageManager();
4142 if (mN.extras.containsKey(EXTRA_SUBSTITUTE_APP_NAME)) {
4143 // only system packages which lump together a bunch of unrelated stuff
4144 // may substitute a different name to make the purpose of the
4145 // notification more clear. the correct package label should always
4146 // be accessible via SystemUI.
4147 final String pkg = mContext.getPackageName();
4148 final String subName = mN.extras.getString(EXTRA_SUBSTITUTE_APP_NAME);
4149 if (PackageManager.PERMISSION_GRANTED == pm.checkPermission(
4150 android.Manifest.permission.SUBSTITUTE_NOTIFICATION_APP_NAME, pkg)) {
4153 Log.w(TAG, "warning: pkg "
4154 + pkg + " attempting to substitute app name '" + subName
4155 + "' without holding perm "
4156 + android.Manifest.permission.SUBSTITUTE_NOTIFICATION_APP_NAME);
4159 if (TextUtils.isEmpty(name)) {
4160 name = pm.getApplicationLabel(mContext.getApplicationInfo());
4162 if (TextUtils.isEmpty(name)) {
4167 return String.valueOf(name);
4169 private void bindHeaderAppName(RemoteViews contentView, boolean ambient) {
4170 contentView.setTextViewText(R.id.app_name_text, loadHeaderAppName());
4171 if (isColorized() && !ambient) {
4172 setTextViewColorPrimary(contentView, R.id.app_name_text);
4174 contentView.setTextColor(R.id.app_name_text,
4175 ambient ? resolveAmbientColor() : resolveContrastColor());
4179 private void bindSmallIcon(RemoteViews contentView, boolean ambient) {
4180 if (mN.mSmallIcon == null && mN.icon != 0) {
4181 mN.mSmallIcon = Icon.createWithResource(mContext, mN.icon);
4183 contentView.setImageViewIcon(R.id.icon, mN.mSmallIcon);
4184 contentView.setDrawableParameters(R.id.icon, false /* targetBackground */,
4185 -1 /* alpha */, -1 /* colorFilter */, null /* mode */, mN.iconLevel);
4186 processSmallIconColor(mN.mSmallIcon, contentView, ambient);
4190 * @return true if the built notification will show the time or the chronometer; false
4193 private boolean showsTimeOrChronometer() {
4194 return mN.showsTime() || mN.showsChronometer();
4197 private void resetStandardTemplateWithActions(RemoteViews big) {
4198 // actions_container is only reset when there are no actions to avoid focus issues with
4200 big.setViewVisibility(R.id.actions, View.GONE);
4201 big.removeAllViews(R.id.actions);
4203 big.setViewVisibility(R.id.notification_material_reply_container, View.GONE);
4204 big.setTextViewText(R.id.notification_material_reply_text_1, null);
4206 big.setViewVisibility(R.id.notification_material_reply_text_2, View.GONE);
4207 big.setTextViewText(R.id.notification_material_reply_text_2, null);
4208 big.setViewVisibility(R.id.notification_material_reply_text_3, View.GONE);
4209 big.setTextViewText(R.id.notification_material_reply_text_3, null);
4211 big.setViewLayoutMarginBottomDimen(R.id.notification_action_list_margin_target, 0);
4214 private RemoteViews applyStandardTemplateWithActions(int layoutId) {
4215 return applyStandardTemplateWithActions(layoutId, mParams.reset().fillTextsFrom(this));
4218 private RemoteViews applyStandardTemplateWithActions(int layoutId,
4219 StandardTemplateParams p) {
4220 RemoteViews big = applyStandardTemplate(layoutId, p);
4222 resetStandardTemplateWithActions(big);
4224 boolean validRemoteInput = false;
4226 int N = mActions.size();
4227 boolean emphazisedMode = mN.fullScreenIntent != null && !p.ambient;
4228 big.setBoolean(R.id.actions, "setEmphasizedMode", emphazisedMode);
4230 big.setViewVisibility(R.id.actions_container, View.VISIBLE);
4231 big.setViewVisibility(R.id.actions, View.VISIBLE);
4233 big.setInt(R.id.actions, "setBackgroundColor", Color.TRANSPARENT);
4234 } else if (isColorized()) {
4235 big.setInt(R.id.actions, "setBackgroundColor", getActionBarColor());
4237 big.setInt(R.id.actions, "setBackgroundColor", mContext.getColor(
4238 R.color.notification_action_list));
4240 big.setViewLayoutMarginBottomDimen(R.id.notification_action_list_margin_target,
4241 R.dimen.notification_action_list_height);
4242 if (N>MAX_ACTION_BUTTONS) N=MAX_ACTION_BUTTONS;
4243 for (int i=0; i<N; i++) {
4244 Action action = mActions.get(i);
4245 validRemoteInput |= hasValidRemoteInput(action);
4247 final RemoteViews button = generateActionButton(action, emphazisedMode,
4248 i % 2 != 0, p.ambient);
4249 big.addView(R.id.actions, button);
4252 big.setViewVisibility(R.id.actions_container, View.GONE);
4255 CharSequence[] replyText = mN.extras.getCharSequenceArray(EXTRA_REMOTE_INPUT_HISTORY);
4256 if (!p.ambient && validRemoteInput && replyText != null
4257 && replyText.length > 0 && !TextUtils.isEmpty(replyText[0])) {
4258 big.setViewVisibility(R.id.notification_material_reply_container, View.VISIBLE);
4259 big.setTextViewText(R.id.notification_material_reply_text_1, replyText[0]);
4260 setTextViewColorSecondary(big, R.id.notification_material_reply_text_1);
4262 if (replyText.length > 1 && !TextUtils.isEmpty(replyText[1])) {
4263 big.setViewVisibility(R.id.notification_material_reply_text_2, View.VISIBLE);
4264 big.setTextViewText(R.id.notification_material_reply_text_2, replyText[1]);
4265 setTextViewColorSecondary(big, R.id.notification_material_reply_text_2);
4267 if (replyText.length > 2 && !TextUtils.isEmpty(replyText[2])) {
4268 big.setViewVisibility(
4269 R.id.notification_material_reply_text_3, View.VISIBLE);
4270 big.setTextViewText(R.id.notification_material_reply_text_3, replyText[2]);
4271 setTextViewColorSecondary(big, R.id.notification_material_reply_text_3);
4279 private boolean hasValidRemoteInput(Action action) {
4280 if (TextUtils.isEmpty(action.title) || action.actionIntent == null) {
4285 RemoteInput[] remoteInputs = action.getRemoteInputs();
4286 if (remoteInputs == null) {
4290 for (RemoteInput r : remoteInputs) {
4291 CharSequence[] choices = r.getChoices();
4292 if (r.getAllowFreeFormInput() || (choices != null && choices.length != 0)) {
4300 * Construct a RemoteViews for the final 1U notification layout. In order:
4301 * 1. Custom contentView from the caller
4302 * 2. Style's proposed content view
4303 * 3. Standard template view
4305 public RemoteViews createContentView() {
4306 return createContentView(false /* increasedheight */ );
4310 * Construct a RemoteViews for the smaller content view.
4312 * @param increasedHeight true if this layout be created with an increased height. Some
4313 * styles may support showing more then just that basic 1U size
4314 * and the system may decide to render important notifications
4315 * slightly bigger even when collapsed.
4319 public RemoteViews createContentView(boolean increasedHeight) {
4320 if (mN.contentView != null && useExistingRemoteView()) {
4321 return mN.contentView;
4322 } else if (mStyle != null) {
4323 final RemoteViews styleView = mStyle.makeContentView(increasedHeight);
4324 if (styleView != null) {
4328 return applyStandardTemplate(getBaseLayoutResource());
4331 private boolean useExistingRemoteView() {
4332 return mStyle == null || (!mStyle.displayCustomViewInline()
4333 && !mRebuildStyledRemoteViews);
4337 * Construct a RemoteViews for the final big notification layout.
4339 public RemoteViews createBigContentView() {
4340 RemoteViews result = null;
4341 if (mN.bigContentView != null && useExistingRemoteView()) {
4342 return mN.bigContentView;
4343 } else if (mStyle != null) {
4344 result = mStyle.makeBigContentView();
4345 hideLine1Text(result);
4346 } else if (mActions.size() != 0) {
4347 result = applyStandardTemplateWithActions(getBigBaseLayoutResource());
4349 makeHeaderExpanded(result);
4354 * Construct a RemoteViews for the final notification header only. This will not be
4357 * @param ambient if true, generate the header for the ambient display layout.
4360 public RemoteViews makeNotificationHeader(boolean ambient) {
4361 Boolean colorized = (Boolean) mN.extras.get(EXTRA_COLORIZED);
4362 mN.extras.putBoolean(EXTRA_COLORIZED, false);
4363 RemoteViews header = new BuilderRemoteViews(mContext.getApplicationInfo(),
4364 ambient ? R.layout.notification_template_ambient_header
4365 : R.layout.notification_template_header);
4366 resetNotificationHeader(header);
4367 bindNotificationHeader(header, ambient);
4368 if (colorized != null) {
4369 mN.extras.putBoolean(EXTRA_COLORIZED, colorized);
4371 mN.extras.remove(EXTRA_COLORIZED);
4377 * Construct a RemoteViews for the ambient version of the notification.
4381 public RemoteViews makeAmbientNotification() {
4382 RemoteViews ambient = applyStandardTemplateWithActions(
4383 R.layout.notification_template_material_ambient,
4384 mParams.reset().ambient(true).fillTextsFrom(this).hasProgress(false));
4388 private void hideLine1Text(RemoteViews result) {
4389 if (result != null) {
4390 result.setViewVisibility(R.id.text_line_1, View.GONE);
4395 * Adapt the Notification header if this view is used as an expanded view.
4399 public static void makeHeaderExpanded(RemoteViews result) {
4400 if (result != null) {
4401 result.setBoolean(R.id.notification_header, "setExpanded", true);
4406 * Construct a RemoteViews for the final heads-up notification layout.
4408 * @param increasedHeight true if this layout be created with an increased height. Some
4409 * styles may support showing more then just that basic 1U size
4410 * and the system may decide to render important notifications
4411 * slightly bigger even when collapsed.
4415 public RemoteViews createHeadsUpContentView(boolean increasedHeight) {
4416 if (mN.headsUpContentView != null && useExistingRemoteView()) {
4417 return mN.headsUpContentView;
4418 } else if (mStyle != null) {
4419 final RemoteViews styleView = mStyle.makeHeadsUpContentView(increasedHeight);
4420 if (styleView != null) {
4423 } else if (mActions.size() == 0) {
4427 return applyStandardTemplateWithActions(getBigBaseLayoutResource());
4431 * Construct a RemoteViews for the final heads-up notification layout.
4433 public RemoteViews createHeadsUpContentView() {
4434 return createHeadsUpContentView(false /* useIncreasedHeight */);
4438 * Construct a RemoteViews for the display in public contexts like on the lockscreen.
4442 public RemoteViews makePublicContentView() {
4443 return makePublicView(false /* ambient */);
4447 * Construct a RemoteViews for the display in public contexts like on the lockscreen.
4451 public RemoteViews makePublicAmbientNotification() {
4452 return makePublicView(true /* ambient */);
4455 private RemoteViews makePublicView(boolean ambient) {
4456 if (mN.publicVersion != null) {
4457 final Builder builder = recoverBuilder(mContext, mN.publicVersion);
4458 return ambient ? builder.makeAmbientNotification() : builder.createContentView();
4460 Bundle savedBundle = mN.extras;
4461 Style style = mStyle;
4463 Icon largeIcon = mN.mLargeIcon;
4464 mN.mLargeIcon = null;
4465 Bitmap largeIconLegacy = mN.largeIcon;
4466 mN.largeIcon = null;
4467 ArrayList<Action> actions = mActions;
4468 mActions = new ArrayList<>();
4469 Bundle publicExtras = new Bundle();
4470 publicExtras.putBoolean(EXTRA_SHOW_WHEN,
4471 savedBundle.getBoolean(EXTRA_SHOW_WHEN));
4472 publicExtras.putBoolean(EXTRA_SHOW_CHRONOMETER,
4473 savedBundle.getBoolean(EXTRA_SHOW_CHRONOMETER));
4474 publicExtras.putBoolean(EXTRA_CHRONOMETER_COUNT_DOWN,
4475 savedBundle.getBoolean(EXTRA_CHRONOMETER_COUNT_DOWN));
4476 publicExtras.putCharSequence(EXTRA_TITLE,
4477 mContext.getString(com.android.internal.R.string.notification_hidden_text));
4478 mN.extras = publicExtras;
4479 final RemoteViews view = ambient ? makeAmbientNotification()
4480 : applyStandardTemplate(getBaseLayoutResource());
4481 mN.extras = savedBundle;
4482 mN.mLargeIcon = largeIcon;
4483 mN.largeIcon = largeIconLegacy;
4490 * Construct a content view for the display when low - priority
4492 * @param useRegularSubtext uses the normal subtext set if there is one available. Otherwise
4493 * a new subtext is created consisting of the content of the
4497 public RemoteViews makeLowPriorityContentView(boolean useRegularSubtext) {
4498 int color = mN.color;
4499 mN.color = COLOR_DEFAULT;
4500 CharSequence summary = mN.extras.getCharSequence(EXTRA_SUB_TEXT);
4501 if (!useRegularSubtext || TextUtils.isEmpty(summary)) {
4502 CharSequence newSummary = createSummaryText();
4503 if (!TextUtils.isEmpty(newSummary)) {
4504 mN.extras.putCharSequence(EXTRA_SUB_TEXT, newSummary);
4508 RemoteViews header = makeNotificationHeader(false /* ambient */);
4509 header.setBoolean(R.id.notification_header, "setAcceptAllTouches", true);
4510 if (summary != null) {
4511 mN.extras.putCharSequence(EXTRA_SUB_TEXT, summary);
4513 mN.extras.remove(EXTRA_SUB_TEXT);
4519 private CharSequence createSummaryText() {
4520 CharSequence titleText = mN.extras.getCharSequence(Notification.EXTRA_TITLE);
4521 if (USE_ONLY_TITLE_IN_LOW_PRIORITY_SUMMARY) {
4524 SpannableStringBuilder summary = new SpannableStringBuilder();
4525 if (titleText == null) {
4526 titleText = mN.extras.getCharSequence(Notification.EXTRA_TITLE_BIG);
4528 BidiFormatter bidi = BidiFormatter.getInstance();
4529 if (titleText != null) {
4530 summary.append(bidi.unicodeWrap(titleText));
4532 CharSequence contentText = mN.extras.getCharSequence(Notification.EXTRA_TEXT);
4533 if (titleText != null && contentText != null) {
4534 summary.append(bidi.unicodeWrap(mContext.getText(
4535 R.string.notification_header_divider_symbol_with_spaces)));
4537 if (contentText != null) {
4538 summary.append(bidi.unicodeWrap(contentText));
4543 private RemoteViews generateActionButton(Action action, boolean emphazisedMode,
4544 boolean oddAction, boolean ambient) {
4545 final boolean tombstone = (action.actionIntent == null);
4546 RemoteViews button = new BuilderRemoteViews(mContext.getApplicationInfo(),
4547 emphazisedMode ? getEmphasizedActionLayoutResource()
4548 : tombstone ? getActionTombstoneLayoutResource()
4549 : getActionLayoutResource());
4551 button.setOnClickPendingIntent(R.id.action0, action.actionIntent);
4553 button.setContentDescription(R.id.action0, action.title);
4554 if (action.mRemoteInputs != null) {
4555 button.setRemoteInputs(R.id.action0, action.mRemoteInputs);
4557 // TODO: handle emphasized mode / actions right
4558 if (emphazisedMode) {
4559 // change the background bgColor
4561 if (isColorized()) {
4562 bgColor = oddAction ? getActionBarColor() : getActionBarColorDeEmphasized();
4564 bgColor = mContext.getColor(oddAction ? R.color.notification_action_list
4565 : R.color.notification_action_list_dark);
4567 button.setDrawableParameters(R.id.button_holder, true, -1, bgColor,
4568 PorterDuff.Mode.SRC_ATOP, -1);
4569 CharSequence title = action.title;
4570 ColorStateList[] outResultColor = null;
4572 title = clearColorSpans(title);
4574 outResultColor = new ColorStateList[1];
4575 title = ensureColorSpanContrast(title, bgColor, outResultColor);
4577 button.setTextViewText(R.id.action0, title);
4578 setTextViewColorPrimary(button, R.id.action0);
4579 if (outResultColor != null && outResultColor[0] != null) {
4580 // We need to set the text color as well since changing a text to uppercase
4581 // clears its spans.
4582 button.setTextColor(R.id.action0, outResultColor[0]);
4583 } else if (mN.color != COLOR_DEFAULT && !isColorized()) {
4584 button.setTextColor(R.id.action0,resolveContrastColor());
4587 button.setTextViewText(R.id.action0, processLegacyText(action.title));
4588 if (isColorized() && !ambient) {
4589 setTextViewColorPrimary(button, R.id.action0);
4590 } else if (mN.color != COLOR_DEFAULT) {
4591 button.setTextColor(R.id.action0,
4592 ambient ? resolveAmbientColor() : resolveContrastColor());
4599 * Clears all color spans of a text
4600 * @param charSequence the input text
4601 * @return the same text but without color spans
4603 private CharSequence clearColorSpans(CharSequence charSequence) {
4604 if (charSequence instanceof Spanned) {
4605 Spanned ss = (Spanned) charSequence;
4606 Object[] spans = ss.getSpans(0, ss.length(), Object.class);
4607 SpannableStringBuilder builder = new SpannableStringBuilder(ss.toString());
4608 for (Object span : spans) {
4609 Object resultSpan = span;
4610 if (resultSpan instanceof CharacterStyle) {
4611 resultSpan = ((CharacterStyle) span).getUnderlying();
4613 if (resultSpan instanceof TextAppearanceSpan) {
4614 TextAppearanceSpan originalSpan = (TextAppearanceSpan) resultSpan;
4615 if (originalSpan.getTextColor() != null) {
4616 resultSpan = new TextAppearanceSpan(
4617 originalSpan.getFamily(),
4618 originalSpan.getTextStyle(),
4619 originalSpan.getTextSize(),
4621 originalSpan.getLinkTextColor());
4623 } else if (resultSpan instanceof ForegroundColorSpan
4624 || (resultSpan instanceof BackgroundColorSpan)) {
4629 builder.setSpan(resultSpan, ss.getSpanStart(span), ss.getSpanEnd(span),
4630 ss.getSpanFlags(span));
4634 return charSequence;
4638 * Ensures contrast on color spans against a background color. also returns the color of the
4639 * text if a span was found that spans over the whole text.
4641 * @param charSequence the charSequence on which the spans are
4642 * @param background the background color to ensure the contrast against
4643 * @param outResultColor an array in which a color will be returned as the first element if
4644 * there exists a full length color span.
4645 * @return the contrasted charSequence
4647 private CharSequence ensureColorSpanContrast(CharSequence charSequence, int background,
4648 ColorStateList[] outResultColor) {
4649 if (charSequence instanceof Spanned) {
4650 Spanned ss = (Spanned) charSequence;
4651 Object[] spans = ss.getSpans(0, ss.length(), Object.class);
4652 SpannableStringBuilder builder = new SpannableStringBuilder(ss.toString());
4653 for (Object span : spans) {
4654 Object resultSpan = span;
4655 int spanStart = ss.getSpanStart(span);
4656 int spanEnd = ss.getSpanEnd(span);
4657 boolean fullLength = (spanEnd - spanStart) == charSequence.length();
4658 if (resultSpan instanceof CharacterStyle) {
4659 resultSpan = ((CharacterStyle) span).getUnderlying();
4661 if (resultSpan instanceof TextAppearanceSpan) {
4662 TextAppearanceSpan originalSpan = (TextAppearanceSpan) resultSpan;
4663 ColorStateList textColor = originalSpan.getTextColor();
4664 if (textColor != null) {
4665 int[] colors = textColor.getColors();
4666 int[] newColors = new int[colors.length];
4667 for (int i = 0; i < newColors.length; i++) {
4668 newColors[i] = NotificationColorUtil.ensureLargeTextContrast(
4669 colors[i], background);
4671 textColor = new ColorStateList(textColor.getStates().clone(),
4673 resultSpan = new TextAppearanceSpan(
4674 originalSpan.getFamily(),
4675 originalSpan.getTextStyle(),
4676 originalSpan.getTextSize(),
4678 originalSpan.getLinkTextColor());
4680 outResultColor[0] = new ColorStateList(
4681 textColor.getStates().clone(), newColors);
4684 } else if (resultSpan instanceof ForegroundColorSpan) {
4685 ForegroundColorSpan originalSpan = (ForegroundColorSpan) resultSpan;
4686 int foregroundColor = originalSpan.getForegroundColor();
4687 foregroundColor = NotificationColorUtil.ensureLargeTextContrast(
4688 foregroundColor, background);
4689 resultSpan = new ForegroundColorSpan(foregroundColor);
4691 outResultColor[0] = ColorStateList.valueOf(foregroundColor);
4697 builder.setSpan(resultSpan, spanStart, spanEnd, ss.getSpanFlags(span));
4701 return charSequence;
4705 * @return Whether we are currently building a notification from a legacy (an app that
4706 * doesn't create material notifications by itself) app.
4708 private boolean isLegacy() {
4709 if (!mIsLegacyInitialized) {
4710 mIsLegacy = mContext.getApplicationInfo().targetSdkVersion
4711 < Build.VERSION_CODES.LOLLIPOP;
4712 mIsLegacyInitialized = true;
4717 private CharSequence processLegacyText(CharSequence charSequence) {
4718 return processLegacyText(charSequence, false /* ambient */);
4721 private CharSequence processLegacyText(CharSequence charSequence, boolean ambient) {
4722 boolean isAlreadyLightText = isLegacy() || textColorsNeedInversion();
4723 boolean wantLightText = ambient;
4724 if (isAlreadyLightText != wantLightText) {
4725 return getColorUtil().invertCharSequenceColors(charSequence);
4727 return charSequence;
4732 * Apply any necessariy colors to the small icon
4734 private void processSmallIconColor(Icon smallIcon, RemoteViews contentView,
4736 boolean colorable = !isLegacy() || getColorUtil().isGrayscaleIcon(mContext, smallIcon);
4737 int color = ambient ? resolveAmbientColor() : getPrimaryHighlightColor();
4739 contentView.setDrawableParameters(R.id.icon, false, -1, color,
4740 PorterDuff.Mode.SRC_ATOP, -1);
4743 contentView.setInt(R.id.notification_header, "setOriginalIconColor",
4744 colorable ? color : NotificationHeaderView.NO_COLOR);
4748 * Make the largeIcon dark if it's a fake smallIcon (that is,
4749 * if it's grayscale).
4751 // TODO: also check bounds, transparency, that sort of thing.
4752 private void processLargeLegacyIcon(Icon largeIcon, RemoteViews contentView) {
4753 if (largeIcon != null && isLegacy()
4754 && getColorUtil().isGrayscaleIcon(mContext, largeIcon)) {
4755 // resolve color will fall back to the default when legacy
4756 contentView.setDrawableParameters(R.id.icon, false, -1, resolveContrastColor(),
4757 PorterDuff.Mode.SRC_ATOP, -1);
4761 private void sanitizeColor() {
4762 if (mN.color != COLOR_DEFAULT) {
4763 mN.color |= 0xFF000000; // no alpha for custom colors
4767 int resolveContrastColor() {
4768 if (mCachedContrastColorIsFor == mN.color && mCachedContrastColor != COLOR_INVALID) {
4769 return mCachedContrastColor;
4773 int background = mBackgroundColorHint;
4774 if (mBackgroundColorHint == COLOR_INVALID) {
4775 background = mContext.getColor(
4776 com.android.internal.R.color.notification_material_background_color);
4778 if (mN.color == COLOR_DEFAULT) {
4780 color = mSecondaryTextColor;
4782 color = NotificationColorUtil.resolveContrastColor(mContext, mN.color,
4785 if (Color.alpha(color) < 255) {
4786 // alpha doesn't go well for color filters, so let's blend it manually
4787 color = NotificationColorUtil.compositeColors(color, background);
4789 mCachedContrastColorIsFor = mN.color;
4790 return mCachedContrastColor = color;
4793 int resolveAmbientColor() {
4794 if (mCachedAmbientColorIsFor == mN.color && mCachedAmbientColorIsFor != COLOR_INVALID) {
4795 return mCachedAmbientColor;
4797 final int contrasted = NotificationColorUtil.resolveAmbientColor(mContext, mN.color);
4799 mCachedAmbientColorIsFor = mN.color;
4800 return mCachedAmbientColor = contrasted;
4804 * Apply the unstyled operations and return a new {@link Notification} object.
4807 public Notification buildUnstyled() {
4808 if (mActions.size() > 0) {
4809 mN.actions = new Action[mActions.size()];
4810 mActions.toArray(mN.actions);
4812 if (!mPersonList.isEmpty()) {
4813 mN.extras.putStringArray(EXTRA_PEOPLE,
4814 mPersonList.toArray(new String[mPersonList.size()]));
4816 if (mN.bigContentView != null || mN.contentView != null
4817 || mN.headsUpContentView != null) {
4818 mN.extras.putBoolean(EXTRA_CONTAINS_CUSTOM_VIEW, true);
4824 * Creates a Builder from an existing notification so further changes can be made.
4825 * @param context The context for your application / activity.
4826 * @param n The notification to create a Builder from.
4828 public static Notification.Builder recoverBuilder(Context context, Notification n) {
4829 // Re-create notification context so we can access app resources.
4830 ApplicationInfo applicationInfo = n.extras.getParcelable(
4831 EXTRA_BUILDER_APPLICATION_INFO);
4832 Context builderContext;
4833 if (applicationInfo != null) {
4835 builderContext = context.createApplicationContext(applicationInfo,
4836 Context.CONTEXT_RESTRICTED);
4837 } catch (NameNotFoundException e) {
4838 Log.e(TAG, "ApplicationInfo " + applicationInfo + " not found");
4839 builderContext = context; // try with our context
4842 builderContext = context; // try with given context
4845 return new Builder(builderContext, n);
4849 * @deprecated Use {@link #build()} instead.
4852 public Notification getNotification() {
4857 * Combine all of the options that have been set and return a new {@link Notification}
4860 public Notification build() {
4861 // first, add any extras from the calling code
4862 if (mUserExtras != null) {
4863 mN.extras = getAllExtras();
4866 mN.creationTime = System.currentTimeMillis();
4868 // lazy stuff from mContext; see comment in Builder(Context, Notification)
4869 Notification.addFieldsFromContext(mContext, mN);
4873 if (mStyle != null) {
4874 mStyle.buildStyled(mN);
4877 if (mContext.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N
4878 && (useExistingRemoteView())) {
4879 if (mN.contentView == null) {
4880 mN.contentView = createContentView();
4881 mN.extras.putInt(EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT,
4882 mN.contentView.getSequenceNumber());
4884 if (mN.bigContentView == null) {
4885 mN.bigContentView = createBigContentView();
4886 if (mN.bigContentView != null) {
4887 mN.extras.putInt(EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT,
4888 mN.bigContentView.getSequenceNumber());
4891 if (mN.headsUpContentView == null) {
4892 mN.headsUpContentView = createHeadsUpContentView();
4893 if (mN.headsUpContentView != null) {
4894 mN.extras.putInt(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT,
4895 mN.headsUpContentView.getSequenceNumber());
4900 if ((mN.defaults & DEFAULT_LIGHTS) != 0) {
4901 mN.flags |= FLAG_SHOW_LIGHTS;
4908 * Apply this Builder to an existing {@link Notification} object.
4912 public Notification buildInto(Notification n) {
4913 build().cloneInto(n, true);
4918 * Removes RemoteViews that were created for compatibility from {@param n}, if they did not
4921 * @return {@param n}, if no stripping is needed, otherwise a stripped clone of {@param n}.
4925 public static Notification maybeCloneStrippedForDelivery(Notification n) {
4926 String templateClass = n.extras.getString(EXTRA_TEMPLATE);
4928 // Only strip views for known Styles because we won't know how to
4929 // re-create them otherwise.
4930 if (!TextUtils.isEmpty(templateClass)
4931 && getNotificationStyleClass(templateClass) == null) {
4935 // Only strip unmodified BuilderRemoteViews.
4936 boolean stripContentView = n.contentView instanceof BuilderRemoteViews &&
4937 n.extras.getInt(EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT, -1) ==
4938 n.contentView.getSequenceNumber();
4939 boolean stripBigContentView = n.bigContentView instanceof BuilderRemoteViews &&
4940 n.extras.getInt(EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT, -1) ==
4941 n.bigContentView.getSequenceNumber();
4942 boolean stripHeadsUpContentView = n.headsUpContentView instanceof BuilderRemoteViews &&
4943 n.extras.getInt(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT, -1) ==
4944 n.headsUpContentView.getSequenceNumber();
4946 // Nothing to do here, no need to clone.
4947 if (!stripContentView && !stripBigContentView && !stripHeadsUpContentView) {
4951 Notification clone = n.clone();
4952 if (stripContentView) {
4953 clone.contentView = null;
4954 clone.extras.remove(EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT);
4956 if (stripBigContentView) {
4957 clone.bigContentView = null;
4958 clone.extras.remove(EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT);
4960 if (stripHeadsUpContentView) {
4961 clone.headsUpContentView = null;
4962 clone.extras.remove(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT);
4967 private int getBaseLayoutResource() {
4968 return R.layout.notification_template_material_base;
4971 private int getBigBaseLayoutResource() {
4972 return R.layout.notification_template_material_big_base;
4975 private int getBigPictureLayoutResource() {
4976 return R.layout.notification_template_material_big_picture;
4979 private int getBigTextLayoutResource() {
4980 return R.layout.notification_template_material_big_text;
4983 private int getInboxLayoutResource() {
4984 return R.layout.notification_template_material_inbox;
4987 private int getMessagingLayoutResource() {
4988 return R.layout.notification_template_material_messaging;
4991 private int getActionLayoutResource() {
4992 return R.layout.notification_material_action;
4995 private int getEmphasizedActionLayoutResource() {
4996 return R.layout.notification_material_action_emphasized;
4999 private int getActionTombstoneLayoutResource() {
5000 return R.layout.notification_material_action_tombstone;
5003 private int getBackgroundColor() {
5004 if (isColorized()) {
5005 return mBackgroundColor != COLOR_INVALID ? mBackgroundColor : mN.color;
5007 return mBackgroundColorHint != COLOR_INVALID ? mBackgroundColorHint
5012 private boolean isColorized() {
5013 return mN.isColorized();
5016 private boolean textColorsNeedInversion() {
5017 if (mStyle == null || !MediaStyle.class.equals(mStyle.getClass())) {
5020 int targetSdkVersion = mContext.getApplicationInfo().targetSdkVersion;
5021 return targetSdkVersion > Build.VERSION_CODES.M
5022 && targetSdkVersion < Build.VERSION_CODES.O;
5026 * Set a color palette to be used as the background and textColors
5028 * @param backgroundColor the color to be used as the background
5029 * @param foregroundColor the color to be used as the foreground
5033 public void setColorPalette(int backgroundColor, int foregroundColor) {
5034 mBackgroundColor = backgroundColor;
5035 mForegroundColor = foregroundColor;
5036 mTextColorsAreForBackground = COLOR_INVALID;
5041 * Sets the background color for this notification to be a different one then the default.
5042 * This is mainly used to calculate contrast and won't necessarily be applied to the
5047 public void setBackgroundColorHint(int backgroundColor) {
5048 mBackgroundColorHint = backgroundColor;
5053 * Forces all styled remoteViews to be built from scratch and not use any cached
5055 * This is needed for legacy apps that are baking in their remoteviews into the
5060 public void setRebuildStyledRemoteViews(boolean rebuild) {
5061 mRebuildStyledRemoteViews = rebuild;
5066 * @return whether this notification is a foreground service notification
5068 private boolean isForegroundService() {
5069 return (flags & Notification.FLAG_FOREGROUND_SERVICE) != 0;
5073 * @return whether this notification has a media session attached
5076 public boolean hasMediaSession() {
5077 return extras.getParcelable(Notification.EXTRA_MEDIA_SESSION) != null;
5081 * @return the style class of this notification
5084 public Class<? extends Notification.Style> getNotificationStyle() {
5085 String templateClass = extras.getString(Notification.EXTRA_TEMPLATE);
5087 if (!TextUtils.isEmpty(templateClass)) {
5088 return Notification.getNotificationStyleClass(templateClass);
5094 * @return true if this notification is colorized.
5098 public boolean isColorized() {
5099 if (isColorizedMedia()) {
5102 return extras.getBoolean(EXTRA_COLORIZED) && isForegroundService();
5106 * @return true if this notification is colorized and it is a media notification
5110 public boolean isColorizedMedia() {
5111 Class<? extends Style> style = getNotificationStyle();
5112 if (MediaStyle.class.equals(style)) {
5113 Boolean colorized = (Boolean) extras.get(EXTRA_COLORIZED);
5114 if ((colorized == null || colorized) && hasMediaSession()) {
5117 } else if (DecoratedMediaCustomViewStyle.class.equals(style)) {
5118 if (extras.getBoolean(EXTRA_COLORIZED) && hasMediaSession()) {
5127 * @return true if this is a media notification
5131 public boolean isMediaNotification() {
5132 Class<? extends Style> style = getNotificationStyle();
5133 if (MediaStyle.class.equals(style)) {
5135 } else if (DecoratedMediaCustomViewStyle.class.equals(style)) {
5141 private boolean hasLargeIcon() {
5142 return mLargeIcon != null || largeIcon != null;
5146 * @return true if the notification will show the time; false otherwise
5149 public boolean showsTime() {
5150 return when != 0 && extras.getBoolean(EXTRA_SHOW_WHEN);
5154 * @return true if the notification will show a chronometer; false otherwise
5157 public boolean showsChronometer() {
5158 return when != 0 && extras.getBoolean(EXTRA_SHOW_CHRONOMETER);
5165 public static Class<? extends Style> getNotificationStyleClass(String templateClass) {
5166 Class<? extends Style>[] classes = new Class[] {
5167 BigTextStyle.class, BigPictureStyle.class, InboxStyle.class, MediaStyle.class,
5168 DecoratedCustomViewStyle.class, DecoratedMediaCustomViewStyle.class,
5169 MessagingStyle.class };
5170 for (Class<? extends Style> innerClass : classes) {
5171 if (templateClass.equals(innerClass.getName())) {
5179 * An object that can apply a rich notification style to a {@link Notification.Builder}
5182 public static abstract class Style {
5183 private CharSequence mBigContentTitle;
5188 protected CharSequence mSummaryText = null;
5193 protected boolean mSummaryTextSet = false;
5195 protected Builder mBuilder;
5198 * Overrides ContentTitle in the big form of the template.
5199 * This defaults to the value passed to setContentTitle().
5201 protected void internalSetBigContentTitle(CharSequence title) {
5202 mBigContentTitle = title;
5206 * Set the first line of text after the detail section in the big form of the template.
5208 protected void internalSetSummaryText(CharSequence cs) {
5210 mSummaryTextSet = true;
5213 public void setBuilder(Builder builder) {
5214 if (mBuilder != builder) {
5216 if (mBuilder != null) {
5217 mBuilder.setStyle(this);
5222 protected void checkBuilder() {
5223 if (mBuilder == null) {
5224 throw new IllegalArgumentException("Style requires a valid Builder object");
5228 protected RemoteViews getStandardView(int layoutId) {
5232 CharSequence oldBuilderContentTitle =
5233 mBuilder.getAllExtras().getCharSequence(EXTRA_TITLE);
5234 if (mBigContentTitle != null) {
5235 mBuilder.setContentTitle(mBigContentTitle);
5238 RemoteViews contentView = mBuilder.applyStandardTemplateWithActions(layoutId);
5240 mBuilder.getAllExtras().putCharSequence(EXTRA_TITLE, oldBuilderContentTitle);
5242 if (mBigContentTitle != null && mBigContentTitle.equals("")) {
5243 contentView.setViewVisibility(R.id.line1, View.GONE);
5245 contentView.setViewVisibility(R.id.line1, View.VISIBLE);
5252 * Construct a Style-specific RemoteViews for the collapsed notification layout.
5253 * The default implementation has nothing additional to add.
5255 * @param increasedHeight true if this layout be created with an increased height.
5258 public RemoteViews makeContentView(boolean increasedHeight) {
5263 * Construct a Style-specific RemoteViews for the final big notification layout.
5266 public RemoteViews makeBigContentView() {
5271 * Construct a Style-specific RemoteViews for the final HUN layout.
5273 * @param increasedHeight true if this layout be created with an increased height.
5276 public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
5281 * Apply any style-specific extras to this notification before shipping it out.
5284 public void addExtras(Bundle extras) {
5285 if (mSummaryTextSet) {
5286 extras.putCharSequence(EXTRA_SUMMARY_TEXT, mSummaryText);
5288 if (mBigContentTitle != null) {
5289 extras.putCharSequence(EXTRA_TITLE_BIG, mBigContentTitle);
5291 extras.putString(EXTRA_TEMPLATE, this.getClass().getName());
5295 * Reconstruct the internal state of this Style object from extras.
5298 protected void restoreFromExtras(Bundle extras) {
5299 if (extras.containsKey(EXTRA_SUMMARY_TEXT)) {
5300 mSummaryText = extras.getCharSequence(EXTRA_SUMMARY_TEXT);
5301 mSummaryTextSet = true;
5303 if (extras.containsKey(EXTRA_TITLE_BIG)) {
5304 mBigContentTitle = extras.getCharSequence(EXTRA_TITLE_BIG);
5312 public Notification buildStyled(Notification wip) {
5313 addExtras(wip.extras);
5320 public void purgeResources() {}
5323 * Calls {@link android.app.Notification.Builder#build()} on the Builder this Style is
5326 * @return the fully constructed Notification.
5328 public Notification build() {
5330 return mBuilder.build();
5335 * @return true if the style positions the progress bar on the second line; false if the
5336 * style hides the progress bar
5338 protected boolean hasProgress() {
5344 * @return Whether we should put the summary be put into the notification header
5346 public boolean hasSummaryInHeader() {
5352 * @return Whether custom content views are displayed inline in the style
5354 public boolean displayCustomViewInline() {
5360 * Helper class for generating large-format notifications that include a large image attachment.
5362 * Here's how you'd set the <code>BigPictureStyle</code> on a notification:
5363 * <pre class="prettyprint">
5364 * Notification notif = new Notification.Builder(mContext)
5365 * .setContentTitle("New photo from " + sender.toString())
5366 * .setContentText(subject)
5367 * .setSmallIcon(R.drawable.new_post)
5368 * .setLargeIcon(aBitmap)
5369 * .setStyle(new Notification.BigPictureStyle()
5370 * .bigPicture(aBigBitmap))
5374 * @see Notification#bigContentView
5376 public static class BigPictureStyle extends Style {
5377 private Bitmap mPicture;
5378 private Icon mBigLargeIcon;
5379 private boolean mBigLargeIconSet = false;
5381 public BigPictureStyle() {
5385 * @deprecated use {@code BigPictureStyle()}.
5388 public BigPictureStyle(Builder builder) {
5389 setBuilder(builder);
5393 * Overrides ContentTitle in the big form of the template.
5394 * This defaults to the value passed to setContentTitle().
5396 public BigPictureStyle setBigContentTitle(CharSequence title) {
5397 internalSetBigContentTitle(safeCharSequence(title));
5402 * Set the first line of text after the detail section in the big form of the template.
5404 public BigPictureStyle setSummaryText(CharSequence cs) {
5405 internalSetSummaryText(safeCharSequence(cs));
5410 * Provide the bitmap to be used as the payload for the BigPicture notification.
5412 public BigPictureStyle bigPicture(Bitmap b) {
5418 * Override the large icon when the big notification is shown.
5420 public BigPictureStyle bigLargeIcon(Bitmap b) {
5421 return bigLargeIcon(b != null ? Icon.createWithBitmap(b) : null);
5425 * Override the large icon when the big notification is shown.
5427 public BigPictureStyle bigLargeIcon(Icon icon) {
5428 mBigLargeIconSet = true;
5429 mBigLargeIcon = icon;
5434 public static final int MIN_ASHMEM_BITMAP_SIZE = 128 * (1 << 10);
5440 public void purgeResources() {
5441 super.purgeResources();
5442 if (mPicture != null &&
5443 mPicture.isMutable() &&
5444 mPicture.getAllocationByteCount() >= MIN_ASHMEM_BITMAP_SIZE) {
5445 mPicture = mPicture.createAshmemBitmap();
5447 if (mBigLargeIcon != null) {
5448 mBigLargeIcon.convertToAshmem();
5455 public RemoteViews makeBigContentView() {
5456 // Replace mN.mLargeIcon with mBigLargeIcon if mBigLargeIconSet
5457 // This covers the following cases:
5458 // 1. mBigLargeIconSet -> mBigLargeIcon (null or non-null) applies, overrides
5460 // 2. !mBigLargeIconSet -> mN.mLargeIcon applies
5461 Icon oldLargeIcon = null;
5462 Bitmap largeIconLegacy = null;
5463 if (mBigLargeIconSet) {
5464 oldLargeIcon = mBuilder.mN.mLargeIcon;
5465 mBuilder.mN.mLargeIcon = mBigLargeIcon;
5466 // The legacy largeIcon might not allow us to clear the image, as it's taken in
5467 // replacement if the other one is null. Because we're restoring these legacy icons
5468 // for old listeners, this is in general non-null.
5469 largeIconLegacy = mBuilder.mN.largeIcon;
5470 mBuilder.mN.largeIcon = null;
5473 RemoteViews contentView = getStandardView(mBuilder.getBigPictureLayoutResource());
5474 if (mSummaryTextSet) {
5475 contentView.setTextViewText(R.id.text, mBuilder.processLegacyText(mSummaryText));
5476 mBuilder.setTextViewColorSecondary(contentView, R.id.text);
5477 contentView.setViewVisibility(R.id.text, View.VISIBLE);
5479 mBuilder.setContentMinHeight(contentView, mBuilder.mN.hasLargeIcon());
5481 if (mBigLargeIconSet) {
5482 mBuilder.mN.mLargeIcon = oldLargeIcon;
5483 mBuilder.mN.largeIcon = largeIconLegacy;
5486 contentView.setImageViewBitmap(R.id.big_picture, mPicture);
5493 public void addExtras(Bundle extras) {
5494 super.addExtras(extras);
5496 if (mBigLargeIconSet) {
5497 extras.putParcelable(EXTRA_LARGE_ICON_BIG, mBigLargeIcon);
5499 extras.putParcelable(EXTRA_PICTURE, mPicture);
5506 protected void restoreFromExtras(Bundle extras) {
5507 super.restoreFromExtras(extras);
5509 if (extras.containsKey(EXTRA_LARGE_ICON_BIG)) {
5510 mBigLargeIconSet = true;
5511 mBigLargeIcon = extras.getParcelable(EXTRA_LARGE_ICON_BIG);
5513 mPicture = extras.getParcelable(EXTRA_PICTURE);
5520 public boolean hasSummaryInHeader() {
5526 * Helper class for generating large-format notifications that include a lot of text.
5528 * Here's how you'd set the <code>BigTextStyle</code> on a notification:
5529 * <pre class="prettyprint">
5530 * Notification notif = new Notification.Builder(mContext)
5531 * .setContentTitle("New mail from " + sender.toString())
5532 * .setContentText(subject)
5533 * .setSmallIcon(R.drawable.new_mail)
5534 * .setLargeIcon(aBitmap)
5535 * .setStyle(new Notification.BigTextStyle()
5536 * .bigText(aVeryLongString))
5540 * @see Notification#bigContentView
5542 public static class BigTextStyle extends Style {
5544 private CharSequence mBigText;
5546 public BigTextStyle() {
5550 * @deprecated use {@code BigTextStyle()}.
5553 public BigTextStyle(Builder builder) {
5554 setBuilder(builder);
5558 * Overrides ContentTitle in the big form of the template.
5559 * This defaults to the value passed to setContentTitle().
5561 public BigTextStyle setBigContentTitle(CharSequence title) {
5562 internalSetBigContentTitle(safeCharSequence(title));
5567 * Set the first line of text after the detail section in the big form of the template.
5569 public BigTextStyle setSummaryText(CharSequence cs) {
5570 internalSetSummaryText(safeCharSequence(cs));
5575 * Provide the longer text to be displayed in the big form of the
5576 * template in place of the content text.
5578 public BigTextStyle bigText(CharSequence cs) {
5579 mBigText = safeCharSequence(cs);
5586 public void addExtras(Bundle extras) {
5587 super.addExtras(extras);
5589 extras.putCharSequence(EXTRA_BIG_TEXT, mBigText);
5596 protected void restoreFromExtras(Bundle extras) {
5597 super.restoreFromExtras(extras);
5599 mBigText = extras.getCharSequence(EXTRA_BIG_TEXT);
5603 * @param increasedHeight true if this layout be created with an increased height.
5608 public RemoteViews makeContentView(boolean increasedHeight) {
5609 if (increasedHeight) {
5610 ArrayList<Action> actions = mBuilder.mActions;
5611 mBuilder.mActions = new ArrayList<>();
5612 RemoteViews remoteViews = makeBigContentView();
5613 mBuilder.mActions = actions;
5616 return super.makeContentView(increasedHeight);
5623 public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
5624 if (increasedHeight && mBuilder.mActions.size() > 0) {
5625 return makeBigContentView();
5627 return super.makeHeadsUpContentView(increasedHeight);
5633 public RemoteViews makeBigContentView() {
5636 CharSequence text = mBuilder.getAllExtras().getCharSequence(EXTRA_TEXT);
5637 mBuilder.getAllExtras().putCharSequence(EXTRA_TEXT, null);
5639 RemoteViews contentView = getStandardView(mBuilder.getBigTextLayoutResource());
5641 mBuilder.getAllExtras().putCharSequence(EXTRA_TEXT, text);
5643 CharSequence bigTextText = mBuilder.processLegacyText(mBigText);
5644 if (TextUtils.isEmpty(bigTextText)) {
5645 // In case the bigtext is null / empty fall back to the normal text to avoid a weird
5647 bigTextText = mBuilder.processLegacyText(text);
5649 applyBigTextContentView(mBuilder, contentView, bigTextText);
5654 static void applyBigTextContentView(Builder builder,
5655 RemoteViews contentView, CharSequence bigTextText) {
5656 contentView.setTextViewText(R.id.big_text, bigTextText);
5657 builder.setTextViewColorSecondary(contentView, R.id.big_text);
5658 contentView.setViewVisibility(R.id.big_text,
5659 TextUtils.isEmpty(bigTextText) ? View.GONE : View.VISIBLE);
5660 contentView.setBoolean(R.id.big_text, "setHasImage", builder.mN.hasLargeIcon());
5665 * Helper class for generating large-format notifications that include multiple back-and-forth
5666 * messages of varying types between any number of people.
5669 * If the platform does not provide large-format notifications, this method has no effect. The
5670 * user will always see the normal notification view.
5672 * This class is a "rebuilder": It attaches to a Builder object and modifies its behavior, like
5674 * <pre class="prettyprint">
5676 * Notification noti = new Notification.Builder()
5677 * .setContentTitle("2 new messages wtih " + sender.toString())
5678 * .setContentText(subject)
5679 * .setSmallIcon(R.drawable.new_message)
5680 * .setLargeIcon(aBitmap)
5681 * .setStyle(new Notification.MessagingStyle(resources.getString(R.string.reply_name))
5682 * .addMessage(messages[0].getText(), messages[0].getTime(), messages[0].getSender())
5683 * .addMessage(messages[1].getText(), messages[1].getTime(), messages[1].getSender()))
5687 public static class MessagingStyle extends Style {
5690 * The maximum number of messages that will be retained in the Notification itself (the
5691 * number displayed is up to the platform).
5693 public static final int MAXIMUM_RETAINED_MESSAGES = 25;
5695 CharSequence mUserDisplayName;
5696 CharSequence mConversationTitle;
5697 List<Message> mMessages = new ArrayList<>();
5698 List<Message> mHistoricMessages = new ArrayList<>();
5704 * @param userDisplayName Required - the name to be displayed for any replies sent by the
5705 * user before the posting app reposts the notification with those messages after they've
5706 * been actually sent and in previous messages sent by the user added in
5707 * {@link #addMessage(Notification.MessagingStyle.Message)}
5709 public MessagingStyle(@NonNull CharSequence userDisplayName) {
5710 mUserDisplayName = userDisplayName;
5714 * Returns the name to be displayed for any replies sent by the user
5716 public CharSequence getUserDisplayName() {
5717 return mUserDisplayName;
5721 * Sets the title to be displayed on this conversation. This should only be used for
5722 * group messaging and left unset for one-on-one conversations.
5723 * @param conversationTitle
5724 * @return this object for method chaining.
5726 public MessagingStyle setConversationTitle(CharSequence conversationTitle) {
5727 mConversationTitle = conversationTitle;
5732 * Return the title to be displayed on this conversation. Can be <code>null</code> and
5733 * should be for one-on-one conversations
5735 public CharSequence getConversationTitle() {
5736 return mConversationTitle;
5740 * Adds a message for display by this notification. Convenience call for a simple
5741 * {@link Message} in {@link #addMessage(Notification.MessagingStyle.Message)}.
5742 * @param text A {@link CharSequence} to be displayed as the message content
5743 * @param timestamp Time at which the message arrived
5744 * @param sender A {@link CharSequence} to be used for displaying the name of the
5745 * sender. Should be <code>null</code> for messages by the current user, in which case
5746 * the platform will insert {@link #getUserDisplayName()}.
5747 * Should be unique amongst all individuals in the conversation, and should be
5748 * consistent during re-posts of the notification.
5750 * @see Message#Message(CharSequence, long, CharSequence)
5752 * @return this object for method chaining
5754 public MessagingStyle addMessage(CharSequence text, long timestamp, CharSequence sender) {
5755 return addMessage(new Message(text, timestamp, sender));
5759 * Adds a {@link Message} for display in this notification.
5761 * <p>The messages should be added in chronologic order, i.e. the oldest first,
5764 * @param message The {@link Message} to be displayed
5765 * @return this object for method chaining
5767 public MessagingStyle addMessage(Message message) {
5768 mMessages.add(message);
5769 if (mMessages.size() > MAXIMUM_RETAINED_MESSAGES) {
5770 mMessages.remove(0);
5776 * Adds a {@link Message} for historic context in this notification.
5778 * <p>Messages should be added as historic if they are not the main subject of the
5779 * notification but may give context to a conversation. The system may choose to present
5780 * them only when relevant, e.g. when replying to a message through a {@link RemoteInput}.
5782 * <p>The messages should be added in chronologic order, i.e. the oldest first,
5785 * @param message The historic {@link Message} to be added
5786 * @return this object for method chaining
5788 public MessagingStyle addHistoricMessage(Message message) {
5789 mHistoricMessages.add(message);
5790 if (mHistoricMessages.size() > MAXIMUM_RETAINED_MESSAGES) {
5791 mHistoricMessages.remove(0);
5797 * Gets the list of {@code Message} objects that represent the notification
5799 public List<Message> getMessages() {
5804 * Gets the list of historic {@code Message}s in the notification.
5806 public List<Message> getHistoricMessages() {
5807 return mHistoricMessages;
5814 public void addExtras(Bundle extras) {
5815 super.addExtras(extras);
5816 if (mUserDisplayName != null) {
5817 extras.putCharSequence(EXTRA_SELF_DISPLAY_NAME, mUserDisplayName);
5819 if (mConversationTitle != null) {
5820 extras.putCharSequence(EXTRA_CONVERSATION_TITLE, mConversationTitle);
5822 if (!mMessages.isEmpty()) { extras.putParcelableArray(EXTRA_MESSAGES,
5823 Message.getBundleArrayForMessages(mMessages));
5825 if (!mHistoricMessages.isEmpty()) { extras.putParcelableArray(EXTRA_HISTORIC_MESSAGES,
5826 Message.getBundleArrayForMessages(mHistoricMessages));
5829 fixTitleAndTextExtras(extras);
5832 private void fixTitleAndTextExtras(Bundle extras) {
5833 Message m = findLatestIncomingMessage();
5834 CharSequence text = (m == null) ? null : m.mText;
5835 CharSequence sender = m == null ? null
5836 : TextUtils.isEmpty(m.mSender) ? mUserDisplayName : m.mSender;
5838 if (!TextUtils.isEmpty(mConversationTitle)) {
5839 if (!TextUtils.isEmpty(sender)) {
5840 BidiFormatter bidi = BidiFormatter.getInstance();
5841 title = mBuilder.mContext.getString(
5842 com.android.internal.R.string.notification_messaging_title_template,
5843 bidi.unicodeWrap(mConversationTitle), bidi.unicodeWrap(m.mSender));
5845 title = mConversationTitle;
5851 if (title != null) {
5852 extras.putCharSequence(EXTRA_TITLE, title);
5855 extras.putCharSequence(EXTRA_TEXT, text);
5863 protected void restoreFromExtras(Bundle extras) {
5864 super.restoreFromExtras(extras);
5867 mHistoricMessages.clear();
5868 mUserDisplayName = extras.getCharSequence(EXTRA_SELF_DISPLAY_NAME);
5869 mConversationTitle = extras.getCharSequence(EXTRA_CONVERSATION_TITLE);
5870 Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES);
5871 if (messages != null && messages instanceof Parcelable[]) {
5872 mMessages = Message.getMessagesFromBundleArray(messages);
5874 Parcelable[] histMessages = extras.getParcelableArray(EXTRA_HISTORIC_MESSAGES);
5875 if (histMessages != null && histMessages instanceof Parcelable[]) {
5876 mHistoricMessages = Message.getMessagesFromBundleArray(histMessages);
5884 public RemoteViews makeContentView(boolean increasedHeight) {
5885 if (!increasedHeight) {
5886 Message m = findLatestIncomingMessage();
5887 CharSequence title = mConversationTitle != null
5888 ? mConversationTitle
5889 : (m == null) ? null : m.mSender;
5890 CharSequence text = (m == null)
5892 : mConversationTitle != null ? makeMessageLine(m, mBuilder) : m.mText;
5894 return mBuilder.applyStandardTemplate(mBuilder.getBaseLayoutResource(),
5895 mBuilder.mParams.reset().hasProgress(false).title(title).text(text));
5897 ArrayList<Action> actions = mBuilder.mActions;
5898 mBuilder.mActions = new ArrayList<>();
5899 RemoteViews remoteViews = makeBigContentView();
5900 mBuilder.mActions = actions;
5905 private Message findLatestIncomingMessage() {
5906 for (int i = mMessages.size() - 1; i >= 0; i--) {
5907 Message m = mMessages.get(i);
5908 // Incoming messages have a non-empty sender.
5909 if (!TextUtils.isEmpty(m.mSender)) {
5913 if (!mMessages.isEmpty()) {
5914 // No incoming messages, fall back to outgoing message
5915 return mMessages.get(mMessages.size() - 1);
5924 public RemoteViews makeBigContentView() {
5925 CharSequence title = !TextUtils.isEmpty(super.mBigContentTitle)
5926 ? super.mBigContentTitle
5927 : mConversationTitle;
5928 boolean hasTitle = !TextUtils.isEmpty(title);
5930 if (mMessages.size() == 1) {
5931 // Special case for a single message: Use the big text style
5932 // so the collapsed and expanded versions match nicely.
5933 CharSequence bigTitle;
5937 text = makeMessageLine(mMessages.get(0), mBuilder);
5939 bigTitle = mMessages.get(0).mSender;
5940 text = mMessages.get(0).mText;
5942 RemoteViews contentView = mBuilder.applyStandardTemplateWithActions(
5943 mBuilder.getBigTextLayoutResource(),
5944 mBuilder.mParams.reset().hasProgress(false).title(bigTitle).text(null));
5945 BigTextStyle.applyBigTextContentView(mBuilder, contentView, text);
5949 RemoteViews contentView = mBuilder.applyStandardTemplateWithActions(
5950 mBuilder.getMessagingLayoutResource(),
5951 mBuilder.mParams.reset().hasProgress(false).title(title).text(null));
5953 int[] rowIds = {R.id.inbox_text0, R.id.inbox_text1, R.id.inbox_text2, R.id.inbox_text3,
5954 R.id.inbox_text4, R.id.inbox_text5, R.id.inbox_text6};
5956 // Make sure all rows are gone in case we reuse a view.
5957 for (int rowId : rowIds) {
5958 contentView.setViewVisibility(rowId, View.GONE);
5962 contentView.setViewLayoutMarginBottomDimen(R.id.line1,
5963 hasTitle ? R.dimen.notification_messaging_spacing : 0);
5964 contentView.setInt(R.id.notification_messaging, "setNumIndentLines",
5965 !mBuilder.mN.hasLargeIcon() ? 0 : (hasTitle ? 1 : 2));
5967 int contractedChildId = View.NO_ID;
5968 Message contractedMessage = findLatestIncomingMessage();
5969 int firstHistoricMessage = Math.max(0, mHistoricMessages.size()
5970 - (rowIds.length - mMessages.size()));
5971 while (firstHistoricMessage + i < mHistoricMessages.size() && i < rowIds.length) {
5972 Message m = mHistoricMessages.get(firstHistoricMessage + i);
5973 int rowId = rowIds[i];
5975 contentView.setTextViewText(rowId, makeMessageLine(m, mBuilder));
5977 if (contractedMessage == m) {
5978 contractedChildId = rowId;
5984 int firstMessage = Math.max(0, mMessages.size() - rowIds.length);
5985 while (firstMessage + i < mMessages.size() && i < rowIds.length) {
5986 Message m = mMessages.get(firstMessage + i);
5987 int rowId = rowIds[i];
5989 contentView.setViewVisibility(rowId, View.VISIBLE);
5990 contentView.setTextViewText(rowId, makeMessageLine(m, mBuilder));
5991 mBuilder.setTextViewColorSecondary(contentView, rowId);
5993 if (contractedMessage == m) {
5994 contractedChildId = rowId;
5999 // Clear the remaining views for reapply. Ensures that historic message views can
6000 // reliably be identified as being GONE and having non-null text.
6001 while (i < rowIds.length) {
6002 int rowId = rowIds[i];
6003 contentView.setTextViewText(rowId, null);
6007 // Record this here to allow transformation between the contracted and expanded views.
6008 contentView.setInt(R.id.notification_messaging, "setContractedChildId",
6013 private CharSequence makeMessageLine(Message m, Builder builder) {
6014 BidiFormatter bidi = BidiFormatter.getInstance();
6015 SpannableStringBuilder sb = new SpannableStringBuilder();
6016 boolean colorize = builder.isColorized();
6017 if (TextUtils.isEmpty(m.mSender)) {
6018 CharSequence replyName = mUserDisplayName == null ? "" : mUserDisplayName;
6019 sb.append(bidi.unicodeWrap(replyName),
6020 makeFontColorSpan(colorize
6021 ? builder.getPrimaryTextColor()
6022 : mBuilder.resolveContrastColor()),
6025 sb.append(bidi.unicodeWrap(m.mSender),
6026 makeFontColorSpan(colorize
6027 ? builder.getPrimaryTextColor()
6031 CharSequence text = m.mText == null ? "" : m.mText;
6032 sb.append(" ").append(bidi.unicodeWrap(text));
6040 public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
6041 if (increasedHeight) {
6042 return makeBigContentView();
6044 Message m = findLatestIncomingMessage();
6045 CharSequence title = mConversationTitle != null
6046 ? mConversationTitle
6047 : (m == null) ? null : m.mSender;
6048 CharSequence text = (m == null)
6050 : mConversationTitle != null ? makeMessageLine(m, mBuilder) : m.mText;
6052 return mBuilder.applyStandardTemplateWithActions(mBuilder.getBigBaseLayoutResource(),
6053 mBuilder.mParams.reset().hasProgress(false).title(title).text(text));
6056 private static TextAppearanceSpan makeFontColorSpan(int color) {
6057 return new TextAppearanceSpan(null, 0, 0,
6058 ColorStateList.valueOf(color), null);
6061 public static final class Message {
6063 static final String KEY_TEXT = "text";
6064 static final String KEY_TIMESTAMP = "time";
6065 static final String KEY_SENDER = "sender";
6066 static final String KEY_DATA_MIME_TYPE = "type";
6067 static final String KEY_DATA_URI= "uri";
6068 static final String KEY_EXTRAS_BUNDLE = "extras";
6070 private final CharSequence mText;
6071 private final long mTimestamp;
6072 private final CharSequence mSender;
6074 private Bundle mExtras = new Bundle();
6075 private String mDataMimeType;
6076 private Uri mDataUri;
6080 * @param text A {@link CharSequence} to be displayed as the message content
6081 * @param timestamp Time at which the message arrived
6082 * @param sender A {@link CharSequence} to be used for displaying the name of the
6083 * sender. Should be <code>null</code> for messages by the current user, in which case
6084 * the platform will insert {@link MessagingStyle#getUserDisplayName()}.
6085 * Should be unique amongst all individuals in the conversation, and should be
6086 * consistent during re-posts of the notification.
6088 public Message(CharSequence text, long timestamp, CharSequence sender){
6090 mTimestamp = timestamp;
6095 * Sets a binary blob of data and an associated MIME type for a message. In the case
6096 * where the platform doesn't support the MIME type, the original text provided in the
6097 * constructor will be used.
6098 * @param dataMimeType The MIME type of the content. See
6099 * <a href="{@docRoot}notifications/messaging.html"> for the list of supported MIME
6100 * types on Android and Android Wear.
6101 * @param dataUri The uri containing the content whose type is given by the MIME type.
6104 * <li>Notification Listeners including the System UI need permission to access the
6105 * data the Uri points to. The recommended ways to do this are:</li>
6106 * <li>Store the data in your own ContentProvider, making sure that other apps have
6107 * the correct permission to access your provider. The preferred mechanism for
6108 * providing access is to use per-URI permissions which are temporary and only
6109 * grant access to the receiving application. An easy way to create a
6110 * ContentProvider like this is to use the FileProvider helper class.</li>
6111 * <li>Use the system MediaStore. The MediaStore is primarily aimed at video, audio
6112 * and image MIME types, however beginning with Android 3.0 (API level 11) it can
6113 * also store non-media types (see MediaStore.Files for more info). Files can be
6114 * inserted into the MediaStore using scanFile() after which a content:// style
6115 * Uri suitable for sharing is passed to the provided onScanCompleted() callback.
6116 * Note that once added to the system MediaStore the content is accessible to any
6117 * app on the device.</li>
6119 * @return this object for method chaining
6121 public Message setData(String dataMimeType, Uri dataUri) {
6122 mDataMimeType = dataMimeType;
6128 * Get the text to be used for this message, or the fallback text if a type and content
6131 public CharSequence getText() {
6136 * Get the time at which this message arrived
6138 public long getTimestamp() {
6143 * Get the extras Bundle for this message.
6145 public Bundle getExtras() {
6150 * Get the text used to display the contact's name in the messaging experience
6152 public CharSequence getSender() {
6157 * Get the MIME type of the data pointed to by the Uri
6159 public String getDataMimeType() {
6160 return mDataMimeType;
6164 * Get the the Uri pointing to the content of the message. Can be null, in which case
6165 * {@see #getText()} is used.
6167 public Uri getDataUri() {
6171 private Bundle toBundle() {
6172 Bundle bundle = new Bundle();
6173 if (mText != null) {
6174 bundle.putCharSequence(KEY_TEXT, mText);
6176 bundle.putLong(KEY_TIMESTAMP, mTimestamp);
6177 if (mSender != null) {
6178 bundle.putCharSequence(KEY_SENDER, mSender);
6180 if (mDataMimeType != null) {
6181 bundle.putString(KEY_DATA_MIME_TYPE, mDataMimeType);
6183 if (mDataUri != null) {
6184 bundle.putParcelable(KEY_DATA_URI, mDataUri);
6186 if (mExtras != null) {
6187 bundle.putBundle(KEY_EXTRAS_BUNDLE, mExtras);
6192 static Bundle[] getBundleArrayForMessages(List<Message> messages) {
6193 Bundle[] bundles = new Bundle[messages.size()];
6194 final int N = messages.size();
6195 for (int i = 0; i < N; i++) {
6196 bundles[i] = messages.get(i).toBundle();
6201 static List<Message> getMessagesFromBundleArray(Parcelable[] bundles) {
6202 List<Message> messages = new ArrayList<>(bundles.length);
6203 for (int i = 0; i < bundles.length; i++) {
6204 if (bundles[i] instanceof Bundle) {
6205 Message message = getMessageFromBundle((Bundle)bundles[i]);
6206 if (message != null) {
6207 messages.add(message);
6214 static Message getMessageFromBundle(Bundle bundle) {
6216 if (!bundle.containsKey(KEY_TEXT) || !bundle.containsKey(KEY_TIMESTAMP)) {
6219 Message message = new Message(bundle.getCharSequence(KEY_TEXT),
6220 bundle.getLong(KEY_TIMESTAMP), bundle.getCharSequence(KEY_SENDER));
6221 if (bundle.containsKey(KEY_DATA_MIME_TYPE) &&
6222 bundle.containsKey(KEY_DATA_URI)) {
6223 message.setData(bundle.getString(KEY_DATA_MIME_TYPE),
6224 (Uri) bundle.getParcelable(KEY_DATA_URI));
6226 if (bundle.containsKey(KEY_EXTRAS_BUNDLE)) {
6227 message.getExtras().putAll(bundle.getBundle(KEY_EXTRAS_BUNDLE));
6231 } catch (ClassCastException e) {
6239 * Helper class for generating large-format notifications that include a list of (up to 5) strings.
6241 * Here's how you'd set the <code>InboxStyle</code> on a notification:
6242 * <pre class="prettyprint">
6243 * Notification notif = new Notification.Builder(mContext)
6244 * .setContentTitle("5 New mails from " + sender.toString())
6245 * .setContentText(subject)
6246 * .setSmallIcon(R.drawable.new_mail)
6247 * .setLargeIcon(aBitmap)
6248 * .setStyle(new Notification.InboxStyle()
6251 * .setContentTitle("")
6252 * .setSummaryText("+3 more"))
6256 * @see Notification#bigContentView
6258 public static class InboxStyle extends Style {
6259 private ArrayList<CharSequence> mTexts = new ArrayList<CharSequence>(5);
6261 public InboxStyle() {
6265 * @deprecated use {@code InboxStyle()}.
6268 public InboxStyle(Builder builder) {
6269 setBuilder(builder);
6273 * Overrides ContentTitle in the big form of the template.
6274 * This defaults to the value passed to setContentTitle().
6276 public InboxStyle setBigContentTitle(CharSequence title) {
6277 internalSetBigContentTitle(safeCharSequence(title));
6282 * Set the first line of text after the detail section in the big form of the template.
6284 public InboxStyle setSummaryText(CharSequence cs) {
6285 internalSetSummaryText(safeCharSequence(cs));
6290 * Append a line to the digest section of the Inbox notification.
6292 public InboxStyle addLine(CharSequence cs) {
6293 mTexts.add(safeCharSequence(cs));
6300 public void addExtras(Bundle extras) {
6301 super.addExtras(extras);
6303 CharSequence[] a = new CharSequence[mTexts.size()];
6304 extras.putCharSequenceArray(EXTRA_TEXT_LINES, mTexts.toArray(a));
6311 protected void restoreFromExtras(Bundle extras) {
6312 super.restoreFromExtras(extras);
6315 if (extras.containsKey(EXTRA_TEXT_LINES)) {
6316 Collections.addAll(mTexts, extras.getCharSequenceArray(EXTRA_TEXT_LINES));
6323 public RemoteViews makeBigContentView() {
6324 // Remove the content text so it disappears unless you have a summary
6326 CharSequence oldBuilderContentText = mBuilder.mN.extras.getCharSequence(EXTRA_TEXT);
6327 mBuilder.getAllExtras().putCharSequence(EXTRA_TEXT, null);
6329 RemoteViews contentView = getStandardView(mBuilder.getInboxLayoutResource());
6331 mBuilder.getAllExtras().putCharSequence(EXTRA_TEXT, oldBuilderContentText);
6333 int[] rowIds = {R.id.inbox_text0, R.id.inbox_text1, R.id.inbox_text2, R.id.inbox_text3,
6334 R.id.inbox_text4, R.id.inbox_text5, R.id.inbox_text6};
6336 // Make sure all rows are gone in case we reuse a view.
6337 for (int rowId : rowIds) {
6338 contentView.setViewVisibility(rowId, View.GONE);
6342 int topPadding = mBuilder.mContext.getResources().getDimensionPixelSize(
6343 R.dimen.notification_inbox_item_top_padding);
6344 boolean first = true;
6346 int maxRows = rowIds.length;
6347 if (mBuilder.mActions.size() > 0) {
6350 while (i < mTexts.size() && i < maxRows) {
6351 CharSequence str = mTexts.get(i);
6352 if (!TextUtils.isEmpty(str)) {
6353 contentView.setViewVisibility(rowIds[i], View.VISIBLE);
6354 contentView.setTextViewText(rowIds[i], mBuilder.processLegacyText(str));
6355 mBuilder.setTextViewColorSecondary(contentView, rowIds[i]);
6356 contentView.setViewPadding(rowIds[i], 0, topPadding, 0, 0);
6357 handleInboxImageMargin(contentView, rowIds[i], first);
6359 onlyViewId = rowIds[i];
6367 if (onlyViewId != 0) {
6368 // We only have 1 entry, lets make it look like the normal Text of a Bigtext
6369 topPadding = mBuilder.mContext.getResources().getDimensionPixelSize(
6370 R.dimen.notification_text_margin_top);
6371 contentView.setViewPadding(onlyViewId, 0, topPadding, 0, 0);
6377 private void handleInboxImageMargin(RemoteViews contentView, int id, boolean first) {
6380 final int max = mBuilder.mN.extras.getInt(EXTRA_PROGRESS_MAX, 0);
6381 final boolean ind = mBuilder.mN.extras.getBoolean(EXTRA_PROGRESS_INDETERMINATE);
6382 boolean hasProgress = max != 0 || ind;
6383 if (mBuilder.mN.hasLargeIcon() && !hasProgress) {
6384 endMargin = R.dimen.notification_content_picture_margin;
6387 contentView.setViewLayoutMarginEndDimen(id, endMargin);
6392 * Notification style for media playback notifications.
6394 * In the expanded form, {@link Notification#bigContentView}, up to 5
6395 * {@link Notification.Action}s specified with
6396 * {@link Notification.Builder#addAction(Action) addAction} will be
6397 * shown as icon-only pushbuttons, suitable for transport controls. The Bitmap given to
6398 * {@link Notification.Builder#setLargeIcon(android.graphics.Bitmap) setLargeIcon()} will be
6399 * treated as album artwork.
6401 * Unlike the other styles provided here, MediaStyle can also modify the standard-size
6402 * {@link Notification#contentView}; by providing action indices to
6403 * {@link #setShowActionsInCompactView(int...)} you can promote up to 3 actions to be displayed
6404 * in the standard view alongside the usual content.
6406 * Notifications created with MediaStyle will have their category set to
6407 * {@link Notification#CATEGORY_TRANSPORT CATEGORY_TRANSPORT} unless you set a different
6408 * category using {@link Notification.Builder#setCategory(String) setCategory()}.
6410 * Finally, if you attach a {@link android.media.session.MediaSession.Token} using
6411 * {@link android.app.Notification.MediaStyle#setMediaSession(MediaSession.Token)},
6412 * the System UI can identify this as a notification representing an active media session
6413 * and respond accordingly (by showing album artwork in the lockscreen, for example).
6416 * Starting at {@link android.os.Build.VERSION_CODES#O Android O} any notification that has a
6417 * media session attached with {@link #setMediaSession(MediaSession.Token)} will be colorized.
6418 * You can opt-out of this behavior by using {@link Notification.Builder#setColorized(boolean)}.
6421 * To use this style with your Notification, feed it to
6422 * {@link Notification.Builder#setStyle(android.app.Notification.Style)} like so:
6423 * <pre class="prettyprint">
6424 * Notification noti = new Notification.Builder()
6425 * .setSmallIcon(R.drawable.ic_stat_player)
6426 * .setContentTitle("Track title")
6427 * .setContentText("Artist - Album")
6428 * .setLargeIcon(albumArtBitmap))
6429 * .setStyle(<b>new Notification.MediaStyle()</b>
6430 * .setMediaSession(mySession))
6434 * @see Notification#bigContentView
6435 * @see Notification.Builder#setColorized(boolean)
6437 public static class MediaStyle extends Style {
6438 static final int MAX_MEDIA_BUTTONS_IN_COMPACT = 3;
6439 static final int MAX_MEDIA_BUTTONS = 5;
6441 private int[] mActionsToShowInCompact = null;
6442 private MediaSession.Token mToken;
6444 public MediaStyle() {
6448 * @deprecated use {@code MediaStyle()}.
6451 public MediaStyle(Builder builder) {
6452 setBuilder(builder);
6456 * Request up to 3 actions (by index in the order of addition) to be shown in the compact
6457 * notification view.
6459 * @param actions the indices of the actions to show in the compact notification view
6461 public MediaStyle setShowActionsInCompactView(int...actions) {
6462 mActionsToShowInCompact = actions;
6467 * Attach a {@link android.media.session.MediaSession.Token} to this Notification
6468 * to provide additional playback information and control to the SystemUI.
6470 public MediaStyle setMediaSession(MediaSession.Token token) {
6479 public Notification buildStyled(Notification wip) {
6480 super.buildStyled(wip);
6481 if (wip.category == null) {
6482 wip.category = Notification.CATEGORY_TRANSPORT;
6491 public RemoteViews makeContentView(boolean increasedHeight) {
6492 return makeMediaContentView();
6499 public RemoteViews makeBigContentView() {
6500 return makeMediaBigContentView();
6507 public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
6508 RemoteViews expanded = makeMediaBigContentView();
6509 return expanded != null ? expanded : makeMediaContentView();
6514 public void addExtras(Bundle extras) {
6515 super.addExtras(extras);
6517 if (mToken != null) {
6518 extras.putParcelable(EXTRA_MEDIA_SESSION, mToken);
6520 if (mActionsToShowInCompact != null) {
6521 extras.putIntArray(EXTRA_COMPACT_ACTIONS, mActionsToShowInCompact);
6529 protected void restoreFromExtras(Bundle extras) {
6530 super.restoreFromExtras(extras);
6532 if (extras.containsKey(EXTRA_MEDIA_SESSION)) {
6533 mToken = extras.getParcelable(EXTRA_MEDIA_SESSION);
6535 if (extras.containsKey(EXTRA_COMPACT_ACTIONS)) {
6536 mActionsToShowInCompact = extras.getIntArray(EXTRA_COMPACT_ACTIONS);
6540 private RemoteViews generateMediaActionButton(Action action, int color) {
6541 final boolean tombstone = (action.actionIntent == null);
6542 RemoteViews button = new BuilderRemoteViews(mBuilder.mContext.getApplicationInfo(),
6543 R.layout.notification_material_media_action);
6544 button.setImageViewIcon(R.id.action0, action.getIcon());
6545 button.setDrawableParameters(R.id.action0, false, -1, color, PorterDuff.Mode.SRC_ATOP,
6548 button.setOnClickPendingIntent(R.id.action0, action.actionIntent);
6550 button.setContentDescription(R.id.action0, action.title);
6554 private RemoteViews makeMediaContentView() {
6555 RemoteViews view = mBuilder.applyStandardTemplate(
6556 R.layout.notification_template_material_media, false /* hasProgress */);
6558 final int numActions = mBuilder.mActions.size();
6559 final int N = mActionsToShowInCompact == null
6561 : Math.min(mActionsToShowInCompact.length, MAX_MEDIA_BUTTONS_IN_COMPACT);
6563 view.removeAllViews(com.android.internal.R.id.media_actions);
6564 for (int i = 0; i < N; i++) {
6565 if (i >= numActions) {
6566 throw new IllegalArgumentException(String.format(
6567 "setShowActionsInCompactView: action %d out of bounds (max %d)",
6568 i, numActions - 1));
6571 final Action action = mBuilder.mActions.get(mActionsToShowInCompact[i]);
6572 final RemoteViews button = generateMediaActionButton(action,
6573 getPrimaryHighlightColor());
6574 view.addView(com.android.internal.R.id.media_actions, button);
6578 // handle the content margin
6579 int endMargin = R.dimen.notification_content_margin_end;
6580 if (mBuilder.mN.hasLargeIcon()) {
6581 endMargin = R.dimen.notification_content_plus_picture_margin_end;
6583 view.setViewLayoutMarginEndDimen(R.id.notification_main_column, endMargin);
6587 private int getPrimaryHighlightColor() {
6588 return mBuilder.getPrimaryHighlightColor();
6591 private RemoteViews makeMediaBigContentView() {
6592 final int actionCount = Math.min(mBuilder.mActions.size(), MAX_MEDIA_BUTTONS);
6593 // Dont add an expanded view if there is no more content to be revealed
6594 int actionsInCompact = mActionsToShowInCompact == null
6596 : Math.min(mActionsToShowInCompact.length, MAX_MEDIA_BUTTONS_IN_COMPACT);
6597 if (!mBuilder.mN.hasLargeIcon() && actionCount <= actionsInCompact) {
6600 RemoteViews big = mBuilder.applyStandardTemplate(
6601 R.layout.notification_template_material_big_media,
6604 if (actionCount > 0) {
6605 big.removeAllViews(com.android.internal.R.id.media_actions);
6606 for (int i = 0; i < actionCount; i++) {
6607 final RemoteViews button = generateMediaActionButton(mBuilder.mActions.get(i),
6608 getPrimaryHighlightColor());
6609 big.addView(com.android.internal.R.id.media_actions, button);
6616 private void handleImage(RemoteViews contentView) {
6617 if (mBuilder.mN.hasLargeIcon()) {
6618 contentView.setViewLayoutMarginEndDimen(R.id.line1, 0);
6619 contentView.setViewLayoutMarginEndDimen(R.id.text, 0);
6627 protected boolean hasProgress() {
6633 * Notification style for custom views that are decorated by the system
6635 * <p>Instead of providing a notification that is completely custom, a developer can set this
6636 * style and still obtain system decorations like the notification header with the expand
6637 * affordance and actions.
6639 * <p>Use {@link android.app.Notification.Builder#setCustomContentView(RemoteViews)},
6640 * {@link android.app.Notification.Builder#setCustomBigContentView(RemoteViews)} and
6641 * {@link android.app.Notification.Builder#setCustomHeadsUpContentView(RemoteViews)} to set the
6642 * corresponding custom views to display.
6644 * To use this style with your Notification, feed it to
6645 * {@link Notification.Builder#setStyle(android.app.Notification.Style)} like so:
6646 * <pre class="prettyprint">
6647 * Notification noti = new Notification.Builder()
6648 * .setSmallIcon(R.drawable.ic_stat_player)
6649 * .setLargeIcon(albumArtBitmap))
6650 * .setCustomContentView(contentView);
6651 * .setStyle(<b>new Notification.DecoratedCustomViewStyle()</b>)
6655 public static class DecoratedCustomViewStyle extends Style {
6657 public DecoratedCustomViewStyle() {
6663 public boolean displayCustomViewInline() {
6671 public RemoteViews makeContentView(boolean increasedHeight) {
6672 return makeStandardTemplateWithCustomContent(mBuilder.mN.contentView);
6679 public RemoteViews makeBigContentView() {
6680 return makeDecoratedBigContentView();
6687 public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
6688 return makeDecoratedHeadsUpContentView();
6691 private RemoteViews makeDecoratedHeadsUpContentView() {
6692 RemoteViews headsUpContentView = mBuilder.mN.headsUpContentView == null
6693 ? mBuilder.mN.contentView
6694 : mBuilder.mN.headsUpContentView;
6695 if (mBuilder.mActions.size() == 0) {
6696 return makeStandardTemplateWithCustomContent(headsUpContentView);
6698 RemoteViews remoteViews = mBuilder.applyStandardTemplateWithActions(
6699 mBuilder.getBigBaseLayoutResource());
6700 buildIntoRemoteViewContent(remoteViews, headsUpContentView);
6704 private RemoteViews makeStandardTemplateWithCustomContent(RemoteViews customContent) {
6705 RemoteViews remoteViews = mBuilder.applyStandardTemplate(
6706 mBuilder.getBaseLayoutResource());
6707 buildIntoRemoteViewContent(remoteViews, customContent);
6711 private RemoteViews makeDecoratedBigContentView() {
6712 RemoteViews bigContentView = mBuilder.mN.bigContentView == null
6713 ? mBuilder.mN.contentView
6714 : mBuilder.mN.bigContentView;
6715 if (mBuilder.mActions.size() == 0) {
6716 return makeStandardTemplateWithCustomContent(bigContentView);
6718 RemoteViews remoteViews = mBuilder.applyStandardTemplateWithActions(
6719 mBuilder.getBigBaseLayoutResource());
6720 buildIntoRemoteViewContent(remoteViews, bigContentView);
6724 private void buildIntoRemoteViewContent(RemoteViews remoteViews,
6725 RemoteViews customContent) {
6726 if (customContent != null) {
6727 // Need to clone customContent before adding, because otherwise it can no longer be
6728 // parceled independently of remoteViews.
6729 customContent = customContent.clone();
6730 remoteViews.removeAllViews(R.id.notification_main_column);
6731 remoteViews.addView(R.id.notification_main_column, customContent);
6733 // also update the end margin if there is an image
6734 int endMargin = R.dimen.notification_content_margin_end;
6735 if (mBuilder.mN.hasLargeIcon()) {
6736 endMargin = R.dimen.notification_content_plus_picture_margin_end;
6738 remoteViews.setViewLayoutMarginEndDimen(R.id.notification_main_column, endMargin);
6743 * Notification style for media custom views that are decorated by the system
6745 * <p>Instead of providing a media notification that is completely custom, a developer can set
6746 * this style and still obtain system decorations like the notification header with the expand
6747 * affordance and actions.
6749 * <p>Use {@link android.app.Notification.Builder#setCustomContentView(RemoteViews)},
6750 * {@link android.app.Notification.Builder#setCustomBigContentView(RemoteViews)} and
6751 * {@link android.app.Notification.Builder#setCustomHeadsUpContentView(RemoteViews)} to set the
6752 * corresponding custom views to display.
6754 * Contrary to {@link MediaStyle} a developer has to opt-in to the colorizing of the
6755 * notification by using {@link Notification.Builder#setColorized(boolean)}.
6757 * To use this style with your Notification, feed it to
6758 * {@link Notification.Builder#setStyle(android.app.Notification.Style)} like so:
6759 * <pre class="prettyprint">
6760 * Notification noti = new Notification.Builder()
6761 * .setSmallIcon(R.drawable.ic_stat_player)
6762 * .setLargeIcon(albumArtBitmap))
6763 * .setCustomContentView(contentView);
6764 * .setStyle(<b>new Notification.DecoratedMediaCustomViewStyle()</b>
6765 * .setMediaSession(mySession))
6769 * @see android.app.Notification.DecoratedCustomViewStyle
6770 * @see android.app.Notification.MediaStyle
6772 public static class DecoratedMediaCustomViewStyle extends MediaStyle {
6774 public DecoratedMediaCustomViewStyle() {
6780 public boolean displayCustomViewInline() {
6788 public RemoteViews makeContentView(boolean increasedHeight) {
6789 RemoteViews remoteViews = super.makeContentView(false /* increasedHeight */);
6790 return buildIntoRemoteView(remoteViews, R.id.notification_content_container,
6791 mBuilder.mN.contentView);
6798 public RemoteViews makeBigContentView() {
6799 RemoteViews customRemoteView = mBuilder.mN.bigContentView != null
6800 ? mBuilder.mN.bigContentView
6801 : mBuilder.mN.contentView;
6802 return makeBigContentViewWithCustomContent(customRemoteView);
6805 private RemoteViews makeBigContentViewWithCustomContent(RemoteViews customRemoteView) {
6806 RemoteViews remoteViews = super.makeBigContentView();
6807 if (remoteViews != null) {
6808 return buildIntoRemoteView(remoteViews, R.id.notification_main_column,
6810 } else if (customRemoteView != mBuilder.mN.contentView){
6811 remoteViews = super.makeContentView(false /* increasedHeight */);
6812 return buildIntoRemoteView(remoteViews, R.id.notification_content_container,
6823 public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
6824 RemoteViews customRemoteView = mBuilder.mN.headsUpContentView != null
6825 ? mBuilder.mN.headsUpContentView
6826 : mBuilder.mN.contentView;
6827 return makeBigContentViewWithCustomContent(customRemoteView);
6830 private RemoteViews buildIntoRemoteView(RemoteViews remoteViews, int id,
6831 RemoteViews customContent) {
6832 if (customContent != null) {
6833 // Need to clone customContent before adding, because otherwise it can no longer be
6834 // parceled independently of remoteViews.
6835 customContent = customContent.clone();
6836 remoteViews.removeAllViews(id);
6837 remoteViews.addView(id, customContent);
6843 // When adding a new Style subclass here, don't forget to update
6844 // Builder.getNotificationStyleClass.
6847 * Extender interface for use with {@link Builder#extend}. Extenders may be used to add
6848 * metadata or change options on a notification builder.
6850 public interface Extender {
6852 * Apply this extender to a notification builder.
6853 * @param builder the builder to be modified.
6854 * @return the build object for chaining.
6856 public Builder extend(Builder builder);
6860 * Helper class to add wearable extensions to notifications.
6861 * <p class="note"> See
6862 * <a href="{@docRoot}wear/notifications/creating.html">Creating Notifications
6863 * for Android Wear</a> for more information on how to use this class.
6865 * To create a notification with wearable extensions:
6867 * <li>Create a {@link android.app.Notification.Builder}, setting any desired
6869 * <li>Create a {@link android.app.Notification.WearableExtender}.
6870 * <li>Set wearable-specific properties using the
6871 * {@code add} and {@code set} methods of {@link android.app.Notification.WearableExtender}.
6872 * <li>Call {@link android.app.Notification.Builder#extend} to apply the extensions to a
6874 * <li>Post the notification to the notification system with the
6875 * {@code NotificationManager.notify(...)} methods.
6878 * <pre class="prettyprint">
6879 * Notification notif = new Notification.Builder(mContext)
6880 * .setContentTitle("New mail from " + sender.toString())
6881 * .setContentText(subject)
6882 * .setSmallIcon(R.drawable.new_mail)
6883 * .extend(new Notification.WearableExtender()
6884 * .setContentIcon(R.drawable.new_mail))
6886 * NotificationManager notificationManger =
6887 * (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
6888 * notificationManger.notify(0, notif);</pre>
6890 * <p>Wearable extensions can be accessed on an existing notification by using the
6891 * {@code WearableExtender(Notification)} constructor,
6892 * and then using the {@code get} methods to access values.
6894 * <pre class="prettyprint">
6895 * Notification.WearableExtender wearableExtender = new Notification.WearableExtender(
6897 * List<Notification> pages = wearableExtender.getPages();</pre>
6899 public static final class WearableExtender implements Extender {
6901 * Sentinel value for an action index that is unset.
6903 public static final int UNSET_ACTION_INDEX = -1;
6906 * Size value for use with {@link #setCustomSizePreset} to show this notification with
6908 * <p>For custom display notifications created using {@link #setDisplayIntent},
6909 * the default is {@link #SIZE_MEDIUM}. All other notifications size automatically based
6912 public static final int SIZE_DEFAULT = 0;
6915 * Size value for use with {@link #setCustomSizePreset} to show this notification
6916 * with an extra small size.
6917 * <p>This value is only applicable for custom display notifications created using
6918 * {@link #setDisplayIntent}.
6920 public static final int SIZE_XSMALL = 1;
6923 * Size value for use with {@link #setCustomSizePreset} to show this notification
6924 * with a small size.
6925 * <p>This value is only applicable for custom display notifications created using
6926 * {@link #setDisplayIntent}.
6928 public static final int SIZE_SMALL = 2;
6931 * Size value for use with {@link #setCustomSizePreset} to show this notification
6932 * with a medium size.
6933 * <p>This value is only applicable for custom display notifications created using
6934 * {@link #setDisplayIntent}.
6936 public static final int SIZE_MEDIUM = 3;
6939 * Size value for use with {@link #setCustomSizePreset} to show this notification
6940 * with a large size.
6941 * <p>This value is only applicable for custom display notifications created using
6942 * {@link #setDisplayIntent}.
6944 public static final int SIZE_LARGE = 4;
6947 * Size value for use with {@link #setCustomSizePreset} to show this notification
6949 * <p>This value is only applicable for custom display notifications created using
6950 * {@link #setDisplayIntent}.
6952 public static final int SIZE_FULL_SCREEN = 5;
6955 * Sentinel value for use with {@link #setHintScreenTimeout} to keep the screen on for a
6956 * short amount of time when this notification is displayed on the screen. This
6957 * is the default value.
6959 public static final int SCREEN_TIMEOUT_SHORT = 0;
6962 * Sentinel value for use with {@link #setHintScreenTimeout} to keep the screen on
6963 * for a longer amount of time when this notification is displayed on the screen.
6965 public static final int SCREEN_TIMEOUT_LONG = -1;
6967 /** Notification extra which contains wearable extensions */
6968 private static final String EXTRA_WEARABLE_EXTENSIONS = "android.wearable.EXTENSIONS";
6970 // Keys within EXTRA_WEARABLE_EXTENSIONS for wearable options.
6971 private static final String KEY_ACTIONS = "actions";
6972 private static final String KEY_FLAGS = "flags";
6973 private static final String KEY_DISPLAY_INTENT = "displayIntent";
6974 private static final String KEY_PAGES = "pages";
6975 private static final String KEY_BACKGROUND = "background";
6976 private static final String KEY_CONTENT_ICON = "contentIcon";
6977 private static final String KEY_CONTENT_ICON_GRAVITY = "contentIconGravity";
6978 private static final String KEY_CONTENT_ACTION_INDEX = "contentActionIndex";
6979 private static final String KEY_CUSTOM_SIZE_PRESET = "customSizePreset";
6980 private static final String KEY_CUSTOM_CONTENT_HEIGHT = "customContentHeight";
6981 private static final String KEY_GRAVITY = "gravity";
6982 private static final String KEY_HINT_SCREEN_TIMEOUT = "hintScreenTimeout";
6983 private static final String KEY_DISMISSAL_ID = "dismissalId";
6984 private static final String KEY_BRIDGE_TAG = "bridgeTag";
6986 // Flags bitwise-ored to mFlags
6987 private static final int FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE = 0x1;
6988 private static final int FLAG_HINT_HIDE_ICON = 1 << 1;
6989 private static final int FLAG_HINT_SHOW_BACKGROUND_ONLY = 1 << 2;
6990 private static final int FLAG_START_SCROLL_BOTTOM = 1 << 3;
6991 private static final int FLAG_HINT_AVOID_BACKGROUND_CLIPPING = 1 << 4;
6992 private static final int FLAG_BIG_PICTURE_AMBIENT = 1 << 5;
6993 private static final int FLAG_HINT_CONTENT_INTENT_LAUNCHES_ACTIVITY = 1 << 6;
6995 // Default value for flags integer
6996 private static final int DEFAULT_FLAGS = FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE;
6998 private static final int DEFAULT_CONTENT_ICON_GRAVITY = Gravity.END;
6999 private static final int DEFAULT_GRAVITY = Gravity.BOTTOM;
7001 private ArrayList<Action> mActions = new ArrayList<Action>();
7002 private int mFlags = DEFAULT_FLAGS;
7003 private PendingIntent mDisplayIntent;
7004 private ArrayList<Notification> mPages = new ArrayList<Notification>();
7005 private Bitmap mBackground;
7006 private int mContentIcon;
7007 private int mContentIconGravity = DEFAULT_CONTENT_ICON_GRAVITY;
7008 private int mContentActionIndex = UNSET_ACTION_INDEX;
7009 private int mCustomSizePreset = SIZE_DEFAULT;
7010 private int mCustomContentHeight;
7011 private int mGravity = DEFAULT_GRAVITY;
7012 private int mHintScreenTimeout;
7013 private String mDismissalId;
7014 private String mBridgeTag;
7017 * Create a {@link android.app.Notification.WearableExtender} with default
7020 public WearableExtender() {
7023 public WearableExtender(Notification notif) {
7024 Bundle wearableBundle = notif.extras.getBundle(EXTRA_WEARABLE_EXTENSIONS);
7025 if (wearableBundle != null) {
7026 List<Action> actions = wearableBundle.getParcelableArrayList(KEY_ACTIONS);
7027 if (actions != null) {
7028 mActions.addAll(actions);
7031 mFlags = wearableBundle.getInt(KEY_FLAGS, DEFAULT_FLAGS);
7032 mDisplayIntent = wearableBundle.getParcelable(KEY_DISPLAY_INTENT);
7034 Notification[] pages = getNotificationArrayFromBundle(
7035 wearableBundle, KEY_PAGES);
7036 if (pages != null) {
7037 Collections.addAll(mPages, pages);
7040 mBackground = wearableBundle.getParcelable(KEY_BACKGROUND);
7041 mContentIcon = wearableBundle.getInt(KEY_CONTENT_ICON);
7042 mContentIconGravity = wearableBundle.getInt(KEY_CONTENT_ICON_GRAVITY,
7043 DEFAULT_CONTENT_ICON_GRAVITY);
7044 mContentActionIndex = wearableBundle.getInt(KEY_CONTENT_ACTION_INDEX,
7045 UNSET_ACTION_INDEX);
7046 mCustomSizePreset = wearableBundle.getInt(KEY_CUSTOM_SIZE_PRESET,
7048 mCustomContentHeight = wearableBundle.getInt(KEY_CUSTOM_CONTENT_HEIGHT);
7049 mGravity = wearableBundle.getInt(KEY_GRAVITY, DEFAULT_GRAVITY);
7050 mHintScreenTimeout = wearableBundle.getInt(KEY_HINT_SCREEN_TIMEOUT);
7051 mDismissalId = wearableBundle.getString(KEY_DISMISSAL_ID);
7052 mBridgeTag = wearableBundle.getString(KEY_BRIDGE_TAG);
7057 * Apply wearable extensions to a notification that is being built. This is typically
7058 * called by the {@link android.app.Notification.Builder#extend} method of
7059 * {@link android.app.Notification.Builder}.
7062 public Notification.Builder extend(Notification.Builder builder) {
7063 Bundle wearableBundle = new Bundle();
7065 if (!mActions.isEmpty()) {
7066 wearableBundle.putParcelableArrayList(KEY_ACTIONS, mActions);
7068 if (mFlags != DEFAULT_FLAGS) {
7069 wearableBundle.putInt(KEY_FLAGS, mFlags);
7071 if (mDisplayIntent != null) {
7072 wearableBundle.putParcelable(KEY_DISPLAY_INTENT, mDisplayIntent);
7074 if (!mPages.isEmpty()) {
7075 wearableBundle.putParcelableArray(KEY_PAGES, mPages.toArray(
7076 new Notification[mPages.size()]));
7078 if (mBackground != null) {
7079 wearableBundle.putParcelable(KEY_BACKGROUND, mBackground);
7081 if (mContentIcon != 0) {
7082 wearableBundle.putInt(KEY_CONTENT_ICON, mContentIcon);
7084 if (mContentIconGravity != DEFAULT_CONTENT_ICON_GRAVITY) {
7085 wearableBundle.putInt(KEY_CONTENT_ICON_GRAVITY, mContentIconGravity);
7087 if (mContentActionIndex != UNSET_ACTION_INDEX) {
7088 wearableBundle.putInt(KEY_CONTENT_ACTION_INDEX,
7089 mContentActionIndex);
7091 if (mCustomSizePreset != SIZE_DEFAULT) {
7092 wearableBundle.putInt(KEY_CUSTOM_SIZE_PRESET, mCustomSizePreset);
7094 if (mCustomContentHeight != 0) {
7095 wearableBundle.putInt(KEY_CUSTOM_CONTENT_HEIGHT, mCustomContentHeight);
7097 if (mGravity != DEFAULT_GRAVITY) {
7098 wearableBundle.putInt(KEY_GRAVITY, mGravity);
7100 if (mHintScreenTimeout != 0) {
7101 wearableBundle.putInt(KEY_HINT_SCREEN_TIMEOUT, mHintScreenTimeout);
7103 if (mDismissalId != null) {
7104 wearableBundle.putString(KEY_DISMISSAL_ID, mDismissalId);
7106 if (mBridgeTag != null) {
7107 wearableBundle.putString(KEY_BRIDGE_TAG, mBridgeTag);
7110 builder.getExtras().putBundle(EXTRA_WEARABLE_EXTENSIONS, wearableBundle);
7115 public WearableExtender clone() {
7116 WearableExtender that = new WearableExtender();
7117 that.mActions = new ArrayList<Action>(this.mActions);
7118 that.mFlags = this.mFlags;
7119 that.mDisplayIntent = this.mDisplayIntent;
7120 that.mPages = new ArrayList<Notification>(this.mPages);
7121 that.mBackground = this.mBackground;
7122 that.mContentIcon = this.mContentIcon;
7123 that.mContentIconGravity = this.mContentIconGravity;
7124 that.mContentActionIndex = this.mContentActionIndex;
7125 that.mCustomSizePreset = this.mCustomSizePreset;
7126 that.mCustomContentHeight = this.mCustomContentHeight;
7127 that.mGravity = this.mGravity;
7128 that.mHintScreenTimeout = this.mHintScreenTimeout;
7129 that.mDismissalId = this.mDismissalId;
7130 that.mBridgeTag = this.mBridgeTag;
7135 * Add a wearable action to this notification.
7137 * <p>When wearable actions are added using this method, the set of actions that
7138 * show on a wearable device splits from devices that only show actions added
7139 * using {@link android.app.Notification.Builder#addAction}. This allows for customization
7140 * of which actions display on different devices.
7142 * @param action the action to add to this notification
7143 * @return this object for method chaining
7144 * @see android.app.Notification.Action
7146 public WearableExtender addAction(Action action) {
7147 mActions.add(action);
7152 * Adds wearable actions to this notification.
7154 * <p>When wearable actions are added using this method, the set of actions that
7155 * show on a wearable device splits from devices that only show actions added
7156 * using {@link android.app.Notification.Builder#addAction}. This allows for customization
7157 * of which actions display on different devices.
7159 * @param actions the actions to add to this notification
7160 * @return this object for method chaining
7161 * @see android.app.Notification.Action
7163 public WearableExtender addActions(List<Action> actions) {
7164 mActions.addAll(actions);
7169 * Clear all wearable actions present on this builder.
7170 * @return this object for method chaining.
7173 public WearableExtender clearActions() {
7179 * Get the wearable actions present on this notification.
7181 public List<Action> getActions() {
7186 * Set an intent to launch inside of an activity view when displaying
7187 * this notification. The {@link PendingIntent} provided should be for an activity.
7189 * <pre class="prettyprint">
7190 * Intent displayIntent = new Intent(context, MyDisplayActivity.class);
7191 * PendingIntent displayPendingIntent = PendingIntent.getActivity(context,
7192 * 0, displayIntent, PendingIntent.FLAG_UPDATE_CURRENT);
7193 * Notification notif = new Notification.Builder(context)
7194 * .extend(new Notification.WearableExtender()
7195 * .setDisplayIntent(displayPendingIntent)
7196 * .setCustomSizePreset(Notification.WearableExtender.SIZE_MEDIUM))
7199 * <p>The activity to launch needs to allow embedding, must be exported, and
7200 * should have an empty task affinity. It is also recommended to use the device
7201 * default light theme.
7203 * <p>Example AndroidManifest.xml entry:
7204 * <pre class="prettyprint">
7205 * <activity android:name="com.example.MyDisplayActivity"
7206 * android:exported="true"
7207 * android:allowEmbedded="true"
7208 * android:taskAffinity=""
7209 * android:theme="@android:style/Theme.DeviceDefault.Light" /></pre>
7211 * @param intent the {@link PendingIntent} for an activity
7212 * @return this object for method chaining
7213 * @see android.app.Notification.WearableExtender#getDisplayIntent
7215 public WearableExtender setDisplayIntent(PendingIntent intent) {
7216 mDisplayIntent = intent;
7221 * Get the intent to launch inside of an activity view when displaying this
7222 * notification. This {@code PendingIntent} should be for an activity.
7224 public PendingIntent getDisplayIntent() {
7225 return mDisplayIntent;
7229 * Add an additional page of content to display with this notification. The current
7230 * notification forms the first page, and pages added using this function form
7231 * subsequent pages. This field can be used to separate a notification into multiple
7234 * @param page the notification to add as another page
7235 * @return this object for method chaining
7236 * @see android.app.Notification.WearableExtender#getPages
7238 public WearableExtender addPage(Notification page) {
7244 * Add additional pages of content to display with this notification. The current
7245 * notification forms the first page, and pages added using this function form
7246 * subsequent pages. This field can be used to separate a notification into multiple
7249 * @param pages a list of notifications
7250 * @return this object for method chaining
7251 * @see android.app.Notification.WearableExtender#getPages
7253 public WearableExtender addPages(List<Notification> pages) {
7254 mPages.addAll(pages);
7259 * Clear all additional pages present on this builder.
7260 * @return this object for method chaining.
7263 public WearableExtender clearPages() {
7269 * Get the array of additional pages of content for displaying this notification. The
7270 * current notification forms the first page, and elements within this array form
7271 * subsequent pages. This field can be used to separate a notification into multiple
7273 * @return the pages for this notification
7275 public List<Notification> getPages() {
7280 * Set a background image to be displayed behind the notification content.
7281 * Contrary to the {@link android.app.Notification.BigPictureStyle}, this background
7282 * will work with any notification style.
7284 * @param background the background bitmap
7285 * @return this object for method chaining
7286 * @see android.app.Notification.WearableExtender#getBackground
7288 public WearableExtender setBackground(Bitmap background) {
7289 mBackground = background;
7294 * Get a background image to be displayed behind the notification content.
7295 * Contrary to the {@link android.app.Notification.BigPictureStyle}, this background
7296 * will work with any notification style.
7298 * @return the background image
7299 * @see android.app.Notification.WearableExtender#setBackground
7301 public Bitmap getBackground() {
7306 * Set an icon that goes with the content of this notification.
7308 public WearableExtender setContentIcon(int icon) {
7309 mContentIcon = icon;
7314 * Get an icon that goes with the content of this notification.
7316 public int getContentIcon() {
7317 return mContentIcon;
7321 * Set the gravity that the content icon should have within the notification display.
7322 * Supported values include {@link android.view.Gravity#START} and
7323 * {@link android.view.Gravity#END}. The default value is {@link android.view.Gravity#END}.
7324 * @see #setContentIcon
7326 public WearableExtender setContentIconGravity(int contentIconGravity) {
7327 mContentIconGravity = contentIconGravity;
7332 * Get the gravity that the content icon should have within the notification display.
7333 * Supported values include {@link android.view.Gravity#START} and
7334 * {@link android.view.Gravity#END}. The default value is {@link android.view.Gravity#END}.
7335 * @see #getContentIcon
7337 public int getContentIconGravity() {
7338 return mContentIconGravity;
7342 * Set an action from this notification's actions to be clickable with the content of
7343 * this notification. This action will no longer display separately from the
7344 * notification's content.
7346 * <p>For notifications with multiple pages, child pages can also have content actions
7347 * set, although the list of available actions comes from the main notification and not
7348 * from the child page's notification.
7350 * @param actionIndex The index of the action to hoist onto the current notification page.
7351 * If wearable actions were added to the main notification, this index
7352 * will apply to that list, otherwise it will apply to the regular
7355 public WearableExtender setContentAction(int actionIndex) {
7356 mContentActionIndex = actionIndex;
7361 * Get the index of the notification action, if any, that was specified as being clickable
7362 * with the content of this notification. This action will no longer display separately
7363 * from the notification's content.
7365 * <p>For notifications with multiple pages, child pages can also have content actions
7366 * set, although the list of available actions comes from the main notification and not
7367 * from the child page's notification.
7369 * <p>If wearable specific actions were added to the main notification, this index will
7370 * apply to that list, otherwise it will apply to the regular actions list.
7372 * @return the action index or {@link #UNSET_ACTION_INDEX} if no action was selected.
7374 public int getContentAction() {
7375 return mContentActionIndex;
7379 * Set the gravity that this notification should have within the available viewport space.
7380 * Supported values include {@link android.view.Gravity#TOP},
7381 * {@link android.view.Gravity#CENTER_VERTICAL} and {@link android.view.Gravity#BOTTOM}.
7382 * The default value is {@link android.view.Gravity#BOTTOM}.
7384 public WearableExtender setGravity(int gravity) {
7390 * Get the gravity that this notification should have within the available viewport space.
7391 * Supported values include {@link android.view.Gravity#TOP},
7392 * {@link android.view.Gravity#CENTER_VERTICAL} and {@link android.view.Gravity#BOTTOM}.
7393 * The default value is {@link android.view.Gravity#BOTTOM}.
7395 public int getGravity() {
7400 * Set the custom size preset for the display of this notification out of the available
7401 * presets found in {@link android.app.Notification.WearableExtender}, e.g.
7402 * {@link #SIZE_LARGE}.
7403 * <p>Some custom size presets are only applicable for custom display notifications created
7404 * using {@link android.app.Notification.WearableExtender#setDisplayIntent}. Check the
7405 * documentation for the preset in question. See also
7406 * {@link #setCustomContentHeight} and {@link #getCustomSizePreset}.
7408 public WearableExtender setCustomSizePreset(int sizePreset) {
7409 mCustomSizePreset = sizePreset;
7414 * Get the custom size preset for the display of this notification out of the available
7415 * presets found in {@link android.app.Notification.WearableExtender}, e.g.
7416 * {@link #SIZE_LARGE}.
7417 * <p>Some custom size presets are only applicable for custom display notifications created
7418 * using {@link #setDisplayIntent}. Check the documentation for the preset in question.
7419 * See also {@link #setCustomContentHeight} and {@link #setCustomSizePreset}.
7421 public int getCustomSizePreset() {
7422 return mCustomSizePreset;
7426 * Set the custom height in pixels for the display of this notification's content.
7427 * <p>This option is only available for custom display notifications created
7428 * using {@link android.app.Notification.WearableExtender#setDisplayIntent}. See also
7429 * {@link android.app.Notification.WearableExtender#setCustomSizePreset} and
7430 * {@link #getCustomContentHeight}.
7432 public WearableExtender setCustomContentHeight(int height) {
7433 mCustomContentHeight = height;
7438 * Get the custom height in pixels for the display of this notification's content.
7439 * <p>This option is only available for custom display notifications created
7440 * using {@link #setDisplayIntent}. See also {@link #setCustomSizePreset} and
7441 * {@link #setCustomContentHeight}.
7443 public int getCustomContentHeight() {
7444 return mCustomContentHeight;
7448 * Set whether the scrolling position for the contents of this notification should start
7449 * at the bottom of the contents instead of the top when the contents are too long to
7450 * display within the screen. Default is false (start scroll at the top).
7452 public WearableExtender setStartScrollBottom(boolean startScrollBottom) {
7453 setFlag(FLAG_START_SCROLL_BOTTOM, startScrollBottom);
7458 * Get whether the scrolling position for the contents of this notification should start
7459 * at the bottom of the contents instead of the top when the contents are too long to
7460 * display within the screen. Default is false (start scroll at the top).
7462 public boolean getStartScrollBottom() {
7463 return (mFlags & FLAG_START_SCROLL_BOTTOM) != 0;
7467 * Set whether the content intent is available when the wearable device is not connected
7468 * to a companion device. The user can still trigger this intent when the wearable device
7469 * is offline, but a visual hint will indicate that the content intent may not be available.
7472 public WearableExtender setContentIntentAvailableOffline(
7473 boolean contentIntentAvailableOffline) {
7474 setFlag(FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE, contentIntentAvailableOffline);
7479 * Get whether the content intent is available when the wearable device is not connected
7480 * to a companion device. The user can still trigger this intent when the wearable device
7481 * is offline, but a visual hint will indicate that the content intent may not be available.
7484 public boolean getContentIntentAvailableOffline() {
7485 return (mFlags & FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE) != 0;
7489 * Set a hint that this notification's icon should not be displayed.
7490 * @param hintHideIcon {@code true} to hide the icon, {@code false} otherwise.
7491 * @return this object for method chaining
7493 public WearableExtender setHintHideIcon(boolean hintHideIcon) {
7494 setFlag(FLAG_HINT_HIDE_ICON, hintHideIcon);
7499 * Get a hint that this notification's icon should not be displayed.
7500 * @return {@code true} if this icon should not be displayed, false otherwise.
7501 * The default value is {@code false} if this was never set.
7503 public boolean getHintHideIcon() {
7504 return (mFlags & FLAG_HINT_HIDE_ICON) != 0;
7508 * Set a visual hint that only the background image of this notification should be
7509 * displayed, and other semantic content should be hidden. This hint is only applicable
7510 * to sub-pages added using {@link #addPage}.
7512 public WearableExtender setHintShowBackgroundOnly(boolean hintShowBackgroundOnly) {
7513 setFlag(FLAG_HINT_SHOW_BACKGROUND_ONLY, hintShowBackgroundOnly);
7518 * Get a visual hint that only the background image of this notification should be
7519 * displayed, and other semantic content should be hidden. This hint is only applicable
7520 * to sub-pages added using {@link android.app.Notification.WearableExtender#addPage}.
7522 public boolean getHintShowBackgroundOnly() {
7523 return (mFlags & FLAG_HINT_SHOW_BACKGROUND_ONLY) != 0;
7527 * Set a hint that this notification's background should not be clipped if possible,
7528 * and should instead be resized to fully display on the screen, retaining the aspect
7529 * ratio of the image. This can be useful for images like barcodes or qr codes.
7530 * @param hintAvoidBackgroundClipping {@code true} to avoid clipping if possible.
7531 * @return this object for method chaining
7533 public WearableExtender setHintAvoidBackgroundClipping(
7534 boolean hintAvoidBackgroundClipping) {
7535 setFlag(FLAG_HINT_AVOID_BACKGROUND_CLIPPING, hintAvoidBackgroundClipping);
7540 * Get a hint that this notification's background should not be clipped if possible,
7541 * and should instead be resized to fully display on the screen, retaining the aspect
7542 * ratio of the image. This can be useful for images like barcodes or qr codes.
7543 * @return {@code true} if it's ok if the background is clipped on the screen, false
7544 * otherwise. The default value is {@code false} if this was never set.
7546 public boolean getHintAvoidBackgroundClipping() {
7547 return (mFlags & FLAG_HINT_AVOID_BACKGROUND_CLIPPING) != 0;
7551 * Set a hint that the screen should remain on for at least this duration when
7552 * this notification is displayed on the screen.
7553 * @param timeout The requested screen timeout in milliseconds. Can also be either
7554 * {@link #SCREEN_TIMEOUT_SHORT} or {@link #SCREEN_TIMEOUT_LONG}.
7555 * @return this object for method chaining
7557 public WearableExtender setHintScreenTimeout(int timeout) {
7558 mHintScreenTimeout = timeout;
7563 * Get the duration, in milliseconds, that the screen should remain on for
7564 * when this notification is displayed.
7565 * @return the duration in milliseconds if > 0, or either one of the sentinel values
7566 * {@link #SCREEN_TIMEOUT_SHORT} or {@link #SCREEN_TIMEOUT_LONG}.
7568 public int getHintScreenTimeout() {
7569 return mHintScreenTimeout;
7573 * Set a hint that this notification's {@link BigPictureStyle} (if present) should be
7574 * converted to low-bit and displayed in ambient mode, especially useful for barcodes and
7575 * qr codes, as well as other simple black-and-white tickets.
7576 * @param hintAmbientBigPicture {@code true} to enable converstion and ambient.
7577 * @return this object for method chaining
7579 public WearableExtender setHintAmbientBigPicture(boolean hintAmbientBigPicture) {
7580 setFlag(FLAG_BIG_PICTURE_AMBIENT, hintAmbientBigPicture);
7585 * Get a hint that this notification's {@link BigPictureStyle} (if present) should be
7586 * converted to low-bit and displayed in ambient mode, especially useful for barcodes and
7587 * qr codes, as well as other simple black-and-white tickets.
7588 * @return {@code true} if it should be displayed in ambient, false otherwise
7589 * otherwise. The default value is {@code false} if this was never set.
7591 public boolean getHintAmbientBigPicture() {
7592 return (mFlags & FLAG_BIG_PICTURE_AMBIENT) != 0;
7596 * Set a hint that this notification's content intent will launch an {@link Activity}
7597 * directly, telling the platform that it can generate the appropriate transitions.
7598 * @param hintContentIntentLaunchesActivity {@code true} if the content intent will launch
7599 * an activity and transitions should be generated, false otherwise.
7600 * @return this object for method chaining
7602 public WearableExtender setHintContentIntentLaunchesActivity(
7603 boolean hintContentIntentLaunchesActivity) {
7604 setFlag(FLAG_HINT_CONTENT_INTENT_LAUNCHES_ACTIVITY, hintContentIntentLaunchesActivity);
7609 * Get a hint that this notification's content intent will launch an {@link Activity}
7610 * directly, telling the platform that it can generate the appropriate transitions
7611 * @return {@code true} if the content intent will launch an activity and transitions should
7612 * be generated, false otherwise. The default value is {@code false} if this was never set.
7614 public boolean getHintContentIntentLaunchesActivity() {
7615 return (mFlags & FLAG_HINT_CONTENT_INTENT_LAUNCHES_ACTIVITY) != 0;
7619 * Sets the dismissal id for this notification. If a notification is posted with a
7620 * dismissal id, then when that notification is canceled, notifications on other wearables
7621 * and the paired Android phone having that same dismissal id will also be canceled. See
7622 * <a href="{@docRoot}wear/notifications/index.html">Adding Wearable Features to
7623 * Notifications</a> for more information.
7624 * @param dismissalId the dismissal id of the notification.
7625 * @return this object for method chaining
7627 public WearableExtender setDismissalId(String dismissalId) {
7628 mDismissalId = dismissalId;
7633 * Returns the dismissal id of the notification.
7634 * @return the dismissal id of the notification or null if it has not been set.
7636 public String getDismissalId() {
7637 return mDismissalId;
7641 * Sets a bridge tag for this notification. A bridge tag can be set for notifications
7642 * posted from a phone to provide finer-grained control on what notifications are bridged
7643 * to wearables. See <a href="{@docRoot}wear/notifications/index.html">Adding Wearable
7644 * Features to Notifications</a> for more information.
7645 * @param bridgeTag the bridge tag of the notification.
7646 * @return this object for method chaining
7648 public WearableExtender setBridgeTag(String bridgeTag) {
7649 mBridgeTag = bridgeTag;
7654 * Returns the bridge tag of the notification.
7655 * @return the bridge tag or null if not present.
7657 public String getBridgeTag() {
7661 private void setFlag(int mask, boolean value) {
7671 * <p>Helper class to add Android Auto extensions to notifications. To create a notification
7672 * with car extensions:
7675 * <li>Create an {@link Notification.Builder}, setting any desired
7677 * <li>Create a {@link CarExtender}.
7678 * <li>Set car-specific properties using the {@code add} and {@code set} methods of
7679 * {@link CarExtender}.
7680 * <li>Call {@link Notification.Builder#extend(Notification.Extender)}
7681 * to apply the extensions to a notification.
7684 * <pre class="prettyprint">
7685 * Notification notification = new Notification.Builder(context)
7687 * .extend(new CarExtender()
7692 * <p>Car extensions can be accessed on an existing notification by using the
7693 * {@code CarExtender(Notification)} constructor, and then using the {@code get} methods
7696 public static final class CarExtender implements Extender {
7697 private static final String TAG = "CarExtender";
7699 private static final String EXTRA_CAR_EXTENDER = "android.car.EXTENSIONS";
7700 private static final String EXTRA_LARGE_ICON = "large_icon";
7701 private static final String EXTRA_CONVERSATION = "car_conversation";
7702 private static final String EXTRA_COLOR = "app_color";
7704 private Bitmap mLargeIcon;
7705 private UnreadConversation mUnreadConversation;
7706 private int mColor = Notification.COLOR_DEFAULT;
7709 * Create a {@link CarExtender} with default options.
7711 public CarExtender() {
7715 * Create a {@link CarExtender} from the CarExtender options of an existing Notification.
7717 * @param notif The notification from which to copy options.
7719 public CarExtender(Notification notif) {
7720 Bundle carBundle = notif.extras == null ?
7721 null : notif.extras.getBundle(EXTRA_CAR_EXTENDER);
7722 if (carBundle != null) {
7723 mLargeIcon = carBundle.getParcelable(EXTRA_LARGE_ICON);
7724 mColor = carBundle.getInt(EXTRA_COLOR, Notification.COLOR_DEFAULT);
7726 Bundle b = carBundle.getBundle(EXTRA_CONVERSATION);
7727 mUnreadConversation = UnreadConversation.getUnreadConversationFromBundle(b);
7732 * Apply car extensions to a notification that is being built. This is typically called by
7733 * the {@link Notification.Builder#extend(Notification.Extender)}
7734 * method of {@link Notification.Builder}.
7737 public Notification.Builder extend(Notification.Builder builder) {
7738 Bundle carExtensions = new Bundle();
7740 if (mLargeIcon != null) {
7741 carExtensions.putParcelable(EXTRA_LARGE_ICON, mLargeIcon);
7743 if (mColor != Notification.COLOR_DEFAULT) {
7744 carExtensions.putInt(EXTRA_COLOR, mColor);
7747 if (mUnreadConversation != null) {
7748 Bundle b = mUnreadConversation.getBundleForUnreadConversation();
7749 carExtensions.putBundle(EXTRA_CONVERSATION, b);
7752 builder.getExtras().putBundle(EXTRA_CAR_EXTENDER, carExtensions);
7757 * Sets the accent color to use when Android Auto presents the notification.
7759 * Android Auto uses the color set with {@link Notification.Builder#setColor(int)}
7760 * to accent the displayed notification. However, not all colors are acceptable in an
7761 * automotive setting. This method can be used to override the color provided in the
7762 * notification in such a situation.
7764 public CarExtender setColor(@ColorInt int color) {
7770 * Gets the accent color.
7775 public int getColor() {
7780 * Sets the large icon of the car notification.
7782 * If no large icon is set in the extender, Android Auto will display the icon
7783 * specified by {@link Notification.Builder#setLargeIcon(android.graphics.Bitmap)}
7785 * @param largeIcon The large icon to use in the car notification.
7786 * @return This object for method chaining.
7788 public CarExtender setLargeIcon(Bitmap largeIcon) {
7789 mLargeIcon = largeIcon;
7794 * Gets the large icon used in this car notification, or null if no icon has been set.
7796 * @return The large icon for the car notification.
7797 * @see CarExtender#setLargeIcon
7799 public Bitmap getLargeIcon() {
7804 * Sets the unread conversation in a message notification.
7806 * @param unreadConversation The unread part of the conversation this notification conveys.
7807 * @return This object for method chaining.
7809 public CarExtender setUnreadConversation(UnreadConversation unreadConversation) {
7810 mUnreadConversation = unreadConversation;
7815 * Returns the unread conversation conveyed by this notification.
7816 * @see #setUnreadConversation(UnreadConversation)
7818 public UnreadConversation getUnreadConversation() {
7819 return mUnreadConversation;
7823 * A class which holds the unread messages from a conversation.
7825 public static class UnreadConversation {
7826 private static final String KEY_AUTHOR = "author";
7827 private static final String KEY_TEXT = "text";
7828 private static final String KEY_MESSAGES = "messages";
7829 private static final String KEY_REMOTE_INPUT = "remote_input";
7830 private static final String KEY_ON_REPLY = "on_reply";
7831 private static final String KEY_ON_READ = "on_read";
7832 private static final String KEY_PARTICIPANTS = "participants";
7833 private static final String KEY_TIMESTAMP = "timestamp";
7835 private final String[] mMessages;
7836 private final RemoteInput mRemoteInput;
7837 private final PendingIntent mReplyPendingIntent;
7838 private final PendingIntent mReadPendingIntent;
7839 private final String[] mParticipants;
7840 private final long mLatestTimestamp;
7842 UnreadConversation(String[] messages, RemoteInput remoteInput,
7843 PendingIntent replyPendingIntent, PendingIntent readPendingIntent,
7844 String[] participants, long latestTimestamp) {
7845 mMessages = messages;
7846 mRemoteInput = remoteInput;
7847 mReadPendingIntent = readPendingIntent;
7848 mReplyPendingIntent = replyPendingIntent;
7849 mParticipants = participants;
7850 mLatestTimestamp = latestTimestamp;
7854 * Gets the list of messages conveyed by this notification.
7856 public String[] getMessages() {
7861 * Gets the remote input that will be used to convey the response to a message list, or
7862 * null if no such remote input exists.
7864 public RemoteInput getRemoteInput() {
7865 return mRemoteInput;
7869 * Gets the pending intent that will be triggered when the user replies to this
7872 public PendingIntent getReplyPendingIntent() {
7873 return mReplyPendingIntent;
7877 * Gets the pending intent that Android Auto will send after it reads aloud all messages
7878 * in this object's message list.
7880 public PendingIntent getReadPendingIntent() {
7881 return mReadPendingIntent;
7885 * Gets the participants in the conversation.
7887 public String[] getParticipants() {
7888 return mParticipants;
7892 * Gets the firs participant in the conversation.
7894 public String getParticipant() {
7895 return mParticipants.length > 0 ? mParticipants[0] : null;
7899 * Gets the timestamp of the conversation.
7901 public long getLatestTimestamp() {
7902 return mLatestTimestamp;
7905 Bundle getBundleForUnreadConversation() {
7906 Bundle b = new Bundle();
7907 String author = null;
7908 if (mParticipants != null && mParticipants.length > 1) {
7909 author = mParticipants[0];
7911 Parcelable[] messages = new Parcelable[mMessages.length];
7912 for (int i = 0; i < messages.length; i++) {
7913 Bundle m = new Bundle();
7914 m.putString(KEY_TEXT, mMessages[i]);
7915 m.putString(KEY_AUTHOR, author);
7918 b.putParcelableArray(KEY_MESSAGES, messages);
7919 if (mRemoteInput != null) {
7920 b.putParcelable(KEY_REMOTE_INPUT, mRemoteInput);
7922 b.putParcelable(KEY_ON_REPLY, mReplyPendingIntent);
7923 b.putParcelable(KEY_ON_READ, mReadPendingIntent);
7924 b.putStringArray(KEY_PARTICIPANTS, mParticipants);
7925 b.putLong(KEY_TIMESTAMP, mLatestTimestamp);
7929 static UnreadConversation getUnreadConversationFromBundle(Bundle b) {
7933 Parcelable[] parcelableMessages = b.getParcelableArray(KEY_MESSAGES);
7934 String[] messages = null;
7935 if (parcelableMessages != null) {
7936 String[] tmp = new String[parcelableMessages.length];
7937 boolean success = true;
7938 for (int i = 0; i < tmp.length; i++) {
7939 if (!(parcelableMessages[i] instanceof Bundle)) {
7943 tmp[i] = ((Bundle) parcelableMessages[i]).getString(KEY_TEXT);
7944 if (tmp[i] == null) {
7956 PendingIntent onRead = b.getParcelable(KEY_ON_READ);
7957 PendingIntent onReply = b.getParcelable(KEY_ON_REPLY);
7959 RemoteInput remoteInput = b.getParcelable(KEY_REMOTE_INPUT);
7961 String[] participants = b.getStringArray(KEY_PARTICIPANTS);
7962 if (participants == null || participants.length != 1) {
7966 return new UnreadConversation(messages,
7970 participants, b.getLong(KEY_TIMESTAMP));
7975 * Builder class for {@link CarExtender.UnreadConversation} objects.
7977 public static class Builder {
7978 private final List<String> mMessages = new ArrayList<String>();
7979 private final String mParticipant;
7980 private RemoteInput mRemoteInput;
7981 private PendingIntent mReadPendingIntent;
7982 private PendingIntent mReplyPendingIntent;
7983 private long mLatestTimestamp;
7986 * Constructs a new builder for {@link CarExtender.UnreadConversation}.
7988 * @param name The name of the other participant in the conversation.
7990 public Builder(String name) {
7991 mParticipant = name;
7995 * Appends a new unread message to the list of messages for this conversation.
7997 * The messages should be added from oldest to newest.
7999 * @param message The text of the new unread message.
8000 * @return This object for method chaining.
8002 public Builder addMessage(String message) {
8003 mMessages.add(message);
8008 * Sets the pending intent and remote input which will convey the reply to this
8011 * @param pendingIntent The pending intent which will be triggered on a reply.
8012 * @param remoteInput The remote input parcelable which will carry the reply.
8013 * @return This object for method chaining.
8015 * @see CarExtender.UnreadConversation#getRemoteInput
8016 * @see CarExtender.UnreadConversation#getReplyPendingIntent
8018 public Builder setReplyAction(
8019 PendingIntent pendingIntent, RemoteInput remoteInput) {
8020 mRemoteInput = remoteInput;
8021 mReplyPendingIntent = pendingIntent;
8027 * Sets the pending intent that will be sent once the messages in this notification
8030 * @param pendingIntent The pending intent to use.
8031 * @return This object for method chaining.
8033 public Builder setReadPendingIntent(PendingIntent pendingIntent) {
8034 mReadPendingIntent = pendingIntent;
8039 * Sets the timestamp of the most recent message in an unread conversation.
8041 * If a messaging notification has been posted by your application and has not
8042 * yet been cancelled, posting a later notification with the same id and tag
8043 * but without a newer timestamp may result in Android Auto not displaying a
8044 * heads up notification for the later notification.
8046 * @param timestamp The timestamp of the most recent message in the conversation.
8047 * @return This object for method chaining.
8049 public Builder setLatestTimestamp(long timestamp) {
8050 mLatestTimestamp = timestamp;
8055 * Builds a new unread conversation object.
8057 * @return The new unread conversation object.
8059 public UnreadConversation build() {
8060 String[] messages = mMessages.toArray(new String[mMessages.size()]);
8061 String[] participants = { mParticipant };
8062 return new UnreadConversation(messages, mRemoteInput, mReplyPendingIntent,
8063 mReadPendingIntent, participants, mLatestTimestamp);
8069 * <p>Helper class to add Android TV extensions to notifications. To create a notification
8070 * with a TV extension:
8073 * <li>Create an {@link Notification.Builder}, setting any desired properties.
8074 * <li>Create a {@link TvExtender}.
8075 * <li>Set TV-specific properties using the {@code set} methods of
8076 * {@link TvExtender}.
8077 * <li>Call {@link Notification.Builder#extend(Notification.Extender)}
8078 * to apply the extension to a notification.
8081 * <pre class="prettyprint">
8082 * Notification notification = new Notification.Builder(context)
8084 * .extend(new TvExtender()
8089 * <p>TV extensions can be accessed on an existing notification by using the
8090 * {@code TvExtender(Notification)} constructor, and then using the {@code get} methods
8096 public static final class TvExtender implements Extender {
8097 private static final String TAG = "TvExtender";
8099 private static final String EXTRA_TV_EXTENDER = "android.tv.EXTENSIONS";
8100 private static final String EXTRA_FLAGS = "flags";
8101 private static final String EXTRA_CONTENT_INTENT = "content_intent";
8102 private static final String EXTRA_DELETE_INTENT = "delete_intent";
8103 private static final String EXTRA_CHANNEL_ID = "channel_id";
8105 // Flags bitwise-ored to mFlags
8106 private static final int FLAG_AVAILABLE_ON_TV = 0x1;
8109 private String mChannelId;
8110 private PendingIntent mContentIntent;
8111 private PendingIntent mDeleteIntent;
8114 * Create a {@link TvExtender} with default options.
8116 public TvExtender() {
8117 mFlags = FLAG_AVAILABLE_ON_TV;
8121 * Create a {@link TvExtender} from the TvExtender options of an existing Notification.
8123 * @param notif The notification from which to copy options.
8125 public TvExtender(Notification notif) {
8126 Bundle bundle = notif.extras == null ?
8127 null : notif.extras.getBundle(EXTRA_TV_EXTENDER);
8128 if (bundle != null) {
8129 mFlags = bundle.getInt(EXTRA_FLAGS);
8130 mChannelId = bundle.getString(EXTRA_CHANNEL_ID);
8131 mContentIntent = bundle.getParcelable(EXTRA_CONTENT_INTENT);
8132 mDeleteIntent = bundle.getParcelable(EXTRA_DELETE_INTENT);
8137 * Apply a TV extension to a notification that is being built. This is typically called by
8138 * the {@link Notification.Builder#extend(Notification.Extender)}
8139 * method of {@link Notification.Builder}.
8142 public Notification.Builder extend(Notification.Builder builder) {
8143 Bundle bundle = new Bundle();
8145 bundle.putInt(EXTRA_FLAGS, mFlags);
8146 bundle.putString(EXTRA_CHANNEL_ID, mChannelId);
8147 if (mContentIntent != null) {
8148 bundle.putParcelable(EXTRA_CONTENT_INTENT, mContentIntent);
8151 if (mDeleteIntent != null) {
8152 bundle.putParcelable(EXTRA_DELETE_INTENT, mDeleteIntent);
8155 builder.getExtras().putBundle(EXTRA_TV_EXTENDER, bundle);
8160 * Returns true if this notification should be shown on TV. This method return true
8161 * if the notification was extended with a TvExtender.
8163 public boolean isAvailableOnTv() {
8164 return (mFlags & FLAG_AVAILABLE_ON_TV) != 0;
8168 * Specifies the channel the notification should be delivered on when shown on TV.
8169 * It can be different from the channel that the notification is delivered to when
8170 * posting on a non-TV device.
8172 public TvExtender setChannel(String channelId) {
8173 mChannelId = channelId;
8178 * Specifies the channel the notification should be delivered on when shown on TV.
8179 * It can be different from the channel that the notification is delivered to when
8180 * posting on a non-TV device.
8182 public TvExtender setChannelId(String channelId) {
8183 mChannelId = channelId;
8189 public String getChannel() {
8194 * Returns the id of the channel this notification posts to on TV.
8196 public String getChannelId() {
8201 * Supplies a {@link PendingIntent} to be sent when the notification is selected on TV.
8202 * If provided, it is used instead of the content intent specified
8203 * at the level of Notification.
8205 public TvExtender setContentIntent(PendingIntent intent) {
8206 mContentIntent = intent;
8211 * Returns the TV-specific content intent. If this method returns null, the
8212 * main content intent on the notification should be used.
8214 * @see {@link Notification#contentIntent}
8216 public PendingIntent getContentIntent() {
8217 return mContentIntent;
8221 * Supplies a {@link PendingIntent} to send when the notification is cleared explicitly
8222 * by the user on TV. If provided, it is used instead of the delete intent specified
8223 * at the level of Notification.
8225 public TvExtender setDeleteIntent(PendingIntent intent) {
8226 mDeleteIntent = intent;
8231 * Returns the TV-specific delete intent. If this method returns null, the
8232 * main delete intent on the notification should be used.
8234 * @see {@link Notification#deleteIntent}
8236 public PendingIntent getDeleteIntent() {
8237 return mDeleteIntent;
8242 * Get an array of Notification objects from a parcelable array bundle field.
8243 * Update the bundle to have a typed array so fetches in the future don't need
8244 * to do an array copy.
8246 private static Notification[] getNotificationArrayFromBundle(Bundle bundle, String key) {
8247 Parcelable[] array = bundle.getParcelableArray(key);
8248 if (array instanceof Notification[] || array == null) {
8249 return (Notification[]) array;
8251 Notification[] typedArray = Arrays.copyOf(array, array.length,
8252 Notification[].class);
8253 bundle.putParcelableArray(key, typedArray);
8257 private static class BuilderRemoteViews extends RemoteViews {
8258 public BuilderRemoteViews(Parcel parcel) {
8262 public BuilderRemoteViews(ApplicationInfo appInfo, int layoutId) {
8263 super(appInfo, layoutId);
8267 public BuilderRemoteViews clone() {
8268 Parcel p = Parcel.obtain();
8269 writeToParcel(p, 0);
8270 p.setDataPosition(0);
8271 BuilderRemoteViews brv = new BuilderRemoteViews(p);
8277 private static class StandardTemplateParams {
8278 boolean hasProgress = true;
8279 boolean ambient = false;
8283 final StandardTemplateParams reset() {
8291 final StandardTemplateParams hasProgress(boolean hasProgress) {
8292 this.hasProgress = hasProgress;
8296 final StandardTemplateParams title(CharSequence title) {
8301 final StandardTemplateParams text(CharSequence text) {
8306 final StandardTemplateParams ambient(boolean ambient) {
8307 Preconditions.checkState(title == null && text == null, "must set ambient before text");
8308 this.ambient = ambient;
8312 final StandardTemplateParams fillTextsFrom(Builder b) {
8313 Bundle extras = b.mN.extras;
8314 title = b.processLegacyText(extras.getCharSequence(EXTRA_TITLE), ambient);
8315 text = b.processLegacyText(extras.getCharSequence(EXTRA_TEXT), ambient);