Fix Notification clipping flicker during AOD=>LS
Fixes the pulsing notification clipping flicker during AOD=>LS by avoiding the visible pulsing notification's height being reset by the AOD=>LockScreen animation. Bug: 279229000 Test: StackScrollAlgorithmTest Change-Id: Id825a1079627d882d423016ed81d06c9d223df15
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user