diff options
9 files changed, 109 insertions, 7 deletions
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 b632a8aaad46..af895c82c975 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 @@ -21,6 +21,8 @@ package com.android.systemui.scene.ui.viewmodel import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import android.view.MotionEvent +import android.view.MotionEvent.ACTION_DOWN +import android.view.MotionEvent.ACTION_OUTSIDE import android.view.View import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest @@ -43,6 +45,7 @@ import com.android.systemui.shade.data.repository.fakeShadeRepository import com.android.systemui.shade.domain.interactor.shadeInteractor import com.android.systemui.shade.shared.flag.DualShade import com.android.systemui.shade.shared.model.ShadeMode +import com.android.systemui.statusbar.data.repository.fakeRemoteInputRepository import com.android.systemui.testKosmos import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever @@ -68,6 +71,7 @@ class SceneContainerViewModelTest : SysuiTestCase() { private val fakeSceneDataSource by lazy { kosmos.fakeSceneDataSource } private val fakeShadeRepository by lazy { kosmos.fakeShadeRepository } private val sceneContainerConfig by lazy { kosmos.sceneContainerConfig } + private val fakeRemoteInputRepository by lazy { kosmos.fakeRemoteInputRepository } private val falsingManager by lazy { kosmos.fakeFalsingManager } private val view = mock<View>() @@ -234,6 +238,35 @@ class SceneContainerViewModelTest : SysuiTestCase() { } @Test + fun userInputOnEmptySpace_insideEvent() = + testScope.runTest { + assertThat(fakeRemoteInputRepository.areRemoteInputsClosed).isFalse() + val insideMotionEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0f, 0f, 0) + underTest.onEmptySpaceMotionEvent(insideMotionEvent) + assertThat(fakeRemoteInputRepository.areRemoteInputsClosed).isFalse() + } + + @Test + fun userInputOnEmptySpace_outsideEvent_remoteInputActive() = + testScope.runTest { + fakeRemoteInputRepository.isRemoteInputActive.value = true + assertThat(fakeRemoteInputRepository.areRemoteInputsClosed).isFalse() + val outsideMotionEvent = MotionEvent.obtain(0, 0, ACTION_OUTSIDE, 0f, 0f, 0) + underTest.onEmptySpaceMotionEvent(outsideMotionEvent) + assertThat(fakeRemoteInputRepository.areRemoteInputsClosed).isTrue() + } + + @Test + fun userInputOnEmptySpace_outsideEvent_remoteInputInactive() = + testScope.runTest { + fakeRemoteInputRepository.isRemoteInputActive.value = false + assertThat(fakeRemoteInputRepository.areRemoteInputsClosed).isFalse() + val outsideMotionEvent = MotionEvent.obtain(0, 0, ACTION_OUTSIDE, 0f, 0f, 0) + underTest.onEmptySpaceMotionEvent(outsideMotionEvent) + assertThat(fakeRemoteInputRepository.areRemoteInputsClosed).isFalse() + } + + @Test fun remoteUserInteraction_keepsContainerVisible() = testScope.runTest { sceneInteractor.setVisible(false, "reason") diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/RemoteInputRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/RemoteInputRepositoryImplTest.kt index f7a8858a2741..3b720ef30db7 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/RemoteInputRepositoryImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/RemoteInputRepositoryImplTest.kt @@ -50,7 +50,7 @@ class RemoteInputRepositoryImplTest : SysuiTestCase() { MockitoAnnotations.initMocks(this) testScope = TestScope() - underTest = RemoteInputRepositoryImpl(remoteInputManager) + underTest = RemoteInputRepositoryImpl(testScope.backgroundScope, remoteInputManager) } @Test diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt index cd38ca6cadef..7d7cab41cf96 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt @@ -77,6 +77,11 @@ class SceneWindowRootView(context: Context, attrs: AttributeSet?) : WindowRootVi } } + override fun onTouchEvent(event: MotionEvent?): Boolean { + event?.let { motionEventHandler?.onEmptySpaceMotionEvent(it) } + return super.onTouchEvent(event) + } + companion object { private const val TAG = "SceneWindowRootView" } 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 82f65cf55211..32d5cb460cd8 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 @@ -39,6 +39,7 @@ import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.scene.ui.composable.Overlay import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.shade.shared.model.ShadeMode +import com.android.systemui.statusbar.domain.interactor.RemoteInputInteractor import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer import dagger.assisted.Assisted import dagger.assisted.AssistedFactory @@ -48,7 +49,6 @@ import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.map -import com.android.app.tracing.coroutines.launchTraced as launch /** Models UI state for the scene container. */ class SceneContainerViewModel @@ -58,6 +58,7 @@ constructor( private val falsingInteractor: FalsingInteractor, private val powerInteractor: PowerInteractor, shadeInteractor: ShadeInteractor, + private val remoteInputInteractor: RemoteInputInteractor, private val splitEdgeDetector: SplitEdgeDetector, private val logger: SceneLogger, hapticsViewModelFactory: SceneContainerHapticsViewModel.Factory, @@ -101,6 +102,10 @@ constructor( this@SceneContainerViewModel.onMotionEvent(motionEvent) } + override fun onEmptySpaceMotionEvent(motionEvent: MotionEvent) { + this@SceneContainerViewModel.onEmptySpaceMotionEvent(motionEvent) + } + override fun onMotionEventComplete() { this@SceneContainerViewModel.onMotionEventComplete() } @@ -147,6 +152,23 @@ constructor( } /** + * Notifies that a [MotionEvent] has propagated through the entire [SharedNotificationContainer] + * and Composable scene container hierarchy without being handled. + * + * Call this after the [MotionEvent] has finished propagating through the UI hierarchy. + */ + fun onEmptySpaceMotionEvent(event: MotionEvent) { + // check if the touch is outside the window and if remote input is active. + // If true, close any active remote inputs. + if ( + event.action == MotionEvent.ACTION_OUTSIDE && + (remoteInputInteractor.isRemoteInputActive as StateFlow).value + ) { + remoteInputInteractor.closeRemoteInputs() + } + } + + /** * Notifies that a scene container user interaction has begun. * * This is a user interaction that has reached the Composable hierarchy of the scene container, @@ -263,6 +285,9 @@ constructor( /** Notifies that a [MotionEvent] has occurred. */ fun onMotionEvent(motionEvent: MotionEvent) + /** Notifies that a [MotionEvent] has occurred outside the root window. */ + fun onEmptySpaceMotionEvent(motionEvent: MotionEvent) + /** * Notifies that the previous [MotionEvent] reported by [onMotionEvent] has finished * processing. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java index 9b1e782fcba4..fdc1c0e4dd22 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java @@ -53,6 +53,7 @@ import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.power.domain.interactor.PowerInteractor; import com.android.systemui.res.R; +import com.android.systemui.scene.shared.flag.SceneContainerFlag; import com.android.systemui.shade.domain.interactor.ShadeInteractor; import com.android.systemui.statusbar.dagger.CentralSurfacesDependenciesModule; import com.android.systemui.statusbar.notification.NotifPipelineFlags; @@ -686,6 +687,7 @@ public class NotificationRemoteInputManager implements CoreStartable { } public void checkRemoteInputOutside(MotionEvent event) { + SceneContainerFlag.assertInLegacyMode(); if (event.getAction() == MotionEvent.ACTION_OUTSIDE // touch outside the source bar && event.getX() == 0 && event.getY() == 0 // a touch outside both bars && isRemoteInputActive()) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/RemoteInputRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/RemoteInputRepository.kt index 9af4b8c18c86..cd1e2ceeca00 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/RemoteInputRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/RemoteInputRepository.kt @@ -16,16 +16,21 @@ package com.android.systemui.statusbar.data.repository -import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.statusbar.NotificationRemoteInputManager import com.android.systemui.statusbar.RemoteInputController +import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import dagger.Binds import dagger.Module import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.stateIn /** * Repository used for tracking the state of notification remote input (e.g. when the user presses @@ -42,30 +47,50 @@ interface RemoteInputRepository { val remoteInputRowBottomBound: Flow<Float?> fun setRemoteInputRowBottomBound(bottom: Float?) + + /** Close any active remote inputs */ + fun closeRemoteInputs() } @SysUISingleton class RemoteInputRepositoryImpl @Inject -constructor(private val notificationRemoteInputManager: NotificationRemoteInputManager) : - RemoteInputRepository { - override val isRemoteInputActive: Flow<Boolean> = conflatedCallbackFlow { - trySend(false) // initial value is false +constructor( + @Application applicationScope: CoroutineScope, + private val notificationRemoteInputManager: NotificationRemoteInputManager, +) : RemoteInputRepository { + private val _isRemoteInputActive = conflatedCallbackFlow { val callback = object : RemoteInputController.Callback { override fun onRemoteInputActive(active: Boolean) { trySend(active) } } + trySend(notificationRemoteInputManager.isRemoteInputActive) notificationRemoteInputManager.addControllerCallback(callback) awaitClose { notificationRemoteInputManager.removeControllerCallback(callback) } } + override val isRemoteInputActive = + if (SceneContainerFlag.isEnabled) { + _isRemoteInputActive.stateIn( + applicationScope, + SharingStarted.WhileSubscribed(), + notificationRemoteInputManager.isRemoteInputActive, + ) + } else { + _isRemoteInputActive + } + override val remoteInputRowBottomBound = MutableStateFlow<Float?>(null) override fun setRemoteInputRowBottomBound(bottom: Float?) { remoteInputRowBottomBound.value = bottom } + + override fun closeRemoteInputs() { + notificationRemoteInputManager.closeRemoteInputs() + } } @Module diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/domain/interactor/RemoteInputInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/domain/interactor/RemoteInputInteractor.kt index b83b0cc8d2c9..ea8c3e2cef91 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/domain/interactor/RemoteInputInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/domain/interactor/RemoteInputInteractor.kt @@ -40,4 +40,9 @@ constructor(private val remoteInputRepository: RemoteInputRepository) { fun setRemoteInputRowBottomBound(bottom: Float?) { remoteInputRepository.setRemoteInputRowBottomBound(bottom) } + + /** Close any active remote inputs */ + fun closeRemoteInputs() { + remoteInputRepository.closeRemoteInputs() + } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt index 4f414d995aab..3300c96b87fd 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt @@ -17,6 +17,7 @@ import com.android.systemui.scene.ui.viewmodel.SceneContainerHapticsViewModel import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel import com.android.systemui.scene.ui.viewmodel.splitEdgeDetector import com.android.systemui.shade.domain.interactor.shadeInteractor +import com.android.systemui.statusbar.domain.interactor.remoteInputInteractor import kotlinx.coroutines.flow.MutableStateFlow import org.mockito.kotlin.mock @@ -87,6 +88,7 @@ val Kosmos.sceneContainerViewModelFactory by Fixture { falsingInteractor = falsingInteractor, powerInteractor = powerInteractor, shadeInteractor = shadeInteractor, + remoteInputInteractor = remoteInputInteractor, splitEdgeDetector = splitEdgeDetector, logger = sceneLogger, hapticsViewModelFactory = sceneContainerHapticsViewModelFactory, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeRemoteInputRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeRemoteInputRepository.kt index 91602c23ac17..375bede594b3 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeRemoteInputRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeRemoteInputRepository.kt @@ -23,6 +23,11 @@ import kotlinx.coroutines.flow.flowOf class FakeRemoteInputRepository : RemoteInputRepository { override val isRemoteInputActive = MutableStateFlow(false) override val remoteInputRowBottomBound: Flow<Float?> = flowOf(null) + var areRemoteInputsClosed: Boolean = false override fun setRemoteInputRowBottomBound(bottom: Float?) {} + + override fun closeRemoteInputs() { + areRemoteInputsClosed = true + } } |