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