• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2022 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 android.util.Log
21 import com.android.app.tracing.coroutines.launchTraced as launch
22 import com.android.keyguard.KeyguardSecurityModel
23 import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
24 import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
25 import com.android.systemui.communal.shared.model.CommunalScenes
26 import com.android.systemui.dagger.SysUISingleton
27 import com.android.systemui.dagger.qualifiers.Background
28 import com.android.systemui.dagger.qualifiers.Main
29 import com.android.systemui.keyguard.KeyguardWmStateRefactor
30 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
31 import com.android.systemui.keyguard.shared.model.Edge
32 import com.android.systemui.keyguard.shared.model.KeyguardState
33 import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
34 import com.android.systemui.keyguard.shared.model.TransitionStep
35 import com.android.systemui.power.domain.interactor.PowerInteractor
36 import com.android.systemui.scene.shared.flag.SceneContainerFlag
37 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
38 import com.android.systemui.util.kotlin.Utils.Companion.sample
39 import com.android.systemui.util.kotlin.sample
40 import com.android.wm.shell.shared.animation.Interpolators
41 import javax.inject.Inject
42 import kotlin.time.Duration.Companion.milliseconds
43 import kotlinx.coroutines.CoroutineDispatcher
44 import kotlinx.coroutines.CoroutineScope
45 import kotlinx.coroutines.flow.Flow
46 import kotlinx.coroutines.flow.distinctUntilChanged
47 import kotlinx.coroutines.flow.filter
48 import kotlinx.coroutines.flow.map
49 import kotlinx.coroutines.flow.onStart
50 
51 @SysUISingleton
52 class FromPrimaryBouncerTransitionInteractor
53 @Inject
54 constructor(
55     override val transitionRepository: KeyguardTransitionRepository,
56     override val internalTransitionInteractor: InternalKeyguardTransitionInteractor,
57     transitionInteractor: KeyguardTransitionInteractor,
58     @Background private val scope: CoroutineScope,
59     @Background bgDispatcher: CoroutineDispatcher,
60     @Main mainDispatcher: CoroutineDispatcher,
61     keyguardInteractor: KeyguardInteractor,
62     private val communalSceneInteractor: CommunalSceneInteractor,
63     private val communalSettingsInteractor: CommunalSettingsInteractor,
64     private val keyguardSecurityModel: KeyguardSecurityModel,
65     private val selectedUserInteractor: SelectedUserInteractor,
66     powerInteractor: PowerInteractor,
67     keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
68 ) :
69     TransitionInteractor(
70         fromState = KeyguardState.PRIMARY_BOUNCER,
71         transitionInteractor = transitionInteractor,
72         mainDispatcher = mainDispatcher,
73         bgDispatcher = bgDispatcher,
74         powerInteractor = powerInteractor,
75         keyguardOcclusionInteractor = keyguardOcclusionInteractor,
76         keyguardInteractor = keyguardInteractor,
77     ) {
78 
79     override fun start() {
80         listenForPrimaryBouncerToGone()
81         listenForPrimaryBouncerToAsleep()
82         listenForPrimaryBouncerNotShowing()
83         listenForTransitionToCamera(scope, keyguardInteractor)
84     }
85 
86     val surfaceBehindVisibility: Flow<Boolean?> =
87         transitionInteractor
88             .transition(
89                 edge = Edge.INVALID,
90                 edgeWithoutSceneContainer =
91                     Edge.create(from = KeyguardState.PRIMARY_BOUNCER, to = KeyguardState.GONE),
92             )
93             .map<TransitionStep, Boolean?> { it.value > TO_GONE_SURFACE_BEHIND_VISIBLE_THRESHOLD }
94             .onStart {
95                 // Default to null ("don't care, use a reasonable default").
96                 emit(null)
97             }
98             .distinctUntilChanged()
99 
100     fun dismissPrimaryBouncer() {
101         scope.launch {
102             startTransitionTo(KeyguardState.GONE)
103             closeHubImmediatelyIfNeeded()
104         }
105     }
106 
107     private fun listenForPrimaryBouncerNotShowing() {
108         if (SceneContainerFlag.isEnabled) return
109         if (KeyguardWmStateRefactor.isEnabled) {
110             scope.launch {
111                 keyguardInteractor.primaryBouncerShowing
112                     .sample(powerInteractor.isAwake, communalSceneInteractor.isIdleOnCommunal)
113                     .filterRelevantKeyguardStateAnd { (isBouncerShowing, _, _) ->
114                         // TODO(b/307976454) - See if we need to listen for SHOW_WHEN_LOCKED
115                         // activities showing up over the bouncer. Camera launch can't show up over
116                         // bouncer since the first power press hides bouncer. Do occluding
117                         // activities auto hide bouncer? Not sure.
118                         !isBouncerShowing
119                     }
120                     .collect { (_, isAwake, isIdleOnCommunal) ->
121                         if (
122                             !maybeStartTransitionToOccludedOrInsecureCamera { state, reason ->
123                                 startTransitionTo(state, ownerReason = reason)
124                             } && isAwake
125                         ) {
126                             val toState =
127                                 if (isIdleOnCommunal) {
128                                     KeyguardState.GLANCEABLE_HUB
129                                 } else {
130                                     KeyguardState.LOCKSCREEN
131                                 }
132                             startTransitionTo(toState)
133                         }
134                     }
135             }
136         } else {
137             scope.launch {
138                 keyguardInteractor.primaryBouncerShowing
139                     .filterRelevantKeyguardStateAnd { isBouncerShowing -> !isBouncerShowing }
140                     .sample(
141                         powerInteractor.isAwake,
142                         keyguardInteractor.isDreaming,
143                         communalSceneInteractor.isIdleOnCommunal,
144                     )
145                     .collect { (_, isAwake, isDreaming, isIdleOnCommunal) ->
146                         val isOccluded = keyguardInteractor.isKeyguardOccluded.value
147                         val hubV2 = communalSettingsInteractor.isV2FlagEnabled()
148                         val toState =
149                             if (isAwake) {
150                                 if (isOccluded && !isDreaming) {
151                                     KeyguardState.OCCLUDED
152                                 } else if (!hubV2 && isIdleOnCommunal) {
153                                     KeyguardState.GLANCEABLE_HUB
154                                 } else if (isDreaming) {
155                                     KeyguardState.DREAMING
156                                 } else if (hubV2 && isIdleOnCommunal) {
157                                     KeyguardState.GLANCEABLE_HUB
158                                 } else {
159                                     KeyguardState.LOCKSCREEN
160                                 }
161                             } else {
162                                 // This shouldn't necessarily happen, but there's a bug in the
163                                 // bouncer logic which is incorrectly showing/hiding rapidly
164                                 Log.i(
165                                     TAG,
166                                     "Going back to sleeping state to correct an attempt to " +
167                                         "show bouncer",
168                                 )
169                                 keyguardInteractor.asleepKeyguardState.value
170                             }
171                         if (hubV2 && toState != KeyguardState.GLANCEABLE_HUB && isIdleOnCommunal) {
172                             // If bouncer is showing over the hub, we need to make sure we
173                             // properly dismiss the hub when transitioning away.
174                             communalSceneInteractor.changeScene(
175                                 newScene = CommunalScenes.Blank,
176                                 loggingReason = "bouncer no longer showing over GH",
177                                 keyguardState = toState,
178                             )
179                         } else {
180                             startTransitionTo(toState)
181                         }
182                     }
183             }
184         }
185     }
186 
187     private fun closeHubImmediatelyIfNeeded() {
188         // If the hub is showing, and we are not animating a widget launch nor transitioning to
189         // edit mode, then close the hub immediately.
190         if (
191             communalSceneInteractor.isIdleOnCommunal.value &&
192                 !communalSceneInteractor.isLaunchingWidget.value &&
193                 communalSceneInteractor.editModeState.value == null
194         ) {
195             communalSceneInteractor.snapToScene(
196                 newScene = CommunalScenes.Blank,
197                 loggingReason = "FromPrimaryBouncerTransitionInteractor",
198             )
199         }
200     }
201 
202     private fun listenForPrimaryBouncerToAsleep() {
203         if (SceneContainerFlag.isEnabled) return
204         scope.launch {
205             if (communalSettingsInteractor.isV2FlagEnabled()) {
206                 powerInteractor.isAsleep
207                     .filter { isAsleep -> isAsleep }
208                     .filterRelevantKeyguardState()
209                     .sample(communalSceneInteractor.isIdleOnCommunal)
210                     .collect { isIdleOnCommunal ->
211                         if (isIdleOnCommunal) {
212                             // If the bouncer is showing on top of the hub, then ensure we also
213                             // hide the hub.
214                             communalSceneInteractor.changeScene(
215                                 newScene = CommunalScenes.Blank,
216                                 loggingReason = "Sleep while primary bouncer showing over hub",
217                                 keyguardState = keyguardInteractor.asleepKeyguardState.value,
218                             )
219                         } else {
220                             startTransitionTo(
221                                 toState = keyguardInteractor.asleepKeyguardState.value,
222                                 ownerReason = "Sleep transition triggered",
223                             )
224                         }
225                     }
226             } else {
227                 listenForSleepTransition()
228             }
229         }
230     }
231 
232     private fun listenForPrimaryBouncerToGone() {
233         if (SceneContainerFlag.isEnabled) return
234         if (KeyguardWmStateRefactor.isEnabled) {
235             // This is handled in KeyguardSecurityContainerController and
236             // StatusBarKeyguardViewManager, which calls the transition interactor to kick off a
237             // transition vs. listening to legacy state flags.
238             return
239         }
240 
241         scope.launch {
242             keyguardInteractor.isKeyguardGoingAway
243                 .filterRelevantKeyguardStateAnd { isKeyguardGoingAway -> isKeyguardGoingAway }
244                 .collect {
245                     val securityMode =
246                         keyguardSecurityModel.getSecurityMode(
247                             selectedUserInteractor.getSelectedUserId()
248                         )
249                     // IME for password requires a slightly faster animation
250                     val duration =
251                         if (securityMode == KeyguardSecurityModel.SecurityMode.Password) {
252                             TO_GONE_SHORT_DURATION
253                         } else {
254                             TO_GONE_DURATION
255                         }
256 
257                     startTransitionTo(
258                         toState = KeyguardState.GONE,
259                         animator =
260                             getDefaultAnimatorForTransitionsToState(KeyguardState.GONE).apply {
261                                 this.duration = duration.inWholeMilliseconds
262                             },
263                         modeOnCanceled = TransitionModeOnCanceled.RESET,
264                     )
265                     closeHubImmediatelyIfNeeded()
266                 }
267         }
268     }
269 
270     override fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator {
271         return ValueAnimator().apply {
272             interpolator = Interpolators.LINEAR
273             duration =
274                 when (toState) {
275                     KeyguardState.AOD -> TO_AOD_DURATION
276                     KeyguardState.DOZING -> TO_DOZING_DURATION
277                     KeyguardState.GONE -> TO_GONE_DURATION
278                     KeyguardState.LOCKSCREEN -> TO_LOCKSCREEN_DURATION
279                     KeyguardState.GLANCEABLE_HUB -> TO_GLANCEABLE_HUB_DURATION
280                     KeyguardState.OCCLUDED -> TO_OCCLUDED_DURATION
281                     else -> DEFAULT_DURATION
282                 }.inWholeMilliseconds
283         }
284     }
285 
286     companion object {
287         private const val TAG = "FromPrimaryBouncerTransitionInteractor"
288         private val DEFAULT_DURATION = 300.milliseconds
289         val TO_AOD_DURATION = DEFAULT_DURATION
290         val TO_DOZING_DURATION = DEFAULT_DURATION
291         val TO_GONE_DURATION = 500.milliseconds
292         val TO_GONE_SHORT_DURATION = 200.milliseconds
293         val TO_LOCKSCREEN_DURATION = 450.milliseconds
294         val TO_OCCLUDED_DURATION = 550.milliseconds
295         val TO_GLANCEABLE_HUB_DURATION = DEFAULT_DURATION
296         val TO_GONE_SURFACE_BEHIND_VISIBLE_THRESHOLD = 0.1f
297         val TO_DREAMING_DURATION = DEFAULT_DURATION
298     }
299 }
300