Merge "Fix notification shelf issues" into tm-dev am: 11bfc9677d am: a4d58a84f1

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/17896364

Change-Id: I145bea3568119066065c339d0ecb8bc8bd8d52ff
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Lyn Han
2022-04-29 18:03:26 +00:00
committed by Automerger Merge Worker
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.ExpandableNotificationRow;
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.AnimationProperties;
import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
@@ -411,7 +410,7 @@ public class NotificationShelf extends ActivatableNotificationView implements
setBackgroundTop(backgroundTop);
setFirstElementRoundness(firstElementRoundness);
mShelfIcons.setSpeedBumpIndex(mHostLayoutController.getSpeedBumpIndex());
mShelfIcons.calculateIconTranslations();
mShelfIcons.calculateIconXTranslations();
mShelfIcons.applyIconStates();
for (int i = 0; i < mHostLayoutController.getChildCount(); i++) {
View child = mHostLayoutController.getChildAt(i);
@@ -636,7 +635,7 @@ public class NotificationShelf extends ActivatableNotificationView implements
float viewEnd = viewStart + fullHeight;
float fullTransitionAmount = 0.0f;
float iconTransitionAmount = 0.0f;
float shelfStart = getTranslationY();
float shelfStart = getTranslationY() - mPaddingBetweenElements;
if (mAmbientState.isExpansionChanging() && !mAmbientState.isOnKeyguard()) {
// TODO(b/172289889) handle icon placement for notification that is clipped by the shelf
if (mIndexOfFirstViewInShelf != -1 && i >= mIndexOfFirstViewInShelf) {

View File

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

View File

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

View File

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

View File

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