Loading libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java +9 −0 Original line number Diff line number Diff line Loading @@ -284,6 +284,15 @@ public class Bubble implements BubbleViewProvider { return mTitle; } /** * @return the ShortcutInfo id if it exists, or the metadata shortcut id otherwise. */ String getShortcutId() { return getShortcutInfo() != null ? getShortcutInfo().getId() : getMetadataShortcutId(); } String getMetadataShortcutId() { return mMetadataShortcutId; } Loading libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +131 −31 Original line number Diff line number Diff line Loading @@ -93,6 +93,7 @@ import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Objects; import java.util.concurrent.Executor; Loading Loading @@ -265,15 +266,7 @@ public class BubbleController { public void initialize() { mBubbleData.setListener(mBubbleDataListener); mBubbleData.setSuppressionChangedListener(bubble -> { // Make sure NoMan knows suppression state so that anyone querying it can tell. try { mBarService.onBubbleNotificationSuppressionChanged(bubble.getKey(), !bubble.showInShade(), bubble.isSuppressed()); } catch (RemoteException e) { // Bad things have happened } }); mBubbleData.setSuppressionChangedListener(this::onBubbleNotificationSuppressionChanged); mBubbleData.setPendingIntentCancelledListener(bubble -> { if (bubble.getBubbleIntent() == null) { Loading Loading @@ -401,6 +394,11 @@ public class BubbleController { return mImpl; } @VisibleForTesting public BubblesImpl.CachedState getImplCachedState() { return mImpl.mCachedState; } public ShellExecutor getMainExecutor() { return mMainExecutor; } Loading Loading @@ -500,6 +498,18 @@ public class BubbleController { updateStack(); } @VisibleForTesting public void onBubbleNotificationSuppressionChanged(Bubble bubble) { // Make sure NoMan knows suppression state so that anyone querying it can tell. try { mBarService.onBubbleNotificationSuppressionChanged(bubble.getKey(), !bubble.showInShade(), bubble.isSuppressed()); } catch (RemoteException e) { // Bad things have happened } mImpl.mCachedState.updateBubbleSuppressedState(bubble); } /** Called when the current user changes. */ @VisibleForTesting public void onUserChanged(int newUserId) { Loading Loading @@ -808,11 +818,6 @@ public class BubbleController { } } private boolean isBubbleExpanded(String key) { return isStackExpanded() && mBubbleData != null && mBubbleData.getSelectedBubble() != null && mBubbleData.getSelectedBubble().getKey().equals(key); } /** Promote the provided bubble from the overflow view. */ public void promoteBubbleFromOverflow(Bubble bubble) { mLogger.log(bubble, BubbleLogger.Event.BUBBLE_OVERFLOW_REMOVE_BACK_TO_STACK); Loading Loading @@ -1191,6 +1196,9 @@ public class BubbleController { mSysuiProxy.notifyInvalidateNotifications("BubbleData.Listener.applyUpdate"); updateStack(); // Update the cached state for queries from SysUI mImpl.mCachedState.update(update); } }; Loading Loading @@ -1364,25 +1372,124 @@ public class BubbleController { } private class BubblesImpl implements Bubbles { // Up-to-date cached state of bubbles data for SysUI to query from the calling thread @VisibleForTesting public class CachedState { private boolean mIsStackExpanded; private String mSelectedBubbleKey; private HashSet<String> mSuppressedBubbleKeys = new HashSet<>(); private HashMap<String, String> mSuppressedGroupToNotifKeys = new HashMap<>(); private HashMap<String, Bubble> mShortcutIdToBubble = new HashMap<>(); private ArrayList<Bubble> mTmpBubbles = new ArrayList<>(); /** * Updates the cached state based on the last full BubbleData change. */ synchronized void update(BubbleData.Update update) { if (update.selectionChanged) { mSelectedBubbleKey = update.selectedBubble != null ? update.selectedBubble.getKey() : null; } if (update.expandedChanged) { mIsStackExpanded = update.expanded; } if (update.suppressedSummaryChanged) { String summaryKey = mBubbleData.getSummaryKey(update.suppressedSummaryGroup); if (summaryKey != null) { mSuppressedGroupToNotifKeys.put(update.suppressedSummaryGroup, summaryKey); } else { mSuppressedGroupToNotifKeys.remove(update.suppressedSummaryGroup); } } mTmpBubbles.clear(); mTmpBubbles.addAll(update.bubbles); mTmpBubbles.addAll(update.overflowBubbles); mSuppressedBubbleKeys.clear(); mShortcutIdToBubble.clear(); for (Bubble b : mTmpBubbles) { mShortcutIdToBubble.put(b.getShortcutId(), b); updateBubbleSuppressedState(b); } } /** * Updates a specific bubble suppressed state. This is used mainly because notification * suppression changes don't go through the same BubbleData update mechanism. */ synchronized void updateBubbleSuppressedState(Bubble b) { if (!b.showInShade()) { mSuppressedBubbleKeys.add(b.getKey()); } else { mSuppressedBubbleKeys.remove(b.getKey()); } } public synchronized boolean isStackExpanded() { return mIsStackExpanded; } public synchronized boolean isBubbleExpanded(String key) { return mIsStackExpanded && key.equals(mSelectedBubbleKey); } public synchronized boolean isBubbleNotificationSuppressedFromShade(String key, String groupKey) { return mSuppressedBubbleKeys.contains(key) || (mSuppressedGroupToNotifKeys.containsKey(groupKey) && key.equals(mSuppressedGroupToNotifKeys.get(groupKey))); } @Nullable public synchronized Bubble getBubbleWithShortcutId(String id) { return mShortcutIdToBubble.get(id); } synchronized void dump(PrintWriter pw) { pw.println("BubbleImpl.CachedState state:"); pw.println("mIsStackExpanded: " + mIsStackExpanded); pw.println("mSelectedBubbleKey: " + mSelectedBubbleKey); pw.print("mSuppressedBubbleKeys: "); pw.println(mSuppressedBubbleKeys.size()); for (String key : mSuppressedBubbleKeys) { pw.println(" suppressing: " + key); } pw.print("mSuppressedGroupToNotifKeys: "); pw.println(mSuppressedGroupToNotifKeys.size()); for (String key : mSuppressedGroupToNotifKeys.keySet()) { pw.println(" suppressing: " + key); } } } private CachedState mCachedState = new CachedState(); @Override public boolean isBubbleNotificationSuppressedFromShade(String key, String groupKey) { return mMainExecutor.executeBlockingForResult(() -> { return BubbleController.this.isBubbleNotificationSuppressedFromShade(key, groupKey); }, Boolean.class); return mCachedState.isBubbleNotificationSuppressedFromShade(key, groupKey); } @Override public boolean isBubbleExpanded(String key) { return mMainExecutor.executeBlockingForResult(() -> { return BubbleController.this.isBubbleExpanded(key); }, Boolean.class); return mCachedState.isBubbleExpanded(key); } @Override public boolean isStackExpanded() { return mMainExecutor.executeBlockingForResult(() -> { return BubbleController.this.isStackExpanded(); }, Boolean.class); return mCachedState.isStackExpanded(); } @Override @Nullable public Bubble getBubbleWithShortcutId(String shortcutId) { return mCachedState.getBubbleWithShortcutId(shortcutId); } @Override Loading Loading @@ -1424,14 +1531,6 @@ public class BubbleController { }); } @Override @Nullable public Bubble getBubbleWithShortcutId(String shortcutId) { return mMainExecutor.executeBlockingForResult(() -> { return BubbleController.this.mBubbleData.getAnyBubbleWithShortcutId(shortcutId); }, Bubble.class); } @Override public void onTaskbarChanged(Bundle b) { mMainExecutor.execute(() -> { Loading Loading @@ -1555,6 +1654,7 @@ public class BubbleController { try { mMainExecutor.executeBlocking(() -> { BubbleController.this.dump(fd, pw, args); mCachedState.dump(pw); }); } catch (InterruptedException e) { Slog.e(TAG, "Failed to dump BubbleController in 2s"); Loading libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java +11 −1 Original line number Diff line number Diff line Loading @@ -73,6 +73,7 @@ public class BubbleData { boolean expandedChanged; boolean selectionChanged; boolean orderChanged; boolean suppressedSummaryChanged; boolean expanded; @Nullable BubbleViewProvider selectedBubble; @Nullable Bubble addedBubble; Loading @@ -81,6 +82,7 @@ public class BubbleData { @Nullable Bubble removedOverflowBubble; @Nullable Bubble suppressedBubble; @Nullable Bubble unsuppressedBubble; @Nullable String suppressedSummaryGroup; // Pair with Bubble and @DismissReason Integer final List<Pair<Bubble, Integer>> removedBubbles = new ArrayList<>(); Loading @@ -103,7 +105,9 @@ public class BubbleData { || removedOverflowBubble != null || orderChanged || suppressedBubble != null || unsuppressedBubble != null; || unsuppressedBubble != null || suppressedSummaryChanged || suppressedSummaryGroup != null; } void bubbleRemoved(Bubble bubbleToRemove, @DismissReason int reason) { Loading Loading @@ -380,6 +384,9 @@ public class BubbleData { */ void addSummaryToSuppress(String groupKey, String notifKey) { mSuppressedGroupKeys.put(groupKey, notifKey); mStateChange.suppressedSummaryChanged = true; mStateChange.suppressedSummaryGroup = groupKey; dispatchPendingChanges(); } /** Loading @@ -397,6 +404,9 @@ public class BubbleData { */ void removeSuppressedSummary(String groupKey) { mSuppressedGroupKeys.remove(groupKey); mStateChange.suppressedSummaryChanged = true; mStateChange.suppressedSummaryGroup = groupKey; dispatchPendingChanges(); } /** Loading packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java +1 −1 Original line number Diff line number Diff line Loading @@ -617,7 +617,7 @@ public class BubblesManager implements Dumpable { * cancel it (and hence the bubbles associated with it). * * @return true if we want to intercept the dismissal of the entry, else false. * @see Bubbles#handleDismissalInterception(BubbleEntry, List, IntConsumer) * @see Bubbles#handleDismissalInterception(BubbleEntry, List, IntConsumer, Executor) */ public boolean handleDismissalInterception(NotificationEntry entry) { if (entry == null) { Loading packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java +90 −75 File changed.Preview size limit exceeded, changes collapsed. Show changes Loading
libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java +9 −0 Original line number Diff line number Diff line Loading @@ -284,6 +284,15 @@ public class Bubble implements BubbleViewProvider { return mTitle; } /** * @return the ShortcutInfo id if it exists, or the metadata shortcut id otherwise. */ String getShortcutId() { return getShortcutInfo() != null ? getShortcutInfo().getId() : getMetadataShortcutId(); } String getMetadataShortcutId() { return mMetadataShortcutId; } Loading
libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +131 −31 Original line number Diff line number Diff line Loading @@ -93,6 +93,7 @@ import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Objects; import java.util.concurrent.Executor; Loading Loading @@ -265,15 +266,7 @@ public class BubbleController { public void initialize() { mBubbleData.setListener(mBubbleDataListener); mBubbleData.setSuppressionChangedListener(bubble -> { // Make sure NoMan knows suppression state so that anyone querying it can tell. try { mBarService.onBubbleNotificationSuppressionChanged(bubble.getKey(), !bubble.showInShade(), bubble.isSuppressed()); } catch (RemoteException e) { // Bad things have happened } }); mBubbleData.setSuppressionChangedListener(this::onBubbleNotificationSuppressionChanged); mBubbleData.setPendingIntentCancelledListener(bubble -> { if (bubble.getBubbleIntent() == null) { Loading Loading @@ -401,6 +394,11 @@ public class BubbleController { return mImpl; } @VisibleForTesting public BubblesImpl.CachedState getImplCachedState() { return mImpl.mCachedState; } public ShellExecutor getMainExecutor() { return mMainExecutor; } Loading Loading @@ -500,6 +498,18 @@ public class BubbleController { updateStack(); } @VisibleForTesting public void onBubbleNotificationSuppressionChanged(Bubble bubble) { // Make sure NoMan knows suppression state so that anyone querying it can tell. try { mBarService.onBubbleNotificationSuppressionChanged(bubble.getKey(), !bubble.showInShade(), bubble.isSuppressed()); } catch (RemoteException e) { // Bad things have happened } mImpl.mCachedState.updateBubbleSuppressedState(bubble); } /** Called when the current user changes. */ @VisibleForTesting public void onUserChanged(int newUserId) { Loading Loading @@ -808,11 +818,6 @@ public class BubbleController { } } private boolean isBubbleExpanded(String key) { return isStackExpanded() && mBubbleData != null && mBubbleData.getSelectedBubble() != null && mBubbleData.getSelectedBubble().getKey().equals(key); } /** Promote the provided bubble from the overflow view. */ public void promoteBubbleFromOverflow(Bubble bubble) { mLogger.log(bubble, BubbleLogger.Event.BUBBLE_OVERFLOW_REMOVE_BACK_TO_STACK); Loading Loading @@ -1191,6 +1196,9 @@ public class BubbleController { mSysuiProxy.notifyInvalidateNotifications("BubbleData.Listener.applyUpdate"); updateStack(); // Update the cached state for queries from SysUI mImpl.mCachedState.update(update); } }; Loading Loading @@ -1364,25 +1372,124 @@ public class BubbleController { } private class BubblesImpl implements Bubbles { // Up-to-date cached state of bubbles data for SysUI to query from the calling thread @VisibleForTesting public class CachedState { private boolean mIsStackExpanded; private String mSelectedBubbleKey; private HashSet<String> mSuppressedBubbleKeys = new HashSet<>(); private HashMap<String, String> mSuppressedGroupToNotifKeys = new HashMap<>(); private HashMap<String, Bubble> mShortcutIdToBubble = new HashMap<>(); private ArrayList<Bubble> mTmpBubbles = new ArrayList<>(); /** * Updates the cached state based on the last full BubbleData change. */ synchronized void update(BubbleData.Update update) { if (update.selectionChanged) { mSelectedBubbleKey = update.selectedBubble != null ? update.selectedBubble.getKey() : null; } if (update.expandedChanged) { mIsStackExpanded = update.expanded; } if (update.suppressedSummaryChanged) { String summaryKey = mBubbleData.getSummaryKey(update.suppressedSummaryGroup); if (summaryKey != null) { mSuppressedGroupToNotifKeys.put(update.suppressedSummaryGroup, summaryKey); } else { mSuppressedGroupToNotifKeys.remove(update.suppressedSummaryGroup); } } mTmpBubbles.clear(); mTmpBubbles.addAll(update.bubbles); mTmpBubbles.addAll(update.overflowBubbles); mSuppressedBubbleKeys.clear(); mShortcutIdToBubble.clear(); for (Bubble b : mTmpBubbles) { mShortcutIdToBubble.put(b.getShortcutId(), b); updateBubbleSuppressedState(b); } } /** * Updates a specific bubble suppressed state. This is used mainly because notification * suppression changes don't go through the same BubbleData update mechanism. */ synchronized void updateBubbleSuppressedState(Bubble b) { if (!b.showInShade()) { mSuppressedBubbleKeys.add(b.getKey()); } else { mSuppressedBubbleKeys.remove(b.getKey()); } } public synchronized boolean isStackExpanded() { return mIsStackExpanded; } public synchronized boolean isBubbleExpanded(String key) { return mIsStackExpanded && key.equals(mSelectedBubbleKey); } public synchronized boolean isBubbleNotificationSuppressedFromShade(String key, String groupKey) { return mSuppressedBubbleKeys.contains(key) || (mSuppressedGroupToNotifKeys.containsKey(groupKey) && key.equals(mSuppressedGroupToNotifKeys.get(groupKey))); } @Nullable public synchronized Bubble getBubbleWithShortcutId(String id) { return mShortcutIdToBubble.get(id); } synchronized void dump(PrintWriter pw) { pw.println("BubbleImpl.CachedState state:"); pw.println("mIsStackExpanded: " + mIsStackExpanded); pw.println("mSelectedBubbleKey: " + mSelectedBubbleKey); pw.print("mSuppressedBubbleKeys: "); pw.println(mSuppressedBubbleKeys.size()); for (String key : mSuppressedBubbleKeys) { pw.println(" suppressing: " + key); } pw.print("mSuppressedGroupToNotifKeys: "); pw.println(mSuppressedGroupToNotifKeys.size()); for (String key : mSuppressedGroupToNotifKeys.keySet()) { pw.println(" suppressing: " + key); } } } private CachedState mCachedState = new CachedState(); @Override public boolean isBubbleNotificationSuppressedFromShade(String key, String groupKey) { return mMainExecutor.executeBlockingForResult(() -> { return BubbleController.this.isBubbleNotificationSuppressedFromShade(key, groupKey); }, Boolean.class); return mCachedState.isBubbleNotificationSuppressedFromShade(key, groupKey); } @Override public boolean isBubbleExpanded(String key) { return mMainExecutor.executeBlockingForResult(() -> { return BubbleController.this.isBubbleExpanded(key); }, Boolean.class); return mCachedState.isBubbleExpanded(key); } @Override public boolean isStackExpanded() { return mMainExecutor.executeBlockingForResult(() -> { return BubbleController.this.isStackExpanded(); }, Boolean.class); return mCachedState.isStackExpanded(); } @Override @Nullable public Bubble getBubbleWithShortcutId(String shortcutId) { return mCachedState.getBubbleWithShortcutId(shortcutId); } @Override Loading Loading @@ -1424,14 +1531,6 @@ public class BubbleController { }); } @Override @Nullable public Bubble getBubbleWithShortcutId(String shortcutId) { return mMainExecutor.executeBlockingForResult(() -> { return BubbleController.this.mBubbleData.getAnyBubbleWithShortcutId(shortcutId); }, Bubble.class); } @Override public void onTaskbarChanged(Bundle b) { mMainExecutor.execute(() -> { Loading Loading @@ -1555,6 +1654,7 @@ public class BubbleController { try { mMainExecutor.executeBlocking(() -> { BubbleController.this.dump(fd, pw, args); mCachedState.dump(pw); }); } catch (InterruptedException e) { Slog.e(TAG, "Failed to dump BubbleController in 2s"); Loading
libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java +11 −1 Original line number Diff line number Diff line Loading @@ -73,6 +73,7 @@ public class BubbleData { boolean expandedChanged; boolean selectionChanged; boolean orderChanged; boolean suppressedSummaryChanged; boolean expanded; @Nullable BubbleViewProvider selectedBubble; @Nullable Bubble addedBubble; Loading @@ -81,6 +82,7 @@ public class BubbleData { @Nullable Bubble removedOverflowBubble; @Nullable Bubble suppressedBubble; @Nullable Bubble unsuppressedBubble; @Nullable String suppressedSummaryGroup; // Pair with Bubble and @DismissReason Integer final List<Pair<Bubble, Integer>> removedBubbles = new ArrayList<>(); Loading @@ -103,7 +105,9 @@ public class BubbleData { || removedOverflowBubble != null || orderChanged || suppressedBubble != null || unsuppressedBubble != null; || unsuppressedBubble != null || suppressedSummaryChanged || suppressedSummaryGroup != null; } void bubbleRemoved(Bubble bubbleToRemove, @DismissReason int reason) { Loading Loading @@ -380,6 +384,9 @@ public class BubbleData { */ void addSummaryToSuppress(String groupKey, String notifKey) { mSuppressedGroupKeys.put(groupKey, notifKey); mStateChange.suppressedSummaryChanged = true; mStateChange.suppressedSummaryGroup = groupKey; dispatchPendingChanges(); } /** Loading @@ -397,6 +404,9 @@ public class BubbleData { */ void removeSuppressedSummary(String groupKey) { mSuppressedGroupKeys.remove(groupKey); mStateChange.suppressedSummaryChanged = true; mStateChange.suppressedSummaryGroup = groupKey; dispatchPendingChanges(); } /** Loading
packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java +1 −1 Original line number Diff line number Diff line Loading @@ -617,7 +617,7 @@ public class BubblesManager implements Dumpable { * cancel it (and hence the bubbles associated with it). * * @return true if we want to intercept the dismissal of the entry, else false. * @see Bubbles#handleDismissalInterception(BubbleEntry, List, IntConsumer) * @see Bubbles#handleDismissalInterception(BubbleEntry, List, IntConsumer, Executor) */ public boolean handleDismissalInterception(NotificationEntry entry) { if (entry == null) { Loading
packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java +90 −75 File changed.Preview size limit exceeded, changes collapsed. Show changes