Merge changes from topic "flexiglass-container-config-b279501596" into udc-dev

* changes:
  [flexiglass] Container config - UI layer.
  [flexiglass] Container config - domain layer.
  [flexiglass] Container config - data layer.
This commit is contained in:
Ale Nijamkin
2023-05-09 21:55:51 +00:00
committed by Android (Google) Code Review
11 changed files with 824 additions and 0 deletions

View File

@@ -0,0 +1,46 @@
/*
* Copyright (C) 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.data.model
import com.android.systemui.scene.shared.model.SceneKey
/** Models the configuration of a single scene container. */
data class SceneContainerConfig(
/** Container name. Must be unique across all containers in System UI. */
val name: String,
/**
* The keys to all scenes in the container, sorted by z-order such that the last one renders on
* top of all previous ones. Scene keys within the same container must not repeat but it's okay
* to have the same scene keys in different containers.
*/
val sceneKeys: List<SceneKey>,
/**
* The key of the scene that is the initial current scene when the container is first set up,
* before taking any application state in to account.
*/
val initialSceneKey: SceneKey,
) {
init {
check(sceneKeys.isNotEmpty()) { "A container must have at least one scene key." }
check(sceneKeys.contains(initialSceneKey)) {
"The initial key \"$initialSceneKey\" is not present in this container."
}
}
}

View File

@@ -0,0 +1,136 @@
/*
* Copyright (C) 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.data.repository
import com.android.systemui.scene.data.model.SceneContainerConfig
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
/** Source of truth for scene framework application state. */
class SceneContainerRepository
@Inject
constructor(
containerConfigurations: Set<SceneContainerConfig>,
) {
private val containerConfigByName: Map<String, SceneContainerConfig> =
containerConfigurations.associateBy { config -> config.name }
private val containerVisibilityByName: Map<String, MutableStateFlow<Boolean>> =
containerConfigByName
.map { (containerName, _) -> containerName to MutableStateFlow(true) }
.toMap()
private val currentSceneByContainerName: Map<String, MutableStateFlow<SceneModel>> =
containerConfigByName
.map { (containerName, config) ->
containerName to MutableStateFlow(SceneModel(config.initialSceneKey))
}
.toMap()
private val sceneTransitionProgressByContainerName: Map<String, MutableStateFlow<Float>> =
containerConfigByName
.map { (containerName, _) -> containerName to MutableStateFlow(1f) }
.toMap()
init {
val repeatedContainerNames =
containerConfigurations
.groupingBy { config -> config.name }
.eachCount()
.filter { (_, count) -> count > 1 }
check(repeatedContainerNames.isEmpty()) {
"Container names must be unique. The following container names appear more than once: ${
repeatedContainerNames
.map { (name, count) -> "\"$name\" appears $count times" }
.joinToString(", ")
}"
}
}
/**
* Returns the keys to all scenes in the container with the given name.
*
* The scenes will be sorted in z-order such that the last one is the one that should be
* rendered on top of all previous ones.
*/
fun allSceneKeys(containerName: String): List<SceneKey> {
return containerConfigByName[containerName]?.sceneKeys
?: error(noSuchContainerErrorMessage(containerName))
}
/** Sets the current scene in the container with the given name. */
fun setCurrentScene(containerName: String, scene: SceneModel) {
check(allSceneKeys(containerName).contains(scene.key)) {
"""
Cannot set current scene key to "${scene.key}". The container "$containerName" does
not contain a scene with that key.
"""
.trimIndent()
}
currentSceneByContainerName.setValue(containerName, scene)
}
/** The current scene in the container with the given name. */
fun currentScene(containerName: String): StateFlow<SceneModel> {
return currentSceneByContainerName.mutableOrError(containerName).asStateFlow()
}
/** Sets whether the container with the given name is visible. */
fun setVisible(containerName: String, isVisible: Boolean) {
containerVisibilityByName.setValue(containerName, isVisible)
}
/** Whether the container with the given name should be visible. */
fun isVisible(containerName: String): StateFlow<Boolean> {
return containerVisibilityByName.mutableOrError(containerName).asStateFlow()
}
/** Sets scene transition progress to the current scene in the container with the given name. */
fun setSceneTransitionProgress(containerName: String, progress: Float) {
sceneTransitionProgressByContainerName.setValue(containerName, progress)
}
/** Progress of the transition into the current scene in the container with the given name. */
fun sceneTransitionProgress(containerName: String): StateFlow<Float> {
return sceneTransitionProgressByContainerName.mutableOrError(containerName).asStateFlow()
}
private fun <T> Map<String, MutableStateFlow<T>>.mutableOrError(
containerName: String,
): MutableStateFlow<T> {
return this[containerName] ?: error(noSuchContainerErrorMessage(containerName))
}
private fun <T> Map<String, MutableStateFlow<T>>.setValue(
containerName: String,
value: T,
) {
val mutable = mutableOrError(containerName)
mutable.value = value
}
private fun noSuchContainerErrorMessage(containerName: String): String {
return """
No container named "$containerName". Existing containers:
${containerConfigByName.values.joinToString(", ") { it.name }}
"""
.trimIndent()
}
}

