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

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

Make bubble settings a pref with an int rather than a bool

This will allow us to do all/selected/none preferences for
bubbles in settings.

- Feature is on by default
- App is none by default
- Channel is off by default

Test: atest NotificationManagerServiceTest BubbleExtractorTest
Bug: 138116133
Change-Id: Ifad1c22525123354f76959c2d44392a25d56347d
parent 4cbb2235
Loading
Loading
Loading
Loading
+2 −3
Original line number Diff line number Diff line
@@ -78,10 +78,9 @@ interface INotificationManager
    boolean shouldHideSilentStatusIcons(String callingPkg);
    void setHideSilentStatusIcons(boolean hide);

    void setBubblesAllowed(String pkg, int uid, boolean allowed);
    void setBubblesAllowed(String pkg, int uid, int bubblePreference);
    boolean areBubblesAllowed(String pkg);
    boolean areBubblesAllowedForPackage(String pkg, int uid);
    boolean hasUserApprovedBubblesForPackage(String pkg, int uid);
    int getBubblePreferenceForPackage(String pkg, int uid);

    void createNotificationChannelGroups(String pkg, in ParceledListSlice channelGroupList);
    void createNotificationChannels(String pkg, in ParceledListSlice channelsList);
+8 −13
Original line number Diff line number Diff line
@@ -102,7 +102,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_BUBBLE = "can_bubble";
    private static final String ATT_ALLOW_BUBBLE = "allow_bubble";
    private static final String ATT_ORIG_IMP = "orig_imp";
    private static final String ATT_PARENT_CHANNEL = "parent";
    private static final String ATT_CONVERSATION_ID = "conv_id";
@@ -168,7 +168,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_BUBBLE = true;
    private static final boolean DEFAULT_ALLOW_BUBBLE = false;

    @UnsupportedAppUsage
    private String mId;
