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

Commit b77f0bbc authored by Mady Mellor's avatar Mady Mellor Committed by Android (Google) Code Review
Browse files

Merge "Add flag to allowing the system to make BubbleMetadata for a notification"

parents 4489dcf2 7f234901
Loading
Loading
Loading
Loading
+19 −7
Original line number Diff line number Diff line
@@ -64,7 +64,8 @@ class Bubble {

    private long mLastUpdated;
    private long mLastAccessed;
    private boolean mIsRemoved;

    private boolean mIsUserCreated;

    /**
     * Whether this notification should be shown in the shade when it is also displayed as a bubble.
@@ -74,9 +75,7 @@ class Bubble {
     */
    private boolean mShowInShadeWhenBubble = true;

    /**
     * Whether the bubble should show a dot for the notification indicating updated content.
     */
    /** Whether the bubble should show a dot for the notification indicating updated content. */
    private boolean mShowBubbleUpdateDot = true;

    /** Whether flyout text should be suppressed, regardless of any other flags or state. */
@@ -294,6 +293,20 @@ class Bubble {
        return (flags & Notification.FLAG_FOREGROUND_SERVICE) != 0;
    }

    /**
     * Whether this bubble was explicitly created by the user via a SysUI affordance.
     */
    boolean isUserCreated() {
        return mIsUserCreated;
    }

    /**
     * Set whether this bubble was explicitly created by the user via a SysUI affordance.
     */
    void setUserCreated(boolean isUserCreated) {
        mIsUserCreated = isUserCreated;
    }

    float getDesiredHeight(Context context) {
        Notification.BubbleMetadata data = mEntry.getBubbleMetadata();
        boolean useRes = data.getDesiredHeightResId() != 0;
@@ -319,9 +332,8 @@ class Bubble {

    @Nullable
    PendingIntent getBubbleIntent(Context context) {
        Notification notif = mEntry.getSbn().getNotification();
        Notification.BubbleMetadata data = notif.getBubbleMetadata();
        if (BubbleController.canLaunchInActivityView(context, mEntry) && data != null) {
        Notification.BubbleMetadata data = mEntry.getBubbleMetadata();
        if (data != null) {
            return data.getIntent();
        }
        return null;
+75 −19
Original line number Diff line number Diff line
@@ -67,7 +67,6 @@ import androidx.annotation.Nullable;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.statusbar.IStatusBarService;
import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -82,6 +81,7 @@ import com.android.systemui.statusbar.notification.NotificationInterruptionState
import com.android.systemui.statusbar.notification.collection.NotificationData;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.phone.StatusBarWindowController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.ZenModeController;
@@ -96,6 +96,8 @@ import java.util.List;
import javax.inject.Inject;
import javax.inject.Singleton;

import dagger.Lazy;

/**
 * Bubbles are a special type of content that can "float" on top of other apps or System UI.
 * Bubbles can be expanded to show more content.
@@ -132,6 +134,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
    private BubbleExpandListener mExpandListener;
    @Nullable private BubbleStackView.SurfaceSynchronizer mSurfaceSynchronizer;
    private final NotificationGroupManager mNotificationGroupManager;
    private final Lazy<ShadeController> mShadeController;

    private BubbleData mBubbleData;
    @Nullable private BubbleStackView mStackView;
@@ -206,24 +209,34 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
    }

    @Inject
    public BubbleController(Context context, StatusBarWindowController statusBarWindowController,
            BubbleData data, ConfigurationController configurationController,
    public BubbleController(Context context,
            StatusBarWindowController statusBarWindowController,
            StatusBarStateController statusBarStateController,
            Lazy<ShadeController> shadeController,
            BubbleData data,
            ConfigurationController configurationController,
            NotificationInterruptionStateProvider interruptionStateProvider,
            ZenModeController zenModeController,
            NotificationLockscreenUserManager notifUserManager,
            NotificationGroupManager groupManager) {
        this(context, statusBarWindowController, data, null /* synchronizer */,
                configurationController, interruptionStateProvider, zenModeController,
                notifUserManager, groupManager);
    }

    public BubbleController(Context context, StatusBarWindowController statusBarWindowController,
            BubbleData data, @Nullable BubbleStackView.SurfaceSynchronizer synchronizer,
            NotificationGroupManager groupManager,
            NotificationEntryManager entryManager) {
        this(context, statusBarWindowController, statusBarStateController, shadeController,
                data, null /* synchronizer */, configurationController, interruptionStateProvider,
                zenModeController, notifUserManager, groupManager, entryManager);
    }

    public BubbleController(Context context,
            StatusBarWindowController statusBarWindowController,
            StatusBarStateController statusBarStateController,
            Lazy<ShadeController> shadeController,
            BubbleData data,
            @Nullable BubbleStackView.SurfaceSynchronizer synchronizer,
            ConfigurationController configurationController,
            NotificationInterruptionStateProvider interruptionStateProvider,
            ZenModeController zenModeController,
            NotificationLockscreenUserManager notifUserManager,
            NotificationGroupManager groupManager) {
            NotificationGroupManager groupManager,
            NotificationEntryManager entryManager) {
        mContext = context;
        mNotificationInterruptionStateProvider = interruptionStateProvider;
        mNotifUserManager = notifUserManager;
@@ -249,7 +262,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
        mBubbleData = data;
        mBubbleData.setListener(mBubbleDataListener);

        mNotificationEntryManager = Dependency.get(NotificationEntryManager.class);
        mNotificationEntryManager = entryManager;
        mNotificationEntryManager.addNotificationEntryListener(mEntryListener);
        mNotificationEntryManager.setNotificationRemoveInterceptor(mRemoveInterceptor);
        mNotificationGroupManager = groupManager;
@@ -271,9 +284,10 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
                    }
                });

        mShadeController = shadeController;
        mStatusBarWindowController = statusBarWindowController;
        mStatusBarStateListener = new StatusBarStateListener();
        Dependency.get(StatusBarStateController.class).addCallback(mStatusBarStateListener);
        statusBarStateController.addCallback(mStatusBarStateListener);

        mTaskStackListener = new BubbleTaskStackListener();
        ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener);
