Loading packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt +66 −35 Original line number Diff line number Diff line Loading @@ -19,6 +19,11 @@ package com.android.systemui.keyguard.ui.composable.section import android.content.Context import android.view.ViewGroup import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import com.android.compose.animation.scene.SceneScope import com.android.systemui.dagger.SysUISingleton Loading @@ -40,33 +45,47 @@ import com.android.systemui.statusbar.notification.stack.ui.viewmodel.Notificati import com.android.systemui.statusbar.notification.stack.ui.viewmodel.SharedNotificationContainerViewModel import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.DisposableHandle @SysUISingleton class NotificationSection @Inject constructor( @Application context: Context, @Application private val context: Context, private val viewModel: NotificationsPlaceholderViewModel, controller: NotificationStackScrollLayoutController, sceneContainerFlags: SceneContainerFlags, sharedNotificationContainer: SharedNotificationContainer, sharedNotificationContainerViewModel: SharedNotificationContainerViewModel, stackScrollLayout: NotificationStackScrollLayout, notificationStackAppearanceViewModel: NotificationStackAppearanceViewModel, ambientState: AmbientState, notificationStackSizeCalculator: NotificationStackSizeCalculator, @Main mainDispatcher: CoroutineDispatcher, private val controller: NotificationStackScrollLayoutController, private val sceneContainerFlags: SceneContainerFlags, private val sharedNotificationContainer: SharedNotificationContainer, private val sharedNotificationContainerViewModel: SharedNotificationContainerViewModel, private val stackScrollLayout: NotificationStackScrollLayout, private val notificationStackAppearanceViewModel: NotificationStackAppearanceViewModel, private val ambientState: AmbientState, private val notificationStackSizeCalculator: NotificationStackSizeCalculator, @Main private val mainDispatcher: CoroutineDispatcher, ) { init { if (!KeyguardShadeMigrationNssl.isUnexpectedlyInLegacyMode()) { // This scene container section moves the NSSL to the SharedNotificationContainer. This // also requires that SharedNotificationContainer gets moved to the SceneWindowRootView // by the SceneWindowRootViewBinder. // Prior to Scene Container, but when the KeyguardShadeMigrationNssl flag is enabled, // NSSL is moved into this container by the NotificationStackScrollLayoutSection. @Composable fun SceneScope.Notifications(modifier: Modifier = Modifier) { if (KeyguardShadeMigrationNssl.isUnexpectedlyInLegacyMode()) { // This scene container section moves the NSSL to the SharedNotificationContainer. // This also requires that SharedNotificationContainer gets moved to the // SceneWindowRootView by the SceneWindowRootViewBinder. Prior to Scene Container, // but when the KeyguardShadeMigrationNssl flag is enabled, NSSL is moved into this // container by the NotificationStackScrollLayoutSection. return } var isBound by remember { mutableStateOf(false) } DisposableEffect(Unit) { val disposableHandles: MutableList<DisposableHandle> = mutableListOf() // Ensure stackScrollLayout is a child of sharedNotificationContainer. if (stackScrollLayout.parent != sharedNotificationContainer) { (stackScrollLayout.parent as? ViewGroup)?.removeView(stackScrollLayout) sharedNotificationContainer.addNotificationStackScrollLayout(stackScrollLayout) } disposableHandles.add( SharedNotificationContainerBinder.bind( sharedNotificationContainer, sharedNotificationContainerViewModel, Loading @@ -75,8 +94,10 @@ constructor( notificationStackSizeCalculator, mainDispatcher, ) ) if (sceneContainerFlags.flexiNotifsEnabled()) { disposableHandles.add( NotificationStackAppearanceViewBinder.bind( context, sharedNotificationContainer, Loading @@ -84,12 +105,22 @@ constructor( ambientState, controller, ) ) } isBound = true onDispose { disposableHandles.forEach { it.dispose() } disposableHandles.clear() isBound = false } } @Composable fun SceneScope.Notifications(modifier: Modifier = Modifier) { if (!isBound) { return } NotificationStack( viewModel = viewModel, modifier = modifier, Loading packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelTest.kt 0 → 100644 +48 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.systemui.statusbar.notification.stack.ui.viewmodel import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.common.shared.model.NotificationContainerBounds import com.android.systemui.keyguard.domain.interactor.keyguardInteractor import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationStackAppearanceInteractor import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import org.junit.Test import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) class NotificationsPlaceholderViewModelTest : SysuiTestCase() { private val kosmos = testKosmos() private val underTest = kosmos.notificationsPlaceholderViewModel @Test fun onBoundsChanged_setsNotificationContainerBounds() { underTest.onBoundsChanged(left = 5f, top = 5f, right = 5f, bottom = 5f) assertThat(kosmos.keyguardInteractor.notificationContainerBounds.value) .isEqualTo(NotificationContainerBounds(left = 5f, top = 5f, right = 5f, bottom = 5f)) assertThat(kosmos.notificationStackAppearanceInteractor.stackBounds.value) .isEqualTo(NotificationContainerBounds(left = 5f, top = 5f, right = 5f, bottom = 5f)) } @Test fun onContentTopChanged_setsContentTop() { underTest.onContentTopChanged(padding = 5f) assertThat(kosmos.notificationStackAppearanceInteractor.contentTop.value).isEqualTo(5f) } } packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt +20 −10 Original line number Diff line number Diff line Loading @@ -57,7 +57,7 @@ constructor( private val mainDispatcher: CoroutineDispatcher, ) : KeyguardSection() { private val placeHolderId = R.id.nssl_placeholder private var disposableHandle: DisposableHandle? = null private val disposableHandles: MutableList<DisposableHandle> = mutableListOf() /** * Align the notification placeholder bottom to the top of either the lock icon or the ambient Loading Loading @@ -102,8 +102,9 @@ constructor( if (!KeyguardShadeMigrationNssl.isEnabled) { return } disposableHandle?.dispose() disposableHandle = disposeHandles() disposableHandles.add( SharedNotificationContainerBinder.bind( sharedNotificationContainer, sharedNotificationContainerViewModel, Loading @@ -112,7 +113,10 @@ constructor( notificationStackSizeCalculator, mainDispatcher, ) ) if (sceneContainerFlags.flexiNotifsEnabled()) { disposableHandles.add( NotificationStackAppearanceViewBinder.bind( context, sharedNotificationContainer, Loading @@ -120,11 +124,17 @@ constructor( ambientState, controller, ) ) } } override fun removeViews(constraintLayout: ConstraintLayout) { disposableHandle?.dispose() disposeHandles() constraintLayout.removeView(placeHolderId) } private fun disposeHandles() { disposableHandles.forEach { it.dispose() } disposableHandles.clear() } } packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +3 −5 Original line number Diff line number Diff line Loading @@ -2472,11 +2472,9 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump return 0; } if (!mKeyguardBypassController.getBypassEnabled()) { if (migrateClocksToBlueprint()) { View nsslPlaceholder = mView.getRootView().findViewById(R.id.nssl_placeholder); if (!mSplitShadeEnabled && nsslPlaceholder != null) { return nsslPlaceholder.getTop(); } if (migrateClocksToBlueprint() && !mSplitShadeEnabled) { return (int) mKeyguardInteractor.getNotificationContainerBounds() .getValue().getTop(); } return mClockPositionResult.stackScrollerPadding; Loading packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt +3 −2 Original line number Diff line number Diff line Loading @@ -26,6 +26,7 @@ import com.android.systemui.statusbar.notification.stack.NotificationStackScroll import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationStackAppearanceViewModel import kotlin.math.roundToInt import kotlinx.coroutines.DisposableHandle import kotlinx.coroutines.launch /** Binds the shared notification container to its view-model. */ Loading @@ -38,8 +39,8 @@ object NotificationStackAppearanceViewBinder { viewModel: NotificationStackAppearanceViewModel, ambientState: AmbientState, controller: NotificationStackScrollLayoutController, ) { view.repeatWhenAttached { ): DisposableHandle { return view.repeatWhenAttached { repeatOnLifecycle(Lifecycle.State.CREATED) { launch { viewModel.stackBounds.collect { bounds -> Loading Loading
packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt +66 −35 Original line number Diff line number Diff line Loading @@ -19,6 +19,11 @@ package com.android.systemui.keyguard.ui.composable.section import android.content.Context import android.view.ViewGroup import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import com.android.compose.animation.scene.SceneScope import com.android.systemui.dagger.SysUISingleton Loading @@ -40,33 +45,47 @@ import com.android.systemui.statusbar.notification.stack.ui.viewmodel.Notificati import com.android.systemui.statusbar.notification.stack.ui.viewmodel.SharedNotificationContainerViewModel import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.DisposableHandle @SysUISingleton class NotificationSection @Inject constructor( @Application context: Context, @Application private val context: Context, private val viewModel: NotificationsPlaceholderViewModel, controller: NotificationStackScrollLayoutController, sceneContainerFlags: SceneContainerFlags, sharedNotificationContainer: SharedNotificationContainer, sharedNotificationContainerViewModel: SharedNotificationContainerViewModel, stackScrollLayout: NotificationStackScrollLayout, notificationStackAppearanceViewModel: NotificationStackAppearanceViewModel, ambientState: AmbientState, notificationStackSizeCalculator: NotificationStackSizeCalculator, @Main mainDispatcher: CoroutineDispatcher, private val controller: NotificationStackScrollLayoutController, private val sceneContainerFlags: SceneContainerFlags, private val sharedNotificationContainer: SharedNotificationContainer, private val sharedNotificationContainerViewModel: SharedNotificationContainerViewModel, private val stackScrollLayout: NotificationStackScrollLayout, private val notificationStackAppearanceViewModel: NotificationStackAppearanceViewModel, private val ambientState: AmbientState, private val notificationStackSizeCalculator: NotificationStackSizeCalculator, @Main private val mainDispatcher: CoroutineDispatcher, ) { init { if (!KeyguardShadeMigrationNssl.isUnexpectedlyInLegacyMode()) { // This scene container section moves the NSSL to the SharedNotificationContainer. This // also requires that SharedNotificationContainer gets moved to the SceneWindowRootView // by the SceneWindowRootViewBinder. // Prior to Scene Container, but when the KeyguardShadeMigrationNssl flag is enabled, // NSSL is moved into this container by the NotificationStackScrollLayoutSection. @Composable fun SceneScope.Notifications(modifier: Modifier = Modifier) { if (KeyguardShadeMigrationNssl.isUnexpectedlyInLegacyMode()) { // This scene container section moves the NSSL to the SharedNotificationContainer. // This also requires that SharedNotificationContainer gets moved to the // SceneWindowRootView by the SceneWindowRootViewBinder. Prior to Scene Container, // but when the KeyguardShadeMigrationNssl flag is enabled, NSSL is moved into this // container by the NotificationStackScrollLayoutSection. return } var isBound by remember { mutableStateOf(false) } DisposableEffect(Unit) { val disposableHandles: MutableList<DisposableHandle> = mutableListOf() // Ensure stackScrollLayout is a child of sharedNotificationContainer. if (stackScrollLayout.parent != sharedNotificationContainer) { (stackScrollLayout.parent as? ViewGroup)?.removeView(stackScrollLayout) sharedNotificationContainer.addNotificationStackScrollLayout(stackScrollLayout) } disposableHandles.add( SharedNotificationContainerBinder.bind( sharedNotificationContainer, sharedNotificationContainerViewModel, Loading @@ -75,8 +94,10 @@ constructor( notificationStackSizeCalculator, mainDispatcher, ) ) if (sceneContainerFlags.flexiNotifsEnabled()) { disposableHandles.add( NotificationStackAppearanceViewBinder.bind( context, sharedNotificationContainer, Loading @@ -84,12 +105,22 @@ constructor( ambientState, controller, ) ) } isBound = true onDispose { disposableHandles.forEach { it.dispose() } disposableHandles.clear() isBound = false } } @Composable fun SceneScope.Notifications(modifier: Modifier = Modifier) { if (!isBound) { return } NotificationStack( viewModel = viewModel, modifier = modifier, Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelTest.kt 0 → 100644 +48 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.systemui.statusbar.notification.stack.ui.viewmodel import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.common.shared.model.NotificationContainerBounds import com.android.systemui.keyguard.domain.interactor.keyguardInteractor import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationStackAppearanceInteractor import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import org.junit.Test import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) class NotificationsPlaceholderViewModelTest : SysuiTestCase() { private val kosmos = testKosmos() private val underTest = kosmos.notificationsPlaceholderViewModel @Test fun onBoundsChanged_setsNotificationContainerBounds() { underTest.onBoundsChanged(left = 5f, top = 5f, right = 5f, bottom = 5f) assertThat(kosmos.keyguardInteractor.notificationContainerBounds.value) .isEqualTo(NotificationContainerBounds(left = 5f, top = 5f, right = 5f, bottom = 5f)) assertThat(kosmos.notificationStackAppearanceInteractor.stackBounds.value) .isEqualTo(NotificationContainerBounds(left = 5f, top = 5f, right = 5f, bottom = 5f)) } @Test fun onContentTopChanged_setsContentTop() { underTest.onContentTopChanged(padding = 5f) assertThat(kosmos.notificationStackAppearanceInteractor.contentTop.value).isEqualTo(5f) } }
packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt +20 −10 Original line number Diff line number Diff line Loading @@ -57,7 +57,7 @@ constructor( private val mainDispatcher: CoroutineDispatcher, ) : KeyguardSection() { private val placeHolderId = R.id.nssl_placeholder private var disposableHandle: DisposableHandle? = null private val disposableHandles: MutableList<DisposableHandle> = mutableListOf() /** * Align the notification placeholder bottom to the top of either the lock icon or the ambient Loading Loading @@ -102,8 +102,9 @@ constructor( if (!KeyguardShadeMigrationNssl.isEnabled) { return } disposableHandle?.dispose() disposableHandle = disposeHandles() disposableHandles.add( SharedNotificationContainerBinder.bind( sharedNotificationContainer, sharedNotificationContainerViewModel, Loading @@ -112,7 +113,10 @@ constructor( notificationStackSizeCalculator, mainDispatcher, ) ) if (sceneContainerFlags.flexiNotifsEnabled()) { disposableHandles.add( NotificationStackAppearanceViewBinder.bind( context, sharedNotificationContainer, Loading @@ -120,11 +124,17 @@ constructor( ambientState, controller, ) ) } } override fun removeViews(constraintLayout: ConstraintLayout) { disposableHandle?.dispose() disposeHandles() constraintLayout.removeView(placeHolderId) } private fun disposeHandles() { disposableHandles.forEach { it.dispose() } disposableHandles.clear() } }
packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +3 −5 Original line number Diff line number Diff line Loading @@ -2472,11 +2472,9 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump return 0; } if (!mKeyguardBypassController.getBypassEnabled()) { if (migrateClocksToBlueprint()) { View nsslPlaceholder = mView.getRootView().findViewById(R.id.nssl_placeholder); if (!mSplitShadeEnabled && nsslPlaceholder != null) { return nsslPlaceholder.getTop(); } if (migrateClocksToBlueprint() && !mSplitShadeEnabled) { return (int) mKeyguardInteractor.getNotificationContainerBounds() .getValue().getTop(); } return mClockPositionResult.stackScrollerPadding; Loading
packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt +3 −2 Original line number Diff line number Diff line Loading @@ -26,6 +26,7 @@ import com.android.systemui.statusbar.notification.stack.NotificationStackScroll import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationStackAppearanceViewModel import kotlin.math.roundToInt import kotlinx.coroutines.DisposableHandle import kotlinx.coroutines.launch /** Binds the shared notification container to its view-model. */ Loading @@ -38,8 +39,8 @@ object NotificationStackAppearanceViewBinder { viewModel: NotificationStackAppearanceViewModel, ambientState: AmbientState, controller: NotificationStackScrollLayoutController, ) { view.repeatWhenAttached { ): DisposableHandle { return view.repeatWhenAttached { repeatOnLifecycle(Lifecycle.State.CREATED) { launch { viewModel.stackBounds.collect { bounds -> Loading