Fix one-icon shelf flicker on lockscreen swipe-up

The flicker happens because floating point comparison
for viewStart and shelfStart is off by ~0.001f sometimes,
such that the notification is thought to be above the shelf,
causing the shelf to hide since it no longer has a full view.

Bug: 222123657
Test: NotificationShelfTest
Test: on lockscreen, swipe up with one icon in shelf
      => no flickering
Change-Id: Iad2bb24d4a51d5a831aa3193de10f768fa7c41be
This commit is contained in:
Lyn Han
2022-05-02 20:20:28 -05:00
parent c8fcbb5ba4
commit 1c88a2be52
2 changed files with 137 additions and 14 deletions

View File

@@ -326,9 +326,9 @@ public class NotificationShelf extends ActivatableNotificationView implements
|| child.isPinned();
boolean isLastChild = child == lastChild;
final float viewStart = child.getTranslationY();
final float inShelfAmount = updateShelfTransformation(i, child, scrollingFast,
expandingAnimated, isLastChild);
final float shelfClipStart = getTranslationY() - mPaddingBetweenElements;
final float inShelfAmount = getAmountInShelf(i, child, scrollingFast,
expandingAnimated, isLastChild, shelfClipStart);
// TODO(b/172289889) scale mPaddingBetweenElements with expansion amount
if ((isLastChild && !child.isInShelf()) || aboveShelf || backgroundForceHidden) {
@@ -609,10 +609,18 @@ public class NotificationShelf extends ActivatableNotificationView implements
}
/**
* @return the amount how much this notification is in the shelf
* @param i Index of the view in the host layout.
* @param view The current ExpandableView.
* @param scrollingFast Whether we are scrolling fast.
* @param expandingAnimated Whether we are expanding a notification.
* @param isLastChild Whether this is the last view.
* @param shelfClipStart The point at which notifications start getting clipped by the shelf.
* @return The amount how much this notification is in the shelf.
* 0f is not in shelf. 1f is completely in shelf.
*/
private float updateShelfTransformation(int i, ExpandableView view, boolean scrollingFast,
boolean expandingAnimated, boolean isLastChild) {
@VisibleForTesting
public float getAmountInShelf(int i, ExpandableView view, boolean scrollingFast,
boolean expandingAnimated, boolean isLastChild, float shelfClipStart) {
// Let's calculate how much the view is in the shelf
float viewStart = view.getTranslationY();
@@ -635,29 +643,33 @@ public class NotificationShelf extends ActivatableNotificationView implements
float viewEnd = viewStart + fullHeight;
float fullTransitionAmount = 0.0f;
float iconTransitionAmount = 0.0f;
float shelfStart = getTranslationY() - mPaddingBetweenElements;
// Don't animate shelf icons during shade expansion.
if (mAmbientState.isExpansionChanging() && !mAmbientState.isOnKeyguard()) {
// TODO(b/172289889) handle icon placement for notification that is clipped by the shelf
if (mIndexOfFirstViewInShelf != -1 && i >= mIndexOfFirstViewInShelf) {
fullTransitionAmount = 1f;
iconTransitionAmount = 1f;
}
} else if (viewEnd >= shelfStart
} else if (viewEnd >= shelfClipStart
&& (!mAmbientState.isUnlockHintRunning() || view.isInShelf())
&& (mAmbientState.isShadeExpanded()
|| (!view.isPinned() && !view.isHeadsUpAnimatingAway()))) {
if (viewStart < shelfStart) {
float fullAmount = (shelfStart - viewStart) / fullHeight;
if (viewStart < shelfClipStart && Math.abs(viewStart - shelfClipStart) > 0.001f) {
// Partially clipped by shelf.
float fullAmount = (shelfClipStart - viewStart) / fullHeight;
fullAmount = Math.min(1.0f, fullAmount);
fullTransitionAmount = 1.0f - fullAmount;
if (isLastChild) {
// Reduce icon transform distance to completely fade in shelf icon
// by the time the notification icon fades out, and vice versa
iconTransitionAmount = (shelfStart - viewStart)
iconTransitionAmount = (shelfClipStart - viewStart)
/ (iconTransformStart - viewStart);
} else {
iconTransitionAmount = (shelfStart - iconTransformStart) / transformDistance;
iconTransitionAmount = (shelfClipStart - iconTransformStart)
/ transformDistance;
}
iconTransitionAmount = MathUtils.constrain(iconTransitionAmount, 0.0f, 1.0f);
iconTransitionAmount = 1.0f - iconTransitionAmount;
@@ -772,6 +784,9 @@ public class NotificationShelf extends ActivatableNotificationView implements
}
private NotificationIconContainer.IconState getIconState(StatusBarIconView icon) {
if (mShelfIcons == null) {
return null;
}
return mShelfIcons.getIconState(icon);
}

View File

@@ -5,8 +5,9 @@ import android.testing.TestableLooper.RunWithLooper
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.NotificationShelf
import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertTrue
import com.android.systemui.statusbar.StatusBarIconView
import com.android.systemui.statusbar.notification.row.ExpandableView
import junit.framework.Assert.*
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -143,6 +144,113 @@ class NotificationShelfTest : SysuiTestCase() {
assertFalse(isYBelowShelfInView)
}
@Test
fun getAmountInShelf_lastViewBelowShelf_completelyInShelf() {
val shelfClipStart = 0f
val viewStart = 1f
val expandableView = mock(ExpandableView::class.java)
whenever(expandableView.shelfIcon).thenReturn(mock(StatusBarIconView::class.java))
whenever(expandableView.translationY).thenReturn(viewStart)
whenever(expandableView.actualHeight).thenReturn(20)
whenever(expandableView.minHeight).thenReturn(20)
whenever(expandableView.shelfTransformationTarget).thenReturn(null) // use translationY
whenever(expandableView.isInShelf).thenReturn(true)
whenever(ambientState.isOnKeyguard).thenReturn(true)
whenever(ambientState.isExpansionChanging).thenReturn(false)
whenever(ambientState.isShadeExpanded).thenReturn(true)
val amountInShelf = shelf.getAmountInShelf(/* i= */ 0,
/* view= */ expandableView,
/* scrollingFast= */ false,
/* expandingAnimated= */ false,
/* isLastChild= */ true,
shelfClipStart)
assertEquals(1f, amountInShelf)
}
@Test
fun getAmountInShelf_lastViewAlmostBelowShelf_completelyInShelf() {
val viewStart = 0f
val shelfClipStart = 0.001f
val expandableView = mock(ExpandableView::class.java)
whenever(expandableView.shelfIcon).thenReturn(mock(StatusBarIconView::class.java))
whenever(expandableView.translationY).thenReturn(viewStart)
whenever(expandableView.actualHeight).thenReturn(20)
whenever(expandableView.minHeight).thenReturn(20)
whenever(expandableView.shelfTransformationTarget).thenReturn(null) // use translationY
whenever(expandableView.isInShelf).thenReturn(true)
whenever(ambientState.isOnKeyguard).thenReturn(true)
whenever(ambientState.isExpansionChanging).thenReturn(false)
whenever(ambientState.isShadeExpanded).thenReturn(true)
val amountInShelf = shelf.getAmountInShelf(/* i= */ 0,
/* view= */ expandableView,
/* scrollingFast= */ false,
/* expandingAnimated= */ false,
/* isLastChild= */ true,
shelfClipStart)
assertEquals(1f, amountInShelf)
}
@Test
fun getAmountInShelf_lastViewHalfClippedByShelf_halfInShelf() {
val viewStart = 0f
val shelfClipStart = 10f
val expandableView = mock(ExpandableView::class.java)
whenever(expandableView.shelfIcon).thenReturn(mock(StatusBarIconView::class.java))
whenever(expandableView.translationY).thenReturn(viewStart)
whenever(expandableView.actualHeight).thenReturn(25)
whenever(expandableView.minHeight).thenReturn(25)
whenever(expandableView.shelfTransformationTarget).thenReturn(null) // use translationY
whenever(expandableView.isInShelf).thenReturn(true)
whenever(ambientState.isOnKeyguard).thenReturn(true)
whenever(ambientState.isExpansionChanging).thenReturn(false)
whenever(ambientState.isShadeExpanded).thenReturn(true)
val amountInShelf = shelf.getAmountInShelf(/* i= */ 0,
/* view= */ expandableView,
/* scrollingFast= */ false,
/* expandingAnimated= */ false,
/* isLastChild= */ true,
shelfClipStart)
assertEquals(0.5f, amountInShelf)
}
@Test
fun getAmountInShelf_lastViewAboveShelf_notInShelf() {
val viewStart = 0f
val shelfClipStart = 15f
val expandableView = mock(ExpandableView::class.java)
whenever(expandableView.shelfIcon).thenReturn(mock(StatusBarIconView::class.java))
whenever(expandableView.translationY).thenReturn(viewStart)
whenever(expandableView.actualHeight).thenReturn(10)
whenever(expandableView.minHeight).thenReturn(10)
whenever(expandableView.shelfTransformationTarget).thenReturn(null) // use translationY
whenever(expandableView.isInShelf).thenReturn(false)
whenever(ambientState.isExpansionChanging).thenReturn(false)
whenever(ambientState.isOnKeyguard).thenReturn(true)
val amountInShelf = shelf.getAmountInShelf(/* i= */ 0,
/* view= */ expandableView,
/* scrollingFast= */ false,
/* expandingAnimated= */ false,
/* isLastChild= */ true,
shelfClipStart)
assertEquals(0f, amountInShelf)
}
private fun setFractionToShade(fraction: Float) {
whenever(ambientState.fractionToShade).thenReturn(fraction)
}