@@ -497,15 +511,45 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
     * @param notif the notification associated with this bubble.
     */
    void updateBubble(NotificationEntry notif) {
        updateBubble(notif, /* supressFlyout */ false);
        updateBubble(notif, false /* suppressFlyout */);
    }

    void updateBubble(NotificationEntry notif, boolean suppressFlyout) {
        updateBubble(notif, suppressFlyout, true /* showInShade */);
    }

    void updateBubble(NotificationEntry notif, boolean suppressFlyout, boolean showInShade) {
        // If this is an interruptive notif, mark that it's interrupted
        if (notif.getImportance() >= NotificationManager.IMPORTANCE_HIGH) {
            notif.setInterruption();
        }
        mBubbleData.notificationEntryUpdated(notif, suppressFlyout);
        mBubbleData.notificationEntryUpdated(notif, suppressFlyout, showInShade);
    }

    /**
     * Called when a user has indicated that an active notification should be shown as a bubble.
     * <p>
     * This method will collapse the shade, create the bubble without a flyout or dot, and suppress
     * the notification from appearing in the shade.
     *
     * @param entry the notification to show as a bubble.
     */
    public void onUserCreatedBubbleFromNotification(NotificationEntry entry) {
        mShadeController.get().collapsePanel(true);
        entry.setFlagBubble(true);
        updateBubble(entry, true /* suppressFlyout */, false /* showInShade */);
        mBubbleData.getBubbleWithKey(entry.getKey()).setUserCreated(true);
    }

    /**
     * Called when a user has indicated that an active notification appearing as a bubble should
     * no longer be shown as a bubble.
     *
     * @param entry the notification to no longer show as a bubble.
     */
    public void onUserDemotedBubbleFromNotification(NotificationEntry entry) {
        entry.setFlagBubble(false);
        removeBubble(entry.getKey(), DISMISS_BLOCKED);
    }

    /**
@@ -571,7 +615,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
                    mNotificationEntryManager.updateNotifications(
                            "BubbleController.onNotificationRemoveRequested");
                    return true;
                } else if (!userRemovedNotif && entry != null) {
                } else if (!userRemovedNotif && entry != null && !bubble.isUserCreated()) {
                    // This wasn't a user removal so we should remove the bubble as well
                    mBubbleData.notificationEntryRemoved(entry, DISMISS_NOTIF_CANCEL);
                    return false;
@@ -631,6 +675,9 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
    private final NotificationEntryListener mEntryListener = new NotificationEntryListener() {
        @Override
        public void onPendingEntryAdded(NotificationEntry entry) {
            Bubble b = mBubbleData.getBubbleWithKey(entry.getKey());
            BubbleExperimentConfig.adjustForExperiments(mContext, entry, b);

            if (mNotificationInterruptionStateProvider.shouldBubbleUp(entry)
                    && canLaunchInActivityView(mContext, entry)) {
                updateBubble(entry);
@@ -639,13 +686,15 @@ public class BubbleController implements ConfigurationController.ConfigurationLi

        @Override
        public void onPreEntryUpdated(NotificationEntry entry) {
            Bubble b = mBubbleData.getBubbleWithKey(entry.getKey());
            BubbleExperimentConfig.adjustForExperiments(mContext, entry, b);

            boolean shouldBubble = mNotificationInterruptionStateProvider.shouldBubbleUp(entry)
                    && canLaunchInActivityView(mContext, entry);
            if (!shouldBubble && mBubbleData.hasBubbleWithKey(entry.getKey())) {
                // It was previously a bubble but no longer a bubble -- lets remove it
                removeBubble(entry.getKey(), DISMISS_NO_LONGER_BUBBLE);
            } else if (shouldBubble) {
                Bubble b = mBubbleData.getBubbleWithKey(entry.getKey());
                updateBubble(entry);
            }
        }
@@ -949,19 +998,26 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
        PendingIntent intent = entry.getBubbleMetadata() != null
                ? entry.getBubbleMetadata().getIntent()
                : null;
        return canLaunchIntentInActivityView(context, entry, intent);
    }

    static boolean canLaunchIntentInActivityView(Context context, NotificationEntry entry,
            PendingIntent intent) {
        if (intent == null) {
            Log.w(TAG, "Unable to create bubble -- no intent");
            Log.w(TAG, "Unable to create bubble -- no intent: " + entry.getKey());
            return false;
        }
        ActivityInfo info =
                intent.getIntent().resolveActivityInfo(context.getPackageManager(), 0);
        if (info == null) {
            Log.w(TAG, "Unable to send as bubble -- couldn't find activity info for intent: "
            Log.w(TAG, "Unable to send as bubble, "
                    + entry.getKey() + " couldn't find activity info for intent: "
                    + intent);
            return false;
        }
        if (!ActivityInfo.isResizeableMode(info.resizeMode)) {
            Log.w(TAG, "Unable to send as bubble -- activity is not resizable for intent: "
            Log.w(TAG, "Unable to send as bubble, "
                    + entry.getKey() + " activity is not resizable for intent: "
                    + intent);
            return false;
        }
+3 −2
Original line number Diff line number Diff line
@@ -179,7 +179,8 @@ public class BubbleData {
        dispatchPendingChanges();
    }

    void notificationEntryUpdated(NotificationEntry entry, boolean suppressFlyout) {
    void notificationEntryUpdated(NotificationEntry entry, boolean suppressFlyout,
            boolean showInShade) {
        if (DEBUG_BUBBLE_DATA) {
            Log.d(TAG, "notificationEntryUpdated: " + entry);
        }
@@ -208,7 +209,7 @@ public class BubbleData {
            setSelectedBubbleInternal(bubble);
        }
        boolean isBubbleExpandedAndSelected = mExpanded && mSelectedBubble == bubble;
        bubble.setShowInShadeWhenBubble(!isBubbleExpandedAndSelected);
        bubble.setShowInShadeWhenBubble(!isBubbleExpandedAndSelected && showInShade);
        bubble.setShowBubbleDot(!isBubbleExpandedAndSelected);
        dispatchPendingChanges();
    }
+6 −4
Original line number Diff line number Diff line
@@ -184,12 +184,14 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList
                        + " mActivityViewStatus=" + mActivityViewStatus
                        + " bubble=" + getBubbleKey());
            }
            if (mBubble != null && !mBubble.isUserCreated()) {
                if (mBubble != null) {
                    // Must post because this is called from a binder thread.
                    post(() -> mBubbleController.removeBubble(mBubble.getKey(),
                            BubbleController.DISMISS_TASK_FINISHED));
                }
            }
        }
    };

    public BubbleExpandedView(Context context) {
+98 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2019 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 com.android.systemui.bubbles;

import android.app.Notification;
import android.app.PendingIntent;
import android.content.Context;
import android.graphics.drawable.Icon;
import android.provider.Settings;

import com.android.systemui.statusbar.notification.collection.NotificationEntry;

/**
 * Common class for experiments controlled via secure settings.
 */
