summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt26
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt22
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt71
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt4
6 files changed, 100 insertions, 40 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 cd84abc50802..aee3ce052d78 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -133,6 +133,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
sceneInteractor = sceneInteractor,
falsingInteractor = kosmos.falsingInteractor,
powerInteractor = kosmos.powerInteractor,
+ motionEventHandlerReceiver = {},
)
.apply { setTransitionState(transitionState) }
}
@@ -199,6 +200,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
shadeSceneContentViewModel.activateIn(testScope)
shadeSceneActionsViewModel.activateIn(testScope)
bouncerSceneContentViewModel.activateIn(testScope)
+ sceneContainerViewModel.activateIn(testScope)
assertWithMessage("Initial scene key mismatch!")
.that(sceneContainerViewModel.currentScene.value)
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 ea95aab4a1c4..f85823ad7c33 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,6 +14,8 @@
* limitations under the License.
*/
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
package com.android.systemui.scene.ui.viewmodel
import android.view.MotionEvent
@@ -25,6 +27,7 @@ import com.android.systemui.classifier.fakeFalsingManager
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.kosmos.testScope
+import com.android.systemui.lifecycle.activateIn
import com.android.systemui.power.data.repository.fakePowerRepository
import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.scene.domain.interactor.sceneInteractor
@@ -37,6 +40,8 @@ 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.Job
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -57,6 +62,9 @@ class SceneContainerViewModelTest : SysuiTestCase() {
private lateinit var underTest: SceneContainerViewModel
+ private lateinit var activationJob: Job
+ private var motionEventHandler: SceneContainerViewModel.MotionEventHandler? = null
+
@Before
fun setUp() {
underTest =
@@ -64,10 +72,28 @@ class SceneContainerViewModelTest : SysuiTestCase() {
sceneInteractor = sceneInteractor,
falsingInteractor = kosmos.falsingInteractor,
powerInteractor = kosmos.powerInteractor,
+ motionEventHandlerReceiver = { motionEventHandler ->
+ this@SceneContainerViewModelTest.motionEventHandler = motionEventHandler
+ },
)
+ activationJob = Job()
+ underTest.activateIn(testScope, activationJob)
}
@Test
+ fun activate_setsMotionEventHandler() =
+ testScope.runTest { assertThat(motionEventHandler).isNotNull() }
+
+ @Test
+ fun deactivate_clearsMotionEventHandler() =
+ testScope.runTest {
+ activationJob.cancel()
+ runCurrent()
+
+ assertThat(motionEventHandler).isNull()
+ }
+
+ @Test
fun isVisible() =
testScope.runTest {
val isVisible by collectLastValue(underTest.isVisible)
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 f6924f222e11..8aa601f3ecf0 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
@@ -26,13 +26,12 @@ class SceneWindowRootView(
attrs,
) {
- private lateinit var viewModel: SceneContainerViewModel
-
+ private var motionEventHandler: SceneContainerViewModel.MotionEventHandler? = null
// TODO(b/298525212): remove once Compose exposes window inset bounds.
private val windowInsets: MutableStateFlow<WindowInsets?> = MutableStateFlow(null)
fun init(
- viewModel: SceneContainerViewModel,
+ viewModelFactory: SceneContainerViewModel.Factory,
containerConfig: SceneContainerConfig,
sharedNotificationContainer: SharedNotificationContainer,
scenes: Set<Scene>,
@@ -40,11 +39,13 @@ class SceneWindowRootView(
sceneDataSourceDelegator: SceneDataSourceDelegator,
alternateBouncerDependencies: AlternateBouncerDependencies,
) {
- this.viewModel = viewModel
setLayoutInsetsController(layoutInsetController)
SceneWindowRootViewBinder.bind(
view = this@SceneWindowRootView,
- viewModel = viewModel,
+ viewModelFactory = viewModelFactory,
+ motionEventHandlerReceiver = { motionEventHandler ->
+ this.motionEventHandler = motionEventHandler
+ },
windowInsets = windowInsets,
containerConfig = containerConfig,
sharedNotificationContainer = sharedNotificationContainer,
@@ -69,10 +70,10 @@ class SceneWindowRootView(
}
override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
- viewModel.onMotionEvent(ev)
+ motionEventHandler?.onMotionEvent(ev)
return super.dispatchTouchEvent(ev).also {
TouchLogger.logDispatchTouch(TAG, ev, it)
- viewModel.onMotionEventComplete()
+ motionEventHandler?.onMotionEventComplete()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt
index 73a8e4c24578..ad68f17720c5 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt
@@ -29,8 +29,6 @@ import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.core.view.isVisible
import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.lifecycleScope
-import androidx.lifecycle.repeatOnLifecycle
import com.android.compose.animation.scene.SceneKey
import com.android.compose.theme.PlatformTheme
import com.android.internal.policy.ScreenDecorationsUtils
@@ -39,7 +37,9 @@ import com.android.systemui.common.ui.compose.windowinsets.DisplayCutout
import com.android.systemui.common.ui.compose.windowinsets.ScreenDecorProvider
import com.android.systemui.keyguard.ui.composable.AlternateBouncer
import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerDependencies
+import com.android.systemui.lifecycle.WindowLifecycleState
import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.lifecycle.viewModel
import com.android.systemui.res.R
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.shared.model.Scene
@@ -51,6 +51,7 @@ import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
@@ -63,7 +64,8 @@ object SceneWindowRootViewBinder {
/** Binds between the view and view-model pertaining to a specific scene container. */
fun bind(
view: ViewGroup,
- viewModel: SceneContainerViewModel,
+ viewModelFactory: SceneContainerViewModel.Factory,
+ motionEventHandlerReceiver: (SceneContainerViewModel.MotionEventHandler?) -> Unit,
windowInsets: StateFlow<WindowInsets?>,
containerConfig: SceneContainerConfig,
sharedNotificationContainer: SharedNotificationContainer,
@@ -85,8 +87,11 @@ object SceneWindowRootViewBinder {
}
view.repeatWhenAttached {
- lifecycleScope.launch {
- repeatOnLifecycle(Lifecycle.State.CREATED) {
+ view.viewModel(
+ minWindowLifecycleState = WindowLifecycleState.ATTACHED,
+ factory = { viewModelFactory.create(motionEventHandlerReceiver) },
+ ) { viewModel ->
+ try {
view.setViewTreeOnBackPressedDispatcherOwner(
object : OnBackPressedDispatcherOwner {
override val onBackPressedDispatcher =
@@ -140,10 +145,11 @@ object SceneWindowRootViewBinder {
onVisibilityChangedInternal(isVisible)
}
}
+ awaitCancellation()
+ } finally {
+ // Here when destroyed.
+ view.removeAllViews()
}
-
- // Here when destroyed.
- view.removeAllViews()
}
}
}
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 a28222e9cea0..2d02f5a5b79d 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
@@ -23,25 +23,26 @@ import com.android.compose.animation.scene.UserAction
import com.android.compose.animation.scene.UserActionResult
import com.android.systemui.classifier.Classifier
import com.android.systemui.classifier.domain.interactor.FalsingInteractor
-import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.lifecycle.SysUiViewModel
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.model.Scenes
-import javax.inject.Inject
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.map
/** Models UI state for the scene container. */
-@SysUISingleton
class SceneContainerViewModel
-@Inject
+@AssistedInject
constructor(
private val sceneInteractor: SceneInteractor,
private val falsingInteractor: FalsingInteractor,
private val powerInteractor: PowerInteractor,
-) {
+ @Assisted private val motionEventHandlerReceiver: (MotionEventHandler?) -> Unit,
+) : SysUiViewModel() {
/**
* Keys of all scenes in the container.
*
@@ -56,6 +57,29 @@ constructor(
/** Whether the container is visible. */
val isVisible: StateFlow<Boolean> = sceneInteractor.isVisible
+ override suspend fun onActivated() {
+ try {
+ // Sends a MotionEventHandler to the owner of the view-model so they can report
+ // MotionEvents into the view-model.
+ motionEventHandlerReceiver(
+ object : MotionEventHandler {
+ override fun onMotionEvent(motionEvent: MotionEvent) {
+ this@SceneContainerViewModel.onMotionEvent(motionEvent)
+ }
+
+ override fun onMotionEventComplete() {
+ this@SceneContainerViewModel.onMotionEventComplete()
+ }
+ }
+ )
+ awaitCancellation()
+ } finally {
+ // Clears the previously-sent MotionEventHandler so the owner of the view-model releases
+ // their reference to it.
+ motionEventHandlerReceiver(null)
+ }
+ }
+
/**
* Binds the given flow so the system remembers it.
*
@@ -136,21 +160,22 @@ constructor(
}
}
- private fun replaceSceneFamilies(
- destinationScenes: Map<UserAction, UserActionResult>,
- ): Flow<Map<UserAction, UserActionResult>> {
- return destinationScenes
- .mapValues { (_, actionResult) ->
- sceneInteractor.resolveSceneFamily(actionResult.toScene).map { scene ->
- actionResult.copy(toScene = scene)
- }
- }
- .combineValueFlows()
+ /** Defines interface for classes that can handle externally-reported [MotionEvent]s. */
+ interface MotionEventHandler {
+ /** Notifies that a [MotionEvent] has occurred. */
+ fun onMotionEvent(motionEvent: MotionEvent)
+
+ /**
+ * Notifies that the previous [MotionEvent] reported by [onMotionEvent] has finished
+ * processing.
+ */
+ fun onMotionEventComplete()
}
-}
-private fun <K, V> Map<K, Flow<V>>.combineValueFlows(): Flow<Map<K, V>> =
- combine(
- asIterable().map { (k, fv) -> fv.map { k to it } },
- transform = Array<Pair<K, V>>::toMap,
- )
+ @AssistedFactory
+ interface Factory {
+ fun create(
+ motionEventHandlerReceiver: (MotionEventHandler?) -> Unit,
+ ): SceneContainerViewModel
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
index 21bbaa5a41f2..606fef0bff62 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
@@ -79,7 +79,7 @@ abstract class ShadeViewProviderModule {
@SysUISingleton
fun providesWindowRootView(
layoutInflater: LayoutInflater,
- viewModelProvider: Provider<SceneContainerViewModel>,
+ viewModelFactory: SceneContainerViewModel.Factory,
containerConfigProvider: Provider<SceneContainerConfig>,
scenesProvider: Provider<Set<@JvmSuppressWildcards Scene>>,
layoutInsetController: NotificationInsetsController,
@@ -91,7 +91,7 @@ abstract class ShadeViewProviderModule {
val sceneWindowRootView =
layoutInflater.inflate(R.layout.scene_window_root, null) as SceneWindowRootView
sceneWindowRootView.init(
- viewModel = viewModelProvider.get(),
+ viewModelFactory = viewModelFactory,
containerConfig = containerConfigProvider.get(),
sharedNotificationContainer =
sceneWindowRootView.requireViewById(R.id.shared_notification_container),