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

Commit d4121bba authored by Mark Renouf's avatar Mark Renouf Committed by Android (Google) Code Review
Browse files

Merge "Bubbles handle user switch" into qt-r1-bubbles-dev

parents bfc89344 c19b4738
Loading
Loading
Loading
Loading
+20 −17
Original line number Diff line number Diff line
@@ -77,6 +77,9 @@ class Bubble {
     */
    private boolean mShowBubbleUpdateDot = true;

    /** Whether flyout text should be suppressed, regardless of any other flags or state. */
    private boolean mSuppressFlyout;

    public static String groupId(NotificationEntry entry) {
        UserHandle user = entry.notification.getUser();
        return user.getIdentifier() + "|" + entry.notification.getPackageName();
@@ -145,6 +148,12 @@ class Bubble {
        return mExpandedView;
    }

    void cleanupExpandedState() {
        if (mExpandedView != null) {
            mExpandedView.cleanUpExpandedState();
        }
    }

    void inflate(LayoutInflater inflater, BubbleStackView stackView) {
        if (mInflated) {
            return;
@@ -174,22 +183,6 @@ class Bubble {
        }
    }

    void setRemoved() {
        mIsRemoved = true;
        // TODO: move this somewhere where it can be guaranteed not to run until safe from flicker
        if (mExpandedView != null) {
            mExpandedView.cleanUpExpandedState();
        }
    }

    void setRemoved(boolean removed) {
        mIsRemoved = removed;
    }

    boolean isRemoved() {
        return mIsRemoved;
    }

    void updateEntry(NotificationEntry entry) {
        mEntry = entry;
        mLastUpdated = entry.notification.getPostTime();
@@ -271,7 +264,17 @@ class Bubble {
     * Whether the flyout for the bubble should be shown.
     */
    boolean showFlyoutForBubble() {
        return !mEntry.shouldSuppressPeek() && !mEntry.shouldSuppressNotificationList();
        return !mSuppressFlyout && !mEntry.shouldSuppressPeek()
                && !mEntry.shouldSuppressNotificationList();
    }

    /**
     * Set whether the flyout text for the bubble should be shown when an update is received.
     *
     * @param suppressFlyout whether the flyout text is shown
     */
    void setSuppressFlyout(boolean suppressFlyout) {
        mSuppressFlyout = suppressFlyout;
    }

    /**
+98 −25
Original line number Diff line number Diff line
@@ -39,6 +39,7 @@ import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.SOURCE;

import android.annotation.UserIdInt;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.NotificationManager;
import android.app.PendingIntent;
@@ -52,8 +53,10 @@ import android.os.ServiceManager;
import android.provider.Settings;
import android.service.notification.NotificationListenerService.RankingMap;
import android.service.notification.ZenModeConfig;
import android.util.ArraySet;
import android.util.Log;
import android.util.Pair;
import android.util.SparseSetArray;
import android.view.Display;
import android.view.IPinnedStackController;
import android.view.IPinnedStackListener;
@@ -72,10 +75,12 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.TaskStackChangeListener;
import com.android.systemui.shared.system.WindowManagerWrapper;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationRemoveInterceptor;
import com.android.systemui.statusbar.notification.NotificationEntryListener;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
import com.android.systemui.statusbar.notification.collection.NotificationData;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.phone.StatusBarWindowController;
import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -103,7 +108,8 @@ public class BubbleController implements ConfigurationController.ConfigurationLi

    @Retention(SOURCE)
    @IntDef({DISMISS_USER_GESTURE, DISMISS_AGED, DISMISS_TASK_FINISHED, DISMISS_BLOCKED,
            DISMISS_NOTIF_CANCEL, DISMISS_ACCESSIBILITY_ACTION, DISMISS_NO_LONGER_BUBBLE})
            DISMISS_NOTIF_CANCEL, DISMISS_ACCESSIBILITY_ACTION, DISMISS_NO_LONGER_BUBBLE,
            DISMISS_USER_CHANGED})
    @Target({FIELD, LOCAL_VARIABLE, PARAMETER})
    @interface DismissReason {}

@@ -114,6 +120,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
    static final int DISMISS_NOTIF_CANCEL = 5;
    static final int DISMISS_ACCESSIBILITY_ACTION = 6;
    static final int DISMISS_NO_LONGER_BUBBLE = 7;
    static final int DISMISS_USER_CHANGED = 8;

    public static final int MAX_BUBBLES = 5; // TODO: actually enforce this

@@ -130,6 +137,11 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
    private BubbleData mBubbleData;
    @Nullable private BubbleStackView mStackView;

    // Tracks the id of the current (foreground) user.
    private int mCurrentUserId;
    // Saves notification keys of active bubbles when users are switched.
    private final SparseSetArray<String> mSavedBubbleKeysPerUser;

    // Bubbles get added to the status bar view
    private final StatusBarWindowController mStatusBarWindowController;
    private final ZenModeController mZenModeController;
@@ -141,6 +153,9 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
    // Used for determining view rect for touch interaction
    private Rect mTempRect = new Rect();

    // Listens to user switch so bubbles can be saved and restored.
    private final NotificationLockscreenUserManager mNotifUserManager;

    /** Last known orientation, used to detect orientation changes in {@link #onConfigChanged}. */
    private int mOrientation = Configuration.ORIENTATION_UNDEFINED;

@@ -195,18 +210,22 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
    public BubbleController(Context context, StatusBarWindowController statusBarWindowController,
            BubbleData data, ConfigurationController configurationController,
            NotificationInterruptionStateProvider interruptionStateProvider,
            ZenModeController zenModeController) {
            ZenModeController zenModeController,
            NotificationLockscreenUserManager notifUserManager) {
        this(context, statusBarWindowController, data, null /* synchronizer */,
                configurationController, interruptionStateProvider, zenModeController);
                configurationController, interruptionStateProvider, zenModeController,
                notifUserManager);
    }

    public BubbleController(Context context, StatusBarWindowController statusBarWindowController,
            BubbleData data, @Nullable BubbleStackView.SurfaceSynchronizer synchronizer,
            ConfigurationController configurationController,
            NotificationInterruptionStateProvider interruptionStateProvider,
            ZenModeController zenModeController) {
            ZenModeController zenModeController,
            NotificationLockscreenUserManager notifUserManager) {
        mContext = context;
        mNotificationInterruptionStateProvider = interruptionStateProvider;
        mNotifUserManager = notifUserManager;
        mZenModeController = zenModeController;
        mZenModeController.addCallback(new ZenModeController.Callback() {
            @Override
@@ -249,6 +268,16 @@ public class BubbleController implements ConfigurationController.ConfigurationLi

        mBarService = IStatusBarService.Stub.asInterface(
                ServiceManager.getService(Context.STATUS_BAR_SERVICE));

        mSavedBubbleKeysPerUser = new SparseSetArray<>();
        mCurrentUserId = mNotifUserManager.getCurrentUserId();
        mNotifUserManager.addUserChangedListener(
                newUserId -> {
                    saveBubbles(mCurrentUserId);
                    mBubbleData.dismissAll(DISMISS_USER_CHANGED);
                    restoreBubbles(newUserId);
                    mCurrentUserId = newUserId;
                });
    }

    /**
@@ -269,6 +298,45 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
        }
    }

    /**
     * Records the notification key for any active bubbles. These are used to restore active
     * bubbles when the user returns to the foreground.
     *
     * @param userId the id of the user
     */
    private void saveBubbles(@UserIdInt int userId) {
        // First clear any existing keys that might be stored.
        mSavedBubbleKeysPerUser.remove(userId);
        // Add in all active bubbles for the current user.
        for (Bubble bubble: mBubbleData.getBubbles()) {
            mSavedBubbleKeysPerUser.add(userId, bubble.getKey());
        }
    }

    /**
     * Promotes existing notifications to Bubbles if they were previously bubbles.
     *
     * @param userId the id of the user
     */
    private void restoreBubbles(@UserIdInt int userId) {
        NotificationData notificationData =
                mNotificationEntryManager.getNotificationData();
        ArraySet<String> savedBubbleKeys = mSavedBubbleKeysPerUser.get(userId);
        if (savedBubbleKeys == null) {
            // There were no bubbles saved for this used.
            return;
        }
        for (NotificationEntry e : notificationData.getNotificationsForCurrentUser()) {
            if (savedBubbleKeys.contains(e.key)
                    && mNotificationInterruptionStateProvider.shouldBubbleUp(e)
                    && canLaunchInActivityView(mContext, e)) {
                updateBubble(e, /* suppressFlyout= */ true);
            }
        }
        // Finally, remove the entries for this user now that bubbles are restored.
        mSavedBubbleKeysPerUser.remove(mCurrentUserId);
    }

    @Override
    public void onUiModeChanged() {
        if (mStackView != null) {
@@ -402,11 +470,15 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
     * @param notif the notification associated with this bubble.
     */
    void updateBubble(NotificationEntry notif) {
        updateBubble(notif, /* supressFlyout */ false);
    }

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

    /**
@@ -446,8 +518,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi

                // The bubble notification sticks around in the data as long as the bubble is
                // not dismissed and the app hasn't cancelled the notification.
                boolean bubbleExtended = entry.isBubble() && !bubble.isRemoved()
                        && userRemovedNotif;
                boolean bubbleExtended = entry.isBubble() && userRemovedNotif;
                if (bubbleExtended) {
                    bubble.setShowInShadeWhenBubble(false);
                    bubble.setShowBubbleDot(false);
@@ -456,7 +527,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
                    }
                    mNotificationEntryManager.updateNotifications();
                    return true;
                } else if (!userRemovedNotif && !bubble.isRemoved()) {
                } else if (!userRemovedNotif) {
                    // This wasn't a user removal so we should remove the bubble as well
                    mBubbleData.notificationEntryRemoved(entry, DISMISS_NOTIF_CANCEL);
                    return false;
@@ -490,7 +561,6 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
                removeBubble(entry.key, DISMISS_NO_LONGER_BUBBLE);
            } else if (shouldBubble) {
                Bubble b = mBubbleData.getBubbleWithKey(entry.key);
                b.setRemoved(false); // updates come back as bubbles even if dismissed
                updateBubble(entry);
            }
        }
@@ -532,6 +602,8 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
                @DismissReason final int reason = removed.second;
                mStackView.removeBubble(bubble);

                // If the bubble is removed for user switching, leave the notification in place.
                if (reason != DISMISS_USER_CHANGED) {
                    if (!mBubbleData.hasBubbleWithKey(bubble.getKey())
                            && !bubble.showInShadeWhenBubble()) {
                        // The bubble is gone & the notification is gone, time to actually remove it
@@ -541,8 +613,8 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
                        // Update the flag for SysUI
                        bubble.getEntry().notification.getNotification().flags &= ~FLAG_BUBBLE;

                    // Make sure NoMan knows it's not a bubble anymore so anyone querying it will
                    // get right result back
                        // Make sure NoMan knows it's not a bubble anymore so anyone querying it
                        // will get right result back
                        try {
                            mBarService.onNotificationBubbleChanged(bubble.getKey(),
                                    false /* isBubble */);
@@ -551,6 +623,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
                        }
                    }
                }
            }

            if (update.updatedBubble != null) {
                mStackView.updateBubble(update.updatedBubble);
+3 −4
Original line number Diff line number Diff line
@@ -166,7 +166,7 @@ public class BubbleData {
        dispatchPendingChanges();
    }

    public void notificationEntryUpdated(NotificationEntry entry) {
    void notificationEntryUpdated(NotificationEntry entry, boolean suppressFlyout) {
        if (DEBUG_BUBBLE_DATA) {
            Log.d(TAG, "notificationEntryUpdated: " + entry);
        }
@@ -174,6 +174,7 @@ public class BubbleData {
        if (bubble == null) {
            // Create a new bubble
            bubble = new Bubble(mContext, entry);
            bubble.setSuppressFlyout(suppressFlyout);
            doAdd(bubble);
            trim();
        } else {
@@ -306,7 +307,6 @@ public class BubbleData {
            Bubble newSelected = mBubbles.get(newIndex);
            setSelectedBubbleInternal(newSelected);
        }
        bubbleToRemove.setRemoved();
        maybeSendDeleteIntent(reason, bubbleToRemove.getEntry());
    }

@@ -321,7 +321,6 @@ public class BubbleData {
        setSelectedBubbleInternal(null);
        while (!mBubbles.isEmpty()) {
            Bubble bubble = mBubbles.remove(0);
            bubble.setRemoved();
            maybeSendDeleteIntent(reason, bubble.getEntry());
            mStateChange.bubbleRemoved(bubble, reason);
        }
+58 −50
Original line number Diff line number Diff line
@@ -717,6 +717,7 @@ public class BubbleStackView extends FrameLayout {
        int removedIndex = mBubbleContainer.indexOfChild(bubble.getIconView());
        if (removedIndex >= 0) {
            mBubbleContainer.removeViewAt(removedIndex);
            bubble.cleanupExpandedState();
            logBubbleEvent(bubble, StatsLog.BUBBLE_UICHANGED__ACTION__DISMISSED);
        } else {
            Log.d(TAG, "was asked to remove Bubble, but didn't find the view! " + bubble);
@@ -1319,12 +1320,20 @@ public class BubbleStackView extends FrameLayout {
    void animateInFlyoutForBubble(Bubble bubble) {
        final CharSequence updateMessage = bubble.getUpdateMessage(getContext());

        // Show the message if one exists, and we're not expanded or animating expansion.
        if (updateMessage != null
                && !isExpanded()
                && !mIsExpansionAnimating
                && !mIsGestureInProgress
                && bubble.showFlyoutForBubble()) {
        if (!bubble.showFlyoutForBubble()) {
            // In case flyout was suppressed for this update, reset now.
            bubble.setSuppressFlyout(false);
            return;
        }

        if (updateMessage == null
                || isExpanded()
                || mIsExpansionAnimating
                || mIsGestureInProgress) {
            // Skip the message if none exists, we're expanded or animating expansion.
            return;
        }

        if (bubble.getIconView() != null) {
            // Temporarily suppress the dot while the flyout is visible.
            bubble.getIconView().setSuppressDot(
@@ -1377,7 +1386,6 @@ public class BubbleStackView extends FrameLayout {
        mFlyout.postDelayed(mHideFlyout, FLYOUT_HIDE_AFTER);
        logBubbleEvent(bubble, StatsLog.BUBBLE_UICHANGED__ACTION__FLYOUT);
    }
    }

    /** Hide the flyout immediately and cancel any pending hide runnables. */
    private void hideFlyoutImmediate() {
+8 −3
Original line number Diff line number Diff line
@@ -58,6 +58,7 @@ import androidx.test.filters.SmallTest;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationPresenter;
import com.android.systemui.statusbar.NotificationRemoveInterceptor;
import com.android.systemui.statusbar.NotificationTestHelper;
@@ -103,6 +104,8 @@ public class BubbleControllerTest extends SysuiTestCase {
    private ZenModeConfig mZenModeConfig;
    @Mock
    private FaceManager mFaceManager;
    @Mock
    private NotificationLockscreenUserManager mLockscreenUserManager;

    private FrameLayout mStatusBarView;
    @Captor
@@ -167,7 +170,7 @@ public class BubbleControllerTest extends SysuiTestCase {
        mBubbleData = new BubbleData(mContext);
        mBubbleController = new TestableBubbleController(mContext, mStatusBarWindowController,
                mBubbleData, mConfigurationController, interruptionStateProvider,
                mZenModeController);
                mZenModeController, mLockscreenUserManager);
        mBubbleController.setBubbleStateChangeListener(mBubbleStateChangeListener);
        mBubbleController.setExpandListener(mBubbleExpandListener);

@@ -592,9 +595,11 @@ public class BubbleControllerTest extends SysuiTestCase {
                StatusBarWindowController statusBarWindowController, BubbleData data,
                ConfigurationController configurationController,
                NotificationInterruptionStateProvider interruptionStateProvider,
                ZenModeController zenModeController) {
                ZenModeController zenModeController,
                NotificationLockscreenUserManager lockscreenUserManager) {
            super(context, statusBarWindowController, data, Runnable::run,
                    configurationController, interruptionStateProvider, zenModeController);
                    configurationController, interruptionStateProvider, zenModeController,
                    lockscreenUserManager);
        }
    }

Loading