Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 20c3d9c5 authored by Ale Nijamkin's avatar Ale Nijamkin Committed by Automerger Merge Worker
Browse files

Merge changes from topic "flexiglass-container-config-b279501596" into udc-dev am: 92e7e120

parents f6fd77b6 92e7e120
Loading
Loading
Loading
Loading
+46 −0
Original line number Diff line number Diff line
/*
 * 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."
        }
    }
}
+136 −0
Original line number Diff line number Diff line
/*
 * 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()
    }
}
+73 −0
Original line number Diff line number Diff line
/*
 * 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)
    }
}
+87 −0
Original line number Diff line number Diff line
/*
 * 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,
}
+49 −0
Original line number Diff line number Diff line
/*
 * 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
    }
}
Loading