Fix dot/icon overlap in notification shelf
This bug was caused by an off-by-one error where
isOverflowing = iconX > overflowX
should have been
isOverflowing = iconX >= overflowX
As a result, the icon that should have been hidden
by the dot was considered "not overflowing"
(iconX=overflowX) so it remained visible.
This change corrects the definition of isOverflowing;
refactors isOverflowing and forceOverflow out of
calculateIconXTranslations; adds tests.
Bug: 232056690
Test: NotificationIconContainer
Test: add notifs to short shelf until overflow
swipe notifs away until short shelf empty
(no dot/icon overlap)
Test: swipe down on ls notif then let go
swipe down on ls notif to go to full shade
add notifs/swipe away notifs in full shade
scroll in full shade with long shelf
(no regressions)
Change-Id: Ie270c55f6c9d27b5d0fee73c22fedb5335760179
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
index a2140c6ab..7b8c5fc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
@@ -423,6 +423,21 @@
+ getActualPaddingEnd();
}
+ @VisibleForTesting
+ boolean shouldForceOverflow(int i, int speedBumpIndex, float iconAppearAmount,
+ int maxVisibleIcons) {
+ return speedBumpIndex != -1 && i >= speedBumpIndex
+ && iconAppearAmount > 0.0f || i >= maxVisibleIcons;
+ }
+
+ @VisibleForTesting
+ boolean isOverflowing(boolean isLastChild, float translationX, float layoutEnd,
+ float iconSize) {
+ // Layout end, as used here, does not include padding end.
+ final float overflowX = isLastChild ? layoutEnd : layoutEnd - iconSize;
+ return translationX >= overflowX;
+ }
+
/**
* Calculate the horizontal translations for each notification based on how much the icons
* are inserted into the notification container.
@@ -448,26 +463,26 @@
if (mFirstVisibleIconState == null) {
mFirstVisibleIconState = iconState;
}
- boolean forceOverflow = mSpeedBumpIndex != -1 && i >= mSpeedBumpIndex
- && iconState.iconAppearAmount > 0.0f || i >= maxVisibleIcons;
- boolean isLastChild = i == childCount - 1;
- float drawingScale = mOnLockScreen && view instanceof StatusBarIconView
- ? ((StatusBarIconView) view).getIconScaleIncreased()
- : 1f;
iconState.visibleState = iconState.hidden
? StatusBarIconView.STATE_HIDDEN
: StatusBarIconView.STATE_ICON;
- final float overflowDotX = layoutEnd - mIconSize;
- boolean isOverflowing = translationX > overflowDotX;
+ final boolean forceOverflow = shouldForceOverflow(i, mSpeedBumpIndex,
+ iconState.iconAppearAmount, maxVisibleIcons);
+ final boolean isOverflowing = forceOverflow || isOverflowing(
+ /* isLastChild= */ i == childCount - 1, translationX, layoutEnd, mIconSize);
- if (firstOverflowIndex == -1 && (forceOverflow || isOverflowing)) {
- firstOverflowIndex = isLastChild && !forceOverflow ? i - 1 : i;
+ // First icon to overflow.
+ if (firstOverflowIndex == -1 && isOverflowing) {
+ firstOverflowIndex = i;
mVisualOverflowStart = layoutEnd - mIconSize;
if (forceOverflow || mIsStaticLayout) {
mVisualOverflowStart = Math.min(translationX, mVisualOverflowStart);
}
}
+ final float drawingScale = mOnLockScreen && view instanceof StatusBarIconView
+ ? ((StatusBarIconView) view).getIconScaleIncreased()
+ : 1f;
translationX += iconState.iconAppearAmount * view.getWidth() * drawingScale;
}
mNumDots = 0;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt
index 2ff6dd4..086e5df 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt
@@ -153,6 +153,106 @@
assertTrue(iconContainer.hasOverflow())
}
+ @Test
+ fun shouldForceOverflow_appearingAboveSpeedBump_true() {
+ val forceOverflow = iconContainer.shouldForceOverflow(
+ /* i= */ 1,
+ /* speedBumpIndex= */ 0,
+ /* iconAppearAmount= */ 1f,
+ /* maxVisibleIcons= */ 5
+ )
+ assertTrue(forceOverflow);
+ }
+
+ @Test
+ fun shouldForceOverflow_moreThanMaxVisible_true() {
+ val forceOverflow = iconContainer.shouldForceOverflow(
+ /* i= */ 10,
+ /* speedBumpIndex= */ 11,
+ /* iconAppearAmount= */ 0f,
+ /* maxVisibleIcons= */ 5
+ )
+ assertTrue(forceOverflow);
+ }
+
+ @Test
+ fun shouldForceOverflow_belowSpeedBumpAndLessThanMaxVisible_false() {
+ val forceOverflow = iconContainer.shouldForceOverflow(
+ /* i= */ 0,
+ /* speedBumpIndex= */ 11,
+ /* iconAppearAmount= */ 0f,
+ /* maxVisibleIcons= */ 5
+ )
+ assertFalse(forceOverflow);
+ }
+
+ @Test
+ fun isOverflowing_lastChildXLessThanLayoutEnd_false() {
+ val isOverflowing = iconContainer.isOverflowing(
+ /* isLastChild= */ true,
+ /* translationX= */ 0f,
+ /* layoutEnd= */ 10f,
+ /* iconSize= */ 2f,
+ )
+ assertFalse(isOverflowing)
+ }
+
+
+ @Test
+ fun isOverflowing_lastChildXEqualToLayoutEnd_true() {
+ val isOverflowing = iconContainer.isOverflowing(
+ /* isLastChild= */ true,
+ /* translationX= */ 10f,
+ /* layoutEnd= */ 10f,
+ /* iconSize= */ 2f,
+ )
+ assertTrue(isOverflowing)
+ }
+
+ @Test
+ fun isOverflowing_lastChildXGreaterThanLayoutEnd_true() {
+ val isOverflowing = iconContainer.isOverflowing(
+ /* isLastChild= */ true,
+ /* translationX= */ 20f,
+ /* layoutEnd= */ 10f,
+ /* iconSize= */ 2f,
+ )
+ assertTrue(isOverflowing)
+ }
+
+ @Test
+ fun isOverflowing_notLastChildXLessThanDotX_false() {
+ val isOverflowing = iconContainer.isOverflowing(
+ /* isLastChild= */ false,
+ /* translationX= */ 0f,
+ /* layoutEnd= */ 10f,
+ /* iconSize= */ 2f,
+ )
+ assertFalse(isOverflowing)
+ }
+
+ @Test
+ fun isOverflowing_notLastChildXGreaterThanDotX_true() {
+ val isOverflowing = iconContainer.isOverflowing(
+ /* isLastChild= */ false,
+ /* translationX= */ 20f,
+ /* layoutEnd= */ 10f,
+ /* iconSize= */ 2f,
+ )
+ assertTrue(isOverflowing)
+ }
+
+ @Test
+ fun isOverflowing_notLastChildXEqualToDotX_true() {
+ val isOverflowing = iconContainer.isOverflowing(
+ /* isLastChild= */ false,
+ /* translationX= */ 8f,
+ /* layoutEnd= */ 10f,
+ /* iconSize= */ 2f,
+ )
+ assertTrue(isOverflowing)
+ }
+
private fun mockStatusBarIcon() : StatusBarIconView {
val iconView = mock(StatusBarIconView::class.java)
whenever(iconView.width).thenReturn(10)