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 boolean mAppearing;
private float mPulseHeight = MAX_PULSE_HEIGHT; 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). */ /** Fraction of lockscreen to shade animation (on lockscreen swipe down). */
private float mFractionToShade; private float mFractionToShade;
@@ -564,6 +570,19 @@ public class AmbientState implements Dumpable {
return mPulsing && entry.isAlerting(); 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() { public boolean isPanelTracking() {
return mPanelTracking; return mPanelTracking;
} }

View File

@@ -548,7 +548,7 @@ public class StackScrollAlgorithm {
ExpandableViewState viewState = view.getViewState(); ExpandableViewState viewState = view.getViewState();
viewState.location = ExpandableViewState.LOCATION_UNKNOWN; viewState.location = ExpandableViewState.LOCATION_UNKNOWN;
final float expansionFraction = getExpansionFractionWithoutShelf( float expansionFraction = getExpansionFractionWithoutShelf(
algorithmState, ambientState); algorithmState, ambientState);
// Add gap between sections. // Add gap between sections.
@@ -619,6 +619,11 @@ public class StackScrollAlgorithm {
updateViewWithShelf(view, viewState, shelfStart); 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. // Clip height of view right before shelf.
viewState.height = (int) (getMaxAllowedChildHeight(view) * expansionFraction); viewState.height = (int) (getMaxAllowedChildHeight(view) * expansionFraction);
} }
@@ -700,9 +705,11 @@ public class StackScrollAlgorithm {
&& !(child instanceof FooterView); && !(child instanceof FooterView);
} }
private void updatePulsingStates(StackScrollAlgorithmState algorithmState, @VisibleForTesting
void updatePulsingStates(StackScrollAlgorithmState algorithmState,
AmbientState ambientState) { AmbientState ambientState) {
int childCount = algorithmState.visibleChildren.size(); int childCount = algorithmState.visibleChildren.size();
ExpandableNotificationRow pulsingRow = null;
for (int i = 0; i < childCount; i++) { for (int i = 0; i < childCount; i++) {
View child = algorithmState.visibleChildren.get(i); View child = algorithmState.visibleChildren.get(i);
if (!(child instanceof ExpandableNotificationRow)) { if (!(child instanceof ExpandableNotificationRow)) {
@@ -714,6 +721,19 @@ public class StackScrollAlgorithm {
} }
ExpandableViewState viewState = row.getViewState(); ExpandableViewState viewState = row.getViewState();
viewState.hidden = false; 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)) .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( private fun createHunViewMock(
isShadeOpen: Boolean, isShadeOpen: Boolean,
fullyVisible: Boolean, fullyVisible: Boolean,
@@ -744,6 +832,29 @@ class StackScrollAlgorithmTest : SysuiTestCase() {
headsUpIsVisible = fullyVisible 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( private fun resetViewStates_expansionChanging_notificationAlphaUpdated(
expansionFraction: Float, expansionFraction: Float,
expectedAlpha: Float, expectedAlpha: Float,