Loading packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java +20 −17 Original line number Diff line number Diff line Loading @@ -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(); Loading Loading @@ -145,6 +148,12 @@ class Bubble { return mExpandedView; } void cleanupExpandedState() { if (mExpandedView != null) { mExpandedView.cleanUpExpandedState(); } } void inflate(LayoutInflater inflater, BubbleStackView stackView) { if (mInflated) { return; Loading Loading @@ -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(); Loading Loading @@ -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; } /** Loading packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java +98 −25 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading Loading @@ -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 {} Loading @@ -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 Loading @@ -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; Loading @@ -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; Loading Loading @@ -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 Loading Loading @@ -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; }); } /** Loading @@ -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) { Loading Loading @@ -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); } /** Loading Loading @@ -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); Loading @@ -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; Loading Loading @@ -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); } } Loading Loading @@ -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 Loading @@ -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 */); Loading @@ -551,6 +623,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi } } } } if (update.updatedBubble != null) { mStackView.updateBubble(update.updatedBubble); Loading packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java +3 −4 Original line number Diff line number Diff line Loading @@ -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); } Loading @@ -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 { Loading Loading @@ -306,7 +307,6 @@ public class BubbleData { Bubble newSelected = mBubbles.get(newIndex); setSelectedBubbleInternal(newSelected); } bubbleToRemove.setRemoved(); maybeSendDeleteIntent(reason, bubbleToRemove.getEntry()); } Loading @@ -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); } Loading packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java +58 −50 Original line number Diff line number Diff line Loading @@ -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); Loading Loading @@ -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( Loading Loading @@ -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() { Loading packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java +8 −3 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -103,6 +104,8 @@ public class BubbleControllerTest extends SysuiTestCase { private ZenModeConfig mZenModeConfig; @Mock private FaceManager mFaceManager; @Mock private NotificationLockscreenUserManager mLockscreenUserManager; private FrameLayout mStatusBarView; @Captor Loading Loading @@ -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); Loading Loading @@ -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 Loading
packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java +20 −17 Original line number Diff line number Diff line Loading @@ -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(); Loading Loading @@ -145,6 +148,12 @@ class Bubble { return mExpandedView; } void cleanupExpandedState() { if (mExpandedView != null) { mExpandedView.cleanUpExpandedState(); } } void inflate(LayoutInflater inflater, BubbleStackView stackView) { if (mInflated) { return; Loading Loading @@ -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(); Loading Loading @@ -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; } /** Loading
packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java +98 −25 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading Loading @@ -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 {} Loading @@ -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 Loading @@ -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; Loading @@ -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; Loading Loading @@ -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 Loading Loading @@ -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; }); } /** Loading @@ -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) { Loading Loading @@ -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); } /** Loading Loading @@ -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); Loading @@ -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; Loading Loading @@ -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); } } Loading Loading @@ -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 Loading @@ -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 */); Loading @@ -551,6 +623,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi } } } } if (update.updatedBubble != null) { mStackView.updateBubble(update.updatedBubble); Loading
packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java +3 −4 Original line number Diff line number Diff line Loading @@ -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); } Loading @@ -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 { Loading Loading @@ -306,7 +307,6 @@ public class BubbleData { Bubble newSelected = mBubbles.get(newIndex); setSelectedBubbleInternal(newSelected); } bubbleToRemove.setRemoved(); maybeSendDeleteIntent(reason, bubbleToRemove.getEntry()); } Loading @@ -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); } Loading
packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java +58 −50 Original line number Diff line number Diff line Loading @@ -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); Loading Loading @@ -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( Loading Loading @@ -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() { Loading
packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java +8 −3 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -103,6 +104,8 @@ public class BubbleControllerTest extends SysuiTestCase { private ZenModeConfig mZenModeConfig; @Mock private FaceManager mFaceManager; @Mock private NotificationLockscreenUserManager mLockscreenUserManager; private FrameLayout mStatusBarView; @Captor Loading Loading @@ -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); Loading Loading @@ -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