1 /* 2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.systemui.scene.domain.interactor 18 19 import com.android.systemui.dagger.SysUISingleton 20 import com.android.systemui.dagger.qualifiers.Application 21 import com.android.systemui.scene.data.repository.SceneContainerRepository 22 import com.android.systemui.scene.shared.logger.SceneLogger 23 import com.android.systemui.scene.shared.model.ObservableTransitionState 24 import com.android.systemui.scene.shared.model.SceneKey 25 import com.android.systemui.scene.shared.model.SceneModel 26 import javax.inject.Inject 27 import kotlinx.coroutines.CoroutineScope 28 import kotlinx.coroutines.flow.Flow 29 import kotlinx.coroutines.flow.SharingStarted 30 import kotlinx.coroutines.flow.StateFlow 31 import kotlinx.coroutines.flow.map 32 import kotlinx.coroutines.flow.stateIn 33 34 /** 35 * Generic business logic and app state accessors for the scene framework. 36 * 37 * Note that this class should not depend on state or logic of other modules or features. Instead, 38 * other feature modules should depend on and call into this class when their parts of the 39 * application state change. 40 */ 41 @SysUISingleton 42 class SceneInteractor 43 @Inject 44 constructor( 45 @Application applicationScope: CoroutineScope, 46 private val repository: SceneContainerRepository, 47 private val logger: SceneLogger, 48 ) { 49 50 /** 51 * The currently *desired* scene. 52 * 53 * **Important:** this value will _commonly be different_ from what is being rendered in the UI, 54 * by design. 55 * 56 * There are two intended sources for this value: 57 * 1. Programmatic requests to transition to another scene (calls to [changeScene]). 58 * 2. Reports from the UI about completing a transition to another scene (calls to 59 * [onSceneChanged]). 60 * 61 * Both the sources above cause the value of this flow to change; however, they cause mismatches 62 * in different ways. 63 * 64 * **Updates from programmatic transitions** 65 * 66 * When an external bit of code asks the framework to switch to another scene, the value here 67 * will update immediately. Downstream, the UI will detect this change and initiate the 68 * transition animation. As the transition animation progresses, a threshold will be reached, at 69 * which point the UI and the state here will match each other. 70 * 71 * **Updates from the UI** 72 * 73 * When the user interacts with the UI, the UI runs a transition animation that tracks the user 74 * pointer (for example, the user's finger). During this time, the state value here and what the 75 * UI shows will likely not match. Once/if a threshold is met, the UI reports it and commits the 76 * change, making the value here match the UI again. 77 */ 78 val desiredScene: StateFlow<SceneModel> = repository.desiredScene 79 80 /** 81 * The current state of the transition. 82 * 83 * Consumers should use this state to know: 84 * 1. Whether there is an ongoing transition or if the system is at rest. 85 * 2. When transitioning, which scenes are being transitioned between. 86 * 3. When transitioning, what the progress of the transition is. 87 */ 88 val transitionState: StateFlow<ObservableTransitionState> = repository.transitionState 89 90 /** 91 * The key of the scene that the UI is currently transitioning to or `null` if there is no 92 * active transition at the moment. 93 * 94 * This is a convenience wrapper around [transitionState], meant for flow-challenged consumers 95 * like Java code. 96 */ 97 val transitioningTo: StateFlow<SceneKey?> = 98 transitionState 99 .map { state -> (state as? ObservableTransitionState.Transition)?.toScene } 100 .stateIn( 101 scope = applicationScope, 102 started = SharingStarted.WhileSubscribed(), 103 initialValue = null, 104 ) 105 106 /** Whether the scene container is visible. */ 107 val isVisible: StateFlow<Boolean> = repository.isVisible 108 109 /** 110 * Returns the keys of all scenes in the container. 111 * 112 * The scenes will be sorted in z-order such that the last one is the one that should be 113 * rendered on top of all previous ones. 114 */ 115 fun allSceneKeys(): List<SceneKey> { 116 return repository.allSceneKeys() 117 } 118 119 /** 120 * Requests a scene change to the given scene. 121 * 122 * The change is animated. Therefore, while the value in [desiredScene] will update immediately, 123 * it will be some time before the UI will switch to the desired scene. The scene change 124 * requested is remembered here but served by the UI layer, which will start a transition 125 * animation. Once enough of the transition has occurred, the system will come into agreement 126 * between the [desiredScene] and the UI. 127 */ 128 fun changeScene(scene: SceneModel, loggingReason: String) { 129 updateDesiredScene(scene, loggingReason, logger::logSceneChangeRequested) 130 } 131 132 /** Sets the visibility of the container. */ 133 fun setVisible(isVisible: Boolean, loggingReason: String) { 134 val wasVisible = repository.isVisible.value 135 if (wasVisible == isVisible) { 136 return 137 } 138 139 logger.logVisibilityChange( 140 from = wasVisible, 141 to = isVisible, 142 reason = loggingReason, 143 ) 144 return repository.setVisible(isVisible) 145 } 146 147 /** 148 * Binds the given flow so the system remembers it. 149 * 150 * Note that you must call is with `null` when the UI is done or risk a memory leak. 151 */ 152 fun setTransitionState(transitionState: Flow<ObservableTransitionState>?) { 153 repository.setTransitionState(transitionState) 154 } 155 156 /** 157 * Notifies that the UI has transitioned sufficiently to the given scene. 158 * 159 * *Not intended for external use!* 160 * 161 * Once a transition between one scene and another passes a threshold, the UI invokes this 162 * method to report it, updating the value in [desiredScene] to match what the UI shows. 163 */ 164 internal fun onSceneChanged(scene: SceneModel, loggingReason: String) { 165 updateDesiredScene(scene, loggingReason, logger::logSceneChangeCommitted) 166 } 167 168 private fun updateDesiredScene( 169 scene: SceneModel, 170 loggingReason: String, 171 log: (from: SceneKey, to: SceneKey, loggingReason: String) -> Unit, 172 ) { 173 val currentSceneKey = desiredScene.value.key 174 if (currentSceneKey == scene.key) { 175 return 176 } 177 178 log( 179 /* from= */ currentSceneKey, 180 /* to= */ scene.key, 181 /* loggingReason= */ loggingReason, 182 ) 183 repository.setDesiredScene(scene) 184 } 185 } 186