diff options
| author | 2024-08-23 11:25:35 -0700 | |
|---|---|---|
| committer | 2024-08-26 15:15:31 -0700 | |
| commit | c08f36a534ea53a94398d84af7f86f9c56c3a861 (patch) | |
| tree | 1aa38b48f6d7a46c7c20f8d940b9d445d7af89b6 | |
| parent | e171a255c83881367b6bfdfbe0e434efee8ce72f (diff) | |
[flexiglass] Reimplement closing guts on outside touch in flexiglass
Adds a flow to SceneContainerRepository of whether the flexiglass Compose hierarchy is being touched, and a flow to NotificationViewHeightRepository of whether the current gesture started on an open notification guts. Closes guts when the former is true and latter is false.
Bug: 359956787
Test: unit tests forthcoming
Flag: com.android.systemui.scene_container
Change-Id: Ia9e4831a3897160ad84c143231bb6b1515c27e11
19 files changed, 233 insertions, 58 deletions
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt index d5874d1a7d3f..e17cb31407da 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt @@ -16,6 +16,8 @@ package com.android.systemui.scene.ui.composable +import androidx.compose.foundation.gestures.awaitEachGesture +import androidx.compose.foundation.gestures.awaitFirstDown import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material3.Text @@ -28,6 +30,7 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.input.pointer.pointerInput import com.android.compose.animation.scene.MutableSceneTransitionLayoutState import com.android.compose.animation.scene.SceneKey import com.android.compose.animation.scene.SceneTransitionLayout @@ -100,7 +103,13 @@ fun SceneContainer( } Box( - modifier = Modifier.fillMaxSize(), + modifier = + Modifier.fillMaxSize().pointerInput(Unit) { + awaitEachGesture { + awaitFirstDown(false) + viewModel.onSceneContainerUserInputStarted() + } + }, ) { SceneTransitionLayout(state = state, modifier = modifier.fillMaxSize()) { sceneByKey.forEach { (sceneKey, composableScene) -> diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt index e3a69a964b45..35cefa6b58df 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt @@ -401,10 +401,10 @@ class SceneInteractorTest : SysuiTestCase() { underTest.setVisible(false, "reason") val isVisible by collectLastValue(underTest.isVisible) assertThat(isVisible).isFalse() - underTest.onRemoteUserInteractionStarted("reason") + underTest.onRemoteUserInputStarted("reason") assertThat(isVisible).isTrue() - underTest.onUserInteractionFinished() + underTest.onUserInputFinished() assertThat(isVisible).isFalse() } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt index f856c559454c..832e7b1bcc0c 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt @@ -237,7 +237,7 @@ class SceneContainerViewModelTest : SysuiTestCase() { sceneInteractor.setVisible(false, "reason") runCurrent() assertThat(underTest.isVisible).isFalse() - sceneInteractor.onRemoteUserInteractionStarted("reason") + sceneInteractor.onRemoteUserInputStarted("reason") runCurrent() assertThat(underTest.isVisible).isTrue() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractorTest.kt index 1356e93db549..06a2c5af2986 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractorTest.kt @@ -21,6 +21,7 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.kosmos.testScope +import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.shade.data.repository.shadeRepository import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimRounding @@ -84,4 +85,48 @@ class NotificationStackAppearanceInteractorTest : SysuiTestCase() { ) ) } + + @Test + fun shouldCloseGuts_userInputOngoing_currentGestureInGuts() = + testScope.runTest { + val shouldCloseGuts by collectLastValue(underTest.shouldCloseGuts) + + kosmos.sceneInteractor.onSceneContainerUserInputStarted() + underTest.setCurrentGestureInGuts(true) + + assertThat(shouldCloseGuts).isFalse() + } + + @Test + fun shouldCloseGuts_userInputOngoing_currentGestureNotInGuts() = + testScope.runTest { + val shouldCloseGuts by collectLastValue(underTest.shouldCloseGuts) + + kosmos.sceneInteractor.onSceneContainerUserInputStarted() + underTest.setCurrentGestureInGuts(false) + + assertThat(shouldCloseGuts).isTrue() + } + + @Test + fun shouldCloseGuts_userInputNotOngoing_currentGestureInGuts() = + testScope.runTest { + val shouldCloseGuts by collectLastValue(underTest.shouldCloseGuts) + + kosmos.sceneInteractor.onUserInputFinished() + underTest.setCurrentGestureInGuts(true) + + assertThat(shouldCloseGuts).isFalse() + } + + @Test + fun shouldCloseGuts_userInputNotOngoing_currentGestureNotInGuts() = + testScope.runTest { + val shouldCloseGuts by collectLastValue(underTest.shouldCloseGuts) + + kosmos.sceneInteractor.onUserInputFinished() + underTest.setCurrentGestureInGuts(false) + + assertThat(shouldCloseGuts).isFalse() + } } diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java index ecf816b263ff..298bb3c6fd8b 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java +++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java @@ -231,7 +231,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis // If scene framework is enabled, set the scene container window to // visible and let the touch "slip" into that window. if (SceneContainerFlag.isEnabled()) { - mSceneInteractor.get().onRemoteUserInteractionStarted("launcher swipe"); + mSceneInteractor.get().onRemoteUserInputStarted("launcher swipe"); } else { mShadeViewControllerLazy.get().startInputFocusTransfer(); } @@ -267,7 +267,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis if (SceneContainerFlag.isEnabled()) { int action = event.getActionMasked(); if (action == ACTION_DOWN) { - mSceneInteractor.get().onRemoteUserInteractionStarted( + mSceneInteractor.get().onRemoteUserInputStarted( "trackpad swipe"); } else if (action == ACTION_UP) { mSceneInteractor.get().changeScene( diff --git a/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt b/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt index 3e2c6306467f..beb6816d70a9 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt @@ -56,7 +56,10 @@ constructor( * * For more information see the logic in `SceneInteractor` that mutates this. */ - val isRemoteUserInteractionOngoing = MutableStateFlow(false) + val isRemoteUserInputOngoing = MutableStateFlow(false) + + /** Whether there's ongoing user input on the scene container Composable hierarchy */ + val isSceneContainerUserInputOngoing = MutableStateFlow(false) private val defaultTransitionState = ObservableTransitionState.Idle(config.initialSceneKey) private val _transitionState = MutableStateFlow<Flow<ObservableTransitionState>?>(null) diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt index 1b9c346129c6..4c404e29018d 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt @@ -148,11 +148,11 @@ constructor( val isVisible: StateFlow<Boolean> = combine( repository.isVisible, - repository.isRemoteUserInteractionOngoing, + repository.isRemoteUserInputOngoing, ) { isVisible, isRemoteUserInteractionOngoing -> isVisibleInternal( raw = isVisible, - isRemoteUserInteractionOngoing = isRemoteUserInteractionOngoing, + isRemoteUserInputOngoing = isRemoteUserInteractionOngoing, ) } .stateIn( @@ -162,8 +162,13 @@ constructor( ) /** Whether there's an ongoing remotely-initiated user interaction. */ - val isRemoteUserInteractionOngoing: StateFlow<Boolean> = - repository.isRemoteUserInteractionOngoing + val isRemoteUserInteractionOngoing: StateFlow<Boolean> = repository.isRemoteUserInputOngoing + + /** + * Whether there's an ongoing user interaction started in the scene container Compose hierarchy. + */ + val isSceneContainerUserInputOngoing: StateFlow<Boolean> = + repository.isSceneContainerUserInputOngoing /** * The amount of transition into or out of the given [scene]. @@ -284,7 +289,7 @@ constructor( * Please do not call this from outside of the scene framework. If you are trying to force the * visibility to visible or invisible, prefer making changes to the existing caller of this * method or to upstream state used to calculate [isVisible]; for an example of the latter, - * please see [onRemoteUserInteractionStarted] and [onUserInteractionFinished]. + * please see [onRemoteUserInputStarted] and [onUserInputFinished]. */ fun setVisible(isVisible: Boolean, loggingReason: String) { val wasVisible = repository.isVisible.value @@ -301,6 +306,16 @@ constructor( } /** + * Notifies that a scene container user interaction has begun. + * + * This is a user interaction that originates within the Composable hierarchy of the scene + * container. + */ + fun onSceneContainerUserInputStarted() { + repository.isSceneContainerUserInputOngoing.value = true + } + + /** * Notifies that a remote user interaction has begun. * * This is a user interaction that originates outside of the UI of the scene container and @@ -311,18 +326,19 @@ constructor( * then rerouted by window manager to System UI. While the user interaction definitely continues * within the System UI process and code, it also originates remotely. */ - fun onRemoteUserInteractionStarted(loggingReason: String) { - logger.logRemoteUserInteractionStarted(loggingReason) - repository.isRemoteUserInteractionOngoing.value = true + fun onRemoteUserInputStarted(loggingReason: String) { + logger.logRemoteUserInputStarted(loggingReason) + repository.isRemoteUserInputOngoing.value = true } /** * Notifies that the current user interaction (internally or remotely started, see - * [onRemoteUserInteractionStarted]) has finished. + * [onSceneContainerUserInputStarted] and [onRemoteUserInputStarted]) has finished. */ - fun onUserInteractionFinished() { - logger.logUserInteractionFinished() - repository.isRemoteUserInteractionOngoing.value = false + fun onUserInputFinished() { + logger.logUserInputFinished() + repository.isSceneContainerUserInputOngoing.value = false + repository.isRemoteUserInputOngoing.value = false } /** @@ -351,9 +367,9 @@ constructor( private fun isVisibleInternal( raw: Boolean = repository.isVisible.value, - isRemoteUserInteractionOngoing: Boolean = repository.isRemoteUserInteractionOngoing.value, + isRemoteUserInputOngoing: Boolean = repository.isRemoteUserInputOngoing.value, ): Boolean { - return raw || isRemoteUserInteractionOngoing + return raw || isRemoteUserInputOngoing } /** diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt index 94c94e22a10b..045a8879f572 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt @@ -115,7 +115,7 @@ class SceneLogger @Inject constructor(@SceneFrameworkLog private val logBuffer: ) } - fun logRemoteUserInteractionStarted( + fun logRemoteUserInputStarted( reason: String, ) { logBuffer.log( @@ -126,7 +126,7 @@ class SceneLogger @Inject constructor(@SceneFrameworkLog private val logBuffer: ) } - fun logUserInteractionFinished() { + fun logUserInputFinished() { logBuffer.log( tag = TAG, level = LogLevel.INFO, diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt index f8a9f8c5a279..74ed5b57026c 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt @@ -29,6 +29,7 @@ import com.android.systemui.power.domain.interactor.PowerInteractor import com.android.systemui.scene.domain.interactor.SceneInteractor import com.android.systemui.scene.shared.logger.SceneLogger import com.android.systemui.scene.shared.model.Scenes +import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject @@ -93,7 +94,9 @@ constructor( } /** - * Notifies that a [MotionEvent] is first seen at the top of the scene container UI. + * Notifies that a [MotionEvent] is first seen at the top of the scene container UI. This + * includes gestures on [SharedNotificationContainer] as well as the Composable scene container + * hierarchy. * * Call this before the [MotionEvent] starts to propagate through the UI hierarchy. */ @@ -104,11 +107,21 @@ constructor( event.actionMasked == MotionEvent.ACTION_UP || event.actionMasked == MotionEvent.ACTION_CANCEL ) { - sceneInteractor.onUserInteractionFinished() + sceneInteractor.onUserInputFinished() } } /** + * Notifies that a scene container user interaction has begun. + * + * This is a user interaction that has reached the Composable hierarchy of the scene container, + * rather than being handled by [SharedNotificationContainer]. + */ + fun onSceneContainerUserInputStarted() { + sceneInteractor.onSceneContainerUserInputStarted() + } + + /** * Notifies that a [MotionEvent] that was previously sent to [onMotionEvent] has passed through * the scene container UI. * 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 f26f8405e299..9efe7dcd8e1d 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 @@ -1253,6 +1253,11 @@ public class NotificationStackScrollLayout } @Override + public void closeGutsOnSceneTouch() { + mController.closeControlsDueToOutsideTouch(); + } + + @Override public void setSyntheticScrollConsumer(@Nullable Consumer<Float> consumer) { mScrollViewFields.setSyntheticScrollConsumer(consumer); } @@ -1263,6 +1268,11 @@ public class NotificationStackScrollLayout } @Override + public void setCurrentGestureInGutsConsumer(@Nullable Consumer<Boolean> consumer) { + mScrollViewFields.setCurrentGestureInGutsConsumer(consumer); + } + + @Override public void setHeadsUpHeightConsumer(@Nullable Consumer<Float> consumer) { mScrollViewFields.setHeadsUpHeightConsumer(consumer); } @@ -3537,33 +3547,41 @@ public class NotificationStackScrollLayout @Override public boolean dispatchTouchEvent(MotionEvent ev) { - if (SceneContainerFlag.isEnabled() && mIsBeingDragged) { + if (SceneContainerFlag.isEnabled()) { int action = ev.getActionMasked(); - boolean isUpOrCancel = action == ACTION_UP || action == ACTION_CANCEL; - if (mSendingTouchesToSceneFramework) { - MotionEvent adjustedEvent = MotionEvent.obtain(ev); - adjustedEvent.setLocation(ev.getRawX(), ev.getRawY()); - mController.sendTouchToSceneFramework(adjustedEvent); - mScrollViewFields.sendCurrentGestureOverscroll( - getExpandedInThisMotion() && !isUpOrCancel); - adjustedEvent.recycle(); - } else if (!isUpOrCancel) { - // if this is the first touch being sent to the scene framework, - // convert it into a synthetic DOWN event. - mSendingTouchesToSceneFramework = true; - MotionEvent downEvent = MotionEvent.obtain(ev); - downEvent.setAction(MotionEvent.ACTION_DOWN); - downEvent.setLocation(ev.getRawX(), ev.getRawY()); - mController.sendTouchToSceneFramework(downEvent); - mScrollViewFields.sendCurrentGestureOverscroll(getExpandedInThisMotion()); - downEvent.recycle(); - } - - if (isUpOrCancel) { - mScrollViewFields.sendCurrentGestureOverscroll(false); - setIsBeingDragged(false); + boolean isTouchInGuts = mController.isTouchInGutsView(ev); + if (action == MotionEvent.ACTION_DOWN && !isTouchInGuts) { + mController.closeControlsDueToOutsideTouch(); + } + if (mIsBeingDragged) { + boolean isUpOrCancel = action == ACTION_UP || action == ACTION_CANCEL; + if (mSendingTouchesToSceneFramework) { + MotionEvent adjustedEvent = MotionEvent.obtain(ev); + adjustedEvent.setLocation(ev.getRawX(), ev.getRawY()); + mScrollViewFields.sendCurrentGestureOverscroll( + getExpandedInThisMotion() && !isUpOrCancel); + mController.sendTouchToSceneFramework(adjustedEvent); + adjustedEvent.recycle(); + } else if (!isUpOrCancel) { + // if this is the first touch being sent to the scene framework, + // convert it into a synthetic DOWN event. + mSendingTouchesToSceneFramework = true; + MotionEvent downEvent = MotionEvent.obtain(ev); + downEvent.setAction(MotionEvent.ACTION_DOWN); + downEvent.setLocation(ev.getRawX(), ev.getRawY()); + mScrollViewFields.sendCurrentGestureInGuts(isTouchInGuts); + mScrollViewFields.sendCurrentGestureOverscroll(getExpandedInThisMotion()); + mController.sendTouchToSceneFramework(downEvent); + downEvent.recycle(); + } + + if (isUpOrCancel) { + mScrollViewFields.sendCurrentGestureInGuts(false); + mScrollViewFields.sendCurrentGestureOverscroll(false); + setIsBeingDragged(false); + } + return false; } - return false; } return TouchLogger.logDispatchTouch(TAG, ev, super.dispatchTouchEvent(ev)); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java index 55f05662f0c6..b62d03b94cd9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java @@ -1688,7 +1688,7 @@ public class NotificationStackScrollLayoutController implements Dumpable { mVisibilityProvider.obtain(entry, true)); } - public void closeControlsIfOutsideTouch(MotionEvent ev) { + private View getGutsView() { NotificationGuts guts = mNotificationGutsManager.getExposedGuts(); NotificationMenuRowPlugin menuRow = mSwipeHelper.getCurrentMenuRow(); View translatingParentView = mSwipeHelper.getTranslatingParentView(); @@ -1701,15 +1701,35 @@ public class NotificationStackScrollLayoutController implements Dumpable { // Checking menu view = translatingParentView; } + return view; + } + + public void closeControlsIfOutsideTouch(MotionEvent ev) { + SceneContainerFlag.assertInLegacyMode(); + View view = getGutsView(); if (view != null && !NotificationSwipeHelper.isTouchInView(ev, view)) { // Touch was outside visible guts / menu notification, close what's visible - mNotificationGutsManager.closeAndSaveGuts(false /* removeLeavebehind */, - false /* force */, true /* removeControls */, -1 /* x */, -1 /* y */, - false /* resetMenu */); - mSwipeHelper.resetExposedMenuView(true /* animate */, true /* force */); + closeAndSaveGuts(); } } + void closeControlsDueToOutsideTouch() { + if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return; + closeAndSaveGuts(); + } + + private void closeAndSaveGuts() { + mNotificationGutsManager.closeAndSaveGuts(false /* removeLeavebehind */, + false /* force */, true /* removeControls */, -1 /* x */, -1 /* y */, + false /* resetMenu */); + mSwipeHelper.resetExposedMenuView(true /* animate */, true /* force */); + } + + boolean isTouchInGutsView(MotionEvent event) { + View view = getGutsView(); + return NotificationSwipeHelper.isTouchInView(event, view); + } + public void clearSilentNotifications() { FooterViewRefactor.assertInLegacyMode(); // Leave the shade open if there will be other notifs left over to clear 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 383d8b3b26b5..aa3953987c10 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 @@ -53,6 +53,11 @@ class ScrollViewFields { */ var currentGestureOverscrollConsumer: Consumer<Boolean>? = null /** + * When a gesture is on open notification guts, which means scene container should not close the + * guts off of this gesture, we can notify the placeholder through here. + */ + var currentGestureInGutsConsumer: Consumer<Boolean>? = null + /** * Any time the heads up height is recalculated, it should be updated here to be used by the * placeholder */ @@ -66,6 +71,10 @@ class ScrollViewFields { fun sendCurrentGestureOverscroll(isCurrentGestureOverscroll: Boolean) = currentGestureOverscrollConsumer?.accept(isCurrentGestureOverscroll) + /** send [isCurrentGestureInGuts] to the [currentGestureInGutsConsumer], if present. */ + fun sendCurrentGestureInGuts(isCurrentGestureInGuts: Boolean) = + currentGestureInGutsConsumer?.accept(isCurrentGestureInGuts) + /** send the [headsUpHeight] to the [headsUpHeightConsumer], if present. */ fun sendHeadsUpHeight(headsUpHeight: Float) = headsUpHeightConsumer?.accept(headsUpHeight) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationViewHeightRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationViewHeightRepository.kt index f6d9351952f1..4907d444070d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationViewHeightRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationViewHeightRepository.kt @@ -39,4 +39,7 @@ class NotificationViewHeightRepository @Inject constructor() { * consumed part of the gesture. */ val isCurrentGestureOverscroll = MutableStateFlow(false) + + /** Whether the current touch gesture is on any open notification guts. */ + val isCurrentGestureInGuts = MutableStateFlow(false) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt index 8557afc6ebd3..756cd87970a4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt @@ -18,6 +18,7 @@ package com.android.systemui.statusbar.notification.stack.domain.interactor import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.scene.domain.interactor.SceneInteractor import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.shade.shared.model.ShadeMode import com.android.systemui.statusbar.notification.stack.data.repository.NotificationPlaceholderRepository @@ -39,6 +40,7 @@ class NotificationStackAppearanceInteractor constructor( private val viewHeightRepository: NotificationViewHeightRepository, private val placeholderRepository: NotificationPlaceholderRepository, + sceneInteractor: SceneInteractor, shadeInteractor: ShadeInteractor, ) { /** The bounds of the notification stack in the current scene. */ @@ -93,6 +95,15 @@ constructor( val isCurrentGestureOverscroll: Flow<Boolean> = viewHeightRepository.isCurrentGestureOverscroll.asStateFlow() + /** Whether we should close any notification guts that are currently open. */ + val shouldCloseGuts: Flow<Boolean> = + combine( + sceneInteractor.isSceneContainerUserInputOngoing, + viewHeightRepository.isCurrentGestureInGuts + ) { isUserInputOngoing, isCurrentGestureInGuts -> + isUserInputOngoing && !isCurrentGestureInGuts + } + /** Sets the alpha to apply to the NSSL for the brightness mirror */ fun setAlphaForBrightnessMirror(alpha: Float) { placeholderRepository.alphaForBrightnessMirror.value = alpha @@ -119,6 +130,10 @@ constructor( viewHeightRepository.isCurrentGestureOverscroll.value = isOverscroll } + fun setCurrentGestureInGuts(isInGuts: Boolean) { + viewHeightRepository.isCurrentGestureInGuts.value = isInGuts + } + fun setConstrainedAvailableSpace(height: Int) { placeholderRepository.constrainedAvailableSpace.value = height } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt index 1289cec3a282..235b4da3f029 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt @@ -71,6 +71,9 @@ interface NotificationScrollView { /** Set a consumer for current gesture overscroll events */ fun setCurrentGestureOverscrollConsumer(consumer: Consumer<Boolean>?) + /** Set a consumer for current gesture in guts events */ + fun setCurrentGestureInGutsConsumer(consumer: Consumer<Boolean>?) + /** Set a consumer for heads up height changed events */ fun setHeadsUpHeightConsumer(consumer: Consumer<Float>?) @@ -92,6 +95,12 @@ interface NotificationScrollView { /** Gets the inset for HUNs when they are not visible */ fun getHeadsUpInset(): Int + /** + * Signals that any open Notification guts should be closed, as scene container is handling + * touch events. + */ + fun closeGutsOnSceneTouch() + /** Adds a listener to be notified, when the stack height might have changed. */ fun addStackHeightChangedListener(runnable: Runnable) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt index c044f6f6a9b1..3cc6e81986c5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt @@ -36,6 +36,7 @@ import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.DisposableHandle import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.filter import kotlinx.coroutines.launch /** Binds the [NotificationScrollView]. */ @@ -98,13 +99,18 @@ constructor( .filter { it } .collect { view.setStackTop(-(view.getHeadsUpInset().toFloat())) } } + launch { + viewModel.shouldCloseGuts.filter { it }.collect { view.closeGutsOnSceneTouch() } + } launchAndDispose { view.setSyntheticScrollConsumer(viewModel.syntheticScrollConsumer) view.setCurrentGestureOverscrollConsumer(viewModel.currentGestureOverscrollConsumer) + view.setCurrentGestureInGutsConsumer(viewModel.currentGestureInGutsConsumer) DisposableHandle { view.setSyntheticScrollConsumer(null) view.setCurrentGestureOverscrollConsumer(null) + view.setCurrentGestureInGutsConsumer(null) } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt index 0e984cf74d9d..d9d639efd78f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt @@ -132,6 +132,9 @@ constructor( val qsExpandFraction: Flow<Float> = shadeInteractor.qsExpansion.dumpWhileCollecting("qsExpandFraction") + /** Whether we should close any open notification guts. */ + val shouldCloseGuts: Flow<Boolean> = stackAppearanceInteractor.shouldCloseGuts + val shouldResetStackTop: Flow<Boolean> = sceneInteractor.transitionState .mapNotNull { state -> @@ -200,6 +203,10 @@ constructor( val currentGestureOverscrollConsumer: (Boolean) -> Unit = stackAppearanceInteractor::setCurrentGestureOverscroll + /** Receives whether the current touch gesture is inside any open guts. */ + val currentGestureInGutsConsumer: (Boolean) -> Unit = + stackAppearanceInteractor::setCurrentGestureInGuts + /** Whether the notification stack is scrollable or not. */ val isScrollable: Flow<Boolean> = sceneInteractor.currentScene diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManagerTest.kt index 230ddf9d25db..48c2cc7f11c4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManagerTest.kt @@ -56,11 +56,11 @@ class StatusBarTouchableRegionManagerTest : SysuiTestCase() { runCurrent() assertThat(underTest.shouldMakeEntireScreenTouchable()).isFalse() - sceneRepository.isRemoteUserInteractionOngoing.value = true + sceneRepository.isRemoteUserInputOngoing.value = true runCurrent() assertThat(underTest.shouldMakeEntireScreenTouchable()).isTrue() - sceneRepository.isRemoteUserInteractionOngoing.value = false + sceneRepository.isRemoteUserInputOngoing.value = false runCurrent() assertThat(underTest.shouldMakeEntireScreenTouchable()).isFalse() } @@ -71,7 +71,7 @@ class StatusBarTouchableRegionManagerTest : SysuiTestCase() { testScope.runTest { assertThat(underTest.shouldMakeEntireScreenTouchable()).isFalse() - sceneRepository.isRemoteUserInteractionOngoing.value = true + sceneRepository.isRemoteUserInputOngoing.value = true runCurrent() assertThat(underTest.shouldMakeEntireScreenTouchable()).isFalse() diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractorKosmos.kt index dbfd9de2aa8c..2772d3698d88 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractorKosmos.kt @@ -18,6 +18,7 @@ package com.android.systemui.statusbar.notification.stack.domain.interactor import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture +import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.shade.domain.interactor.shadeInteractor import com.android.systemui.statusbar.notification.stack.data.repository.notificationPlaceholderRepository import com.android.systemui.statusbar.notification.stack.data.repository.notificationViewHeightRepository @@ -26,6 +27,7 @@ val Kosmos.notificationStackAppearanceInteractor by Fixture { NotificationStackAppearanceInteractor( viewHeightRepository = notificationViewHeightRepository, placeholderRepository = notificationPlaceholderRepository, + sceneInteractor = sceneInteractor, shadeInteractor = shadeInteractor, ) } |