summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt3
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt17
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt29
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt58
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt20
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt6
8 files changed, 131 insertions, 11 deletions
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
index fff0a316cbf4..667f516317be 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -615,6 +615,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
private fun TestScope.emulatePendingTransitionProgress(
expectedVisible: Boolean = true,
) {
+ val isVisible by collectLastValue(sceneContainerViewModel.isVisible)
assertWithMessage("The FakeSceneDataSource has to be paused for this to do anything.")
.that(fakeSceneDataSource.isPaused)
.isTrue()
@@ -651,7 +652,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
runCurrent()
assertWithMessage("Visibility mismatch after scene transition from $from to $to!")
- .that(sceneContainerViewModel.isVisible.value)
+ .that(isVisible)
.isEqualTo(expectedVisible)
assertThat(sceneContainerViewModel.currentScene.value).isEqualTo(to)
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 dd3eb6845789..db94c39e1cb1 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
@@ -14,6 +14,8 @@
* limitations under the License.
*/
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
package com.android.systemui.scene.domain.interactor
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -31,6 +33,7 @@ import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.fakeSceneDataSource
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.runCurrent
@@ -275,4 +278,18 @@ class SceneInteractorTest : SysuiTestCase() {
underTest.setVisible(true, "reason")
assertThat(isVisible).isTrue()
}
+
+ @Test
+ fun isVisible_duringRemoteUserInteraction_forcedVisible() =
+ testScope.runTest {
+ underTest.setVisible(false, "reason")
+ val isVisible by collectLastValue(underTest.isVisible)
+ assertThat(isVisible).isFalse()
+ underTest.onRemoteUserInteractionStarted("reason")
+ assertThat(isVisible).isTrue()
+
+ underTest.onUserInteractionFinished()
+
+ 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 ffbdafe338e7..27ae8b60009c 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
@@ -14,10 +14,9 @@
* limitations under the License.
*/
-@file:OptIn(ExperimentalCoroutinesApi::class)
-
package com.android.systemui.scene.ui.viewmodel
+import android.view.MotionEvent
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -35,9 +34,9 @@ import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.fakeSceneDataSource
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -50,7 +49,7 @@ class SceneContainerViewModelTest : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope by lazy { kosmos.testScope }
- private val interactor by lazy { kosmos.sceneInteractor }
+ private val sceneInteractor by lazy { kosmos.sceneInteractor }
private val fakeSceneDataSource = kosmos.fakeSceneDataSource
private val sceneContainerConfig = kosmos.sceneContainerConfig
private val falsingManager = kosmos.fakeFalsingManager
@@ -62,7 +61,7 @@ class SceneContainerViewModelTest : SysuiTestCase() {
kosmos.fakeSceneContainerFlags.enabled = true
underTest =
SceneContainerViewModel(
- sceneInteractor = interactor,
+ sceneInteractor = sceneInteractor,
falsingInteractor = kosmos.falsingInteractor,
powerInteractor = kosmos.powerInteractor,
)
@@ -74,10 +73,10 @@ class SceneContainerViewModelTest : SysuiTestCase() {
val isVisible by collectLastValue(underTest.isVisible)
assertThat(isVisible).isTrue()
- interactor.setVisible(false, "reason")
+ sceneInteractor.setVisible(false, "reason")
assertThat(isVisible).isFalse()
- interactor.setVisible(true, "reason")
+ sceneInteractor.setVisible(true, "reason")
assertThat(isVisible).isTrue()
}
@@ -199,4 +198,20 @@ class SceneContainerViewModelTest : SysuiTestCase() {
underTest.onMotionEvent(mock())
assertThat(kosmos.fakePowerRepository.userTouchRegistered).isTrue()
}
+
+ @Test
+ fun remoteUserInteraction_keepsContainerVisible() =
+ testScope.runTest {
+ sceneInteractor.setVisible(false, "reason")
+ val isVisible by collectLastValue(underTest.isVisible)
+ assertThat(isVisible).isFalse()
+ sceneInteractor.onRemoteUserInteractionStarted("reason")
+ assertThat(isVisible).isTrue()
+
+ underTest.onMotionEvent(
+ mock { whenever(actionMasked).thenReturn(MotionEvent.ACTION_UP) }
+ )
+
+ assertThat(isVisible).isFalse()
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index 000f3c09d84c..5f8b5ddc9de6 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -221,7 +221,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 (mSceneContainerFlags.isEnabled()) {
- mSceneInteractor.get().setVisible(true, "swipe down on launcher");
+ mSceneInteractor.get().onRemoteUserInteractionStarted("launcher swipe");
} else {
mShadeViewControllerLazy.get().startInputFocusTransfer();
}
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 a3021946713f..e60dff183148 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
@@ -49,6 +49,13 @@ constructor(
private val _isVisible = MutableStateFlow(true)
val isVisible: StateFlow<Boolean> = _isVisible.asStateFlow()
+ /**
+ * Whether there's an ongoing remotely-initiated user interaction.
+ *
+ * For more information see the logic in `SceneInteractor` that mutates this.
+ */
+ val isRemoteUserInteractionOngoing = MutableStateFlow(false)
+
private val defaultTransitionState = ObservableTransitionState.Idle(config.initialSceneKey)
private val _transitionState = MutableStateFlow<Flow<ObservableTransitionState>?>(null)
val transitionState: StateFlow<ObservableTransitionState> =
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 0add4443fa7a..6b7c672fbfe0 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
@@ -31,6 +31,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
@@ -121,7 +122,21 @@ constructor(
)
/** Whether the scene container is visible. */
- val isVisible: StateFlow<Boolean> = repository.isVisible
+ val isVisible: StateFlow<Boolean> =
+ combine(
+ repository.isVisible,
+ repository.isRemoteUserInteractionOngoing,
+ ) { isVisible, isRemoteUserInteractionOngoing ->
+ isVisibleInternal(
+ raw = isVisible,
+ isRemoteUserInteractionOngoing = isRemoteUserInteractionOngoing,
+ )
+ }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = isVisibleInternal()
+ )
/**
* Returns the keys of all scenes in the container.
@@ -164,7 +179,14 @@ constructor(
repository.changeScene(toScene, transitionKey)
}
- /** Sets the visibility of the container. */
+ /**
+ * Sets the visibility of the container.
+ *
+ * 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].
+ */
fun setVisible(isVisible: Boolean, loggingReason: String) {
val wasVisible = repository.isVisible.value
if (wasVisible == isVisible) {
@@ -180,6 +202,31 @@ constructor(
}
/**
+ * Notifies that a remote user interaction has begun.
+ *
+ * This is a user interaction that originates outside of the UI of the scene container and
+ * possibly outside of the System UI process itself.
+ *
+ * As an example, consider the dragging that can happen in the launcher that expands the shade.
+ * This is a user interaction that begins remotely (as it starts in the launcher process) and is
+ * 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
+ }
+
+ /**
+ * Notifies that the current user interaction (internally or remotely started, see
+ * [onRemoteUserInteractionStarted]) has finished.
+ */
+ fun onUserInteractionFinished() {
+ logger.logUserInteractionFinished()
+ repository.isRemoteUserInteractionOngoing.value = false
+ }
+
+ /**
* Binds the given flow so the system remembers it.
*
* Note that you must call is with `null` when the UI is done or risk a memory leak.
@@ -187,4 +234,11 @@ constructor(
fun setTransitionState(transitionState: Flow<ObservableTransitionState>?) {
repository.setTransitionState(transitionState)
}
+
+ private fun isVisibleInternal(
+ raw: Boolean = repository.isVisible.value,
+ isRemoteUserInteractionOngoing: Boolean = repository.isRemoteUserInteractionOngoing.value,
+ ): Boolean {
+ return raw || isRemoteUserInteractionOngoing
+ }
}
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 d59fcff34796..cbf7b3e7a971 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
@@ -95,6 +95,26 @@ class SceneLogger @Inject constructor(@SceneFrameworkLog private val logBuffer:
)
}
+ fun logRemoteUserInteractionStarted(
+ reason: String,
+ ) {
+ logBuffer.log(
+ tag = TAG,
+ level = LogLevel.INFO,
+ messageInitializer = { str1 = reason },
+ messagePrinter = { "remote user interaction started, reason: $str3" },
+ )
+ }
+
+ fun logUserInteractionFinished() {
+ logBuffer.log(
+ tag = TAG,
+ level = LogLevel.INFO,
+ messageInitializer = {},
+ messagePrinter = { "user interaction finished" },
+ )
+ }
+
companion object {
private const val TAG = "SceneFramework"
}
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 4cd3baa7a52e..91861aa5c29a 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
@@ -68,6 +68,12 @@ constructor(
fun onMotionEvent(event: MotionEvent) {
powerInteractor.onUserTouch()
falsingInteractor.onTouchEvent(event)
+ if (
+ event.actionMasked == MotionEvent.ACTION_UP ||
+ event.actionMasked == MotionEvent.ACTION_CANCEL
+ ) {
+ sceneInteractor.onUserInteractionFinished()
+ }
}
/**