Loading packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java +19 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; } Loading packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java +22 −2 Original line number Diff line number Diff line Loading @@ -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. Loading Loading @@ -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); } Loading Loading @@ -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)) { Loading @@ -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); } } Loading packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt +111 −0 Original line number Diff line number Diff line Loading @@ -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, Loading Loading @@ -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, Loading Loading
packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java +19 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; } Loading
packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java +22 −2 Original line number Diff line number Diff line Loading @@ -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. Loading Loading @@ -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); } Loading Loading @@ -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)) { Loading @@ -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); } } Loading
packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt +111 −0 Original line number Diff line number Diff line Loading @@ -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, Loading Loading @@ -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, Loading