summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Ale Nijamkin <nijamkin@google.com> 2023-07-11 04:13:59 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2023-07-11 04:13:59 +0000
commitc9f03886f715480c4973c5d6aead70936f3fc7c8 (patch)
tree8daffd36be1c6b70f0e3760196b408583f583f4d
parent3a4263184d5df212e3e680bb8eef25db1407de90 (diff)
parent0407508cfa73a8b5c48e55a105d264109433a592 (diff)
Merge "[flexiglass] Drive window view visibility." into udc-qpr-dev
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartableModule.kt32
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/domain/startable/SystemUiDefaultSceneContainerStartable.kt69
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt61
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt92
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SystemUiDefaultSceneContainerStartableTest.kt98
7 files changed, 317 insertions, 49 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt b/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt
index 0a9839e2f18b..26c52199f493 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt
@@ -16,6 +16,7 @@
package com.android.systemui.scene
+import com.android.systemui.scene.domain.startable.SceneContainerStartableModule
import com.android.systemui.scene.shared.model.SceneContainerConfigModule
import com.android.systemui.scene.ui.composable.SceneModule
import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModelModule
@@ -25,6 +26,7 @@ import dagger.Module
includes =
[
SceneContainerConfigModule::class,
+ SceneContainerStartableModule::class,
SceneContainerViewModelModule::class,
SceneModule::class,
],
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 c704ea838130..4582370679ab 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
@@ -24,7 +24,17 @@ import com.android.systemui.scene.shared.model.SceneTransitionModel
import javax.inject.Inject
import kotlinx.coroutines.flow.StateFlow
-/** Business logic and app state accessors for the scene framework. */
+/**
+ * Generic business logic and app state accessors for the scene framework.
+ *
+ * Note that scene container specific business logic does not belong in this class. Instead, it
+ * should be hoisted to a class that is specific to that scene container, for an example, please see
+ * [SystemUiDefaultSceneContainerStartable].
+ *
+ * Also note that this class should not depend on state or logic of other modules or features.
+ * Instead, other feature modules should depend on and call into this class when their parts of the
+ * application state change.
+ */
@SysUISingleton
class SceneInteractor
@Inject
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartableModule.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartableModule.kt
new file mode 100644
index 000000000000..b3de2d158a53
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartableModule.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2023 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.scene.domain.startable
+
+import com.android.systemui.CoreStartable
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+
+@Module
+interface SceneContainerStartableModule {
+
+ @Binds
+ @IntoMap
+ @ClassKey(SystemUiDefaultSceneContainerStartable::class)
+ fun bind(impl: SystemUiDefaultSceneContainerStartable): CoreStartable
+}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SystemUiDefaultSceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SystemUiDefaultSceneContainerStartable.kt
new file mode 100644
index 000000000000..91400196bbb1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SystemUiDefaultSceneContainerStartable.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2023 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.scene.domain.startable
+
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.model.SceneContainerNames
+import com.android.systemui.scene.shared.model.SceneKey
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.launch
+
+/**
+ * Hooks up business logic that manipulates the state of the [SceneInteractor] for the default
+ * system UI scene container (the one named [SceneContainerNames.SYSTEM_UI_DEFAULT]) based on state
+ * from other systems.
+ */
+@SysUISingleton
+class SystemUiDefaultSceneContainerStartable
+@Inject
+constructor(
+ @Application private val applicationScope: CoroutineScope,
+ private val sceneInteractor: SceneInteractor,
+ private val featureFlags: FeatureFlags,
+) : CoreStartable {
+
+ override fun start() {
+ if (featureFlags.isEnabled(Flags.SCENE_CONTAINER)) {
+ keepVisibilityUpdated()
+ }
+ }
+
+ /** Drives visibility of the scene container. */
+ private fun keepVisibilityUpdated() {
+ applicationScope.launch {
+ sceneInteractor
+ .currentScene(CONTAINER_NAME)
+ .map { it.key }
+ .distinctUntilChanged()
+ .collect { sceneKey ->
+ sceneInteractor.setVisible(CONTAINER_NAME, sceneKey != SceneKey.Gone)
+ }
+ }
+ }
+
+ companion object {
+ private const val CONTAINER_NAME = SceneContainerNames.SYSTEM_UI_DEFAULT
+ }
+}
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 2ad5429668d0..c456be6e5ab2 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
@@ -2,19 +2,10 @@ package com.android.systemui.scene.ui.view
import android.content.Context
import android.util.AttributeSet
-import androidx.activity.OnBackPressedDispatcher
-import androidx.activity.OnBackPressedDispatcherOwner
-import androidx.activity.setViewTreeOnBackPressedDispatcherOwner
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.lifecycleScope
-import androidx.lifecycle.repeatOnLifecycle
-import com.android.systemui.compose.ComposeFacade
-import com.android.systemui.lifecycle.repeatWhenAttached
+import android.view.View
import com.android.systemui.scene.shared.model.Scene
import com.android.systemui.scene.shared.model.SceneContainerConfig
-import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
-import kotlinx.coroutines.launch
/** A root view of the main SysUI window that supports scenes. */
class SceneWindowRootView(
@@ -30,45 +21,19 @@ class SceneWindowRootView(
containerConfig: SceneContainerConfig,
scenes: Set<Scene>,
) {
- val unsortedSceneByKey: Map<SceneKey, Scene> = scenes.associateBy { scene -> scene.key }
- val sortedSceneByKey: Map<SceneKey, Scene> = buildMap {
- containerConfig.sceneKeys.forEach { sceneKey ->
- val scene =
- checkNotNull(unsortedSceneByKey[sceneKey]) {
- "Scene not found for key \"$sceneKey\"!"
- }
-
- put(sceneKey, scene)
+ SceneWindowRootViewBinder.bind(
+ view = this@SceneWindowRootView,
+ viewModel = viewModel,
+ containerConfig = containerConfig,
+ scenes = scenes,
+ onVisibilityChangedInternal = { isVisible ->
+ super.setVisibility(if (isVisible) View.VISIBLE else View.INVISIBLE)
}
- }
-
- repeatWhenAttached {
- lifecycleScope.launch {
- repeatOnLifecycle(Lifecycle.State.CREATED) {
- setViewTreeOnBackPressedDispatcherOwner(
- object : OnBackPressedDispatcherOwner {
- override val onBackPressedDispatcher =
- OnBackPressedDispatcher().apply {
- setOnBackInvokedDispatcher(viewRootImpl.onBackInvokedDispatcher)
- }
-
- override val lifecycle: Lifecycle =
- this@repeatWhenAttached.lifecycle
- }
- )
-
- addView(
- ComposeFacade.createSceneContainerView(
- context = context,
- viewModel = viewModel,
- sceneByKey = sortedSceneByKey,
- )
- )
- }
+ )
+ }
- // Here when destroyed.
- removeAllViews()
- }
- }
+ override fun setVisibility(visibility: Int) {
+ // Do nothing. We don't want external callers to invoke this. Instead, we drive our own
+ // visibility from our view-binder.
}
}
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
new file mode 100644
index 000000000000..5aa5feeedcf1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2023 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.scene.ui.view
+
+import android.view.ViewGroup
+import androidx.activity.OnBackPressedDispatcher
+import androidx.activity.OnBackPressedDispatcherOwner
+import androidx.activity.setViewTreeOnBackPressedDispatcherOwner
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.compose.ComposeFacade
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.scene.shared.model.Scene
+import com.android.systemui.scene.shared.model.SceneContainerConfig
+import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
+import kotlinx.coroutines.launch
+
+object SceneWindowRootViewBinder {
+
+ /** Binds between the view and view-model pertaining to a specific scene container. */
+ fun bind(
+ view: ViewGroup,
+ viewModel: SceneContainerViewModel,
+ containerConfig: SceneContainerConfig,
+ scenes: Set<Scene>,
+ onVisibilityChangedInternal: (isVisible: Boolean) -> Unit,
+ ) {
+ val unsortedSceneByKey: Map<SceneKey, Scene> = scenes.associateBy { scene -> scene.key }
+ val sortedSceneByKey: Map<SceneKey, Scene> = buildMap {
+ containerConfig.sceneKeys.forEach { sceneKey ->
+ val scene =
+ checkNotNull(unsortedSceneByKey[sceneKey]) {
+ "Scene not found for key \"$sceneKey\"!"
+ }
+
+ put(sceneKey, scene)
+ }
+ }
+
+ view.repeatWhenAttached {
+ lifecycleScope.launch {
+ repeatOnLifecycle(Lifecycle.State.CREATED) {
+ view.setViewTreeOnBackPressedDispatcherOwner(
+ object : OnBackPressedDispatcherOwner {
+ override val onBackPressedDispatcher =
+ OnBackPressedDispatcher().apply {
+ setOnBackInvokedDispatcher(
+ view.viewRootImpl.onBackInvokedDispatcher
+ )
+ }
+
+ override val lifecycle: Lifecycle = this@repeatWhenAttached.lifecycle
+ }
+ )
+
+ view.addView(
+ ComposeFacade.createSceneContainerView(
+ context = view.context,
+ viewModel = viewModel,
+ sceneByKey = sortedSceneByKey,
+ )
+ )
+
+ launch {
+ viewModel.isVisible.collect { isVisible ->
+ onVisibilityChangedInternal(isVisible)
+ }
+ }
+ }
+
+ // Here when destroyed.
+ view.removeAllViews()
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SystemUiDefaultSceneContainerStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SystemUiDefaultSceneContainerStartableTest.kt
new file mode 100644
index 000000000000..df3701edc6de
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SystemUiDefaultSceneContainerStartableTest.kt
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2023 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.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class, ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.scene.domain.startable
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.scene.shared.model.SceneContainerNames
+import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.SceneModel
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@SmallTest
+@RunWith(JUnit4::class)
+class SystemUiDefaultSceneContainerStartableTest : SysuiTestCase() {
+
+ private val utils = SceneTestUtils(this)
+ private val testScope = utils.testScope
+ private val sceneInteractor = utils.sceneInteractor()
+ private val featureFlags = FakeFeatureFlags()
+
+ private val underTest =
+ SystemUiDefaultSceneContainerStartable(
+ applicationScope = testScope.backgroundScope,
+ sceneInteractor = sceneInteractor,
+ featureFlags = featureFlags,
+ )
+
+ @Test
+ fun start_featureEnabled_keepsVisibilityUpdated() =
+ testScope.runTest {
+ featureFlags.set(Flags.SCENE_CONTAINER, true)
+ val isVisible by
+ collectLastValue(sceneInteractor.isVisible(SceneContainerNames.SYSTEM_UI_DEFAULT))
+ assertThat(isVisible).isTrue()
+
+ underTest.start()
+
+ sceneInteractor.setCurrentScene(
+ SceneContainerNames.SYSTEM_UI_DEFAULT,
+ SceneModel(SceneKey.Gone)
+ )
+ assertThat(isVisible).isFalse()
+
+ sceneInteractor.setCurrentScene(
+ SceneContainerNames.SYSTEM_UI_DEFAULT,
+ SceneModel(SceneKey.Shade)
+ )
+ assertThat(isVisible).isTrue()
+ }
+
+ @Test
+ fun start_featureDisabled_doesNotUpdateVisibility() =
+ testScope.runTest {
+ featureFlags.set(Flags.SCENE_CONTAINER, false)
+ val isVisible by
+ collectLastValue(sceneInteractor.isVisible(SceneContainerNames.SYSTEM_UI_DEFAULT))
+ assertThat(isVisible).isTrue()
+
+ underTest.start()
+
+ sceneInteractor.setCurrentScene(
+ SceneContainerNames.SYSTEM_UI_DEFAULT,
+ SceneModel(SceneKey.Gone)
+ )
+ assertThat(isVisible).isTrue()
+
+ sceneInteractor.setCurrentScene(
+ SceneContainerNames.SYSTEM_UI_DEFAULT,
+ SceneModel(SceneKey.Shade)
+ )
+ assertThat(isVisible).isTrue()
+ }
+}