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

Commit 5fbbe448 authored by Ale Nijamkin's avatar Ale Nijamkin Committed by Android (Google) Code Review
Browse files

Merge "[flexiglass] Use transitionState instead of currentScene." into udc-qpr-dev

parents ae856b40 60eef3c4
Loading
Loading
Loading
Loading
+5 −1
Original line number Diff line number Diff line
@@ -77,7 +77,7 @@ fun SceneContainer(

    SceneTransitionLayout(
        currentScene = currentSceneKey.toTransitionSceneKey(),
        onChangeScene = { sceneKey -> viewModel.setCurrentScene(sceneKey.toModel()) },
        onChangeScene = viewModel::onSceneChanged,
        transitions = transitions {},
        state = state,
        modifier = modifier.fillMaxSize(),
@@ -154,3 +154,7 @@ private fun UserAction.toTransitionUserAction(): SceneTransitionUserAction {
        is UserAction.Back -> Back
    }
}

private fun SceneContainerViewModel.onSceneChanged(sceneKey: SceneTransitionSceneKey) {
    onSceneChanged(sceneKey.toModel())
}
+10 −12
Original line number Diff line number Diff line
@@ -476,18 +476,16 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
        if (mFeatureFlags.isEnabled(Flags.SCENE_CONTAINER)) {
            // When the scene framework transitions from bouncer to gone, we dismiss the keyguard.
            mSceneTransitionCollectionJob = mJavaAdapter.get().alwaysCollectFlow(
                mSceneInteractor.get().getTransitions(),
                sceneTransitionModel -> {
                    if (sceneTransitionModel != null
                            && sceneTransitionModel.getFrom() == SceneKey.Bouncer.INSTANCE
                            && sceneTransitionModel.getTo() == SceneKey.Gone.INSTANCE) {
                mSceneInteractor.get().finishedSceneTransitions(
                    /* from= */ SceneKey.Bouncer.INSTANCE,
                    /* to= */ SceneKey.Gone.INSTANCE),
                unused -> {
                    final int selectedUserId = mUserInteractor.getSelectedUserId();
                    showNextSecurityScreenOrFinish(
                            /* authenticated= */ true,
                            selectedUserId,
                            /* bypassSecondaryLockScreen= */ true,
                            mSecurityModel.getSecurityMode(selectedUserId));
                    }
                });
        }
    }
+3 −3
Original line number Diff line number Diff line
@@ -116,12 +116,12 @@ constructor(
                repository.setMessage(
                    message ?: promptMessage(authenticationInteractor.getAuthenticationMethod())
                )
                sceneInteractor.setCurrentScene(
                sceneInteractor.changeScene(
                    scene = SceneModel(SceneKey.Bouncer),
                    loggingReason = "request to unlock device while authentication required",
                )
            } else {
                sceneInteractor.setCurrentScene(
                sceneInteractor.changeScene(
                    scene = SceneModel(SceneKey.Gone),
                    loggingReason = "request to unlock device while authentication isn't required",
                )
@@ -169,7 +169,7 @@ constructor(
            authenticationInteractor.authenticate(input, tryAutoConfirm) ?: return null

        if (isAuthenticated) {
            sceneInteractor.setCurrentScene(
            sceneInteractor.changeScene(
                scene = SceneModel(SceneKey.Gone),
                loggingReason = "successful authentication",
            )
+24 −46
Original line number Diff line number Diff line
@@ -18,50 +18,49 @@

package com.android.systemui.scene.data.repository

import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.scene.shared.model.ObservableTransitionState
import com.android.systemui.scene.shared.model.SceneContainerConfig
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
import com.android.systemui.scene.shared.model.SceneTransitionModel
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.stateIn

/** Source of truth for scene framework application state. */
class SceneContainerRepository
@Inject
constructor(
    @Application applicationScope: CoroutineScope,
    private val config: SceneContainerConfig,
) {
    private val _desiredScene = MutableStateFlow(SceneModel(config.initialSceneKey))
    val desiredScene: StateFlow<SceneModel> = _desiredScene.asStateFlow()

    private val _isVisible = MutableStateFlow(true)
    val isVisible: StateFlow<Boolean> = _isVisible.asStateFlow()

    private val _currentScene = MutableStateFlow(SceneModel(config.initialSceneKey))
    val currentScene: StateFlow<SceneModel> = _currentScene.asStateFlow()

    private val transitionState = MutableStateFlow<Flow<ObservableTransitionState>?>(null)
    val transitionProgress: Flow<Float> =
        transitionState.flatMapLatest { observableTransitionStateFlow ->
            observableTransitionStateFlow?.flatMapLatest { observableTransitionState ->
                when (observableTransitionState) {
                    is ObservableTransitionState.Idle -> flowOf(1f)
                    is ObservableTransitionState.Transition -> observableTransitionState.progress
                }
            }
                ?: flowOf(1f)
        }

    private val _transitions = MutableStateFlow<SceneTransitionModel?>(null)
    val transitions: StateFlow<SceneTransitionModel?> = _transitions.asStateFlow()
    private val defaultTransitionState = ObservableTransitionState.Idle(config.initialSceneKey)
    private val _transitionState = MutableStateFlow<Flow<ObservableTransitionState>?>(null)
    val transitionState: StateFlow<ObservableTransitionState> =
        _transitionState
            .flatMapLatest { innerFlowOrNull -> innerFlowOrNull ?: flowOf(defaultTransitionState) }
            .stateIn(
                scope = applicationScope,
                started = SharingStarted.Eagerly,
                initialValue = defaultTransitionState,
            )

    /**
     * Returns the keys to all scenes in the container with the given name.
     * Returns the keys to 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.
@@ -70,40 +69,19 @@ constructor(
        return config.sceneKeys
    }

    /** Sets the current scene in the container with the given name. */
    fun setCurrentScene(scene: SceneModel) {
    fun setDesiredScene(scene: SceneModel) {
        check(allSceneKeys().contains(scene.key)) {
            """
                Cannot set current scene key to "${scene.key}". The configuration does not contain a
                scene with that key.
            """
                .trimIndent()
        }

        _currentScene.value = scene
    }

    /** Sets the scene transition in the container with the given name. */
    fun setSceneTransition(from: SceneKey, to: SceneKey) {
        check(allSceneKeys().contains(from)) {
            """
                Cannot set current scene key to "$from". The configuration does not contain a scene
                with that key.
            """
                .trimIndent()
        }
        check(allSceneKeys().contains(to)) {
            """
                Cannot set current scene key to "$to". The configuration does not contain a scene
                with that key.
                Cannot set the desired scene key to "${scene.key}". The configuration does not
                contain a scene with that key.
            """
                .trimIndent()
        }

        _transitions.value = SceneTransitionModel(from = from, to = to)
        _desiredScene.value = scene
    }

    /** Sets whether the container with the given name is visible. */
    /** Sets whether the container is visible. */
    fun setVisible(isVisible: Boolean) {
        _isVisible.value = isVisible
    }
@@ -114,6 +92,6 @@ constructor(
     * Note that you must call is with `null` when the UI is done or risk a memory leak.
     */
    fun setTransitionState(transitionState: Flow<ObservableTransitionState>?) {
        this.transitionState.value = transitionState
        _transitionState.value = transitionState
    }
}
+106 −35
Original line number Diff line number Diff line
@@ -23,12 +23,15 @@ import com.android.systemui.scene.shared.model.ObservableTransitionState
import com.android.systemui.scene.shared.model.RemoteUserInput
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
import com.android.systemui.scene.shared.model.SceneTransitionModel
import com.android.systemui.util.kotlin.pairwise
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapNotNull

/**
 * Generic business logic and app state accessors for the scene framework.
@@ -46,7 +49,54 @@ constructor(
) {

    /**
     * Returns the keys of all scenes in the container with the given name.
     * The currently *desired* scene.
     *
     * **Important:** this value will _commonly be different_ from what is being rendered in the UI,
     * by design.
     *
     * There are two intended sources for this value:
     * 1. Programmatic requests to transition to another scene (calls to [changeScene]).
     * 2. Reports from the UI about completing a transition to another scene (calls to
     *    [onSceneChanged]).
     *
     * Both the sources above cause the value of this flow to change; however, they cause mismatches
     * in different ways.
     *
     * **Updates from programmatic transitions**
     *
     * When an external bit of code asks the framework to switch to another scene, the value here
     * will update immediately. Downstream, the UI will detect this change and initiate the
     * transition animation. As the transition animation progresses, a threshold will be reached, at
     * which point the UI and the state here will match each other.
     *
     * **Updates from the UI**
     *
     * When the user interacts with the UI, the UI runs a transition animation that tracks the user
     * pointer (for example, the user's finger). During this time, the state value here and what the
     * UI shows will likely not match. Once/if a threshold is met, the UI reports it and commits the
     * change, making the value here match the UI again.
     */
    val desiredScene: StateFlow<SceneModel> = repository.desiredScene

    /**
     * The current state of the transition.
     *
     * Consumers should use this state to know:
     * 1. Whether there is an ongoing transition or if the system is at rest.
     * 2. When transitioning, which scenes are being transitioned between.
     * 3. When transitioning, what the progress of the transition is.
     */
    val transitionState: StateFlow<ObservableTransitionState> = repository.transitionState

    /** Whether the scene container is visible. */
    val isVisible: StateFlow<Boolean> = repository.isVisible

    private val _remoteUserInput: MutableStateFlow<RemoteUserInput?> = MutableStateFlow(null)
    /** A flow of motion events originating from outside of the scene framework. */
    val remoteUserInput: StateFlow<RemoteUserInput?> = _remoteUserInput.asStateFlow()

    /**
     * Returns the 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.
@@ -55,26 +105,20 @@ constructor(
        return repository.allSceneKeys()
    }

    /** Sets the scene in the container with the given name. */
    fun setCurrentScene(scene: SceneModel, loggingReason: String) {
        val currentSceneKey = repository.currentScene.value.key
        if (currentSceneKey == scene.key) {
            return
        }

        logger.logSceneChange(
            from = currentSceneKey,
            to = scene.key,
            reason = loggingReason,
        )
        repository.setCurrentScene(scene)
        repository.setSceneTransition(from = currentSceneKey, to = scene.key)
    /**
     * Requests a scene change to the given scene.
     *
     * The change is animated. Therefore, while the value in [desiredScene] will update immediately,
     * it will be some time before the UI will switch to the desired scene. The scene change
     * requested is remembered here but served by the UI layer, which will start a transition
     * animation. Once enough of the transition has occurred, the system will come into agreement
     * between the [desiredScene] and the UI.
     */
    fun changeScene(scene: SceneModel, loggingReason: String) {
        updateDesiredScene(scene, loggingReason, logger::logSceneChangeRequested)
    }

    /** The current scene in the container with the given name. */
    val currentScene: StateFlow<SceneModel> = repository.currentScene

    /** Sets the visibility of the container with the given name. */
    /** Sets the visibility of the container. */
    fun setVisible(isVisible: Boolean, loggingReason: String) {
        val wasVisible = repository.isVisible.value
        if (wasVisible == isVisible) {
@@ -89,9 +133,6 @@ constructor(
        return repository.setVisible(isVisible)
    }

    /** Whether the container with the given name is visible. */
    val isVisible: StateFlow<Boolean> = repository.isVisible

    /**
     * Binds the given flow so the system remembers it.
     *
@@ -101,23 +142,53 @@ constructor(
        repository.setTransitionState(transitionState)
    }

    /** Progress of the transition into the current scene in the container with the given name. */
    val transitionProgress: Flow<Float> = repository.transitionProgress

    /**
     * Scene transitions as pairs of keys. A new value is emitted exactly once, each time a scene
     * transition occurs. The flow begins with a `null` value at first, because the initial scene is
     * not something that we transition to from another scene.
     * Returns a stream of events that emits one [Unit] every time the framework transitions from
     * [from] to [to].
     */
    val transitions: StateFlow<SceneTransitionModel?> = repository.transitions

    private val _remoteUserInput: MutableStateFlow<RemoteUserInput?> = MutableStateFlow(null)

    /** A flow of motion events originating from outside of the scene framework. */
    val remoteUserInput: StateFlow<RemoteUserInput?> = _remoteUserInput.asStateFlow()
    fun finishedSceneTransitions(from: SceneKey, to: SceneKey): Flow<Unit> {
        return transitionState
            .mapNotNull { it as? ObservableTransitionState.Idle }
            .map { idleState -> idleState.scene }
            .distinctUntilChanged()
            .pairwise()
            .mapNotNull { (previousSceneKey, currentSceneKey) ->
                Unit.takeIf { previousSceneKey == from && currentSceneKey == to }
            }
    }

    /** Handles a remote user input. */
    fun onRemoteUserInput(input: RemoteUserInput) {
        _remoteUserInput.value = input
    }

    /**
     * Notifies that the UI has transitioned sufficiently to the given scene.
     *
     * *Not intended for external use!*
     *
     * Once a transition between one scene and another passes a threshold, the UI invokes this
     * method to report it, updating the value in [desiredScene] to match what the UI shows.
     */
    internal fun onSceneChanged(scene: SceneModel, loggingReason: String) {
        updateDesiredScene(scene, loggingReason, logger::logSceneChangeCommitted)
    }

    private fun updateDesiredScene(
        scene: SceneModel,
        loggingReason: String,
        log: (from: SceneKey, to: SceneKey, loggingReason: String) -> Unit,
    ) {
        val currentSceneKey = desiredScene.value.key
        if (currentSceneKey == scene.key) {
            return
        }

        log(
            /* from= */ currentSceneKey,
            /* to= */ scene.key,
            /* loggingReason= */ loggingReason,
        )
        repository.setDesiredScene(scene)
    }
}
Loading