public class BubbleExperimentConfig {

    private static final String ALLOW_ANY_NOTIF_TO_BUBBLE = "allow_any_notif_to_bubble";
    private static final boolean ALLOW_ANY_NOTIF_TO_BUBBLE_DEFAULT = false;

    private static final String ALLOW_MESSAGE_NOTIFS_TO_BUBBLE = "allow_message_notifs_to_bubble";
    private static final boolean ALLOW_MESSAGE_NOTIFS_TO_BUBBLE_DEFAULT = false;

    /**
     * When true, if a notification has the information necessary to bubble (i.e. valid
     * contentIntent and an icon or image), then a {@link android.app.Notification.BubbleMetadata}
     * object will be created by the system and added to the notification.
     *
     * This does not produce a bubble, only adds the metadata. It should be used in conjunction
     * with {@see #allowNotifBubbleMenu} which shows an affordance to bubble notification content.
     */
    static boolean allowAnyNotifToBubble(Context context) {
        return Settings.Secure.getInt(context.getContentResolver(),
                ALLOW_ANY_NOTIF_TO_BUBBLE,
                ALLOW_ANY_NOTIF_TO_BUBBLE_DEFAULT ? 1 : 0) != 0;
    }

    /**
     * Same as {@link #allowAnyNotifToBubble(Context)} except it filters for notifications that
     * are using {@link Notification.MessagingStyle} and have remote input.
     */
    static boolean allowMessageNotifsToBubble(Context context) {
        return Settings.Secure.getInt(context.getContentResolver(),
                ALLOW_MESSAGE_NOTIFS_TO_BUBBLE,
                ALLOW_MESSAGE_NOTIFS_TO_BUBBLE_DEFAULT ? 1 : 0) != 0;
    }

