Loading libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java +5 −0 Original line number Diff line number Diff line Loading @@ -367,4 +367,9 @@ public class BadgedImageView extends ConstraintLayout { void hideBadge() { mAppIcon.setVisibility(GONE); } @Override public String toString() { return "BadgedImageView{" + mBubble + "}"; } } libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +36 −17 Original line number Diff line number Diff line Loading @@ -24,6 +24,7 @@ import static android.view.View.INVISIBLE; import static android.view.View.VISIBLE; import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_CONTROLLER; import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES; import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.wm.shell.bubbles.BubblePositioner.TASKBAR_POSITION_BOTTOM; Loading Loading @@ -997,6 +998,12 @@ public class BubbleController { // Update the bubble but don't promote it out of overflow Bubble b = mBubbleData.getOverflowBubbleWithKey(notif.getKey()); b.setEntry(notif); } else if (mBubbleData.isSuppressedWithLocusId(notif.getLocusId())) { // Update the bubble but don't promote it out of overflow Bubble b = mBubbleData.getSuppressedBubbleWithKey(notif.getKey()); if (b != null) { b.setEntry(notif); } } else { Bubble bubble = mBubbleData.getOrCreateBubble(notif, null /* persistedBubble */); inflateAndAdd(bubble, suppressFlyout, showInShade); Loading Loading @@ -1170,6 +1177,18 @@ public class BubbleController { @Override public void applyUpdate(BubbleData.Update update) { if (DEBUG_BUBBLE_CONTROLLER) { Log.d(TAG, "applyUpdate:" + " bubbleAdded=" + (update.addedBubble != null) + " bubbleRemoved=" + (update.removedBubbles != null && update.removedBubbles.size() > 0) + " bubbleUpdated=" + (update.updatedBubble != null) + " orderChanged=" + update.orderChanged + " expandedChanged=" + update.expandedChanged + " selectionChanged=" + update.selectionChanged + " suppressed=" + (update.suppressedBubble != null) + " unsuppressed=" + (update.unsuppressedBubble != null)); } ensureStackViewCreated(); // Lazy load overflow bubbles from disk Loading Loading @@ -1249,6 +1268,14 @@ public class BubbleController { mStackView.updateBubble(update.updatedBubble); } if (update.suppressedBubble != null && mStackView != null) { mStackView.setBubbleSuppressed(update.suppressedBubble, true); } if (update.unsuppressedBubble != null && mStackView != null) { mStackView.setBubbleSuppressed(update.unsuppressedBubble, false); } // At this point, the correct bubbles are inflated in the stack. // Make sure the order in bubble data is reflected in bubble row. if (update.orderChanged && mStackView != null) { Loading @@ -1263,14 +1290,6 @@ public class BubbleController { } } if (update.suppressedBubble != null && mStackView != null) { mStackView.setBubbleVisibility(update.suppressedBubble, false); } if (update.unsuppressedBubble != null && mStackView != null) { mStackView.setBubbleVisibility(update.unsuppressedBubble, true); } // Expanding? Apply this last. if (update.expandedChanged && update.expanded) { if (mStackView != null) { Loading libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java +141 −28 Original line number Diff line number Diff line Loading @@ -224,7 +224,8 @@ public class BubbleData { } public boolean hasAnyBubbleWithKey(String key) { return hasBubbleInStackWithKey(key) || hasOverflowBubbleWithKey(key); return hasBubbleInStackWithKey(key) || hasOverflowBubbleWithKey(key) || hasSuppressedBubbleWithKey(key); } public boolean hasBubbleInStackWithKey(String key) { Loading @@ -235,6 +236,20 @@ public class BubbleData { return getOverflowBubbleWithKey(key) != null; } /** * Check if there are any bubbles suppressed with the given notification <code>key</code> */ public boolean hasSuppressedBubbleWithKey(String key) { return mSuppressedBubbles.values().stream().anyMatch(b -> b.getKey().equals(key)); } /** * Check if there are any bubbles suppressed with the given <code>LocusId</code> */ public boolean isSuppressedWithLocusId(LocusId locusId) { return mSuppressedBubbles.get(locusId) != null; } @Nullable public BubbleViewProvider getSelectedBubble() { return mSelectedBubble; Loading Loading @@ -356,11 +371,11 @@ public class BubbleData { boolean isSuppressed = mSuppressedBubbles.containsKey(locusId); if (isSuppressed && (!bubble.isSuppressed() || !bubble.isSuppressable())) { mSuppressedBubbles.remove(locusId); mStateChange.unsuppressedBubble = bubble; doUnsuppress(bubble); } else if (!isSuppressed && (bubble.isSuppressed() || bubble.isSuppressable() && mVisibleLocusIds.contains(locusId))) { mSuppressedBubbles.put(locusId, bubble); mStateChange.suppressedBubble = bubble; doSuppress(bubble); } } dispatchPendingChanges(); Loading Loading @@ -532,16 +547,19 @@ public class BubbleData { if (mPendingBubbles.containsKey(key)) { mPendingBubbles.remove(key); } int indexToRemove = indexForKey(key); if (indexToRemove == -1) { if (hasOverflowBubbleWithKey(key) && (reason == Bubbles.DISMISS_NOTIF_CANCEL boolean shouldRemoveHiddenBubble = reason == Bubbles.DISMISS_NOTIF_CANCEL || reason == Bubbles.DISMISS_GROUP_CANCELLED || reason == Bubbles.DISMISS_NO_LONGER_BUBBLE || reason == Bubbles.DISMISS_BLOCKED || reason == Bubbles.DISMISS_SHORTCUT_REMOVED || reason == Bubbles.DISMISS_PACKAGE_REMOVED || reason == Bubbles.DISMISS_USER_CHANGED)) { || reason == Bubbles.DISMISS_USER_CHANGED; int indexToRemove = indexForKey(key); if (indexToRemove == -1) { if (hasOverflowBubbleWithKey(key) && shouldRemoveHiddenBubble) { Bubble b = getOverflowBubbleWithKey(key); if (DEBUG_BUBBLE_DATA) { Loading @@ -555,6 +573,17 @@ public class BubbleData { mStateChange.bubbleRemoved(b, reason); mStateChange.removedOverflowBubble = b; } if (hasSuppressedBubbleWithKey(key) && shouldRemoveHiddenBubble) { Bubble b = getSuppressedBubbleWithKey(key); if (DEBUG_BUBBLE_DATA) { Log.d(TAG, "Cancel suppressed bubble: " + b); } if (b != null) { mSuppressedBubbles.remove(b.getLocusId()); b.stopInflation(); mStateChange.bubbleRemoved(b, reason); } } return; } Bubble bubbleToRemove = mBubbles.get(indexToRemove); Loading @@ -579,12 +608,66 @@ public class BubbleData { // Note: If mBubbles.isEmpty(), then mSelectedBubble is now null. if (Objects.equals(mSelectedBubble, bubbleToRemove)) { setNewSelectedIndex(indexToRemove); } maybeSendDeleteIntent(reason, bubbleToRemove); } private void setNewSelectedIndex(int indexOfSelected) { if (mBubbles.isEmpty()) { Log.w(TAG, "Bubbles list empty when attempting to select index: " + indexOfSelected); return; } // Move selection to the new bubble at the same position. int newIndex = Math.min(indexToRemove, mBubbles.size() - 1); int newIndex = Math.min(indexOfSelected, mBubbles.size() - 1); if (DEBUG_BUBBLE_DATA) { Log.d(TAG, "setNewSelectedIndex: " + indexOfSelected); } BubbleViewProvider newSelected = mBubbles.get(newIndex); setSelectedBubbleInternal(newSelected); } maybeSendDeleteIntent(reason, bubbleToRemove); private void doSuppress(Bubble bubble) { if (DEBUG_BUBBLE_DATA) { Log.d(TAG, "doSuppressed: " + bubble); } mStateChange.suppressedBubble = bubble; bubble.setSuppressBubble(true); int indexToRemove = mBubbles.indexOf(bubble); // Order changes if we are not suppressing the last bubble mStateChange.orderChanged = !(mBubbles.size() - 1 == indexToRemove); mBubbles.remove(indexToRemove); // Update selection if we suppressed the selected bubble if (Objects.equals(mSelectedBubble, bubble)) { if (mBubbles.isEmpty()) { // Don't use setSelectedBubbleInternal because we don't want to trigger an // applyUpdate mSelectedBubble = null; } else { // Mark new first bubble as selected setNewSelectedIndex(0); } } } private void doUnsuppress(Bubble bubble) { if (DEBUG_BUBBLE_DATA) { Log.d(TAG, "doUnsuppressed: " + bubble); } bubble.setSuppressBubble(false); mStateChange.unsuppressedBubble = bubble; mBubbles.add(bubble); if (mBubbles.size() > 1) { // See where the bubble actually lands repackAll(); mStateChange.orderChanged = true; } if (mBubbles.get(0) == bubble) { // Unsuppressed bubble is sorted to first position. Mark it as the selected. setNewSelectedIndex(0); } } void overflowBubble(@DismissReason int reason, Bubble bubble) { Loading Loading @@ -619,7 +702,7 @@ public class BubbleData { if (DEBUG_BUBBLE_DATA) { Log.d(TAG, "dismissAll: reason=" + reason); } if (mBubbles.isEmpty()) { if (mBubbles.isEmpty() && mSuppressedBubbles.isEmpty()) { return; } setExpandedInternal(false); Loading @@ -627,6 +710,10 @@ public class BubbleData { while (!mBubbles.isEmpty()) { doRemove(mBubbles.get(0).getKey(), reason); } while (!mSuppressedBubbles.isEmpty()) { Bubble bubble = mSuppressedBubbles.removeAt(0); doRemove(bubble.getKey(), reason); } dispatchPendingChanges(); } Loading @@ -640,6 +727,10 @@ public class BubbleData { * @param visible whether the task with the locusId is visible or not. */ public void onLocusVisibilityChanged(int taskId, LocusId locusId, boolean visible) { if (DEBUG_BUBBLE_DATA) { Log.d(TAG, "onLocusVisibilityChanged: " + locusId + " visible=" + visible); } Bubble matchingBubble = getBubbleInStackWithLocusId(locusId); // Don't add the locus if it's from a bubble'd activity, we only suppress for non-bubbled. if (visible && (matchingBubble == null || matchingBubble.getTaskId() != taskId)) { Loading @@ -647,21 +738,23 @@ public class BubbleData { } else { mVisibleLocusIds.remove(locusId); } if (matchingBubble == null) { // Check if there is a suppressed bubble for this LocusId matchingBubble = mSuppressedBubbles.get(locusId); if (matchingBubble == null) { return; } } boolean isAlreadySuppressed = mSuppressedBubbles.get(locusId) != null; if (visible && !isAlreadySuppressed && matchingBubble.isSuppressable() && taskId != matchingBubble.getTaskId()) { mSuppressedBubbles.put(locusId, matchingBubble); matchingBubble.setSuppressBubble(true); mStateChange.suppressedBubble = matchingBubble; doSuppress(matchingBubble); dispatchPendingChanges(); } else if (!visible) { Bubble unsuppressedBubble = mSuppressedBubbles.remove(locusId); if (unsuppressedBubble != null) { unsuppressedBubble.setSuppressBubble(false); mStateChange.unsuppressedBubble = unsuppressedBubble; doUnsuppress(unsuppressedBubble); } dispatchPendingChanges(); } Loading Loading @@ -869,6 +962,9 @@ public class BubbleData { if (b == null) { b = getOverflowBubbleWithKey(key); } if (b == null) { b = getSuppressedBubbleWithKey(key); } return b; } Loading Loading @@ -946,6 +1042,23 @@ public class BubbleData { return null; } /** * Get a suppressed bubble with given notification <code>key</code> * * @param key notification key * @return bubble that matches or null */ @Nullable @VisibleForTesting(visibility = PRIVATE) public Bubble getSuppressedBubbleWithKey(String key) { for (Bubble b : mSuppressedBubbles.values()) { if (b.getKey().equals(key)) { return b; } } return null; } @VisibleForTesting(visibility = PRIVATE) void setTimeSource(TimeSource timeSource) { mTimeSource = timeSource; Loading libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java +55 −30 Original line number Diff line number Diff line Loading @@ -170,11 +170,11 @@ public class BubbleStackView extends FrameLayout new SurfaceSynchronizer() { @Override public void syncSurfaceAndRun(Runnable callback) { Choreographer.getInstance().postFrameCallback(new Choreographer.FrameCallback() { // Just wait 2 frames. There is no guarantee, but this is usually enough time that // the requested change is reflected on the screen. // TODO: Once SurfaceFlinger provide APIs to sync the state of {@code View} and // surfaces, rewrite this logic with them. Choreographer.FrameCallback frameCallback = new Choreographer.FrameCallback() { // Just wait 2 frames. There is no guarantee, but this is usually enough // time that the requested change is reflected on the screen. // TODO: Once SurfaceFlinger provide APIs to sync the state of // {@code View} and surfaces, rewrite this logic with them. private int mFrameWait = 2; @Override Loading @@ -185,7 +185,8 @@ public class BubbleStackView extends FrameLayout callback.run(); } } }); }; Choreographer.getInstance().postFrameCallback(frameCallback); } }; private final BubbleController mBubbleController; Loading Loading @@ -1656,8 +1657,13 @@ public class BubbleStackView extends FrameLayout return; } } // If a bubble is suppressed, it is not attached to the container. Clean it up. if (bubble.isSuppressed()) { bubble.cleanupViews(); } else { Log.d(TAG, "was asked to remove Bubble, but didn't find the view! " + bubble); } } private void updateOverflowVisibility() { mBubbleOverflow.setVisible((mIsExpanded || mBubbleData.isShowingOverflow()) Loading Loading @@ -1842,11 +1848,30 @@ public class BubbleStackView extends FrameLayout } } void setBubbleVisibility(Bubble b, boolean visible) { if (b.getIconView() != null) { b.getIconView().setVisibility(visible ? VISIBLE : GONE); void setBubbleSuppressed(Bubble bubble, boolean suppressed) { if (DEBUG_BUBBLE_STACK_VIEW) { Log.d(TAG, "setBubbleSuppressed: suppressed=" + suppressed + " bubble=" + bubble); } if (suppressed) { int index = getBubbleIndex(bubble); mBubbleContainer.removeViewAt(index); updateExpandedView(); } else { if (bubble.getIconView() == null) { return; } if (bubble.getIconView().getParent() != null) { Log.e(TAG, "Bubble is already added to parent. Can't unsuppress: " + bubble); return; } int index = mBubbleData.getBubbles().indexOf(bubble); // Add the view back to the correct position mBubbleContainer.addView(bubble.getIconView(), index, new LayoutParams(mPositioner.getBubbleSize(), mPositioner.getBubbleSize())); updateBubbleShadows(false /* showForAllBubbles */); requestUpdate(); } // TODO(b/181166384): Animate in / out & handle adjusting how the bubbles overlap } /** Loading libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/PhysicsAnimationLayout.java +5 −0 Original line number Diff line number Diff line Loading @@ -364,6 +364,11 @@ public class PhysicsAnimationLayout extends FrameLayout { final int oldIndex = indexOfChild(view); super.removeView(view); if (view.getParent() != null) { // View still has a parent. This could have been added as a transient view. // Remove it from transient views. super.removeTransientView(view); } addViewInternal(view, index, view.getLayoutParams(), true /* isReorder */); if (mController != null) { Loading Loading
libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java +5 −0 Original line number Diff line number Diff line Loading @@ -367,4 +367,9 @@ public class BadgedImageView extends ConstraintLayout { void hideBadge() { mAppIcon.setVisibility(GONE); } @Override public String toString() { return "BadgedImageView{" + mBubble + "}"; } }
libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +36 −17 Original line number Diff line number Diff line Loading @@ -24,6 +24,7 @@ import static android.view.View.INVISIBLE; import static android.view.View.VISIBLE; import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_CONTROLLER; import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES; import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.wm.shell.bubbles.BubblePositioner.TASKBAR_POSITION_BOTTOM; Loading Loading @@ -997,6 +998,12 @@ public class BubbleController { // Update the bubble but don't promote it out of overflow Bubble b = mBubbleData.getOverflowBubbleWithKey(notif.getKey()); b.setEntry(notif); } else if (mBubbleData.isSuppressedWithLocusId(notif.getLocusId())) { // Update the bubble but don't promote it out of overflow Bubble b = mBubbleData.getSuppressedBubbleWithKey(notif.getKey()); if (b != null) { b.setEntry(notif); } } else { Bubble bubble = mBubbleData.getOrCreateBubble(notif, null /* persistedBubble */); inflateAndAdd(bubble, suppressFlyout, showInShade); Loading Loading @@ -1170,6 +1177,18 @@ public class BubbleController { @Override public void applyUpdate(BubbleData.Update update) { if (DEBUG_BUBBLE_CONTROLLER) { Log.d(TAG, "applyUpdate:" + " bubbleAdded=" + (update.addedBubble != null) + " bubbleRemoved=" + (update.removedBubbles != null && update.removedBubbles.size() > 0) + " bubbleUpdated=" + (update.updatedBubble != null) + " orderChanged=" + update.orderChanged + " expandedChanged=" + update.expandedChanged + " selectionChanged=" + update.selectionChanged + " suppressed=" + (update.suppressedBubble != null) + " unsuppressed=" + (update.unsuppressedBubble != null)); } ensureStackViewCreated(); // Lazy load overflow bubbles from disk Loading Loading @@ -1249,6 +1268,14 @@ public class BubbleController { mStackView.updateBubble(update.updatedBubble); } if (update.suppressedBubble != null && mStackView != null) { mStackView.setBubbleSuppressed(update.suppressedBubble, true); } if (update.unsuppressedBubble != null && mStackView != null) { mStackView.setBubbleSuppressed(update.unsuppressedBubble, false); } // At this point, the correct bubbles are inflated in the stack. // Make sure the order in bubble data is reflected in bubble row. if (update.orderChanged && mStackView != null) { Loading @@ -1263,14 +1290,6 @@ public class BubbleController { } } if (update.suppressedBubble != null && mStackView != null) { mStackView.setBubbleVisibility(update.suppressedBubble, false); } if (update.unsuppressedBubble != null && mStackView != null) { mStackView.setBubbleVisibility(update.unsuppressedBubble, true); } // Expanding? Apply this last. if (update.expandedChanged && update.expanded) { if (mStackView != null) { Loading
libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java +141 −28 Original line number Diff line number Diff line Loading @@ -224,7 +224,8 @@ public class BubbleData { } public boolean hasAnyBubbleWithKey(String key) { return hasBubbleInStackWithKey(key) || hasOverflowBubbleWithKey(key); return hasBubbleInStackWithKey(key) || hasOverflowBubbleWithKey(key) || hasSuppressedBubbleWithKey(key); } public boolean hasBubbleInStackWithKey(String key) { Loading @@ -235,6 +236,20 @@ public class BubbleData { return getOverflowBubbleWithKey(key) != null; } /** * Check if there are any bubbles suppressed with the given notification <code>key</code> */ public boolean hasSuppressedBubbleWithKey(String key) { return mSuppressedBubbles.values().stream().anyMatch(b -> b.getKey().equals(key)); } /** * Check if there are any bubbles suppressed with the given <code>LocusId</code> */ public boolean isSuppressedWithLocusId(LocusId locusId) { return mSuppressedBubbles.get(locusId) != null; } @Nullable public BubbleViewProvider getSelectedBubble() { return mSelectedBubble; Loading Loading @@ -356,11 +371,11 @@ public class BubbleData { boolean isSuppressed = mSuppressedBubbles.containsKey(locusId); if (isSuppressed && (!bubble.isSuppressed() || !bubble.isSuppressable())) { mSuppressedBubbles.remove(locusId); mStateChange.unsuppressedBubble = bubble; doUnsuppress(bubble); } else if (!isSuppressed && (bubble.isSuppressed() || bubble.isSuppressable() && mVisibleLocusIds.contains(locusId))) { mSuppressedBubbles.put(locusId, bubble); mStateChange.suppressedBubble = bubble; doSuppress(bubble); } } dispatchPendingChanges(); Loading Loading @@ -532,16 +547,19 @@ public class BubbleData { if (mPendingBubbles.containsKey(key)) { mPendingBubbles.remove(key); } int indexToRemove = indexForKey(key); if (indexToRemove == -1) { if (hasOverflowBubbleWithKey(key) && (reason == Bubbles.DISMISS_NOTIF_CANCEL boolean shouldRemoveHiddenBubble = reason == Bubbles.DISMISS_NOTIF_CANCEL || reason == Bubbles.DISMISS_GROUP_CANCELLED || reason == Bubbles.DISMISS_NO_LONGER_BUBBLE || reason == Bubbles.DISMISS_BLOCKED || reason == Bubbles.DISMISS_SHORTCUT_REMOVED || reason == Bubbles.DISMISS_PACKAGE_REMOVED || reason == Bubbles.DISMISS_USER_CHANGED)) { || reason == Bubbles.DISMISS_USER_CHANGED; int indexToRemove = indexForKey(key); if (indexToRemove == -1) { if (hasOverflowBubbleWithKey(key) && shouldRemoveHiddenBubble) { Bubble b = getOverflowBubbleWithKey(key); if (DEBUG_BUBBLE_DATA) { Loading @@ -555,6 +573,17 @@ public class BubbleData { mStateChange.bubbleRemoved(b, reason); mStateChange.removedOverflowBubble = b; } if (hasSuppressedBubbleWithKey(key) && shouldRemoveHiddenBubble) { Bubble b = getSuppressedBubbleWithKey(key); if (DEBUG_BUBBLE_DATA) { Log.d(TAG, "Cancel suppressed bubble: " + b); } if (b != null) { mSuppressedBubbles.remove(b.getLocusId()); b.stopInflation(); mStateChange.bubbleRemoved(b, reason); } } return; } Bubble bubbleToRemove = mBubbles.get(indexToRemove); Loading @@ -579,12 +608,66 @@ public class BubbleData { // Note: If mBubbles.isEmpty(), then mSelectedBubble is now null. if (Objects.equals(mSelectedBubble, bubbleToRemove)) { setNewSelectedIndex(indexToRemove); } maybeSendDeleteIntent(reason, bubbleToRemove); } private void setNewSelectedIndex(int indexOfSelected) { if (mBubbles.isEmpty()) { Log.w(TAG, "Bubbles list empty when attempting to select index: " + indexOfSelected); return; } // Move selection to the new bubble at the same position. int newIndex = Math.min(indexToRemove, mBubbles.size() - 1); int newIndex = Math.min(indexOfSelected, mBubbles.size() - 1); if (DEBUG_BUBBLE_DATA) { Log.d(TAG, "setNewSelectedIndex: " + indexOfSelected); } BubbleViewProvider newSelected = mBubbles.get(newIndex); setSelectedBubbleInternal(newSelected); } maybeSendDeleteIntent(reason, bubbleToRemove); private void doSuppress(Bubble bubble) { if (DEBUG_BUBBLE_DATA) { Log.d(TAG, "doSuppressed: " + bubble); } mStateChange.suppressedBubble = bubble; bubble.setSuppressBubble(true); int indexToRemove = mBubbles.indexOf(bubble); // Order changes if we are not suppressing the last bubble mStateChange.orderChanged = !(mBubbles.size() - 1 == indexToRemove); mBubbles.remove(indexToRemove); // Update selection if we suppressed the selected bubble if (Objects.equals(mSelectedBubble, bubble)) { if (mBubbles.isEmpty()) { // Don't use setSelectedBubbleInternal because we don't want to trigger an // applyUpdate mSelectedBubble = null; } else { // Mark new first bubble as selected setNewSelectedIndex(0); } } } private void doUnsuppress(Bubble bubble) { if (DEBUG_BUBBLE_DATA) { Log.d(TAG, "doUnsuppressed: " + bubble); } bubble.setSuppressBubble(false); mStateChange.unsuppressedBubble = bubble; mBubbles.add(bubble); if (mBubbles.size() > 1) { // See where the bubble actually lands repackAll(); mStateChange.orderChanged = true; } if (mBubbles.get(0) == bubble) { // Unsuppressed bubble is sorted to first position. Mark it as the selected. setNewSelectedIndex(0); } } void overflowBubble(@DismissReason int reason, Bubble bubble) { Loading Loading @@ -619,7 +702,7 @@ public class BubbleData { if (DEBUG_BUBBLE_DATA) { Log.d(TAG, "dismissAll: reason=" + reason); } if (mBubbles.isEmpty()) { if (mBubbles.isEmpty() && mSuppressedBubbles.isEmpty()) { return; } setExpandedInternal(false); Loading @@ -627,6 +710,10 @@ public class BubbleData { while (!mBubbles.isEmpty()) { doRemove(mBubbles.get(0).getKey(), reason); } while (!mSuppressedBubbles.isEmpty()) { Bubble bubble = mSuppressedBubbles.removeAt(0); doRemove(bubble.getKey(), reason); } dispatchPendingChanges(); } Loading @@ -640,6 +727,10 @@ public class BubbleData { * @param visible whether the task with the locusId is visible or not. */ public void onLocusVisibilityChanged(int taskId, LocusId locusId, boolean visible) { if (DEBUG_BUBBLE_DATA) { Log.d(TAG, "onLocusVisibilityChanged: " + locusId + " visible=" + visible); } Bubble matchingBubble = getBubbleInStackWithLocusId(locusId); // Don't add the locus if it's from a bubble'd activity, we only suppress for non-bubbled. if (visible && (matchingBubble == null || matchingBubble.getTaskId() != taskId)) { Loading @@ -647,21 +738,23 @@ public class BubbleData { } else { mVisibleLocusIds.remove(locusId); } if (matchingBubble == null) { // Check if there is a suppressed bubble for this LocusId matchingBubble = mSuppressedBubbles.get(locusId); if (matchingBubble == null) { return; } } boolean isAlreadySuppressed = mSuppressedBubbles.get(locusId) != null; if (visible && !isAlreadySuppressed && matchingBubble.isSuppressable() && taskId != matchingBubble.getTaskId()) { mSuppressedBubbles.put(locusId, matchingBubble); matchingBubble.setSuppressBubble(true); mStateChange.suppressedBubble = matchingBubble; doSuppress(matchingBubble); dispatchPendingChanges(); } else if (!visible) { Bubble unsuppressedBubble = mSuppressedBubbles.remove(locusId); if (unsuppressedBubble != null) { unsuppressedBubble.setSuppressBubble(false); mStateChange.unsuppressedBubble = unsuppressedBubble; doUnsuppress(unsuppressedBubble); } dispatchPendingChanges(); } Loading Loading @@ -869,6 +962,9 @@ public class BubbleData { if (b == null) { b = getOverflowBubbleWithKey(key); } if (b == null) { b = getSuppressedBubbleWithKey(key); } return b; } Loading Loading @@ -946,6 +1042,23 @@ public class BubbleData { return null; } /** * Get a suppressed bubble with given notification <code>key</code> * * @param key notification key * @return bubble that matches or null */ @Nullable @VisibleForTesting(visibility = PRIVATE) public Bubble getSuppressedBubbleWithKey(String key) { for (Bubble b : mSuppressedBubbles.values()) { if (b.getKey().equals(key)) { return b; } } return null; } @VisibleForTesting(visibility = PRIVATE) void setTimeSource(TimeSource timeSource) { mTimeSource = timeSource; Loading
libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java +55 −30 Original line number Diff line number Diff line Loading @@ -170,11 +170,11 @@ public class BubbleStackView extends FrameLayout new SurfaceSynchronizer() { @Override public void syncSurfaceAndRun(Runnable callback) { Choreographer.getInstance().postFrameCallback(new Choreographer.FrameCallback() { // Just wait 2 frames. There is no guarantee, but this is usually enough time that // the requested change is reflected on the screen. // TODO: Once SurfaceFlinger provide APIs to sync the state of {@code View} and // surfaces, rewrite this logic with them. Choreographer.FrameCallback frameCallback = new Choreographer.FrameCallback() { // Just wait 2 frames. There is no guarantee, but this is usually enough // time that the requested change is reflected on the screen. // TODO: Once SurfaceFlinger provide APIs to sync the state of // {@code View} and surfaces, rewrite this logic with them. private int mFrameWait = 2; @Override Loading @@ -185,7 +185,8 @@ public class BubbleStackView extends FrameLayout callback.run(); } } }); }; Choreographer.getInstance().postFrameCallback(frameCallback); } }; private final BubbleController mBubbleController; Loading Loading @@ -1656,8 +1657,13 @@ public class BubbleStackView extends FrameLayout return; } } // If a bubble is suppressed, it is not attached to the container. Clean it up. if (bubble.isSuppressed()) { bubble.cleanupViews(); } else { Log.d(TAG, "was asked to remove Bubble, but didn't find the view! " + bubble); } } private void updateOverflowVisibility() { mBubbleOverflow.setVisible((mIsExpanded || mBubbleData.isShowingOverflow()) Loading Loading @@ -1842,11 +1848,30 @@ public class BubbleStackView extends FrameLayout } } void setBubbleVisibility(Bubble b, boolean visible) { if (b.getIconView() != null) { b.getIconView().setVisibility(visible ? VISIBLE : GONE); void setBubbleSuppressed(Bubble bubble, boolean suppressed) { if (DEBUG_BUBBLE_STACK_VIEW) { Log.d(TAG, "setBubbleSuppressed: suppressed=" + suppressed + " bubble=" + bubble); } if (suppressed) { int index = getBubbleIndex(bubble); mBubbleContainer.removeViewAt(index); updateExpandedView(); } else { if (bubble.getIconView() == null) { return; } if (bubble.getIconView().getParent() != null) { Log.e(TAG, "Bubble is already added to parent. Can't unsuppress: " + bubble); return; } int index = mBubbleData.getBubbles().indexOf(bubble); // Add the view back to the correct position mBubbleContainer.addView(bubble.getIconView(), index, new LayoutParams(mPositioner.getBubbleSize(), mPositioner.getBubbleSize())); updateBubbleShadows(false /* showForAllBubbles */); requestUpdate(); } // TODO(b/181166384): Animate in / out & handle adjusting how the bubbles overlap } /** Loading
libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/PhysicsAnimationLayout.java +5 −0 Original line number Diff line number Diff line Loading @@ -364,6 +364,11 @@ public class PhysicsAnimationLayout extends FrameLayout { final int oldIndex = indexOfChild(view); super.removeView(view); if (view.getParent() != null) { // View still has a parent. This could have been added as a transient view. // Remove it from transient views. super.removeTransientView(view); } addViewInternal(view, index, view.getLayoutParams(), true /* isReorder */); if (mController != null) { Loading