diff options
5 files changed, 285 insertions, 17 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java index 9a82ecf01449..855798ca1e96 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java @@ -40,6 +40,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.policy.SystemBarUtils; import com.android.systemui.animation.ShadeInterpolation; import com.android.systemui.res.R; +import com.android.systemui.scene.shared.flag.SceneContainerFlag; import com.android.systemui.shade.transition.LargeScreenShadeInterpolator; import com.android.systemui.statusbar.notification.ColorUpdateLogger; import com.android.systemui.statusbar.notification.NotificationUtils; @@ -93,6 +94,7 @@ public class NotificationShelf extends ActivatableNotificationView { private float mCornerAnimationDistance; private float mActualWidth = -1; private int mMaxIconsOnLockscreen; + private int mNotificationScrimPadding; private boolean mCanModifyColorOfNotifications; private boolean mCanInteract; private NotificationStackScrollLayout mHostLayout; @@ -136,6 +138,7 @@ public class NotificationShelf extends ActivatableNotificationView { mStatusBarHeight = SystemBarUtils.getStatusBarHeight(mContext); mPaddingBetweenElements = res.getDimensionPixelSize(R.dimen.notification_divider_height); mMaxIconsOnLockscreen = res.getInteger(R.integer.max_notif_icons_on_lockscreen); + mNotificationScrimPadding = res.getDimensionPixelSize(R.dimen.notification_side_paddings); ViewGroup.LayoutParams layoutParams = getLayoutParams(); final int newShelfHeight = res.getDimensionPixelOffset(R.dimen.notification_shelf_height); @@ -261,15 +264,31 @@ public class NotificationShelf extends ActivatableNotificationView { viewState.hasItemsInStableShelf = false; } - final float stackEnd = ambientState.getStackY() + ambientState.getStackHeight(); + final float stackBottom = SceneContainerFlag.isEnabled() + ? getStackBottom(ambientState) + : ambientState.getStackY() + ambientState.getStackHeight(); + if (viewState.hidden) { // if the shelf is hidden, position it at the end of the stack (plus the clip // padding), such that when it appears animated, it will smoothly move in from the // bottom, without jump cutting any notifications - viewState.setYTranslation(stackEnd + mPaddingBetweenElements); + viewState.setYTranslation(stackBottom + mPaddingBetweenElements); } else { - viewState.setYTranslation(stackEnd - viewState.height); + viewState.setYTranslation(stackBottom - viewState.height); + } + } + + /** + * bottom-most position, where we can draw the stack + */ + private float getStackBottom(AmbientState ambientState) { + if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return 0f; + float stackBottom = ambientState.getStackCutoff() - mNotificationScrimPadding; + if (ambientState.isExpansionChanging()) { + stackBottom = MathUtils.lerp(stackBottom * StackScrollAlgorithm.START_FRACTION, + stackBottom, ambientState.getExpansionFraction()); } + return stackBottom; } private int getSpeedBumpIndex() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java index 5f4e832f31a3..456c321a0c14 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java @@ -29,6 +29,7 @@ import com.android.systemui.Dumpable; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dump.DumpManager; import com.android.systemui.res.R; +import com.android.systemui.scene.shared.flag.SceneContainerFlag; import com.android.systemui.shade.transition.LargeScreenShadeInterpolator; import com.android.systemui.statusbar.NotificationShelf; import com.android.systemui.statusbar.StatusBarState; @@ -63,6 +64,7 @@ public class AmbientState implements Dumpable { * Used to read bouncer states. */ private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; + private float mStackCutoff; private int mScrollY; private float mOverScrollTopAmount; private float mOverScrollBottomAmount; @@ -346,6 +348,21 @@ public class AmbientState implements Dumpable { return mZDistanceBetweenElements; } + /** + * Y coordinate in view pixels above which the bottom of the notification stack / shelf / footer + * must be. + */ + public float getStackCutoff() { + if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return 0f; + return mStackCutoff; + } + + /** @see #getStackCutoff() */ + public void setStackCutoff(float stackCutoff) { + if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return; + this.mStackCutoff = stackCutoff; + } + public int getScrollY() { return mScrollY; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index 60a26dcb483d..0e77ed44293a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -837,7 +837,7 @@ public class NotificationStackScrollLayout y = (int) mScrollViewFields.getStackTop(); drawDebugInfo(canvas, y, Color.RED, /* label= */ "getStackTop() = " + y); - y = (int) mScrollViewFields.getStackCutoff(); + y = (int) mAmbientState.getStackCutoff(); drawDebugInfo(canvas, y, Color.MAGENTA, /* label= */ "getStackCutoff() = " + y); y = (int) mScrollViewFields.getHeadsUpTop(); @@ -1221,7 +1221,7 @@ public class NotificationStackScrollLayout @Override public void setStackCutoff(float stackCutoff) { - mScrollViewFields.setStackCutoff(stackCutoff); + mAmbientState.setStackCutoff(stackCutoff); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ScrollViewFields.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ScrollViewFields.kt index bffc1704cdb4..2e86ad97a5f4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ScrollViewFields.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ScrollViewFields.kt @@ -34,11 +34,6 @@ class ScrollViewFields { var scrimClippingShape: ShadeScrimShape? = null /** Y coordinate in view pixels of the top of the notification stack */ var stackTop: Float = 0f - /** - * Y coordinate in view pixels above which the bottom of the notification stack / shelf / footer - * must be. - */ - var stackCutoff: Float = 0f /** Y coordinate in view pixels of the top of the HUN */ var headsUpTop: Float = 0f /** Whether the notifications are scrolled all the way to the top (i.e. when freshly opened) */ @@ -82,7 +77,6 @@ class ScrollViewFields { pw.printSection("StackViewStates") { pw.println("scrimClippingShape", scrimClippingShape) pw.println("stackTop", stackTop) - pw.println("stackCutoff", stackCutoff) pw.println("headsUpTop", headsUpTop) pw.println("isScrolledToTop", isScrolledToTop) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt index 48e8f88a15c0..9b0fd96e22e7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt @@ -10,6 +10,8 @@ import androidx.test.filters.SmallTest import com.android.keyguard.BouncerPanelExpansionCalculator.aboutToShowBouncerProgress import com.android.systemui.SysuiTestCase import com.android.systemui.animation.ShadeInterpolation +import com.android.systemui.flags.DisableSceneContainer +import com.android.systemui.flags.EnableSceneContainer import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.FeatureFlags import com.android.systemui.res.R @@ -30,8 +32,8 @@ import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.Mockito.mock -import org.mockito.MockitoAnnotations import org.mockito.Mockito.`when` as whenever +import org.mockito.MockitoAnnotations /** Tests for {@link NotificationShelf}. */ @SmallTest @@ -332,6 +334,144 @@ open class NotificationShelfTest : SysuiTestCase() { } @Test + fun updateState_lastViewAlmostBelowShelf_completelyInShelf() { + val viewStart = 0f + val shelfClipStart = 0.001f + + val expandableView = mock(ExpandableView::class.java) + whenever(expandableView.shelfIcon).thenReturn(mock(StatusBarIconView::class.java)) + whenever(expandableView.translationY).thenReturn(viewStart) + whenever(expandableView.actualHeight).thenReturn(20) + + whenever(expandableView.minHeight).thenReturn(20) + whenever(expandableView.shelfTransformationTarget).thenReturn(null) // use translationY + whenever(expandableView.isInShelf).thenReturn(true) + + whenever(ambientState.isOnKeyguard).thenReturn(true) + whenever(ambientState.isExpansionChanging).thenReturn(false) + whenever(ambientState.isShadeExpanded).thenReturn(true) + + val amountInShelf = + shelf.getAmountInShelf( + /* i= */ 0, + /* view= */ expandableView, + /* scrollingFast= */ false, + /* expandingAnimated= */ false, + /* isLastChild= */ true, + shelfClipStart + ) + assertEquals(1f, amountInShelf) + } + + @Test + @EnableSceneContainer + fun updateState_withViewInShelf_showShelf() { + // GIVEN a view is scrolled into the shelf + val stackCutoff = 200f + val scrimPadding = + context.resources.getDimensionPixelSize(R.dimen.notification_side_paddings) + val shelfTop = stackCutoff - scrimPadding - shelf.height + val stackScrollAlgorithmState = StackScrollAlgorithmState() + val viewInShelf = mock(ExpandableView::class.java) + + whenever(ambientState.stackCutoff).thenReturn(stackCutoff) + whenever(ambientState.isShadeExpanded).thenReturn(true) + whenever(ambientState.lastVisibleBackgroundChild).thenReturn(viewInShelf) + whenever(viewInShelf.viewState).thenReturn(ExpandableViewState()) + whenever(viewInShelf.shelfIcon).thenReturn(mock(StatusBarIconView::class.java)) + whenever(viewInShelf.translationY).thenReturn(shelfTop) + whenever(viewInShelf.actualHeight).thenReturn(10) + whenever(viewInShelf.isInShelf).thenReturn(true) + whenever(viewInShelf.minHeight).thenReturn(10) + whenever(viewInShelf.shelfTransformationTarget).thenReturn(null) // use translationY + whenever(viewInShelf.isInShelf).thenReturn(true) + + stackScrollAlgorithmState.visibleChildren.add(viewInShelf) + stackScrollAlgorithmState.firstViewInShelf = viewInShelf + + // WHEN Shelf's ViewState is updated + shelf.updateState(stackScrollAlgorithmState, ambientState) + + // THEN the shelf is visible, and positioned correctly + val shelfState = shelf.viewState as NotificationShelf.ShelfState + assertEquals(false, shelfState.hidden) + assertEquals(shelf.height, shelfState.height) + assertEquals(shelfTop, shelfState.yTranslation) + } + + @Test + @EnableSceneContainer + fun updateState_withViewInShelfDuringExpansion_showShelf() { + // GIVEN a view is scrolled into the shelf + val stackCutoff = 200f + val scrimPadding = + context.resources.getDimensionPixelSize(R.dimen.notification_side_paddings) + val stackBottom = stackCutoff - scrimPadding + val shelfTop = stackBottom - shelf.height + val stackScrollAlgorithmState = StackScrollAlgorithmState() + val viewInShelf = mock(ExpandableView::class.java) + + // AND a shade expansion is in progress + val shadeExpansionFraction = 0.5f + + whenever(ambientState.stackCutoff).thenReturn(stackCutoff) + whenever(ambientState.isShadeExpanded).thenReturn(true) + whenever(ambientState.lastVisibleBackgroundChild).thenReturn(viewInShelf) + whenever(ambientState.isExpansionChanging).thenReturn(true) + whenever(ambientState.expansionFraction).thenReturn(shadeExpansionFraction) + whenever(viewInShelf.viewState).thenReturn(ExpandableViewState()) + whenever(viewInShelf.shelfIcon).thenReturn(mock(StatusBarIconView::class.java)) + whenever(viewInShelf.translationY).thenReturn(shelfTop) + whenever(viewInShelf.actualHeight).thenReturn(10) + whenever(viewInShelf.isInShelf).thenReturn(true) + whenever(viewInShelf.minHeight).thenReturn(10) + whenever(viewInShelf.shelfTransformationTarget).thenReturn(null) // use translationY + whenever(viewInShelf.isInShelf).thenReturn(true) + + stackScrollAlgorithmState.visibleChildren.add(viewInShelf) + stackScrollAlgorithmState.firstViewInShelf = viewInShelf + + // WHEN Shelf's ViewState is updated + shelf.updateState(stackScrollAlgorithmState, ambientState) + + // THEN the shelf is visible + val shelfState = shelf.viewState as NotificationShelf.ShelfState + assertEquals(false, shelfState.hidden) + assertEquals(shelf.height, shelfState.height) + // AND its translation is scaled by the shade expansion + assertEquals((stackBottom * 0.75f) - shelf.height, shelfState.yTranslation) + } + + @Test + @EnableSceneContainer + fun updateState_withNullLastVisibleBackgroundChild_hideShelf_withSceneContainer() { + // GIVEN + val stackCutoff = 200f + val scrimPadding = + context.resources.getDimensionPixelSize(R.dimen.notification_side_paddings) + val paddingBetweenElements = + context.resources.getDimensionPixelSize(R.dimen.notification_divider_height) + whenever(ambientState.stackCutoff).thenReturn(stackCutoff) + whenever(ambientState.isShadeExpanded).thenReturn(true) + val lastVisibleBackgroundChild = mock<ExpandableView>() + val expandableViewState = ExpandableViewState() + whenever(lastVisibleBackgroundChild.viewState).thenReturn(expandableViewState) + val stackScrollAlgorithmState = StackScrollAlgorithmState() + stackScrollAlgorithmState.firstViewInShelf = mock() + + whenever(ambientState.lastVisibleBackgroundChild).thenReturn(null) + + // WHEN + shelf.updateState(stackScrollAlgorithmState, ambientState) + + // THEN + val shelfState = shelf.viewState as NotificationShelf.ShelfState + assertEquals(true, shelfState.hidden) + assertEquals(stackCutoff - scrimPadding + paddingBetweenElements, shelfState.yTranslation) + } + + @Test + @DisableSceneContainer fun updateState_withNullLastVisibleBackgroundChild_hideShelf() { // GIVEN whenever(ambientState.stackY).thenReturn(100f) @@ -358,6 +498,35 @@ open class NotificationShelfTest : SysuiTestCase() { } @Test + @EnableSceneContainer + fun updateState_withNullFirstViewInShelf_hideShelf_withSceneContainer() { + // GIVEN + val stackCutoff = 200f + val scrimPadding = + context.resources.getDimensionPixelSize(R.dimen.notification_side_paddings) + val paddingBetweenElements = + context.resources.getDimensionPixelSize(R.dimen.notification_divider_height) + whenever(ambientState.stackCutoff).thenReturn(stackCutoff) + whenever(ambientState.isShadeExpanded).thenReturn(true) + val lastVisibleBackgroundChild = mock<ExpandableView>() + val expandableViewState = ExpandableViewState() + whenever(lastVisibleBackgroundChild.viewState).thenReturn(expandableViewState) + whenever(ambientState.lastVisibleBackgroundChild).thenReturn(lastVisibleBackgroundChild) + val stackScrollAlgorithmState = StackScrollAlgorithmState() + + stackScrollAlgorithmState.firstViewInShelf = null + + // WHEN + shelf.updateState(stackScrollAlgorithmState, ambientState) + + // THEN + val shelfState = shelf.viewState as NotificationShelf.ShelfState + assertEquals(true, shelfState.hidden) + assertEquals(stackCutoff - scrimPadding + paddingBetweenElements, shelfState.yTranslation) + } + + @Test + @DisableSceneContainer fun updateState_withNullFirstViewInShelf_hideShelf() { // GIVEN whenever(ambientState.stackY).thenReturn(100f) @@ -384,6 +553,35 @@ open class NotificationShelfTest : SysuiTestCase() { } @Test + @EnableSceneContainer + fun updateState_withCollapsedShade_hideShelf_withSceneContainer() { + // GIVEN + val stackCutoff = 200f + val scrimPadding = + context.resources.getDimensionPixelSize(R.dimen.notification_side_paddings) + val paddingBetweenElements = + context.resources.getDimensionPixelSize(R.dimen.notification_divider_height) + whenever(ambientState.stackCutoff).thenReturn(stackCutoff) + val lastVisibleBackgroundChild = mock<ExpandableView>() + val expandableViewState = ExpandableViewState() + whenever(lastVisibleBackgroundChild.viewState).thenReturn(expandableViewState) + whenever(ambientState.lastVisibleBackgroundChild).thenReturn(lastVisibleBackgroundChild) + val stackScrollAlgorithmState = StackScrollAlgorithmState() + stackScrollAlgorithmState.firstViewInShelf = mock() + + whenever(ambientState.isShadeExpanded).thenReturn(false) + + // WHEN + shelf.updateState(stackScrollAlgorithmState, ambientState) + + // THEN + val shelfState = shelf.viewState as NotificationShelf.ShelfState + assertEquals(true, shelfState.hidden) + assertEquals(stackCutoff - scrimPadding + paddingBetweenElements, shelfState.yTranslation) + } + + @Test + @DisableSceneContainer fun updateState_withCollapsedShade_hideShelf() { // GIVEN whenever(ambientState.stackY).thenReturn(100f) @@ -410,6 +608,49 @@ open class NotificationShelfTest : SysuiTestCase() { } @Test + @EnableSceneContainer + fun updateState_withHiddenSectionBeforeShelf_hideShelf_withSceneContianer() { + // GIVEN + val stackCutoff = 200f + whenever(ambientState.stackCutoff).thenReturn(stackCutoff) + val scrimPadding = + context.resources.getDimensionPixelSize(R.dimen.notification_side_paddings) + val paddingBetweenElements = + context.resources.getDimensionPixelSize(R.dimen.notification_divider_height) + whenever(ambientState.isShadeExpanded).thenReturn(true) + val lastVisibleBackgroundChild = mock<ExpandableView>() + val expandableViewState = ExpandableViewState() + whenever(lastVisibleBackgroundChild.viewState).thenReturn(expandableViewState) + val stackScrollAlgorithmState = StackScrollAlgorithmState() + whenever(ambientState.lastVisibleBackgroundChild).thenReturn(lastVisibleBackgroundChild) + + val ssaVisibleChild = mock<ExpandableView>() + val ssaVisibleChildState = ExpandableViewState() + ssaVisibleChildState.hidden = true + whenever(ssaVisibleChild.viewState).thenReturn(ssaVisibleChildState) + + val ssaVisibleChild1 = mock<ExpandableView>() + val ssaVisibleChildState1 = ExpandableViewState() + ssaVisibleChildState1.hidden = true + whenever(ssaVisibleChild1.viewState).thenReturn(ssaVisibleChildState1) + + stackScrollAlgorithmState.visibleChildren.add(ssaVisibleChild) + stackScrollAlgorithmState.visibleChildren.add(ssaVisibleChild1) + whenever(ambientState.isExpansionChanging).thenReturn(true) + whenever(ambientState.expansionFraction).thenReturn(1f) + stackScrollAlgorithmState.firstViewInShelf = ssaVisibleChild1 + + // WHEN + shelf.updateState(stackScrollAlgorithmState, ambientState) + + // THEN + val shelfState = shelf.viewState as NotificationShelf.ShelfState + assertEquals(true, shelfState.hidden) + assertEquals(stackCutoff - scrimPadding + paddingBetweenElements, shelfState.yTranslation) + } + + @Test + @DisableSceneContainer fun updateState_withHiddenSectionBeforeShelf_hideShelf() { // GIVEN whenever(ambientState.stackY).thenReturn(100f) @@ -461,12 +702,9 @@ open class NotificationShelfTest : SysuiTestCase() { expectedAlpha: Float ) { val sbnMock: StatusBarNotification = mock() - val mockEntry = mock<NotificationEntry>().apply { - whenever(this.sbn).thenReturn(sbnMock) - } + val mockEntry = mock<NotificationEntry>().apply { whenever(this.sbn).thenReturn(sbnMock) } val row = ExpandableNotificationRow(mContext, null, mockEntry) - whenever(ambientState.lastVisibleBackgroundChild) - .thenReturn(row) + whenever(ambientState.lastVisibleBackgroundChild).thenReturn(row) whenever(ambientState.isExpansionChanging).thenReturn(true) whenever(ambientState.expansionFraction).thenReturn(expansionFraction) whenever(hostLayoutController.speedBumpIndex).thenReturn(0) |