/* * 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.app.Flags.evenlyDividedCallStyleActionLayout; import static android.app.admin.DevicePolicyResources.Drawables.Source.NOTIFICATION; import static android.app.admin.DevicePolicyResources.Drawables.Style.SOLID_COLORED; import static android.app.admin.DevicePolicyResources.Drawables.WORK_PROFILE_ICON; import static android.app.admin.DevicePolicyResources.UNDEFINED; import static android.graphics.drawable.Icon.TYPE_URI; import static android.graphics.drawable.Icon.TYPE_URI_ADAPTIVE_BITMAP; import static java.util.Objects.requireNonNull; import android.annotation.ColorInt; import android.annotation.ColorRes; import android.annotation.DimenRes; import android.annotation.Dimension; import android.annotation.DrawableRes; import android.annotation.FlaggedApi; 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.StringRes; import android.annotation.StyleableRes; import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.annotation.TestApi; import android.app.admin.DevicePolicyManager; import android.app.compat.CompatChanges; import android.compat.annotation.ChangeId; import android.compat.annotation.EnabledSince; import android.compat.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.Trace; import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; 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.StrikethroughSpan; import android.text.style.StyleSpan; import android.text.style.TextAppearanceSpan; import android.text.style.UnderlineSpan; import android.util.ArraySet; import android.util.Log; import android.util.Pair; import android.util.SparseArray; import android.util.TypedValue; import android.util.proto.ProtoOutputStream; import android.view.ContextThemeWrapper; import android.view.Gravity; import android.view.View; 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.graphics.ColorUtils; import com.android.internal.util.ArrayUtils; import com.android.internal.util.ContrastColorUtil; import com.android.internal.util.NewlineNormalizer; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.reflect.Array; 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 = 1024; /** * Maximum entries of reply text that are accepted by Builder and friends. */ private static final int MAX_REPLY_HISTORY = 5; /** * Maximum aspect ratio of the large icon. 16:9 */ private static final float MAX_LARGE_ICON_ASPECT_RATIO = 16f / 9f; /** * 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"; /** * The call to WearableExtender#setBackground(Bitmap) will have no effect and the passed * Bitmap will not be retained in memory. */ @ChangeId @EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM) @VisibleForTesting static final long WEARABLE_EXTENDER_BACKGROUND_BLOCKED = 270551184L; /** * 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.
*
* Note that using this flag will stop any ongoing alerting behaviour such
* as sound, vibration or blinking notification LED.
*/
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;
/**
* Bit to be bitswised-ored into the {@link #flags} field that should be
* set by the system if this notification is not dismissible.
*
* This flag is for internal use only; applications cannot set this flag directly.
* @hide
*/
public static final int FLAG_NO_DISMISS = 0x00002000;
/**
* Bit to be bitwise-ORed into the {@link #flags} field that should be
* set by the system if the app that sent this notification does not have the permission to send
* full screen intents.
*
* This flag is for internal use only; applications cannot set this flag directly.
* @hide
*/
public static final int FLAG_FSI_REQUESTED_BUT_DENIED = 0x00004000;
/**
* Bit to be bitwise-ored into the {@link #flags} field that should be
* set if this notification represents a currently running user-initiated job.
*
* This flag is for internal use only; applications cannot set this flag directly.
* @hide
*/
@TestApi
public static final int FLAG_USER_INITIATED_JOB = 0x00008000;
/**
* Bit to be bitwise-ored into the {@link #flags} field that should be
* set if this notification has been lifetime extended due to a direct reply.
*
* This flag is for internal use only; applications cannot set this flag directly.
* @hide
*/
@FlaggedApi(Flags.FLAG_LIFETIME_EXTENSION_REFACTOR)
public static final int FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY = 0x00010000;
private static final List
* A notification that is at least {@link #PRIORITY_HIGH} is more likely to be presented
* as a heads-up notification.
* 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;
/**
* Constant for the {@link Builder#setGroup(String) group key} that is added to notifications
* that are not already grouped when {@link Builder#setSilent()} is used.
*
* @hide
*/
public static final String GROUP_KEY_SILENT = "silent";
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.
*
* As of Android {@link android.os.Build.VERSION_CODES#S}, apps targeting API level {@link
* android.os.Build.VERSION_CODES#S} or higher won't be able to start activities while
* processing broadcast receivers or services in response to notification action clicks. To
* launch an activity in those cases, provide a {@link PendingIntent} for the activity itself.
*/
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";
/**
* 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;
/**
* {@code SemanticAction}: Mark the conversation associated with the notification as a
* priority. Note that this is only for use by the notification assistant services. The
* type will be ignored for actions an app adds to its own notifications.
* @hide
*/
@SystemApi
public static final int SEMANTIC_ACTION_MARK_CONVERSATION_AS_PRIORITY = 11;
/**
* {@code SemanticAction}: Mark content as a potential phishing attempt.
* Note that this is only for use by the notification assistant services. The type will
* be ignored for actions an app adds to its own notifications.
* @hide
*/
@SystemApi
public static final int SEMANTIC_ACTION_CONVERSATION_IS_PHISHING = 12;
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;
private boolean mAuthenticationRequired;
/**
* 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;
mAuthenticationRequired = in.readInt() == 1;
}
/**
* @deprecated Use {@link android.app.Notification.Action.Builder}.
*/
@Deprecated
public Action(int icon, CharSequence title, @Nullable PendingIntent intent) {
this(Icon.createWithResource("", icon), title, intent, new Bundle(), null, true,
SEMANTIC_ACTION_NONE, false /* isContextual */, false /* requireAuth */);
}
/** 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,
boolean requireAuth) {
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;
this.mAuthenticationRequired = requireAuth;
}
/**
* 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 getParcelableArrayFromBundle(mExtras, EXTRA_DATA_ONLY_INPUTS, RemoteInput.class);
}
/**
* Returns whether the OS should only send this action's {@link PendingIntent} on an
* unlocked device.
*
* If the device is locked when the action is invoked, the OS should show the keyguard and
* require successful authentication before invoking the intent.
*/
public boolean isAuthenticationRequired() {
return mAuthenticationRequired;
}
/**
* 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 As of Android {@link android.os.Build.VERSION_CODES#N},
* action button icons will not be displayed on action buttons, but are still required
* and are available to
* {@link android.service.notification.NotificationListenerService notification listeners},
* which may display them in other contexts, for example on a wearable device.
* @param icon icon to show for this action
* @param title the title of the action
* @param intent the {@link PendingIntent} to fire when users trigger this action. May
* be null, in which case the action may be rendered in a disabled presentation by the
* system UI.
*/
@Deprecated
public Builder(int icon, CharSequence title, @Nullable PendingIntent intent) {
this(Icon.createWithResource("", icon), title, intent);
}
/**
* Construct a new builder for {@link Action} object.
*
* As of Android {@link android.os.Build.VERSION_CODES#S}, apps targeting API level
* {@link android.os.Build.VERSION_CODES#S} or higher won't be able to start activities
* while processing broadcast receivers or services in response to notification action
* clicks. To launch an activity in those cases, provide a {@link PendingIntent} for the
* activity itself.
*
* How an Action is displayed, including whether the {@code icon}, {@code text}, or
* both are displayed or required, depends on where and how the action is used, and the
* {@link Style} applied to the Notification.
*
* As of Android {@link android.os.Build.VERSION_CODES#N}, action button icons
* will not be displayed on action buttons, but are still required and are available
* to {@link android.service.notification.NotificationListenerService notification
* listeners}, which may display them in other contexts, for example on a wearable
* device.
*
* When the {@code title} is a {@link android.text.Spanned}, any colors set by a
* {@link ForegroundColorSpan} or {@link TextAppearanceSpan} may be removed or displayed
* with an altered in luminance to ensure proper contrast within the Notification.
*
* @param icon icon to show for this action
* @param title the title of the action
* @param intent the {@link PendingIntent} to fire when users trigger this action. May
* be null, in which case the action may be rendered in a disabled presentation by the
* system UI.
*/
public Builder(Icon icon, CharSequence title, @Nullable PendingIntent intent) {
this(icon, title, intent, new Bundle(), null, true, SEMANTIC_ACTION_NONE, false);
}
/**
* Construct a new builder for {@link Action} object using the fields from an
* {@link Action}.
* @param action the action to read fields from.
*/
public Builder(Action action) {
this(action.getIcon(), action.title, action.actionIntent,
new Bundle(action.mExtras), action.getRemoteInputs(),
action.getAllowGeneratedReplies(), action.getSemanticAction(),
action.isAuthenticationRequired());
}
private Builder(@Nullable Icon icon, @Nullable CharSequence title,
@Nullable PendingIntent intent, @NonNull Bundle extras,
@Nullable RemoteInput[] remoteInputs, boolean allowGeneratedReplies,
@SemanticAction int semanticAction, boolean authRequired) {
mIcon = icon;
mTitle = title;
mIntent = intent;
mExtras = extras;
if (remoteInputs != null) {
mRemoteInputs = new ArrayList<>(remoteInputs.length);
Collections.addAll(mRemoteInputs, remoteInputs);
}
mAllowGeneratedReplies = allowGeneratedReplies;
mSemanticAction = semanticAction;
mAuthenticationRequired = authRequired;
}
/**
* Merge additional metadata into this builder.
*
* 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, Parcelable.class) != 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 some Launchers that display notification content to hide shortcuts that duplicate
* notifications.
*/
public String getShortcutId() {
return mShortcutId;
}
/**
* Gets the {@link LocusId} associated with this notification.
*
* Used by the device's intelligence services to correlate objects (such as
* {@link ShortcutInfo} and {@link ContentCaptureContext}) that are correlated.
*/
@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;
}
/**
* Sets the {@link BubbleMetadata} for this notification.
* @hide
*/
public void setBubbleMetadata(BubbleMetadata data) {
mBubbleMetadata = data;
}
/**
* 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
*/
public boolean hasAppProvidedWhen() {
return when != 0 && when != creationTime;
}
/**
* @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.
*/
@Nullable
public Pair Example:
*
*
* For example, the following are some examples of notifications that belong in the
* conversation space:
*
* Additionally, this method can be used for all types of notifications to mark this
* notification as duplicative of a Launcher shortcut. Launchers that show badges or
* notification content may then suppress the shortcut in favor of the content of this
* notification.
*
* If this notification has {@link BubbleMetadata} attached that was created with
* a shortcutId a check will be performed to ensure the shortcutId supplied to bubble
* metadata matches the shortcutId set here, if one was set. If the shortcutId's were
* specified but do not match, an exception is thrown.
*
* @param shortcutId the {@link ShortcutInfo#getId() id} of the shortcut this notification
* is linked to
*
* @see BubbleMetadata.Builder#Builder(String)
*/
@NonNull
public Builder setShortcutId(String shortcutId) {
mN.mShortcutId = shortcutId;
return this;
}
/**
* Sets the {@link LocusId} associated with this notification.
*
* This method should be called when the {@link LocusId} is used in other places (such
* as {@link ShortcutInfo} and {@link ContentCaptureContext}) so the device's intelligence
* services 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 and/or peek
* on screen, 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;
}
/**
* If {@code true}, silences this instance of the notification, regardless of the sounds or
* vibrations set on the notification or notification channel. If {@code false}, then the
* normal sound and vibration logic applies.
*
* @hide
*/
public @NonNull Builder setSilent(boolean silent) {
if (!silent) {
return this;
}
if (mN.isGroupSummary()) {
setGroupAlertBehavior(GROUP_ALERT_CHILDREN);
} else {
setGroupAlertBehavior(GROUP_ALERT_SUMMARY);
}
setVibrate(null);
setSound(null);
mN.defaults &= ~DEFAULT_SOUND;
mN.defaults &= ~DEFAULT_VIBRATE;
setDefaults(mN.defaults);
if (TextUtils.isEmpty(mN.mGroupKey)) {
setGroup(GROUP_KEY_SILENT);
}
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}. As of Android {@link android.os.Build.VERSION_CODES#S}, apps targeting API level
* {@link android.os.Build.VERSION_CODES#S} or higher won't be able to start activities
* while processing broadcast receivers or services in response to notification clicks. To
* launch an activity in those cases, provide a {@link PendingIntent} for the activity
* itself.
*
* As of {@link android.os.Build.VERSION_CODES#HONEYCOMB}, if this field is unset and you
* have specified a custom RemoteViews with {@link #setContent(RemoteViews)}, you can use
* {@link RemoteViews#setOnClickPendingIntent RemoteViews.setOnClickPendingIntent(int,PendingIntent)}
* to assign PendingIntents to individual views in that custom layout (i.e., to create
* clickable buttons inside the notification view).
*
* @see Notification#contentIntent Notification.contentIntent
*/
@NonNull
public Builder setContentIntent(PendingIntent intent) {
mN.contentIntent = intent;
return this;
}
/**
* Supply a {@link PendingIntent} to send when the notification is cleared explicitly by the user.
*
* @see Notification#deleteIntent
*/
@NonNull
public Builder setDeleteIntent(PendingIntent intent) {
mN.deleteIntent = intent;
return this;
}
/**
* An intent to launch instead of posting the notification to the status bar.
* Only for use with extremely high-priority notifications demanding the user's
* immediate attention, such as an incoming phone call or
* alarm clock that the user has explicitly set to a particular time.
* If this facility is used for something else, please give the user an option
* to turn it off and use a normal notification, as this can be extremely
* disruptive.
*
* 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.
* Prior to {@link Build.VERSION_CODES#TIRAMISU}, the system may display a
* heads up notification (which may display on screen longer than other heads up
* notifications), instead of launching the intent, while the user is using the device.
* From {@link Build.VERSION_CODES#TIRAMISU},
* the system UI will display a heads up notification, instead of launching this intent,
* while the user is using the device. This notification will display with emphasized
* action buttons. If the posting app holds
* {@link android.Manifest.permission#USE_FULL_SCREEN_INTENT}, then the heads
* up notification will appear persistently until the user dismisses or snoozes it, or
* the app cancels it. If the posting app does not hold
* {@link android.Manifest.permission#USE_FULL_SCREEN_INTENT}, then the notification will
* appear as heads up notification even when the screen is locked or turned off, and this
* notification will only be persistent for 60 seconds.
*
* To be launched as a full screen intent, the notification must also be posted to a
* channel with importance level set to IMPORTANCE_HIGH or higher.
*
* 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.
*
* Note that using this flag will stop any ongoing alerting behaviour such
* as sound, vibration or blinking notification LED.
*
* @see Notification#FLAG_ONLY_ALERT_ONCE
*/
@NonNull
public Builder setOnlyAlertOnce(boolean onlyAlertOnce) {
setFlag(FLAG_ONLY_ALERT_ONCE, onlyAlertOnce);
return this;
}
/**
* Specify a desired visibility policy for a Notification associated with a
* foreground service. By default, the system can choose to defer
* visibility of the notification for a short time after the service is
* started. Pass
* {@link Notification#FOREGROUND_SERVICE_IMMEDIATE FOREGROUND_SERVICE_IMMEDIATE}
* to this method in order to guarantee that visibility is never deferred. Pass
* {@link Notification#FOREGROUND_SERVICE_DEFERRED FOREGROUND_SERVICE_DEFERRED}
* to request that visibility is deferred whenever possible.
*
* Note that deferred visibility is not guaranteed. There
* may be some circumstances under which the system will show the foreground
* service's associated Notification immediately even when the app has used
* this method to explicitly request deferred display. 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;
}
/**
* 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}).
*
* As of Android {@link android.os.Build.VERSION_CODES#S}, apps targeting API level
* {@link android.os.Build.VERSION_CODES#S} or higher won't be able to start activities
* while processing broadcast receivers or services in response to notification action
* clicks. To launch an activity in those cases, provide a {@link PendingIntent} to the
* activity itself.
*
* As of Android {@link android.os.Build.VERSION_CODES#N},
* action button icons will not be displayed on action buttons, but are still required
* and are available to
* {@link android.service.notification.NotificationListenerService notification listeners},
* which may display them in other contexts, for example on a wearable device.
*
* @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;
}
/**
* Set the value for a notification flag
*
* @param mask Bit mask of the flag
* @param value Status (on/off) of the flag
*
* @return The same Builder.
*/
@NonNull
public Builder setFlag(@NotificationFlags 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 void bindPhishingAlertIcon(RemoteViews contentView, StandardTemplateParams p) {
contentView.setDrawableTint(
R.id.phishing_alert,
false /* targetBackground */,
getColors(p).getErrorColor(),
PorterDuff.Mode.SRC_ATOP);
}
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.
DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
return dpm.getResources().getDrawable(
getUpdatableProfileBadgeId(), SOLID_COLORED, NOTIFICATION,
this::getDefaultProfileBadgeDrawable);
}
private String getUpdatableProfileBadgeId() {
return mContext.getSystemService(UserManager.class).isManagedProfile()
? WORK_PROFILE_ICON : UNDEFINED;
}
private Drawable getDefaultProfileBadgeDrawable() {
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 (isBackgroundColorized(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 */,
getColors(p).getSecondaryTextColor(),
PorterDuff.Mode.SRC_IN);
}
/**
* @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);
}
/**
* 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.expand_button, "setExpanded", false);
contentView.setViewVisibility(R.id.app_name_text, View.GONE);
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);
mN.mUsesStandardHeader = false;
}
private RemoteViews applyStandardTemplate(int resId, StandardTemplateParams p,
TemplateBindResult result) {
p.headerless(resId == getBaseLayoutResource()
|| resId == getHeadsUpBaseLayoutResource()
|| resId == getCompactHeadsUpBaseLayoutResource()
|| resId == getMessagingCompactHeadsUpLayoutResource()
|| resId == getMessagingLayoutResource()
|| resId == R.layout.notification_template_material_media);
RemoteViews contentView = new BuilderRemoteViews(mContext.getApplicationInfo(), resId);
resetStandardTemplate(contentView);
final Bundle ex = mN.extras;
updateBackgroundColor(contentView, p);
bindNotificationHeader(contentView, p);
bindLargeIconAndApplyMargin(contentView, p, result);
boolean showProgress = handleProgressBar(contentView, ex, p);
boolean hasSecondLine = showProgress;
if (p.hasTitle()) {
contentView.setViewVisibility(p.mTitleViewId, View.VISIBLE);
contentView.setTextViewText(p.mTitleViewId,
ensureColorSpanContrastOrStripStyling(p.mTitle, p));
setTextViewColorPrimary(contentView, p.mTitleViewId, p);
} else if (p.mTitleViewId != R.id.title) {
// This alternate title view ID is not cleared by resetStandardTemplate
contentView.setViewVisibility(p.mTitleViewId, View.GONE);
contentView.setTextViewText(p.mTitleViewId, null);
}
if (p.mText != null && p.mText.length() != 0
&& (!showProgress || p.mAllowTextWithProgress)) {
contentView.setViewVisibility(p.mTextViewId, View.VISIBLE);
contentView.setTextViewText(p.mTextViewId,
ensureColorSpanContrastOrStripStyling(p.mText, p));
setTextViewColorSecondary(contentView, p.mTextViewId, p);
hasSecondLine = true;
} else if (p.mTextViewId != R.id.text) {
// This alternate text view ID is not cleared by resetStandardTemplate
contentView.setViewVisibility(p.mTextViewId, View.GONE);
contentView.setTextViewText(p.mTextViewId, null);
}
setHeaderlessVerticalMargins(contentView, p, hasSecondLine);
return contentView;
}
private static void setHeaderlessVerticalMargins(RemoteViews contentView,
StandardTemplateParams p, boolean hasSecondLine) {
if (!p.mHeaderless) {
return;
}
int marginDimen = hasSecondLine
? R.dimen.notification_headerless_margin_twoline
: R.dimen.notification_headerless_margin_oneline;
contentView.setViewLayoutMarginDimen(R.id.notification_headerless_view_column,
RemoteViews.MARGIN_TOP, marginDimen);
contentView.setViewLayoutMarginDimen(R.id.notification_headerless_view_column,
RemoteViews.MARGIN_BOTTOM, marginDimen);
}
private void setTextViewColorPrimary(RemoteViews contentView, @IdRes int id,
StandardTemplateParams p) {
contentView.setTextColor(id, getPrimaryTextColor(p));
}
/**
* @param p the template params to inflate this with
* @return the primary text color
* @hide
*/
@VisibleForTesting
public @ColorInt int getPrimaryTextColor(StandardTemplateParams p) {
return getColors(p).getPrimaryTextColor();
}
/**
* @param p the template params to inflate this with
* @return the secondary text color
* @hide
*/
@VisibleForTesting
public @ColorInt int getSecondaryTextColor(StandardTemplateParams p) {
return getColors(p).getSecondaryTextColor();
}
private void setTextViewColorSecondary(RemoteViews contentView, @IdRes int id,
StandardTemplateParams p) {
contentView.setTextColor(id, getSecondaryTextColor(p));
}
private Colors getColors(StandardTemplateParams p) {
mColors.resolvePalette(mContext, mN.color, isBackgroundColorized(p), mInNightMode);
return mColors;
}
/**
* @param isHeader If the notification is a notification header
* @return An instance of mColors after resolving the palette
*/
private Colors getColors(boolean isHeader) {
mColors.resolvePalette(mContext, mN.color, !isHeader && mN.isColorized(), mInNightMode);
return mColors;
}
private void updateBackgroundColor(RemoteViews contentView,
StandardTemplateParams p) {
if (isBackgroundColorized(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);
}
}
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.mHideProgress && (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,
mContext.getColorStateList(R.color.notification_progress_background_color));
ColorStateList progressTint = ColorStateList.valueOf(getPrimaryAccentColor(p));
contentView.setProgressTintList(R.id.progress, progressTint);
contentView.setProgressIndeterminateTintList(R.id.progress, progressTint);
return true;
} else {
contentView.setViewVisibility(R.id.progress, View.GONE);
return false;
}
}
private void bindLargeIconAndApplyMargin(RemoteViews contentView,
@NonNull StandardTemplateParams p,
@Nullable TemplateBindResult result) {
if (result == null) {
result = new TemplateBindResult();
}
bindLargeIcon(contentView, p, result);
if (!p.mHeaderless) {
// views in states with a header (big states)
result.mHeadingExtraMarginSet.applyToView(contentView, R.id.notification_header);
result.mTitleMarginSet.applyToView(contentView, R.id.title);
// If there is no title, the text (or big_text) needs to wrap around the image
result.mTitleMarginSet.applyToView(contentView, p.mTextViewId);
contentView.setInt(p.mTextViewId, "setNumIndentLines", p.hasTitle() ? 0 : 1);
}
}
// This code is executed on behalf of other apps' notifications, sometimes even by 3p apps,
// a use case that is not supported by the Compat Framework library. Workarounds to resolve
// the change's state in NotificationManagerService were very complex. These behavior
// changes are entirely visual, and should otherwise be undetectable by apps.
@SuppressWarnings("AndroidFrameworkCompatChange")
private void calculateRightIconDimens(Icon rightIcon, boolean isPromotedPicture,
@NonNull TemplateBindResult result) {
final Resources resources = mContext.getResources();
final float density = resources.getDisplayMetrics().density;
final float iconMarginDp = resources.getDimension(
R.dimen.notification_right_icon_content_margin) / density;
final float contentMarginDp = resources.getDimension(
R.dimen.notification_content_margin_end) / density;
final float expanderSizeDp = resources.getDimension(
R.dimen.notification_header_expand_icon_size) / density - contentMarginDp;
final float viewHeightDp = resources.getDimension(
R.dimen.notification_right_icon_size) / density;
float viewWidthDp = viewHeightDp; // icons are 1:1 by default
if (rightIcon != null && (isPromotedPicture
|| mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.S)) {
Drawable drawable = rightIcon.loadDrawable(mContext);
if (drawable != null) {
int iconWidth = drawable.getIntrinsicWidth();
int iconHeight = drawable.getIntrinsicHeight();
if (iconWidth > iconHeight && iconHeight > 0) {
final float maxViewWidthDp = viewHeightDp * MAX_LARGE_ICON_ASPECT_RATIO;
viewWidthDp = Math.min(viewHeightDp * iconWidth / iconHeight,
maxViewWidthDp);
}
}
}
final float extraMarginEndDpIfVisible = viewWidthDp + iconMarginDp;
result.setRightIconState(rightIcon != null /* visible */, viewWidthDp,
viewHeightDp, extraMarginEndDpIfVisible, expanderSizeDp);
}
/**
* Bind the large icon.
*/
private void bindLargeIcon(RemoteViews contentView, @NonNull StandardTemplateParams p,
@NonNull TemplateBindResult result) {
if (mN.mLargeIcon == null && mN.largeIcon != null) {
mN.mLargeIcon = Icon.createWithBitmap(mN.largeIcon);
}
// Determine the left and right icons
Icon leftIcon = p.mHideLeftIcon ? null : mN.mLargeIcon;
Icon rightIcon = p.mHideRightIcon ? null
: (p.mPromotedPicture != null ? p.mPromotedPicture : mN.mLargeIcon);
// Apply the left icon (without duplicating the bitmap)
if (leftIcon != rightIcon || leftIcon == null) {
// If the leftIcon is explicitly hidden or different from the rightIcon, then set it
// explicitly and make sure it won't take the right_icon drawable.
contentView.setImageViewIcon(R.id.left_icon, leftIcon);
contentView.setIntTag(R.id.left_icon, R.id.tag_uses_right_icon_drawable, 0);
} else {
// If the leftIcon equals the rightIcon, just set the flag to use the right_icon
// drawable. This avoids the view having two copies of the same bitmap.
contentView.setIntTag(R.id.left_icon, R.id.tag_uses_right_icon_drawable, 1);
}
// Always calculate dimens to populate `result` for the GONE case
boolean isPromotedPicture = p.mPromotedPicture != null;
calculateRightIconDimens(rightIcon, isPromotedPicture, result);
// Bind the right icon
if (rightIcon != null) {
contentView.setViewLayoutWidth(R.id.right_icon,
result.mRightIconWidthDp, TypedValue.COMPLEX_UNIT_DIP);
contentView.setViewLayoutHeight(R.id.right_icon,
result.mRightIconHeightDp, TypedValue.COMPLEX_UNIT_DIP);
contentView.setViewVisibility(R.id.right_icon, View.VISIBLE);
contentView.setImageViewIcon(R.id.right_icon, rightIcon);
contentView.setIntTag(R.id.right_icon, R.id.tag_keep_when_showing_left_icon,
isPromotedPicture ? 1 : 0);
processLargeLegacyIcon(rightIcon, contentView, p);
} else {
// The "reset" doesn't clear the drawable, so we do it here. This clear is
// important because the presence of a drawable in this view (regardless of the
// visibility) is used by NotificationGroupingUtil to set the visibility.
contentView.setImageViewIcon(R.id.right_icon, null);
contentView.setIntTag(R.id.right_icon, R.id.tag_keep_when_showing_left_icon, 0);
}
}
private void bindNotificationHeader(RemoteViews contentView, StandardTemplateParams p) {
bindSmallIcon(contentView, p);
// Populate text left-to-right so that separators are only shown between strings
boolean hasTextToLeft = bindHeaderAppName(contentView, p, false /* force */);
hasTextToLeft |= bindHeaderTextSecondary(contentView, p, hasTextToLeft);
hasTextToLeft |= bindHeaderText(contentView, p, hasTextToLeft);
if (!hasTextToLeft) {
// If there's still no text, force add the app name so there is some text.
hasTextToLeft |= bindHeaderAppName(contentView, p, true /* force */);
}
bindHeaderChronometerAndTime(contentView, p, hasTextToLeft);
bindPhishingAlertIcon(contentView, p);
bindProfileBadge(contentView, p);
bindAlertedIcon(contentView, p);
bindExpandButton(contentView, p);
mN.mUsesStandardHeader = true;
}
private void bindExpandButton(RemoteViews contentView, StandardTemplateParams p) {
// set default colors
int bgColor = getBackgroundColor(p);
int pillColor = Colors.flattenAlpha(getColors(p).getProtectionColor(), bgColor);
int textColor = Colors.flattenAlpha(getPrimaryTextColor(p), pillColor);
contentView.setInt(R.id.expand_button, "setDefaultTextColor", textColor);
contentView.setInt(R.id.expand_button, "setDefaultPillColor", pillColor);
// Use different highlighted colors for conversations' unread count
if (p.mHighlightExpander) {
pillColor = Colors.flattenAlpha(
getColors(p).getTertiaryFixedDimAccentColor(), bgColor);
textColor = Colors.flattenAlpha(
getColors(p).getOnTertiaryFixedAccentTextColor(), pillColor);
}
contentView.setInt(R.id.expand_button, "setHighlightTextColor", textColor);
contentView.setInt(R.id.expand_button, "setHighlightPillColor", pillColor);
}
private void bindHeaderChronometerAndTime(RemoteViews contentView,
StandardTemplateParams p, boolean hasTextToLeft) {
if (!p.mHideTime && showsTimeOrChronometer()) {
if (hasTextToLeft) {
contentView.setViewVisibility(R.id.time_divider, View.VISIBLE);
setTextViewColorSecondary(contentView, R.id.time_divider, p);
}
if (mN.extras.getBoolean(EXTRA_SHOW_CHRONOMETER)) {
contentView.setViewVisibility(R.id.chronometer, View.VISIBLE);
contentView.setLong(R.id.chronometer, "setBase", mN.getWhen()
+ (SystemClock.elapsedRealtime() - System.currentTimeMillis()));
contentView.setBoolean(R.id.chronometer, "setStarted", true);
boolean countsDown = mN.extras.getBoolean(EXTRA_CHRONOMETER_COUNT_DOWN);
contentView.setChronometerCountDown(R.id.chronometer, countsDown);
setTextViewColorSecondary(contentView, R.id.chronometer, p);
} else {
contentView.setViewVisibility(R.id.time, View.VISIBLE);
contentView.setLong(R.id.time, "setTime", mN.getWhen());
setTextViewColorSecondary(contentView, R.id.time, p);
}
} else {
// We still want a time to be set but gone, such that we can show and hide it
// on demand in case it's a child notification without anything in the header
contentView.setLong(R.id.time, "setTime", mN.getWhen() != 0 ? mN.getWhen() :
mN.creationTime);
setTextViewColorSecondary(contentView, R.id.time, p);
}
}
/**
* @return true if the header text will be visible
*/
private boolean bindHeaderText(RemoteViews contentView, StandardTemplateParams p,
boolean hasTextToLeft) {
if (p.mHideSubText) {
return false;
}
CharSequence headerText = p.mSubText;
if (headerText == null && mStyle != null && mStyle.mSummaryTextSet
&& mStyle.hasSummaryInHeader()) {
headerText = mStyle.mSummaryText;
}
if (headerText == null
&& mContext.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N
&& mN.extras.getCharSequence(EXTRA_INFO_TEXT) != null) {
headerText = mN.extras.getCharSequence(EXTRA_INFO_TEXT);
}
if (!TextUtils.isEmpty(headerText)) {
contentView.setTextViewText(R.id.header_text, ensureColorSpanContrastOrStripStyling(
processLegacyText(headerText), p));
setTextViewColorSecondary(contentView, R.id.header_text, p);
contentView.setViewVisibility(R.id.header_text, View.VISIBLE);
if (hasTextToLeft) {
contentView.setViewVisibility(R.id.header_text_divider, View.VISIBLE);
setTextViewColorSecondary(contentView, R.id.header_text_divider, p);
}
return true;
}
return false;
}
/**
* @return true if the secondary header text will be visible
*/
private boolean bindHeaderTextSecondary(RemoteViews contentView, StandardTemplateParams p,
boolean hasTextToLeft) {
if (p.mHideSubText) {
return false;
}
if (!TextUtils.isEmpty(p.mHeaderTextSecondary)) {
contentView.setTextViewText(R.id.header_text_secondary,
ensureColorSpanContrastOrStripStyling(
processLegacyText(p.mHeaderTextSecondary), p));
setTextViewColorSecondary(contentView, R.id.header_text_secondary, p);
contentView.setViewVisibility(R.id.header_text_secondary, View.VISIBLE);
if (hasTextToLeft) {
contentView.setViewVisibility(R.id.header_text_secondary_divider, View.VISIBLE);
setTextViewColorSecondary(contentView, R.id.header_text_secondary_divider, p);
}
return true;
}
return false;
}
/**
* @hide
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public String loadHeaderAppName() {
return mN.loadHeaderAppName(mContext);
}
/**
* @return true if the app name will be visible
*/
private boolean bindHeaderAppName(RemoteViews contentView, StandardTemplateParams p,
boolean force) {
if (p.mViewType == StandardTemplateParams.VIEW_TYPE_MINIMIZED && !force) {
// unless the force flag is set, don't show the app name in the minimized state.
return false;
}
if (p.mHeaderless && p.hasTitle()) {
// the headerless template will have the TITLE in this position; return true to
// keep the divider visible between that title and the next text element.
return true;
}
if (p.mHideAppName) {
// The app name is being hidden, so we definitely want to return here.
// Assume that there is a title which will replace it in the header.
return p.hasTitle();
}
contentView.setViewVisibility(R.id.app_name_text, View.VISIBLE);
contentView.setTextViewText(R.id.app_name_text, loadHeaderAppName());
contentView.setTextColor(R.id.app_name_text, getSecondaryTextColor(p));
return true;
}
/**
* Determines if the notification should be colorized *for the purposes of applying colors*.
* If this is the minimized view of a colorized notification, this will return false so that
* internal coloring logic can still render the notification normally.
*/
private boolean isBackgroundColorized(StandardTemplateParams p) {
return p.allowColorization && mN.isColorized();
}
private boolean isCallActionColorCustomizable() {
// NOTE: this doesn't need to check StandardTemplateParams.allowColorization because
// that is only used for disallowing colorization of headers for the minimized state,
// and neither of those conditions applies when showing actions.
// Not requiring StandardTemplateParams as an argument simplifies the creation process.
return mN.isColorized() && mContext.getResources().getBoolean(
R.bool.config_callNotificationActionColorsRequireColorized);
}
private void bindSmallIcon(RemoteViews contentView, StandardTemplateParams p) {
if (Flags.notificationsUseAppIcon()) {
// Override small icon with app icon
mN.mSmallIcon = Icon.createWithResource(mContext,
mN.loadHeaderAppIconRes(mContext));
} else if (mN.mSmallIcon == null && mN.icon != 0) {
mN.mSmallIcon = Icon.createWithResource(mContext, mN.icon);
}
contentView.setImageViewIcon(R.id.icon, mN.mSmallIcon);
contentView.setInt(R.id.icon, "setImageLevel", mN.iconLevel);
// Don't change color if we're using the app icon.
if (!Flags.notificationsUseAppIcon()) {
processSmallIconColor(mN.mSmallIcon, contentView, p);
}
}
/**
* @return true if the built notification will show the time or the chronometer; false
* otherwise
*/
private boolean showsTimeOrChronometer() {
return mN.showsTime() || mN.showsChronometer();
}
private void resetStandardTemplateWithActions(RemoteViews big) {
// actions_container is only reset when there are no actions to avoid focus issues with
// remote inputs.
big.setViewVisibility(R.id.actions, View.GONE);
big.removeAllViews(R.id.actions);
big.setViewVisibility(R.id.notification_material_reply_container, View.GONE);
big.setTextViewText(R.id.notification_material_reply_text_1, null);
big.setViewVisibility(R.id.notification_material_reply_text_1_container, View.GONE);
big.setViewVisibility(R.id.notification_material_reply_progress, View.GONE);
big.setViewVisibility(R.id.notification_material_reply_text_2, View.GONE);
big.setTextViewText(R.id.notification_material_reply_text_2, null);
big.setViewVisibility(R.id.notification_material_reply_text_3, View.GONE);
big.setTextViewText(R.id.notification_material_reply_text_3, null);
// This may get erased by bindSnoozeAction
big.setViewLayoutMarginDimen(R.id.notification_action_list_margin_target,
RemoteViews.MARGIN_BOTTOM, R.dimen.notification_content_margin);
}
private void bindSnoozeAction(RemoteViews big, StandardTemplateParams p) {
boolean hideSnoozeButton = mN.isFgsOrUij()
|| mN.fullScreenIntent != null
|| isBackgroundColorized(p)
|| p.mViewType != StandardTemplateParams.VIEW_TYPE_BIG;
big.setBoolean(R.id.snooze_button, "setEnabled", !hideSnoozeButton);
if (hideSnoozeButton) {
// Only hide; NotificationContentView will show it when it adds the click listener
big.setViewVisibility(R.id.snooze_button, View.GONE);
}
final boolean snoozeEnabled = !hideSnoozeButton
&& mContext.getContentResolver() != null
&& isSnoozeSettingEnabled();
if (snoozeEnabled) {
big.setViewLayoutMarginDimen(R.id.notification_action_list_margin_target,
RemoteViews.MARGIN_BOTTOM, 0);
}
}
private boolean isSnoozeSettingEnabled() {
try {
return Settings.Secure.getIntForUser(mContext.getContentResolver(),
Settings.Secure.SHOW_NOTIFICATION_SNOOZE, 0, UserHandle.USER_CURRENT) == 1;
} catch (SecurityException ex) {
// Most 3p apps can't access this snooze setting, so their NotificationListeners
// would be unable to create notification views if we propagated this exception.
return false;
}
}
/**
* Returns the actions that are not contextual.
*/
private @NonNull List
* Note: Calling build() multiple times returns the same Notification instance,
* so reusing a builder to create multiple Notifications is discouraged.
*
* @return the fully constructed Notification.
*/
public Notification build() {
checkBuilder();
return mBuilder.build();
}
/**
* @hide
* @return Whether we should put the summary be put into the notification header
*/
public boolean hasSummaryInHeader() {
return true;
}
/**
* @hide
* @return Whether custom content views are displayed inline in the style
*/
public boolean displayCustomViewInline() {
return false;
}
/**
* Reduces the image sizes contained in this style.
*
* @hide
*/
public void reduceImageSizes(Context context) {
}
/**
* Validate that this style was properly composed. This is called at build time.
* @hide
*/
public void validate(Context context) {
}
/**
* @hide
*/
@SuppressWarnings("HiddenAbstractMethod")
public abstract boolean areNotificationsVisiblyDifferent(Style other);
/**
* @return the text that should be displayed in the statusBar when heads-upped.
* If {@code null} is returned, the default implementation will be used.
*
* @hide
*/
public CharSequence getHeadsUpStatusBarText() {
return null;
}
}
/**
* Helper class for generating large-format notifications that include a large image attachment.
*
* Here's how you'd set the
* 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 {@link android.os.Build.VERSION_CODES#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)}.
*
*
* From Android {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, messaging style
* notifications that are associated with a valid conversation shortcut
* (via {@link Notification.Builder#setShortcutId(String)}) are displayed in a dedicated
* conversation section in the shade above non-conversation alerting and silence notifications.
* To be a valid conversation shortcut, the shortcut must be a
* {@link ShortcutInfo.Builder#setLongLived(boolean)} dynamic or cached sharing shortcut.
*
*
* 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:
* Starting in {@link Build.VERSION_CODES#R}, this conversation title will be ignored
* if a valid shortcutId is added via {@link Notification.Builder#setShortcutId(String)}.
* In this case, {@link ShortcutInfo#getLongLabel()} (or, if missing,
* {@link ShortcutInfo#getShortLabel()}) will be shown as the conversation title
* instead.
*
* 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;
}
/**
* Sets the icon to be displayed on the conversation, derived from the shortcutId.
*
* @hide
*/
public MessagingStyle setShortcutIcon(@Nullable Icon conversationIcon) {
mShortcutIcon = conversationIcon;
return this;
}
/**
* Return the icon to be displayed on this conversation, derived from the shortcutId. May
* return {@code null}.
*
* @hide
*/
@Nullable
public Icon getShortcutIcon() {
return mShortcutIcon;
}
/**
* Sets the conversation type of this MessageStyle notification.
* {@link #CONVERSATION_TYPE_LEGACY} will use the "older" layout from pre-R,
* {@link #CONVERSATION_TYPE_NORMAL} will use the new "conversation" layout, and
* {@link #CONVERSATION_TYPE_IMPORTANT} will add additional "important" treatments.
*
* @hide
*/
public MessagingStyle setConversationType(@ConversationType int conversationType) {
mConversationType = conversationType;
return this;
}
/** @hide */
@ConversationType
public int getConversationType() {
return mConversationType;
}
/** @hide */
public int getUnreadMessageCount() {
return mUnreadMessageCount;
}
/** @hide */
public MessagingStyle setUnreadMessageCount(int unreadMessageCount) {
mUnreadMessageCount = unreadMessageCount;
return this;
}
/**
* 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 in milliseconds 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);
addExtras(extras, false, 0);
}
/**
* @hide
*/
public void addExtras(Bundle extras, boolean ensureContrast, int backgroundColor) {
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,
getBundleArrayForMessages(mMessages, ensureContrast, backgroundColor));
}
if (!mHistoricMessages.isEmpty()) {
extras.putParcelableArray(EXTRA_HISTORIC_MESSAGES, getBundleArrayForMessages(
mHistoricMessages, ensureContrast, backgroundColor));
}
if (mShortcutIcon != null) {
extras.putParcelable(EXTRA_CONVERSATION_ICON, mShortcutIcon);
}
extras.putInt(EXTRA_CONVERSATION_UNREAD_MESSAGE_COUNT, mUnreadMessageCount);
fixTitleAndTextExtras(extras);
extras.putBoolean(EXTRA_IS_GROUP_CONVERSATION, mIsGroupConversation);
}
private static Bundle[] getBundleArrayForMessages(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.
*
* Notification Listeners including the System UI need permission to access the
* data the Uri points to. The recommended ways to do this are:
*
* 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)}.
*
*
*
* Starting at {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM Android V} the
* {@link Notification#FLAG_NO_CLEAR NO_CLEAR flag} will be set for valid MediaStyle
* notifications.
*
*
* To use this style with your Notification, feed it to
* {@link Notification.Builder#setStyle(android.app.Notification.Style)} like so:
*
* This method is intended for system applications to provide information and/or
* functionality that would otherwise be unavailable to the default output switcher because
* the media originated on a remote device.
*
* @param deviceName The name of the remote device to display
* @param iconResource Icon resource representing the device
* @param chipIntent PendingIntent to send when the output switcher is tapped. May be
* {@code null}, in which case the output switcher will be disabled.
* This intent should open an Activity or it will be ignored.
* @return MediaStyle
*/
@RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL)
@NonNull
public MediaStyle setRemotePlaybackInfo(@NonNull CharSequence deviceName,
@DrawableRes int iconResource, @Nullable PendingIntent chipIntent) {
mDeviceName = deviceName;
mDeviceIcon = iconResource;
mDeviceIntent = chipIntent;
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(null /* customContent */);
}
/**
* @hide
*/
@Override
public RemoteViews makeBigContentView() {
return makeMediaBigContentView(null /* customContent */);
}
/**
* @hide
*/
@Override
public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
return makeMediaContentView(null /* customContent */);
}
/** @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);
}
if (mDeviceName != null) {
extras.putCharSequence(EXTRA_MEDIA_REMOTE_DEVICE, mDeviceName);
}
if (mDeviceIcon > 0) {
extras.putInt(EXTRA_MEDIA_REMOTE_ICON, mDeviceIcon);
}
if (mDeviceIntent != null) {
extras.putParcelable(EXTRA_MEDIA_REMOTE_INTENT, mDeviceIntent);
}
}
/**
* @hide
*/
@Override
protected void restoreFromExtras(Bundle extras) {
super.restoreFromExtras(extras);
if (extras.containsKey(EXTRA_MEDIA_SESSION)) {
mToken = extras.getParcelable(EXTRA_MEDIA_SESSION, MediaSession.Token.class);
}
if (extras.containsKey(EXTRA_COMPACT_ACTIONS)) {
mActionsToShowInCompact = extras.getIntArray(EXTRA_COMPACT_ACTIONS);
}
if (extras.containsKey(EXTRA_MEDIA_REMOTE_DEVICE)) {
mDeviceName = extras.getCharSequence(EXTRA_MEDIA_REMOTE_DEVICE);
}
if (extras.containsKey(EXTRA_MEDIA_REMOTE_ICON)) {
mDeviceIcon = extras.getInt(EXTRA_MEDIA_REMOTE_ICON);
}
if (extras.containsKey(EXTRA_MEDIA_REMOTE_INTENT)) {
mDeviceIntent = extras.getParcelable(
EXTRA_MEDIA_REMOTE_INTENT, PendingIntent.class);
}
}
/**
* @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.
int tintColor = mBuilder.getStandardActionColor(p);
container.setDrawableTint(buttonId, false, tintColor,
PorterDuff.Mode.SRC_ATOP);
int rippleAlpha = mBuilder.getColors(p).getRippleAlpha();
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);
}
/** @hide */
protected RemoteViews makeMediaContentView(@Nullable RemoteViews customContent) {
final int numActions = mBuilder.mActions.size();
final int numActionsToShow = Math.min(mActionsToShowInCompact == null
? 0 : 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));
}
StandardTemplateParams p = mBuilder.mParams.reset()
.viewType(StandardTemplateParams.VIEW_TYPE_NORMAL)
.hideTime(numActionsToShow > 1) // hide if actions wider than a right icon
.hideSubText(numActionsToShow > 1) // hide if actions wider than a right icon
.hideLeftIcon(false) // allow large icon on left when grouped
.hideRightIcon(numActionsToShow > 0) // right icon or actions; not both
.hideProgress(true)
.fillTextsFrom(mBuilder);
TemplateBindResult result = new TemplateBindResult();
RemoteViews template = mBuilder.applyStandardTemplate(
R.layout.notification_template_material_media, p,
null /* result */);
for (int i = 0; i < MAX_MEDIA_BUTTONS_IN_COMPACT; i++) {
if (i < numActionsToShow) {
final Action action = mBuilder.mActions.get(mActionsToShowInCompact[i]);
bindMediaActionButton(template, MEDIA_BUTTON_IDS[i], action, p);
} else {
template.setViewVisibility(MEDIA_BUTTON_IDS[i], View.GONE);
}
}
// Prevent a swooping expand animation when there are no actions
boolean hasActions = numActionsToShow != 0;
template.setViewVisibility(R.id.media_actions, hasActions ? View.VISIBLE : View.GONE);
// Add custom view if provided by subclass.
buildCustomContentIntoTemplate(mBuilder.mContext, template, customContent, p, result);
return template;
}
/** @hide */
protected RemoteViews makeMediaBigContentView(@Nullable RemoteViews customContent) {
final int actionCount = Math.min(mBuilder.mActions.size(), MAX_MEDIA_BUTTONS);
StandardTemplateParams p = mBuilder.mParams.reset()
.viewType(StandardTemplateParams.VIEW_TYPE_BIG)
.hideProgress(true)
.fillTextsFrom(mBuilder);
TemplateBindResult result = new TemplateBindResult();
RemoteViews template = mBuilder.applyStandardTemplate(
R.layout.notification_template_material_big_media, p , result);
for (int i = 0; i < MAX_MEDIA_BUTTONS; i++) {
if (i < actionCount) {
bindMediaActionButton(template,
MEDIA_BUTTON_IDS[i], mBuilder.mActions.get(i), p);
} else {
template.setViewVisibility(MEDIA_BUTTON_IDS[i], View.GONE);
}
}
buildCustomContentIntoTemplate(mBuilder.mContext, template, customContent, p, result);
return template;
}
}
/**
* Helper class for generating large-format notifications that include a large image attachment.
*
* Here's how you'd set the 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 and an
* expanded state that displays an activity. These may be defined via
* {@link Builder#Builder(PendingIntent, Icon)} or they may
* be defined via an existing shortcut using {@link Builder#Builder(String)}.
* This flag has no effect if the app posting the bubble is not in the foreground.
* The app is considered foreground if it is visible and on the screen, note that
* a foreground service does not qualify.
* Generally this flag should only be set if the user has performed an action to request
* or create a bubble. Apps sending bubbles may set this flag so that the bubble is posted without
* the associated notification in the notification shade. Generally this flag should only be set by the app 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. The system will also update this flag with Apps sending bubbles may set this flag so that the bubble is posted without
* the associated notification in the notification shade. Generally the app should only set this flag 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. The system will update this flag with The shortcut icon will be used to represent the bubble when it is collapsed. The shortcut activity will be used when the bubble is expanded. This will display
* the shortcut activity in a floating window over the existing foreground activity. When the activity is launched from a bubble,
* {@link Activity#isLaunchedFromBubble()} will return with {@code true}.
* If the shortcut has not been published when the bubble notification is sent,
* no bubble will be produced. If the shortcut is deleted while the bubble is active,
* the bubble will be removed. The icon will be used to represent the bubble when it is collapsed. An icon
* should be representative of the content within the bubble. If your app produces
* multiple bubbles, the icon should be unique for each of them. The intent that will be used when the bubble is expanded. This will display the
* app content in a floating window over the existing foreground activity. The intent
* should point to a resizable activity. When the activity is launched from a bubble,
* {@link Activity#isLaunchedFromBubble()} will return with {@code true}.
* The intent that will be used when the bubble is expanded. This will display the
* app content in a floating window over the existing foreground activity. The intent
* should point to a resizable activity. The icon will be used to represent the bubble when it is collapsed. An icon
* should be representative of the content within the bubble. If your app produces
* multiple bubbles, the icon should be unique for each of them. It is recommended to use an {@link Icon} of type {@link Icon#TYPE_URI}
* or {@link Icon#TYPE_URI_ADAPTIVE_BITMAP} 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.
* The app is considered foreground if it is visible and on the screen, note that
* a foreground service does not qualify.
* 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. 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";
static final String KEY_DISPLAY_INTENT = "displayIntent";
private static final String KEY_PAGES = "pages";
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;
}
}
private void visitUris(@NonNull Consumer 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, Bitmap.class);
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;
}
private void visitUris(@NonNull Consumer 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.
*/
@FlaggedApi(Flags.FLAG_API_TVEXTENDER)
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";
static final String EXTRA_CONTENT_INTENT = "content_intent";
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(@NonNull 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, PendingIntent.class);
mDeleteIntent = bundle.getParcelable(EXTRA_DELETE_INTENT, PendingIntent.class);
}
}
/**
* 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
@NonNull
public Notification.Builder extend(@NonNull 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 returns 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. Prefer to use {@link setChannelId(String)}.
*
* @hide
*/
@SystemApi
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.
*
* @return this object for method chaining
*/
@NonNull
public TvExtender setChannelId(@Nullable String channelId) {
mChannelId = channelId;
return this;
}
/**
* @removed
* @hide
*/
@Deprecated
@SystemApi
public String getChannel() {
return mChannelId;
}
/**
* Returns the id of the channel this notification posts to on TV.
*/
@Nullable
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.
*
* @param intent the {@link PendingIntent} for the associated notification content
* @return this object for method chaining
*/
@NonNull
public TvExtender setContentIntent(@Nullable 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 Notification#contentIntent
*/
@Nullable
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.
*
* @param intent the {@link PendingIntent} for the associated notification deletion
* @return this object for method chaining
*/
@NonNull
public TvExtender setDeleteIntent(@Nullable 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 Notification#deleteIntent
*/
@Nullable
public PendingIntent getDeleteIntent() {
return mDeleteIntent;
}
/**
* Specifies whether this notification should suppress showing a message over top of apps
* outside of the launcher.
*
* @param suppress whether the notification should suppress showing over apps.
* @return this object for method chaining
*/
@NonNull
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.
*
* @hide
*/
@SystemApi
public boolean getSuppressShowOverApps() {
return mSuppressShowOverApps;
}
/**
* Returns true if this notification should not show messages over top of apps
* outside of the launcher.
*/
public boolean isSuppressShowOverApps() {
return mSuppressShowOverApps;
}
private void visitUris(@NonNull ConsumerCATEGORY_*
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.
*
*
* {@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,
SEMANTIC_ACTION_MARK_CONVERSATION_AS_PRIORITY,
SEMANTIC_ACTION_CONVERSATION_IS_PHISHING
})
@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();
if (Flags.sortSectionByTime()) {
creationTime = when;
extras.putBoolean(EXTRA_SHOW_WHEN, true);
} else {
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)
{
if (Flags.sortSectionByTime()) {
creationTime = when;
extras.putBoolean(EXTRA_SHOW_WHEN, true);
}
new Builder(context)
.setWhen(when)
.setSmallIcon(icon)
.setTicker(tickerText)
.setContentTitle(contentTitle)
.setContentText(contentText)
.setContentIntent(PendingIntent.getActivity(
context, 0, contentIntent, PendingIntent.FLAG_MUTABLE))
.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;
if (Flags.sortSectionByTime()) {
creationTime = when;
extras.putBoolean(EXTRA_SHOW_WHEN, true);
} else {
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 ArrayList
*
* And the following are some examples of notifications that do not belong in the
* conversation space:
*
*
* when
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.
*
*
*
*
* If the app has specified
* {@code NotificationBuilder.setForegroundServiceBehavior(BEHAVIOR_DEFERRED_DISPLAY)}
* then this method will return {@code false} and notification visibility will be
* deferred following the service's transition to the foreground state even in the
* circumstances described above.
*
* @return whether this notification should be displayed immediately when
* its associated service transitions to the foreground state
* @hide
*/
@TestApi
public boolean shouldShowForegroundImmediately() {
// Has the app demanded immediate display?
if (mFgsDeferBehavior == FOREGROUND_SERVICE_IMMEDIATE) {
return true;
}
// Has the app demanded deferred display?
if (mFgsDeferBehavior == FOREGROUND_SERVICE_DEFERRED) {
return false;
}
// We show these sorts of notifications immediately in the absence of
// any explicit app declaration
if (isMediaNotification()
|| CATEGORY_CALL.equals(category)
|| CATEGORY_NAVIGATION.equals(category)
|| (actions != null && actions.length > 0)) {
return true;
}
// No extenuating circumstances: defer visibility
return false;
}
/**
* Has forced deferral for FGS purposes been specified?
* @hide
*/
public boolean isForegroundDisplayForceDeferred() {
return FOREGROUND_SERVICE_DEFERRED == mFgsDeferBehavior;
}
/**
* @return the style class of this notification
* @hide
*/
public Class extends Notification.Style> getNotificationStyle() {
String templateClass = extras.getString(Notification.EXTRA_TEMPLATE);
if (!TextUtils.isEmpty(templateClass)) {
return Notification.getNotificationStyleClass(templateClass);
}
return null;
}
/**
* @return whether the style of this notification is the one provided
* @hide
*/
public boolean isStyle(@NonNull Class extends Style> styleClass) {
String templateClass = extras.getString(Notification.EXTRA_TEMPLATE);
return Objects.equals(templateClass, styleClass.getName());
}
/**
* @return true if this notification is colorized *for the purposes of ranking*. If the
* {@link #color} is {@link #COLOR_DEFAULT} this will be true, even though the actual
* appearance of the notification may not be "colorized".
*
* @hide
*/
public boolean isColorized() {
return extras.getBoolean(EXTRA_COLORIZED)
&& (hasColorizedPermission() || isFgsOrUij());
}
/**
* Returns whether an app can colorize due to the android.permission.USE_COLORIZED_NOTIFICATIONS
* permission. The permission is checked when a notification is enqueued.
*
* @hide
*/
public boolean hasColorizedPermission() {
return (flags & Notification.FLAG_CAN_COLORIZE) != 0;
}
/**
* @return true if this is a media style notification with a media session
*
* @hide
*/
public boolean isMediaNotification() {
Class extends Style> style = getNotificationStyle();
boolean isMediaStyle = (MediaStyle.class.equals(style)
|| DecoratedMediaCustomViewStyle.class.equals(style));
boolean hasMediaSession = extras.getParcelable(Notification.EXTRA_MEDIA_SESSION,
MediaSession.Token.class) != null;
return isMediaStyle && hasMediaSession;
}
/**
* @return true for custom notifications, including notifications
* with DecoratedCustomViewStyle or DecoratedMediaCustomViewStyle,
* and other notifications with user-provided custom views.
*
* @hide
*/
public Boolean isCustomNotification() {
if (contentView == null
&& bigContentView == null
&& headsUpContentView == null) {
return false;
}
return true;
}
/**
* @return true if this notification is showing as a bubble
*
* @hide
*/
public boolean isBubbleNotification() {
return (flags & Notification.FLAG_BUBBLE) != 0;
}
private boolean hasLargeIcon() {
return mLargeIcon != null || largeIcon != null;
}
/**
* Returns #when, unless it's set to 0, which should be shown as/treated as a 'current'
* notification. 0 is treated as a special value because it was special in an old version of
* android, and some apps are still (incorrectly) using it.
*
* @hide
*/
public long getWhen() {
if (Flags.sortSectionByTime()) {
if (when == 0) {
return creationTime;
}
}
return when;
}
/**
* @return true if the notification will show the time; false otherwise
* @hide
*/
public boolean showsTime() {
if (Flags.sortSectionByTime()) {
return extras.getBoolean(EXTRA_SHOW_WHEN);
}
return when != 0 && extras.getBoolean(EXTRA_SHOW_WHEN);
}
/**
* @return true if the notification will show a chronometer; false otherwise
* @hide
*/
public boolean showsChronometer() {
if (Flags.sortSectionByTime()) {
return extras.getBoolean(EXTRA_SHOW_CHRONOMETER);
}
return when != 0 && extras.getBoolean(EXTRA_SHOW_CHRONOMETER);
}
/**
* @return true if the notification has image
*/
public boolean hasImage() {
if (isStyle(MessagingStyle.class) && extras != null) {
final Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES,
Parcelable.class);
if (!ArrayUtils.isEmpty(messages)) {
for (MessagingStyle.Message m : MessagingStyle.Message
.getMessagesFromBundleArray(messages)) {
if (m.getDataUri() != null
&& m.getDataMimeType() != null
&& m.getDataMimeType().startsWith("image/")) {
return true;
}
}
}
} else if (hasLargeIcon()) {
return true;
} else if (extras.containsKey(EXTRA_BACKGROUND_IMAGE_URI)) {
return true;
}
return false;
}
/**
* @removed
*/
@SystemApi
public static Class extends Style> getNotificationStyleClass(String templateClass) {
for (Class extends Style> innerClass : PLATFORM_STYLE_CLASSES) {
if (templateClass.equals(innerClass.getName())) {
return innerClass;
}
}
return null;
}
private static void buildCustomContentIntoTemplate(@NonNull Context context,
@NonNull RemoteViews template, @Nullable RemoteViews customContent,
@NonNull StandardTemplateParams p, @NonNull 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();
if (p.mHeaderless) {
template.removeFromParent(R.id.notification_top_line);
// We do not know how many lines ar emote view has, so we presume it has 2; this
// ensures that we don't under-pad the content, which could lead to abuse, at the
// cost of making single-line custom content over-padded.
Builder.setHeaderlessVerticalMargins(template, p, true /* hasSecondLine */);
} else {
// also update the end margin to account for the large icon or expander
Resources resources = context.getResources();
result.mTitleMarginSet.applyToView(template, R.id.notification_main_column,
resources.getDimension(R.dimen.notification_content_margin_end)
/ resources.getDisplayMetrics().density);
}
template.removeAllViewsExceptId(R.id.notification_main_column, R.id.progress);
template.addView(R.id.notification_main_column, customContent, 0 /* index */);
template.addFlags(RemoteViews.FLAG_REAPPLY_DISALLOWED);
childIndex = 0;
}
template.setIntTag(R.id.notification_main_column,
com.android.internal.R.id.notification_custom_view_index_tag,
childIndex);
}
/**
* An object that can apply a rich notification style to a {@link Notification.Builder}
* object.
*/
public static abstract class Style {
/**
* @deprecated public access to the constructor of Style() is only useful for creating
* custom subclasses, but that has actually been impossible due to hidden abstract
* methods, so this constructor is now officially deprecated to clarify that this is
* intended to be disallowed.
*/
@Deprecated
public Style() {}
/**
* The number of items allowed simulatanously in the remote input history.
* @hide
*/
static final int MAX_REMOTE_INPUT_HISTORY_LINES = 3;
private CharSequence mBigContentTitle;
/**
* @hide
*/
protected CharSequence mSummaryText = null;
/**
* @hide
*/
protected boolean mSummaryTextSet = false;
protected Builder mBuilder;
/**
* Overrides ContentTitle in the big form of the template.
* This defaults to the value passed to setContentTitle().
*/
protected void internalSetBigContentTitle(CharSequence title) {
mBigContentTitle = title;
}
/**
* Set the first line of text after the detail section in the big form of the template.
*/
protected void internalSetSummaryText(CharSequence cs) {
mSummaryText = cs;
mSummaryTextSet = true;
}
public void setBuilder(Builder builder) {
if (mBuilder != builder) {
mBuilder = builder;
if (mBuilder != null) {
mBuilder.setStyle(this);
}
}
}
protected void checkBuilder() {
if (mBuilder == null) {
throw new IllegalArgumentException("Style requires a valid Builder object");
}
}
protected RemoteViews getStandardView(int layoutId) {
// TODO(jeffdq): set the view type based on the layout resource?
StandardTemplateParams p = mBuilder.mParams.reset()
.viewType(StandardTemplateParams.VIEW_TYPE_UNSPECIFIED)
.fillTextsFrom(mBuilder);
return getStandardView(layoutId, p, null);
}
/**
* Get the standard view for this style.
*
* @param layoutId The layout id to use.
* @param p the params for this inflation.
* @param result The result where template bind information is saved.
* @return A remoteView for this style.
* @hide
*/
protected RemoteViews getStandardView(int layoutId, StandardTemplateParams p,
TemplateBindResult result) {
checkBuilder();
if (mBigContentTitle != null) {
p.mTitle = mBigContentTitle;
}
return mBuilder.applyStandardTemplateWithActions(layoutId, p, result);
}
/**
* Construct a Style-specific RemoteViews for the collapsed notification layout.
* The default implementation has nothing additional to add.
*
* @param increasedHeight true if this layout be created with an increased height.
* @hide
*/
public RemoteViews makeContentView(boolean increasedHeight) {
return null;
}
/**
* Construct a Style-specific RemoteViews for the final big notification layout.
* @hide
*/
public RemoteViews makeBigContentView() {
return null;
}
/**
* Construct a Style-specific RemoteViews for the final HUN layout.
*
* @param increasedHeight true if this layout be created with an increased height.
* @hide
*/
public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
return null;
}
/**
* Construct a Style-specific RemoteViews for the final compact HUN layout.
* return null to use the standard compact heads up view.
* @hide
*/
@Nullable
public RemoteViews makeCompactHeadsUpContentView() {
return null;
}
/**
* Apply any style-specific extras to this notification before shipping it out.
* @hide
*/
public void addExtras(Bundle extras) {
if (mSummaryTextSet) {
extras.putCharSequence(EXTRA_SUMMARY_TEXT, mSummaryText);
}
if (mBigContentTitle != null) {
extras.putCharSequence(EXTRA_TITLE_BIG, mBigContentTitle);
}
extras.putString(EXTRA_TEMPLATE, this.getClass().getName());
}
/**
* Reconstruct the internal state of this Style object from extras.
* @hide
*/
protected void restoreFromExtras(Bundle extras) {
if (extras.containsKey(EXTRA_SUMMARY_TEXT)) {
mSummaryText = extras.getCharSequence(EXTRA_SUMMARY_TEXT);
mSummaryTextSet = true;
}
if (extras.containsKey(EXTRA_TITLE_BIG)) {
mBigContentTitle = extras.getCharSequence(EXTRA_TITLE_BIG);
}
}
/**
* @hide
*/
public Notification buildStyled(Notification wip) {
addExtras(wip.extras);
return wip;
}
/**
* @hide
*/
public void purgeResources() {}
/**
* Calls {@link android.app.Notification.Builder#build()} on the Builder this Style is
* attached to.
* 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 Icon mPictureIcon;
private Icon mBigLargeIcon;
private boolean mBigLargeIconSet = false;
private CharSequence mPictureContentDescription;
private boolean mShowBigPictureWhenCollapsed;
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().
*/
@NonNull
public BigPictureStyle setBigContentTitle(@Nullable CharSequence title) {
internalSetBigContentTitle(safeCharSequence(title));
return this;
}
/**
* Set the first line of text after the detail section in the big form of the template.
*/
@NonNull
public BigPictureStyle setSummaryText(@Nullable CharSequence cs) {
internalSetSummaryText(safeCharSequence(cs));
return this;
}
/**
* Set the content description of the big picture.
*/
@NonNull
public BigPictureStyle setContentDescription(
@Nullable CharSequence contentDescription) {
mPictureContentDescription = contentDescription;
return this;
}
/**
* @hide
*/
@Nullable
public Icon getBigPicture() {
if (mPictureIcon != null) {
return mPictureIcon;
}
return null;
}
/**
* Provide the bitmap to be used as the payload for the BigPicture notification.
*/
@NonNull
public BigPictureStyle bigPicture(@Nullable Bitmap b) {
mPictureIcon = b == null ? null : Icon.createWithBitmap(b);
return this;
}
/**
* Provide the content Uri to be used as the payload for the BigPicture notification.
*/
@NonNull
public BigPictureStyle bigPicture(@Nullable Icon icon) {
mPictureIcon = icon;
return this;
}
/**
* When set, the {@link #bigPicture(Bitmap) big picture} of this style will be promoted and
* shown in place of the {@link Builder#setLargeIcon(Icon) large icon} in the collapsed
* state of this notification.
*/
@NonNull
public BigPictureStyle showBigPictureWhenCollapsed(boolean show) {
mShowBigPictureWhenCollapsed = show;
return this;
}
/**
* Override the large icon when the big notification is shown.
*/
@NonNull
public BigPictureStyle bigLargeIcon(@Nullable Bitmap b) {
return bigLargeIcon(b != null ? Icon.createWithBitmap(b) : null);
}
/**
* Override the large icon when the big notification is shown.
*/
@NonNull
public BigPictureStyle bigLargeIcon(@Nullable 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 (mPictureIcon != null) {
mPictureIcon.convertToAshmem();
}
if (mBigLargeIcon != null) {
mBigLargeIcon.convertToAshmem();
}
}
/**
* @hide
*/
@Override
public void reduceImageSizes(Context context) {
super.reduceImageSizes(context);
Resources resources = context.getResources();
boolean isLowRam = ActivityManager.isLowRamDeviceStatic();
if (mPictureIcon != null) {
int maxPictureHeight = resources.getDimensionPixelSize(isLowRam
? R.dimen.notification_big_picture_max_height_low_ram
: R.dimen.notification_big_picture_max_height);
int maxPictureWidth = resources.getDimensionPixelSize(isLowRam
? R.dimen.notification_big_picture_max_width_low_ram
: R.dimen.notification_big_picture_max_width);
mPictureIcon.scaleDownIfNecessary(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
*/
@Override
public RemoteViews makeContentView(boolean increasedHeight) {
if (mPictureIcon == null || !mShowBigPictureWhenCollapsed) {
return super.makeContentView(increasedHeight);
}
StandardTemplateParams p = mBuilder.mParams.reset()
.viewType(StandardTemplateParams.VIEW_TYPE_NORMAL)
.fillTextsFrom(mBuilder)
.promotedPicture(mPictureIcon);
return getStandardView(mBuilder.getBaseLayoutResource(), p, null /* result */);
}
/**
* @hide
*/
@Override
public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
if (mPictureIcon == null || !mShowBigPictureWhenCollapsed) {
return super.makeHeadsUpContentView(increasedHeight);
}
StandardTemplateParams p = mBuilder.mParams.reset()
.viewType(StandardTemplateParams.VIEW_TYPE_HEADS_UP)
.fillTextsFrom(mBuilder)
.promotedPicture(mPictureIcon);
return getStandardView(mBuilder.getHeadsUpBaseLayoutResource(), p, null /* result */);
}
/**
* @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()
.viewType(StandardTemplateParams.VIEW_TYPE_BIG).fillTextsFrom(mBuilder);
RemoteViews contentView = getStandardView(mBuilder.getBigPictureLayoutResource(),
p, null /* result */);
if (mSummaryTextSet) {
contentView.setTextViewText(R.id.text,
mBuilder.ensureColorSpanContrastOrStripStyling(
mBuilder.processLegacyText(mSummaryText), p));
mBuilder.setTextViewColorSecondary(contentView, R.id.text, p);
contentView.setViewVisibility(R.id.text, View.VISIBLE);
}
if (mBigLargeIconSet) {
mBuilder.mN.mLargeIcon = oldLargeIcon;
mBuilder.mN.largeIcon = largeIconLegacy;
}
contentView.setImageViewIcon(R.id.big_picture, mPictureIcon);
if (mPictureContentDescription != null) {
contentView.setContentDescription(R.id.big_picture, mPictureContentDescription);
}
return contentView;
}
/**
* @hide
*/
public void addExtras(Bundle extras) {
super.addExtras(extras);
if (mBigLargeIconSet) {
extras.putParcelable(EXTRA_LARGE_ICON_BIG, mBigLargeIcon);
}
if (mPictureContentDescription != null) {
extras.putCharSequence(EXTRA_PICTURE_CONTENT_DESCRIPTION,
mPictureContentDescription);
}
extras.putBoolean(EXTRA_SHOW_BIG_PICTURE_WHEN_COLLAPSED, mShowBigPictureWhenCollapsed);
if (mPictureIcon == null) {
extras.remove(EXTRA_PICTURE_ICON);
extras.remove(EXTRA_PICTURE);
} else if (mPictureIcon.getType() == Icon.TYPE_BITMAP) {
// If the icon contains a bitmap, use the old extra so that listeners which look
// for that extra can still find the picture. Don't include the new extra in
// that case, to avoid duplicating data. Leave the unused extra set to null to avoid
// crashing apps that came to expect it to be present but null.
extras.putParcelable(EXTRA_PICTURE, mPictureIcon.getBitmap());
extras.putParcelable(EXTRA_PICTURE_ICON, null);
} else {
extras.putParcelable(EXTRA_PICTURE, null);
extras.putParcelable(EXTRA_PICTURE_ICON, mPictureIcon);
}
}
/**
* @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, Icon.class);
}
if (extras.containsKey(EXTRA_PICTURE_CONTENT_DESCRIPTION)) {
mPictureContentDescription =
extras.getCharSequence(EXTRA_PICTURE_CONTENT_DESCRIPTION);
}
mShowBigPictureWhenCollapsed = extras.getBoolean(EXTRA_SHOW_BIG_PICTURE_WHEN_COLLAPSED);
mPictureIcon = getPictureIcon(extras);
}
/** @hide */
@Nullable
public static Icon getPictureIcon(@Nullable Bundle extras) {
if (extras == null) return null;
// When this style adds a picture, we only add one of the keys. If both were added,
// it would most likely be a legacy app trying to override the picture in some way.
// Because of that case it's better to give precedence to the legacy field.
Bitmap bitmapPicture = extras.getParcelable(EXTRA_PICTURE, Bitmap.class);
if (bitmapPicture != null) {
return Icon.createWithBitmap(bitmapPicture);
} else {
return extras.getParcelable(EXTRA_PICTURE_ICON, Icon.class);
}
}
/**
* @hide
*/
@Override
public boolean hasSummaryInHeader() {
return false;
}
/**
* @hide
*/
@Override
public boolean areNotificationsVisiblyDifferent(Style other) {
if (other == null || getClass() != other.getClass()) {
return true;
}
BigPictureStyle otherS = (BigPictureStyle) other;
return areIconsMaybeDifferent(getBigPicture(), otherS.getBigPicture());
}
}
/**
* 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) {
ArrayList
*
* 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;
/** @hide */
public static final int CONVERSATION_TYPE_LEGACY = 0;
/** @hide */
public static final int CONVERSATION_TYPE_NORMAL = 1;
/** @hide */
public static final int CONVERSATION_TYPE_IMPORTANT = 2;
/** @hide */
@IntDef(prefix = {"CONVERSATION_TYPE_"}, value = {
CONVERSATION_TYPE_LEGACY, CONVERSATION_TYPE_NORMAL, CONVERSATION_TYPE_IMPORTANT
})
@Retention(RetentionPolicy.SOURCE)
public @interface ConversationType {}
@NonNull Person mUser;
@Nullable CharSequence mConversationTitle;
@Nullable Icon mShortcutIcon;
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#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 in milliseconds 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#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_ITEMS}.
*
*
* @return this object for method chaining
*/
public Message setData(String dataMimeType, Uri dataUri) {
mDataMimeType = dataMimeType;
mDataUri = dataUri;
return this;
}
/**
* Strip styling or updates TextAppearance spans in message text.
* @hide
*/
public void ensureColorContrastOrStripStyling(int backgroundColor) {
if (Flags.cleanUpSpansAndNewLines()) {
mText = stripNonStyleSpans(mText);
} else {
ensureColorContrast(backgroundColor);
}
}
private CharSequence stripNonStyleSpans(CharSequence text) {
if (text instanceof Spanned) {
Spanned ss = (Spanned) text;
Object[] spans = ss.getSpans(0, ss.length(), Object.class);
SpannableStringBuilder builder = new SpannableStringBuilder(ss.toString());
for (Object span : spans) {
final Object resultSpan;
if (span instanceof StyleSpan
|| span instanceof StrikethroughSpan
|| span instanceof UnderlineSpan) {
resultSpan = span;
} else if (span instanceof TextAppearanceSpan) {
final TextAppearanceSpan originalSpan = (TextAppearanceSpan) span;
resultSpan = new TextAppearanceSpan(
null,
originalSpan.getTextStyle(),
-1,
null,
null);
} else {
continue;
}
builder.setSpan(resultSpan, ss.getSpanStart(span), ss.getSpanEnd(span),
ss.getSpanFlags(span));
}
return builder;
}
return text;
}
/**
* Updates TextAppearance spans in the message text so it has sufficient contrast
* against its background.
* @hide
*/
public void ensureColorContrast(int backgroundColor) {
mText = ContrastColorUtil.ensureColorSpanContrast(mText, backgroundColor);
}
/**
* 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_ITEMS}.
* @hide
*/
public boolean isRemoteInputHistory() {
return mRemoteInputHistory;
}
/**
* Converts the message into a {@link Bundle}. To extract the message back,
* check {@link #getMessageFromBundle()}
* @hide
*/
@NonNull
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, safeCharSequence(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;
}
/**
* See {@link Notification#visitUris(Consumer)}.
*
* @hide
*/
public void visitUris(@NonNull ConsumerInboxStyle
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;
private CharSequence mDeviceName;
private int mDeviceIcon;
private PendingIntent mDeviceIntent;
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;
}
/**
* For media notifications associated with playback on a remote device, provide device
* information that will replace the default values for the output switcher chip on the
* media control, as well as an intent to use when the output switcher chip is tapped,
* on devices where this is supported.
* CallStyle
on a notification:
*
* Notification notif = new Notification.Builder(mContext)
* .setSmallIcon(R.drawable.new_post)
* .setStyle(Notification.CallStyle.forIncomingCall(caller, declineIntent, answerIntent))
* .build();
*
*/
public static class CallStyle extends Style {
/**
* @hide
*/
public static final boolean DEBUG_NEW_ACTION_LAYOUT = true;
/**
* @hide
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef({
CALL_TYPE_UNKNOWN,
CALL_TYPE_INCOMING,
CALL_TYPE_ONGOING,
CALL_TYPE_SCREENING
})
public @interface CallType {};
/**
* Unknown call type.
*
* See {@link #EXTRA_CALL_TYPE}.
*/
public static final int CALL_TYPE_UNKNOWN = 0;
/**
* Call type for incoming calls.
*
* See {@link #EXTRA_CALL_TYPE}.
*/
public static final int CALL_TYPE_INCOMING = 1;
/**
* Call type for ongoing calls.
*
* See {@link #EXTRA_CALL_TYPE}.
*/
public static final int CALL_TYPE_ONGOING = 2;
/**
* Call type for calls that are being screened.
*
* See {@link #EXTRA_CALL_TYPE}.
*/
public static final int CALL_TYPE_SCREENING = 3;
/**
* This is a key used privately on the action.extras to give spacing priority
* to the required call actions
*/
private static final String KEY_ACTION_PRIORITY = "key_action_priority";
private int mCallType;
private Person mPerson;
private PendingIntent mAnswerIntent;
private PendingIntent mDeclineIntent;
private PendingIntent mHangUpIntent;
private boolean mIsVideo;
private Integer mAnswerButtonColor;
private Integer mDeclineButtonColor;
private Icon mVerificationIcon;
private CharSequence mVerificationText;
CallStyle() {
}
/**
* Create a CallStyle for an incoming call.
* This notification will have a decline and an answer action, will allow a single
* custom {@link Builder#addAction(Action) action}, and will have a default
* {@link Builder#setContentText(CharSequence) content text} for an incoming call.
*
* @param person The person displayed as the caller.
* The person also needs to have a non-empty name associated with it.
* @param declineIntent The intent to be sent when the user taps the decline action
* @param answerIntent The intent to be sent when the user taps the answer action
*/
@NonNull
public static CallStyle forIncomingCall(@NonNull Person person,
@NonNull PendingIntent declineIntent, @NonNull PendingIntent answerIntent) {
return new CallStyle(CALL_TYPE_INCOMING, person,
null /* hangUpIntent */,
requireNonNull(declineIntent, "declineIntent is required"),
requireNonNull(answerIntent, "answerIntent is required")
);
}
/**
* Create a CallStyle for an ongoing call.
* This notification will have a hang up action, will allow up to two
* custom {@link Builder#addAction(Action) actions}, and will have a default
* {@link Builder#setContentText(CharSequence) content text} for an ongoing call.
*
* @param person The person displayed as being on the other end of the call.
* The person also needs to have a non-empty name associated with it.
* @param hangUpIntent The intent to be sent when the user taps the hang up action
*/
@NonNull
public static CallStyle forOngoingCall(@NonNull Person person,
@NonNull PendingIntent hangUpIntent) {
return new CallStyle(CALL_TYPE_ONGOING, person,
requireNonNull(hangUpIntent, "hangUpIntent is required"),
null /* declineIntent */,
null /* answerIntent */
);
}
/**
* Create a CallStyle for a call that is being screened.
* This notification will have a hang up and an answer action, will allow a single
* custom {@link Builder#addAction(Action) action}, and will have a default
* {@link Builder#setContentText(CharSequence) content text} for a call that is being
* screened.
*
* @param person The person displayed as the caller.
* The person also needs to have a non-empty name associated with it.
* @param hangUpIntent The intent to be sent when the user taps the hang up action
* @param answerIntent The intent to be sent when the user taps the answer action
*/
@NonNull
public static CallStyle forScreeningCall(@NonNull Person person,
@NonNull PendingIntent hangUpIntent, @NonNull PendingIntent answerIntent) {
return new CallStyle(CALL_TYPE_SCREENING, person,
requireNonNull(hangUpIntent, "hangUpIntent is required"),
null /* declineIntent */,
requireNonNull(answerIntent, "answerIntent is required")
);
}
/**
* @param callType The type of the call
* @param person The person displayed for the incoming call.
* The user also needs to have a non-empty name associated with it.
* @param hangUpIntent The intent to be sent when the user taps the hang up action
* @param declineIntent The intent to be sent when the user taps the decline action
* @param answerIntent The intent to be sent when the user taps the answer action
*/
private CallStyle(@CallType int callType, @NonNull Person person,
@Nullable PendingIntent hangUpIntent, @Nullable PendingIntent declineIntent,
@Nullable PendingIntent answerIntent) {
if (person == null || TextUtils.isEmpty(person.getName())) {
throw new IllegalArgumentException("person must have a non-empty a name");
}
mCallType = callType;
mPerson = person;
mAnswerIntent = answerIntent;
mDeclineIntent = declineIntent;
mHangUpIntent = hangUpIntent;
}
/**
* Sets whether the call is a video call, which may affect the icons or text used on the
* required action buttons.
*/
@NonNull
public CallStyle setIsVideo(boolean isVideo) {
mIsVideo = isVideo;
return this;
}
/**
* Optional icon to be displayed with {@link #setVerificationText(CharSequence) text}
* as a verification status of the caller.
*/
@NonNull
public CallStyle setVerificationIcon(@Nullable Icon verificationIcon) {
mVerificationIcon = verificationIcon;
return this;
}
/**
* Optional text to be displayed with an {@link #setVerificationIcon(Icon) icon}
* as a verification status of the caller.
*/
@NonNull
public CallStyle setVerificationText(@Nullable CharSequence verificationText) {
mVerificationText = safeCharSequence(verificationText);
return this;
}
/**
* Optional color to be used as a hint for the Answer action button's color.
* The system may change this color to ensure sufficient contrast with the background.
* The system may choose to disregard this hint if the notification is not colorized.
*/
@NonNull
public CallStyle setAnswerButtonColorHint(@ColorInt int color) {
mAnswerButtonColor = color;
return this;
}
/**
* Optional color to be used as a hint for the Decline or Hang Up action button's color.
* The system may change this color to ensure sufficient contrast with the background.
* The system may choose to disregard this hint if the notification is not colorized.
*/
@NonNull
public CallStyle setDeclineButtonColorHint(@ColorInt int color) {
mDeclineButtonColor = color;
return this;
}
/** @hide */
@Override
public Notification buildStyled(Notification wip) {
wip = super.buildStyled(wip);
// ensure that the actions in the builder and notification are corrected.
mBuilder.mActions = getActionsListWithSystemActions();
wip.actions = new Action[mBuilder.mActions.size()];
mBuilder.mActions.toArray(wip.actions);
return wip;
}
/**
* @hide
*/
public boolean displayCustomViewInline() {
// This is a lie; True is returned to make sure that the custom view is not used
// instead of the template, but it will not actually be included.
return true;
}
/**
* @hide
*/
@Override
public void purgeResources() {
super.purgeResources();
if (mVerificationIcon != null) {
mVerificationIcon.convertToAshmem();
}
}
/**
* @hide
*/
@Override
public void reduceImageSizes(Context context) {
super.reduceImageSizes(context);
if (mVerificationIcon != null) {
int rightIconSize = context.getResources().getDimensionPixelSize(
ActivityManager.isLowRamDeviceStatic()
? R.dimen.notification_right_icon_size_low_ram
: R.dimen.notification_right_icon_size);
mVerificationIcon.scaleDownIfNecessary(rightIconSize, rightIconSize);
}
}
/**
* @hide
*/
@Override
public RemoteViews makeContentView(boolean increasedHeight) {
return makeCallLayout(StandardTemplateParams.VIEW_TYPE_NORMAL);
}
/**
* @hide
*/
@Override
public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
return makeCallLayout(StandardTemplateParams.VIEW_TYPE_HEADS_UP);
}
/**
* @hide
*/
@Nullable
@Override
public RemoteViews makeCompactHeadsUpContentView() {
// Use existing heads up for call style.
return makeHeadsUpContentView(false);
}
/**
* @hide
*/
public RemoteViews makeBigContentView() {
return makeCallLayout(StandardTemplateParams.VIEW_TYPE_BIG);
}
@NonNull
private Action makeNegativeAction() {
if (mDeclineIntent == null) {
return makeAction(R.drawable.ic_call_decline,
R.string.call_notification_hang_up_action,
mDeclineButtonColor, R.color.call_notification_decline_color,
mHangUpIntent);
} else {
return makeAction(R.drawable.ic_call_decline,
R.string.call_notification_decline_action,
mDeclineButtonColor, R.color.call_notification_decline_color,
mDeclineIntent);
}
}
@Nullable
private Action makeAnswerAction() {
return mAnswerIntent == null ? null : makeAction(
mIsVideo ? R.drawable.ic_call_answer_video : R.drawable.ic_call_answer,
mIsVideo ? R.string.call_notification_answer_video_action
: R.string.call_notification_answer_action,
mAnswerButtonColor, R.color.call_notification_answer_color,
mAnswerIntent);
}
@NonNull
private Action makeAction(@DrawableRes int icon, @StringRes int title,
@ColorInt Integer colorInt, @ColorRes int defaultColorRes, PendingIntent intent) {
if (colorInt == null || !mBuilder.isCallActionColorCustomizable()) {
colorInt = mBuilder.mContext.getColor(defaultColorRes);
}
Action action = new Action.Builder(Icon.createWithResource("", icon),
new SpannableStringBuilder().append(mBuilder.mContext.getString(title),
new ForegroundColorSpan(colorInt),
SpannableStringBuilder.SPAN_INCLUSIVE_INCLUSIVE),
intent).build();
action.getExtras().putBoolean(KEY_ACTION_PRIORITY, true);
return action;
}
private boolean isActionAddedByCallStyle(Action action) {
// This is an internal extra added by the style to these actions. If an app were to add
// this extra to the action themselves, the action would be dropped. :shrug:
return action != null && action.getExtras().getBoolean(KEY_ACTION_PRIORITY);
}
/**
* Gets the actions list for the call with the answer/decline/hangUp actions inserted in
* the correct place. This returns the correct result even if the system actions have
* already been added, and even if more actions were added since then.
* @hide
*/
@NonNull
public ArrayList
* 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 (headsUpContentView == null) {
return null; // no custom view; use the default behavior
}
if (mBuilder.mActions.size() == 0) {
return makeStandardTemplateWithCustomContent(headsUpContentView);
}
TemplateBindResult result = new TemplateBindResult();
StandardTemplateParams p = mBuilder.mParams.reset()
.viewType(StandardTemplateParams.VIEW_TYPE_HEADS_UP)
.decorationType(StandardTemplateParams.DECORATION_PARTIAL)
.fillTextsFrom(mBuilder);
RemoteViews remoteViews = mBuilder.applyStandardTemplateWithActions(
mBuilder.getHeadsUpBaseLayoutResource(), p, result);
buildCustomContentIntoTemplate(mBuilder.mContext, remoteViews, headsUpContentView,
p, result);
return remoteViews;
}
private RemoteViews makeStandardTemplateWithCustomContent(RemoteViews customContent) {
if (customContent == null) {
return null; // no custom view; use the default behavior
}
TemplateBindResult result = new TemplateBindResult();
StandardTemplateParams p = mBuilder.mParams.reset()
.viewType(StandardTemplateParams.VIEW_TYPE_NORMAL)
.decorationType(StandardTemplateParams.DECORATION_PARTIAL)
.fillTextsFrom(mBuilder);
RemoteViews remoteViews = mBuilder.applyStandardTemplate(
mBuilder.getBaseLayoutResource(), p, result);
buildCustomContentIntoTemplate(mBuilder.mContext, remoteViews, customContent,
p, result);
return remoteViews;
}
private RemoteViews makeDecoratedBigContentView() {
RemoteViews bigContentView = mBuilder.mN.bigContentView == null
? mBuilder.mN.contentView
: mBuilder.mN.bigContentView;
if (bigContentView == null) {
return null; // no custom view; use the default behavior
}
TemplateBindResult result = new TemplateBindResult();
StandardTemplateParams p = mBuilder.mParams.reset()
.viewType(StandardTemplateParams.VIEW_TYPE_BIG)
.decorationType(StandardTemplateParams.DECORATION_PARTIAL)
.fillTextsFrom(mBuilder);
RemoteViews remoteViews = mBuilder.applyStandardTemplateWithActions(
mBuilder.getBigBaseLayoutResource(), p, result);
buildCustomContentIntoTemplate(mBuilder.mContext, remoteViews, bigContentView,
p, result);
return remoteViews;
}
/**
* @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) {
return makeMediaContentView(mBuilder.mN.contentView);
}
/**
* @hide
*/
@Override
public RemoteViews makeBigContentView() {
RemoteViews customContent = mBuilder.mN.bigContentView != null
? mBuilder.mN.bigContentView
: mBuilder.mN.contentView;
return makeMediaBigContentView(customContent);
}
/**
* @hide
*/
@Override
public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
RemoteViews customContent = mBuilder.mN.headsUpContentView != null
? mBuilder.mN.headsUpContentView
: mBuilder.mN.contentView;
return makeMediaBigContentView(customContent);
}
/**
* @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;
}
}
/**
* Encapsulates the information needed to display a notification as a bubble.
*
* true
the notification is
* hidden, when false
the notification shows as normal.
*
* true
to hide the notification
* from the user once the bubble has been expanded. true
the notification is
* hidden, when false
the notification shows as normal.
*
* true
to hide the notification
* from the user once the bubble has been expanded.true
the notification is
* hidden, when false
the notification shows as normal.
*
* @hide
*/
public void setSuppressNotification(boolean suppressed) {
if (suppressed) {
mFlags |= Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION;
} else {
mFlags &= ~Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION;
}
}
/**
* Sets whether the bubble should be visually suppressed from the bubble stack if the
* user is viewing the same content outside of the bubble. For example, the user has a
* bubble with Alice and then opens up the main app and navigates to Alice's page.
*
* @hide
*/
public void setSuppressBubble(boolean suppressed) {
if (suppressed) {
mFlags |= Notification.BubbleMetadata.FLAG_SUPPRESS_BUBBLE;
} else {
mFlags &= ~Notification.BubbleMetadata.FLAG_SUPPRESS_BUBBLE;
}
}
/**
* @hide
*/
public void setFlags(int flags) {
mFlags = flags;
}
/**
* @hide
*/
public int getFlags() {
return mFlags;
}
public static final @android.annotation.NonNull Parcelable.Creator
*
*
*
* 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 | PendingIntent.FLAG_MUTABLE_UNAUDITED);
* 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();
*
*
*