@@ -545,15 +545,8 @@ 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 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 <
     * {@link NotificationManager#IMPORTANCE_HIGH}.</p>
     *
     * <p>Only modifiable before the channel is submitted to
     *      * {@link NotificationManager#createNotificationChannel(NotificationChannel)}.</p>
     * As of Android 11 this value is no longer respected.
     * @see #canBubble()
     * @see Notification#getBubbleMetadata()
     */
    public void setAllowBubbles(boolean allowBubbles) {
@@ -702,8 +695,10 @@ 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.
     * Returns whether notifications posted to this channel are allowed to display outside of the
     * notification shade, in a floating window on top of other apps.
     *
     * @see Notification#getBubbleMetadata()
     */
    public boolean canBubble() {
        return mAllowBubbles;
+14 −1
Original line number Diff line number Diff line
@@ -452,6 +452,19 @@ public class NotificationManager {
     */
    public static final int IMPORTANCE_MAX = 5;

    /**
     * @hide
     */
    public static final int BUBBLE_PREFERENCE_NONE = 0;
    /**
     * @hide
     */
    public static final int BUBBLE_PREFERENCE_ALL = 1;
    /**
     * @hide
     */
    public static final int BUBBLE_PREFERENCE_SELECTED = 2;

    @UnsupportedAppUsage
    private static INotificationManager sService;

@@ -1213,7 +1226,7 @@ public class NotificationManager {


    /**
     * Sets whether notifications posted by this app can appear outside of the
     * Gets whether all notifications posted by this app can appear outside of the
     * notification shade, floating over other apps' content.
     *
     * <p>This value will be ignored for notifications that are posted to channels that do not
+9 −0
Original line number Diff line number Diff line
@@ -17,6 +17,8 @@
package com.android.systemui.statusbar.notification.row;

import static android.app.Notification.EXTRA_IS_GROUP_CONVERSATION;
import static android.app.NotificationManager.BUBBLE_PREFERENCE_NONE;
import static android.app.NotificationManager.BUBBLE_PREFERENCE_SELECTED;
import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
import static android.app.NotificationManager.IMPORTANCE_LOW;
import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
@@ -598,6 +600,13 @@ public class NotificationConversationInfo extends LinearLayout implements
                                !mChannelToUpdate.isImportantConversation());
                        if (mChannelToUpdate.isImportantConversation()) {
                            mChannelToUpdate.setAllowBubbles(true);
                            int currentPref =
                                    mINotificationManager.getBubblePreferenceForPackage(
                                            mAppPkg, mAppUid);
                            if (currentPref == BUBBLE_PREFERENCE_NONE) {
                                mINotificationManager.setBubblesAllowed(mAppPkg, mAppUid,
                                        BUBBLE_PREFERENCE_SELECTED);
                            }
                        }
                        mChannelToUpdate.setImportance(Math.max(
                                mChannelToUpdate.getOriginalImportance(), IMPORTANCE_DEFAULT));
+103 −158
Original line number Diff line number Diff line
@@ -16,12 +16,17 @@
package com.android.server.notification;

import static android.app.Notification.FLAG_BUBBLE;
import static android.app.NotificationChannel.USER_LOCKED_ALLOW_BUBBLE;
import static android.app.NotificationManager.BUBBLE_PREFERENCE_ALL;
import static android.app.NotificationManager.BUBBLE_PREFERENCE_NONE;
import static android.app.NotificationManager.BUBBLE_PREFERENCE_SELECTED;

import static com.android.internal.util.FrameworkStatsLog.BUBBLE_DEVELOPER_ERROR_REPORTED__ERROR__ACTIVITY_INFO_MISSING;
import static com.android.internal.util.FrameworkStatsLog.BUBBLE_DEVELOPER_ERROR_REPORTED__ERROR__ACTIVITY_INFO_NOT_RESIZABLE;

import android.app.ActivityManager;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
@@ -32,13 +37,13 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.FrameworkStatsLog;

/**
 * Determines whether a bubble can be shown for this notification
 * Determines whether a bubble can be shown for this notification.
 */
public class BubbleExtractor implements NotificationSignalExtractor {
    private static final String TAG = "BubbleExtractor";
    private static final boolean DBG = false;

    private BubbleChecker mBubbleChecker;
    private ShortcutHelper mShortcutHelper;
    private RankingConfig mConfig;
    private ActivityManager mActivityManager;
    private Context mContext;
@@ -60,24 +65,34 @@ public class BubbleExtractor implements NotificationSignalExtractor {
            return null;
        }

        if (mBubbleChecker == null) {
            if (DBG) Slog.d(TAG, "missing bubble checker");
        if (mShortcutHelper == null) {
            if (DBG) Slog.d(TAG, "missing shortcut helper");
            return null;
        }

        boolean appCanShowBubble =
                mConfig.areBubblesAllowed(record.getSbn().getPackageName(), record.getSbn().getUid());
        if (!mConfig.bubblesEnabled() || !appCanShowBubble) {
        int bubblePreference =
                mConfig.getBubblePreference(
                        record.getSbn().getPackageName(), record.getSbn().getUid());
        NotificationChannel recordChannel = record.getChannel();

        if (!mConfig.bubblesEnabled() || bubblePreference == BUBBLE_PREFERENCE_NONE) {
            record.setAllowBubble(false);
        } else {
            if (record.getChannel() != null) {
                record.setAllowBubble(record.getChannel().canBubble() && appCanShowBubble);
            } else {
                record.setAllowBubble(appCanShowBubble);
            }
        }
        final boolean applyFlag = mBubbleChecker.isNotificationAppropriateToBubble(record)
                && !record.isFlagBubbleRemoved();
        } else if (recordChannel == null) {
            // the app is allowed but there's no channel to check
            record.setAllowBubble(true);
        } else if (bubblePreference == BUBBLE_PREFERENCE_ALL) {
            // by default the channel is not allowed, only don't bubble if the user specified
            boolean userLockedNoBubbles = !recordChannel.canBubble()
                    && (recordChannel.getUserLockedFields() & USER_LOCKED_ALLOW_BUBBLE) != 0;
            record.setAllowBubble(!userLockedNoBubbles);
        } else if (bubblePreference == BUBBLE_PREFERENCE_SELECTED) {
            record.setAllowBubble(recordChannel.canBubble());
        }

        final boolean fulfillsPolicy = record.isConversation()
                && !mActivityManager.isLowRamDevice()
                && record.canBubble();
        final boolean applyFlag = fulfillsPolicy && canPresentAsBubble(record);
        if (applyFlag) {
            record.getNotification().flags |= FLAG_BUBBLE;
        } else {
@@ -95,92 +110,24 @@ public class BubbleExtractor implements NotificationSignalExtractor {
    public void setZenHelper(ZenModeHelper helper) {
    }

    /**
     * Expected to be called after {@link #setConfig(RankingConfig)} has occurred.
     */
    void setShortcutHelper(ShortcutHelper helper) {
        if (mConfig == null) {
            if (DBG) Slog.d(TAG, "setting shortcut helper prior to setConfig");
            return;
        }
        mBubbleChecker = new BubbleChecker(mContext, helper, mConfig, mActivityManager);
    }

    @VisibleForTesting
    void setBubbleChecker(BubbleChecker checker) {
        mBubbleChecker = checker;
    }

    /**
     * Encapsulates special checks to see if a notification can be flagged as a bubble. This
     * makes testing a bit easier.
     */
    public static class BubbleChecker {

        private ActivityManager mActivityManager;
        private RankingConfig mRankingConfig;
        private Context mContext;
        private ShortcutHelper mShortcutHelper;

        BubbleChecker(Context context, ShortcutHelper helper, RankingConfig config,
                ActivityManager activityManager) {
            mContext = context;
            mActivityManager = activityManager;
    public void setShortcutHelper(ShortcutHelper helper) {
        mShortcutHelper = helper;
            mRankingConfig = config;
    }

        /**
         * @return whether the provided notification record is allowed to be represented as a
         * bubble, accounting for user choice & policy.
         */
        public boolean isNotificationAppropriateToBubble(NotificationRecord r) {
            final String pkg = r.getSbn().getPackageName();
            final int userId = r.getSbn().getUser().getIdentifier();
            Notification notification = r.getNotification();
            if (!canBubble(r, pkg, userId)) {
                // no log: canBubble has its own
                return false;
            }

            if (mActivityManager.isLowRamDevice()) {
                logBubbleError(r.getKey(), "low ram device");
                return false;
            }

            boolean isMessageStyle = Notification.MessagingStyle.class.equals(
                    notification.getNotificationStyle());
            if (!isMessageStyle) {
                logBubbleError(r.getKey(), "must be Notification.MessageStyle");
                return false;
            }
            return true;
    @VisibleForTesting
    public void setActivityManager(ActivityManager manager) {
        mActivityManager = manager;
    }

    /**
         * @return whether the user has enabled the provided notification to bubble, and if the
         * developer has provided valid information for the notification to bubble.
     * @return whether there is valid information for the notification to bubble.
     */
    @VisibleForTesting
        boolean canBubble(NotificationRecord r, String pkg, int userId) {
    boolean canPresentAsBubble(NotificationRecord r) {
        Notification notification = r.getNotification();
        Notification.BubbleMetadata metadata = notification.getBubbleMetadata();
        String pkg = r.getSbn().getPackageName();
        if (metadata == null) {
                // no log: no need to inform dev if they didn't attach bubble metadata
                return false;
            }
            if (!mRankingConfig.bubblesEnabled()) {
                logBubbleError(r.getKey(), "bubbles disabled for user: " + userId);
                return false;
            }
            if (!mRankingConfig.areBubblesAllowed(pkg, userId)) {
                logBubbleError(r.getKey(),
                        "bubbles for package: " + pkg + " disabled for user: " + userId);
                return false;
            }
            if (!r.getChannel().canBubble()) {
                logBubbleError(r.getKey(),
                        "bubbles for channel " + r.getChannel().getId() + " disabled");
            return false;
        }

@@ -203,15 +150,15 @@ public class BubbleExtractor implements NotificationSignalExtractor {
            return false;
        }
        if (shortcutValid) {
            // TODO: check the shortcut intent / ensure it can show in activity view
            return true;
        }
            // no log: canLaunch method has the failure log
        return canLaunchInActivityView(mContext, metadata.getIntent(), pkg);
    }

    /**
     * Whether an intent is properly configured to display in an {@link
         * android.app.ActivityView}.
     * android.app.ActivityView} for bubbling.
     *
     * @param context       the context to use.
     * @param pendingIntent the pending intent of the bubble.
@@ -227,7 +174,6 @@ public class BubbleExtractor implements NotificationSignalExtractor {
        }

        Intent intent = pendingIntent.getIntent();

        ActivityInfo info = intent != null
                ? intent.resolveActivityInfo(context.getPackageManager(), 0)
                : null;
@@ -256,4 +202,3 @@ public class BubbleExtractor implements NotificationSignalExtractor {
        }
    }
}
}
Loading