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

Commit b6bd93d9 authored by Julia Reynolds's avatar Julia Reynolds
Browse files

Add APIs for notification app overlays

- Can be enabled/disabled at channel and channel group levels
- An activity to launch can be added to notification

Test: atest, cts
Bug: 111236845
Change-Id: I9a4832211676cca4649d1f28e6e3e3157954d268
parent 2a7854ad
Loading
Loading
Loading
Loading
+6 −0
Original line number Diff line number Diff line
@@ -5217,6 +5217,7 @@ package android.app {
    ctor public Notification(android.os.Parcel);
    method public android.app.Notification clone();
    method public int describeContents();
    method public android.app.PendingIntent getAppOverlayIntent();
    method public int getBadgeIconType();
    method public java.lang.String getChannelId();
    method public java.lang.String getGroup();
@@ -5446,6 +5447,7 @@ package android.app {
    method public android.app.Notification.Style getStyle();
    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 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 setCategory(java.lang.String);
@@ -5663,6 +5665,7 @@ package android.app {
  public final class NotificationChannel implements android.os.Parcelable {
    ctor public NotificationChannel(java.lang.String, java.lang.CharSequence, int);
    method public boolean canBypassDnd();
    method public boolean canOverlayApps();
    method public boolean canShowBadge();
    method public int describeContents();
    method public void enableLights(boolean);
@@ -5678,6 +5681,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 setBypassDnd(boolean);
    method public void setDescription(java.lang.String);
    method public void setGroup(java.lang.String);
@@ -5697,6 +5701,7 @@ package android.app {
  public final class NotificationChannelGroup implements android.os.Parcelable {
    ctor public NotificationChannelGroup(java.lang.String, java.lang.CharSequence);
    method public boolean canOverlayApps();
    method public android.app.NotificationChannelGroup clone();
    method public int describeContents();
    method public java.util.List<android.app.NotificationChannel> getChannels();
@@ -5704,6 +5709,7 @@ package android.app {
    method public java.lang.String getId();
    method public java.lang.CharSequence getName();
    method public boolean isBlocked();
    method public void setAllowAppOverlay(boolean);
    method public void setDescription(java.lang.String);
    method public void writeToParcel(android.os.Parcel, int);
    field public static final android.os.Parcelable.Creator<android.app.NotificationChannelGroup> CREATOR;
+1 −1
Original line number Diff line number Diff line
@@ -987,8 +987,8 @@ package android.content {
    field public static final java.lang.String ACTION_PRE_BOOT_COMPLETED = "android.intent.action.PRE_BOOT_COMPLETED";
    field public static final java.lang.String ACTION_QUERY_PACKAGE_RESTART = "android.intent.action.QUERY_PACKAGE_RESTART";
    field public static final java.lang.String ACTION_RESOLVE_INSTANT_APP_PACKAGE = "android.intent.action.RESOLVE_INSTANT_APP_PACKAGE";
    field public static final java.lang.String ACTION_REVIEW_PERMISSION_USAGE = "android.intent.action.REVIEW_PERMISSION_USAGE";
    field public static final java.lang.String ACTION_REVIEW_PERMISSIONS = "android.intent.action.REVIEW_PERMISSIONS";
    field public static final java.lang.String ACTION_REVIEW_PERMISSION_USAGE = "android.intent.action.REVIEW_PERMISSION_USAGE";
    field public static final java.lang.String ACTION_SHOW_SUSPENDED_APP_DETAILS = "android.intent.action.SHOW_SUSPENDED_APP_DETAILS";
    field public static final deprecated java.lang.String ACTION_SIM_STATE_CHANGED = "android.intent.action.SIM_STATE_CHANGED";
    field public static final java.lang.String ACTION_SPLIT_CONFIGURATION_CHANGED = "android.intent.action.SPLIT_CONFIGURATION_CHANGED";
+3 −0
Original line number Diff line number Diff line
@@ -140,7 +140,10 @@ package android.app {
  }

  public final class NotificationChannelGroup implements android.os.Parcelable {
    method public int getUserLockedFields();
    method public void lockFields(int);
    method public void setBlocked(boolean);
    field public static final int USER_LOCKED_ALLOW_APP_OVERLAY = 2; // 0x2
  }

  public class NotificationManager {
+38 −0
Original line number Diff line number Diff line
@@ -1275,6 +1275,8 @@ public class Notification implements Parcelable
    private String mShortcutId;
    private CharSequence mSettingsText;

    private PendingIntent mAppOverlayIntent;

    /** @hide */
    @IntDef(prefix = { "GROUP_ALERT_" }, value = {
            GROUP_ALERT_ALL, GROUP_ALERT_CHILDREN, GROUP_ALERT_SUMMARY
@@ -2225,6 +2227,9 @@ public class Notification implements Parcelable
        }

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

    @Override
@@ -2339,6 +2344,7 @@ public class Notification implements Parcelable
        that.mBadgeIcon = this.mBadgeIcon;
        that.mSettingsText = this.mSettingsText;
        that.mGroupAlertBehavior = this.mGroupAlertBehavior;
        that.mAppOverlayIntent = this.mAppOverlayIntent;

        if (!heavy) {
            that.lightenPayload(); // will clean out extras
@@ -2660,6 +2666,13 @@ public class Notification implements Parcelable

        parcel.writeInt(mGroupAlertBehavior);

        if (mAppOverlayIntent != null) {
            parcel.writeInt(1);
            mAppOverlayIntent.writeToParcel(parcel, 0);
        } else {
            parcel.writeInt(0);
        }

        // mUsesStandardHeader is not written because it should be recomputed in listeners
    }

@@ -3072,6 +3085,14 @@ public class Notification implements Parcelable
        return mGroupAlertBehavior;
    }

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

    /**
     * The small icon representing this notification in the status bar and content view.
     *
@@ -3406,6 +3427,23 @@ public class Notification implements Parcelable
            return this;
        }

        /**
         * Sets the intent 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>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>
         */
        public Builder setAppOverlayIntent(PendingIntent intent) {
            mN.mAppOverlayIntent = intent;
            return this;
        }

        /** @removed */
        @Deprecated
        public Builder setChannel(String channelId) {
+79 −46
Original line number Diff line number Diff line
@@ -15,6 +15,8 @@
 */
package android.app;

import static android.app.NotificationManager.IMPORTANCE_HIGH;

import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.UnsupportedAppUsage;
@@ -41,6 +43,7 @@ import org.xmlpull.v1.XmlSerializer;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.Objects;

/**
 * A representation of settings that apply to a collection of similarly themed notifications.
@@ -81,6 +84,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 DELIMITER = ",";

    /**
@@ -113,6 +117,11 @@ public final class NotificationChannel implements Parcelable {
     */
    public static final int USER_LOCKED_SHOW_BADGE = 0x00000080;

    /**
     * @hide
     */
    public static final int USER_LOCKED_ALLOW_APP_OVERLAY = 0x00000100;

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

    private static final int DEFAULT_LIGHT_COLOR = 0;
@@ -133,6 +143,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;

    @UnsupportedAppUsage
    private final String mId;
@@ -156,6 +167,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;

    /**
     * Creates a notification channel.
@@ -217,6 +229,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();
    }

    @Override
@@ -269,6 +282,7 @@ public final class NotificationChannel implements Parcelable {
        }
        dest.writeInt(mLightColor);
        dest.writeBoolean(mBlockableSystem);
        dest.writeBoolean(mAllowAppOverlay);
    }

    /**
@@ -460,6 +474,22 @@ public final class NotificationChannel implements Parcelable {
        this.mLockscreenVisibility = lockscreenVisibility;
    }

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

    /**
     * Returns the id of this channel.
     */
@@ -572,6 +602,22 @@ public final class NotificationChannel implements Parcelable {
        return mGroup;
    }

    /**
     * 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;
    }

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

    /**
     * @hide
     */
@@ -605,6 +651,7 @@ public final class NotificationChannel implements Parcelable {
    /**
     * Returns whether the user has chosen the importance of this channel, either to affirm the
     * initial selection from the app, or changed it to be higher or lower.
     * @see #getImportance()
     */
    public boolean hasUserSetImportance() {
        return (mUserLockedFields & USER_LOCKED_IMPORTANCE) != 0;
@@ -652,6 +699,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));
    }

    @Nullable
@@ -770,6 +818,9 @@ 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()));
        }

        out.endTag(null, TAG_CHANNEL);
    }
@@ -812,6 +863,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());
        return record;
    }

@@ -899,58 +951,36 @@ public final class NotificationChannel implements Parcelable {
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        NotificationChannel that = (NotificationChannel) o;

        if (getImportance() != that.getImportance()) return false;
        if (mBypassDnd != that.mBypassDnd) return false;
        if (getLockscreenVisibility() != that.getLockscreenVisibility()) return false;
        if (mLights != that.mLights) return false;
        if (getLightColor() != that.getLightColor()) return false;
        if (getUserLockedFields() != that.getUserLockedFields()) return false;
        if (mVibrationEnabled != that.mVibrationEnabled) return false;
        if (mShowBadge != that.mShowBadge) return false;
        if (isDeleted() != that.isDeleted()) return false;
        if (isBlockableSystem() != that.isBlockableSystem()) return false;
        if (getId() != null ? !getId().equals(that.getId()) : that.getId() != null) return false;
        if (getName() != null ? !getName().equals(that.getName()) : that.getName() != null) {
            return false;
        }
        if (getDescription() != null ? !getDescription().equals(that.getDescription())
                : that.getDescription() != null) {
            return false;
        }
        if (getSound() != null ? !getSound().equals(that.getSound()) : that.getSound() != null) {
            return false;
        }
        if (!Arrays.equals(mVibration, that.mVibration)) return false;
        if (getGroup() != null ? !getGroup().equals(that.getGroup()) : that.getGroup() != null) {
            return false;
        }
        return getAudioAttributes() != null ? getAudioAttributes().equals(that.getAudioAttributes())
                : that.getAudioAttributes() == null;

        return getImportance() == that.getImportance() &&
                mBypassDnd == that.mBypassDnd &&
                getLockscreenVisibility() == that.getLockscreenVisibility() &&
                mLights == that.mLights &&
                getLightColor() == that.getLightColor() &&
                getUserLockedFields() == that.getUserLockedFields() &&
                isFgServiceShown() == that.isFgServiceShown() &&
                mVibrationEnabled == that.mVibrationEnabled &&
                mShowBadge == that.mShowBadge &&
                isDeleted() == that.isDeleted() &&
                isBlockableSystem() == that.isBlockableSystem() &&
                mAllowAppOverlay == that.mAllowAppOverlay &&
                Objects.equals(getId(), that.getId()) &&
                Objects.equals(getName(), that.getName()) &&
                Objects.equals(mDesc, that.mDesc) &&
                Objects.equals(getSound(), that.getSound()) &&
                Arrays.equals(mVibration, that.mVibration) &&
                Objects.equals(getGroup(), that.getGroup()) &&
                Objects.equals(getAudioAttributes(), that.getAudioAttributes());
    }

    @Override
    public int hashCode() {
        int result = getId() != null ? getId().hashCode() : 0;
        result = 31 * result + (getName() != null ? getName().hashCode() : 0);
        result = 31 * result + (getDescription() != null ? getDescription().hashCode() : 0);
        result = 31 * result + getImportance();
        result = 31 * result + (mBypassDnd ? 1 : 0);
        result = 31 * result + getLockscreenVisibility();
        result = 31 * result + (getSound() != null ? getSound().hashCode() : 0);
        result = 31 * result + (mLights ? 1 : 0);
        result = 31 * result + getLightColor();
        int result = Objects.hash(getId(), getName(), mDesc, getImportance(), mBypassDnd,
                getLockscreenVisibility(), getSound(), mLights, getLightColor(),
                getUserLockedFields(),
                isFgServiceShown(), mVibrationEnabled, mShowBadge, isDeleted(), getGroup(),
                getAudioAttributes(), isBlockableSystem(), mAllowAppOverlay);
        result = 31 * result + Arrays.hashCode(mVibration);
        result = 31 * result + getUserLockedFields();
        result = 31 * result + (mVibrationEnabled ? 1 : 0);
        result = 31 * result + (mShowBadge ? 1 : 0);
        result = 31 * result + (isDeleted() ? 1 : 0);
        result = 31 * result + (getGroup() != null ? getGroup().hashCode() : 0);
        result = 31 * result + (getAudioAttributes() != null ? getAudioAttributes().hashCode() : 0);
        result = 31 * result + (isBlockableSystem() ? 1 : 0);
        return result;
    }

@@ -976,6 +1006,7 @@ public final class NotificationChannel implements Parcelable {
                + ", mGroup='" + mGroup + '\''
                + ", mAudioAttributes=" + mAudioAttributes
                + ", mBlockableSystem=" + mBlockableSystem
                + ", mAllowAppOverlay=" + mAllowAppOverlay
                + '}';
        pw.println(prefix + output);
    }
@@ -1001,6 +1032,7 @@ public final class NotificationChannel implements Parcelable {
                + ", mGroup='" + mGroup + '\''
                + ", mAudioAttributes=" + mAudioAttributes
                + ", mBlockableSystem=" + mBlockableSystem
                + ", mAllowAppOverlay=" + mAllowAppOverlay
                + '}';
    }

@@ -1034,6 +1066,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.end(token);
    }
Loading