diff --git a/packages/SystemUI/src/com/android/systemui/common/shared/model/SharedNotificationContainerPosition.kt b/packages/SystemUI/src/com/android/systemui/common/shared/model/SharedNotificationContainerPosition.kt index b2bc06f0ae29722ccbf0dec09be7ce97769d7f61..48d374207388eadd6aac4566a5dbd7ce39849709 100644 --- a/packages/SystemUI/src/com/android/systemui/common/shared/model/SharedNotificationContainerPosition.kt +++ b/packages/SystemUI/src/com/android/systemui/common/shared/model/SharedNotificationContainerPosition.kt @@ -20,4 +20,7 @@ package com.android.systemui.common.shared.model data class SharedNotificationContainerPosition( val top: Float = 0f, val bottom: Float = 0f, + + /** Whether any modifications to top/bottom are smoothly animated */ + val animate: Boolean = false, ) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt index 4d5c503d1c4e719e41d11a9c38c4a33933a422fb..67a12b06de0f55e862dd6885d1d59921d79b9ccc 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt @@ -22,6 +22,8 @@ import android.view.View import android.view.View.OnLayoutChangeListener import android.view.ViewGroup import android.view.ViewGroup.OnHierarchyChangeListener +import android.view.WindowInsets +import android.view.WindowInsets.Type import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle import com.android.internal.jank.InteractionJankMonitor @@ -242,11 +244,18 @@ object KeyguardRootViewBinder { } ) + view.setOnApplyWindowInsetsListener { v: View, insets: WindowInsets -> + val insetTypes = WindowInsets.Type.systemBars() or WindowInsets.Type.displayCutout() + viewModel.topInset = insets.getInsetsIgnoringVisibility(insetTypes).top + insets + } + return object : DisposableHandle { override fun dispose() { disposableHandle.dispose() view.removeOnLayoutChangeListener(onLayoutChangeListener) view.setOnHierarchyChangeListener(null) + view.setOnApplyWindowInsetsListener(null) childViews.clear() } } @@ -288,7 +297,6 @@ object KeyguardRootViewBinder { oldBottom: Int ) { val nsslPlaceholder = v.findViewById(R.id.nssl_placeholder) as View? - if (nsslPlaceholder != null) { // After layout, ensure the notifications are positioned correctly viewModel.onSharedNotificationContainerPositionChanged( @@ -296,6 +304,11 @@ object KeyguardRootViewBinder { nsslPlaceholder.bottom.toFloat(), ) } + + val ksv = v.findViewById(R.id.keyguard_status_view) as View? + if (ksv != null) { + viewModel.statusViewTop = ksv.top + } } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt index 1f98082c406513a7225b26db425e6543fdf90ece..e12da53287ed9c58f74519173250d6b3b5f839bb 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt @@ -65,7 +65,12 @@ constructor( */ private val previewMode = MutableStateFlow(PreviewMode()) - public var clockControllerProvider: Provider<ClockController>? = null + var clockControllerProvider: Provider<ClockController>? = null + + /** System insets that keyguard needs to stay out of */ + var topInset: Int = 0 + /** Status view top, without translation added in */ + var statusViewTop: Int = 0 val burnInLayerVisibility: Flow<Int> = keyguardTransitionInteractor.startedKeyguardState @@ -102,9 +107,12 @@ constructor( scale = MathUtils.lerp(burnIn.scale, 1f, 1f - interpolation), ) } else { + // Ensure the desired translation doesn't encroach on the top inset + val burnInY = MathUtils.lerp(0, burnIn.translationY, interpolation).toInt() + val translationY = -(statusViewTop - Math.max(topInset, statusViewTop + burnInY)) BurnInModel( translationX = MathUtils.lerp(0, burnIn.translationX, interpolation).toInt(), - translationY = MathUtils.lerp(0, burnIn.translationY, interpolation).toInt(), + translationY = translationY, scale = MathUtils.lerp(burnIn.scale, 1f, 1f - interpolation), scaleClockOnly = true, ) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt index 2af7181f2f31d6558e6a020fc5a36695993b5296..6785da4bf4f15710c4d430e287606195dda87903 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt @@ -59,10 +59,8 @@ object SharedNotificationContainerBinder { launch { viewModel.position.collect { - controller.updateTopPadding( - it.top, - controller.isAddOrRemoveAnimationPending() - ) + val animate = it.animate || controller.isAddOrRemoveAnimationPending() + controller.updateTopPadding(it.top, animate) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt index 1229cb9b49acab4a9ccfe469256e79c0464e3079..b86b5dcc7939d578a938f62ad831550f4fca32be 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt @@ -25,6 +25,7 @@ import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController import com.android.systemui.statusbar.notification.stack.NotificationStackSizeCalculator import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor +import com.android.systemui.util.kotlin.sample import javax.inject.Inject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine @@ -118,8 +119,15 @@ constructor( } } } else { - interactor.topPosition.map { top -> - keyguardInteractor.sharedNotificationContainerPosition.value.copy(top = top) + interactor.topPosition.sample(shadeInteractor.qsExpansion, ::Pair).map { + (top, qsExpansion) -> + // When QS expansion > 0, it should directly set the top padding so do not + // animate it + val animate = qsExpansion == 0f + keyguardInteractor.sharedNotificationContainerPosition.value.copy( + top = top, + animate = animate + ) } } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt index 4f545cb0e288ce44b4d952a43bb138c96c0f6d30..b80771ff646c72501b8e5ddffbe621a06348fad7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt @@ -179,6 +179,8 @@ class KeyguardRootViewModelTest : SysuiTestCase() { val translationY by collectLastValue(underTest.translationY) val scale by collectLastValue(underTest.scale) + underTest.statusViewTop = 100 + // Set to dozing (on AOD) dozeAmountTransitionStep.emit(TransitionStep(value = 1f)) // Trigger a change to the burn-in model @@ -199,6 +201,37 @@ class KeyguardRootViewModelTest : SysuiTestCase() { assertThat(scale).isEqualTo(Pair(1f, true /* scaleClockOnly */)) } + @Test + fun translationAndScaleFromBurnFullyDozingStaysOutOfTopInset() = + testScope.runTest { + val translationX by collectLastValue(underTest.translationX) + val translationY by collectLastValue(underTest.translationY) + val scale by collectLastValue(underTest.scale) + + underTest.statusViewTop = 100 + underTest.topInset = 80 + + // Set to dozing (on AOD) + dozeAmountTransitionStep.emit(TransitionStep(value = 1f)) + // Trigger a change to the burn-in model + burnInFlow.value = + BurnInModel( + translationX = 20, + translationY = -30, + scale = 0.5f, + ) + assertThat(translationX).isEqualTo(20) + // -20 instead of -30, due to inset of 80 + assertThat(translationY).isEqualTo(-20) + assertThat(scale).isEqualTo(Pair(0.5f, true /* scaleClockOnly */)) + + // Set to the beginning of GONE->AOD transition + goneToAodTransitionStep.emit(TransitionStep(value = 0f)) + assertThat(translationX).isEqualTo(0) + assertThat(translationY).isEqualTo(0) + assertThat(scale).isEqualTo(Pair(1f, true /* scaleClockOnly */)) + } + @Test fun translationAndScaleFromBurnInUseScaleOnly() = testScope.runTest { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt index 60421c981e6d7da80dcdbf1c39ca3bf87047c3bc..3a9d111bacf7b0707bc7b1a299dc7b6f7e526972 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt @@ -314,7 +314,26 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { sharedNotificationContainerInteractor.setTopPosition(10f) assertThat(position) - .isEqualTo(SharedNotificationContainerPosition(top = 10f, bottom = 0f)) + .isEqualTo( + SharedNotificationContainerPosition(top = 10f, bottom = 0f, animate = true) + ) + } + + @Test + fun positionOnQS() = + testScope.runTest { + val position by collectLastValue(underTest.position) + + // Start on lockscreen with shade expanded + showLockscreenWithQSExpanded() + + // When not in split shade + sharedNotificationContainerInteractor.setTopPosition(10f) + + assertThat(position) + .isEqualTo( + SharedNotificationContainerPosition(top = 10f, bottom = 0f, animate = false) + ) } @Test @@ -390,6 +409,17 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { ) } + private suspend fun TestScope.showLockscreenWithQSExpanded() { + shadeRepository.setLockscreenShadeExpansion(0f) + shadeRepository.setQsExpansion(1f) + keyguardRepository.setStatusBarState(StatusBarState.SHADE_LOCKED) + keyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.AOD, + to = KeyguardState.LOCKSCREEN, + this, + ) + } + @SysUISingleton @Component( modules =