    /**
     * If {@link #allowAnyNotifToBubble(Context)} is true, this method creates and adds
     * {@link android.app.Notification.BubbleMetadata} to the notification entry as long as
     * the notification has necessary info for BubbleMetadata.
     */
    static void adjustForExperiments(Context context, NotificationEntry entry,
            Bubble previousBubble) {
        if (entry.getBubbleMetadata() != null) {
            // Has metadata, nothing to do.
            return;
        }

        Notification notification = entry.getSbn().getNotification();
        boolean isMessage = Notification.MessagingStyle.class.equals(
                notification.getNotificationStyle());
        boolean bubbleNotifForExperiment = (isMessage && allowMessageNotifsToBubble(context))
                || allowAnyNotifToBubble(context);

        final PendingIntent intent = notification.contentIntent;
        if (bubbleNotifForExperiment
                && BubbleController.canLaunchIntentInActivityView(context, entry, intent)) {
            final Icon smallIcon = entry.getSbn().getNotification().getSmallIcon();
            Notification.BubbleMetadata.Builder metadata =
                    new Notification.BubbleMetadata.Builder()
                            .setDesiredHeight(10000)
                            .setIcon(smallIcon)
                            .setIntent(intent);
            entry.setBubbleMetadata(metadata.build());
        }

        if (previousBubble != null) {
            // Update to a previously user-created bubble, set its flag now so the update goes
            // to the bubble.
            entry.setFlagBubble(true);
        }
    }
}
Loading