summaryrefslogtreecommitdiff
path: root/core/java/android/app/Notification.java
diff options
context:
space:
mode:
authorMady Mellor <madym@google.com>2020-01-08 17:18:54 -0800
committerMady Mellor <madym@google.com>2020-01-21 12:51:04 -0800
commit2ac2d3aafccb6728b89be610ec9feb22c010e1ad (patch)
tree8ea469f3e0454d85421b234b27d9e47902ee05cd /core/java/android/app/Notification.java
parent871fe0a968e39dc4c78d2ee6e82cb5f15d183d20 (diff)
Bubble API: Allow developers to create BubbleMetadata from a shortcut id
Adds an option to specify a bubble via a shortcutId. If the builder had icon / intent previously specified, the shortcutId will clobber those and vice versa. Unfortunately, this means that BubbleMetadata#getIcon and #getIntent may return null when previously they were non-null. I've deprecated these getters and introduced new ones along with new setters so that the naming remains consistent. NoMan will check if that shortcut exists before applying FLAG_BUBBLE. Update SystemUI to use BubbleMetadata builder with shortcut id for the shortcut experiment. Adds test to ensure the launcher apps callback is added / removed appropriately & that notifs with shortcut bubbles are properly flagged or unflagged. Test: atest NotificationManagerServiceTest Test: atest NotificationTest NotificationManagerTest (see CTS CL) Bug: 138116133 Bug: 144352570 Change-Id: I2e8155edc7fd70d6978fc80310f071bf6510e0d2
Diffstat (limited to 'core/java/android/app/Notification.java')
-rw-r--r--core/java/android/app/Notification.java190
1 files changed, 146 insertions, 44 deletions
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 6f63eea1163d..3c4e861d55f8 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -8542,13 +8542,16 @@ public class Notification implements Parcelable
* Encapsulates the information needed to display a notification as a bubble.
*
* <p>A bubble is used to display app content in a floating window over the existing
- * foreground activity. A bubble has a collapsed state represented by an icon,
- * {@link BubbleMetadata.Builder#setIcon(Icon)} and an expanded state which is populated
- * via {@link BubbleMetadata.Builder#setIntent(PendingIntent)}.</p>
+ * 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 BubbleMetadata.Builder#createIntentBubble(PendingIntent, Icon)} or they may
+ * be definied via an existing shortcut using
+ * {@link BubbleMetadata.Builder#createShortcutBubble(String)}.
+ * </p>
*
* <b>Notifications with a valid and allowed bubble will display in collapsed state
* outside of the notification shade on unlocked devices. When a user interacts with the
- * collapsed bubble, the bubble intent will be invoked and displayed.</b>
+ * collapsed bubble, the bubble activity will be invoked and displayed.</b>
*
* @see Notification.Builder#setBubbleMetadata(BubbleMetadata)
*/
@@ -8560,10 +8563,12 @@ public class Notification implements Parcelable
private int mDesiredHeight;
@DimenRes private int mDesiredHeightResId;
private int mFlags;
+ private String mShortcutId;
/**
* If set and the app creating the bubble is in the foreground, the bubble will be posted
- * in its expanded state, with the contents of {@link #getIntent()} in a floating window.
+ * in its expanded state, with the contents of {@link #getBubbleIntent()} in a floating
+ * window.
*
* <p>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
@@ -8595,34 +8600,61 @@ public class Notification implements Parcelable
public static final int FLAG_SUPPRESS_NOTIFICATION = 0x00000002;
private BubbleMetadata(PendingIntent expandIntent, PendingIntent deleteIntent,
- Icon icon, int height, @DimenRes int heightResId) {
+ Icon icon, int height, @DimenRes int heightResId, String shortcutId) {
mPendingIntent = expandIntent;
mIcon = icon;
mDesiredHeight = height;
mDesiredHeightResId = heightResId;
mDeleteIntent = deleteIntent;
+ mShortcutId = shortcutId;
}
private BubbleMetadata(Parcel in) {
- mPendingIntent = PendingIntent.CREATOR.createFromParcel(in);
- mIcon = Icon.CREATOR.createFromParcel(in);
+ if (in.readInt() != 0) {
+ mPendingIntent = PendingIntent.CREATOR.createFromParcel(in);
+ }
+ if (in.readInt() != 0) {
+ mIcon = Icon.CREATOR.createFromParcel(in);
+ }
mDesiredHeight = in.readInt();
mFlags = in.readInt();
if (in.readInt() != 0) {
mDeleteIntent = PendingIntent.CREATOR.createFromParcel(in);
}
mDesiredHeightResId = in.readInt();
+ if (in.readInt() != 0) {
+ mShortcutId = in.readString();
+ }
}
/**
- * @return the pending intent used to populate the floating window for this bubble.
+ * @return the shortcut id used to populate the bubble, if it exists.
*/
+ @Nullable
+ public String getShortcutId() {
+ return mShortcutId;
+ }
+
+ /**
+ * @deprecated use {@link #getBubbleIntent()} or use {@link #getShortcutId()} if created
+ * with a valid shortcut instead.
+ */
+ @Deprecated
@NonNull
public PendingIntent getIntent() {
return mPendingIntent;
}
/**
+ * @return the pending intent used to populate the floating window for this bubble, or
+ * null if this bubble is shortcut based.
+ */
+ @Nullable
+ public PendingIntent getBubbleIntent() {
+ return mPendingIntent;
+ }
+
+ /**
* @return the pending intent to send when the bubble is dismissed by a user, if one exists.
*/
@Nullable
@@ -8631,17 +8663,28 @@ public class Notification implements Parcelable
}
/**
- * @return the icon that will be displayed for this bubble when it is collapsed.
+ * @deprecated use {@link #getBubbleIcon()} or use {@link #getShortcutId()} if created
+ * with a valid shortcut instead.
*/
+ @Deprecated
@NonNull
public Icon getIcon() {
return mIcon;
}
/**
+ * @return the icon that will be displayed for this bubble when it is collapsed, or null
+ * if the bubble is shortcut based.
+ */
+ @Nullable
+ public Icon getBubbleIcon() {
+ return mIcon;
+ }
+
+ /**
* @return the ideal height, in DPs, for the floating window that app content defined by
- * {@link #getIntent()} for this bubble. A value of 0 indicates a desired height has not
- * been set.
+ * {@link #getBubbleIntent()} for this bubble. A value of 0 indicates a desired height has
+ * not been set.
*/
@Dimension(unit = DP)
public int getDesiredHeight() {
@@ -8650,7 +8693,7 @@ public class Notification implements Parcelable
/**
* @return the resId of ideal height for the floating window that app content defined by
- * {@link #getIntent()} for this bubble. A value of 0 indicates a res value has not
+ * {@link #getBubbleIntent()} for this bubble. A value of 0 indicates a res value has not
* been provided for the desired height.
*/
@DimenRes
@@ -8697,8 +8740,14 @@ public class Notification implements Parcelable
@Override
public void writeToParcel(Parcel out, int flags) {
- mPendingIntent.writeToParcel(out, 0);
- mIcon.writeToParcel(out, 0);
+ out.writeInt(mPendingIntent != null ? 1 : 0);
+ if (mPendingIntent != null) {
+ mPendingIntent.writeToParcel(out, 0);
+ }
+ out.writeInt(mIcon != null ? 1 : 0);
+ if (mIcon != null) {
+ mIcon.writeToParcel(out, 0);
+ }
out.writeInt(mDesiredHeight);
out.writeInt(mFlags);
out.writeInt(mDeleteIntent != null ? 1 : 0);
@@ -8706,6 +8755,10 @@ public class Notification implements Parcelable
mDeleteIntent.writeToParcel(out, 0);
}
out.writeInt(mDesiredHeightResId);
+ out.writeInt(TextUtils.isEmpty(mShortcutId) ? 0 : 1);
+ if (!TextUtils.isEmpty(mShortcutId)) {
+ out.writeString(mShortcutId);
+ }
}
/**
@@ -8733,6 +8786,7 @@ public class Notification implements Parcelable
@DimenRes private int mDesiredHeightResId;
private int mFlags;
private PendingIntent mDeleteIntent;
+ private String mShortcutId;
/**
* Constructs a new builder object.
@@ -8741,50 +8795,98 @@ public class Notification implements Parcelable
}
/**
- * Sets 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.
+ * Creates a {@link BubbleMetadata.Builder} based on a shortcut. Only
+ * {@link android.content.pm.ShortcutManager#addDynamicShortcuts(List)} shortcuts are
+ * supported.
+ *
+ * <p>The shortcut icon will be used to represent the bubble when it is collapsed.</p>
*
- * <p>An intent is required.</p>
+ * <p>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.</p>
*
- * @throws IllegalArgumentException if intent is null
+ * <p>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.</p>
+ *
+ * <p>Calling this method will clear the contents of
+ * {@link #createIntentBubble(PendingIntent, Icon)} if it was previously called on
+ * this builder.</p>
*/
@NonNull
- public BubbleMetadata.Builder setIntent(@NonNull PendingIntent intent) {
- if (intent == null) {
- throw new IllegalArgumentException("Bubble requires non-null pending intent");
+ public BubbleMetadata.Builder createShortcutBubble(@NonNull String shortcutId) {
+ if (!TextUtils.isEmpty(shortcutId)) {
+ // If shortcut id is set, we don't use these if they were previously set.
+ mPendingIntent = null;
+ mIcon = null;
}
- mPendingIntent = intent;
+ mShortcutId = shortcutId;
return this;
}
/**
- * Sets the icon that will represent the bubble when it is collapsed.
+ * Creates a {@link BubbleMetadata.Builder} based on the provided intent and icon.
*
- * <p>An icon is required and should be representative of the content within the bubble.
- * If your app produces multiple bubbles, the image should be unique for each of them.
- * </p>
+ * <p>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.</p>
*
- * <p>The shape of a bubble icon is adaptive and will match the device theme.
+ * <p>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.</p>
*
- * Ideally your icon should be constructed via
- * {@link Icon#createWithAdaptiveBitmap(Bitmap)}, otherwise, the icon will be shrunk
- * and placed on an adaptive shape.
+ * <p>Calling this method will clear the contents of
+ * {@link #createShortcutBubble(String)} if it was previously called on this builder.
* </p>
*
+ * @throws IllegalArgumentException if intent is null.
* @throws IllegalArgumentException if icon is null.
*/
@NonNull
+ public BubbleMetadata.Builder createIntentBubble(@NonNull PendingIntent intent,
+ @NonNull Icon icon) {
+ if (intent == null) {
+ throw new IllegalArgumentException("Bubble requires non-null pending intent");
+ }
+ if (icon == null) {
+ throw new IllegalArgumentException("Bubbles require non-null icon");
+ }
+ mShortcutId = null;
+ mPendingIntent = intent;
+ mIcon = icon;
+ return this;
+ }
+
+ /**
+ * @deprecated use {@link #createIntentBubble(PendingIntent, Icon)}
+ * or {@link #createShortcutBubble(String)} instead.
+ */
+ @Deprecated
+ @NonNull
+ public BubbleMetadata.Builder setIntent(@NonNull PendingIntent intent) {
+ if (intent == null) {
+ throw new IllegalArgumentException("Bubble requires non-null pending intent");
+ }
+ mShortcutId = null;
+ mPendingIntent = intent;
+ return this;
+ }
+
+ /**
+ * @deprecated use {@link #createIntentBubble(PendingIntent, Icon)}
+ * or {@link #createShortcutBubble(String)} instead.
+ */
+ @Deprecated
+ @NonNull
public BubbleMetadata.Builder setIcon(@NonNull Icon icon) {
if (icon == null) {
throw new IllegalArgumentException("Bubbles require non-null icon");
}
+ mShortcutId = null;
mIcon = icon;
return this;
}
/**
- * Sets the desired height in DPs for the app content defined by
- * {@link #setIntent(PendingIntent)}.
+ * Sets the desired height in DPs for the expanded content of the bubble.
*
* <p>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.</p>
@@ -8806,8 +8908,7 @@ public class Notification implements Parcelable
/**
- * Sets the desired height via resId for the app content defined by
- * {@link #setIntent(PendingIntent)}.
+ * Sets the desired height via resId for the expanded content of the bubble.
*
* <p>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.</p>
@@ -8829,7 +8930,7 @@ public class Notification implements Parcelable
/**
* Sets whether the bubble will be posted in its expanded state (with the contents of
- * {@link #getIntent()} in a floating window).
+ * {@link #getBubbleIntent()} in a floating window).
*
* <p>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
@@ -8882,20 +8983,21 @@ public class Notification implements Parcelable
/**
* Creates the {@link BubbleMetadata} defined by this builder.
*
- * @throws IllegalStateException if {@link #setIntent(PendingIntent)} and/or
- * {@link #setIcon(Icon)} have not been called on this
- * builder.
+ * @throws IllegalStateException if neither {@link #createShortcutBubble(String)} or
+ * {@link #createIntentBubble(PendingIntent, Icon)} have been called on this builder.
*/
@NonNull
public BubbleMetadata build() {
- if (mPendingIntent == null) {
- throw new IllegalStateException("Must supply pending intent to bubble");
+ if (mShortcutId == null && mPendingIntent == null) {
+ throw new IllegalStateException(
+ "Must supply pending intent or shortcut to bubble");
}
- if (mIcon == null) {
- throw new IllegalStateException("Must supply an icon for the bubble");
+ if (mShortcutId == null && mIcon == null) {
+ throw new IllegalStateException(
+ "Must supply an icon or shortcut for the bubble");
}
BubbleMetadata data = new BubbleMetadata(mPendingIntent, mDeleteIntent,
- mIcon, mDesiredHeight, mDesiredHeightResId);
+ mIcon, mDesiredHeight, mDesiredHeightResId, mShortcutId);
data.setFlags(mFlags);
return data;
}