/* * Copyright (C) 2012 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.support.v4.app; import android.app.Notification; import android.app.PendingIntent; import android.content.Context; import android.graphics.Bitmap; import android.media.AudioManager; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Parcelable; import android.support.v4.view.GravityCompat; import android.view.Gravity; import android.widget.RemoteViews; import java.util.ArrayList; import java.util.Collections; import java.util.List; /** * Helper for accessing features in {@link android.app.Notification} * introduced after API level 4 in a backwards compatible fashion. */ public class NotificationCompat { /** * Use all default values (where applicable). */ public static final int DEFAULT_ALL = ~0; /** * Use the default notification sound. This will ignore any sound set using * {@link Builder#setSound} * * @see Builder#setDefaults */ public static final int DEFAULT_SOUND = 1; /** * Use the default notification vibrate. This will ignore any vibrate set using * {@link Builder#setVibrate}. Using phone vibration requires the * {@link android.Manifest.permission#VIBRATE VIBRATE} permission. * * @see Builder#setDefaults */ public static final int DEFAULT_VIBRATE = 2; /** * Use the default notification lights. This will ignore the * {@link #FLAG_SHOW_LIGHTS} bit, and values set with {@link Builder#setLights}. * * @see Builder#setDefaults */ public static final int DEFAULT_LIGHTS = 4; /** * 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}. */ public static final int STREAM_DEFAULT = -1; /** * Bit set in the Notification flags field when LEDs should be turned on * for this notification. */ public static final int FLAG_SHOW_LIGHTS = 0x00000001; /** * Bit set in the Notification flags field 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 set in the Notification flags field if * the audio will be repeated until the notification is * cancelled or the notification window is opened. */ public static final int FLAG_INSISTENT = 0x00000004; /** * Bit set in the Notification flags field if the notification's sound, * vibrate and ticker should only be played if the notification is not already showing. */ public static final int FLAG_ONLY_ALERT_ONCE = 0x00000008; /** * Bit set in the Notification flags field if the notification should be canceled when * it is clicked by the user. */ public static final int FLAG_AUTO_CANCEL = 0x00000010; /** * Bit set in the Notification flags field if the notification should not be canceled * when the user clicks the Clear all button. */ public static final int FLAG_NO_CLEAR = 0x00000020; /** * Bit set in the Notification flags field if this notification represents a currently * running service. This will normally be set for you by * {@link android.app.Service#startForeground}. */ public static final int FLAG_FOREGROUND_SERVICE = 0x00000040; /** * Obsolete flag indicating high-priority notifications; use the priority field instead. * * @deprecated Use {@link NotificationCompat.Builder#setPriority(int)} with a positive value. */ public static final int FLAG_HIGH_PRIORITY = 0x00000080; /** * Bit set in the Notification flags field 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 set in the Notification flags field 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; /** * Default notification priority for {@link NotificationCompat.Builder#setPriority(int)}. * If your application does not prioritize its own notifications, * use this value for all notifications. */ public static final int PRIORITY_DEFAULT = 0; /** * Lower notification priority for {@link NotificationCompat.Builder#setPriority(int)}, * for items that are less important. The UI may choose to show * these items smaller, or at a different position in the list, * compared with your app's {@link #PRIORITY_DEFAULT} items. */ public static final int PRIORITY_LOW = -1; /** * Lowest notification priority for {@link NotificationCompat.Builder#setPriority(int)}; * these items might not be shown to the user except under * special circumstances, such as detailed notification logs. */ public static final int PRIORITY_MIN = -2; /** * Higher notification priority for {@link NotificationCompat.Builder#setPriority(int)}, * for more important notifications or alerts. The UI may choose * to show these items larger, or at a different position in * notification lists, compared with your app's {@link #PRIORITY_DEFAULT} items. */ public static final int PRIORITY_HIGH = 1; /** * Highest notification priority for {@link NotificationCompat.Builder#setPriority(int)}, * for your application's most important items that require the user's * prompt attention or input. */ public static final int PRIORITY_MAX = 2; /** * Notification extras key: this is the title of the notification, * as supplied to {@link Builder#setContentTitle(CharSequence)}. */ public static final String EXTRA_TITLE = "android.title"; /** * Notification extras key: this is the title of the notification when shown in expanded form, * e.g. as supplied to {@link BigTextStyle#setBigContentTitle(CharSequence)}. */ public static final String EXTRA_TITLE_BIG = EXTRA_TITLE + ".big"; /** * Notification extras key: this is the main text payload, as supplied to * {@link Builder#setContentText(CharSequence)}. */ public static final String EXTRA_TEXT = "android.text"; /** * Notification extras key: this is a third line of text, as supplied to * {@link Builder#setSubText(CharSequence)}. */ public static final String EXTRA_SUB_TEXT = "android.subText"; /** * Notification extras key: this is a small piece of additional text as supplied to * {@link Builder#setContentInfo(CharSequence)}. */ public static final String EXTRA_INFO_TEXT = "android.infoText"; /** * Notification extras key: this is a line of summary information intended to be shown * alongside expanded notifications, as supplied to (e.g.) * {@link BigTextStyle#setSummaryText(CharSequence)}. */ public static final String EXTRA_SUMMARY_TEXT = "android.summaryText"; /** * Notification extras key: this is the resource ID of the notification's main small icon, as * supplied to {@link Builder#setSmallIcon(int)}. */ public static final String EXTRA_SMALL_ICON = "android.icon"; /** * Notification extras key: this is a bitmap to be used instead of the small icon when showing the * notification payload, as * supplied to {@link Builder#setLargeIcon(android.graphics.Bitmap)}. */ public static final String EXTRA_LARGE_ICON = "android.largeIcon"; /** * Notification extras key: this is a bitmap to be used instead of the one from * {@link Builder#setLargeIcon(android.graphics.Bitmap)} when the notification is * shown in its expanded form, as supplied to * {@link BigPictureStyle#bigLargeIcon(android.graphics.Bitmap)}. */ public static final String EXTRA_LARGE_ICON_BIG = EXTRA_LARGE_ICON + ".big"; /** * Notification extras key: this is the progress value supplied to * {@link Builder#setProgress(int, int, boolean)}. */ public static final String EXTRA_PROGRESS = "android.progress"; /** * Notification extras key: this is the maximum value supplied to * {@link Builder#setProgress(int, int, boolean)}. */ public static final String EXTRA_PROGRESS_MAX = "android.progressMax"; /** * Notification extras key: whether the progress bar is indeterminate, supplied to * {@link Builder#setProgress(int, int, boolean)}. */ public static final String EXTRA_PROGRESS_INDETERMINATE = "android.progressIndeterminate"; /** * Notification extras key: whether the when field set using {@link Builder#setWhen} should * be shown as a count-up timer (specifically a {@link android.widget.Chronometer}) instead * of a timestamp, as supplied to {@link Builder#setUsesChronometer(boolean)}. */ public static final String EXTRA_SHOW_CHRONOMETER = "android.showChronometer"; /** * Notification extras key: this is a bitmap to be shown in {@link BigPictureStyle} expanded * notifications, supplied to {@link BigPictureStyle#bigPicture(android.graphics.Bitmap)}. */ public static final String EXTRA_PICTURE = "android.picture"; /** * Notification extras key: An array of CharSequences to show in {@link InboxStyle} expanded * notifications, each of which was supplied to {@link InboxStyle#addLine(CharSequence)}. */ public static final String EXTRA_TEXT_LINES = "android.textLines"; /** * Notification extras key: An array of people that this notification relates to, specified * by contacts provider contact URI. */ public static final String EXTRA_PEOPLE = "android.people"; private static final NotificationCompatImpl IMPL; interface NotificationCompatImpl { public Notification build(Builder b); public Bundle getExtras(Notification n); public int getActionCount(Notification n); public Action getAction(Notification n, int actionIndex); public Action[] getActionsFromParcelableArrayList(ArrayList parcelables); public ArrayList getParcelableArrayListForActions(Action[] actions); public boolean getLocalOnly(Notification n); public String getGroup(Notification n); public boolean isGroupSummary(Notification n); public String getSortKey(Notification n); } static class NotificationCompatImplBase implements NotificationCompatImpl { @Override public Notification build(Builder b) { Notification result = b.mNotification; result.setLatestEventInfo(b.mContext, b.mContentTitle, b.mContentText, b.mContentIntent); // translate high priority requests into legacy flag if (b.mPriority > PRIORITY_DEFAULT) { result.flags |= FLAG_HIGH_PRIORITY; } return result; } @Override public Bundle getExtras(Notification n) { return null; } @Override public int getActionCount(Notification n) { return 0; } @Override public Action getAction(Notification n, int actionIndex) { return null; } @Override public Action[] getActionsFromParcelableArrayList( ArrayList parcelables) { return null; } @Override public ArrayList getParcelableArrayListForActions(Action[] actions) { return null; } @Override public boolean getLocalOnly(Notification n) { return false; } @Override public String getGroup(Notification n) { return null; } @Override public boolean isGroupSummary(Notification n) { return false; } @Override public String getSortKey(Notification n) { return null; } } static class NotificationCompatImplGingerbread extends NotificationCompatImplBase { @Override public Notification build(Builder b) { Notification result = b.mNotification; result.setLatestEventInfo(b.mContext, b.mContentTitle, b.mContentText, b.mContentIntent); result = NotificationCompatGingerbread.add(result, b.mContext, b.mContentTitle, b.mContentText, b.mContentIntent, b.mFullScreenIntent); // translate high priority requests into legacy flag if (b.mPriority > PRIORITY_DEFAULT) { result.flags |= FLAG_HIGH_PRIORITY; } return result; } } static class NotificationCompatImplHoneycomb extends NotificationCompatImplBase { @Override public Notification build(Builder b) { return NotificationCompatHoneycomb.add(b.mContext, b.mNotification, b.mContentTitle, b.mContentText, b.mContentInfo, b.mTickerView, b.mNumber, b.mContentIntent, b.mFullScreenIntent, b.mLargeIcon); } } static class NotificationCompatImplIceCreamSandwich extends NotificationCompatImplBase { @Override public Notification build(Builder b) { return NotificationCompatIceCreamSandwich.add(b.mContext, b.mNotification, b.mContentTitle, b.mContentText, b.mContentInfo, b.mTickerView, b.mNumber, b.mContentIntent, b.mFullScreenIntent, b.mLargeIcon, b.mProgressMax, b.mProgress, b.mProgressIndeterminate); } } static class NotificationCompatImplJellybean extends NotificationCompatImplBase { @Override public Notification build(Builder b) { NotificationCompatJellybean.Builder builder = new NotificationCompatJellybean.Builder( b.mContext, b.mNotification, b.mContentTitle, b.mContentText, b.mContentInfo, b.mTickerView, b.mNumber, b.mContentIntent, b.mFullScreenIntent, b.mLargeIcon, b.mProgressMax, b.mProgress, b.mProgressIndeterminate, b.mUseChronometer, b.mPriority, b.mSubText, b.mLocalOnly, b.mExtras, b.mGroupKey, b.mGroupSummary, b.mSortKey); addActionsToBuilder(builder, b.mActions); addStyleToBuilderJellybean(builder, b.mStyle); return builder.build(); } @Override public Bundle getExtras(Notification n) { return NotificationCompatJellybean.getExtras(n); } @Override public int getActionCount(Notification n) { return NotificationCompatJellybean.getActionCount(n); } @Override public Action getAction(Notification n, int actionIndex) { return (Action) NotificationCompatJellybean.getAction(n, actionIndex, Action.FACTORY, RemoteInput.FACTORY); } @Override public Action[] getActionsFromParcelableArrayList( ArrayList parcelables) { return (Action[]) NotificationCompatJellybean.getActionsFromParcelableArrayList( parcelables, Action.FACTORY, RemoteInput.FACTORY); } @Override public ArrayList getParcelableArrayListForActions( Action[] actions) { return NotificationCompatJellybean.getParcelableArrayListForActions(actions); } @Override public boolean getLocalOnly(Notification n) { return NotificationCompatJellybean.getLocalOnly(n); } @Override public String getGroup(Notification n) { return NotificationCompatJellybean.getGroup(n); } @Override public boolean isGroupSummary(Notification n) { return NotificationCompatJellybean.isGroupSummary(n); } @Override public String getSortKey(Notification n) { return NotificationCompatJellybean.getSortKey(n); } } static class NotificationCompatImplKitKat extends NotificationCompatImplJellybean { @Override public Notification build(Builder b) { NotificationCompatKitKat.Builder builder = new NotificationCompatKitKat.Builder( b.mContext, b.mNotification, b.mContentTitle, b.mContentText, b.mContentInfo, b.mTickerView, b.mNumber, b.mContentIntent, b.mFullScreenIntent, b.mLargeIcon, b.mProgressMax, b.mProgress, b.mProgressIndeterminate, b.mUseChronometer, b.mPriority, b.mSubText, b.mLocalOnly, b.mExtras, b.mGroupKey, b.mGroupSummary, b.mSortKey); addActionsToBuilder(builder, b.mActions); addStyleToBuilderJellybean(builder, b.mStyle); return builder.build(); } @Override public Bundle getExtras(Notification n) { return NotificationCompatKitKat.getExtras(n); } @Override public int getActionCount(Notification n) { return NotificationCompatKitKat.getActionCount(n); } @Override public Action getAction(Notification n, int actionIndex) { return (Action) NotificationCompatKitKat.getAction(n, actionIndex, Action.FACTORY, RemoteInput.FACTORY); } @Override public boolean getLocalOnly(Notification n) { return NotificationCompatKitKat.getLocalOnly(n); } @Override public String getGroup(Notification n) { return NotificationCompatKitKat.getGroup(n); } @Override public boolean isGroupSummary(Notification n) { return NotificationCompatKitKat.isGroupSummary(n); } @Override public String getSortKey(Notification n) { return NotificationCompatKitKat.getSortKey(n); } } static class NotificationCompatImplApi20 extends NotificationCompatImplKitKat { @Override public Notification build(Builder b) { NotificationCompatApi20.Builder builder = new NotificationCompatApi20.Builder( b.mContext, b.mNotification, b.mContentTitle, b.mContentText, b.mContentInfo, b.mTickerView, b.mNumber, b.mContentIntent, b.mFullScreenIntent, b.mLargeIcon, b.mProgressMax, b.mProgress, b.mProgressIndeterminate, b.mUseChronometer, b.mPriority, b.mSubText, b.mLocalOnly, b.mExtras, b.mGroupKey, b.mGroupSummary, b.mSortKey); addActionsToBuilder(builder, b.mActions); addStyleToBuilderJellybean(builder, b.mStyle); return builder.build(); } @Override public Action getAction(Notification n, int actionIndex) { return (Action) NotificationCompatApi20.getAction(n, actionIndex, Action.FACTORY, RemoteInput.FACTORY); } @Override public Action[] getActionsFromParcelableArrayList( ArrayList parcelables) { return (Action[]) NotificationCompatApi20.getActionsFromParcelableArrayList( parcelables, Action.FACTORY, RemoteInput.FACTORY); } @Override public ArrayList getParcelableArrayListForActions( Action[] actions) { return NotificationCompatApi20.getParcelableArrayListForActions(actions); } @Override public boolean getLocalOnly(Notification n) { return NotificationCompatApi20.getLocalOnly(n); } @Override public String getGroup(Notification n) { return NotificationCompatApi20.getGroup(n); } @Override public boolean isGroupSummary(Notification n) { return NotificationCompatApi20.isGroupSummary(n); } @Override public String getSortKey(Notification n) { return NotificationCompatApi20.getSortKey(n); } } private static void addActionsToBuilder(NotificationBuilderWithActions builder, ArrayList actions) { for (Action action : actions) { builder.addAction(action); } } private static void addStyleToBuilderJellybean(NotificationBuilderWithBuilderAccessor builder, Style style) { if (style != null) { if (style instanceof BigTextStyle) { BigTextStyle bigTextStyle = (BigTextStyle) style; NotificationCompatJellybean.addBigTextStyle(builder, bigTextStyle.mBigContentTitle, bigTextStyle.mSummaryTextSet, bigTextStyle.mSummaryText, bigTextStyle.mBigText); } else if (style instanceof InboxStyle) { InboxStyle inboxStyle = (InboxStyle) style; NotificationCompatJellybean.addInboxStyle(builder, inboxStyle.mBigContentTitle, inboxStyle.mSummaryTextSet, inboxStyle.mSummaryText, inboxStyle.mTexts); } else if (style instanceof BigPictureStyle) { BigPictureStyle bigPictureStyle = (BigPictureStyle) style; NotificationCompatJellybean.addBigPictureStyle(builder, bigPictureStyle.mBigContentTitle, bigPictureStyle.mSummaryTextSet, bigPictureStyle.mSummaryText, bigPictureStyle.mPicture, bigPictureStyle.mBigLargeIcon, bigPictureStyle.mBigLargeIconSet); } } } static { if (Build.VERSION.SDK_INT >= 20) { IMPL = new NotificationCompatImplApi20(); } else if (Build.VERSION.SDK_INT >= 19) { IMPL = new NotificationCompatImplKitKat(); } else if (Build.VERSION.SDK_INT >= 16) { IMPL = new NotificationCompatImplJellybean(); } else if (Build.VERSION.SDK_INT >= 14) { IMPL = new NotificationCompatImplIceCreamSandwich(); } else if (Build.VERSION.SDK_INT >= 11) { IMPL = new NotificationCompatImplHoneycomb(); } else if (Build.VERSION.SDK_INT >= 9) { IMPL = new NotificationCompatImplGingerbread(); } else { IMPL = new NotificationCompatImplBase(); } } /** * Builder class for {@link NotificationCompat} objects. Allows easier control over * all the flags, as well as help constructing the typical notification layouts. *

* On platform versions that don't offer expanded notifications, methods that depend on * expanded notifications have no effect. *

*

* For example, action buttons won't appear on platforms prior to Android 4.1. Action * buttons depend on expanded notifications, which are only available in Android 4.1 * and later. *

* For this reason, you should always ensure that UI controls in a notification are also * available in an {@link android.app.Activity} in your app, and you should always start that * {@link android.app.Activity} when users click the notification. To do this, use the * {@link NotificationCompat.Builder#setContentIntent setContentIntent()} * method. *

* */ public static class Builder { Context mContext; CharSequence mContentTitle; CharSequence mContentText; PendingIntent mContentIntent; PendingIntent mFullScreenIntent; RemoteViews mTickerView; Bitmap mLargeIcon; CharSequence mContentInfo; int mNumber; int mPriority; boolean mUseChronometer; Style mStyle; CharSequence mSubText; int mProgressMax; int mProgress; boolean mProgressIndeterminate; String mGroupKey; boolean mGroupSummary; String mSortKey; ArrayList mActions = new ArrayList(); boolean mLocalOnly = false; Bundle mExtras; Notification mNotification = new Notification(); /** * Constructor. * * Automatically sets the when field to {@link System#currentTimeMillis() * System.currentTimeMillis()} and the audio stream to the * {@link Notification#STREAM_DEFAULT}. * * @param context A {@link Context} that will be used to construct the * RemoteViews. The Context will not be held past the lifetime of this * Builder object. */ public Builder(Context context) { mContext = context; // Set defaults to match the defaults of a Notification mNotification.when = System.currentTimeMillis(); mNotification.audioStreamType = Notification.STREAM_DEFAULT; mPriority = PRIORITY_DEFAULT; } /** * Set the time that the event occurred. Notifications in the panel are * sorted by this time. */ public Builder setWhen(long when) { mNotification.when = when; return this; } /** * Show the {@link Notification#when} field as a stopwatch. * * Instead of presenting 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). * * @see android.widget.Chronometer * @see Notification#when */ public Builder setUsesChronometer(boolean b) { mUseChronometer = b; return this; } /** * Set the small icon to use in the notification layouts. Different classes of devices * may return different sizes. See the UX guidelines for more information on how to * design these icons. * * @param icon A resource ID in the application's package of the drawble to use. */ public Builder setSmallIcon(int icon) { mNotification.icon = icon; return this; } /** * 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 drawble to use. * @param level The level to use for the icon. * * @see android.graphics.drawable.LevelListDrawable */ public Builder setSmallIcon(int icon, int level) { mNotification.icon = icon; mNotification.iconLevel = level; return this; } /** * Set the title (first row) of the notification, in a standard notification. */ public Builder setContentTitle(CharSequence title) { mContentTitle = title; return this; } /** * Set the text (second row) of the notification, in a standard notification. */ public Builder setContentText(CharSequence text) { mContentText = text; return this; } /** * Set the third line of text in the platform notification template. * Don't use if you're also using {@link #setProgress(int, int, boolean)}; * they occupy the same location in the standard template. *
* If the platform does not provide large-format notifications, this method has no effect. * The third line of text only appears in expanded view. *
*/ public Builder setSubText(CharSequence text) { mSubText = text; return this; } /** * Set the large number at the right-hand side of the notification. This is * equivalent to setContentInfo, although it might show the number in a different * font size for readability. */ public Builder setNumber(int number) { mNumber = number; return this; } /** * Set the large text at the right-hand side of the notification. */ public Builder setContentInfo(CharSequence info) { mContentInfo = info; return this; } /** * Set the progress this notification represents, which may be * represented as a {@link android.widget.ProgressBar}. */ public Builder setProgress(int max, int progress, boolean indeterminate) { mProgressMax = max; mProgress = progress; mProgressIndeterminate = indeterminate; return this; } /** * Supply a custom RemoteViews to use instead of the standard one. */ public Builder setContent(RemoteViews views) { mNotification.contentView = views; return this; } /** * Supply a {@link PendingIntent} to send when the notification is clicked. * If you do not supply an intent, you can now add PendingIntents to individual * views to be launched when clicked by calling {@link RemoteViews#setOnClickPendingIntent * RemoteViews.setOnClickPendingIntent(int,PendingIntent)}. Be sure to * read {@link Notification#contentIntent Notification.contentIntent} for * how to correctly use this. */ public Builder setContentIntent(PendingIntent intent) { mContentIntent = intent; return this; } /** * Supply a {@link PendingIntent} to send when the notification is cleared by the user * directly from the notification panel. For example, this intent is sent when the user * clicks the "Clear all" button, or the individual "X" buttons on notifications. This * intent is not sent when the application calls * {@link android.app.NotificationManager#cancel NotificationManager.cancel(int)}. */ public Builder setDeleteIntent(PendingIntent intent) { mNotification.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. * * @param intent The pending intent to launch. * @param highPriority Passing true will cause this notification to be sent * even if other notifications are suppressed. */ public Builder setFullScreenIntent(PendingIntent intent, boolean highPriority) { mFullScreenIntent = intent; setFlag(FLAG_HIGH_PRIORITY, highPriority); return this; } /** * Set the text that is displayed in the status bar when the notification first * arrives. */ public Builder setTicker(CharSequence tickerText) { mNotification.tickerText = tickerText; return this; } /** * Set the text that is displayed in the status bar when the notification first * arrives, and also a RemoteViews object that may be displayed instead on some * devices. */ public Builder setTicker(CharSequence tickerText, RemoteViews views) { mNotification.tickerText = tickerText; mTickerView = views; return this; } /** * Set the large icon that is shown in the ticker and notification. */ public Builder setLargeIcon(Bitmap icon) { mLargeIcon = icon; return this; } /** * Set the sound to play. It will play on the default stream. */ public Builder setSound(Uri sound) { mNotification.sound = sound; mNotification.audioStreamType = Notification.STREAM_DEFAULT; return this; } /** * Set the sound to play. It will play on the stream you supply. * * @see Notification#STREAM_DEFAULT * @see AudioManager for the STREAM_ constants. */ public Builder setSound(Uri sound, int streamType) { mNotification.sound = sound; mNotification.audioStreamType = streamType; return this; } /** * Set the vibration pattern to use. * * @see android.os.Vibrator for a discussion of the pattern * parameter. */ public Builder setVibrate(long[] pattern) { mNotification.vibrate = pattern; return this; } /** * Set the argb value that you would like the LED on the device to blnk, as well as the * rate. The rate is specified in terms of the number of milliseconds to be on * and then the number of milliseconds to be off. */ public Builder setLights(int argb, int onMs, int offMs) { mNotification.ledARGB = argb; mNotification.ledOnMS = onMs; mNotification.ledOffMS = offMs; boolean showLights = mNotification.ledOnMS != 0 && mNotification.ledOffMS != 0; mNotification.flags = (mNotification.flags & ~Notification.FLAG_SHOW_LIGHTS) | (showLights ? Notification.FLAG_SHOW_LIGHTS : 0); return this; } /** * Set whether this is an ongoing notification. * *

Ongoing notifications differ from regular notifications in the following ways: *

    *
  • Ongoing notifications are sorted above the regular notifications in the * notification panel.
  • *
  • Ongoing notifications do not have an 'X' close button, and are not affected * by the "Clear all" button. *
*/ public Builder setOngoing(boolean ongoing) { setFlag(Notification.FLAG_ONGOING_EVENT, ongoing); 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. */ public Builder setOnlyAlertOnce(boolean onlyAlertOnce) { setFlag(Notification.FLAG_ONLY_ALERT_ONCE, onlyAlertOnce); return this; } /** * Setting this flag will make it so the notification is automatically * canceled when the user clicks it in the panel. The PendingIntent * set with {@link #setDeleteIntent} will be broadcast when the notification * is canceled. */ public Builder setAutoCancel(boolean autoCancel) { setFlag(Notification.FLAG_AUTO_CANCEL, autoCancel); return this; } /** * Set whether or not this notification is only relevant to the current device. * *

Some notifications can be bridged to other devices for remote display. * This hint can be set to recommend this notification not be bridged. */ public Builder setLocalOnly(boolean b) { mLocalOnly = b; return this; } /** * Set the default notification options that will be used. *

* The value should be one or more of the following fields combined with * bitwise-or: * {@link Notification#DEFAULT_SOUND}, {@link Notification#DEFAULT_VIBRATE}, * {@link Notification#DEFAULT_LIGHTS}. *

* For all default values, use {@link Notification#DEFAULT_ALL}. */ public Builder setDefaults(int defaults) { mNotification.defaults = defaults; if ((defaults & Notification.DEFAULT_LIGHTS) != 0) { mNotification.flags |= Notification.FLAG_SHOW_LIGHTS; } return this; } private void setFlag(int mask, boolean value) { if (value) { mNotification.flags |= mask; } else { mNotification.flags &= ~mask; } } /** * Set the relative priority for this notification. * * Priority is an indication of how much of the user's * valuable attention should be consumed by this * notification. Low-priority notifications may be hidden from * the user in certain situations, while the user might be * interrupted for a higher-priority notification. * The system sets a notification's priority based on various factors including the * setPriority value. The effect may differ slightly on different platforms. * * @param pri Relative priority for this notification. Must be one of * the priority constants defined by {@link NotificationCompat}. * Acceptable values range from {@link * NotificationCompat#PRIORITY_MIN} (-2) to {@link * NotificationCompat#PRIORITY_MAX} (2). */ public Builder setPriority(int pri) { mPriority = pri; return this; } /** * Set this notification to be part of a group of notifications sharing the same key. * Grouped notifications may display in a cluster or stack on devices which * support such rendering. * *

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 */ public Builder setGroup(String groupKey) { 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. Requires a group key also be set using {@link #setGroup}. * @param isGroupSummary Whether this notification should be a group summary. * @return this object for method chaining */ public Builder setGroupSummary(boolean isGroupSummary) { mGroupSummary = 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 Builder#setGroup}. * * @see String#compareTo(String) */ public Builder setSortKey(String sortKey) { 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 */ public Builder addExtras(Bundle extras) { if (extras != null) { if (mExtras == null) { mExtras = new Bundle(extras); } else { mExtras.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 */ public Builder setExtras(Bundle extras) { mExtras = 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() { if (mExtras == null) { mExtras = new Bundle(); } return mExtras; } /** * Add an action to this notification. Actions are typically displayed by * the system as a button adjacent to the notification content. *
* Action buttons won't appear on platforms prior to Android 4.1. Action * buttons depend on expanded notifications, which are only available in Android 4.1 * and later. To ensure that an action button's functionality is always available, first * implement the functionality in the {@link android.app.Activity} that starts when a user * clicks the notification (see {@link #setContentIntent setContentIntent()}), and then * enhance the notification by implementing the same functionality with * {@link #addAction addAction()}. * * @param icon Resource ID of a drawable that represents the action. * @param title Text describing the action. * @param intent {@link android.app.PendingIntent} to be fired when the action is invoked. */ public Builder addAction(int icon, CharSequence title, PendingIntent intent) { mActions.add(new Action(icon, 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. *
* Action buttons won't appear on platforms prior to Android 4.1. Action * buttons depend on expanded notifications, which are only available in Android 4.1 * and later. To ensure that an action button's functionality is always available, first * implement the functionality in the {@link android.app.Activity} that starts when a user * clicks the notification (see {@link #setContentIntent setContentIntent()}), and then * enhance the notification by implementing the same functionality with * {@link #addAction addAction()}. * * @param action The action to add. */ public Builder addAction(Action action) { mActions.add(action); return this; } /** * Add a rich notification style to be applied at build time. *
* If the platform does not provide rich notification styles, this method has no effect. The * user will always see the normal notification style. * * @param style Object responsible for modifying the notification style. */ public Builder setStyle(Style style) { if (mStyle != style) { mStyle = style; if (mStyle != null) { mStyle.setBuilder(this); } } return this; } /** * Apply an extender to this notification builder. Extenders may be used to add * metadata or change options on this builder. */ public Builder extend(Extender extender) { extender.extend(this); return this; } /** * @deprecated Use {@link #build()} instead. */ @Deprecated public Notification getNotification() { return IMPL.build(this); } /** * Combine all of the options that have been set and return a new {@link Notification} * object. */ public Notification build() { return IMPL.build(this); } } /** * An object that can apply a rich notification style to a {@link Notification.Builder} * object. *
* If the platform does not provide rich notification styles, methods in this class have no * effect. */ public static abstract class Style { Builder mBuilder; CharSequence mBigContentTitle; CharSequence mSummaryText; boolean mSummaryTextSet = false; public void setBuilder(Builder builder) { if (mBuilder != builder) { mBuilder = builder; if (mBuilder != null) { mBuilder.setStyle(this); } } } public Notification build() { Notification notification = null; if (mBuilder != null) { notification = mBuilder.build(); } return notification; } } /** * Helper class for generating large-format notifications that include a large image attachment. *
* If the platform does not provide large-format notifications, this method has no effect. The * user will always see the normal notification view. *
* This class is a "rebuilder": It attaches to a Builder object and modifies its behavior, like so: *

     * 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 { Bitmap mPicture; Bitmap mBigLargeIcon; boolean mBigLargeIconSet; public BigPictureStyle() { } public BigPictureStyle(Builder builder) { setBuilder(builder); } /** * Overrides ContentTitle in the big form of the template. * This defaults to the value passed to setContentTitle(). */ public BigPictureStyle setBigContentTitle(CharSequence title) { mBigContentTitle = title; return this; } /** * Set the first line of text after the detail section in the big form of the template. */ public BigPictureStyle setSummaryText(CharSequence cs) { mSummaryText = cs; mSummaryTextSet = true; return this; } /** * Provide the bitmap to be used as the payload for the BigPicture notification. */ public BigPictureStyle bigPicture(Bitmap b) { mPicture = b; return this; } /** * Override the large icon when the big notification is shown. */ public BigPictureStyle bigLargeIcon(Bitmap b) { mBigLargeIcon = b; mBigLargeIconSet = true; return this; } } /** * Helper class for generating large-format notifications that include a lot of text. * *
* If the platform does not provide large-format notifications, this method has no effect. The * user will always see the normal notification view. *
* This class is a "rebuilder": It attaches to a Builder object and modifies its behavior, like so: *
     * 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 { CharSequence mBigText; public BigTextStyle() { } 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) { mBigContentTitle = 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) { mSummaryText = cs; mSummaryTextSet = true; 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 = cs; return this; } } /** * Helper class for generating large-format notifications that include a list of (up to 5) strings. * *
* If the platform does not provide large-format notifications, this method has no effect. The * user will always see the normal notification view. *
* This class is a "rebuilder": It attaches to a Builder object and modifies its behavior, like so: *
     * Notification noti = new Notification.Builder()
     *     .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 { ArrayList mTexts = new ArrayList(); public InboxStyle() { } public InboxStyle(Builder builder) { setBuilder(builder); } /** * Overrides ContentTitle in the big form of the template. * This defaults to the value passed to setContentTitle(). */ public InboxStyle setBigContentTitle(CharSequence title) { mBigContentTitle = title; return this; } /** * Set the first line of text after the detail section in the big form of the template. */ public InboxStyle setSummaryText(CharSequence cs) { mSummaryText = cs; mSummaryTextSet = true; return this; } /** * Append a line to the digest section of the Inbox notification. */ public InboxStyle addLine(CharSequence cs) { mTexts.add(cs); return this; } } /** * 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. Action buttons won't appear on platforms prior to Android 4.1. *

* Apps should use {@link NotificationCompat.Builder#addAction(int, CharSequence, PendingIntent)} * or {@link NotificationCompat.Builder#addAction(NotificationCompat.Action)} * to attach actions. */ public static class Action extends NotificationCompatBase.Action { private final Bundle mExtras; private final RemoteInput[] mRemoteInputs; /** * Small icon representing the action. */ 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. */ public PendingIntent actionIntent; public Action(int icon, CharSequence title, PendingIntent intent) { this(icon, title, intent, new Bundle(), null); } private Action(int icon, CharSequence title, PendingIntent intent, Bundle extras, RemoteInput[] remoteInputs) { this.icon = icon; this.title = title; this.actionIntent = intent; this.mExtras = extras != null ? extras : new Bundle(); this.mRemoteInputs = remoteInputs; } @Override protected int getIcon() { return icon; } @Override protected CharSequence getTitle() { return title; } @Override protected PendingIntent getActionIntent() { return actionIntent; } /** * Get additional metadata carried around with this Action. */ public Bundle getExtras() { return mExtras; } /** * 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. */ public RemoteInput[] getRemoteInputs() { return mRemoteInputs; } /** * Builder class for {@link Action} objects. */ public static final class Builder { private final int mIcon; private final CharSequence mTitle; private final PendingIntent mIntent; private final Bundle mExtras; private ArrayList mRemoteInputs; /** * Construct a new builder for {@link Action} object. * @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 */ public Builder(int icon, CharSequence title, PendingIntent intent) { this(icon, title, intent, new Bundle()); } /** * 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.icon, action.title, action.actionIntent, new Bundle(action.mExtras)); } private Builder(int icon, CharSequence title, PendingIntent intent, Bundle extras) { mIcon = icon; mTitle = title; mIntent = intent; mExtras = extras; } /** * Merge additional metadata into this builder. * *

Values within the Bundle will replace existing extras values in this Builder. * * @see NotificationCompat.Action#getExtras */ 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. */ 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 */ public Builder addRemoteInput(RemoteInput remoteInput) { if (mRemoteInputs == null) { mRemoteInputs = new ArrayList(); } mRemoteInputs.add(remoteInput); return this; } /** * Apply an extender to this action builder. Extenders may be used to add * metadata or change options on this builder. */ public Builder extend(Extender extender) { extender.extend(this); return this; } /** * Combine all of the options that have been set and return a new {@link Action} * object. * @return the built action */ public Action build() { RemoteInput[] remoteInputs = mRemoteInputs != null ? mRemoteInputs.toArray(new RemoteInput[mRemoteInputs.size()]) : null; return new Action(mIcon, mTitle, mIntent, mExtras, remoteInputs); } } /** * Extender interface for use with {@link Builder#extend}. Extenders may be used to add * metadata or change options on an action builder. */ public interface Extender { /** * Apply this extender to a notification action builder. * @param builder the builder to be modified. * @return the build object for chaining. */ public Builder extend(Builder builder); } /** * Wearable extender for notification actions. To add extensions to an action, * create a new {@link NotificationCompat.Action.WearableExtender} object using * the {@code WearableExtender()} constructor and apply it to a * {@link NotificationCompat.Action.Builder} using * {@link NotificationCompat.Action.Builder#extend}. * *

         * NotificationCompat.Action action = new NotificationCompat.Action.Builder(
         *         R.drawable.archive_all, "Archive all", actionIntent)
         *         .extend(new NotificationCompat.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"; private static final String KEY_FLAGS = "flags"; // Flags bitwise-ored to mFlags private static final int FLAG_AVAILABLE_OFFLINE = 0x1; // Default value for flags integer private static final int DEFAULT_FLAGS = FLAG_AVAILABLE_OFFLINE; private int mFlags = DEFAULT_FLAGS; /** * Create a {@link NotificationCompat.Action.WearableExtender} with default * options. */ public WearableExtender() { } /** * Create a {@link NotificationCompat.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); } } /** * Apply wearable extensions to a notification action that is being built. This is * typically called by the {@link NotificationCompat.Action.Builder#extend} * method of {@link NotificationCompat.Action.Builder}. */ @Override public Action.Builder extend(Action.Builder builder) { Bundle wearableBundle = new Bundle(); if (mFlags != DEFAULT_FLAGS) { wearableBundle.putInt(KEY_FLAGS, mFlags); } builder.getExtras().putBundle(EXTRA_WEARABLE_EXTENSIONS, wearableBundle); return builder; } @Override public WearableExtender clone() { WearableExtender that = new WearableExtender(); that.mFlags = this.mFlags; 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; } } } /** @hide */ public static final Factory FACTORY = new Factory() { @Override public Action build(int icon, CharSequence title, PendingIntent actionIntent, Bundle extras, RemoteInputCompatBase.RemoteInput[] remoteInputs) { return new Action(icon, title, actionIntent, extras, (RemoteInput[]) remoteInputs); } @Override public Action[] newArray(int length) { return new Action[length]; } }; } /** * Extender interface for use with {@link Builder#extend}. Extenders may be used to add * metadata or change options on a notification builder. */ public interface Extender { /** * Apply this extender to a notification builder. * @param builder the builder to be modified. * @return the build object for chaining. */ public Builder extend(Builder builder); } /** * Helper class to add wearable extensions to notifications. *

See * Creating Notifications * for Android Wear for more information on how to use this class. *

* To create a notification with wearable extensions: *

    *
  1. Create a {@link NotificationCompat.Builder}, setting any desired * properties. *
  2. Create a {@link NotificationCompat.WearableExtender}. *
  3. Set wearable-specific properties using the * {@code add} and {@code set} methods of {@link NotificationCompat.WearableExtender}. *
  4. Call {@link NotificationCompat.Builder#extend} to apply the extensions to a * notification. *
  5. Post the notification to the notification * system with the {@code NotificationManagerCompat.notify(...)} methods * and not the {@code NotificationManager.notify(...)} methods. *
* *
     * Notification notif = new NotificationCompat.Builder(mContext)
     *         .setContentTitle("New mail from " + sender.toString())
     *         .setContentText(subject)
     *         .setSmallIcon(R.drawable.new_mail)
     *         .extend(new NotificationCompat.WearableExtender()
     *                 .setContentIcon(R.drawable.new_mail))
     *         .build();
     * NotificationManagerCompat.from(mContext).notify(0, notif);
* *

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. * *

     * NotificationCompat.WearableExtender wearableExtender =
     *         new NotificationCompat.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. *

For custom display notifications created using {@link #setDisplayIntent}, * the default is {@link #SIZE_LARGE}. All other notifications size automatically based * on their content. */ 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}. */ 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}. */ 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}. */ 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}. */ 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}. */ public static final int SIZE_FULL_SCREEN = 5; /** Notification extra which contains wearable extensions */ private static final String EXTRA_WEARABLE_EXTENSIONS = "android.wearable.EXTENSIONS"; // Keys within EXTRA_WEARABLE_OPTIONS for wearable options. private static final String KEY_ACTIONS = "actions"; private static final String KEY_FLAGS = "flags"; private static final String KEY_DISPLAY_INTENT = "displayIntent"; private static final String KEY_PAGES = "pages"; private static final String KEY_BACKGROUND = "background"; private static final String KEY_CONTENT_ICON = "contentIcon"; private static final String KEY_CONTENT_ICON_GRAVITY = "contentIconGravity"; private static final String KEY_CONTENT_ACTION_INDEX = "contentActionIndex"; private static final String KEY_CUSTOM_SIZE_PRESET = "customSizePreset"; private static final String KEY_CUSTOM_CONTENT_HEIGHT = "customContentHeight"; private static final String KEY_GRAVITY = "gravity"; // 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; // Default value for flags integer private static final int DEFAULT_FLAGS = FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE; private static final int DEFAULT_CONTENT_ICON_GRAVITY = GravityCompat.END; private static final int DEFAULT_GRAVITY = Gravity.BOTTOM; private ArrayList mActions = new ArrayList(); private int mFlags = DEFAULT_FLAGS; private PendingIntent mDisplayIntent; private ArrayList mPages = new ArrayList(); private Bitmap mBackground; private int mContentIcon; private int mContentIconGravity = DEFAULT_CONTENT_ICON_GRAVITY; private int mContentActionIndex = UNSET_ACTION_INDEX; private int mCustomSizePreset = SIZE_DEFAULT; private int mCustomContentHeight; private int mGravity = DEFAULT_GRAVITY; /** * Create a {@link NotificationCompat.WearableExtender} with default * options. */ public WearableExtender() { } public WearableExtender(Notification notif) { Bundle extras = getExtras(notif); Bundle wearableBundle = extras != null ? extras.getBundle(EXTRA_WEARABLE_EXTENSIONS) : null; if (wearableBundle != null) { Action[] actions = IMPL.getActionsFromParcelableArrayList( wearableBundle.getParcelableArrayList(KEY_ACTIONS)); if (actions != null) { Collections.addAll(mActions, actions); } mFlags = wearableBundle.getInt(KEY_FLAGS, DEFAULT_FLAGS); mDisplayIntent = wearableBundle.getParcelable(KEY_DISPLAY_INTENT); Notification[] pages = getNotificationArrayFromBundle( wearableBundle, KEY_PAGES); if (pages != null) { Collections.addAll(mPages, pages); } mBackground = wearableBundle.getParcelable(KEY_BACKGROUND); mContentIcon = wearableBundle.getInt(KEY_CONTENT_ICON); mContentIconGravity = wearableBundle.getInt(KEY_CONTENT_ICON_GRAVITY, DEFAULT_CONTENT_ICON_GRAVITY); mContentActionIndex = wearableBundle.getInt(KEY_CONTENT_ACTION_INDEX, UNSET_ACTION_INDEX); mCustomSizePreset = wearableBundle.getInt(KEY_CUSTOM_SIZE_PRESET, SIZE_DEFAULT); mCustomContentHeight = wearableBundle.getInt(KEY_CUSTOM_CONTENT_HEIGHT); mGravity = wearableBundle.getInt(KEY_GRAVITY, DEFAULT_GRAVITY); } } /** * Apply wearable extensions to a notification that is being built. This is typically * called by the {@link NotificationCompat.Builder#extend} method of * {@link NotificationCompat.Builder}. */ @Override public NotificationCompat.Builder extend(NotificationCompat.Builder builder) { Bundle wearableBundle = new Bundle(); if (!mActions.isEmpty()) { wearableBundle.putParcelableArrayList(KEY_ACTIONS, IMPL.getParcelableArrayListForActions(mActions.toArray( new Action[mActions.size()]))); } if (mFlags != DEFAULT_FLAGS) { wearableBundle.putInt(KEY_FLAGS, mFlags); } if (mDisplayIntent != null) { wearableBundle.putParcelable(KEY_DISPLAY_INTENT, mDisplayIntent); } if (!mPages.isEmpty()) { wearableBundle.putParcelableArray(KEY_PAGES, mPages.toArray( new Notification[mPages.size()])); } if (mBackground != null) { wearableBundle.putParcelable(KEY_BACKGROUND, mBackground); } if (mContentIcon != 0) { wearableBundle.putInt(KEY_CONTENT_ICON, mContentIcon); } if (mContentIconGravity != DEFAULT_CONTENT_ICON_GRAVITY) { wearableBundle.putInt(KEY_CONTENT_ICON_GRAVITY, mContentIconGravity); } if (mContentActionIndex != UNSET_ACTION_INDEX) { wearableBundle.putInt(KEY_CONTENT_ACTION_INDEX, mContentActionIndex); } if (mCustomSizePreset != SIZE_DEFAULT) { wearableBundle.putInt(KEY_CUSTOM_SIZE_PRESET, mCustomSizePreset); } if (mCustomContentHeight != 0) { wearableBundle.putInt(KEY_CUSTOM_CONTENT_HEIGHT, mCustomContentHeight); } if (mGravity != DEFAULT_GRAVITY) { wearableBundle.putInt(KEY_GRAVITY, mGravity); } builder.getExtras().putBundle(EXTRA_WEARABLE_EXTENSIONS, wearableBundle); return builder; } @Override public WearableExtender clone() { WearableExtender that = new WearableExtender(); that.mActions = new ArrayList(this.mActions); that.mFlags = this.mFlags; that.mDisplayIntent = this.mDisplayIntent; that.mPages = new ArrayList(this.mPages); that.mBackground = this.mBackground; that.mContentIcon = this.mContentIcon; that.mContentIconGravity = this.mContentIconGravity; that.mContentActionIndex = this.mContentActionIndex; that.mCustomSizePreset = this.mCustomSizePreset; that.mCustomContentHeight = this.mCustomContentHeight; that.mGravity = this.mGravity; return that; } /** * Add a wearable action 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 NotificationCompat.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 NotificationCompat.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 NotificationCompat.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 NotificationCompat.Action */ public WearableExtender addActions(List actions) { mActions.addAll(actions); return this; } /** * Clear all wearable actions present on this builder. * @return this object for method chaining. * @see #addAction */ public WearableExtender clearActions() { mActions.clear(); return this; } /** * Get the wearable actions present on this notification. */ public List getActions() { return mActions; } /** * Set an intent to launch inside of an activity view when displaying * this notification. The {@link PendingIntent} provided should be for an activity. * *

         * Intent displayIntent = new Intent(context, MyDisplayActivity.class);
         * PendingIntent displayPendingIntent = PendingIntent.getActivity(context,
         *         0, displayIntent, PendingIntent.FLAG_UPDATE_CURRENT);
         * Notification notif = new NotificationCompat.Builder(context)
         *         .extend(new NotificationCompat.WearableExtender()
         *                 .setDisplayIntent(displayPendingIntent)
         *                 .setCustomSizePreset(NotificationCompat.WearableExtender.SIZE_MEDIUM))
         *         .build();
* *

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: *

         * <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 NotificationCompat.WearableExtender#getDisplayIntent */ 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. */ 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 NotificationCompat.WearableExtender#getPages */ 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 NotificationCompat.WearableExtender#getPages */ public WearableExtender addPages(List pages) { mPages.addAll(pages); return this; } /** * Clear all additional pages present on this builder. * @return this object for method chaining. * @see #addPage */ public WearableExtender clearPages() { mPages.clear(); return this; } /** * Get the array of additional pages of content for displaying this notification. The * current notification forms the first page, and elements within this array form * subsequent pages. This field can be used to separate a notification into multiple * sections. * @return the pages for this notification */ public List getPages() { return mPages; } /** * Set a background image to be displayed behind the notification content. * Contrary to the {@link NotificationCompat.BigPictureStyle}, this background * will work with any notification style. * * @param background the background bitmap * @return this object for method chaining * @see NotificationCompat.WearableExtender#getBackground */ public WearableExtender setBackground(Bitmap background) { mBackground = background; return this; } /** * Get a background image to be displayed behind the notification content. * Contrary to the {@link NotificationCompat.BigPictureStyle}, this background * will work with any notification style. * * @return the background image * @see NotificationCompat.WearableExtender#setBackground */ public Bitmap getBackground() { return mBackground; } /** * Set an icon that goes with the content of this notification. */ public WearableExtender setContentIcon(int icon) { mContentIcon = icon; return this; } /** * Get an icon that goes with the content of this notification. */ public int getContentIcon() { return mContentIcon; } /** * Set the gravity that the content icon should have within the notification display. * Supported values include {@link android.view.Gravity#START} and * {@link android.view.Gravity#END}. The default value is {@link android.view.Gravity#END}. * @see #setContentIcon */ public WearableExtender setContentIconGravity(int contentIconGravity) { mContentIconGravity = contentIconGravity; return this; } /** * Get the gravity that the content icon should have within the notification display. * Supported values include {@link android.view.Gravity#START} and * {@link android.view.Gravity#END}. The default value is {@link android.view.Gravity#END}. * @see #getContentIcon */ public int getContentIconGravity() { return mContentIconGravity; } /** * Set an action from this notification's actions to be clickable with the content of * this notification. This action will no longer display separately from the * notification's content. * *

For notifications with multiple pages, child pages can also have content actions * set, although the list of available actions comes from the main notification and not * from the child page's notification. * * @param actionIndex The index of the action to hoist onto the current notification page. * If wearable actions were added to the main notification, this index * will apply to that list, otherwise it will apply to the regular * actions list. */ public WearableExtender setContentAction(int actionIndex) { mContentActionIndex = actionIndex; return this; } /** * Get the index of the notification action, if any, that was specified as being clickable * with the content of this notification. This action will no longer display separately * from the notification's content. * *

For notifications with multiple pages, child pages can also have content actions * set, although the list of available actions comes from the main notification and not * from the child page's notification. * *

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}. */ 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}. */ public int getGravity() { return mGravity; } /** * Set the custom size preset for the display of this notification out of the available * presets found in {@link NotificationCompat.WearableExtender}, e.g. * {@link #SIZE_LARGE}. *

Some custom size presets are only applicable for custom display notifications created * using {@link NotificationCompat.WearableExtender#setDisplayIntent}. Check the * documentation for the preset in question. See also * {@link #setCustomContentHeight} and {@link #getCustomSizePreset}. */ 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 NotificationCompat.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}. */ 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 NotificationCompat.WearableExtender#setDisplayIntent}. See also * {@link NotificationCompat.WearableExtender#setCustomSizePreset} and * {@link #getCustomContentHeight}. */ 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}. */ 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 */ 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. */ 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}. */ 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 NotificationCompat.WearableExtender#addPage}. */ public boolean getHintShowBackgroundOnly() { return (mFlags & FLAG_HINT_SHOW_BACKGROUND_ONLY) != 0; } private void setFlag(int mask, boolean value) { if (value) { mFlags |= mask; } else { mFlags &= ~mask; } } } /** * Get an array of Notification objects from a parcelable array bundle field. * Update the bundle to have a typed array so fetches in the future don't need * to do an array copy. */ private static Notification[] getNotificationArrayFromBundle(Bundle bundle, String key) { Parcelable[] array = bundle.getParcelableArray(key); if (array instanceof Notification[] || array == null) { return (Notification[]) array; } Notification[] typedArray = new Notification[array.length]; for (int i = 0; i < array.length; i++) { typedArray[i] = (Notification) array[i]; } bundle.putParcelableArray(key, typedArray); return typedArray; } /** * Gets the {@link Notification#extras} field from a notification in a backwards * compatible manner. Extras field was supported from JellyBean (Api level 16) * forwards. This function will return null on older api levels. */ public static Bundle getExtras(Notification notif) { return IMPL.getExtras(notif); } /** * Get the number of actions in this notification in a backwards compatible * manner. Actions were supported from JellyBean (Api level 16) forwards. */ public static int getActionCount(Notification notif) { return IMPL.getActionCount(notif); } /** * Get an action on this notification in a backwards compatible * manner. Actions were supported from JellyBean (Api level 16) forwards. * @param notif The notification to inspect. * @param actionIndex The index of the action to retrieve. */ public static Action getAction(Notification notif, int actionIndex) { return IMPL.getAction(notif, actionIndex); } /** * Get whether or not this notification is only relevant to the current device. * *

Some notifications can be bridged to other devices for remote display. * If this hint is set, it is recommend that this notification not be bridged. */ public static boolean getLocalOnly(Notification notif) { return IMPL.getLocalOnly(notif); } /** * Get the key used to group this notification into a cluster or stack * with other notifications on devices which support such rendering. */ public static String getGroup(Notification notif) { return IMPL.getGroup(notif); } /** * Get whether 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. Requires a group key also be set using {@link Builder#setGroup}. * @return Whether this notification is a group summary. */ public static boolean isGroupSummary(Notification notif) { return IMPL.isGroupSummary(notif); } /** * Get a sort key that orders this notification among other notifications from the * same package. This can be useful if an external sort was already applied and an app * would like to preserve this. Notifications will be sorted lexicographically using this * value, although providing different priorities in addition to providing sort key may * cause this value to be ignored. * *

This sort key can also be used to order members of a notification group. See * {@link Builder#setGroup}. * * @see String#compareTo(String) */ public static String getSortKey(Notification notif) { return IMPL.getSortKey(notif); } }