Merge "Fix Notification clipping flicker during AOD=>LS" into udc-dev
This commit is contained in:
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user