View File

@@ -0,0 +1,73 @@
/*
* Copyright (C) 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.interactor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.scene.data.repository.SceneContainerRepository
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
import javax.inject.Inject
import kotlinx.coroutines.flow.StateFlow
/** Business logic and app state accessors for the scene framework. */
@SysUISingleton
class SceneInteractor
@Inject
constructor(
private val repository: SceneContainerRepository,
) {
/**
* Returns the keys of all scenes in the container with the given name.
*
* The scenes will be sorted in z-order such that the last one is the one that should be
* rendered on top of all previous ones.
*/
fun allSceneKeys(containerName: String): List<SceneKey> {
return repository.allSceneKeys(containerName)
}
/** Sets the scene in the container with the given name. */
fun setCurrentScene(containerName: String, scene: SceneModel) {
repository.setCurrentScene(containerName, scene)
}
/** The current scene in the container with the given name. */
fun currentScene(containerName: String): StateFlow<SceneModel> {
return repository.currentScene(containerName)
}
/** Sets the visibility of the container with the given name. */
fun setVisible(containerName: String, isVisible: Boolean) {
return repository.setVisible(containerName, isVisible)
}
/** Whether the container with the given name is visible. */
fun isVisible(containerName: String): StateFlow<Boolean> {
return repository.isVisible(containerName)
}
/** Sets scene transition progress to the current scene in the container with the given name. */
fun setSceneTransitionProgress(containerName: String, progress: Float) {
repository.setSceneTransitionProgress(containerName, progress)
}
/** Progress of the transition into the current scene in the container with the given name. */
fun sceneTransitionProgress(containerName: String): StateFlow<Float> {
return repository.sceneTransitionProgress(containerName)
}
}

View File

@@ -0,0 +1,87 @@
/*
* Copyright (C) 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.shared.model
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
/**
* Defines interface for classes that can describe a "scene".
*
* In the scene framework, there can be multiple scenes in a single scene "container". The container
* takes care of rendering the current scene and allowing scenes to be switched from one to another
* based on either user action (for example, swiping down while on the lock screen scene may switch
* to the shade scene).
*
* The framework also supports multiple containers, each one with its own configuration.
*/
interface Scene {
/** Uniquely-identifying key for this scene. The key must be unique within its container. */
val key: SceneKey
/**
* Returns a mapping between [UserAction] and flows that emit a [SceneModel].
*
* When the scene framework detects the user action, it starts a transition to the scene
* described by the latest value in the flow that's mapped from that user action.
*
* Once the [Scene] becomes the current one, the scene framework will invoke this method and set
* up collectors to watch for new values emitted to each of the flows. If a value is added to
* the map at a given [UserAction], the framework will set up user input handling for that
* [UserAction] and, if such a user action is detected, the framework will initiate a transition
* to that [SceneModel].
*
* Note that calling this method does _not_ mean that the given user action has occurred.
* Instead, the method is called before any user action/gesture is detected so that the
* framework can decide whether to set up gesture/input detectors/listeners for that type of
* user action.
*
* Note that a missing value for a specific [UserAction] means that the user action of the given
* type is not currently active in the scene and should be ignored by the framework, while the
* current scene is this one.
*
* The API is designed such that it's possible to emit ever-changing values for each
* [UserAction] to enable, disable, or change the destination scene of a given user action.
*/
fun destinationScenes(): StateFlow<Map<UserAction, SceneModel>> =
MutableStateFlow(emptyMap<UserAction, SceneModel>()).asStateFlow()
}
/** Enumerates all scene framework supported user actions. */
sealed interface UserAction {
/** The user is scrolling, dragging, swiping, or flinging. */
data class Swipe(
/** The direction of the swipe. */
val direction: Direction,
/** The number of pointers that were used (for example, one or two fingers). */
val pointerCount: Int = 1,
) : UserAction
/** The user has hit the back button or performed the back navigation gesture. */
object Back : UserAction
}
/** Enumerates all known "cardinal" directions for user actions. */
enum class Direction {
LEFT,
UP,
RIGHT,
DOWN,
}

View File

