/* * Copyright (C) 2007 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.app; import static android.annotation.Dimension.DP; import static android.graphics.drawable.Icon.TYPE_BITMAP; import static com.android.internal.util.ContrastColorUtil.satisfiesTextContrast; import android.annotation.ColorInt; import android.annotation.DimenRes; import android.annotation.Dimension; import android.annotation.DrawableRes; import android.annotation.IdRes; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; import android.annotation.SystemApi; import android.annotation.UnsupportedAppUsage; import android.content.Context; import android.content.Intent; import android.content.LocusId; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ShortcutInfo; import android.content.res.ColorStateList; import android.content.res.Configuration; import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.PorterDuff; import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; import android.media.AudioAttributes; import android.media.AudioManager; import android.media.PlayerBase; import android.media.session.MediaSession; import android.net.Uri; import android.os.BadParcelableException; import android.os.Build; import android.os.Bundle; import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; import android.os.SystemClock; import android.os.SystemProperties; import android.os.UserHandle; import android.text.BidiFormatter; import android.text.SpannableStringBuilder; import android.text.Spanned; import android.text.TextUtils; import android.text.style.AbsoluteSizeSpan; import android.text.style.CharacterStyle; import android.text.style.ForegroundColorSpan; import android.text.style.RelativeSizeSpan; import android.text.style.TextAppearanceSpan; import android.util.ArraySet; import android.util.Log; import android.util.Pair; import android.util.SparseArray; import android.util.proto.ProtoOutputStream; import android.view.Gravity; import android.view.NotificationHeaderView; import android.view.View; import android.view.ViewGroup; import android.view.contentcapture.ContentCaptureContext; import android.widget.ProgressBar; import android.widget.RemoteViews; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; import com.android.internal.util.ContrastColorUtil; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.reflect.Constructor; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.Set; import java.util.function.Consumer; /** * A class that represents how a persistent notification is to be presented to * the user using the {@link android.app.NotificationManager}. * *
The {@link Notification.Builder Notification.Builder} has been added to make it * easier to construct Notifications.
* *For a guide to creating notifications, read the * Status Bar Notifications * developer guide.
** A notification that is noisy is more likely to be presented as a heads-up notification. *
* * @see #defaults */ public static final int DEFAULT_SOUND = 1; /** * Use the default notification vibrate. This will ignore any given * {@link #vibrate}. Using phone vibration requires the * {@link android.Manifest.permission#VIBRATE VIBRATE} permission. * ** A notification that vibrates is more likely to be presented as a heads-up notification. *
* * @see #defaults */ public static final int DEFAULT_VIBRATE = 2; /** * Use the default notification lights. This will ignore the * {@link #FLAG_SHOW_LIGHTS} bit, and {@link #ledARGB}, {@link #ledOffMS}, or * {@link #ledOnMS}. * * @see #defaults */ public static final int DEFAULT_LIGHTS = 4; /** * Maximum length of CharSequences accepted by Builder and friends. * ** Avoids spamming the system with overly large strings such as full e-mails. */ private static final int MAX_CHARSEQUENCE_LENGTH = 5 * 1024; /** * Maximum entries of reply text that are accepted by Builder and friends. */ private static final int MAX_REPLY_HISTORY = 5; /** * Maximum number of (generic) action buttons in a notification (contextual action buttons are * handled separately). * @hide */ public static final int MAX_ACTION_BUTTONS = 3; /** * If the notification contained an unsent draft for a RemoteInput when the user clicked on it, * we're adding the draft as a String extra to the {@link #contentIntent} using this key. * *
Apps may use this extra to prepopulate text fields in the app, where the user usually * sends messages.
*/ public static final String EXTRA_REMOTE_INPUT_DRAFT = "android.remoteInputDraft"; /** * A timestamp related to this notification, in milliseconds since the epoch. * * Default value: {@link System#currentTimeMillis() Now}. * * Choose a timestamp that will be most relevant to the user. For most finite events, this * corresponds to the time the event happened (or will happen, in the case of events that have * yet to occur but about which the user is being informed). Indefinite events should be * timestamped according to when the activity began. * * Some examples: * ** The system UI may choose to display a heads-up notification, instead of * launching this intent, while the user is using the device. *
* * @see Notification.Builder#setFullScreenIntent */ public PendingIntent fullScreenIntent; /** * Text that summarizes this notification for accessibility services. * * As of the L release, this text is no longer shown on screen, but it is still useful to * accessibility services (where it serves as an audible announcement of the notification's * appearance). * * @see #tickerView */ public CharSequence tickerText; /** * Formerly, a view showing the {@link #tickerText}. * * No longer displayed in the status bar as of API 21. */ @Deprecated public RemoteViews tickerView; /** * The view that will represent this notification in the notification list (which is pulled * down from the status bar). * * As of N, this field may be null. The notification view is determined by the inputs * to {@link Notification.Builder}; a custom RemoteViews can optionally be * supplied with {@link Notification.Builder#setCustomContentView(RemoteViews)}. */ @Deprecated public RemoteViews contentView; /** * A large-format version of {@link #contentView}, giving the Notification an * opportunity to show more detail. The system UI may choose to show this * instead of the normal content view at its discretion. * * As of N, this field may be null. The expanded notification view is determined by the * inputs to {@link Notification.Builder}; a custom RemoteViews can optionally be * supplied with {@link Notification.Builder#setCustomBigContentView(RemoteViews)}. */ @Deprecated public RemoteViews bigContentView; /** * A medium-format version of {@link #contentView}, providing the Notification an * opportunity to add action buttons to contentView. At its discretion, the system UI may * choose to show this as a heads-up notification, which will pop up so the user can see * it without leaving their current activity. * * As of N, this field may be null. The heads-up notification view is determined by the * inputs to {@link Notification.Builder}; a custom RemoteViews can optionally be * supplied with {@link Notification.Builder#setCustomHeadsUpContentView(RemoteViews)}. */ @Deprecated public RemoteViews headsUpContentView; private boolean mUsesStandardHeader; private static final ArraySet* A notification that is noisy is more likely to be presented as a heads-up notification. *
* ** To play the default notification sound, see {@link #defaults}. *
* @deprecated use {@link NotificationChannel#getSound()}. */ @Deprecated public Uri sound; /** * Use this constant as the value for audioStreamType to request that * the default stream type for notifications be used. Currently the * default stream type is {@link AudioManager#STREAM_NOTIFICATION}. * * @deprecated Use {@link NotificationChannel#getAudioAttributes()} instead. */ @Deprecated public static final int STREAM_DEFAULT = -1; /** * The audio stream type to use when playing the sound. * Should be one of the STREAM_ constants from * {@link android.media.AudioManager}. * * @deprecated Use {@link #audioAttributes} instead. */ @Deprecated public int audioStreamType = STREAM_DEFAULT; /** * The default value of {@link #audioAttributes}. */ public static final AudioAttributes AUDIO_ATTRIBUTES_DEFAULT = new AudioAttributes.Builder() .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) .setUsage(AudioAttributes.USAGE_NOTIFICATION) .build(); /** * The {@link AudioAttributes audio attributes} to use when playing the sound. * * @deprecated use {@link NotificationChannel#getAudioAttributes()} instead. */ @Deprecated public AudioAttributes audioAttributes = AUDIO_ATTRIBUTES_DEFAULT; /** * The pattern with which to vibrate. * ** To vibrate the default pattern, see {@link #defaults}. *
* * @see android.os.Vibrator#vibrate(long[],int) * @deprecated use {@link NotificationChannel#getVibrationPattern()}. */ @Deprecated public long[] vibrate; /** * The color of the led. The hardware will do its best approximation. * * @see #FLAG_SHOW_LIGHTS * @see #flags * @deprecated use {@link NotificationChannel#shouldShowLights()}. */ @ColorInt @Deprecated public int ledARGB; /** * The number of milliseconds for the LED to be on while it's flashing. * The hardware will do its best approximation. * * @see #FLAG_SHOW_LIGHTS * @see #flags * @deprecated use {@link NotificationChannel#shouldShowLights()}. */ @Deprecated public int ledOnMS; /** * The number of milliseconds for the LED to be off while it's flashing. * The hardware will do its best approximation. * * @see #FLAG_SHOW_LIGHTS * @see #flags * * @deprecated use {@link NotificationChannel#shouldShowLights()}. */ @Deprecated public int ledOffMS; /** * Specifies which values should be taken from the defaults. ** To set, OR the desired from {@link #DEFAULT_SOUND}, * {@link #DEFAULT_VIBRATE}, {@link #DEFAULT_LIGHTS}. For all default * values, use {@link #DEFAULT_ALL}. *
* * @deprecated use {@link NotificationChannel#getSound()} and * {@link NotificationChannel#shouldShowLights()} and * {@link NotificationChannel#shouldVibrate()}. */ @Deprecated public int defaults; /** * Bit to be bitwise-ored into the {@link #flags} field that should be * set if you want the LED on for this notification. ** Since hardware varies, you are not guaranteed that any of the values * you pass are honored exactly. Use the system defaults if possible * because they will be set to values that work on any given hardware. *
* The alpha channel must be set for forward compatibility. * * @deprecated use {@link NotificationChannel#shouldShowLights()}. */ @Deprecated public static final int FLAG_SHOW_LIGHTS = 0x00000001; /** * Bit to be bitwise-ored into the {@link #flags} field that should be * set if this notification is in reference to something that is ongoing, * like a phone call. It should not be set if this notification is in * reference to something that happened at a particular point in time, * like a missed phone call. */ public static final int FLAG_ONGOING_EVENT = 0x00000002; /** * Bit to be bitwise-ored into the {@link #flags} field that if set, * the audio will be repeated until the notification is * cancelled or the notification window is opened. */ public static final int FLAG_INSISTENT = 0x00000004; /** * Bit to be bitwise-ored into the {@link #flags} field that should be * set if you would only like the sound, vibrate and ticker to be played * if the notification was not already showing. */ public static final int FLAG_ONLY_ALERT_ONCE = 0x00000008; /** * Bit to be bitwise-ored into the {@link #flags} field that should be * set if the notification should be canceled when it is clicked by the * user. */ public static final int FLAG_AUTO_CANCEL = 0x00000010; /** * Bit to be bitwise-ored into the {@link #flags} field that should be * set if the notification should not be canceled when the user clicks * the Clear all button. */ public static final int FLAG_NO_CLEAR = 0x00000020; /** * Bit to be bitwise-ored into the {@link #flags} field that should be * set if this notification represents a currently running service. This * will normally be set for you by {@link Service#startForeground}. */ public static final int FLAG_FOREGROUND_SERVICE = 0x00000040; /** * Obsolete flag indicating high-priority notifications; use the priority field instead. * * @deprecated Use {@link #priority} with a positive value. */ @Deprecated public static final int FLAG_HIGH_PRIORITY = 0x00000080; /** * Bit to be bitswise-ored into the {@link #flags} field that should be * set if this notification is relevant to the current device only * and it is not recommended that it bridge to other devices. */ public static final int FLAG_LOCAL_ONLY = 0x00000100; /** * Bit to be bitswise-ored into the {@link #flags} field that should be * set if this notification is the group summary for a group of notifications. * Grouped notifications may display in a cluster or stack on devices which * support such rendering. Requires a group key also be set using {@link Builder#setGroup}. */ public static final int FLAG_GROUP_SUMMARY = 0x00000200; /** * Bit to be bitswise-ored into the {@link #flags} field that should be * set if this notification is the group summary for an auto-group of notifications. * * @hide */ @SystemApi public static final int FLAG_AUTOGROUP_SUMMARY = 0x00000400; /** * @hide */ public static final int FLAG_CAN_COLORIZE = 0x00000800; /** * Bit to be bitswised-ored into the {@link #flags} field that should be * set by the system if this notification is showing as a bubble. * * Applications cannot set this flag directly; they should instead call * {@link Notification.Builder#setBubbleMetadata(BubbleMetadata)} to * request that a notification be displayed as a bubble, and then check * this flag to see whether that request was honored by the system. */ public static final int FLAG_BUBBLE = 0x00001000; public int flags; /** @hide */ @IntDef(prefix = { "PRIORITY_" }, value = { PRIORITY_DEFAULT, PRIORITY_LOW, PRIORITY_MIN, PRIORITY_HIGH, PRIORITY_MAX }) @Retention(RetentionPolicy.SOURCE) public @interface Priority {} /** * Default notification {@link #priority}. If your application does not prioritize its own * notifications, use this value for all notifications. * * @deprecated use {@link NotificationManager#IMPORTANCE_DEFAULT} instead. */ @Deprecated public static final int PRIORITY_DEFAULT = 0; /** * Lower {@link #priority}, for items that are less important. The UI may choose to show these * items smaller, or at a different position in the list, compared with your app's * {@link #PRIORITY_DEFAULT} items. * * @deprecated use {@link NotificationManager#IMPORTANCE_LOW} instead. */ @Deprecated public static final int PRIORITY_LOW = -1; /** * Lowest {@link #priority}; these items might not be shown to the user except under special * circumstances, such as detailed notification logs. * * @deprecated use {@link NotificationManager#IMPORTANCE_MIN} instead. */ @Deprecated public static final int PRIORITY_MIN = -2; /** * Higher {@link #priority}, for more important notifications or alerts. The UI may choose to * show these items larger, or at a different position in notification lists, compared with * your app's {@link #PRIORITY_DEFAULT} items. * * @deprecated use {@link NotificationManager#IMPORTANCE_HIGH} instead. */ @Deprecated public static final int PRIORITY_HIGH = 1; /** * Highest {@link #priority}, for your application's most important items that require the * user's prompt attention or input. * * @deprecated use {@link NotificationManager#IMPORTANCE_HIGH} instead. */ @Deprecated public static final int PRIORITY_MAX = 2; /** * Relative priority for this notification. * * Priority is an indication of how much of the user's valuable attention should be consumed by * this notification. Low-priority notifications may be hidden from the user in certain * situations, while the user might be interrupted for a higher-priority notification. The * system will make a determination about how to interpret this priority when presenting * the notification. * *
* A notification that is at least {@link #PRIORITY_HIGH} is more likely to be presented * as a heads-up notification. *
* * @deprecated use {@link NotificationChannel#getImportance()} instead. */ @Priority @Deprecated public int priority; /** * Accent color (an ARGB integer like the constants in {@link android.graphics.Color}) * to be applied by the standard Style templates when presenting this notification. * * The current template design constructs a colorful header image by overlaying the * {@link #icon} image (stenciled in white) atop a field of this color. Alpha components are * ignored. */ @ColorInt public int color = COLOR_DEFAULT; /** * Special value of {@link #color} telling the system not to decorate this notification with * any special color but instead use default colors when presenting this notification. */ @ColorInt public static final int COLOR_DEFAULT = 0; // AKA Color.TRANSPARENT /** * Special value of {@link #color} used as a place holder for an invalid color. * @hide */ @ColorInt public static final int COLOR_INVALID = 1; /** * Sphere of visibility of this notification, which affects how and when the SystemUI reveals * the notification's presence and contents in untrusted situations (namely, on the secure * lockscreen). * * The default level, {@link #VISIBILITY_PRIVATE}, behaves exactly as notifications have always * done on Android: The notification's {@link #icon} and {@link #tickerText} (if available) are * shown in all situations, but the contents are only available if the device is unlocked for * the appropriate user. * * A more permissive policy can be expressed by {@link #VISIBILITY_PUBLIC}; such a notification * can be read even in an "insecure" context (that is, above a secure lockscreen). * To modify the public version of this notification—for example, to redact some portions—see * {@link Builder#setPublicVersion(Notification)}. * * Finally, a notification can be made {@link #VISIBILITY_SECRET}, which will suppress its icon * and ticker until the user has bypassed the lockscreen. */ public @Visibility int visibility; /** @hide */ @IntDef(prefix = { "VISIBILITY_" }, value = { VISIBILITY_PUBLIC, VISIBILITY_PRIVATE, VISIBILITY_SECRET, }) @Retention(RetentionPolicy.SOURCE) public @interface Visibility {} /** * Notification visibility: Show this notification in its entirety on all lockscreens. * * {@see #visibility} */ public static final int VISIBILITY_PUBLIC = 1; /** * Notification visibility: Show this notification on all lockscreens, but conceal sensitive or * private information on secure lockscreens. * * {@see #visibility} */ public static final int VISIBILITY_PRIVATE = 0; /** * Notification visibility: Do not reveal any part of this notification on a secure lockscreen. * * {@see #visibility} */ public static final int VISIBILITY_SECRET = -1; /** * Notification category: incoming call (voice or video) or similar synchronous communication request. */ public static final String CATEGORY_CALL = "call"; /** * Notification category: map turn-by-turn navigation. */ public static final String CATEGORY_NAVIGATION = "navigation"; /** * Notification category: incoming direct message (SMS, instant message, etc.). */ public static final String CATEGORY_MESSAGE = "msg"; /** * Notification category: asynchronous bulk message (email). */ public static final String CATEGORY_EMAIL = "email"; /** * Notification category: calendar event. */ public static final String CATEGORY_EVENT = "event"; /** * Notification category: promotion or advertisement. */ public static final String CATEGORY_PROMO = "promo"; /** * Notification category: alarm or timer. */ public static final String CATEGORY_ALARM = "alarm"; /** * Notification category: progress of a long-running background operation. */ public static final String CATEGORY_PROGRESS = "progress"; /** * Notification category: social network or sharing update. */ public static final String CATEGORY_SOCIAL = "social"; /** * Notification category: error in background operation or authentication status. */ public static final String CATEGORY_ERROR = "err"; /** * Notification category: media transport control for playback. */ public static final String CATEGORY_TRANSPORT = "transport"; /** * Notification category: system or device status update. Reserved for system use. */ public static final String CATEGORY_SYSTEM = "sys"; /** * Notification category: indication of running background service. */ public static final String CATEGORY_SERVICE = "service"; /** * Notification category: a specific, timely recommendation for a single thing. * For example, a news app might want to recommend a news story it believes the user will * want to read next. */ public static final String CATEGORY_RECOMMENDATION = "recommendation"; /** * Notification category: ongoing information about device or contextual status. */ public static final String CATEGORY_STATUS = "status"; /** * Notification category: user-scheduled reminder. */ public static final String CATEGORY_REMINDER = "reminder"; /** * Notification category: extreme car emergencies. * @hide */ @SystemApi public static final String CATEGORY_CAR_EMERGENCY = "car_emergency"; /** * Notification category: car warnings. * @hide */ @SystemApi public static final String CATEGORY_CAR_WARNING = "car_warning"; /** * Notification category: general car system information. * @hide */ @SystemApi public static final String CATEGORY_CAR_INFORMATION = "car_information"; /** * One of the predefined notification categories (see theCATEGORY_*
constants)
* that best describes this Notification. May be used by the system for ranking and filtering.
*/
public String category;
@UnsupportedAppUsage
private String mGroupKey;
/**
* Get the key used to group this notification into a cluster or stack
* with other notifications on devices which support such rendering.
*/
public String getGroup() {
return mGroupKey;
}
private String mSortKey;
/**
* Get a sort key that orders this notification among other notifications from the
* same package. This can be useful if an external sort was already applied and an app
* would like to preserve this. Notifications will be sorted lexicographically using this
* value, although providing different priorities in addition to providing sort key may
* cause this value to be ignored.
*
* This sort key can also be used to order members of a notification group. See * {@link Builder#setGroup}. * * @see String#compareTo(String) */ public String getSortKey() { return mSortKey; } /** * Additional semantic data to be carried around with this Notification. *
* The extras keys defined here are intended to capture the original inputs to {@link Builder}
* APIs, and are intended to be used by
* {@link android.service.notification.NotificationListenerService} implementations to extract
* detailed information from notification objects.
*/
public Bundle extras = new Bundle();
/**
* All pending intents in the notification as the system needs to be able to access them but
* touching the extras bundle in the system process is not safe because the bundle may contain
* custom parcelable objects.
*
* @hide
*/
@UnsupportedAppUsage
public ArraySet For example, you might want to use this constant if you post a number of children
* notifications at once (say, after a periodic sync), and only need to notify the user
* audibly once.
*/
public static final int GROUP_ALERT_SUMMARY = 1;
/**
* Constant for {@link Builder#setGroupAlertBehavior(int)}, meaning that the summary
* notification in a group should be silenced (no sound or vibration) even if they are
* posted to a {@link NotificationChannel} that has sound and/or vibration. Use this constant
* to mute this notification if this notification is a group summary.
*
* For example, you might want to use this constant if only the children notifications
* in your group have content and the summary is only used to visually group notifications
* rather than to alert the user that new information is available.
*/
public static final int GROUP_ALERT_CHILDREN = 2;
private int mGroupAlertBehavior = GROUP_ALERT_ALL;
/**
* If this notification is being shown as a badge, always show as a number.
*/
public static final int BADGE_ICON_NONE = 0;
/**
* If this notification is being shown as a badge, use the {@link #getSmallIcon()} to
* represent this notification.
*/
public static final int BADGE_ICON_SMALL = 1;
/**
* If this notification is being shown as a badge, use the {@link #getLargeIcon()} to
* represent this notification.
*/
public static final int BADGE_ICON_LARGE = 2;
private int mBadgeIcon = BADGE_ICON_NONE;
/**
* Determines whether the platform can generate contextual actions for a notification.
*/
private boolean mAllowSystemGeneratedContextualActions = true;
/**
* Structure to encapsulate a named action that can be shown as part of this notification.
* It must include an icon, a label, and a {@link PendingIntent} to be fired when the action is
* selected by the user.
*
* Apps should use {@link Notification.Builder#addAction(int, CharSequence, PendingIntent)}
* or {@link Notification.Builder#addAction(Notification.Action)}
* to attach actions.
*/
public static class Action implements Parcelable {
/**
* {@link #extras} key: Keys to a {@link Parcelable} {@link ArrayList} of
* {@link RemoteInput}s.
*
* This is intended for {@link RemoteInput}s that only accept data, meaning
* {@link RemoteInput#getAllowFreeFormInput} is false, {@link RemoteInput#getChoices}
* is null or empty, and {@link RemoteInput#getAllowedDataTypes} is non-null and not
* empty. These {@link RemoteInput}s will be ignored by devices that do not
* support non-text-based {@link RemoteInput}s. See {@link Builder#build}.
*
* You can test if a RemoteInput matches these constraints using
* {@link RemoteInput#isDataOnly}.
*/
private static final String EXTRA_DATA_ONLY_INPUTS = "android.extra.DATA_ONLY_INPUTS";
/**
* {@link }: No semantic action defined.
*/
public static final int SEMANTIC_ACTION_NONE = 0;
/**
* {@code SemanticAction}: Reply to a conversation, chat, group, or wherever replies
* may be appropriate.
*/
public static final int SEMANTIC_ACTION_REPLY = 1;
/**
* {@code SemanticAction}: Mark content as read.
*/
public static final int SEMANTIC_ACTION_MARK_AS_READ = 2;
/**
* {@code SemanticAction}: Mark content as unread.
*/
public static final int SEMANTIC_ACTION_MARK_AS_UNREAD = 3;
/**
* {@code SemanticAction}: Delete the content associated with the notification. This
* could mean deleting an email, message, etc.
*/
public static final int SEMANTIC_ACTION_DELETE = 4;
/**
* {@code SemanticAction}: Archive the content associated with the notification. This
* could mean archiving an email, message, etc.
*/
public static final int SEMANTIC_ACTION_ARCHIVE = 5;
/**
* {@code SemanticAction}: Mute the content associated with the notification. This could
* mean silencing a conversation or currently playing media.
*/
public static final int SEMANTIC_ACTION_MUTE = 6;
/**
* {@code SemanticAction}: Unmute the content associated with the notification. This could
* mean un-silencing a conversation or currently playing media.
*/
public static final int SEMANTIC_ACTION_UNMUTE = 7;
/**
* {@code SemanticAction}: Mark content with a thumbs up.
*/
public static final int SEMANTIC_ACTION_THUMBS_UP = 8;
/**
* {@code SemanticAction}: Mark content with a thumbs down.
*/
public static final int SEMANTIC_ACTION_THUMBS_DOWN = 9;
/**
* {@code SemanticAction}: Call a contact, group, etc.
*/
public static final int SEMANTIC_ACTION_CALL = 10;
private final Bundle mExtras;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
private Icon mIcon;
private final RemoteInput[] mRemoteInputs;
private boolean mAllowGeneratedReplies = true;
private final @SemanticAction int mSemanticAction;
private final boolean mIsContextual;
/**
* Small icon representing the action.
*
* @deprecated Use {@link Action#getIcon()} instead.
*/
@Deprecated
public int icon;
/**
* Title of the action.
*/
public CharSequence title;
/**
* Intent to send when the user invokes this action. May be null, in which case the action
* may be rendered in a disabled presentation by the system UI.
*/
public PendingIntent actionIntent;
private Action(Parcel in) {
if (in.readInt() != 0) {
mIcon = Icon.CREATOR.createFromParcel(in);
if (mIcon.getType() == Icon.TYPE_RESOURCE) {
icon = mIcon.getResId();
}
}
title = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
if (in.readInt() == 1) {
actionIntent = PendingIntent.CREATOR.createFromParcel(in);
}
mExtras = Bundle.setDefusable(in.readBundle(), true);
mRemoteInputs = in.createTypedArray(RemoteInput.CREATOR);
mAllowGeneratedReplies = in.readInt() == 1;
mSemanticAction = in.readInt();
mIsContextual = in.readInt() == 1;
}
/**
* @deprecated Use {@link android.app.Notification.Action.Builder}.
*/
@Deprecated
public Action(int icon, CharSequence title, PendingIntent intent) {
this(Icon.createWithResource("", icon), title, intent, new Bundle(), null, true,
SEMANTIC_ACTION_NONE, false /* isContextual */);
}
/** Keep in sync with {@link Notification.Action.Builder#Builder(Action)}! */
private Action(Icon icon, CharSequence title, PendingIntent intent, Bundle extras,
RemoteInput[] remoteInputs, boolean allowGeneratedReplies,
@SemanticAction int semanticAction, boolean isContextual) {
this.mIcon = icon;
if (icon != null && icon.getType() == Icon.TYPE_RESOURCE) {
this.icon = icon.getResId();
}
this.title = title;
this.actionIntent = intent;
this.mExtras = extras != null ? extras : new Bundle();
this.mRemoteInputs = remoteInputs;
this.mAllowGeneratedReplies = allowGeneratedReplies;
this.mSemanticAction = semanticAction;
this.mIsContextual = isContextual;
}
/**
* Return an icon representing the action.
*/
public Icon getIcon() {
if (mIcon == null && icon != 0) {
// you snuck an icon in here without using the builder; let's try to keep it
mIcon = Icon.createWithResource("", icon);
}
return mIcon;
}
/**
* Get additional metadata carried around with this Action.
*/
public Bundle getExtras() {
return mExtras;
}
/**
* Return whether the platform should automatically generate possible replies for this
* {@link Action}
*/
public boolean getAllowGeneratedReplies() {
return mAllowGeneratedReplies;
}
/**
* Get the list of inputs to be collected from the user when this action is sent.
* May return null if no remote inputs were added. Only returns inputs which accept
* a text input. For inputs which only accept data use {@link #getDataOnlyRemoteInputs}.
*/
public RemoteInput[] getRemoteInputs() {
return mRemoteInputs;
}
/**
* Returns the {@code SemanticAction} associated with this {@link Action}. A
* {@code SemanticAction} denotes what an {@link Action}'s {@link PendingIntent} will do
* (eg. reply, mark as read, delete, etc).
*/
public @SemanticAction int getSemanticAction() {
return mSemanticAction;
}
/**
* Returns whether this is a contextual Action, i.e. whether the action is dependent on the
* notification message body. An example of a contextual action could be an action opening a
* map application with an address shown in the notification.
*/
public boolean isContextual() {
return mIsContextual;
}
/**
* Get the list of inputs to be collected from the user that ONLY accept data when this
* action is sent. These remote inputs are guaranteed to return true on a call to
* {@link RemoteInput#isDataOnly}.
*
* Returns null if there are no data-only remote inputs.
*
* This method exists so that legacy RemoteInput collectors that pre-date the addition
* of non-textual RemoteInputs do not access these remote inputs.
*/
public RemoteInput[] getDataOnlyRemoteInputs() {
return (RemoteInput[]) mExtras.getParcelableArray(EXTRA_DATA_ONLY_INPUTS);
}
/**
* Builder class for {@link Action} objects.
*/
public static final class Builder {
@Nullable private final Icon mIcon;
@Nullable private final CharSequence mTitle;
@Nullable private final PendingIntent mIntent;
private boolean mAllowGeneratedReplies = true;
@NonNull private final Bundle mExtras;
@Nullable private ArrayList Values within the Bundle will replace existing extras values in this Builder.
*
* @see Notification.Action#extras
*/
@NonNull
public Builder addExtras(Bundle extras) {
if (extras != null) {
mExtras.putAll(extras);
}
return this;
}
/**
* Get the metadata Bundle used by this Builder.
*
* The returned Bundle is shared with this Builder.
*/
@NonNull
public Bundle getExtras() {
return mExtras;
}
/**
* Add an input to be collected from the user when this action is sent.
* Response values can be retrieved from the fired intent by using the
* {@link RemoteInput#getResultsFromIntent} function.
* @param remoteInput a {@link RemoteInput} to add to the action
* @return this object for method chaining
*/
@NonNull
public Builder addRemoteInput(RemoteInput remoteInput) {
if (mRemoteInputs == null) {
mRemoteInputs = new ArrayList
* For backwards compatibility {@code extras} holds some references to "real" member data such
* as {@link getLargeIcon()} which is mirrored by {@link #EXTRA_LARGE_ICON}. This is mostly
* fine as long as the object stays in one process.
*
* However, once the notification goes into a parcel each reference gets marshalled separately,
* wasting memory. Especially with large images on Auto and TV, this is worth fixing.
*/
private void fixDuplicateExtras() {
if (extras != null) {
fixDuplicateExtra(mLargeIcon, EXTRA_LARGE_ICON);
}
}
/**
* If we find an extra that's exactly the same as one of the "real" fields but refers to a
* separate object, replace it with the field's version to avoid holding duplicate copies.
*/
private void fixDuplicateExtra(@Nullable Parcelable original, @NonNull String extraName) {
if (original != null && extras.getParcelable(extraName) != null) {
extras.putParcelable(extraName, original);
}
}
/**
* Sets the {@link #contentView} field to be a view with the standard "Latest Event"
* layout.
*
* Uses the {@link #icon} and {@link #when} fields to set the icon and time fields
* in the view. Used by the Android system to correlate objects (such as
* {@link ShortcutInfo} and {@link ContentCaptureContext}).
*/
@Nullable
public LocusId getLocusId() {
return mLocusId;
}
/**
* Returns the settings text provided to {@link Builder#setSettingsText(CharSequence)}.
*/
public CharSequence getSettingsText() {
return mSettingsText;
}
/**
* Returns which type of notifications in a group are responsible for audibly alerting the
* user. See {@link #GROUP_ALERT_ALL}, {@link #GROUP_ALERT_CHILDREN},
* {@link #GROUP_ALERT_SUMMARY}.
*/
public @GroupAlertBehavior int getGroupAlertBehavior() {
return mGroupAlertBehavior;
}
/**
* Returns the bubble metadata that will be used to display app content in a floating window
* over the existing foreground activity.
*/
@Nullable
public BubbleMetadata getBubbleMetadata() {
return mBubbleMetadata;
}
/**
* Returns whether the platform is allowed (by the app developer) to generate contextual actions
* for this notification.
*/
public boolean getAllowSystemGeneratedContextualActions() {
return mAllowSystemGeneratedContextualActions;
}
/**
* The small icon representing this notification in the status bar and content view.
*
* @return the small icon representing this notification.
*
* @see Builder#getSmallIcon()
* @see Builder#setSmallIcon(Icon)
*/
public Icon getSmallIcon() {
return mSmallIcon;
}
/**
* Used when notifying to clean up legacy small icons.
* @hide
*/
@UnsupportedAppUsage
public void setSmallIcon(Icon icon) {
mSmallIcon = icon;
}
/**
* The large icon shown in this notification's content view.
* @see Builder#getLargeIcon()
* @see Builder#setLargeIcon(Icon)
*/
public Icon getLargeIcon() {
return mLargeIcon;
}
/**
* @hide
*/
@UnsupportedAppUsage
public boolean isGroupSummary() {
return mGroupKey != null && (flags & FLAG_GROUP_SUMMARY) != 0;
}
/**
* @hide
*/
@UnsupportedAppUsage
public boolean isGroupChild() {
return mGroupKey != null && (flags & FLAG_GROUP_SUMMARY) == 0;
}
/**
* @hide
*/
public boolean suppressAlertingDueToGrouping() {
if (isGroupSummary()
&& getGroupAlertBehavior() == Notification.GROUP_ALERT_CHILDREN) {
return true;
} else if (isGroupChild()
&& getGroupAlertBehavior() == Notification.GROUP_ALERT_SUMMARY) {
return true;
}
return false;
}
/**
* Finds and returns a remote input and its corresponding action.
*
* @param requiresFreeform requires the remoteinput to allow freeform or not.
* @return the result pair, {@code null} if no result is found.
*
* @hide
*/
@Nullable
public Pair Example:
*
* This method should be called when the {@link LocusId} is used in other places (such
* as {@link ShortcutInfo} and {@link ContentCaptureContext}) so the Android system can
* correlate them.
*/
@NonNull
public Builder setLocusId(@Nullable LocusId locusId) {
mN.mLocusId = locusId;
return this;
}
/**
* Sets which icon to display as a badge for this notification.
*
* Must be one of {@link #BADGE_ICON_NONE}, {@link #BADGE_ICON_SMALL},
* {@link #BADGE_ICON_LARGE}.
*
* Note: This value might be ignored, for launchers that don't support badge icons.
*/
@NonNull
public Builder setBadgeIconType(int icon) {
mN.mBadgeIcon = icon;
return this;
}
/**
* Sets the group alert behavior for this notification. Use this method to mute this
* notification if alerts for this notification's group should be handled by a different
* notification. This is only applicable for notifications that belong to a
* {@link #setGroup(String) group}. This must be called on all notifications you want to
* mute. For example, if you want only the summary of your group to make noise, all
* children in the group should have the group alert behavior {@link #GROUP_ALERT_SUMMARY}.
*
* The default value is {@link #GROUP_ALERT_ALL}. This data will be ignored unless the notification is posted to a channel that
* allows {@link NotificationChannel#canBubble() bubbles}. Notifications allowed to bubble that have valid bubble metadata will display in
* collapsed state outside of the notification shade on unlocked devices. When a user
* interacts with the collapsed state, the bubble intent will be invoked and displayed. This is only relevant if {@link #setUsesChronometer(boolean)} has been set to true.
* If it isn't set the chronometer will count up.
*
* @see #setUsesChronometer(boolean)
*/
@NonNull
public Builder setChronometerCountDown(boolean countDown) {
mN.extras.putBoolean(EXTRA_CHRONOMETER_COUNT_DOWN, countDown);
return this;
}
/**
* Set the small icon resource, which will be used to represent the notification in the
* status bar.
*
* The platform template for the expanded view will draw this icon in the left, unless a
* {@link #setLargeIcon(Bitmap) large icon} has also been specified, in which case the small
* icon will be moved to the right-hand side.
*
* @param icon
* A resource ID in the application's package of the drawable to use.
* @see Notification#icon
*/
@NonNull
public Builder setSmallIcon(@DrawableRes int icon) {
return setSmallIcon(icon != 0
? Icon.createWithResource(mContext, icon)
: null);
}
/**
* A variant of {@link #setSmallIcon(int) setSmallIcon(int)} that takes an additional
* level parameter for when the icon is a {@link android.graphics.drawable.LevelListDrawable
* LevelListDrawable}.
*
* @param icon A resource ID in the application's package of the drawable to use.
* @param level The level to use for the icon.
*
* @see Notification#icon
* @see Notification#iconLevel
*/
@NonNull
public Builder setSmallIcon(@DrawableRes int icon, int level) {
mN.iconLevel = level;
return setSmallIcon(icon);
}
/**
* Set the small icon, which will be used to represent the notification in the
* status bar and content view (unless overridden there by a
* {@link #setLargeIcon(Bitmap) large icon}).
*
* @param icon An Icon object to use.
* @see Notification#icon
*/
@NonNull
public Builder setSmallIcon(Icon icon) {
mN.setSmallIcon(icon);
if (icon != null && icon.getType() == Icon.TYPE_RESOURCE) {
mN.icon = icon.getResId();
}
return this;
}
/**
* Set the first line of text in the platform notification template.
*/
@NonNull
public Builder setContentTitle(CharSequence title) {
mN.extras.putCharSequence(EXTRA_TITLE, safeCharSequence(title));
return this;
}
/**
* Set the second line of text in the platform notification template.
*/
@NonNull
public Builder setContentText(CharSequence text) {
mN.extras.putCharSequence(EXTRA_TEXT, safeCharSequence(text));
return this;
}
/**
* This provides some additional information that is displayed in the notification. No
* guarantees are given where exactly it is displayed.
*
* This information should only be provided if it provides an essential
* benefit to the understanding of the notification. The more text you provide the
* less readable it becomes. For example, an email client should only provide the account
* name here if more than one email account has been added. As of {@link android.os.Build.VERSION_CODES#N} this information is displayed in the
* notification header area.
*
* On Android versions before {@link android.os.Build.VERSION_CODES#N}
* this will be shown in the third line of text in the platform notification template.
* You should not be using {@link #setProgress(int, int, boolean)} at the
* same time on those versions; they occupy the same place.
* This text does not appear within notification {@link Style templates} but may
* appear when the user uses an affordance to learn more about the notification.
* Additionally, this text will not appear unless you provide a valid link target by
* handling {@link #INTENT_CATEGORY_NOTIFICATION_PREFERENCES}.
*
* This text is meant to be concise description about what the user can customize
* when they click on this link. The recommended maximum length is 40 characters.
* @param text
* @return
*/
@NonNull
public Builder setSettingsText(CharSequence text) {
mN.mSettingsText = safeCharSequence(text);
return this;
}
/**
* Set the remote input history.
*
* This should be set to the most recent inputs that have been sent
* through a {@link RemoteInput} of this Notification and cleared once the it is no
* longer relevant (e.g. for chat notifications once the other party has responded).
*
* The most recent input must be stored at the 0 index, the second most recent at the
* 1 index, etc. Note that the system will limit both how far back the inputs will be shown
* and how much of each individual input is shown.
*
* Note: The reply text will only be shown on notifications that have least one action
* with a {@code RemoteInput}.
* The system UI may choose to display a heads-up notification, instead of
* launching this intent, while the user is using the device.
* Apps targeting {@link Build.VERSION_CODES#Q} and above will have to request
* a permission ({@link android.Manifest.permission#USE_FULL_SCREEN_INTENT}) in order to
* use full screen intents.
* A notification that vibrates is more likely to be presented as a heads-up notification.
*
* This should only be used for high priority ongoing tasks like navigation, an ongoing
* call, or other similarly high-priority events for the user.
*
* For most styles, the coloring will only be applied if the notification is for a
* foreground service notification.
* However, for {@link MediaStyle} and {@link DecoratedMediaCustomViewStyle} notifications
* that have a media session attached there is no such requirement.
*
* @see #setColor(int)
* @see MediaStyle#setMediaSession(MediaSession.Token)
*/
@NonNull
public Builder setColorized(boolean colorize) {
mN.extras.putBoolean(EXTRA_COLORIZED, colorize);
return this;
}
/**
* Set this flag if you would only like the sound, vibrate
* and ticker to be played if the notification is not already showing.
*
* @see Notification#FLAG_ONLY_ALERT_ONCE
*/
@NonNull
public Builder setOnlyAlertOnce(boolean onlyAlertOnce) {
setFlag(FLAG_ONLY_ALERT_ONCE, onlyAlertOnce);
return this;
}
/**
* Make this notification automatically dismissed when the user touches it.
*
* @see Notification#FLAG_AUTO_CANCEL
*/
@NonNull
public Builder setAutoCancel(boolean autoCancel) {
setFlag(FLAG_AUTO_CANCEL, autoCancel);
return this;
}
/**
* Set whether or not this notification should not bridge to other devices.
*
* Some notifications can be bridged to other devices for remote display.
* This hint can be set to recommend this notification not be bridged.
*/
@NonNull
public Builder setLocalOnly(boolean localOnly) {
setFlag(FLAG_LOCAL_ONLY, localOnly);
return this;
}
/**
* Set which notification properties will be inherited from system defaults.
*
* The value should be one or more of the following fields combined with
* bitwise-or:
* {@link #DEFAULT_SOUND}, {@link #DEFAULT_VIBRATE}, {@link #DEFAULT_LIGHTS}.
*
* For all default values, use {@link #DEFAULT_ALL}.
*
* @deprecated use {@link NotificationChannel#enableVibration(boolean)} and
* {@link NotificationChannel#enableLights(boolean)} and
* {@link NotificationChannel#setSound(Uri, AudioAttributes)} instead.
*/
@Deprecated
public Builder setDefaults(int defaults) {
mN.defaults = defaults;
return this;
}
/**
* Set the priority of this notification.
*
* @see Notification#priority
* @deprecated use {@link NotificationChannel#setImportance(int)} instead.
*/
@Deprecated
public Builder setPriority(@Priority int pri) {
mN.priority = pri;
return this;
}
/**
* Set the notification category.
*
* @see Notification#category
*/
@NonNull
public Builder setCategory(String category) {
mN.category = category;
return this;
}
/**
* Add a person that is relevant to this notification.
*
*
* Depending on user preferences, this annotation may allow the notification to pass
* through interruption filters, if this notification is of category {@link #CATEGORY_CALL}
* or {@link #CATEGORY_MESSAGE}. The addition of people may also cause this notification to
* appear more prominently in the user interface.
*
* The person should be specified by the {@code String} representation of a
* {@link android.provider.ContactsContract.Contacts#CONTENT_LOOKUP_URI}.
* The system will also attempt to resolve {@code mailto:} and {@code tel:} schema
* URIs. The path part of these URIs must exist in the contacts database, in the
* appropriate column, or the reference will be discarded as invalid. Telephone schema
* URIs will be resolved by {@link android.provider.ContactsContract.PhoneLookup}.
* It is also possible to provide a URI with the schema {@code name:} in order to uniquely
* identify a person without an entry in the contacts database.
*
* Depending on user preferences, this annotation may allow the notification to pass
* through interruption filters, if this notification is of category {@link #CATEGORY_CALL}
* or {@link #CATEGORY_MESSAGE}. The addition of people may also cause this notification to
* appear more prominently in the user interface.
*
* A person should usually contain a uri in order to benefit from the ranking boost.
* However, even if no uri is provided, it's beneficial to provide other people in the
* notification, such that listeners and voice only devices can announce and handle them
* properly.
* To make this notification the summary for its group, also call
* {@link #setGroupSummary}. A sort order can be specified for group members by using
* {@link #setSortKey}.
* @param groupKey The group key of the group.
* @return this object for method chaining
*/
@NonNull
public Builder setGroup(String groupKey) {
mN.mGroupKey = groupKey;
return this;
}
/**
* Set this notification to be the group summary for a group of notifications.
* Grouped notifications may display in a cluster or stack on devices which
* support such rendering. If thereRequires a group key also be set using {@link #setGroup}.
* The group summary may be suppressed if too few notifications are included in the group.
* @param isGroupSummary Whether this notification should be a group summary.
* @return this object for method chaining
*/
@NonNull
public Builder setGroupSummary(boolean isGroupSummary) {
setFlag(FLAG_GROUP_SUMMARY, isGroupSummary);
return this;
}
/**
* Set a sort key that orders this notification among other notifications from the
* same package. This can be useful if an external sort was already applied and an app
* would like to preserve this. Notifications will be sorted lexicographically using this
* value, although providing different priorities in addition to providing sort key may
* cause this value to be ignored.
*
* This sort key can also be used to order members of a notification group. See
* {@link #setGroup}.
*
* @see String#compareTo(String)
*/
@NonNull
public Builder setSortKey(String sortKey) {
mN.mSortKey = sortKey;
return this;
}
/**
* Merge additional metadata into this notification.
*
* Values within the Bundle will replace existing extras values in this Builder.
*
* @see Notification#extras
*/
@NonNull
public Builder addExtras(Bundle extras) {
if (extras != null) {
mUserExtras.putAll(extras);
}
return this;
}
/**
* Set metadata for this notification.
*
* A reference to the Bundle is held for the lifetime of this Builder, and the Bundle's
* current contents are copied into the Notification each time {@link #build()} is
* called.
*
* Replaces any existing extras values with those from the provided Bundle.
* Use {@link #addExtras} to merge in metadata instead.
*
* @see Notification#extras
*/
@NonNull
public Builder setExtras(Bundle extras) {
if (extras != null) {
mUserExtras = extras;
}
return this;
}
/**
* Get the current metadata Bundle used by this notification Builder.
*
* The returned Bundle is shared with this Builder.
*
* The current contents of this Bundle are copied into the Notification each time
* {@link #build()} is called.
*
* @see Notification#extras
*/
public Bundle getExtras() {
return mUserExtras;
}
private Bundle getAllExtras() {
final Bundle saveExtras = (Bundle) mUserExtras.clone();
saveExtras.putAll(mN.extras);
return saveExtras;
}
/**
* Add an action to this notification. Actions are typically displayed by
* the system as a button adjacent to the notification content.
*
* Every action must have an icon (32dp square and matching the
* Holo
* Dark action bar visual style), a textual label, and a {@link PendingIntent}.
*
* A notification in its expanded form can display up to 3 actions, from left to right in
* the order they were added. Actions will not be displayed when the notification is
* collapsed, however, so be sure that any essential functions may be accessed by the user
* in some other way (for example, in the Activity pointed to by {@link #contentIntent}).
*
* @param icon Resource ID of a drawable that represents the action.
* @param title Text describing the action.
* @param intent PendingIntent to be fired when the action is invoked.
*
* @deprecated Use {@link #addAction(Action)} instead.
*/
@Deprecated
public Builder addAction(int icon, CharSequence title, PendingIntent intent) {
mActions.add(new Action(icon, safeCharSequence(title), intent));
return this;
}
/**
* Add an action to this notification. Actions are typically displayed by
* the system as a button adjacent to the notification content.
*
* Every action must have an icon (32dp square and matching the
* Holo
* Dark action bar visual style), a textual label, and a {@link PendingIntent}.
*
* A notification in its expanded form can display up to 3 actions, from left to right in
* the order they were added. Actions will not be displayed when the notification is
* collapsed, however, so be sure that any essential functions may be accessed by the user
* in some other way (for example, in the Activity pointed to by {@link #contentIntent}).
*
* @param action The action to add.
*/
@NonNull
public Builder addAction(Action action) {
if (action != null) {
mActions.add(action);
}
return this;
}
/**
* Alter the complete list of actions attached to this notification.
* @see #addAction(Action).
*
* @param actions
* @return
*/
@NonNull
public Builder setActions(Action... actions) {
mActions.clear();
for (int i = 0; i < actions.length; i++) {
if (actions[i] != null) {
mActions.add(actions[i]);
}
}
return this;
}
/**
* Add a rich notification style to be applied at build time.
*
* @param style Object responsible for modifying the notification style.
*/
@NonNull
public Builder setStyle(Style style) {
if (mStyle != style) {
mStyle = style;
if (mStyle != null) {
mStyle.setBuilder(this);
mN.extras.putString(EXTRA_TEMPLATE, style.getClass().getName());
} else {
mN.extras.remove(EXTRA_TEMPLATE);
}
}
return this;
}
/**
* Returns the style set by {@link #setStyle(Style)}.
*/
public Style getStyle() {
return mStyle;
}
/**
* Specify the value of {@link #visibility}.
*
* @return The same Builder.
*/
@NonNull
public Builder setVisibility(@Visibility int visibility) {
mN.visibility = visibility;
return this;
}
/**
* Supply a replacement Notification whose contents should be shown in insecure contexts
* (i.e. atop the secure lockscreen). See {@link #visibility} and {@link #VISIBILITY_PUBLIC}.
* @param n A replacement notification, presumably with some or all info redacted.
* @return The same Builder.
*/
@NonNull
public Builder setPublicVersion(Notification n) {
if (n != null) {
mN.publicVersion = new Notification();
n.cloneInto(mN.publicVersion, /*heavy=*/ true);
} else {
mN.publicVersion = null;
}
return this;
}
/**
* Apply an extender to this notification builder. Extenders may be used to add
* metadata or change options on this builder.
*/
@NonNull
public Builder extend(Extender extender) {
extender.extend(this);
return this;
}
/**
* @hide
*/
@NonNull
public Builder setFlag(int mask, boolean value) {
if (value) {
mN.flags |= mask;
} else {
mN.flags &= ~mask;
}
return this;
}
/**
* Sets {@link Notification#color}.
*
* @param argb The accent color to use
*
* @return The same Builder.
*/
@NonNull
public Builder setColor(@ColorInt int argb) {
mN.color = argb;
sanitizeColor();
return this;
}
private Drawable getProfileBadgeDrawable() {
if (mContext.getUserId() == UserHandle.USER_SYSTEM) {
// This user can never be a badged profile,
// and also includes USER_ALL system notifications.
return null;
}
// Note: This assumes that the current user can read the profile badge of the
// originating user.
return mContext.getPackageManager().getUserBadgeForDensityNoBackground(
new UserHandle(mContext.getUserId()), 0);
}
private Bitmap getProfileBadge() {
Drawable badge = getProfileBadgeDrawable();
if (badge == null) {
return null;
}
final int size = mContext.getResources().getDimensionPixelSize(
R.dimen.notification_badge_size);
Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
badge.setBounds(0, 0, size, size);
badge.draw(canvas);
return bitmap;
}
private void bindProfileBadge(RemoteViews contentView, StandardTemplateParams p) {
Bitmap profileBadge = getProfileBadge();
if (profileBadge != null) {
contentView.setImageViewBitmap(R.id.profile_badge, profileBadge);
contentView.setViewVisibility(R.id.profile_badge, View.VISIBLE);
if (isColorized(p)) {
contentView.setDrawableTint(R.id.profile_badge, false,
getPrimaryTextColor(p), PorterDuff.Mode.SRC_ATOP);
}
}
}
private void bindAlertedIcon(RemoteViews contentView, StandardTemplateParams p) {
contentView.setDrawableTint(
R.id.alerted_icon,
false /* targetBackground */,
getNeutralColor(p),
PorterDuff.Mode.SRC_ATOP);
}
/**
* @hide
*/
public boolean usesStandardHeader() {
if (mN.mUsesStandardHeader) {
return true;
}
if (mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.N) {
if (mN.contentView == null && mN.bigContentView == null) {
return true;
}
}
boolean contentViewUsesHeader = mN.contentView == null
|| STANDARD_LAYOUTS.contains(mN.contentView.getLayoutId());
boolean bigContentViewUsesHeader = mN.bigContentView == null
|| STANDARD_LAYOUTS.contains(mN.bigContentView.getLayoutId());
return contentViewUsesHeader && bigContentViewUsesHeader;
}
private void resetStandardTemplate(RemoteViews contentView) {
resetNotificationHeader(contentView);
contentView.setViewVisibility(R.id.right_icon, View.GONE);
contentView.setViewVisibility(R.id.title, View.GONE);
contentView.setTextViewText(R.id.title, null);
contentView.setViewVisibility(R.id.text, View.GONE);
contentView.setTextViewText(R.id.text, null);
contentView.setViewVisibility(R.id.text_line_1, View.GONE);
contentView.setTextViewText(R.id.text_line_1, null);
}
/**
* Resets the notification header to its original state
*/
private void resetNotificationHeader(RemoteViews contentView) {
// Small icon doesn't need to be reset, as it's always set. Resetting would prevent
// re-using the drawable when the notification is updated.
contentView.setBoolean(R.id.notification_header, "setExpanded", false);
contentView.setTextViewText(R.id.app_name_text, null);
contentView.setViewVisibility(R.id.chronometer, View.GONE);
contentView.setViewVisibility(R.id.header_text, View.GONE);
contentView.setTextViewText(R.id.header_text, null);
contentView.setViewVisibility(R.id.header_text_secondary, View.GONE);
contentView.setTextViewText(R.id.header_text_secondary, null);
contentView.setViewVisibility(R.id.header_text_divider, View.GONE);
contentView.setViewVisibility(R.id.header_text_secondary_divider, View.GONE);
contentView.setViewVisibility(R.id.time_divider, View.GONE);
contentView.setViewVisibility(R.id.time, View.GONE);
contentView.setImageViewIcon(R.id.profile_badge, null);
contentView.setViewVisibility(R.id.profile_badge, View.GONE);
contentView.setViewVisibility(R.id.alerted_icon, View.GONE);
mN.mUsesStandardHeader = false;
}
private RemoteViews applyStandardTemplate(int resId, TemplateBindResult result) {
return applyStandardTemplate(resId, mParams.reset().fillTextsFrom(this),
result);
}
private RemoteViews applyStandardTemplate(int resId, StandardTemplateParams p,
TemplateBindResult result) {
RemoteViews contentView = new BuilderRemoteViews(mContext.getApplicationInfo(), resId);
resetStandardTemplate(contentView);
final Bundle ex = mN.extras;
updateBackgroundColor(contentView, p);
bindNotificationHeader(contentView, p);
bindLargeIconAndReply(contentView, p, result);
boolean showProgress = handleProgressBar(contentView, ex, p);
if (p.title != null) {
contentView.setViewVisibility(R.id.title, View.VISIBLE);
contentView.setTextViewText(R.id.title, processTextSpans(p.title));
setTextViewColorPrimary(contentView, R.id.title, p);
contentView.setViewLayoutWidth(R.id.title, showProgress
? ViewGroup.LayoutParams.WRAP_CONTENT
: ViewGroup.LayoutParams.MATCH_PARENT);
}
if (p.text != null) {
int textId = showProgress ? com.android.internal.R.id.text_line_1
: com.android.internal.R.id.text;
contentView.setTextViewText(textId, processTextSpans(p.text));
setTextViewColorSecondary(contentView, textId, p);
contentView.setViewVisibility(textId, View.VISIBLE);
}
setContentMinHeight(contentView, showProgress || mN.hasLargeIcon());
return contentView;
}
private CharSequence processTextSpans(CharSequence text) {
if (hasForegroundColor() || mInNightMode) {
return ContrastColorUtil.clearColorSpans(text);
}
return text;
}
private void setTextViewColorPrimary(RemoteViews contentView, int id,
StandardTemplateParams p) {
ensureColors(p);
contentView.setTextColor(id, mPrimaryTextColor);
}
private boolean hasForegroundColor() {
return mForegroundColor != COLOR_INVALID;
}
/**
* Return the primary text color using the existing template params
* @hide
*/
@VisibleForTesting
public int getPrimaryTextColor() {
return getPrimaryTextColor(mParams);
}
/**
* @param p the template params to inflate this with
* @return the primary text color
* @hide
*/
@VisibleForTesting
public int getPrimaryTextColor(StandardTemplateParams p) {
ensureColors(p);
return mPrimaryTextColor;
}
/**
* Return the secondary text color using the existing template params
* @hide
*/
@VisibleForTesting
public int getSecondaryTextColor() {
return getSecondaryTextColor(mParams);
}
/**
* @param p the template params to inflate this with
* @return the secondary text color
* @hide
*/
@VisibleForTesting
public int getSecondaryTextColor(StandardTemplateParams p) {
ensureColors(p);
return mSecondaryTextColor;
}
private void setTextViewColorSecondary(RemoteViews contentView, int id,
StandardTemplateParams p) {
ensureColors(p);
contentView.setTextColor(id, mSecondaryTextColor);
}
private void ensureColors(StandardTemplateParams p) {
int backgroundColor = getBackgroundColor(p);
if (mPrimaryTextColor == COLOR_INVALID
|| mSecondaryTextColor == COLOR_INVALID
|| mTextColorsAreForBackground != backgroundColor) {
mTextColorsAreForBackground = backgroundColor;
if (!hasForegroundColor() || !isColorized(p)) {
mPrimaryTextColor = ContrastColorUtil.resolvePrimaryColor(mContext,
backgroundColor, mInNightMode);
mSecondaryTextColor = ContrastColorUtil.resolveSecondaryColor(mContext,
backgroundColor, mInNightMode);
if (backgroundColor != COLOR_DEFAULT && isColorized(p)) {
mPrimaryTextColor = ContrastColorUtil.findAlphaToMeetContrast(
mPrimaryTextColor, backgroundColor, 4.5);
mSecondaryTextColor = ContrastColorUtil.findAlphaToMeetContrast(
mSecondaryTextColor, backgroundColor, 4.5);
}
} else {
double backLum = ContrastColorUtil.calculateLuminance(backgroundColor);
double textLum = ContrastColorUtil.calculateLuminance(mForegroundColor);
double contrast = ContrastColorUtil.calculateContrast(mForegroundColor,
backgroundColor);
// We only respect the given colors if worst case Black or White still has
// contrast
boolean backgroundLight = backLum > textLum
&& satisfiesTextContrast(backgroundColor, Color.BLACK)
|| backLum <= textLum
&& !satisfiesTextContrast(backgroundColor, Color.WHITE);
if (contrast < 4.5f) {
if (backgroundLight) {
mSecondaryTextColor = ContrastColorUtil.findContrastColor(
mForegroundColor,
backgroundColor,
true /* findFG */,
4.5f);
mPrimaryTextColor = ContrastColorUtil.changeColorLightness(
mSecondaryTextColor, -LIGHTNESS_TEXT_DIFFERENCE_LIGHT);
} else {
mSecondaryTextColor =
ContrastColorUtil.findContrastColorAgainstDark(
mForegroundColor,
backgroundColor,
true /* findFG */,
4.5f);
mPrimaryTextColor = ContrastColorUtil.changeColorLightness(
mSecondaryTextColor, -LIGHTNESS_TEXT_DIFFERENCE_DARK);
}
} else {
mPrimaryTextColor = mForegroundColor;
mSecondaryTextColor = ContrastColorUtil.changeColorLightness(
mPrimaryTextColor, backgroundLight ? LIGHTNESS_TEXT_DIFFERENCE_LIGHT
: LIGHTNESS_TEXT_DIFFERENCE_DARK);
if (ContrastColorUtil.calculateContrast(mSecondaryTextColor,
backgroundColor) < 4.5f) {
// oh well the secondary is not good enough
if (backgroundLight) {
mSecondaryTextColor = ContrastColorUtil.findContrastColor(
mSecondaryTextColor,
backgroundColor,
true /* findFG */,
4.5f);
} else {
mSecondaryTextColor
= ContrastColorUtil.findContrastColorAgainstDark(
mSecondaryTextColor,
backgroundColor,
true /* findFG */,
4.5f);
}
mPrimaryTextColor = ContrastColorUtil.changeColorLightness(
mSecondaryTextColor, backgroundLight
? -LIGHTNESS_TEXT_DIFFERENCE_LIGHT
: -LIGHTNESS_TEXT_DIFFERENCE_DARK);
}
}
}
}
}
private void updateBackgroundColor(RemoteViews contentView,
StandardTemplateParams p) {
if (isColorized(p)) {
contentView.setInt(R.id.status_bar_latest_event_content, "setBackgroundColor",
getBackgroundColor(p));
} else {
// Clear it!
contentView.setInt(R.id.status_bar_latest_event_content, "setBackgroundResource",
0);
}
}
/**
* @param remoteView the remote view to update the minheight in
* @param hasMinHeight does it have a mimHeight
* @hide
*/
void setContentMinHeight(RemoteViews remoteView, boolean hasMinHeight) {
int minHeight = 0;
if (hasMinHeight) {
// we need to set the minHeight of the notification
minHeight = mContext.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.notification_min_content_height);
}
remoteView.setInt(R.id.notification_main_column, "setMinimumHeight", minHeight);
}
private boolean handleProgressBar(RemoteViews contentView, Bundle ex,
StandardTemplateParams p) {
final int max = ex.getInt(EXTRA_PROGRESS_MAX, 0);
final int progress = ex.getInt(EXTRA_PROGRESS, 0);
final boolean ind = ex.getBoolean(EXTRA_PROGRESS_INDETERMINATE);
if (p.hasProgress && (max != 0 || ind)) {
contentView.setViewVisibility(com.android.internal.R.id.progress, View.VISIBLE);
contentView.setProgressBar(
R.id.progress, max, progress, ind);
contentView.setProgressBackgroundTintList(
R.id.progress, ColorStateList.valueOf(mContext.getColor(
R.color.notification_progress_background_color)));
if (getRawColor(p) != COLOR_DEFAULT) {
int color = isColorized(p) ? getPrimaryTextColor(p) : resolveContrastColor(p);
ColorStateList colorStateList = ColorStateList.valueOf(color);
contentView.setProgressTintList(R.id.progress, colorStateList);
contentView.setProgressIndeterminateTintList(R.id.progress, colorStateList);
}
return true;
} else {
contentView.setViewVisibility(R.id.progress, View.GONE);
return false;
}
}
private void bindLargeIconAndReply(RemoteViews contentView, StandardTemplateParams p,
TemplateBindResult result) {
boolean largeIconShown = bindLargeIcon(contentView, p);
boolean replyIconShown = bindReplyIcon(contentView, p);
boolean iconContainerVisible = largeIconShown || replyIconShown;
contentView.setViewVisibility(R.id.right_icon_container,
iconContainerVisible ? View.VISIBLE : View.GONE);
int marginEnd = calculateMarginEnd(largeIconShown, replyIconShown);
contentView.setViewLayoutMarginEnd(R.id.line1, marginEnd);
contentView.setViewLayoutMarginEnd(R.id.text, marginEnd);
contentView.setViewLayoutMarginEnd(R.id.progress, marginEnd);
if (result != null) {
result.setIconMarginEnd(marginEnd);
result.setRightIconContainerVisible(iconContainerVisible);
}
}
private int calculateMarginEnd(boolean largeIconShown, boolean replyIconShown) {
int marginEnd = 0;
int contentMargin = mContext.getResources().getDimensionPixelSize(
R.dimen.notification_content_margin_end);
int iconSize = mContext.getResources().getDimensionPixelSize(
R.dimen.notification_right_icon_size);
if (replyIconShown) {
// The size of the reply icon
marginEnd += iconSize;
int replyInset = mContext.getResources().getDimensionPixelSize(
R.dimen.notification_reply_inset);
// We're subtracting the inset of the reply icon to make sure it's
// aligned nicely on the right, and remove it from the following padding
marginEnd -= replyInset * 2;
}
if (largeIconShown) {
// adding size of the right icon
marginEnd += iconSize;
if (replyIconShown) {
// We also add some padding to the reply icon if it's around
marginEnd += contentMargin;
}
}
if (replyIconShown || largeIconShown) {
// The padding to the content
marginEnd += contentMargin;
}
return marginEnd;
}
/**
* Bind the large icon.
* @return if the largeIcon is visible
*/
private boolean bindLargeIcon(RemoteViews contentView, StandardTemplateParams p) {
if (mN.mLargeIcon == null && mN.largeIcon != null) {
mN.mLargeIcon = Icon.createWithBitmap(mN.largeIcon);
}
boolean showLargeIcon = mN.mLargeIcon != null && !p.hideLargeIcon;
if (showLargeIcon) {
contentView.setViewVisibility(R.id.right_icon, View.VISIBLE);
contentView.setImageViewIcon(R.id.right_icon, mN.mLargeIcon);
processLargeLegacyIcon(mN.mLargeIcon, contentView, p);
}
return showLargeIcon;
}
/**
* Bind the reply icon.
* @return if the reply icon is visible
*/
private boolean bindReplyIcon(RemoteViews contentView, StandardTemplateParams p) {
boolean actionVisible = !p.hideReplyIcon;
Action action = null;
if (actionVisible) {
action = findReplyAction();
actionVisible = action != null;
}
if (actionVisible) {
contentView.setViewVisibility(R.id.reply_icon_action, View.VISIBLE);
contentView.setDrawableTint(R.id.reply_icon_action,
false /* targetBackground */,
getNeutralColor(p),
PorterDuff.Mode.SRC_ATOP);
contentView.setOnClickPendingIntent(R.id.reply_icon_action, action.actionIntent);
contentView.setRemoteInputs(R.id.reply_icon_action, action.mRemoteInputs);
} else {
contentView.setRemoteInputs(R.id.reply_icon_action, null);
}
contentView.setViewVisibility(R.id.reply_icon_action,
actionVisible ? View.VISIBLE : View.GONE);
return actionVisible;
}
private Action findReplyAction() {
ArrayList
* If the platform does not provide large-format notifications, this method has no effect. The
* user will always see the normal notification view.
*
*
* If the app is targeting Android P and above, it is required to use the {@link Person}
* class in order to get an optimal rendering of the notification and its avatars. For
* conversations involving multiple people, the app should also make sure that it marks the
* conversation as a group with {@link #setGroupConversation(boolean)}.
*
*
* This class is a "rebuilder": It attaches to a Builder object and modifies its behavior.
* Here's an example of how this may be used:
* This API's behavior was changed in SDK version {@link Build.VERSION_CODES#P}. If your
* application's target version is less than {@link Build.VERSION_CODES#P}, setting a
* conversation title to a non-null value will make {@link #isGroupConversation()} return
* {@code true} and passing {@code null} will make it return {@code false}. In
* {@link Build.VERSION_CODES#P} and beyond, use {@link #setGroupConversation(boolean)}
* to set group conversation status.
*
* @param conversationTitle Title displayed for this conversation
* @return this object for method chaining
*/
public MessagingStyle setConversationTitle(@Nullable CharSequence conversationTitle) {
mConversationTitle = conversationTitle;
return this;
}
/**
* Return the title to be displayed on this conversation. May return {@code null}.
*/
@Nullable
public CharSequence getConversationTitle() {
return mConversationTitle;
}
/**
* Adds a message for display by this notification. Convenience call for a simple
* {@link Message} in {@link #addMessage(Notification.MessagingStyle.Message)}.
* @param text A {@link CharSequence} to be displayed as the message content
* @param timestamp Time at which the message arrived
* @param sender A {@link CharSequence} to be used for displaying the name of the
* sender. Should be The messages should be added in chronologic order, i.e. the oldest first,
* the newest last.
*
* @param message The {@link Message} to be displayed
* @return this object for method chaining
*/
public MessagingStyle addMessage(Message message) {
mMessages.add(message);
if (mMessages.size() > MAXIMUM_RETAINED_MESSAGES) {
mMessages.remove(0);
}
return this;
}
/**
* Adds a {@link Message} for historic context in this notification.
*
* Messages should be added as historic if they are not the main subject of the
* notification but may give context to a conversation. The system may choose to present
* them only when relevant, e.g. when replying to a message through a {@link RemoteInput}.
*
* The messages should be added in chronologic order, i.e. the oldest first,
* the newest last.
*
* @param message The historic {@link Message} to be added
* @return this object for method chaining
*/
public MessagingStyle addHistoricMessage(Message message) {
mHistoricMessages.add(message);
if (mHistoricMessages.size() > MAXIMUM_RETAINED_MESSAGES) {
mHistoricMessages.remove(0);
}
return this;
}
/**
* Gets the list of {@code Message} objects that represent the notification
*/
public List If the application that generated this {@link MessagingStyle} targets an SDK version
* less than {@link Build.VERSION_CODES#P}, this method becomes dependent on whether or
* not the conversation title is set; returning {@code true} if the conversation title is
* a non-null value, or {@code false} otherwise. From {@link Build.VERSION_CODES#P} forward,
* this method returns what's set by {@link #setGroupConversation(boolean)} allowing for
* named, non-group conversations.
*
* @see #setConversationTitle(CharSequence)
*/
public boolean isGroupConversation() {
// When target SDK version is < P, a non-null conversation title dictates if this is
// as group conversation.
if (mBuilder != null
&& mBuilder.mContext.getApplicationInfo().targetSdkVersion
< Build.VERSION_CODES.P) {
return mConversationTitle != null;
}
return mIsGroupConversation;
}
/**
* @hide
*/
@Override
public void addExtras(Bundle extras) {
super.addExtras(extras);
if (mUser != null) {
// For legacy usages
extras.putCharSequence(EXTRA_SELF_DISPLAY_NAME, mUser.getName());
extras.putParcelable(EXTRA_MESSAGING_PERSON, mUser);
}
if (mConversationTitle != null) {
extras.putCharSequence(EXTRA_CONVERSATION_TITLE, mConversationTitle);
}
if (!mMessages.isEmpty()) { extras.putParcelableArray(EXTRA_MESSAGES,
Message.getBundleArrayForMessages(mMessages));
}
if (!mHistoricMessages.isEmpty()) { extras.putParcelableArray(EXTRA_HISTORIC_MESSAGES,
Message.getBundleArrayForMessages(mHistoricMessages));
}
fixTitleAndTextExtras(extras);
extras.putBoolean(EXTRA_IS_GROUP_CONVERSATION, mIsGroupConversation);
}
private void fixTitleAndTextExtras(Bundle extras) {
Message m = findLatestIncomingMessage();
CharSequence text = (m == null) ? null : m.mText;
CharSequence sender = m == null ? null
: m.mSender == null || TextUtils.isEmpty(m.mSender.getName())
? mUser.getName() : m.mSender.getName();
CharSequence title;
if (!TextUtils.isEmpty(mConversationTitle)) {
if (!TextUtils.isEmpty(sender)) {
BidiFormatter bidi = BidiFormatter.getInstance();
title = mBuilder.mContext.getString(
com.android.internal.R.string.notification_messaging_title_template,
bidi.unicodeWrap(mConversationTitle), bidi.unicodeWrap(sender));
} else {
title = mConversationTitle;
}
} else {
title = sender;
}
if (title != null) {
extras.putCharSequence(EXTRA_TITLE, title);
}
if (text != null) {
extras.putCharSequence(EXTRA_TEXT, text);
}
}
/**
* @hide
*/
@Override
protected void restoreFromExtras(Bundle extras) {
super.restoreFromExtras(extras);
mUser = extras.getParcelable(EXTRA_MESSAGING_PERSON);
if (mUser == null) {
CharSequence displayName = extras.getCharSequence(EXTRA_SELF_DISPLAY_NAME);
mUser = new Person.Builder().setName(displayName).build();
}
mConversationTitle = extras.getCharSequence(EXTRA_CONVERSATION_TITLE);
Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES);
mMessages = Message.getMessagesFromBundleArray(messages);
Parcelable[] histMessages = extras.getParcelableArray(EXTRA_HISTORIC_MESSAGES);
mHistoricMessages = Message.getMessagesFromBundleArray(histMessages);
mIsGroupConversation = extras.getBoolean(EXTRA_IS_GROUP_CONVERSATION);
}
/**
* @hide
*/
@Override
public RemoteViews makeContentView(boolean increasedHeight) {
mBuilder.mOriginalActions = mBuilder.mActions;
mBuilder.mActions = new ArrayList<>();
RemoteViews remoteViews = makeMessagingView(true /* displayImagesAtEnd */,
false /* hideLargeIcon */);
mBuilder.mActions = mBuilder.mOriginalActions;
mBuilder.mOriginalActions = null;
return remoteViews;
}
/**
* @hide
* Spans are ignored when comparing text for visual difference.
*/
@Override
public boolean areNotificationsVisiblyDifferent(Style other) {
if (other == null || getClass() != other.getClass()) {
return true;
}
MessagingStyle newS = (MessagingStyle) other;
List
* The person provided should contain an Icon, set with
* {@link Person.Builder#setIcon(Icon)} and also have a name provided
* with {@link Person.Builder#setName(CharSequence)}. If multiple users have the same
* name, consider providing a key with {@link Person.Builder#setKey(String)} in order
* to differentiate between the different users.
*
* The person provided should contain an Icon, set with
* {@link Person.Builder#setIcon(Icon)} and also have a name provided
* with {@link Person.Builder#setName(CharSequence)}. If multiple users have the same
* name, consider providing a key with {@link Person.Builder#setKey(String)} in order
* to differentiate between the different users.
*
*
* Unlike the other styles provided here, MediaStyle can also modify the standard-size
* {@link Notification#contentView}; by providing action indices to
* {@link #setShowActionsInCompactView(int...)} you can promote up to 3 actions to be displayed
* in the standard view alongside the usual content.
*
* Notifications created with MediaStyle will have their category set to
* {@link Notification#CATEGORY_TRANSPORT CATEGORY_TRANSPORT} unless you set a different
* category using {@link Notification.Builder#setCategory(String) setCategory()}.
*
* Finally, if you attach a {@link android.media.session.MediaSession.Token} using
* {@link android.app.Notification.MediaStyle#setMediaSession(MediaSession.Token)},
* the System UI can identify this as a notification representing an active media session
* and respond accordingly (by showing album artwork in the lockscreen, for example).
*
*
* Starting at {@link android.os.Build.VERSION_CODES#O Android O} any notification that has a
* media session attached with {@link #setMediaSession(MediaSession.Token)} will be colorized.
* You can opt-out of this behavior by using {@link Notification.Builder#setColorized(boolean)}.
*
*
* To use this style with your Notification, feed it to
* {@link Notification.Builder#setStyle(android.app.Notification.Style)} like so:
* Instead of providing a notification that is completely custom, a developer can set this
* style and still obtain system decorations like the notification header with the expand
* affordance and actions.
*
* Use {@link android.app.Notification.Builder#setCustomContentView(RemoteViews)},
* {@link android.app.Notification.Builder#setCustomBigContentView(RemoteViews)} and
* {@link android.app.Notification.Builder#setCustomHeadsUpContentView(RemoteViews)} to set the
* corresponding custom views to display.
*
* To use this style with your Notification, feed it to
* {@link Notification.Builder#setStyle(android.app.Notification.Style)} like so:
* Instead of providing a media notification that is completely custom, a developer can set
* this style and still obtain system decorations like the notification header with the expand
* affordance and actions.
*
* Use {@link android.app.Notification.Builder#setCustomContentView(RemoteViews)},
* {@link android.app.Notification.Builder#setCustomBigContentView(RemoteViews)} and
* {@link android.app.Notification.Builder#setCustomHeadsUpContentView(RemoteViews)} to set the
* corresponding custom views to display.
*
* Contrary to {@link MediaStyle} a developer has to opt-in to the colorizing of the
* notification by using {@link Notification.Builder#setColorized(boolean)}.
*
* To use this style with your Notification, feed it to
* {@link Notification.Builder#setStyle(android.app.Notification.Style)} like so:
* A bubble is used to display app content in a floating window over the existing
* foreground activity. A bubble has a collapsed state represented by an icon,
* {@link BubbleMetadata.Builder#setIcon(Icon)} and an expanded state which is populated
* via {@link BubbleMetadata.Builder#setIntent(PendingIntent)}. If the app creating the bubble is not in the foreground this flag has no effect. Generally this flag should only be set if the user has performed an action to request
* or create a bubble. If the app posting the bubble is not in the foreground this flag has no effect. Generally this flag should only be set if the user has performed an action to request
* or create a bubble, or if the user has seen the content in the notification and the
* notification is no longer relevant. An intent is required. An icon is required and should be representative of the content within the bubble.
* If your app produces multiple bubbles, the image should be unique for each of them.
* The shape of a bubble icon is adaptive and can match the device theme.
*
* If your icon is bitmap-based, you should create it using
* {@link Icon#createWithAdaptiveBitmap(Bitmap)}, otherwise this method will throw.
*
* If your icon is not bitmap-based, you should expect that the icon will be tinted.
* This height may not be respected if there is not enough space on the screen or if
* the provided height is too small to be useful. If {@link #setDesiredHeightResId(int)} was previously called on this builder, the
* previous value set will be cleared after calling this method, and this value will
* be used instead. A desired height (in DPs or via resID) is optional. This height may not be respected if there is not enough space on the screen or if
* the provided height is too small to be useful. If {@link #setDesiredHeight(int)} was previously called on this builder, the
* previous value set will be cleared after calling this method, and this value will
* be used instead. A desired height (in DPs or via resID) is optional. This flag has no effect if the app posting the bubble is not in the foreground.
* Generally, this flag should only be set if the user has performed an action to
* request or create a bubble. Setting this flag is optional; it defaults to false. This flag has no effect if the app posting the bubble is not in the foreground.
* Generally, this flag should only be set if the user has performed an action to
* request or create a bubble, or if the user has seen the content in the notification
* and the notification is no longer relevant. Setting this flag is optional; it defaults to false. Setting a delete intent is optional. See
* Creating Notifications
* for Android Wear for more information on how to use this class.
*
* To create a notification with wearable extensions:
* Wearable extensions can be accessed on an existing notification by using the
* {@code WearableExtender(Notification)} constructor,
* and then using the {@code get} methods to access values.
*
* For custom display notifications created using {@link #setDisplayIntent},
* the default is {@link #SIZE_MEDIUM}. All other notifications size automatically based
* on their content.
*
* @deprecated Display intents are no longer supported.
*/
@Deprecated
public static final int SIZE_DEFAULT = 0;
/**
* Size value for use with {@link #setCustomSizePreset} to show this notification
* with an extra small size.
* This value is only applicable for custom display notifications created using
* {@link #setDisplayIntent}.
*
* @deprecated Display intents are no longer supported.
*/
@Deprecated
public static final int SIZE_XSMALL = 1;
/**
* Size value for use with {@link #setCustomSizePreset} to show this notification
* with a small size.
* This value is only applicable for custom display notifications created using
* {@link #setDisplayIntent}.
*
* @deprecated Display intents are no longer supported.
*/
@Deprecated
public static final int SIZE_SMALL = 2;
/**
* Size value for use with {@link #setCustomSizePreset} to show this notification
* with a medium size.
* This value is only applicable for custom display notifications created using
* {@link #setDisplayIntent}.
*
* @deprecated Display intents are no longer supported.
*/
@Deprecated
public static final int SIZE_MEDIUM = 3;
/**
* Size value for use with {@link #setCustomSizePreset} to show this notification
* with a large size.
* This value is only applicable for custom display notifications created using
* {@link #setDisplayIntent}.
*
* @deprecated Display intents are no longer supported.
*/
@Deprecated
public static final int SIZE_LARGE = 4;
/**
* Size value for use with {@link #setCustomSizePreset} to show this notification
* full screen.
* This value is only applicable for custom display notifications created using
* {@link #setDisplayIntent}.
*
* @deprecated Display intents are no longer supported.
*/
@Deprecated
public static final int SIZE_FULL_SCREEN = 5;
/**
* Sentinel value for use with {@link #setHintScreenTimeout} to keep the screen on for a
* short amount of time when this notification is displayed on the screen. This
* is the default value.
*
* @deprecated This feature is no longer supported.
*/
@Deprecated
public static final int SCREEN_TIMEOUT_SHORT = 0;
/**
* Sentinel value for use with {@link #setHintScreenTimeout} to keep the screen on
* for a longer amount of time when this notification is displayed on the screen.
*
* @deprecated This feature is no longer supported.
*/
@Deprecated
public static final int SCREEN_TIMEOUT_LONG = -1;
/** Notification extra which contains wearable extensions */
private static final String EXTRA_WEARABLE_EXTENSIONS = "android.wearable.EXTENSIONS";
// Keys within EXTRA_WEARABLE_EXTENSIONS for wearable options.
private static final String KEY_ACTIONS = "actions";
private static final String KEY_FLAGS = "flags";
private static final String KEY_DISPLAY_INTENT = "displayIntent";
private static final String KEY_PAGES = "pages";
private static final String KEY_BACKGROUND = "background";
private static final String KEY_CONTENT_ICON = "contentIcon";
private static final String KEY_CONTENT_ICON_GRAVITY = "contentIconGravity";
private static final String KEY_CONTENT_ACTION_INDEX = "contentActionIndex";
private static final String KEY_CUSTOM_SIZE_PRESET = "customSizePreset";
private static final String KEY_CUSTOM_CONTENT_HEIGHT = "customContentHeight";
private static final String KEY_GRAVITY = "gravity";
private static final String KEY_HINT_SCREEN_TIMEOUT = "hintScreenTimeout";
private static final String KEY_DISMISSAL_ID = "dismissalId";
private static final String KEY_BRIDGE_TAG = "bridgeTag";
// Flags bitwise-ored to mFlags
private static final int FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE = 0x1;
private static final int FLAG_HINT_HIDE_ICON = 1 << 1;
private static final int FLAG_HINT_SHOW_BACKGROUND_ONLY = 1 << 2;
private static final int FLAG_START_SCROLL_BOTTOM = 1 << 3;
private static final int FLAG_HINT_AVOID_BACKGROUND_CLIPPING = 1 << 4;
private static final int FLAG_BIG_PICTURE_AMBIENT = 1 << 5;
private static final int FLAG_HINT_CONTENT_INTENT_LAUNCHES_ACTIVITY = 1 << 6;
// Default value for flags integer
private static final int DEFAULT_FLAGS = FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE;
private static final int DEFAULT_CONTENT_ICON_GRAVITY = Gravity.END;
private static final int DEFAULT_GRAVITY = Gravity.BOTTOM;
private ArrayList When wearable actions are added using this method, the set of actions that
* show on a wearable device splits from devices that only show actions added
* using {@link android.app.Notification.Builder#addAction}. This allows for customization
* of which actions display on different devices.
*
* @param action the action to add to this notification
* @return this object for method chaining
* @see android.app.Notification.Action
*/
public WearableExtender addAction(Action action) {
mActions.add(action);
return this;
}
/**
* Adds wearable actions to this notification.
*
* When wearable actions are added using this method, the set of actions that
* show on a wearable device splits from devices that only show actions added
* using {@link android.app.Notification.Builder#addAction}. This allows for customization
* of which actions display on different devices.
*
* @param actions the actions to add to this notification
* @return this object for method chaining
* @see android.app.Notification.Action
*/
public WearableExtender addActions(List The activity to launch needs to allow embedding, must be exported, and
* should have an empty task affinity. It is also recommended to use the device
* default light theme.
*
* Example AndroidManifest.xml entry:
* If wearable specific actions were added to the main notification, this index will
* apply to that list, otherwise it will apply to the regular actions list.
*
* @return the action index or {@link #UNSET_ACTION_INDEX} if no action was selected.
*/
public int getContentAction() {
return mContentActionIndex;
}
/**
* Set the gravity that this notification should have within the available viewport space.
* Supported values include {@link android.view.Gravity#TOP},
* {@link android.view.Gravity#CENTER_VERTICAL} and {@link android.view.Gravity#BOTTOM}.
* The default value is {@link android.view.Gravity#BOTTOM}.
*/
@Deprecated
public WearableExtender setGravity(int gravity) {
mGravity = gravity;
return this;
}
/**
* Get the gravity that this notification should have within the available viewport space.
* Supported values include {@link android.view.Gravity#TOP},
* {@link android.view.Gravity#CENTER_VERTICAL} and {@link android.view.Gravity#BOTTOM}.
* The default value is {@link android.view.Gravity#BOTTOM}.
*/
@Deprecated
public int getGravity() {
return mGravity;
}
/**
* Set the custom size preset for the display of this notification out of the available
* presets found in {@link android.app.Notification.WearableExtender}, e.g.
* {@link #SIZE_LARGE}.
* Some custom size presets are only applicable for custom display notifications created
* using {@link android.app.Notification.WearableExtender#setDisplayIntent}. Check the
* documentation for the preset in question. See also
* {@link #setCustomContentHeight} and {@link #getCustomSizePreset}.
*/
@Deprecated
public WearableExtender setCustomSizePreset(int sizePreset) {
mCustomSizePreset = sizePreset;
return this;
}
/**
* Get the custom size preset for the display of this notification out of the available
* presets found in {@link android.app.Notification.WearableExtender}, e.g.
* {@link #SIZE_LARGE}.
* Some custom size presets are only applicable for custom display notifications created
* using {@link #setDisplayIntent}. Check the documentation for the preset in question.
* See also {@link #setCustomContentHeight} and {@link #setCustomSizePreset}.
*/
@Deprecated
public int getCustomSizePreset() {
return mCustomSizePreset;
}
/**
* Set the custom height in pixels for the display of this notification's content.
* This option is only available for custom display notifications created
* using {@link android.app.Notification.WearableExtender#setDisplayIntent}. See also
* {@link android.app.Notification.WearableExtender#setCustomSizePreset} and
* {@link #getCustomContentHeight}.
*/
@Deprecated
public WearableExtender setCustomContentHeight(int height) {
mCustomContentHeight = height;
return this;
}
/**
* Get the custom height in pixels for the display of this notification's content.
* This option is only available for custom display notifications created
* using {@link #setDisplayIntent}. See also {@link #setCustomSizePreset} and
* {@link #setCustomContentHeight}.
*/
@Deprecated
public int getCustomContentHeight() {
return mCustomContentHeight;
}
/**
* Set whether the scrolling position for the contents of this notification should start
* at the bottom of the contents instead of the top when the contents are too long to
* display within the screen. Default is false (start scroll at the top).
*/
public WearableExtender setStartScrollBottom(boolean startScrollBottom) {
setFlag(FLAG_START_SCROLL_BOTTOM, startScrollBottom);
return this;
}
/**
* Get whether the scrolling position for the contents of this notification should start
* at the bottom of the contents instead of the top when the contents are too long to
* display within the screen. Default is false (start scroll at the top).
*/
public boolean getStartScrollBottom() {
return (mFlags & FLAG_START_SCROLL_BOTTOM) != 0;
}
/**
* Set whether the content intent is available when the wearable device is not connected
* to a companion device. The user can still trigger this intent when the wearable device
* is offline, but a visual hint will indicate that the content intent may not be available.
* Defaults to true.
*/
public WearableExtender setContentIntentAvailableOffline(
boolean contentIntentAvailableOffline) {
setFlag(FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE, contentIntentAvailableOffline);
return this;
}
/**
* Get whether the content intent is available when the wearable device is not connected
* to a companion device. The user can still trigger this intent when the wearable device
* is offline, but a visual hint will indicate that the content intent may not be available.
* Defaults to true.
*/
public boolean getContentIntentAvailableOffline() {
return (mFlags & FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE) != 0;
}
/**
* Set a hint that this notification's icon should not be displayed.
* @param hintHideIcon {@code true} to hide the icon, {@code false} otherwise.
* @return this object for method chaining
*/
@Deprecated
public WearableExtender setHintHideIcon(boolean hintHideIcon) {
setFlag(FLAG_HINT_HIDE_ICON, hintHideIcon);
return this;
}
/**
* Get a hint that this notification's icon should not be displayed.
* @return {@code true} if this icon should not be displayed, false otherwise.
* The default value is {@code false} if this was never set.
*/
@Deprecated
public boolean getHintHideIcon() {
return (mFlags & FLAG_HINT_HIDE_ICON) != 0;
}
/**
* Set a visual hint that only the background image of this notification should be
* displayed, and other semantic content should be hidden. This hint is only applicable
* to sub-pages added using {@link #addPage}.
*/
@Deprecated
public WearableExtender setHintShowBackgroundOnly(boolean hintShowBackgroundOnly) {
setFlag(FLAG_HINT_SHOW_BACKGROUND_ONLY, hintShowBackgroundOnly);
return this;
}
/**
* Get a visual hint that only the background image of this notification should be
* displayed, and other semantic content should be hidden. This hint is only applicable
* to sub-pages added using {@link android.app.Notification.WearableExtender#addPage}.
*/
@Deprecated
public boolean getHintShowBackgroundOnly() {
return (mFlags & FLAG_HINT_SHOW_BACKGROUND_ONLY) != 0;
}
/**
* Set a hint that this notification's background should not be clipped if possible,
* and should instead be resized to fully display on the screen, retaining the aspect
* ratio of the image. This can be useful for images like barcodes or qr codes.
* @param hintAvoidBackgroundClipping {@code true} to avoid clipping if possible.
* @return this object for method chaining
*/
@Deprecated
public WearableExtender setHintAvoidBackgroundClipping(
boolean hintAvoidBackgroundClipping) {
setFlag(FLAG_HINT_AVOID_BACKGROUND_CLIPPING, hintAvoidBackgroundClipping);
return this;
}
/**
* Get a hint that this notification's background should not be clipped if possible,
* and should instead be resized to fully display on the screen, retaining the aspect
* ratio of the image. This can be useful for images like barcodes or qr codes.
* @return {@code true} if it's ok if the background is clipped on the screen, false
* otherwise. The default value is {@code false} if this was never set.
*/
@Deprecated
public boolean getHintAvoidBackgroundClipping() {
return (mFlags & FLAG_HINT_AVOID_BACKGROUND_CLIPPING) != 0;
}
/**
* Set a hint that the screen should remain on for at least this duration when
* this notification is displayed on the screen.
* @param timeout The requested screen timeout in milliseconds. Can also be either
* {@link #SCREEN_TIMEOUT_SHORT} or {@link #SCREEN_TIMEOUT_LONG}.
* @return this object for method chaining
*/
@Deprecated
public WearableExtender setHintScreenTimeout(int timeout) {
mHintScreenTimeout = timeout;
return this;
}
/**
* Get the duration, in milliseconds, that the screen should remain on for
* when this notification is displayed.
* @return the duration in milliseconds if > 0, or either one of the sentinel values
* {@link #SCREEN_TIMEOUT_SHORT} or {@link #SCREEN_TIMEOUT_LONG}.
*/
@Deprecated
public int getHintScreenTimeout() {
return mHintScreenTimeout;
}
/**
* Set a hint that this notification's {@link BigPictureStyle} (if present) should be
* converted to low-bit and displayed in ambient mode, especially useful for barcodes and
* qr codes, as well as other simple black-and-white tickets.
* @param hintAmbientBigPicture {@code true} to enable converstion and ambient.
* @return this object for method chaining
* @deprecated This feature is no longer supported.
*/
@Deprecated
public WearableExtender setHintAmbientBigPicture(boolean hintAmbientBigPicture) {
setFlag(FLAG_BIG_PICTURE_AMBIENT, hintAmbientBigPicture);
return this;
}
/**
* Get a hint that this notification's {@link BigPictureStyle} (if present) should be
* converted to low-bit and displayed in ambient mode, especially useful for barcodes and
* qr codes, as well as other simple black-and-white tickets.
* @return {@code true} if it should be displayed in ambient, false otherwise
* otherwise. The default value is {@code false} if this was never set.
* @deprecated This feature is no longer supported.
*/
@Deprecated
public boolean getHintAmbientBigPicture() {
return (mFlags & FLAG_BIG_PICTURE_AMBIENT) != 0;
}
/**
* Set a hint that this notification's content intent will launch an {@link Activity}
* directly, telling the platform that it can generate the appropriate transitions.
* @param hintContentIntentLaunchesActivity {@code true} if the content intent will launch
* an activity and transitions should be generated, false otherwise.
* @return this object for method chaining
*/
public WearableExtender setHintContentIntentLaunchesActivity(
boolean hintContentIntentLaunchesActivity) {
setFlag(FLAG_HINT_CONTENT_INTENT_LAUNCHES_ACTIVITY, hintContentIntentLaunchesActivity);
return this;
}
/**
* Get a hint that this notification's content intent will launch an {@link Activity}
* directly, telling the platform that it can generate the appropriate transitions
* @return {@code true} if the content intent will launch an activity and transitions should
* be generated, false otherwise. The default value is {@code false} if this was never set.
*/
public boolean getHintContentIntentLaunchesActivity() {
return (mFlags & FLAG_HINT_CONTENT_INTENT_LAUNCHES_ACTIVITY) != 0;
}
/**
* Sets the dismissal id for this notification. If a notification is posted with a
* dismissal id, then when that notification is canceled, notifications on other wearables
* and the paired Android phone having that same dismissal id will also be canceled. See
* Adding Wearable Features to
* Notifications for more information.
* @param dismissalId the dismissal id of the notification.
* @return this object for method chaining
*/
public WearableExtender setDismissalId(String dismissalId) {
mDismissalId = dismissalId;
return this;
}
/**
* Returns the dismissal id of the notification.
* @return the dismissal id of the notification or null if it has not been set.
*/
public String getDismissalId() {
return mDismissalId;
}
/**
* Sets a bridge tag for this notification. A bridge tag can be set for notifications
* posted from a phone to provide finer-grained control on what notifications are bridged
* to wearables. See Adding Wearable
* Features to Notifications for more information.
* @param bridgeTag the bridge tag of the notification.
* @return this object for method chaining
*/
public WearableExtender setBridgeTag(String bridgeTag) {
mBridgeTag = bridgeTag;
return this;
}
/**
* Returns the bridge tag of the notification.
* @return the bridge tag or null if not present.
*/
public String getBridgeTag() {
return mBridgeTag;
}
private void setFlag(int mask, boolean value) {
if (value) {
mFlags |= mask;
} else {
mFlags &= ~mask;
}
}
}
/**
* Helper class to add Android Auto extensions to notifications. To create a notification
* with car extensions:
*
* Car extensions can be accessed on an existing notification by using the
* {@code CarExtender(Notification)} constructor, and then using the {@code get} methods
* to access values.
*/
public static final class CarExtender implements Extender {
private static final String TAG = "CarExtender";
private static final String EXTRA_CAR_EXTENDER = "android.car.EXTENSIONS";
private static final String EXTRA_LARGE_ICON = "large_icon";
private static final String EXTRA_CONVERSATION = "car_conversation";
private static final String EXTRA_COLOR = "app_color";
private Bitmap mLargeIcon;
private UnreadConversation mUnreadConversation;
private int mColor = Notification.COLOR_DEFAULT;
/**
* Create a {@link CarExtender} with default options.
*/
public CarExtender() {
}
/**
* Create a {@link CarExtender} from the CarExtender options of an existing Notification.
*
* @param notif The notification from which to copy options.
*/
public CarExtender(Notification notif) {
Bundle carBundle = notif.extras == null ?
null : notif.extras.getBundle(EXTRA_CAR_EXTENDER);
if (carBundle != null) {
mLargeIcon = carBundle.getParcelable(EXTRA_LARGE_ICON);
mColor = carBundle.getInt(EXTRA_COLOR, Notification.COLOR_DEFAULT);
Bundle b = carBundle.getBundle(EXTRA_CONVERSATION);
mUnreadConversation = UnreadConversation.getUnreadConversationFromBundle(b);
}
}
/**
* Apply car extensions to a notification that is being built. This is typically called by
* the {@link Notification.Builder#extend(Notification.Extender)}
* method of {@link Notification.Builder}.
*/
@Override
public Notification.Builder extend(Notification.Builder builder) {
Bundle carExtensions = new Bundle();
if (mLargeIcon != null) {
carExtensions.putParcelable(EXTRA_LARGE_ICON, mLargeIcon);
}
if (mColor != Notification.COLOR_DEFAULT) {
carExtensions.putInt(EXTRA_COLOR, mColor);
}
if (mUnreadConversation != null) {
Bundle b = mUnreadConversation.getBundleForUnreadConversation();
carExtensions.putBundle(EXTRA_CONVERSATION, b);
}
builder.getExtras().putBundle(EXTRA_CAR_EXTENDER, carExtensions);
return builder;
}
/**
* Sets the accent color to use when Android Auto presents the notification.
*
* Android Auto uses the color set with {@link Notification.Builder#setColor(int)}
* to accent the displayed notification. However, not all colors are acceptable in an
* automotive setting. This method can be used to override the color provided in the
* notification in such a situation.
*/
public CarExtender setColor(@ColorInt int color) {
mColor = color;
return this;
}
/**
* Gets the accent color.
*
* @see #setColor
*/
@ColorInt
public int getColor() {
return mColor;
}
/**
* Sets the large icon of the car notification.
*
* If no large icon is set in the extender, Android Auto will display the icon
* specified by {@link Notification.Builder#setLargeIcon(android.graphics.Bitmap)}
*
* @param largeIcon The large icon to use in the car notification.
* @return This object for method chaining.
*/
public CarExtender setLargeIcon(Bitmap largeIcon) {
mLargeIcon = largeIcon;
return this;
}
/**
* Gets the large icon used in this car notification, or null if no icon has been set.
*
* @return The large icon for the car notification.
* @see CarExtender#setLargeIcon
*/
public Bitmap getLargeIcon() {
return mLargeIcon;
}
/**
* Sets the unread conversation in a message notification.
*
* @param unreadConversation The unread part of the conversation this notification conveys.
* @return This object for method chaining.
*/
public CarExtender setUnreadConversation(UnreadConversation unreadConversation) {
mUnreadConversation = unreadConversation;
return this;
}
/**
* Returns the unread conversation conveyed by this notification.
* @see #setUnreadConversation(UnreadConversation)
*/
public UnreadConversation getUnreadConversation() {
return mUnreadConversation;
}
/**
* A class which holds the unread messages from a conversation.
*/
public static class UnreadConversation {
private static final String KEY_AUTHOR = "author";
private static final String KEY_TEXT = "text";
private static final String KEY_MESSAGES = "messages";
private static final String KEY_REMOTE_INPUT = "remote_input";
private static final String KEY_ON_REPLY = "on_reply";
private static final String KEY_ON_READ = "on_read";
private static final String KEY_PARTICIPANTS = "participants";
private static final String KEY_TIMESTAMP = "timestamp";
private final String[] mMessages;
private final RemoteInput mRemoteInput;
private final PendingIntent mReplyPendingIntent;
private final PendingIntent mReadPendingIntent;
private final String[] mParticipants;
private final long mLatestTimestamp;
UnreadConversation(String[] messages, RemoteInput remoteInput,
PendingIntent replyPendingIntent, PendingIntent readPendingIntent,
String[] participants, long latestTimestamp) {
mMessages = messages;
mRemoteInput = remoteInput;
mReadPendingIntent = readPendingIntent;
mReplyPendingIntent = replyPendingIntent;
mParticipants = participants;
mLatestTimestamp = latestTimestamp;
}
/**
* Gets the list of messages conveyed by this notification.
*/
public String[] getMessages() {
return mMessages;
}
/**
* Gets the remote input that will be used to convey the response to a message list, or
* null if no such remote input exists.
*/
public RemoteInput getRemoteInput() {
return mRemoteInput;
}
/**
* Gets the pending intent that will be triggered when the user replies to this
* notification.
*/
public PendingIntent getReplyPendingIntent() {
return mReplyPendingIntent;
}
/**
* Gets the pending intent that Android Auto will send after it reads aloud all messages
* in this object's message list.
*/
public PendingIntent getReadPendingIntent() {
return mReadPendingIntent;
}
/**
* Gets the participants in the conversation.
*/
public String[] getParticipants() {
return mParticipants;
}
/**
* Gets the firs participant in the conversation.
*/
public String getParticipant() {
return mParticipants.length > 0 ? mParticipants[0] : null;
}
/**
* Gets the timestamp of the conversation.
*/
public long getLatestTimestamp() {
return mLatestTimestamp;
}
Bundle getBundleForUnreadConversation() {
Bundle b = new Bundle();
String author = null;
if (mParticipants != null && mParticipants.length > 1) {
author = mParticipants[0];
}
Parcelable[] messages = new Parcelable[mMessages.length];
for (int i = 0; i < messages.length; i++) {
Bundle m = new Bundle();
m.putString(KEY_TEXT, mMessages[i]);
m.putString(KEY_AUTHOR, author);
messages[i] = m;
}
b.putParcelableArray(KEY_MESSAGES, messages);
if (mRemoteInput != null) {
b.putParcelable(KEY_REMOTE_INPUT, mRemoteInput);
}
b.putParcelable(KEY_ON_REPLY, mReplyPendingIntent);
b.putParcelable(KEY_ON_READ, mReadPendingIntent);
b.putStringArray(KEY_PARTICIPANTS, mParticipants);
b.putLong(KEY_TIMESTAMP, mLatestTimestamp);
return b;
}
static UnreadConversation getUnreadConversationFromBundle(Bundle b) {
if (b == null) {
return null;
}
Parcelable[] parcelableMessages = b.getParcelableArray(KEY_MESSAGES);
String[] messages = null;
if (parcelableMessages != null) {
String[] tmp = new String[parcelableMessages.length];
boolean success = true;
for (int i = 0; i < tmp.length; i++) {
if (!(parcelableMessages[i] instanceof Bundle)) {
success = false;
break;
}
tmp[i] = ((Bundle) parcelableMessages[i]).getString(KEY_TEXT);
if (tmp[i] == null) {
success = false;
break;
}
}
if (success) {
messages = tmp;
} else {
return null;
}
}
PendingIntent onRead = b.getParcelable(KEY_ON_READ);
PendingIntent onReply = b.getParcelable(KEY_ON_REPLY);
RemoteInput remoteInput = b.getParcelable(KEY_REMOTE_INPUT);
String[] participants = b.getStringArray(KEY_PARTICIPANTS);
if (participants == null || participants.length != 1) {
return null;
}
return new UnreadConversation(messages,
remoteInput,
onReply,
onRead,
participants, b.getLong(KEY_TIMESTAMP));
}
};
/**
* Builder class for {@link CarExtender.UnreadConversation} objects.
*/
public static class Builder {
private final List Helper class to add Android TV extensions to notifications. To create a notification
* with a TV extension:
*
* TV extensions can be accessed on an existing notification by using the
* {@code TvExtender(Notification)} constructor, and then using the {@code get} methods
* to access values.
*
* @hide
*/
@SystemApi
public static final class TvExtender implements Extender {
private static final String TAG = "TvExtender";
private static final String EXTRA_TV_EXTENDER = "android.tv.EXTENSIONS";
private static final String EXTRA_FLAGS = "flags";
private static final String EXTRA_CONTENT_INTENT = "content_intent";
private static final String EXTRA_DELETE_INTENT = "delete_intent";
private static final String EXTRA_CHANNEL_ID = "channel_id";
private static final String EXTRA_SUPPRESS_SHOW_OVER_APPS = "suppressShowOverApps";
// Flags bitwise-ored to mFlags
private static final int FLAG_AVAILABLE_ON_TV = 0x1;
private int mFlags;
private String mChannelId;
private PendingIntent mContentIntent;
private PendingIntent mDeleteIntent;
private boolean mSuppressShowOverApps;
/**
* Create a {@link TvExtender} with default options.
*/
public TvExtender() {
mFlags = FLAG_AVAILABLE_ON_TV;
}
/**
* Create a {@link TvExtender} from the TvExtender options of an existing Notification.
*
* @param notif The notification from which to copy options.
*/
public TvExtender(Notification notif) {
Bundle bundle = notif.extras == null ?
null : notif.extras.getBundle(EXTRA_TV_EXTENDER);
if (bundle != null) {
mFlags = bundle.getInt(EXTRA_FLAGS);
mChannelId = bundle.getString(EXTRA_CHANNEL_ID);
mSuppressShowOverApps = bundle.getBoolean(EXTRA_SUPPRESS_SHOW_OVER_APPS);
mContentIntent = bundle.getParcelable(EXTRA_CONTENT_INTENT);
mDeleteIntent = bundle.getParcelable(EXTRA_DELETE_INTENT);
}
}
/**
* Apply a TV extension to a notification that is being built. This is typically called by
* the {@link Notification.Builder#extend(Notification.Extender)}
* method of {@link Notification.Builder}.
*/
@Override
public Notification.Builder extend(Notification.Builder builder) {
Bundle bundle = new Bundle();
bundle.putInt(EXTRA_FLAGS, mFlags);
bundle.putString(EXTRA_CHANNEL_ID, mChannelId);
bundle.putBoolean(EXTRA_SUPPRESS_SHOW_OVER_APPS, mSuppressShowOverApps);
if (mContentIntent != null) {
bundle.putParcelable(EXTRA_CONTENT_INTENT, mContentIntent);
}
if (mDeleteIntent != null) {
bundle.putParcelable(EXTRA_DELETE_INTENT, mDeleteIntent);
}
builder.getExtras().putBundle(EXTRA_TV_EXTENDER, bundle);
return builder;
}
/**
* Returns true if this notification should be shown on TV. This method return true
* if the notification was extended with a TvExtender.
*/
public boolean isAvailableOnTv() {
return (mFlags & FLAG_AVAILABLE_ON_TV) != 0;
}
/**
* Specifies the channel the notification should be delivered on when shown on TV.
* It can be different from the channel that the notification is delivered to when
* posting on a non-TV device.
*/
public TvExtender setChannel(String channelId) {
mChannelId = channelId;
return this;
}
/**
* Specifies the channel the notification should be delivered on when shown on TV.
* It can be different from the channel that the notification is delivered to when
* posting on a non-TV device.
*/
public TvExtender setChannelId(String channelId) {
mChannelId = channelId;
return this;
}
/** @removed */
@Deprecated
public String getChannel() {
return mChannelId;
}
/**
* Returns the id of the channel this notification posts to on TV.
*/
public String getChannelId() {
return mChannelId;
}
/**
* Supplies a {@link PendingIntent} to be sent when the notification is selected on TV.
* If provided, it is used instead of the content intent specified
* at the level of Notification.
*/
public TvExtender setContentIntent(PendingIntent intent) {
mContentIntent = intent;
return this;
}
/**
* Returns the TV-specific content intent. If this method returns null, the
* main content intent on the notification should be used.
*
* @see {@link Notification#contentIntent}
*/
public PendingIntent getContentIntent() {
return mContentIntent;
}
/**
* Supplies a {@link PendingIntent} to send when the notification is cleared explicitly
* by the user on TV. If provided, it is used instead of the delete intent specified
* at the level of Notification.
*/
public TvExtender setDeleteIntent(PendingIntent intent) {
mDeleteIntent = intent;
return this;
}
/**
* Returns the TV-specific delete intent. If this method returns null, the
* main delete intent on the notification should be used.
*
* @see {@link Notification#deleteIntent}
*/
public PendingIntent getDeleteIntent() {
return mDeleteIntent;
}
/**
* Specifies whether this notification should suppress showing a message over top of apps
* outside of the launcher.
*/
public TvExtender setSuppressShowOverApps(boolean suppress) {
mSuppressShowOverApps = suppress;
return this;
}
/**
* Returns true if this notification should not show messages over top of apps
* outside of the launcher.
*/
public boolean getSuppressShowOverApps() {
return mSuppressShowOverApps;
}
}
/**
* Get an array of Notification objects from a parcelable array bundle field.
* Update the bundle to have a typed array so fetches in the future don't need
* to do an array copy.
*/
private static Notification[] getNotificationArrayFromBundle(Bundle bundle, String key) {
Parcelable[] array = bundle.getParcelableArray(key);
if (array instanceof Notification[] || array == null) {
return (Notification[]) array;
}
Notification[] typedArray = Arrays.copyOf(array, array.length,
Notification[].class);
bundle.putParcelableArray(key, typedArray);
return typedArray;
}
private static class BuilderRemoteViews extends RemoteViews {
public BuilderRemoteViews(Parcel parcel) {
super(parcel);
}
public BuilderRemoteViews(ApplicationInfo appInfo, int layoutId) {
super(appInfo, layoutId);
}
@Override
public BuilderRemoteViews clone() {
Parcel p = Parcel.obtain();
writeToParcel(p, 0);
p.setDataPosition(0);
BuilderRemoteViews brv = new BuilderRemoteViews(p);
p.recycle();
return brv;
}
}
/**
* A result object where information about the template that was created is saved.
*/
private static class TemplateBindResult {
int mIconMarginEnd;
boolean mRightIconContainerVisible;
/**
* Get the margin end that needs to be added to any fields that may overlap
* with the right actions.
*/
public int getIconMarginEnd() {
return mIconMarginEnd;
}
/**
* Is the icon container visible on the right size because of the reply button or the
* right icon.
*/
public boolean isRightIconContainerVisible() {
return mRightIconContainerVisible;
}
public void setIconMarginEnd(int iconMarginEnd) {
this.mIconMarginEnd = iconMarginEnd;
}
public void setRightIconContainerVisible(boolean iconContainerVisible) {
mRightIconContainerVisible = iconContainerVisible;
}
}
private static class StandardTemplateParams {
boolean hasProgress = true;
CharSequence title;
CharSequence text;
CharSequence headerTextSecondary;
CharSequence summaryText;
int maxRemoteInputHistory = Style.MAX_REMOTE_INPUT_HISTORY_LINES;
boolean hideLargeIcon;
boolean hideReplyIcon;
boolean allowColorization = true;
boolean forceDefaultColor = false;
final StandardTemplateParams reset() {
hasProgress = true;
title = null;
text = null;
summaryText = null;
headerTextSecondary = null;
maxRemoteInputHistory = Style.MAX_REMOTE_INPUT_HISTORY_LINES;
allowColorization = true;
forceDefaultColor = false;
return this;
}
final StandardTemplateParams hasProgress(boolean hasProgress) {
this.hasProgress = hasProgress;
return this;
}
final StandardTemplateParams title(CharSequence title) {
this.title = title;
return this;
}
final StandardTemplateParams text(CharSequence text) {
this.text = text;
return this;
}
final StandardTemplateParams summaryText(CharSequence text) {
this.summaryText = text;
return this;
}
final StandardTemplateParams headerTextSecondary(CharSequence text) {
this.headerTextSecondary = text;
return this;
}
final StandardTemplateParams hideLargeIcon(boolean hideLargeIcon) {
this.hideLargeIcon = hideLargeIcon;
return this;
}
final StandardTemplateParams hideReplyIcon(boolean hideReplyIcon) {
this.hideReplyIcon = hideReplyIcon;
return this;
}
final StandardTemplateParams disallowColorization() {
this.allowColorization = false;
return this;
}
final StandardTemplateParams forceDefaultColor() {
this.forceDefaultColor = true;
return this;
}
final StandardTemplateParams fillTextsFrom(Builder b) {
Bundle extras = b.mN.extras;
this.title = b.processLegacyText(extras.getCharSequence(EXTRA_TITLE));
CharSequence text = extras.getCharSequence(EXTRA_BIG_TEXT);
if (TextUtils.isEmpty(text)) {
text = extras.getCharSequence(EXTRA_TEXT);
}
this.text = b.processLegacyText(text);
this.summaryText = extras.getCharSequence(EXTRA_SUB_TEXT);
return this;
}
/**
* Set the maximum lines of remote input history lines allowed.
* @param maxRemoteInputHistory The number of lines.
* @return The builder for method chaining.
*/
public StandardTemplateParams setMaxRemoteInputHistory(int maxRemoteInputHistory) {
this.maxRemoteInputHistory = maxRemoteInputHistory;
return this;
}
}
}
* {@code
* Notification.Builder myBuilder = (build your Notification as normal);
* myBuilder.getExtras().putString(EXTRA_AUDIO_CONTENTS_URI, myAudioUri.toString());
* }
*
*/
public static final String EXTRA_AUDIO_CONTENTS_URI = "android.audioContents";
/** @hide */
@SystemApi
@RequiresPermission(android.Manifest.permission.SUBSTITUTE_NOTIFICATION_APP_NAME)
public static final String EXTRA_SUBSTITUTE_APP_NAME = "android.substName";
/**
* This is set on the notifications shown by system_server about apps running foreground
* services. It indicates that the notification should be shown
* only if any of the given apps do not already have a properly tagged
* {@link #FLAG_FOREGROUND_SERVICE} notification currently visible to the user.
* This is a string array of all package names of the apps.
* @hide
*/
public static final String EXTRA_FOREGROUND_APPS = "android.foregroundApps";
@UnsupportedAppUsage
private Icon mSmallIcon;
@UnsupportedAppUsage
private Icon mLargeIcon;
@UnsupportedAppUsage
private String mChannelId;
private long mTimeout;
private String mShortcutId;
private LocusId mLocusId;
private CharSequence mSettingsText;
private BubbleMetadata mBubbleMetadata;
/** @hide */
@IntDef(prefix = { "GROUP_ALERT_" }, value = {
GROUP_ALERT_ALL, GROUP_ALERT_CHILDREN, GROUP_ALERT_SUMMARY
})
@Retention(RetentionPolicy.SOURCE)
public @interface GroupAlertBehavior {}
/**
* Constant for {@link Builder#setGroupAlertBehavior(int)}, meaning that all notifications in a
* group with sound or vibration ought to make sound or vibrate (respectively), so this
* notification will not be muted when it is in a group.
*/
public static final int GROUP_ALERT_ALL = 0;
/**
* Constant for {@link Builder#setGroupAlertBehavior(int)}, meaning that all children
* notification in a group should be silenced (no sound or vibration) even if they are posted
* to a {@link NotificationChannel} that has sound and/or vibration. Use this constant to
* mute this notification if this notification is a group child. This must be applied to all
* children notifications you want to mute.
*
*
* Notification.Action action = new Notification.Action.Builder(
* R.drawable.archive_all, "Archive all", actionIntent)
* .extend(new Notification.Action.WearableExtender()
* .setAvailableOffline(false))
* .build();
*/
public static final class WearableExtender implements Extender {
/** Notification action extra which contains wearable extensions */
private static final String EXTRA_WEARABLE_EXTENSIONS = "android.wearable.EXTENSIONS";
// Keys within EXTRA_WEARABLE_EXTENSIONS for wearable options.
private static final String KEY_FLAGS = "flags";
private static final String KEY_IN_PROGRESS_LABEL = "inProgressLabel";
private static final String KEY_CONFIRM_LABEL = "confirmLabel";
private static final String KEY_CANCEL_LABEL = "cancelLabel";
// Flags bitwise-ored to mFlags
private static final int FLAG_AVAILABLE_OFFLINE = 0x1;
private static final int FLAG_HINT_LAUNCHES_ACTIVITY = 1 << 1;
private static final int FLAG_HINT_DISPLAY_INLINE = 1 << 2;
// Default value for flags integer
private static final int DEFAULT_FLAGS = FLAG_AVAILABLE_OFFLINE;
private int mFlags = DEFAULT_FLAGS;
private CharSequence mInProgressLabel;
private CharSequence mConfirmLabel;
private CharSequence mCancelLabel;
/**
* Create a {@link android.app.Notification.Action.WearableExtender} with default
* options.
*/
public WearableExtender() {
}
/**
* Create a {@link android.app.Notification.Action.WearableExtender} by reading
* wearable options present in an existing notification action.
* @param action the notification action to inspect.
*/
public WearableExtender(Action action) {
Bundle wearableBundle = action.getExtras().getBundle(EXTRA_WEARABLE_EXTENSIONS);
if (wearableBundle != null) {
mFlags = wearableBundle.getInt(KEY_FLAGS, DEFAULT_FLAGS);
mInProgressLabel = wearableBundle.getCharSequence(KEY_IN_PROGRESS_LABEL);
mConfirmLabel = wearableBundle.getCharSequence(KEY_CONFIRM_LABEL);
mCancelLabel = wearableBundle.getCharSequence(KEY_CANCEL_LABEL);
}
}
/**
* Apply wearable extensions to a notification action that is being built. This is
* typically called by the {@link android.app.Notification.Action.Builder#extend}
* method of {@link android.app.Notification.Action.Builder}.
*/
@Override
public Action.Builder extend(Action.Builder builder) {
Bundle wearableBundle = new Bundle();
if (mFlags != DEFAULT_FLAGS) {
wearableBundle.putInt(KEY_FLAGS, mFlags);
}
if (mInProgressLabel != null) {
wearableBundle.putCharSequence(KEY_IN_PROGRESS_LABEL, mInProgressLabel);
}
if (mConfirmLabel != null) {
wearableBundle.putCharSequence(KEY_CONFIRM_LABEL, mConfirmLabel);
}
if (mCancelLabel != null) {
wearableBundle.putCharSequence(KEY_CANCEL_LABEL, mCancelLabel);
}
builder.getExtras().putBundle(EXTRA_WEARABLE_EXTENSIONS, wearableBundle);
return builder;
}
@Override
public WearableExtender clone() {
WearableExtender that = new WearableExtender();
that.mFlags = this.mFlags;
that.mInProgressLabel = this.mInProgressLabel;
that.mConfirmLabel = this.mConfirmLabel;
that.mCancelLabel = this.mCancelLabel;
return that;
}
/**
* Set whether this action is available when the wearable device is not connected to
* a companion device. The user can still trigger this action when the wearable device is
* offline, but a visual hint will indicate that the action may not be available.
* Defaults to true.
*/
public WearableExtender setAvailableOffline(boolean availableOffline) {
setFlag(FLAG_AVAILABLE_OFFLINE, availableOffline);
return this;
}
/**
* Get whether this action is available when the wearable device is not connected to
* a companion device. The user can still trigger this action when the wearable device is
* offline, but a visual hint will indicate that the action may not be available.
* Defaults to true.
*/
public boolean isAvailableOffline() {
return (mFlags & FLAG_AVAILABLE_OFFLINE) != 0;
}
private void setFlag(int mask, boolean value) {
if (value) {
mFlags |= mask;
} else {
mFlags &= ~mask;
}
}
/**
* Set a label to display while the wearable is preparing to automatically execute the
* action. This is usually a 'ing' verb ending in ellipsis like "Sending..."
*
* @param label the label to display while the action is being prepared to execute
* @return this object for method chaining
*/
@Deprecated
public WearableExtender setInProgressLabel(CharSequence label) {
mInProgressLabel = label;
return this;
}
/**
* Get the label to display while the wearable is preparing to automatically execute
* the action. This is usually a 'ing' verb ending in ellipsis like "Sending..."
*
* @return the label to display while the action is being prepared to execute
*/
@Deprecated
public CharSequence getInProgressLabel() {
return mInProgressLabel;
}
/**
* Set a label to display to confirm that the action should be executed.
* This is usually an imperative verb like "Send".
*
* @param label the label to confirm the action should be executed
* @return this object for method chaining
*/
@Deprecated
public WearableExtender setConfirmLabel(CharSequence label) {
mConfirmLabel = label;
return this;
}
/**
* Get the label to display to confirm that the action should be executed.
* This is usually an imperative verb like "Send".
*
* @return the label to confirm the action should be executed
*/
@Deprecated
public CharSequence getConfirmLabel() {
return mConfirmLabel;
}
/**
* Set a label to display to cancel the action.
* This is usually an imperative verb, like "Cancel".
*
* @param label the label to display to cancel the action
* @return this object for method chaining
*/
@Deprecated
public WearableExtender setCancelLabel(CharSequence label) {
mCancelLabel = label;
return this;
}
/**
* Get the label to display to cancel the action.
* This is usually an imperative verb like "Cancel".
*
* @return the label to display to cancel the action
*/
@Deprecated
public CharSequence getCancelLabel() {
return mCancelLabel;
}
/**
* Set a hint that this Action will launch an {@link Activity} directly, telling the
* platform that it can generate the appropriate transitions.
* @param hintLaunchesActivity {@code true} if the content intent will launch
* an activity and transitions should be generated, false otherwise.
* @return this object for method chaining
*/
public WearableExtender setHintLaunchesActivity(
boolean hintLaunchesActivity) {
setFlag(FLAG_HINT_LAUNCHES_ACTIVITY, hintLaunchesActivity);
return this;
}
/**
* Get a hint that this Action will launch an {@link Activity} directly, telling the
* platform that it can generate the appropriate transitions
* @return {@code true} if the content intent will launch an activity and transitions
* should be generated, false otherwise. The default value is {@code false} if this was
* never set.
*/
public boolean getHintLaunchesActivity() {
return (mFlags & FLAG_HINT_LAUNCHES_ACTIVITY) != 0;
}
/**
* Set a hint that this Action should be displayed inline.
*
* @param hintDisplayInline {@code true} if action should be displayed inline, false
* otherwise
* @return this object for method chaining
*/
public WearableExtender setHintDisplayActionInline(
boolean hintDisplayInline) {
setFlag(FLAG_HINT_DISPLAY_INLINE, hintDisplayInline);
return this;
}
/**
* Get a hint that this Action should be displayed inline.
*
* @return {@code true} if the Action should be displayed inline, {@code false}
* otherwise. The default value is {@code false} if this was never set.
*/
public boolean getHintDisplayActionInline() {
return (mFlags & FLAG_HINT_DISPLAY_INLINE) != 0;
}
}
/**
* Provides meaning to an {@link Action} that hints at what the associated
* {@link PendingIntent} will do. For example, an {@link Action} with a
* {@link PendingIntent} that replies to a text message notification may have the
* {@link #SEMANTIC_ACTION_REPLY} {@code SemanticAction} set within it.
*
* @hide
*/
@IntDef(prefix = { "SEMANTIC_ACTION_" }, value = {
SEMANTIC_ACTION_NONE,
SEMANTIC_ACTION_REPLY,
SEMANTIC_ACTION_MARK_AS_READ,
SEMANTIC_ACTION_MARK_AS_UNREAD,
SEMANTIC_ACTION_DELETE,
SEMANTIC_ACTION_ARCHIVE,
SEMANTIC_ACTION_MUTE,
SEMANTIC_ACTION_UNMUTE,
SEMANTIC_ACTION_THUMBS_UP,
SEMANTIC_ACTION_THUMBS_DOWN,
SEMANTIC_ACTION_CALL
})
@Retention(RetentionPolicy.SOURCE)
public @interface SemanticAction {}
}
/**
* Array of all {@link Action} structures attached to this notification by
* {@link Builder#addAction(int, CharSequence, PendingIntent)}. Mostly useful for instances of
* {@link android.service.notification.NotificationListenerService} that provide an alternative
* interface for invoking actions.
*/
public Action[] actions;
/**
* Replacement version of this notification whose content will be shown
* in an insecure context such as atop a secure keyguard. See {@link #visibility}
* and {@link #VISIBILITY_PUBLIC}.
*/
public Notification publicVersion;
/**
* Constructs a Notification object with default values.
* You might want to consider using {@link Builder} instead.
*/
public Notification()
{
this.when = System.currentTimeMillis();
this.creationTime = System.currentTimeMillis();
this.priority = PRIORITY_DEFAULT;
}
/**
* @hide
*/
@UnsupportedAppUsage
public Notification(Context context, int icon, CharSequence tickerText, long when,
CharSequence contentTitle, CharSequence contentText, Intent contentIntent)
{
new Builder(context)
.setWhen(when)
.setSmallIcon(icon)
.setTicker(tickerText)
.setContentTitle(contentTitle)
.setContentText(contentText)
.setContentIntent(PendingIntent.getActivity(context, 0, contentIntent, 0))
.buildInto(this);
}
/**
* Constructs a Notification object with the information needed to
* have a status bar icon without the standard expanded view.
*
* @param icon The resource id of the icon to put in the status bar.
* @param tickerText The text that flows by in the status bar when the notification first
* activates.
* @param when The time to show in the time field. In the System.currentTimeMillis
* timebase.
*
* @deprecated Use {@link Builder} instead.
*/
@Deprecated
public Notification(int icon, CharSequence tickerText, long when)
{
this.icon = icon;
this.tickerText = tickerText;
this.when = when;
this.creationTime = System.currentTimeMillis();
}
/**
* Unflatten the notification from a parcel.
*/
@SuppressWarnings("unchecked")
public Notification(Parcel parcel) {
// IMPORTANT: Add unmarshaling code in readFromParcel as the pending
// intents in extras are always written as the last entry.
readFromParcelImpl(parcel);
// Must be read last!
allPendingIntents = (ArraySet
* Notification noti = new Notification.Builder(mContext)
* .setContentTitle("New mail from " + sender.toString())
* .setContentText(subject)
* .setSmallIcon(R.drawable.new_mail)
* .setLargeIcon(aBitmap)
* .build();
*
*/
public static class Builder {
/**
* @hide
*/
public static final String EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT =
"android.rebuild.contentViewActionCount";
/**
* @hide
*/
public static final String EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT
= "android.rebuild.bigViewActionCount";
/**
* @hide
*/
public static final String EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT
= "android.rebuild.hudViewActionCount";
private static final boolean USE_ONLY_TITLE_IN_LOW_PRIORITY_SUMMARY =
SystemProperties.getBoolean("notifications.only_title", true);
/**
* The lightness difference that has to be added to the primary text color to obtain the
* secondary text color when the background is light.
*/
private static final int LIGHTNESS_TEXT_DIFFERENCE_LIGHT = 20;
/**
* The lightness difference that has to be added to the primary text color to obtain the
* secondary text color when the background is dark.
* A bit less then the above value, since it looks better on dark backgrounds.
*/
private static final int LIGHTNESS_TEXT_DIFFERENCE_DARK = -10;
private Context mContext;
private Notification mN;
private Bundle mUserExtras = new Bundle();
private Style mStyle;
@UnsupportedAppUsage
private ArrayListwhen
as a timestamp, the notification will show an
* automatically updating display of the minutes and seconds since when
.
*
* Useful when showing an elapsed time (like an ongoing phone call).
*
* The counter can also be set to count down to when
when using
* {@link #setChronometerCountDown(boolean)}.
*
* @see android.widget.Chronometer
* @see Notification#when
* @see #setChronometerCountDown(boolean)
*/
@NonNull
public Builder setUsesChronometer(boolean b) {
mN.extras.putBoolean(EXTRA_SHOW_CHRONOMETER, b);
return this;
}
/**
* Sets the Chronometer to count down instead of counting up.
*
* STREAM_
constants.
*
* @deprecated use {@link NotificationChannel#setSound(Uri, AudioAttributes)}.
*/
@Deprecated
public Builder setSound(Uri sound, int streamType) {
PlayerBase.deprecateStreamTypeForPlayback(streamType, "Notification", "setSound()");
mN.sound = sound;
mN.audioStreamType = streamType;
return this;
}
/**
* Set the sound to play, along with specific {@link AudioAttributes audio attributes} to
* use during playback.
*
* @deprecated use {@link NotificationChannel#setSound(Uri, AudioAttributes)} instead.
* @see Notification#sound
*/
@Deprecated
public Builder setSound(Uri sound, AudioAttributes audioAttributes) {
mN.sound = sound;
mN.audioAttributes = audioAttributes;
return this;
}
/**
* Set the vibration pattern to use.
*
* See {@link android.os.Vibrator#vibrate(long[], int)} for a discussion of the
* pattern
parameter.
*
* BigPictureStyle
on a notification:
*
* Notification notif = new Notification.Builder(mContext)
* .setContentTitle("New photo from " + sender.toString())
* .setContentText(subject)
* .setSmallIcon(R.drawable.new_post)
* .setLargeIcon(aBitmap)
* .setStyle(new Notification.BigPictureStyle()
* .bigPicture(aBigBitmap))
* .build();
*
*
* @see Notification#bigContentView
*/
public static class BigPictureStyle extends Style {
private Bitmap mPicture;
private Icon mBigLargeIcon;
private boolean mBigLargeIconSet = false;
public BigPictureStyle() {
}
/**
* @deprecated use {@code BigPictureStyle()}.
*/
@Deprecated
public BigPictureStyle(Builder builder) {
setBuilder(builder);
}
/**
* Overrides ContentTitle in the big form of the template.
* This defaults to the value passed to setContentTitle().
*/
public BigPictureStyle setBigContentTitle(CharSequence title) {
internalSetBigContentTitle(safeCharSequence(title));
return this;
}
/**
* Set the first line of text after the detail section in the big form of the template.
*/
public BigPictureStyle setSummaryText(CharSequence cs) {
internalSetSummaryText(safeCharSequence(cs));
return this;
}
/**
* @hide
*/
public Bitmap getBigPicture() {
return mPicture;
}
/**
* Provide the bitmap to be used as the payload for the BigPicture notification.
*/
public BigPictureStyle bigPicture(Bitmap b) {
mPicture = b;
return this;
}
/**
* Override the large icon when the big notification is shown.
*/
public BigPictureStyle bigLargeIcon(Bitmap b) {
return bigLargeIcon(b != null ? Icon.createWithBitmap(b) : null);
}
/**
* Override the large icon when the big notification is shown.
*/
public BigPictureStyle bigLargeIcon(Icon icon) {
mBigLargeIconSet = true;
mBigLargeIcon = icon;
return this;
}
/** @hide */
public static final int MIN_ASHMEM_BITMAP_SIZE = 128 * (1 << 10);
/**
* @hide
*/
@Override
public void purgeResources() {
super.purgeResources();
if (mPicture != null &&
mPicture.isMutable() &&
mPicture.getAllocationByteCount() >= MIN_ASHMEM_BITMAP_SIZE) {
mPicture = mPicture.createAshmemBitmap();
}
if (mBigLargeIcon != null) {
mBigLargeIcon.convertToAshmem();
}
}
/**
* @hide
*/
@Override
public void reduceImageSizes(Context context) {
super.reduceImageSizes(context);
Resources resources = context.getResources();
boolean isLowRam = ActivityManager.isLowRamDeviceStatic();
if (mPicture != null) {
int maxPictureWidth = resources.getDimensionPixelSize(isLowRam
? R.dimen.notification_big_picture_max_height_low_ram
: R.dimen.notification_big_picture_max_height);
int maxPictureHeight = resources.getDimensionPixelSize(isLowRam
? R.dimen.notification_big_picture_max_width_low_ram
: R.dimen.notification_big_picture_max_width);
mPicture = Icon.scaleDownIfNecessary(mPicture, maxPictureWidth, maxPictureHeight);
}
if (mBigLargeIcon != null) {
int rightIconSize = resources.getDimensionPixelSize(isLowRam
? R.dimen.notification_right_icon_size_low_ram
: R.dimen.notification_right_icon_size);
mBigLargeIcon.scaleDownIfNecessary(rightIconSize, rightIconSize);
}
}
/**
* @hide
*/
public RemoteViews makeBigContentView() {
// Replace mN.mLargeIcon with mBigLargeIcon if mBigLargeIconSet
// This covers the following cases:
// 1. mBigLargeIconSet -> mBigLargeIcon (null or non-null) applies, overrides
// mN.mLargeIcon
// 2. !mBigLargeIconSet -> mN.mLargeIcon applies
Icon oldLargeIcon = null;
Bitmap largeIconLegacy = null;
if (mBigLargeIconSet) {
oldLargeIcon = mBuilder.mN.mLargeIcon;
mBuilder.mN.mLargeIcon = mBigLargeIcon;
// The legacy largeIcon might not allow us to clear the image, as it's taken in
// replacement if the other one is null. Because we're restoring these legacy icons
// for old listeners, this is in general non-null.
largeIconLegacy = mBuilder.mN.largeIcon;
mBuilder.mN.largeIcon = null;
}
StandardTemplateParams p = mBuilder.mParams.reset().fillTextsFrom(mBuilder);
RemoteViews contentView = getStandardView(mBuilder.getBigPictureLayoutResource(),
p, null /* result */);
if (mSummaryTextSet) {
contentView.setTextViewText(R.id.text, mBuilder.processTextSpans(
mBuilder.processLegacyText(mSummaryText)));
mBuilder.setTextViewColorSecondary(contentView, R.id.text, p);
contentView.setViewVisibility(R.id.text, View.VISIBLE);
}
mBuilder.setContentMinHeight(contentView, mBuilder.mN.hasLargeIcon());
if (mBigLargeIconSet) {
mBuilder.mN.mLargeIcon = oldLargeIcon;
mBuilder.mN.largeIcon = largeIconLegacy;
}
contentView.setImageViewBitmap(R.id.big_picture, mPicture);
return contentView;
}
/**
* @hide
*/
public void addExtras(Bundle extras) {
super.addExtras(extras);
if (mBigLargeIconSet) {
extras.putParcelable(EXTRA_LARGE_ICON_BIG, mBigLargeIcon);
}
extras.putParcelable(EXTRA_PICTURE, mPicture);
}
/**
* @hide
*/
@Override
protected void restoreFromExtras(Bundle extras) {
super.restoreFromExtras(extras);
if (extras.containsKey(EXTRA_LARGE_ICON_BIG)) {
mBigLargeIconSet = true;
mBigLargeIcon = extras.getParcelable(EXTRA_LARGE_ICON_BIG);
}
mPicture = extras.getParcelable(EXTRA_PICTURE);
}
/**
* @hide
*/
@Override
public boolean hasSummaryInHeader() {
return false;
}
/**
* @hide
* Note that we aren't actually comparing the contents of the bitmaps here, so this
* is only doing a cursory inspection. Bitmaps of equal size will appear the same.
*/
@Override
public boolean areNotificationsVisiblyDifferent(Style other) {
if (other == null || getClass() != other.getClass()) {
return true;
}
BigPictureStyle otherS = (BigPictureStyle) other;
return areBitmapsObviouslyDifferent(getBigPicture(), otherS.getBigPicture());
}
private static boolean areBitmapsObviouslyDifferent(Bitmap a, Bitmap b) {
if (a == b) {
return false;
}
if (a == null || b == null) {
return true;
}
return a.getWidth() != b.getWidth()
|| a.getHeight() != b.getHeight()
|| a.getConfig() != b.getConfig()
|| a.getGenerationId() != b.getGenerationId();
}
}
/**
* Helper class for generating large-format notifications that include a lot of text.
*
* Here's how you'd set the BigTextStyle
on a notification:
*
* Notification notif = new Notification.Builder(mContext)
* .setContentTitle("New mail from " + sender.toString())
* .setContentText(subject)
* .setSmallIcon(R.drawable.new_mail)
* .setLargeIcon(aBitmap)
* .setStyle(new Notification.BigTextStyle()
* .bigText(aVeryLongString))
* .build();
*
*
* @see Notification#bigContentView
*/
public static class BigTextStyle extends Style {
private CharSequence mBigText;
public BigTextStyle() {
}
/**
* @deprecated use {@code BigTextStyle()}.
*/
@Deprecated
public BigTextStyle(Builder builder) {
setBuilder(builder);
}
/**
* Overrides ContentTitle in the big form of the template.
* This defaults to the value passed to setContentTitle().
*/
public BigTextStyle setBigContentTitle(CharSequence title) {
internalSetBigContentTitle(safeCharSequence(title));
return this;
}
/**
* Set the first line of text after the detail section in the big form of the template.
*/
public BigTextStyle setSummaryText(CharSequence cs) {
internalSetSummaryText(safeCharSequence(cs));
return this;
}
/**
* Provide the longer text to be displayed in the big form of the
* template in place of the content text.
*/
public BigTextStyle bigText(CharSequence cs) {
mBigText = safeCharSequence(cs);
return this;
}
/**
* @hide
*/
public CharSequence getBigText() {
return mBigText;
}
/**
* @hide
*/
public void addExtras(Bundle extras) {
super.addExtras(extras);
extras.putCharSequence(EXTRA_BIG_TEXT, mBigText);
}
/**
* @hide
*/
@Override
protected void restoreFromExtras(Bundle extras) {
super.restoreFromExtras(extras);
mBigText = extras.getCharSequence(EXTRA_BIG_TEXT);
}
/**
* @param increasedHeight true if this layout be created with an increased height.
*
* @hide
*/
@Override
public RemoteViews makeContentView(boolean increasedHeight) {
if (increasedHeight) {
mBuilder.mOriginalActions = mBuilder.mActions;
mBuilder.mActions = new ArrayList<>();
RemoteViews remoteViews = makeBigContentView();
mBuilder.mActions = mBuilder.mOriginalActions;
mBuilder.mOriginalActions = null;
return remoteViews;
}
return super.makeContentView(increasedHeight);
}
/**
* @hide
*/
@Override
public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
if (increasedHeight && mBuilder.mActions.size() > 0) {
return makeBigContentView();
}
return super.makeHeadsUpContentView(increasedHeight);
}
/**
* @hide
*/
public RemoteViews makeBigContentView() {
StandardTemplateParams p = mBuilder.mParams.reset().fillTextsFrom(mBuilder).text(null);
TemplateBindResult result = new TemplateBindResult();
RemoteViews contentView = getStandardView(mBuilder.getBigTextLayoutResource(), p,
result);
contentView.setInt(R.id.big_text, "setImageEndMargin", result.getIconMarginEnd());
CharSequence bigTextText = mBuilder.processLegacyText(mBigText);
if (TextUtils.isEmpty(bigTextText)) {
// In case the bigtext is null / empty fall back to the normal text to avoid a weird
// experience
bigTextText = mBuilder.processLegacyText(
mBuilder.getAllExtras().getCharSequence(EXTRA_TEXT));
}
contentView.setTextViewText(R.id.big_text, mBuilder.processTextSpans(bigTextText));
mBuilder.setTextViewColorSecondary(contentView, R.id.big_text, p);
contentView.setViewVisibility(R.id.big_text,
TextUtils.isEmpty(bigTextText) ? View.GONE : View.VISIBLE);
contentView.setBoolean(R.id.big_text, "setHasImage",
result.isRightIconContainerVisible());
return contentView;
}
/**
* @hide
* Spans are ignored when comparing text for visual difference.
*/
@Override
public boolean areNotificationsVisiblyDifferent(Style other) {
if (other == null || getClass() != other.getClass()) {
return true;
}
BigTextStyle newS = (BigTextStyle) other;
return !Objects.equals(String.valueOf(getBigText()), String.valueOf(newS.getBigText()));
}
}
/**
* Helper class for generating large-format notifications that include multiple back-and-forth
* messages of varying types between any number of people.
*
*
*
* Person user = new Person.Builder().setIcon(userIcon).setName(userName).build();
* MessagingStyle style = new MessagingStyle(user)
* .addMessage(messages[1].getText(), messages[1].getTime(), messages[1].getPerson())
* .addMessage(messages[2].getText(), messages[2].getTime(), messages[2].getPerson())
* .setGroupConversation(hasMultiplePeople());
*
* Notification noti = new Notification.Builder()
* .setContentTitle("2 new messages with " + sender.toString())
* .setContentText(subject)
* .setSmallIcon(R.drawable.new_message)
* .setLargeIcon(aBitmap)
* .setStyle(style)
* .build();
*
*/
public static class MessagingStyle extends Style {
/**
* The maximum number of messages that will be retained in the Notification itself (the
* number displayed is up to the platform).
*/
public static final int MAXIMUM_RETAINED_MESSAGES = 25;
@NonNull Person mUser;
@Nullable CharSequence mConversationTitle;
Listnull
for messages by the current user, in which case
* the platform will insert {@link #getUserDisplayName()}.
* Should be unique amongst all individuals in the conversation, and should be
* consistent during re-posts of the notification.
*
* @see Message#Notification.MessagingStyle.Message(CharSequence, long, CharSequence)
*
* @return this object for method chaining
*
* @deprecated use {@link #addMessage(CharSequence, long, Person)}
*/
public MessagingStyle addMessage(CharSequence text, long timestamp, CharSequence sender) {
return addMessage(text, timestamp,
sender == null ? null : new Person.Builder().setName(sender).build());
}
/**
* Adds a message for display by this notification. Convenience call for a simple
* {@link Message} in {@link #addMessage(Notification.MessagingStyle.Message)}.
* @param text A {@link CharSequence} to be displayed as the message content
* @param timestamp Time at which the message arrived
* @param sender The {@link Person} who sent the message.
* Should be null
for messages by the current user, in which case
* the platform will insert the user set in {@code MessagingStyle(Person)}.
*
* @see Message#Notification.MessagingStyle.Message(CharSequence, long, CharSequence)
*
* @return this object for method chaining
*/
public MessagingStyle addMessage(@NonNull CharSequence text, long timestamp,
@Nullable Person sender) {
return addMessage(new Message(text, timestamp, sender));
}
/**
* Adds a {@link Message} for display in this notification.
*
* null
for messages by the current user, in which case
* the platform will insert {@link MessagingStyle#getUserDisplayName()}.
* Should be unique amongst all individuals in the conversation, and should be
* consistent during re-posts of the notification.
*
* @deprecated use {@code Message(CharSequence, long, Person)}
*/
public Message(CharSequence text, long timestamp, CharSequence sender){
this(text, timestamp, sender == null ? null
: new Person.Builder().setName(sender).build());
}
/**
* Constructor
* @param text A {@link CharSequence} to be displayed as the message content
* @param timestamp Time at which the message arrived
* @param sender The {@link Person} who sent the message.
* Should be null
for messages by the current user, in which case
* the platform will insert the user set in {@code MessagingStyle(Person)}.
* null
for messages by the current user, in which case
* the platform will insert the user set in {@code MessagingStyle(Person)}.
* @param remoteInputHistory True if the messages was generated from the extra
* {@link Notification#EXTRA_REMOTE_INPUT_HISTORY}.
*
*
* @return this object for method chaining
*/
public Message setData(String dataMimeType, Uri dataUri) {
mDataMimeType = dataMimeType;
mDataUri = dataUri;
return this;
}
/**
* Get the text to be used for this message, or the fallback text if a type and content
* Uri have been set
*/
public CharSequence getText() {
return mText;
}
/**
* Get the time at which this message arrived
*/
public long getTimestamp() {
return mTimestamp;
}
/**
* Get the extras Bundle for this message.
*/
public Bundle getExtras() {
return mExtras;
}
/**
* Get the text used to display the contact's name in the messaging experience
*
* @deprecated use {@link #getSenderPerson()}
*/
public CharSequence getSender() {
return mSender == null ? null : mSender.getName();
}
/**
* Get the sender associated with this message.
*/
@Nullable
public Person getSenderPerson() {
return mSender;
}
/**
* Get the MIME type of the data pointed to by the Uri
*/
public String getDataMimeType() {
return mDataMimeType;
}
/**
* Get the Uri pointing to the content of the message. Can be null, in which case
* {@see #getText()} is used.
*/
public Uri getDataUri() {
return mDataUri;
}
/**
* @return True if the message was generated from
* {@link Notification#EXTRA_REMOTE_INPUT_HISTORY}.
* @hide
*/
public boolean isRemoteInputHistory() {
return mRemoteInputHistory;
}
/**
* @hide
*/
@VisibleForTesting
public Bundle toBundle() {
Bundle bundle = new Bundle();
if (mText != null) {
bundle.putCharSequence(KEY_TEXT, mText);
}
bundle.putLong(KEY_TIMESTAMP, mTimestamp);
if (mSender != null) {
// Legacy listeners need this
bundle.putCharSequence(KEY_SENDER, mSender.getName());
bundle.putParcelable(KEY_SENDER_PERSON, mSender);
}
if (mDataMimeType != null) {
bundle.putString(KEY_DATA_MIME_TYPE, mDataMimeType);
}
if (mDataUri != null) {
bundle.putParcelable(KEY_DATA_URI, mDataUri);
}
if (mExtras != null) {
bundle.putBundle(KEY_EXTRAS_BUNDLE, mExtras);
}
if (mRemoteInputHistory) {
bundle.putBoolean(KEY_REMOTE_INPUT_HISTORY, mRemoteInputHistory);
}
return bundle;
}
static Bundle[] getBundleArrayForMessages(ListInboxStyle
on a notification:
*
* Notification notif = new Notification.Builder(mContext)
* .setContentTitle("5 New mails from " + sender.toString())
* .setContentText(subject)
* .setSmallIcon(R.drawable.new_mail)
* .setLargeIcon(aBitmap)
* .setStyle(new Notification.InboxStyle()
* .addLine(str1)
* .addLine(str2)
* .setContentTitle("")
* .setSummaryText("+3 more"))
* .build();
*
*
* @see Notification#bigContentView
*/
public static class InboxStyle extends Style {
/**
* The number of lines of remote input history allowed until we start reducing lines.
*/
private static final int NUMBER_OF_HISTORY_ALLOWED_UNTIL_REDUCTION = 1;
private ArrayList
* Notification noti = new Notification.Builder()
* .setSmallIcon(R.drawable.ic_stat_player)
* .setContentTitle("Track title")
* .setContentText("Artist - Album")
* .setLargeIcon(albumArtBitmap))
* .setStyle(new Notification.MediaStyle()
* .setMediaSession(mySession))
* .build();
*
*
* @see Notification#bigContentView
* @see Notification.Builder#setColorized(boolean)
*/
public static class MediaStyle extends Style {
// Changing max media buttons requires also changing templates
// (notification_template_material_media and notification_template_material_big_media).
static final int MAX_MEDIA_BUTTONS_IN_COMPACT = 3;
static final int MAX_MEDIA_BUTTONS = 5;
@IdRes private static final int[] MEDIA_BUTTON_IDS = {
R.id.action0,
R.id.action1,
R.id.action2,
R.id.action3,
R.id.action4,
};
private int[] mActionsToShowInCompact = null;
private MediaSession.Token mToken;
public MediaStyle() {
}
/**
* @deprecated use {@code MediaStyle()}.
*/
@Deprecated
public MediaStyle(Builder builder) {
setBuilder(builder);
}
/**
* Request up to 3 actions (by index in the order of addition) to be shown in the compact
* notification view.
*
* @param actions the indices of the actions to show in the compact notification view
*/
public MediaStyle setShowActionsInCompactView(int...actions) {
mActionsToShowInCompact = actions;
return this;
}
/**
* Attach a {@link android.media.session.MediaSession.Token} to this Notification
* to provide additional playback information and control to the SystemUI.
*/
public MediaStyle setMediaSession(MediaSession.Token token) {
mToken = token;
return this;
}
/**
* @hide
*/
@Override
@UnsupportedAppUsage
public Notification buildStyled(Notification wip) {
super.buildStyled(wip);
if (wip.category == null) {
wip.category = Notification.CATEGORY_TRANSPORT;
}
return wip;
}
/**
* @hide
*/
@Override
public RemoteViews makeContentView(boolean increasedHeight) {
return makeMediaContentView();
}
/**
* @hide
*/
@Override
public RemoteViews makeBigContentView() {
return makeMediaBigContentView();
}
/**
* @hide
*/
@Override
public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
RemoteViews expanded = makeMediaBigContentView();
return expanded != null ? expanded : makeMediaContentView();
}
/** @hide */
@Override
public void addExtras(Bundle extras) {
super.addExtras(extras);
if (mToken != null) {
extras.putParcelable(EXTRA_MEDIA_SESSION, mToken);
}
if (mActionsToShowInCompact != null) {
extras.putIntArray(EXTRA_COMPACT_ACTIONS, mActionsToShowInCompact);
}
}
/**
* @hide
*/
@Override
protected void restoreFromExtras(Bundle extras) {
super.restoreFromExtras(extras);
if (extras.containsKey(EXTRA_MEDIA_SESSION)) {
mToken = extras.getParcelable(EXTRA_MEDIA_SESSION);
}
if (extras.containsKey(EXTRA_COMPACT_ACTIONS)) {
mActionsToShowInCompact = extras.getIntArray(EXTRA_COMPACT_ACTIONS);
}
}
/**
* @hide
*/
@Override
public boolean areNotificationsVisiblyDifferent(Style other) {
if (other == null || getClass() != other.getClass()) {
return true;
}
// All fields to compare are on the Notification object
return false;
}
private void bindMediaActionButton(RemoteViews container, @IdRes int buttonId,
Action action, StandardTemplateParams p) {
final boolean tombstone = (action.actionIntent == null);
container.setViewVisibility(buttonId, View.VISIBLE);
container.setImageViewIcon(buttonId, action.getIcon());
// If the action buttons should not be tinted, then just use the default
// notification color. Otherwise, just use the passed-in color.
Resources resources = mBuilder.mContext.getResources();
Configuration currentConfig = resources.getConfiguration();
boolean inNightMode = (currentConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK)
== Configuration.UI_MODE_NIGHT_YES;
int tintColor = mBuilder.shouldTintActionButtons() || mBuilder.isColorized(p)
? getActionColor(p)
: ContrastColorUtil.resolveColor(mBuilder.mContext,
Notification.COLOR_DEFAULT, inNightMode);
container.setDrawableTint(buttonId, false, tintColor,
PorterDuff.Mode.SRC_ATOP);
final TypedArray typedArray = mBuilder.mContext.obtainStyledAttributes(
new int[]{ android.R.attr.colorControlHighlight });
int rippleAlpha = Color.alpha(typedArray.getColor(0, 0));
typedArray.recycle();
int rippleColor = Color.argb(rippleAlpha, Color.red(tintColor), Color.green(tintColor),
Color.blue(tintColor));
container.setRippleDrawableColor(buttonId, ColorStateList.valueOf(rippleColor));
if (!tombstone) {
container.setOnClickPendingIntent(buttonId, action.actionIntent);
}
container.setContentDescription(buttonId, action.title);
}
private RemoteViews makeMediaContentView() {
StandardTemplateParams p = mBuilder.mParams.reset().hasProgress(false).fillTextsFrom(
mBuilder);
RemoteViews view = mBuilder.applyStandardTemplate(
R.layout.notification_template_material_media, p,
null /* result */);
final int numActions = mBuilder.mActions.size();
final int numActionsToShow = mActionsToShowInCompact == null
? 0
: Math.min(mActionsToShowInCompact.length, MAX_MEDIA_BUTTONS_IN_COMPACT);
if (numActionsToShow > numActions) {
throw new IllegalArgumentException(String.format(
"setShowActionsInCompactView: action %d out of bounds (max %d)",
numActions, numActions - 1));
}
for (int i = 0; i < MAX_MEDIA_BUTTONS_IN_COMPACT; i++) {
if (i < numActionsToShow) {
final Action action = mBuilder.mActions.get(mActionsToShowInCompact[i]);
bindMediaActionButton(view, MEDIA_BUTTON_IDS[i], action, p);
} else {
view.setViewVisibility(MEDIA_BUTTON_IDS[i], View.GONE);
}
}
handleImage(view);
// handle the content margin
int endMargin = R.dimen.notification_content_margin_end;
if (mBuilder.mN.hasLargeIcon()) {
endMargin = R.dimen.notification_media_image_margin_end;
}
view.setViewLayoutMarginEndDimen(R.id.notification_main_column, endMargin);
return view;
}
private int getActionColor(StandardTemplateParams p) {
return mBuilder.isColorized(p) ? mBuilder.getPrimaryTextColor(p)
: mBuilder.resolveContrastColor(p);
}
private RemoteViews makeMediaBigContentView() {
final int actionCount = Math.min(mBuilder.mActions.size(), MAX_MEDIA_BUTTONS);
// Dont add an expanded view if there is no more content to be revealed
int actionsInCompact = mActionsToShowInCompact == null
? 0
: Math.min(mActionsToShowInCompact.length, MAX_MEDIA_BUTTONS_IN_COMPACT);
if (!mBuilder.mN.hasLargeIcon() && actionCount <= actionsInCompact) {
return null;
}
StandardTemplateParams p = mBuilder.mParams.reset().hasProgress(false).fillTextsFrom(
mBuilder);
RemoteViews big = mBuilder.applyStandardTemplate(
R.layout.notification_template_material_big_media, p , null /* result */);
for (int i = 0; i < MAX_MEDIA_BUTTONS; i++) {
if (i < actionCount) {
bindMediaActionButton(big, MEDIA_BUTTON_IDS[i], mBuilder.mActions.get(i), p);
} else {
big.setViewVisibility(MEDIA_BUTTON_IDS[i], View.GONE);
}
}
bindMediaActionButton(big, R.id.media_seamless, new Action(R.drawable.ic_media_seamless,
mBuilder.mContext.getString(
com.android.internal.R.string.ext_media_seamless_action), null), p);
big.setViewVisibility(R.id.media_seamless, View.GONE);
handleImage(big);
return big;
}
private void handleImage(RemoteViews contentView) {
if (mBuilder.mN.hasLargeIcon()) {
contentView.setViewLayoutMarginEndDimen(R.id.line1, 0);
contentView.setViewLayoutMarginEndDimen(R.id.text, 0);
}
}
/**
* @hide
*/
@Override
protected boolean hasProgress() {
return false;
}
}
/**
* Notification style for custom views that are decorated by the system
*
*
* Notification noti = new Notification.Builder()
* .setSmallIcon(R.drawable.ic_stat_player)
* .setLargeIcon(albumArtBitmap))
* .setCustomContentView(contentView);
* .setStyle(new Notification.DecoratedCustomViewStyle())
* .build();
*
*/
public static class DecoratedCustomViewStyle extends Style {
public DecoratedCustomViewStyle() {
}
/**
* @hide
*/
public boolean displayCustomViewInline() {
return true;
}
/**
* @hide
*/
@Override
public RemoteViews makeContentView(boolean increasedHeight) {
return makeStandardTemplateWithCustomContent(mBuilder.mN.contentView);
}
/**
* @hide
*/
@Override
public RemoteViews makeBigContentView() {
return makeDecoratedBigContentView();
}
/**
* @hide
*/
@Override
public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
return makeDecoratedHeadsUpContentView();
}
private RemoteViews makeDecoratedHeadsUpContentView() {
RemoteViews headsUpContentView = mBuilder.mN.headsUpContentView == null
? mBuilder.mN.contentView
: mBuilder.mN.headsUpContentView;
if (mBuilder.mActions.size() == 0) {
return makeStandardTemplateWithCustomContent(headsUpContentView);
}
TemplateBindResult result = new TemplateBindResult();
RemoteViews remoteViews = mBuilder.applyStandardTemplateWithActions(
mBuilder.getBigBaseLayoutResource(), result);
buildIntoRemoteViewContent(remoteViews, headsUpContentView, result);
return remoteViews;
}
private RemoteViews makeStandardTemplateWithCustomContent(RemoteViews customContent) {
TemplateBindResult result = new TemplateBindResult();
RemoteViews remoteViews = mBuilder.applyStandardTemplate(
mBuilder.getBaseLayoutResource(), result);
buildIntoRemoteViewContent(remoteViews, customContent, result);
return remoteViews;
}
private RemoteViews makeDecoratedBigContentView() {
RemoteViews bigContentView = mBuilder.mN.bigContentView == null
? mBuilder.mN.contentView
: mBuilder.mN.bigContentView;
if (mBuilder.mActions.size() == 0) {
return makeStandardTemplateWithCustomContent(bigContentView);
}
TemplateBindResult result = new TemplateBindResult();
RemoteViews remoteViews = mBuilder.applyStandardTemplateWithActions(
mBuilder.getBigBaseLayoutResource(), result);
buildIntoRemoteViewContent(remoteViews, bigContentView, result);
return remoteViews;
}
private void buildIntoRemoteViewContent(RemoteViews remoteViews,
RemoteViews customContent, TemplateBindResult result) {
int childIndex = -1;
if (customContent != null) {
// Need to clone customContent before adding, because otherwise it can no longer be
// parceled independently of remoteViews.
customContent = customContent.clone();
remoteViews.removeAllViewsExceptId(R.id.notification_main_column, R.id.progress);
remoteViews.addView(R.id.notification_main_column, customContent, 0 /* index */);
remoteViews.addFlags(RemoteViews.FLAG_REAPPLY_DISALLOWED);
childIndex = 0;
}
remoteViews.setIntTag(R.id.notification_main_column,
com.android.internal.R.id.notification_custom_view_index_tag,
childIndex);
// also update the end margin if there is an image
Resources resources = mBuilder.mContext.getResources();
int endMargin = resources.getDimensionPixelSize(
R.dimen.notification_content_margin_end) + result.getIconMarginEnd();
remoteViews.setViewLayoutMarginEnd(R.id.notification_main_column, endMargin);
}
/**
* @hide
*/
@Override
public boolean areNotificationsVisiblyDifferent(Style other) {
if (other == null || getClass() != other.getClass()) {
return true;
}
// Comparison done for all custom RemoteViews, independent of style
return false;
}
}
/**
* Notification style for media custom views that are decorated by the system
*
*
* Notification noti = new Notification.Builder()
* .setSmallIcon(R.drawable.ic_stat_player)
* .setLargeIcon(albumArtBitmap))
* .setCustomContentView(contentView);
* .setStyle(new Notification.DecoratedMediaCustomViewStyle()
* .setMediaSession(mySession))
* .build();
*
*
* @see android.app.Notification.DecoratedCustomViewStyle
* @see android.app.Notification.MediaStyle
*/
public static class DecoratedMediaCustomViewStyle extends MediaStyle {
public DecoratedMediaCustomViewStyle() {
}
/**
* @hide
*/
public boolean displayCustomViewInline() {
return true;
}
/**
* @hide
*/
@Override
public RemoteViews makeContentView(boolean increasedHeight) {
RemoteViews remoteViews = super.makeContentView(false /* increasedHeight */);
return buildIntoRemoteView(remoteViews, R.id.notification_content_container,
mBuilder.mN.contentView);
}
/**
* @hide
*/
@Override
public RemoteViews makeBigContentView() {
RemoteViews customRemoteView = mBuilder.mN.bigContentView != null
? mBuilder.mN.bigContentView
: mBuilder.mN.contentView;
return makeBigContentViewWithCustomContent(customRemoteView);
}
private RemoteViews makeBigContentViewWithCustomContent(RemoteViews customRemoteView) {
RemoteViews remoteViews = super.makeBigContentView();
if (remoteViews != null) {
return buildIntoRemoteView(remoteViews, R.id.notification_main_column,
customRemoteView);
} else if (customRemoteView != mBuilder.mN.contentView){
remoteViews = super.makeContentView(false /* increasedHeight */);
return buildIntoRemoteView(remoteViews, R.id.notification_content_container,
customRemoteView);
} else {
return null;
}
}
/**
* @hide
*/
@Override
public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
RemoteViews customRemoteView = mBuilder.mN.headsUpContentView != null
? mBuilder.mN.headsUpContentView
: mBuilder.mN.contentView;
return makeBigContentViewWithCustomContent(customRemoteView);
}
/**
* @hide
*/
@Override
public boolean areNotificationsVisiblyDifferent(Style other) {
if (other == null || getClass() != other.getClass()) {
return true;
}
// Comparison done for all custom RemoteViews, independent of style
return false;
}
private RemoteViews buildIntoRemoteView(RemoteViews remoteViews, int id,
RemoteViews customContent) {
if (customContent != null) {
// Need to clone customContent before adding, because otherwise it can no longer be
// parceled independently of remoteViews.
customContent = customContent.clone();
customContent.overrideTextColors(mBuilder.getPrimaryTextColor(mBuilder.mParams));
remoteViews.removeAllViews(id);
remoteViews.addView(id, customContent);
remoteViews.addFlags(RemoteViews.FLAG_REAPPLY_DISALLOWED);
}
return remoteViews;
}
}
/**
* Encapsulates the information needed to display a notification as a bubble.
*
*
*
*
*
* Notification notif = new Notification.Builder(mContext)
* .setContentTitle("New mail from " + sender.toString())
* .setContentText(subject)
* .setSmallIcon(R.drawable.new_mail)
* .extend(new Notification.WearableExtender()
* .setContentIcon(R.drawable.new_mail))
* .build();
* NotificationManager notificationManger =
* (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
* notificationManger.notify(0, notif);
*
*
* Notification.WearableExtender wearableExtender = new Notification.WearableExtender(
* notification);
* List<Notification> pages = wearableExtender.getPages();
*/
public static final class WearableExtender implements Extender {
/**
* Sentinel value for an action index that is unset.
*/
public static final int UNSET_ACTION_INDEX = -1;
/**
* Size value for use with {@link #setCustomSizePreset} to show this notification with
* default sizing.
*
* Intent displayIntent = new Intent(context, MyDisplayActivity.class);
* PendingIntent displayPendingIntent = PendingIntent.getActivity(context,
* 0, displayIntent, PendingIntent.FLAG_UPDATE_CURRENT);
* Notification notif = new Notification.Builder(context)
* .extend(new Notification.WearableExtender()
* .setDisplayIntent(displayPendingIntent)
* .setCustomSizePreset(Notification.WearableExtender.SIZE_MEDIUM))
* .build();
*
*
* <activity android:name="com.example.MyDisplayActivity"
* android:exported="true"
* android:allowEmbedded="true"
* android:taskAffinity=""
* android:theme="@android:style/Theme.DeviceDefault.Light" />
*
* @param intent the {@link PendingIntent} for an activity
* @return this object for method chaining
* @see android.app.Notification.WearableExtender#getDisplayIntent
* @deprecated Display intents are no longer supported.
*/
@Deprecated
public WearableExtender setDisplayIntent(PendingIntent intent) {
mDisplayIntent = intent;
return this;
}
/**
* Get the intent to launch inside of an activity view when displaying this
* notification. This {@code PendingIntent} should be for an activity.
*
* @deprecated Display intents are no longer supported.
*/
@Deprecated
public PendingIntent getDisplayIntent() {
return mDisplayIntent;
}
/**
* Add an additional page of content to display with this notification. The current
* notification forms the first page, and pages added using this function form
* subsequent pages. This field can be used to separate a notification into multiple
* sections.
*
* @param page the notification to add as another page
* @return this object for method chaining
* @see android.app.Notification.WearableExtender#getPages
* @deprecated Multiple content pages are no longer supported.
*/
@Deprecated
public WearableExtender addPage(Notification page) {
mPages.add(page);
return this;
}
/**
* Add additional pages of content to display with this notification. The current
* notification forms the first page, and pages added using this function form
* subsequent pages. This field can be used to separate a notification into multiple
* sections.
*
* @param pages a list of notifications
* @return this object for method chaining
* @see android.app.Notification.WearableExtender#getPages
* @deprecated Multiple content pages are no longer supported.
*/
@Deprecated
public WearableExtender addPages(List
*
*
*
* Notification notification = new Notification.Builder(context)
* ...
* .extend(new CarExtender()
* .set*(...))
* .build();
*
*
*
*
*
*
* Notification notification = new Notification.Builder(context)
* ...
* .extend(new TvExtender()
* .set*(...))
* .build();
*
*
*