Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit c39b4aed authored by Mady Mellor's avatar Mady Mellor
Browse files

Create BubbleMetadata use it instead of app overlay intent

* BubbleMetadata encapsulates necessary info to display a bubble
* Replaces app overlay intent usages with BubbleMetadata
* Renames existing bubble APIs to use 'bubble' rather than 'app overlay'

Bug: 111236845
Test: existing tests pass
Change-Id: I6a85d3c41dda47139fb8d960cadf1c8e109cf29b
parent 4a09436a
Loading
Loading
Loading
Loading
+24 −5
Original line number Diff line number Diff line
@@ -5245,8 +5245,8 @@ package android.app {
    method public android.app.Notification clone();
    method public int describeContents();
    method public boolean getAllowSystemGeneratedContextualActions();
    method public android.app.PendingIntent getAppOverlayIntent();
    method public int getBadgeIconType();
    method public android.app.Notification.BubbleMetadata getBubbleMetadata();
    method public java.lang.String getChannelId();
    method public java.lang.String getGroup();
    method public int getGroupAlertBehavior();
@@ -5459,6 +5459,25 @@ package android.app {
    method public android.app.Notification.BigTextStyle setSummaryText(java.lang.CharSequence);
  }
  public static final class Notification.BubbleMetadata implements android.os.Parcelable {
    method public int describeContents();
    method public int getDesiredHeight();
    method public android.graphics.drawable.Icon getIcon();
    method public android.app.PendingIntent getIntent();
    method public java.lang.CharSequence getTitle();
    method public void writeToParcel(android.os.Parcel, int);
    field public static final android.os.Parcelable.Creator<android.app.Notification.BubbleMetadata> CREATOR;
  }
  public static class Notification.BubbleMetadata.Builder {
    ctor public Notification.BubbleMetadata.Builder();
    method public android.app.Notification.BubbleMetadata build();
    method public android.app.Notification.BubbleMetadata.Builder setDesiredHeight(int);
    method public android.app.Notification.BubbleMetadata.Builder setIcon(android.graphics.drawable.Icon);
    method public android.app.Notification.BubbleMetadata.Builder setIntent(android.app.PendingIntent);
    method public android.app.Notification.BubbleMetadata.Builder setTitle(java.lang.CharSequence);
  }
  public static class Notification.Builder {
    ctor public Notification.Builder(android.content.Context, java.lang.String);
    ctor public deprecated Notification.Builder(android.content.Context);
@@ -5478,9 +5497,9 @@ package android.app {
    method public static android.app.Notification.Builder recoverBuilder(android.content.Context, android.app.Notification);
    method public android.app.Notification.Builder setActions(android.app.Notification.Action...);
    method public android.app.Notification.Builder setAllowSystemGeneratedContextualActions(boolean);
    method public android.app.Notification.Builder setAppOverlayIntent(android.app.PendingIntent);
    method public android.app.Notification.Builder setAutoCancel(boolean);
    method public android.app.Notification.Builder setBadgeIconType(int);
    method public android.app.Notification.Builder setBubbleMetadata(android.app.Notification.BubbleMetadata);
    method public android.app.Notification.Builder setCategory(java.lang.String);
    method public android.app.Notification.Builder setChannelId(java.lang.String);
    method public android.app.Notification.Builder setChronometerCountDown(boolean);
@@ -5695,8 +5714,8 @@ package android.app {
  public final class NotificationChannel implements android.os.Parcelable {
    ctor public NotificationChannel(java.lang.String, java.lang.CharSequence, int);
    method public boolean canBubble();
    method public boolean canBypassDnd();
    method public boolean canOverlayApps();
    method public boolean canShowBadge();
    method public int describeContents();
    method public void enableLights(boolean);
@@ -5712,7 +5731,7 @@ package android.app {
    method public android.net.Uri getSound();
    method public long[] getVibrationPattern();
    method public boolean hasUserSetImportance();
    method public void setAllowAppOverlay(boolean);
    method public void setAllowBubbles(boolean);
    method public void setBypassDnd(boolean);
    method public void setDescription(java.lang.String);
    method public void setGroup(java.lang.String);
@@ -5746,7 +5765,7 @@ package android.app {
  public class NotificationManager {
    method public java.lang.String addAutomaticZenRule(android.app.AutomaticZenRule);
    method public boolean areAppOverlaysAllowed();
    method public boolean areBubblesAllowed();
    method public boolean areNotificationsEnabled();
    method public boolean canNotifyAsPackage(java.lang.String);
    method public void cancel(int);
+1 −1
Original line number Diff line number Diff line
@@ -1284,8 +1284,8 @@ package android.content {
    field public static final java.lang.String EXTRA_LONG_VERSION_CODE = "android.intent.extra.LONG_VERSION_CODE";
    field public static final java.lang.String EXTRA_ORIGINATING_UID = "android.intent.extra.ORIGINATING_UID";
    field public static final java.lang.String EXTRA_PACKAGES = "android.intent.extra.PACKAGES";
    field public static final java.lang.String EXTRA_PERMISSION_NAME = "android.intent.extra.PERMISSION_NAME";
    field public static final java.lang.String EXTRA_PERMISSION_GROUP_NAME = "android.intent.extra.PERMISSION_GROUP_NAME";
    field public static final java.lang.String EXTRA_PERMISSION_NAME = "android.intent.extra.PERMISSION_NAME";
    field public static final java.lang.String EXTRA_REASON = "android.intent.extra.REASON";
    field public static final java.lang.String EXTRA_REMOTE_CALLBACK = "android.intent.extra.REMOTE_CALLBACK";
    field public static final java.lang.String EXTRA_RESULT_NEEDED = "android.intent.extra.RESULT_NEEDED";
+3 −3
Original line number Diff line number Diff line
@@ -65,9 +65,9 @@ interface INotificationManager
    boolean areNotificationsEnabled(String pkg);
    int getPackageImportance(String pkg);

    void setAppOverlaysAllowed(String pkg, int uid, boolean allowed);
    boolean areAppOverlaysAllowed(String pkg);
    boolean areAppOverlaysAllowedForPackage(String pkg, int uid);
    void setBubblesAllowed(String pkg, int uid, boolean allowed);
    boolean areBubblesAllowed(String pkg);
    boolean areBubblesAllowedForPackage(String pkg, int uid);

    void createNotificationChannelGroups(String pkg, in ParceledListSlice channelGroupList);
    void createNotificationChannels(String pkg, in ParceledListSlice channelsList);
+198 −19
Original line number Diff line number Diff line
@@ -1276,7 +1276,7 @@ public class Notification implements Parcelable
    private String mShortcutId;
    private CharSequence mSettingsText;

    private PendingIntent mAppOverlayIntent;
    private BubbleMetadata mBubbleMetadata;

    /** @hide */
    @IntDef(prefix = { "GROUP_ALERT_" }, value = {
@@ -2278,7 +2278,7 @@ public class Notification implements Parcelable

        mGroupAlertBehavior = parcel.readInt();
        if (parcel.readInt() != 0) {
            mAppOverlayIntent = PendingIntent.CREATOR.createFromParcel(parcel);
            mBubbleMetadata = BubbleMetadata.CREATOR.createFromParcel(parcel);
        }

        mAllowSystemGeneratedContextualActions = parcel.readBoolean();
@@ -2396,7 +2396,7 @@ public class Notification implements Parcelable
        that.mBadgeIcon = this.mBadgeIcon;
        that.mSettingsText = this.mSettingsText;
        that.mGroupAlertBehavior = this.mGroupAlertBehavior;
        that.mAppOverlayIntent = this.mAppOverlayIntent;
        that.mBubbleMetadata = this.mBubbleMetadata;
        that.mAllowSystemGeneratedContextualActions = this.mAllowSystemGeneratedContextualActions;

        if (!heavy) {
@@ -2719,9 +2719,9 @@ public class Notification implements Parcelable

        parcel.writeInt(mGroupAlertBehavior);

        if (mAppOverlayIntent != null) {
        if (mBubbleMetadata != null) {
            parcel.writeInt(1);
            mAppOverlayIntent.writeToParcel(parcel, 0);
            mBubbleMetadata.writeToParcel(parcel, 0);
        } else {
            parcel.writeInt(0);
        }
@@ -3141,11 +3141,11 @@ public class Notification implements Parcelable
    }

    /**
     * Returns the intent that will be used to display app content in a floating window over the
     * existing foreground activity.
     * Returns the bubble metadata that will be used to display app content in a floating window
     * over the existing foreground activity.
     */
    public PendingIntent getAppOverlayIntent() {
        return mAppOverlayIntent;
    public BubbleMetadata getBubbleMetadata() {
        return mBubbleMetadata;
    }

    /**
@@ -3508,19 +3508,18 @@ public class Notification implements Parcelable
        }

        /**
         * Sets the intent that will be used to display app content in a floating window
         * over the existing foreground activity.
         * Sets the {@link BubbleMetadata} that will be used to display app content in a floating
         * window over the existing foreground activity.
         *
         * <p>This intent will be ignored unless this notification is posted to a channel that
         * allows {@link NotificationChannel#canOverlayApps() app overlays}.</p>
         * <p>This data will be ignored unless the notification is posted to a channel that
         * allows {@link NotificationChannel#canBubble() bubbles}.</p>
         *
         * <p>Notifications with a valid and allowed app overlay intent will be displayed as
         * floating windows outside of the notification shade on unlocked devices. When a user
         * interacts with one of these windows, this app overlay intent will be invoked and
         * displayed.</p>
         * <b>Notifications with a valid and allowed 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.</b>
         */
        public Builder setAppOverlayIntent(PendingIntent intent) {
            mN.mAppOverlayIntent = intent;
        public Builder setBubbleMetadata(BubbleMetadata data) {
            mN.mBubbleMetadata = data;
            return this;
        }

@@ -8422,6 +8421,186 @@ 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>
     *
     * <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>
     *
     * @see Notification.Builder#setBubbleMetadata(BubbleMetadata)
     */
    public static final class BubbleMetadata implements Parcelable {

        private PendingIntent mPendingIntent;
        private CharSequence mTitle;
        private Icon mIcon;
        private int mDesiredHeight;

        private BubbleMetadata(PendingIntent intent, CharSequence title, Icon icon, int height) {
            mPendingIntent = intent;
            mTitle = title;
            mIcon = icon;
            mDesiredHeight = height;
        }

        private BubbleMetadata(Parcel in) {
            mPendingIntent = PendingIntent.CREATOR.createFromParcel(in);
            mTitle = in.readCharSequence();
            mIcon = Icon.CREATOR.createFromParcel(in);
            mDesiredHeight = in.readInt();
        }

        /**
         * @return the pending intent used to populate the floating window for this bubble.
         */
        public PendingIntent getIntent() {
            return mPendingIntent;
        }

        /**
         * @return the title that will appear along with the app content defined by
         * {@link #getIntent()} for this bubble.
         */
        public CharSequence getTitle() {
            return mTitle;
        }

        /**
         * @return the icon that will be displayed for this bubble when it is collapsed.
         */
        public Icon getIcon() {
            return mIcon;
        }

        /**
         * @return the ideal height for the floating window that app content defined by
         * {@link #getIntent()} for this bubble.
         */
        public int getDesiredHeight() {
            return mDesiredHeight;
        }

        public static final Parcelable.Creator<BubbleMetadata> CREATOR =
                new Parcelable.Creator<BubbleMetadata>() {

                    @Override
                    public BubbleMetadata createFromParcel(Parcel source) {
                        return new BubbleMetadata(source);
                    }

                    @Override
                    public BubbleMetadata[] newArray(int size) {
                        return new BubbleMetadata[size];
                    }
                };

        @Override
        public int describeContents() {
            return 0;
        }

        @Override
        public void writeToParcel(Parcel out, int flags) {
            mPendingIntent.writeToParcel(out, 0);
            out.writeCharSequence(mTitle);
            mIcon.writeToParcel(out, 0);
            out.writeInt(mDesiredHeight);
        }

        /**
         * Builder to construct a {@link BubbleMetadata} object.
         */
        public static class Builder {

            private PendingIntent mPendingIntent;
            private CharSequence mTitle;
            private Icon mIcon;
            private int mDesiredHeight;

            /**
             * Constructs a new builder object.
             */
            public Builder() {
            }

            /**
             * 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.
             */
            public BubbleMetadata.Builder setIntent(PendingIntent intent) {
                if (intent == null) {
                    throw new IllegalArgumentException("Bubble requires non-null pending intent");
                }
                mPendingIntent = intent;
                return this;
            }

            /**
             * Sets the title that will appear along with the app content for this bubble.
             *
             * <p>A title is required and should expect to fit on a single line and make sense when
             * shown with the content defined by {@link #setIntent(PendingIntent)}.</p>
             */
            public BubbleMetadata.Builder setTitle(CharSequence title) {
                if (TextUtils.isEmpty(title)) {
                    throw new IllegalArgumentException("Bubbles require non-null or empty title");
                }
                mTitle = title;
                return this;
            }

            /**
             * Sets the icon that will represent the bubble when it is collapsed.
             *
             * <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>
             */
            public BubbleMetadata.Builder setIcon(Icon icon) {
                if (icon == null) {
                    throw new IllegalArgumentException("Bubbles require non-null icon");
                }
                mIcon = icon;
                return this;
            }

            /**
             * Sets the desired height for the app content defined by
             * {@link #setIntent(PendingIntent)}, 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.
             */
            public BubbleMetadata.Builder setDesiredHeight(int height) {
                mDesiredHeight = Math.max(height, 0);
                return this;
            }

            /**
             * Creates the {@link BubbleMetadata} defined by this builder.
             * <p>Will throw {@link IllegalStateException} if required fields have not been set
             * on this builder.</p>
             */
            public BubbleMetadata build() {
                if (mPendingIntent == null) {
                    throw new IllegalStateException("Must supply pending intent to bubble");
                }
                if (TextUtils.isEmpty(mTitle)) {
                    throw new IllegalStateException("Must supply a title for the bubble");
                }
                if (mIcon == null) {
                    throw new IllegalStateException("Must supply an icon for the bubble");
                }
                return new BubbleMetadata(mPendingIntent, mTitle, mIcon, mDesiredHeight);
            }
        }
    }


    // When adding a new Style subclass here, don't forget to update
    // Builder.getNotificationStyleClass.

+25 −25
Original line number Diff line number Diff line
@@ -85,7 +85,7 @@ public final class NotificationChannel implements Parcelable {
    private static final String ATT_FG_SERVICE_SHOWN = "fgservice";
    private static final String ATT_GROUP = "group";
    private static final String ATT_BLOCKABLE_SYSTEM = "blockable_system";
    private static final String ATT_ALLOW_APP_OVERLAY = "app_overlay";
    private static final String ATT_ALLOW_BUBBLE = "allow_bubble";
    private static final String DELIMITER = ",";

    /**
@@ -121,7 +121,7 @@ public final class NotificationChannel implements Parcelable {
    /**
     * @hide
     */
    public static final int USER_LOCKED_ALLOW_APP_OVERLAY = 0x00000100;
    public static final int USER_LOCKED_ALLOW_BUBBLE = 0x00000100;

    /**
     * @hide
@@ -134,7 +134,7 @@ public final class NotificationChannel implements Parcelable {
            USER_LOCKED_VIBRATION,
            USER_LOCKED_SOUND,
            USER_LOCKED_SHOW_BADGE,
            USER_LOCKED_ALLOW_APP_OVERLAY
            USER_LOCKED_ALLOW_BUBBLE
    };

    private static final int DEFAULT_LIGHT_COLOR = 0;
@@ -144,7 +144,7 @@ public final class NotificationChannel implements Parcelable {
            NotificationManager.IMPORTANCE_UNSPECIFIED;
    private static final boolean DEFAULT_DELETED = false;
    private static final boolean DEFAULT_SHOW_BADGE = true;
    private static final boolean DEFAULT_ALLOW_APP_OVERLAY = true;
    private static final boolean DEFAULT_ALLOW_BUBBLE = true;

    @UnsupportedAppUsage
    private final String mId;
@@ -168,7 +168,7 @@ public final class NotificationChannel implements Parcelable {
    private AudioAttributes mAudioAttributes = Notification.AUDIO_ATTRIBUTES_DEFAULT;
    // If this is a blockable system notification channel.
    private boolean mBlockableSystem = false;
    private boolean mAllowAppOverlay = DEFAULT_ALLOW_APP_OVERLAY;
    private boolean mAllowBubbles = DEFAULT_ALLOW_BUBBLE;
    private boolean mImportanceLockedByOEM;

    /**
@@ -231,7 +231,7 @@ public final class NotificationChannel implements Parcelable {
        mAudioAttributes = in.readInt() > 0 ? AudioAttributes.CREATOR.createFromParcel(in) : null;
        mLightColor = in.readInt();
        mBlockableSystem = in.readBoolean();
        mAllowAppOverlay = in.readBoolean();
        mAllowBubbles = in.readBoolean();
        mImportanceLockedByOEM = in.readBoolean();
    }

@@ -285,7 +285,7 @@ public final class NotificationChannel implements Parcelable {
        }
        dest.writeInt(mLightColor);
        dest.writeBoolean(mBlockableSystem);
        dest.writeBoolean(mAllowAppOverlay);
        dest.writeBoolean(mAllowBubbles);
        dest.writeBoolean(mImportanceLockedByOEM);
    }

@@ -480,7 +480,7 @@ public final class NotificationChannel implements Parcelable {

    /**
     * Sets whether notifications posted to this channel can appear outside of the notification
     * shade, floating over other apps' content.
     * shade, floating over other apps' content as a bubble.
     *
     * <p>This value will be ignored for channels that aren't allowed to pop on screen (that is,
     * channels whose {@link #getImportance() importance} is <
@@ -488,10 +488,10 @@ public final class NotificationChannel implements Parcelable {
     *
     * <p>Only modifiable before the channel is submitted to
     *      * {@link NotificationManager#createNotificationChannel(NotificationChannel)}.</p>
     * @see Notification#getAppOverlayIntent()
     * @see Notification#getBubbleMetadata()
     */
    public void setAllowAppOverlay(boolean allowAppOverlay) {
        mAllowAppOverlay = allowAppOverlay;
    public void setAllowBubbles(boolean allowBubbles) {
        mAllowBubbles = allowBubbles;
    }

    /**
@@ -610,16 +610,16 @@ public final class NotificationChannel implements Parcelable {
     * Returns whether notifications posted to this channel can display outside of the notification
     * shade, in a floating window on top of other apps.
     */
    public boolean canOverlayApps() {
        return isAppOverlayAllowed() && getImportance() >= IMPORTANCE_HIGH;
    public boolean canBubble() {
        return isBubbleAllowed() && getImportance() >= IMPORTANCE_HIGH;
    }

    /**
     * Like {@link #canOverlayApps()}, but only checks the permission, not the importance.
     * Like {@link #canBubble()}, but only checks the permission, not the importance.
     * @hide
     */
    public boolean isAppOverlayAllowed() {
        return mAllowAppOverlay;
    public boolean isBubbleAllowed() {
        return mAllowBubbles;
    }

    /**
@@ -719,7 +719,7 @@ public final class NotificationChannel implements Parcelable {
        lockFields(safeInt(parser, ATT_USER_LOCKED, 0));
        setFgServiceShown(safeBool(parser, ATT_FG_SERVICE_SHOWN, false));
        setBlockableSystem(safeBool(parser, ATT_BLOCKABLE_SYSTEM, false));
        setAllowAppOverlay(safeBool(parser, ATT_ALLOW_APP_OVERLAY, DEFAULT_ALLOW_APP_OVERLAY));
        setAllowBubbles(safeBool(parser, ATT_ALLOW_BUBBLE, DEFAULT_ALLOW_BUBBLE));
    }

    @Nullable
@@ -838,8 +838,8 @@ public final class NotificationChannel implements Parcelable {
        if (isBlockableSystem()) {
            out.attribute(null, ATT_BLOCKABLE_SYSTEM, Boolean.toString(isBlockableSystem()));
        }
        if (canOverlayApps() != DEFAULT_ALLOW_APP_OVERLAY) {
            out.attribute(null, ATT_ALLOW_APP_OVERLAY, Boolean.toString(canOverlayApps()));
        if (canBubble() != DEFAULT_ALLOW_BUBBLE) {
            out.attribute(null, ATT_ALLOW_BUBBLE, Boolean.toString(canBubble()));
        }

        out.endTag(null, TAG_CHANNEL);
@@ -883,7 +883,7 @@ public final class NotificationChannel implements Parcelable {
        record.put(ATT_DELETED, Boolean.toString(isDeleted()));
        record.put(ATT_GROUP, getGroup());
        record.put(ATT_BLOCKABLE_SYSTEM, isBlockableSystem());
        record.put(ATT_ALLOW_APP_OVERLAY, canOverlayApps());
        record.put(ATT_ALLOW_BUBBLE, canBubble());
        return record;
    }

@@ -983,7 +983,7 @@ public final class NotificationChannel implements Parcelable {
                && mShowBadge == that.mShowBadge
                && isDeleted() == that.isDeleted()
                && isBlockableSystem() == that.isBlockableSystem()
                && mAllowAppOverlay == that.mAllowAppOverlay
                && mAllowBubbles == that.mAllowBubbles
                && Objects.equals(getId(), that.getId())
                && Objects.equals(getName(), that.getName())
                && Objects.equals(mDesc, that.mDesc)
@@ -1000,7 +1000,7 @@ public final class NotificationChannel implements Parcelable {
                getLockscreenVisibility(), getSound(), mLights, getLightColor(),
                getUserLockedFields(),
                isFgServiceShown(), mVibrationEnabled, mShowBadge, isDeleted(), getGroup(),
                getAudioAttributes(), isBlockableSystem(), mAllowAppOverlay,
                getAudioAttributes(), isBlockableSystem(), mAllowBubbles,
                mImportanceLockedByOEM);
        result = 31 * result + Arrays.hashCode(mVibration);
        return result;
@@ -1028,7 +1028,7 @@ public final class NotificationChannel implements Parcelable {
                + ", mGroup='" + mGroup + '\''
                + ", mAudioAttributes=" + mAudioAttributes
                + ", mBlockableSystem=" + mBlockableSystem
                + ", mAllowAppOverlay=" + mAllowAppOverlay
                + ", mAllowBubbles=" + mAllowBubbles
                + ", mImportanceLockedByOEM=" + mImportanceLockedByOEM
                + '}';
        pw.println(prefix + output);
@@ -1055,7 +1055,7 @@ public final class NotificationChannel implements Parcelable {
                + ", mGroup='" + mGroup + '\''
                + ", mAudioAttributes=" + mAudioAttributes
                + ", mBlockableSystem=" + mBlockableSystem
                + ", mAllowAppOverlay=" + mAllowAppOverlay
                + ", mAllowBubbles=" + mAllowBubbles
                + ", mImportanceLockedByOEM=" + mImportanceLockedByOEM
                + '}';
    }
@@ -1090,7 +1090,7 @@ public final class NotificationChannel implements Parcelable {
            mAudioAttributes.writeToProto(proto, NotificationChannelProto.AUDIO_ATTRIBUTES);
        }
        proto.write(NotificationChannelProto.IS_BLOCKABLE_SYSTEM, mBlockableSystem);
        proto.write(NotificationChannelProto.ALLOW_APP_OVERLAY, mAllowAppOverlay);
        proto.write(NotificationChannelProto.ALLOW_APP_OVERLAY, mAllowBubbles);

        proto.end(token);
    }
Loading