Merge "Fix notification shelf issues" into tm-dev

This commit is contained in:
Lyn Han
2022-04-28 16:19:15 +00:00
committed by Android (Google) Code Review
6 changed files with 213 additions and 45 deletions

View File

@@ -39,7 +39,6 @@ import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.statusbar.notification.row.ActivatableNotificationView; import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView; import com.android.systemui.statusbar.notification.row.ExpandableView;
import com.android.systemui.statusbar.notification.row.NotificationBackgroundView;
import com.android.systemui.statusbar.notification.stack.AmbientState; import com.android.systemui.statusbar.notification.stack.AmbientState;
import com.android.systemui.statusbar.notification.stack.AnimationProperties; import com.android.systemui.statusbar.notification.stack.AnimationProperties;
import com.android.systemui.statusbar.notification.stack.ExpandableViewState; import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
@@ -411,7 +410,7 @@ public class NotificationShelf extends ActivatableNotificationView implements
setBackgroundTop(backgroundTop); setBackgroundTop(backgroundTop);
setFirstElementRoundness(firstElementRoundness); setFirstElementRoundness(firstElementRoundness);
mShelfIcons.setSpeedBumpIndex(mHostLayoutController.getSpeedBumpIndex()); mShelfIcons.setSpeedBumpIndex(mHostLayoutController.getSpeedBumpIndex());
mShelfIcons.calculateIconTranslations(); mShelfIcons.calculateIconXTranslations();
mShelfIcons.applyIconStates(); mShelfIcons.applyIconStates();
for (int i = 0; i < mHostLayoutController.getChildCount(); i++) { for (int i = 0; i < mHostLayoutController.getChildCount(); i++) {
View child = mHostLayoutController.getChildAt(i); View child = mHostLayoutController.getChildAt(i);
@@ -636,7 +635,7 @@ public class NotificationShelf extends ActivatableNotificationView implements
float viewEnd = viewStart + fullHeight; float viewEnd = viewStart + fullHeight;
float fullTransitionAmount = 0.0f; float fullTransitionAmount = 0.0f;
float iconTransitionAmount = 0.0f; float iconTransitionAmount = 0.0f;
float shelfStart = getTranslationY(); float shelfStart = getTranslationY() - mPaddingBetweenElements;
if (mAmbientState.isExpansionChanging() && !mAmbientState.isOnKeyguard()) { if (mAmbientState.isExpansionChanging() && !mAmbientState.isOnKeyguard()) {
// TODO(b/172289889) handle icon placement for notification that is clipped by the shelf // TODO(b/172289889) handle icon placement for notification that is clipped by the shelf
if (mIndexOfFirstViewInShelf != -1 && i >= mIndexOfFirstViewInShelf) { if (mIndexOfFirstViewInShelf != -1 && i >= mIndexOfFirstViewInShelf) {

View File

@@ -207,9 +207,7 @@ constructor(
visibleIndex: Int visibleIndex: Int
): Float { ): Float {
var height = stack.calculateGapHeight(previous, current, visibleIndex) var height = stack.calculateGapHeight(previous, current, visibleIndex)
if (visibleIndex != 0) { height += dividerHeight
height += dividerHeight
}
return height return height
} }

View File

@@ -314,7 +314,8 @@ public class StackScrollAlgorithm {
if (ambientState.getShelf() != null) { if (ambientState.getShelf() != null) {
final float shelfStart = ambientState.getStackEndHeight() final float shelfStart = ambientState.getStackEndHeight()
- ambientState.getShelf().getIntrinsicHeight(); - ambientState.getShelf().getIntrinsicHeight()
- mPaddingBetweenElements;
if (currentY >= shelfStart if (currentY >= shelfStart
&& !(view instanceof FooterView) && !(view instanceof FooterView)
&& state.firstViewInShelf == null) { && state.firstViewInShelf == null) {
@@ -507,8 +508,9 @@ public class StackScrollAlgorithm {
|| bypassPulseNotExpanding || bypassPulseNotExpanding
? ambientState.getInnerHeight() ? ambientState.getInnerHeight()
: (int) ambientState.getStackHeight(); : (int) ambientState.getStackHeight();
final int shelfStart = final int shelfStart = stackBottom
stackBottom - ambientState.getShelf().getIntrinsicHeight(); - ambientState.getShelf().getIntrinsicHeight()
- mPaddingBetweenElements;
viewState.yTranslation = Math.min(viewState.yTranslation, shelfStart); viewState.yTranslation = Math.min(viewState.yTranslation, shelfStart);
if (viewState.yTranslation >= shelfStart) { if (viewState.yTranslation >= shelfStart) {
viewState.hidden = !view.isExpandAnimationRunning() viewState.hidden = !view.isExpandAnimationRunning()

View File

@@ -27,11 +27,13 @@ import android.graphics.Paint;
import android.graphics.Rect; import android.graphics.Rect;
import android.graphics.drawable.Icon; import android.graphics.drawable.Icon;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.util.MathUtils;
import android.util.Property; import android.util.Property;
import android.view.ContextThemeWrapper; import android.view.ContextThemeWrapper;
import android.view.View; import android.view.View;
import android.view.animation.Interpolator; import android.view.animation.Interpolator;
import androidx.annotation.VisibleForTesting;
import androidx.collection.ArrayMap; import androidx.collection.ArrayMap;
import com.android.internal.statusbar.StatusBarIcon; import com.android.internal.statusbar.StatusBarIcon;
@@ -136,6 +138,8 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout {
}.setDuration(CONTENT_FADE_DURATION); }.setDuration(CONTENT_FADE_DURATION);
private static final int MAX_ICONS_ON_AOD = 3; private static final int MAX_ICONS_ON_AOD = 3;
/* Maximum number of icons in short shelf on lockscreen when also showing overflow dot. */
public static final int MAX_ICONS_ON_LOCKSCREEN = 3; public static final int MAX_ICONS_ON_LOCKSCREEN = 3;
public static final int MAX_STATIC_ICONS = 4; public static final int MAX_STATIC_ICONS = 4;
private static final int MAX_DOTS = 1; private static final int MAX_DOTS = 1;
@@ -145,7 +149,6 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout {
private int mDotPadding; private int mDotPadding;
private int mStaticDotRadius; private int mStaticDotRadius;
private int mStaticDotDiameter; private int mStaticDotDiameter;
private int mOverflowWidth;
private int mActualLayoutWidth = NO_VALUE; private int mActualLayoutWidth = NO_VALUE;
private float mActualPaddingEnd = NO_VALUE; private float mActualPaddingEnd = NO_VALUE;
private float mActualPaddingStart = NO_VALUE; private float mActualPaddingStart = NO_VALUE;
@@ -219,10 +222,6 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout {
paint.setColor(Color.RED); paint.setColor(Color.RED);
canvas.drawLine(mVisualOverflowStart, 0, mVisualOverflowStart, height, paint); canvas.drawLine(mVisualOverflowStart, 0, mVisualOverflowStart, height, paint);
paint.setColor(Color.YELLOW);
float overflow = getMaxOverflowStart();
canvas.drawLine(overflow, 0, overflow, height, paint);
} }
} }
@@ -255,14 +254,14 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout {
} }
} }
private void setIconSize(int size) { @VisibleForTesting
public void setIconSize(int size) {
mIconSize = size; mIconSize = size;
mOverflowWidth = mIconSize + (MAX_DOTS - 1) * (mStaticDotDiameter + mDotPadding);
} }
private void updateState() { private void updateState() {
resetViewStates(); resetViewStates();
calculateIconTranslations(); calculateIconXTranslations();
applyIconStates(); applyIconStates();
} }
@@ -390,12 +389,11 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout {
* @return Width of shelf for the given number of icons * @return Width of shelf for the given number of icons
*/ */
public float calculateWidthFor(float numIcons) { public float calculateWidthFor(float numIcons) {
if (getChildCount() == 0) { if (numIcons == 0) {
return 0f; return 0f;
} }
final float contentWidth = numIcons <= MAX_ICONS_ON_LOCKSCREEN + 1 final float contentWidth =
? numIcons * mIconSize mIconSize * MathUtils.min(numIcons, MAX_ICONS_ON_LOCKSCREEN + 1);
: MAX_ICONS_ON_LOCKSCREEN * mIconSize + (float) mOverflowWidth;
return getActualPaddingStart() return getActualPaddingStart()
+ contentWidth + contentWidth
+ getActualPaddingEnd(); + getActualPaddingEnd();
@@ -406,14 +404,13 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout {
* are inserted into the notification container. * are inserted into the notification container.
* If this is not a whole number, the fraction means by how much the icon is appearing. * If this is not a whole number, the fraction means by how much the icon is appearing.
*/ */
public void calculateIconTranslations() { public void calculateIconXTranslations() {
float translationX = getActualPaddingStart(); float translationX = getActualPaddingStart();
int firstOverflowIndex = -1; int firstOverflowIndex = -1;
int childCount = getChildCount(); int childCount = getChildCount();
int maxVisibleIcons = mOnLockScreen ? MAX_ICONS_ON_AOD : int maxVisibleIcons = mOnLockScreen ? MAX_ICONS_ON_AOD :
mIsStaticLayout ? MAX_STATIC_ICONS : childCount; mIsStaticLayout ? MAX_STATIC_ICONS : childCount;
float layoutEnd = getLayoutEnd(); float layoutEnd = getLayoutEnd();
float overflowStart = getMaxOverflowStart();
mVisualOverflowStart = 0; mVisualOverflowStart = 0;
mFirstVisibleIconState = null; mFirstVisibleIconState = null;
for (int i = 0; i < childCount; i++) { for (int i = 0; i < childCount; i++) {
@@ -438,12 +435,12 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout {
? StatusBarIconView.STATE_HIDDEN ? StatusBarIconView.STATE_HIDDEN
: StatusBarIconView.STATE_ICON; : StatusBarIconView.STATE_ICON;
boolean isOverflowing = final float overflowDotX = layoutEnd - mIconSize;
(translationX > (isLastChild ? layoutEnd - mIconSize boolean isOverflowing = translationX > overflowDotX;
: overflowStart - mIconSize));
if (firstOverflowIndex == -1 && (forceOverflow || isOverflowing)) { if (firstOverflowIndex == -1 && (forceOverflow || isOverflowing)) {
firstOverflowIndex = isLastChild && !forceOverflow ? i - 1 : i; firstOverflowIndex = isLastChild && !forceOverflow ? i - 1 : i;
mVisualOverflowStart = layoutEnd - mOverflowWidth; mVisualOverflowStart = layoutEnd - mIconSize;
if (forceOverflow || mIsStaticLayout) { if (forceOverflow || mIsStaticLayout) {
mVisualOverflowStart = Math.min(translationX, mVisualOverflowStart); mVisualOverflowStart = Math.min(translationX, mVisualOverflowStart);
} }
@@ -477,7 +474,6 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout {
mLastVisibleIconState = mIconStates.get(lastChild); mLastVisibleIconState = mIconStates.get(lastChild);
mFirstVisibleIconState = mIconStates.get(getChildAt(0)); mFirstVisibleIconState = mIconStates.get(getChildAt(0));
} }
if (isLayoutRtl()) { if (isLayoutRtl()) {
for (int i = 0; i < childCount; i++) { for (int i = 0; i < childCount; i++) {
View view = getChildAt(i); View view = getChildAt(i);
@@ -568,7 +564,7 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout {
} }
private float getMaxOverflowStart() { private float getMaxOverflowStart() {
return getLayoutEnd() - mOverflowWidth; return getLayoutEnd() - mIconSize;
} }
public void setChangingViewPositions(boolean changingViewPositions) { public void setChangingViewPositions(boolean changingViewPositions) {
@@ -635,7 +631,7 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout {
return 0; return 0;
} }
int collapsedPadding = mOverflowWidth; int collapsedPadding = mIconSize;
if (collapsedPadding + getFinalTranslationX() > getWidth()) { if (collapsedPadding + getFinalTranslationX() > getWidth()) {
collapsedPadding = getWidth() - getFinalTranslationX(); collapsedPadding = getWidth() - getFinalTranslationX();

View File

@@ -49,7 +49,7 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() {
@Mock private lateinit var lockscreenShadeTransitionController: LockscreenShadeTransitionController @Mock private lateinit var lockscreenShadeTransitionController: LockscreenShadeTransitionController
@Mock private lateinit var stackLayout: NotificationStackScrollLayout @Mock private lateinit var stackLayout: NotificationStackScrollLayout
private val testableResources = mContext.getOrCreateTestableResources() private val testableResources = mContext.orCreateTestableResources
private lateinit var sizeCalculator: NotificationStackSizeCalculator private lateinit var sizeCalculator: NotificationStackSizeCalculator
@@ -121,17 +121,16 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() {
} }
@Test @Test
fun computeHeight_returnsAtMostSpaceAvailable_withGapBeforeShelf() { fun computeHeight_gapBeforeShelf_returnsSpaceUsed() {
// Each row in separate section.
setGapHeight(gapHeight) setGapHeight(gapHeight)
val shelfHeight = shelfHeight val spaceUsed =
val availableSpace =
listOf( listOf(
rowHeight + dividerHeight, dividerHeight + rowHeight,
gapHeight + rowHeight + dividerHeight, dividerHeight + gapHeight + rowHeight,
gapHeight + dividerHeight + shelfHeight) dividerHeight + gapHeight + shelfHeight)
.sum() .sum()
val availableSpace = spaceUsed + 1;
// All rows in separate sections (default setup).
val rows = val rows =
listOf(createMockRow(rowHeight), createMockRow(rowHeight), createMockRow(rowHeight)) listOf(createMockRow(rowHeight), createMockRow(rowHeight), createMockRow(rowHeight))
@@ -139,23 +138,29 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() {
assertThat(maxNotifications).isEqualTo(2) assertThat(maxNotifications).isEqualTo(2)
val height = sizeCalculator.computeHeight(stackLayout, maxNotifications, this.shelfHeight) val height = sizeCalculator.computeHeight(stackLayout, maxNotifications, this.shelfHeight)
assertThat(height).isAtMost(availableSpace) assertThat(height).isEqualTo(spaceUsed)
} }
@Test @Test
fun computeHeight_noGapBeforeShelf_returnsAtMostSpaceAvailable() { fun computeHeight_noGapBeforeShelf_returnsSpaceUsed() {
// Both rows are in the same section. // Both rows are in the same section.
setGapHeight(0f) setGapHeight(0f)
val rowHeight = rowHeight val rowHeight = rowHeight
val shelfHeight = shelfHeight val shelfHeight = shelfHeight
val availableSpace = listOf(rowHeight + dividerHeight, dividerHeight + shelfHeight).sum() val spaceUsed =
listOf(
dividerHeight + rowHeight,
dividerHeight + shelfHeight)
.sum()
val availableSpace = spaceUsed + 1
val rows = listOf(createMockRow(rowHeight), createMockRow(rowHeight)) val rows = listOf(createMockRow(rowHeight), createMockRow(rowHeight))
val maxNotifications = computeMaxKeyguardNotifications(rows, availableSpace, shelfHeight) val maxNotifications = computeMaxKeyguardNotifications(rows, availableSpace, shelfHeight)
assertThat(maxNotifications).isEqualTo(1) assertThat(maxNotifications).isEqualTo(1)
val height = sizeCalculator.computeHeight(stackLayout, maxNotifications, this.shelfHeight) val height = sizeCalculator.computeHeight(stackLayout, maxNotifications, this.shelfHeight)
assertThat(height).isAtMost(availableSpace) assertThat(height).isEqualTo(spaceUsed)
} }
@Test @Test
@@ -190,7 +195,7 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() {
val space = sizeCalculator.spaceNeeded(expandableView, visibleIndex = 0, val space = sizeCalculator.spaceNeeded(expandableView, visibleIndex = 0,
previousView = null, stack = stackLayout, onLockscreen = true) previousView = null, stack = stackLayout, onLockscreen = true)
assertThat(space).isEqualTo(5) assertThat(space).isEqualTo(5 + dividerHeight)
} }
@Test @Test
@@ -204,7 +209,7 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() {
val space = sizeCalculator.spaceNeeded(expandableView, visibleIndex = 0, val space = sizeCalculator.spaceNeeded(expandableView, visibleIndex = 0,
previousView = null, stack = stackLayout, onLockscreen = false) previousView = null, stack = stackLayout, onLockscreen = false)
assertThat(space).isEqualTo(10) assertThat(space).isEqualTo(10 + dividerHeight)
} }
private fun computeMaxKeyguardNotifications( private fun computeMaxKeyguardNotifications(

View File

@@ -0,0 +1,168 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.statusbar.phone
import android.service.notification.StatusBarNotification
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.StatusBarIconView
import junit.framework.Assert.assertEquals
import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertTrue
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.mock
import org.mockito.Mockito.`when` as whenever
/**
* Tests for {@link NotificationIconContainer}.
*/
@SmallTest
@RunWith(AndroidTestingRunner::class)
@RunWithLooper
class NotificationIconContainerTest : SysuiTestCase() {
private val iconContainer = NotificationIconContainer(context, /* attrs= */ null)
@Test
fun calculateWidthFor_zeroIcons_widthIsZero() {
assertEquals(/* expected= */ iconContainer.calculateWidthFor(/* numIcons= */ 0f),
/* actual= */ 0f)
}
@Test
fun calculateWidthFor_oneIcon_widthForOneIcon() {
iconContainer.setActualPaddingStart(10f)
iconContainer.setActualPaddingEnd(10f)
iconContainer.setIconSize(10);
assertEquals(/* expected= */ iconContainer.calculateWidthFor(/* numIcons= */ 1f),
/* actual= */ 30f)
}
@Test
fun calculateWidthFor_fourIcons_widthForFourIcons() {
iconContainer.setActualPaddingStart(10f)
iconContainer.setActualPaddingEnd(10f)
iconContainer.setIconSize(10);
assertEquals(/* expected= */ iconContainer.calculateWidthFor(/* numIcons= */ 4f),
/* actual= */ 60f)
}
@Test
fun calculateWidthFor_fiveIcons_widthForFourIcons() {
iconContainer.setActualPaddingStart(10f)
iconContainer.setActualPaddingEnd(10f)
iconContainer.setIconSize(10);
assertEquals(/* expected= */ iconContainer.calculateWidthFor(/* numIcons= */ 5f),
/* actual= */ 60f)
}
@Test
fun calculateIconXTranslations_shortShelfOneIcon_atCorrectXWithoutOverflowDot() {
iconContainer.setActualPaddingStart(10f)
iconContainer.setActualPaddingEnd(10f)
iconContainer.setIconSize(10);
val icon = mockStatusBarIcon()
iconContainer.addView(icon)
assertEquals(1, iconContainer.childCount)
val iconState = iconContainer.getIconState(icon)
iconState.iconAppearAmount = 1f
val width = iconContainer.calculateWidthFor(/* numIcons= */ 1f)
iconContainer.setActualLayoutWidth(width.toInt())
iconContainer.calculateIconXTranslations()
assertEquals(10f, iconState.xTranslation)
assertFalse(iconContainer.hasOverflow())
}
@Test
fun calculateIconXTranslations_shortShelfFourIcons_atCorrectXWithoutOverflowDot() {
iconContainer.setActualPaddingStart(10f)
iconContainer.setActualPaddingEnd(10f)
iconContainer.setIconSize(10);
val iconOne = mockStatusBarIcon()
val iconTwo = mockStatusBarIcon()
val iconThree = mockStatusBarIcon()
val iconFour = mockStatusBarIcon()
iconContainer.addView(iconOne)
iconContainer.addView(iconTwo)
iconContainer.addView(iconThree)
iconContainer.addView(iconFour)
assertEquals(4, iconContainer.childCount)
val width = iconContainer.calculateWidthFor(/* numIcons= */ 4f)
iconContainer.setActualLayoutWidth(width.toInt())
iconContainer.calculateIconXTranslations()
assertEquals(10f, iconContainer.getIconState(iconOne).xTranslation)
assertEquals(20f, iconContainer.getIconState(iconTwo).xTranslation)
assertEquals(30f, iconContainer.getIconState(iconThree).xTranslation)
assertEquals(40f, iconContainer.getIconState(iconFour).xTranslation)
assertFalse(iconContainer.hasOverflow())
}
@Test
fun calculateIconXTranslations_shortShelfFiveIcons_atCorrectXWithOverflowDot() {
iconContainer.setActualPaddingStart(10f)
iconContainer.setActualPaddingEnd(10f)
iconContainer.setIconSize(10);
val iconOne = mockStatusBarIcon()
val iconTwo = mockStatusBarIcon()
val iconThree = mockStatusBarIcon()
val iconFour = mockStatusBarIcon()
val iconFive = mockStatusBarIcon()
iconContainer.addView(iconOne)
iconContainer.addView(iconTwo)
iconContainer.addView(iconThree)
iconContainer.addView(iconFour)
iconContainer.addView(iconFive)
assertEquals(5, iconContainer.childCount)
val width = iconContainer.calculateWidthFor(/* numIcons= */ 5f)
iconContainer.setActualLayoutWidth(width.toInt())
iconContainer.calculateIconXTranslations()
assertEquals(10f, iconContainer.getIconState(iconOne).xTranslation)
assertEquals(20f, iconContainer.getIconState(iconTwo).xTranslation)
assertEquals(30f, iconContainer.getIconState(iconThree).xTranslation)
assertTrue(iconContainer.hasOverflow())
}
private fun mockStatusBarIcon() : StatusBarIconView {
val iconView = mock(StatusBarIconView::class.java)
whenever(iconView.width).thenReturn(10)
val icon = mock(android.graphics.drawable.Icon::class.java)
whenever(iconView.sourceIcon).thenReturn(icon)
val sbn = mock(StatusBarNotification::class.java)
whenever(sbn.groupKey).thenReturn("groupKey")
whenever(iconView.notification).thenReturn(sbn)
return iconView
}
}