Merge "Fix Notification clipping flicker during AOD=>LS" into udc-dev

This commit is contained in:
Yining Liu
2023-05-15 18:01:40 +00:00
committed by Android (Google) Code Review
3 changed files with 152 additions and 2 deletions

View File

@@ -93,6 +93,12 @@ public class AmbientState implements Dumpable {
private boolean mAppearing;
private float mPulseHeight = MAX_PULSE_HEIGHT;
/**
* The ExpandableNotificationRow that is pulsing, or the one that was pulsing
* when the device started to transition from AOD to LockScreen.
*/
private ExpandableNotificationRow mPulsingRow;
/** Fraction of lockscreen to shade animation (on lockscreen swipe down). */
private float mFractionToShade;
@@ -564,6 +570,19 @@ public class AmbientState implements Dumpable {
return mPulsing && entry.isAlerting();
}
public void setPulsingRow(ExpandableNotificationRow row) {
mPulsingRow = row;
}
/**
* @param row The row to check
* @return true if row is the pulsing row when the device started to transition from AOD to lock
* screen
*/
public boolean isPulsingRow(ExpandableView row) {
return mPulsingRow == row;
}
public boolean isPanelTracking() {
return mPanelTracking;
}

View File

@@ -548,7 +548,7 @@ public class StackScrollAlgorithm {
ExpandableViewState viewState = view.getViewState();
viewState.location = ExpandableViewState.LOCATION_UNKNOWN;
final float expansionFraction = getExpansionFractionWithoutShelf(
float expansionFraction = getExpansionFractionWithoutShelf(
algorithmState, ambientState);
// Add gap between sections.
@@ -619,6 +619,11 @@ public class StackScrollAlgorithm {
updateViewWithShelf(view, viewState, shelfStart);
}
}
// Avoid pulsing notification flicker during AOD to LS
// A pulsing notification is already expanded, no need to expand it again with animation
if (ambientState.isPulsingRow(view)) {
expansionFraction = 1.0f;
}
// Clip height of view right before shelf.
viewState.height = (int) (getMaxAllowedChildHeight(view) * expansionFraction);
}
@@ -700,9 +705,11 @@ public class StackScrollAlgorithm {
&& !(child instanceof FooterView);
}
private void updatePulsingStates(StackScrollAlgorithmState algorithmState,
@VisibleForTesting
void updatePulsingStates(StackScrollAlgorithmState algorithmState,
AmbientState ambientState) {
int childCount = algorithmState.visibleChildren.size();
ExpandableNotificationRow pulsingRow = null;
for (int i = 0; i < childCount; i++) {
View child = algorithmState.visibleChildren.get(i);
if (!(child instanceof ExpandableNotificationRow)) {
@@ -714,6 +721,19 @@ public class StackScrollAlgorithm {
}
ExpandableViewState viewState = row.getViewState();
viewState.hidden = false;
pulsingRow = row;
}
// Set AmbientState#pulsingRow to the current pulsing row when on AOD.
// Set AmbientState#pulsingRow=null when on lockscreen, since AmbientState#pulsingRow
// is only used for skipping the unfurl animation for (the notification that was already
// showing at full height on AOD) during the AOD=>lockscreen transition, where
// dozeAmount=[1f, 0f). We also need to reset the pulsingRow once it is no longer used
// because it will interfere with future unfurling animations - for example, during the
// LS=>AOD animation, the pulsingRow may stay at full height when it should squish with the
// rest of the stack.
if (ambientState.getDozeAmount() == 0.0f || ambientState.getDozeAmount() == 1.0f) {
ambientState.setPulsingRow(pulsingRow);
}
}

View File

@@ -716,6 +716,94 @@ class StackScrollAlgorithmTest : SysuiTestCase() {
.isLessThan(px(R.dimen.heads_up_pinned_elevation))
}
@Test
fun aodToLockScreen_hasPulsingNotification_pulsingNotificationRowDoesNotChange() {
// Given: Before AOD to LockScreen, there was a pulsing notification
val pulsingNotificationView = createPulsingViewMock()
val algorithmState = StackScrollAlgorithm.StackScrollAlgorithmState()
algorithmState.visibleChildren.add(pulsingNotificationView)
ambientState.setPulsingRow(pulsingNotificationView)
// When: during AOD to LockScreen, any dozeAmount between (0, 1.0) is equivalent as a middle
// stage; here we use 0.5 for testing.
// stackScrollAlgorithm.updatePulsingStates is called
ambientState.dozeAmount = 0.5f
stackScrollAlgorithm.updatePulsingStates(algorithmState, ambientState)
// Then: ambientState.pulsingRow should still be pulsingNotificationView
assertTrue(ambientState.isPulsingRow(pulsingNotificationView))
}
@Test
fun deviceOnAod_hasPulsingNotification_recordPulsingNotificationRow() {
// Given: Device is on AOD, there is a pulsing notification
// ambientState.pulsingRow is null before stackScrollAlgorithm.updatePulsingStates
ambientState.dozeAmount = 1.0f
val pulsingNotificationView = createPulsingViewMock()
val algorithmState = StackScrollAlgorithm.StackScrollAlgorithmState()
algorithmState.visibleChildren.add(pulsingNotificationView)
ambientState.setPulsingRow(null)
// When: stackScrollAlgorithm.updatePulsingStates is called
stackScrollAlgorithm.updatePulsingStates(algorithmState, ambientState)
// Then: ambientState.pulsingRow should record the pulsingNotificationView
assertTrue(ambientState.isPulsingRow(pulsingNotificationView))
}
@Test
fun deviceOnLockScreen_hasPulsingNotificationBefore_clearPulsingNotificationRowRecord() {
// Given: Device finished AOD to LockScreen, there was a pulsing notification, and
// ambientState.pulsingRow was not null before AOD to LockScreen
// pulsingNotificationView.showingPulsing() returns false since the device is on LockScreen
ambientState.dozeAmount = 0.0f
val pulsingNotificationView = createPulsingViewMock()
whenever(pulsingNotificationView.showingPulsing()).thenReturn(false)
val algorithmState = StackScrollAlgorithm.StackScrollAlgorithmState()
algorithmState.visibleChildren.add(pulsingNotificationView)
ambientState.setPulsingRow(pulsingNotificationView)
// When: stackScrollAlgorithm.updatePulsingStates is called
stackScrollAlgorithm.updatePulsingStates(algorithmState, ambientState)
// Then: ambientState.pulsingRow should be null
assertTrue(ambientState.isPulsingRow(null))
}
@Test
fun aodToLockScreen_hasPulsingNotification_pulsingNotificationRowShowAtFullHeight() {
// Given: Before AOD to LockScreen, there was a pulsing notification
val pulsingNotificationView = createPulsingViewMock()
val algorithmState = StackScrollAlgorithm.StackScrollAlgorithmState()
algorithmState.visibleChildren.add(pulsingNotificationView)
ambientState.setPulsingRow(pulsingNotificationView)
// When: during AOD to LockScreen, any dozeAmount between (0, 1.0) is equivalent as a middle
// stage; here we use 0.5 for testing. The expansionFraction is also 0.5.
// stackScrollAlgorithm.resetViewStates is called.
ambientState.dozeAmount = 0.5f
setExpansionFractionWithoutShelfDuringAodToLockScreen(
ambientState,
algorithmState,
fraction = 0.5f
)
stackScrollAlgorithm.resetViewStates(ambientState, 0)
// Then: pulsingNotificationView should show at full height
assertEquals(
stackScrollAlgorithm.getMaxAllowedChildHeight(pulsingNotificationView),
pulsingNotificationView.viewState.height
)
// After: reset dozeAmount and expansionFraction
ambientState.dozeAmount = 0f
setExpansionFractionWithoutShelfDuringAodToLockScreen(
ambientState,
algorithmState,
fraction = 1f
)
}
private fun createHunViewMock(
isShadeOpen: Boolean,
fullyVisible: Boolean,
@@ -744,6 +832,29 @@ class StackScrollAlgorithmTest : SysuiTestCase() {
headsUpIsVisible = fullyVisible
}
private fun createPulsingViewMock(
) =
mock<ExpandableNotificationRow>().apply {
whenever(this.viewState).thenReturn(ExpandableViewState())
whenever(this.showingPulsing()).thenReturn(true)
}
private fun setExpansionFractionWithoutShelfDuringAodToLockScreen(
ambientState: AmbientState,
algorithmState: StackScrollAlgorithm.StackScrollAlgorithmState,
fraction: Float
) {
// showingShelf: false
algorithmState.firstViewInShelf = null
// scrimPadding: 0, because device is on lock screen
ambientState.setStatusBarState(StatusBarState.KEYGUARD)
ambientState.dozeAmount = 0.0f
// set stackEndHeight and stackHeight
// ExpansionFractionWithoutShelf == stackHeight / stackEndHeight
ambientState.stackEndHeight = 100f
ambientState.stackHeight = ambientState.stackEndHeight * fraction
}
private fun resetViewStates_expansionChanging_notificationAlphaUpdated(
expansionFraction: Float,
expectedAlpha: Float,