@@ -0,0 +1,49 @@
/*
* Copyright (C) 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.shared.model
/** Keys of all known scenes. */
sealed class SceneKey(
private val loggingName: String,
) {
/**
* The bouncer is the scene that displays authentication challenges like PIN, password, or
* pattern.
*/
object Bouncer : SceneKey("bouncer")
/**
* "Gone" is not a real scene but rather the absence of scenes when we want to skip showing any
* content from the scene framework.
*/
object Gone : SceneKey("gone")
/** The lock screen is the scene that shows when the device is locked. */
object LockScreen : SceneKey("lockscreen")
/**
* The shade is the scene whose primary purpose is to show a scrollable list of notifications.
*/
object Shade : SceneKey("shade")
/** The quick settings scene shows the quick setting tiles. */
object QuickSettings : SceneKey("quick_settings")
override fun toString(): String {
return loggingName
}
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright (C) 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.shared.model
/** Models a scene. */
data class SceneModel(
/** The key of the scene. */
val key: SceneKey,
/** An optional name for the transition that led to this scene being the current scene. */
val transitionName: String? = null,
)

View File

@@ -0,0 +1,64 @@
/*
* Copyright (C) 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.viewmodel
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.flow.StateFlow
/** Models UI state for a single scene container. */
class SceneContainerViewModel
@AssistedInject
constructor(
private val interactor: SceneInteractor,
@Assisted private val containerName: String,
) {
/**
* Keys of all scenes in the container.
*
* The scenes will be sorted in z-order such that the last one is the one that should be
* rendered on top of all previous ones.
*/
val allSceneKeys: List<SceneKey> = interactor.allSceneKeys(containerName)
/** The current scene. */
val currentScene: StateFlow<SceneModel> = interactor.currentScene(containerName)
/** Whether the container is visible. */
val isVisible: StateFlow<Boolean> = interactor.isVisible(containerName)
/** Requests a transition to the scene with the given key. */
fun setCurrentScene(scene: SceneModel) {
interactor.setCurrentScene(containerName, scene)
}
/** Notifies of the progress of a scene transition. */
fun setSceneTransitionProgress(progress: Float) {
interactor.setSceneTransitionProgress(containerName, progress)
}
@AssistedFactory
interface Factory {
fun create(
containerName: String,
): SceneContainerViewModel
}
}

View File

@@ -0,0 +1,51 @@
/*
* Copyright (C) 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.data.repository
import com.android.systemui.scene.data.model.SceneContainerConfig
import com.android.systemui.scene.shared.model.SceneKey
fun fakeSceneContainerRepository(
containerConfigurations: Set<SceneContainerConfig> =
setOf(
fakeSceneContainerConfig("container1"),
fakeSceneContainerConfig("container2"),
)
): SceneContainerRepository {
return SceneContainerRepository(containerConfigurations)
}
fun fakeSceneKeys(): List<SceneKey> {
return listOf(
SceneKey.QuickSettings,
SceneKey.Shade,
SceneKey.LockScreen,
SceneKey.Bouncer,
SceneKey.Gone,
)
}
fun fakeSceneContainerConfig(
name: String,
sceneKeys: List<SceneKey> = fakeSceneKeys(),
): SceneContainerConfig {
return SceneContainerConfig(
name = name,
sceneKeys = sceneKeys,
initialSceneKey = SceneKey.LockScreen,
)
}

View File

@@ -0,0 +1,139 @@
/*
* Copyright (C) 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)
package com.android.systemui.scene.data.repository
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
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 SceneContainerRepositoryTest : SysuiTestCase() {
@Test
fun allSceneKeys() {
val underTest = fakeSceneContainerRepository()
assertThat(underTest.allSceneKeys("container1"))
.isEqualTo(
listOf(
SceneKey.QuickSettings,
SceneKey.Shade,
SceneKey.LockScreen,
SceneKey.Bouncer,
SceneKey.Gone,
)
)
}
@Test(expected = IllegalStateException::class)
fun allSceneKeys_noSuchContainer_throws() {
val underTest = fakeSceneContainerRepository()
underTest.allSceneKeys("nonExistingContainer")
}
@Test
fun currentScene() = runTest {
val underTest = fakeSceneContainerRepository()
val currentScene by collectLastValue(underTest.currentScene("container1"))
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.LockScreen))
underTest.setCurrentScene("container1", SceneModel(SceneKey.Shade))
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Shade))
}
@Test(expected = IllegalStateException::class)
fun currentScene_noSuchContainer_throws() {
val underTest = fakeSceneContainerRepository()
underTest.currentScene("nonExistingContainer")
}
@Test(expected = IllegalStateException::class)
fun setCurrentScene_noSuchContainer_throws() {
val underTest = fakeSceneContainerRepository()
underTest.setCurrentScene("nonExistingContainer", SceneModel(SceneKey.Shade))
}
@Test(expected = IllegalStateException::class)
fun setCurrentScene_noSuchSceneInContainer_throws() {
val underTest =
fakeSceneContainerRepository(
setOf(
fakeSceneContainerConfig("container1"),
fakeSceneContainerConfig(
"container2",
listOf(SceneKey.QuickSettings, SceneKey.LockScreen)
),
)
)
underTest.setCurrentScene("container2", SceneModel(SceneKey.Shade))
}
@Test
fun isVisible() = runTest {
val underTest = fakeSceneContainerRepository()
val isVisible by collectLastValue(underTest.isVisible("container1"))
assertThat(isVisible).isTrue()
underTest.setVisible("container1", false)
assertThat(isVisible).isFalse()
underTest.setVisible("container1", true)
assertThat(isVisible).isTrue()
}
@Test(expected = IllegalStateException::class)
fun isVisible_noSuchContainer_throws() {
val underTest = fakeSceneContainerRepository()
underTest.isVisible("nonExistingContainer")
}
@Test(expected = IllegalStateException::class)
fun setVisible_noSuchContainer_throws() {
val underTest = fakeSceneContainerRepository()
underTest.setVisible("nonExistingContainer", false)
}
@Test
fun sceneTransitionProgress() = runTest {
val underTest = fakeSceneContainerRepository()
val sceneTransitionProgress by
collectLastValue(underTest.sceneTransitionProgress("container1"))
assertThat(sceneTransitionProgress).isEqualTo(1f)
underTest.setSceneTransitionProgress("container1", 0.1f)
assertThat(sceneTransitionProgress).isEqualTo(0.1f)
underTest.setSceneTransitionProgress("container1", 0.9f)
assertThat(sceneTransitionProgress).isEqualTo(0.9f)
}
@Test(expected = IllegalStateException::class)
fun sceneTransitionProgress_noSuchContainer_throws() {
val underTest = fakeSceneContainerRepository()
underTest.sceneTransitionProgress("nonExistingContainer")
}
}

View File

@@ -0,0 +1,78 @@
/*
* Copyright (C) 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.interactor
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.scene.data.repository.fakeSceneContainerRepository
import com.android.systemui.scene.data.repository.fakeSceneKeys
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 SceneInteractorTest : SysuiTestCase() {
private val underTest =
SceneInteractor(
repository = fakeSceneContainerRepository(),
)
@Test
fun allSceneKeys() {
assertThat(underTest.allSceneKeys("container1")).isEqualTo(fakeSceneKeys())
}
@Test
fun sceneTransitions() = runTest {
val currentScene by collectLastValue(underTest.currentScene("container1"))
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.LockScreen))
underTest.setCurrentScene("container1", SceneModel(SceneKey.Shade))
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Shade))
}
@Test
fun sceneTransitionProgress() = runTest {
val progress by collectLastValue(underTest.sceneTransitionProgress("container1"))
assertThat(progress).isEqualTo(1f)
underTest.setSceneTransitionProgress("container1", 0.55f)
assertThat(progress).isEqualTo(0.55f)
}
@Test
fun isVisible() = runTest {
val isVisible by collectLastValue(underTest.isVisible("container1"))
assertThat(isVisible).isTrue()
underTest.setVisible("container1", false)
assertThat(isVisible).isFalse()
underTest.setVisible("container1", true)
assertThat(isVisible).isTrue()
}
}

View File

@@ -0,0 +1,74 @@
/*
* Copyright (C) 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)
package com.android.systemui.scene.ui.viewmodel
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.scene.data.repository.fakeSceneContainerRepository
import com.android.systemui.scene.data.repository.fakeSceneKeys
import com.android.systemui.scene.domain.interactor.SceneInteractor
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 SceneContainerViewModelTest : SysuiTestCase() {
private val interactor =
SceneInteractor(
repository = fakeSceneContainerRepository(),
)
private val underTest =
SceneContainerViewModel(
interactor = interactor,
containerName = "container1",
)
@Test
fun isVisible() = runTest {
val isVisible by collectLastValue(underTest.isVisible)
assertThat(isVisible).isTrue()
interactor.setVisible("container1", false)
assertThat(isVisible).isFalse()
interactor.setVisible("container1", true)
assertThat(isVisible).isTrue()
}
@Test
fun allSceneKeys() {
assertThat(underTest.allSceneKeys).isEqualTo(fakeSceneKeys())
}
@Test
fun sceneTransition() = runTest {
val currentScene by collectLastValue(underTest.currentScene)
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.LockScreen))
underTest.setCurrentScene(SceneModel(SceneKey.Shade))
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Shade))
}
}