diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index ac156ade57914..ce8ce51b1d5ea 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -102,7 +102,7 @@
16dp
- 8dp
+ 3dp
60dp
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index 7dcedfe50b37f..a91bf046965de 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -710,6 +710,7 @@ public class BubbleController {
|| !newConfig.windowConfiguration.getBounds().equals(mScreenBounds)) {
mDensityDpi = newConfig.densityDpi;
mScreenBounds.set(newConfig.windowConfiguration.getBounds());
+ mBubbleData.onMaxBubblesChanged();
mBubbleIconFactory = new BubbleIconFactory(mContext);
mStackView.onDisplaySizeChanged();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
index 6f526ecf2b62e..69a741c674dbe 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
@@ -141,9 +141,11 @@ public class BubbleData {
private final BubbleOverflow mOverflow;
private boolean mShowingOverflow;
private boolean mExpanded;
- private final int mMaxBubbles;
+ private int mMaxBubbles;
private int mMaxOverflowBubbles;
+ private boolean mNeedsTrimming;
+
// State tracked during an operation -- keeps track of what listener events to dispatch.
private Update mStateChange;
@@ -180,7 +182,7 @@ public class BubbleData {
mOverflowBubbles = new ArrayList<>();
mPendingBubbles = new HashMap<>();
mStateChange = new Update(mBubbles, mOverflowBubbles);
- mMaxBubbles = mContext.getResources().getInteger(R.integer.bubbles_max_rendered);
+ mMaxBubbles = mPositioner.getMaxBubbles();
mMaxOverflowBubbles = mContext.getResources().getInteger(R.integer.bubbles_max_overflow);
}
@@ -194,6 +196,16 @@ public class BubbleData {
mCancelledListener = listener;
}
+ public void onMaxBubblesChanged() {
+ mMaxBubbles = mPositioner.getMaxBubbles();
+ if (!mExpanded) {
+ trim();
+ dispatchPendingChanges();
+ } else {
+ mNeedsTrimming = true;
+ }
+ }
+
public boolean hasBubbles() {
return !mBubbles.isEmpty();
}
@@ -455,13 +467,19 @@ public class BubbleData {
private void trim() {
if (mBubbles.size() > mMaxBubbles) {
+ int numtoRemove = mBubbles.size() - mMaxBubbles;
+ ArrayList toRemove = new ArrayList<>();
mBubbles.stream()
// sort oldest first (ascending lastActivity)
.sorted(Comparator.comparingLong(Bubble::getLastActivity))
// skip the selected bubble
.filter((b) -> !b.equals(mSelectedBubble))
- .findFirst()
- .ifPresent((b) -> doRemove(b.getKey(), Bubbles.DISMISS_AGED));
+ .forEachOrdered((b) -> {
+ if (toRemove.size() < numtoRemove) {
+ toRemove.add(b);
+ }
+ });
+ toRemove.forEach((b) -> doRemove(b.getKey(), Bubbles.DISMISS_AGED));
}
}
@@ -770,6 +788,10 @@ public class BubbleData {
}
}
}
+ if (mNeedsTrimming) {
+ mNeedsTrimming = false;
+ trim();
+ }
mExpanded = shouldExpand;
mStateChange.expanded = shouldExpand;
mStateChange.expandedChanged = true;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
index 62df2cb69da99..c600f56ba0c5a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
@@ -64,9 +64,12 @@ public class BubblePositioner {
private Rect mPositionRect;
private @Surface.Rotation int mRotation = Surface.ROTATION_0;
private Insets mInsets;
+ private int mDefaultMaxBubbles;
+ private int mMaxBubbles;
private int mBubbleSize;
private int mBubbleBadgeSize;
+ private int mSpacingBetweenBubbles;
private int mExpandedViewLargeScreenWidth;
private int mExpandedViewPadding;
private int mPointerMargin;
@@ -149,17 +152,45 @@ public class BubblePositioner {
Resources res = mContext.getResources();
mBubbleSize = res.getDimensionPixelSize(R.dimen.bubble_size);
mBubbleBadgeSize = res.getDimensionPixelSize(R.dimen.bubble_badge_size);
+ mSpacingBetweenBubbles = res.getDimensionPixelSize(R.dimen.bubble_spacing);
+ mDefaultMaxBubbles = res.getInteger(R.integer.bubbles_max_rendered);
+
mExpandedViewLargeScreenWidth = res.getDimensionPixelSize(
R.dimen.bubble_expanded_view_tablet_width);
mExpandedViewPadding = res.getDimensionPixelSize(R.dimen.bubble_expanded_view_padding);
mPointerWidth = res.getDimensionPixelSize(R.dimen.bubble_pointer_width);
mPointerHeight = res.getDimensionPixelSize(R.dimen.bubble_pointer_height);
mPointerMargin = res.getDimensionPixelSize(R.dimen.bubble_pointer_margin);
+
+ mMaxBubbles = calculateMaxBubbles();
+
if (mShowingInTaskbar) {
adjustForTaskbar();
}
}
+ /**
+ * @return the maximum number of bubbles that can fit on the screen when expanded. If the
+ * screen size / screen density is too small to support the default maximum number, then
+ * the number will be adjust to something lower to ensure everything is presented nicely.
+ */
+ private int calculateMaxBubbles() {
+ // Use the shortest edge.
+ // In portrait the bubbles should align with the expanded view so subtract its padding.
+ // We always show the overflow so subtract one bubble size.
+ int padding = showBubblesVertically() ? 0 : (mExpandedViewPadding * 2);
+ int availableSpace = Math.min(mPositionRect.width(), mPositionRect.height())
+ - padding
+ - mBubbleSize;
+ // Each of the bubbles have spacing because the overflow is at the end.
+ int howManyFit = availableSpace / (mBubbleSize + mSpacingBetweenBubbles);
+ if (howManyFit < mDefaultMaxBubbles) {
+ // Not enough space for the default.
+ return howManyFit;
+ }
+ return mDefaultMaxBubbles;
+ }
+
/**
* Taskbar insets appear as navigationBar insets, however, unlike navigationBar this should
* not inset bubbles UI as bubbles floats above the taskbar. This adjust the available space
@@ -229,6 +260,11 @@ public class BubblePositioner {
: mBubbleSize;
}
+ /** The maximum number of bubbles that can be displayed comfortably on screen. */
+ public int getMaxBubbles() {
+ return mMaxBubbles;
+ }
+
/**
* Calculates the left & right padding for the bubble expanded view.
*
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index ee822109f40ca..3680071f6b320 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -243,7 +243,6 @@ public class BubbleStackView extends FrameLayout
@Nullable private RelativeStackPosition mRelativeStackPositionBeforeRotation;
- private int mMaxBubbles;
private int mBubbleSize;
private int mBubbleElevation;
private int mBubbleTouchPadding;
@@ -764,7 +763,6 @@ public class BubbleStackView extends FrameLayout
mBubbleData = data;
Resources res = getResources();
- mMaxBubbles = res.getInteger(R.integer.bubbles_max_rendered);
mBubbleSize = res.getDimensionPixelSize(R.dimen.bubble_size);
mBubbleElevation = res.getDimensionPixelSize(R.dimen.bubble_elevation);
mBubbleTouchPadding = res.getDimensionPixelSize(R.dimen.bubble_touch_padding);
@@ -2725,7 +2723,7 @@ public class BubbleStackView extends FrameLayout
private void updateBubbleShadows(boolean showForAllBubbles) {
int bubbleCount = getBubbleCount();
for (int i = 0; i < bubbleCount; i++) {
- final float z = (mMaxBubbles * mBubbleElevation) - i;
+ final float z = (mPositioner.getMaxBubbles() * mBubbleElevation) - i;
BadgedImageView bv = (BadgedImageView) mBubbleContainer.getChildAt(i);
boolean isDraggedOut = mMagnetizedObject != null
&& mMagnetizedObject.getUnderlyingObject().equals(bv);
@@ -2759,7 +2757,7 @@ public class BubbleStackView extends FrameLayout
for (int i = 0; i < bubbleCount; i++) {
BadgedImageView bv = (BadgedImageView) mBubbleContainer.getChildAt(i);
bv.setZ(i < NUM_VISIBLE_WHEN_RESTING
- ? (mMaxBubbles * mBubbleElevation) - i
+ ? (mPositioner.getMaxBubbles() * mBubbleElevation) - i
: 0f);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java
index 9dfd8c591db55..df2b440c19df4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java
@@ -211,16 +211,8 @@ public class ExpandedAnimationController
mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top);
mStackOffsetPx = res.getDimensionPixelSize(R.dimen.bubble_stack_offset);
mBubbleSizePx = mPositioner.getBubbleSize();
- mBubblesMaxRendered = res.getInteger(R.integer.bubbles_max_rendered);
- mBubblesMaxSpace = res.getDimensionPixelSize(R.dimen.bubble_spacing);
- final float availableSpace = mPositioner.isLandscape()
- ? mPositioner.getAvailableRect().height()
- : mPositioner.getAvailableRect().width();
- final float spaceForMaxBubbles = (mExpandedViewPadding * 2)
- + (mBubblesMaxRendered + 1) * mBubbleSizePx;
- float spaceBetweenBubbles =
- (availableSpace - spaceForMaxBubbles) / mBubblesMaxRendered;
- mSpaceBetweenBubbles = Math.min(spaceBetweenBubbles, mBubblesMaxSpace);
+ mBubblesMaxRendered = mPositioner.getMaxBubbles();
+ mSpaceBetweenBubbles = res.getDimensionPixelSize(R.dimen.bubble_spacing);
}
/**
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
index 2bb7204c79411..3e3195fe8dc55 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
@@ -19,9 +19,12 @@ package com.android.wm.shell.bubbles;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
+import static junit.framework.Assert.assertNotNull;
import static junit.framework.TestCase.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
@@ -90,6 +93,7 @@ public class BubbleDataTest extends ShellTestCase {
private Bubble mBubbleDismissed;
private BubbleData mBubbleData;
+ private TestableBubblePositioner mPositioner;
@Mock
private TimeSource mTimeSource;
@@ -150,9 +154,9 @@ public class BubbleDataTest extends ShellTestCase {
mMainExecutor);
mBubbleC1 = new Bubble(mEntryC1, mSuppressionListener, mPendingIntentCanceledListener,
mMainExecutor);
- TestableBubblePositioner positioner = new TestableBubblePositioner(mContext,
+ mPositioner = new TestableBubblePositioner(mContext,
mock(WindowManager.class));
- mBubbleData = new BubbleData(getContext(), mBubbleLogger, positioner,
+ mBubbleData = new BubbleData(getContext(), mBubbleLogger, mPositioner,
mMainExecutor);
// Used by BubbleData to set lastAccessedTime
@@ -810,6 +814,61 @@ public class BubbleDataTest extends ShellTestCase {
assertEquals(1, mBubbleData.getOverflowBubbles().size());
}
+ @Test
+ public void test_onMaxBubblesChanged_notExpanded() {
+ mBubbleData.setListener(mListener);
+ mPositioner.setMaxBubbles(5);
+ sendUpdatedEntryAtTime(mEntryA1, 1000);
+ sendUpdatedEntryAtTime(mEntryA2, 2000);
+ sendUpdatedEntryAtTime(mEntryA3, 3000);
+ sendUpdatedEntryAtTime(mEntryB1, 4000);
+ sendUpdatedEntryAtTime(mEntryB2, 5000);
+ mBubbleData.setExpanded(false);
+ reset(mListener);
+
+ mPositioner.setMaxBubbles(3);
+ mBubbleData.onMaxBubblesChanged();
+ verifyUpdateReceived();
+
+ BubbleData.Update update = mUpdateCaptor.getValue();
+ assertThat(update.removedBubbles.get(0)).isEqualTo(
+ Pair.create(mBubbleA1, Bubbles.DISMISS_AGED));
+ assertThat(update.removedBubbles.get(1)).isEqualTo(
+ Pair.create(mBubbleA2, Bubbles.DISMISS_AGED));
+
+ assertNotNull(mBubbleData.getOverflowBubbleWithKey(mBubbleA1.getKey()));
+ assertNotNull(mBubbleData.getOverflowBubbleWithKey(mBubbleA2.getKey()));
+ }
+
+ @Test
+ public void test_onMaxBubblesChanged_expanded() {
+ mBubbleData.setListener(mListener);
+ mPositioner.setMaxBubbles(5);
+ sendUpdatedEntryAtTime(mEntryA1, 1000);
+ sendUpdatedEntryAtTime(mEntryA2, 2000);
+ sendUpdatedEntryAtTime(mEntryA3, 3000);
+ sendUpdatedEntryAtTime(mEntryB1, 4000);
+ sendUpdatedEntryAtTime(mEntryB2, 5000);
+ mBubbleData.setExpanded(true);
+ reset(mListener);
+
+ mPositioner.setMaxBubbles(3);
+ mBubbleData.onMaxBubblesChanged();
+ verify(mListener, never()).applyUpdate(any());
+
+ mBubbleData.setExpanded(false);
+ verifyUpdateReceived();
+
+ BubbleData.Update update = mUpdateCaptor.getValue();
+ assertThat(update.removedBubbles.get(0)).isEqualTo(
+ Pair.create(mBubbleA1, Bubbles.DISMISS_AGED));
+ assertThat(update.removedBubbles.get(1)).isEqualTo(
+ Pair.create(mBubbleA2, Bubbles.DISMISS_AGED));
+
+ assertNotNull(mBubbleData.getOverflowBubbleWithKey(mBubbleA1.getKey()));
+ assertNotNull(mBubbleData.getOverflowBubbleWithKey(mBubbleA2.getKey()));
+ }
+
private void verifyUpdateReceived() {
verify(mListener).applyUpdate(mUpdateCaptor.capture());
reset(mListener);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/TestableBubblePositioner.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/TestableBubblePositioner.java
index 96bc5335a32cb..9027ca442e2c1 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/TestableBubblePositioner.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/TestableBubblePositioner.java
@@ -22,7 +22,10 @@ import android.graphics.Insets;
import android.graphics.Rect;
import android.view.WindowManager;
+import com.android.wm.shell.R;
+
public class TestableBubblePositioner extends BubblePositioner {
+ private int mMaxBubbles;
public TestableBubblePositioner(Context context,
WindowManager windowManager) {
@@ -31,5 +34,15 @@ public class TestableBubblePositioner extends BubblePositioner {
updateInternal(Configuration.ORIENTATION_PORTRAIT,
Insets.of(0, 0, 0, 0),
new Rect(0, 0, 500, 1000));
+ mMaxBubbles = context.getResources().getInteger(R.integer.bubbles_max_rendered);
+ }
+
+ public void setMaxBubbles(int max) {
+ mMaxBubbles = max;
+ }
+
+ @Override
+ public int getMaxBubbles() {
+ return mMaxBubbles;
}
}