1 /*
<lambda>null2 * 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.ui.viewmodel
18
19 import android.view.MotionEvent
20 import com.android.compose.animation.scene.ObservableTransitionState
21 import com.android.compose.animation.scene.SceneKey
22 import com.android.compose.animation.scene.UserAction
23 import com.android.compose.animation.scene.UserActionResult
24 import com.android.systemui.classifier.Classifier
25 import com.android.systemui.classifier.domain.interactor.FalsingInteractor
26 import com.android.systemui.dagger.SysUISingleton
27 import com.android.systemui.power.domain.interactor.PowerInteractor
28 import com.android.systemui.scene.domain.interactor.SceneInteractor
29 import com.android.systemui.scene.shared.model.Scene
30 import com.android.systemui.scene.shared.model.Scenes
31 import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated
32 import javax.inject.Inject
33 import kotlinx.coroutines.CoroutineScope
34 import kotlinx.coroutines.flow.Flow
35 import kotlinx.coroutines.flow.SharingStarted
36 import kotlinx.coroutines.flow.StateFlow
37 import kotlinx.coroutines.flow.combine
38 import kotlinx.coroutines.flow.map
39 import kotlinx.coroutines.flow.stateIn
40
41 /** Models UI state for the scene container. */
42 @SysUISingleton
43 class SceneContainerViewModel
44 @Inject
45 constructor(
46 private val sceneInteractor: SceneInteractor,
47 private val falsingInteractor: FalsingInteractor,
48 private val powerInteractor: PowerInteractor,
49 scenes: Set<@JvmSuppressWildcards Scene>,
50 ) {
51 /**
52 * Keys of all scenes in the container.
53 *
54 * The scenes will be sorted in z-order such that the last one is the one that should be
55 * rendered on top of all previous ones.
56 */
57 val allSceneKeys: List<SceneKey> = sceneInteractor.allSceneKeys()
58
59 /** The scene that should be rendered. */
60 val currentScene: StateFlow<SceneKey> = sceneInteractor.currentScene
61
62 /** Whether the container is visible. */
63 val isVisible: StateFlow<Boolean> = sceneInteractor.isVisible
64
65 private val destinationScenesBySceneKey =
66 scenes.associate { scene ->
67 scene.key to scene.destinationScenes.flatMapLatestConflated { replaceSceneFamilies(it) }
68 }
69
70 fun currentDestinationScenes(
71 scope: CoroutineScope,
72 ): StateFlow<Map<UserAction, UserActionResult>> {
73 return currentScene
74 .flatMapLatestConflated { currentSceneKey ->
75 checkNotNull(destinationScenesBySceneKey[currentSceneKey])
76 }
77 .stateIn(
78 scope = scope,
79 started = SharingStarted.WhileSubscribed(),
80 initialValue = emptyMap(),
81 )
82 }
83
84 /**
85 * Binds the given flow so the system remembers it.
86 *
87 * Note that you must call is with `null` when the UI is done or risk a memory leak.
88 */
89 fun setTransitionState(transitionState: Flow<ObservableTransitionState>?) {
90 sceneInteractor.setTransitionState(transitionState)
91 }
92
93 /**
94 * Notifies that a [MotionEvent] is first seen at the top of the scene container UI.
95 *
96 * Call this before the [MotionEvent] starts to propagate through the UI hierarchy.
97 */
98 fun onMotionEvent(event: MotionEvent) {
99 powerInteractor.onUserTouch()
100 falsingInteractor.onTouchEvent(event)
101 if (
102 event.actionMasked == MotionEvent.ACTION_UP ||
103 event.actionMasked == MotionEvent.ACTION_CANCEL
104 ) {
105 sceneInteractor.onUserInteractionFinished()
106 }
107 }
108
109 /**
110 * Notifies that a [MotionEvent] that was previously sent to [onMotionEvent] has passed through
111 * the scene container UI.
112 *
113 * Call this after the [MotionEvent] propagates completely through the UI hierarchy.
114 */
115 fun onMotionEventComplete() {
116 falsingInteractor.onMotionEventComplete()
117 }
118
119 /**
120 * Returns `true` if a change to [toScene] is currently allowed; `false` otherwise.
121 *
122 * This is invoked only for user-initiated transitions. The goal is to check with the falsing
123 * system whether the change from the current scene to the given scene should be rejected due to
124 * it being a false touch.
125 */
126 fun canChangeScene(toScene: SceneKey): Boolean {
127 val interactionTypeOrNull =
128 when (toScene) {
129 Scenes.Bouncer -> Classifier.BOUNCER_UNLOCK
130 Scenes.Gone -> Classifier.UNLOCK
131 Scenes.NotificationsShade -> Classifier.NOTIFICATION_DRAG_DOWN
132 Scenes.Shade -> Classifier.NOTIFICATION_DRAG_DOWN
133 Scenes.QuickSettings -> Classifier.QUICK_SETTINGS
134 Scenes.QuickSettingsShade -> Classifier.QUICK_SETTINGS
135 else -> null
136 }
137
138 return interactionTypeOrNull?.let { interactionType ->
139 // It's important that the falsing system is always queried, even if no enforcement will
140 // occur. This helps build up the right signal in the system.
141 val isFalseTouch = falsingInteractor.isFalseTouch(interactionType)
142
143 // Only enforce falsing if moving from the lockscreen scene to a new scene.
144 val fromLockscreenScene = currentScene.value == Scenes.Lockscreen
145
146 !fromLockscreenScene || !isFalseTouch
147 } ?: true
148 }
149
150 /**
151 * Immediately resolves any scene families present in [actionResultMap] to their current
152 * resolution target.
153 */
154 fun resolveSceneFamilies(
155 actionResultMap: Map<UserAction, UserActionResult>,
156 ): Map<UserAction, UserActionResult> {
157 return actionResultMap.mapValues { (_, actionResult) ->
158 sceneInteractor.resolveSceneFamilyOrNull(actionResult.toScene)?.value?.let {
159 actionResult.copy(toScene = it)
160 } ?: actionResult
161 }
162 }
163
164 private fun replaceSceneFamilies(
165 destinationScenes: Map<UserAction, UserActionResult>,
166 ): Flow<Map<UserAction, UserActionResult>> {
167 return destinationScenes
168 .mapValues { (_, actionResult) ->
169 sceneInteractor.resolveSceneFamily(actionResult.toScene).map { scene ->
170 actionResult.copy(toScene = scene)
171 }
172 }
173 .combineValueFlows()
174 }
175 }
176
combineValueFlowsnull177 private fun <K, V> Map<K, Flow<V>>.combineValueFlows(): Flow<Map<K, V>> =
178 combine(
179 asIterable().map { (k, fv) -> fv.map { k to it } },
180 transform = Array<Pair<K, V>>::toMap,
181 )
182