• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.keyguard.domain.interactor
18 
19 import android.animation.ValueAnimator
20 import com.android.app.animation.Interpolators
21 import com.android.app.tracing.coroutines.launchTraced as launch
22 import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
23 import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
24 import com.android.systemui.communal.shared.model.CommunalScenes
25 import com.android.systemui.communal.shared.model.CommunalTransitionKeys
26 import com.android.systemui.communal.shared.model.EditModeState
27 import com.android.systemui.dagger.SysUISingleton
28 import com.android.systemui.dagger.qualifiers.Background
29 import com.android.systemui.dagger.qualifiers.Main
30 import com.android.systemui.keyguard.KeyguardWmStateRefactor
31 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
32 import com.android.systemui.keyguard.shared.model.KeyguardState
33 import com.android.systemui.power.domain.interactor.PowerInteractor
34 import com.android.systemui.scene.shared.flag.SceneContainerFlag
35 import com.android.systemui.util.kotlin.BooleanFlowOperators.allOf
36 import com.android.systemui.util.kotlin.BooleanFlowOperators.noneOf
37 import com.android.systemui.util.kotlin.Utils.Companion.sampleFilter
38 import com.android.systemui.util.kotlin.sample
39 import javax.inject.Inject
40 import kotlin.time.Duration.Companion.milliseconds
41 import kotlin.time.Duration.Companion.seconds
42 import kotlinx.coroutines.CoroutineDispatcher
43 import kotlinx.coroutines.CoroutineScope
44 import kotlinx.coroutines.FlowPreview
45 import kotlinx.coroutines.flow.combine
46 import kotlinx.coroutines.flow.debounce
47 
48 @OptIn(FlowPreview::class)
49 @SysUISingleton
50 class FromGlanceableHubTransitionInteractor
51 @Inject
52 constructor(
53     @Background private val scope: CoroutineScope,
54     @Main mainDispatcher: CoroutineDispatcher,
55     @Background bgDispatcher: CoroutineDispatcher,
56     private val communalSettingsInteractor: CommunalSettingsInteractor,
57     keyguardInteractor: KeyguardInteractor,
58     private val communalSceneInteractor: CommunalSceneInteractor,
59     override val transitionRepository: KeyguardTransitionRepository,
60     override val internalTransitionInteractor: InternalKeyguardTransitionInteractor,
61     transitionInteractor: KeyguardTransitionInteractor,
62     powerInteractor: PowerInteractor,
63     keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
64 ) :
65     TransitionInteractor(
66         fromState = KeyguardState.GLANCEABLE_HUB,
67         transitionInteractor = transitionInteractor,
68         mainDispatcher = mainDispatcher,
69         bgDispatcher = bgDispatcher,
70         powerInteractor = powerInteractor,
71         keyguardOcclusionInteractor = keyguardOcclusionInteractor,
72         keyguardInteractor = keyguardInteractor,
73     ) {
74 
75     override fun start() {
76         if (SceneContainerFlag.isEnabled) return
77         if (!communalSettingsInteractor.isCommunalFlagEnabled()) {
78             return
79         }
80         listenForHubToAodOrDozing()
81         listenForHubToPrimaryBouncer()
82         listenForHubToAlternateBouncer()
83         listenForHubToOccluded()
84         listenForHubToGone()
85         listenForHubToDreaming()
86     }
87 
88     override fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator {
89         return ValueAnimator().apply {
90             interpolator = Interpolators.LINEAR
91             duration =
92                 when (toState) {
93                     KeyguardState.LOCKSCREEN -> TO_LOCKSCREEN_DURATION
94                     KeyguardState.OCCLUDED -> TO_OCCLUDED_DURATION
95                     KeyguardState.ALTERNATE_BOUNCER -> TO_BOUNCER_DURATION
96                     KeyguardState.PRIMARY_BOUNCER -> TO_BOUNCER_DURATION
97                     else -> DEFAULT_DURATION
98                 }.inWholeMilliseconds
99         }
100     }
101 
102     private fun listenForHubToPrimaryBouncer() {
103         scope.launch("$TAG#listenForHubToPrimaryBouncer") {
104             keyguardInteractor.primaryBouncerShowing
105                 .filterRelevantKeyguardStateAnd { primaryBouncerShowing -> primaryBouncerShowing }
106                 .collect {
107                     // Bouncer shows on top of the hub, so do not change scenes here.
108                     startTransitionTo(KeyguardState.PRIMARY_BOUNCER)
109                 }
110         }
111     }
112 
113     private fun listenForHubToAlternateBouncer() {
114         scope.launch("$TAG#listenForHubToAlternateBouncer") {
115             keyguardInteractor.alternateBouncerShowing
116                 .filterRelevantKeyguardStateAnd { alternateBouncerShowing ->
117                     alternateBouncerShowing
118                 }
119                 .collect { pair ->
120                     // Bouncer shows on top of the hub, so do not change scenes here.
121                     startTransitionTo(KeyguardState.ALTERNATE_BOUNCER)
122                 }
123         }
124     }
125 
126     private fun listenForHubToAodOrDozing() {
127         scope.launch {
128             powerInteractor.isAsleep
129                 .filterRelevantKeyguardStateAnd { isAsleep -> isAsleep }
130                 .collect {
131                     communalSceneInteractor.changeScene(
132                         newScene = CommunalScenes.Blank,
133                         loggingReason = "hub to sleep",
134                         keyguardState = keyguardInteractor.asleepKeyguardState.value,
135                     )
136                 }
137         }
138     }
139 
140     private fun listenForHubToDreaming() {
141         if (!communalSettingsInteractor.isV2FlagEnabled()) {
142             return
143         }
144 
145         scope.launch {
146             keyguardInteractor.isAbleToDream
147                 .filterRelevantKeyguardStateAnd { isAbleToDream -> isAbleToDream }
148                 .collect {
149                     communalSceneInteractor.changeScene(
150                         newScene = CommunalScenes.Blank,
151                         loggingReason = "hub to dreaming",
152                         keyguardState = KeyguardState.DREAMING,
153                     )
154                 }
155         }
156     }
157 
158     private fun listenForHubToOccluded() {
159         if (KeyguardWmStateRefactor.isEnabled) {
160             scope.launch {
161                 keyguardOcclusionInteractor.isShowWhenLockedActivityOnTop
162                     .filterRelevantKeyguardStateAnd { onTop -> onTop }
163                     .collect {
164                         maybeStartTransitionToOccludedOrInsecureCamera { state, reason ->
165                             communalSceneInteractor.changeScene(
166                                 newScene = CommunalScenes.Blank,
167                                 loggingReason = "hub to occluded (KeyguardWmStateRefactor)",
168                                 transitionKey = CommunalTransitionKeys.SimpleFade,
169                                 keyguardState = state,
170                             )
171                             null
172                         }
173                     }
174             }
175         } else {
176             scope.launch {
177                 combine(
178                         keyguardInteractor.isKeyguardOccluded,
179                         keyguardInteractor.isDreaming,
180                         ::Pair,
181                     )
182                     // Debounce signals since there is a race condition between the occluded and
183                     // dreaming signals when starting or stopping dreaming. We therefore add a small
184                     // delay to give enough time for occluded to flip to false when the dream
185                     // ends, to avoid transitioning to OCCLUDED erroneously when exiting the dream
186                     // or when the dream starts underneath the hub.
187                     .debounce(200.milliseconds)
188                     .sampleFilter(
189                         // When launching activities from widgets on the hub, we have a
190                         // custom occlusion animation.
191                         communalSceneInteractor.isLaunchingWidget
192                     ) { launchingWidget ->
193                         !launchingWidget
194                     }
195                     .filterRelevantKeyguardStateAnd { (isOccluded, isDreaming) ->
196                         isOccluded && !isDreaming
197                     }
198                     .collect { _ ->
199                         communalSceneInteractor.changeScene(
200                             newScene = CommunalScenes.Blank,
201                             loggingReason = "hub to occluded",
202                             transitionKey = CommunalTransitionKeys.SimpleFade,
203                             keyguardState = KeyguardState.OCCLUDED,
204                         )
205                     }
206             }
207         }
208     }
209 
210     private fun listenForHubToGone() {
211         if (SceneContainerFlag.isEnabled) return
212         scope.launch {
213             allOf(
214                     keyguardInteractor.isKeyguardGoingAway,
215                     // TODO(b/327225415): Handle edit mode opening here to avoid going to GONE
216                     // state until after edit mode is ready to be shown.
217                     noneOf(
218                         // When launching activities from widgets on the hub, we wait to change
219                         // scenes until the activity launch is complete.
220                         communalSceneInteractor.isLaunchingWidget
221                     ),
222                 )
223                 .filterRelevantKeyguardStateAnd { isKeyguardGoingAway -> isKeyguardGoingAway }
224                 .sample(communalSceneInteractor.editModeState, ::Pair)
225                 .collect { (_, editModeState) ->
226                     if (
227                         editModeState == EditModeState.STARTING ||
228                             editModeState == EditModeState.SHOWING
229                     ) {
230                         // Don't change scenes here as that is handled by the edit activity.
231                         startTransitionTo(KeyguardState.GONE)
232                     } else {
233                         communalSceneInteractor.changeScene(
234                             newScene = CommunalScenes.Blank,
235                             loggingReason = "hub to gone",
236                             transitionKey = CommunalTransitionKeys.SimpleFade,
237                             keyguardState = KeyguardState.GONE,
238                         )
239                     }
240                 }
241         }
242     }
243 
244     companion object {
245         const val TAG = "FromGlanceableHubTransitionInteractor"
246 
247         /**
248          * DEFAULT_DURATION controls the timing for all animations other than those with overrides
249          * in [getDefaultAnimatorForTransitionsToState].
250          *
251          * Set at 400ms for parity with [FromLockscreenTransitionInteractor]
252          */
253         val DEFAULT_DURATION = 400.milliseconds
254         // To lockscreen duration must be at least 500ms to allow for potential screen rotation
255         // during the transition while the animation begins after 500ms.
256         val TO_LOCKSCREEN_DURATION = 1.seconds
257         val TO_BOUNCER_DURATION = 400.milliseconds
258         val TO_OCCLUDED_DURATION = 450.milliseconds
259         val TO_AOD_DURATION = 500.milliseconds
260     }
261 }
262