• 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.MathUtils
21 import com.android.app.animation.Interpolators
22 import com.android.app.tracing.coroutines.launchTraced as launch
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.Application
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.Edge
33 import com.android.systemui.keyguard.shared.model.KeyguardState
34 import com.android.systemui.keyguard.shared.model.StatusBarState.KEYGUARD
35 import com.android.systemui.keyguard.shared.model.TransitionInfo
36 import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
37 import com.android.systemui.keyguard.shared.model.TransitionState
38 import com.android.systemui.keyguard.shared.model.TransitionStep
39 import com.android.systemui.power.domain.interactor.PowerInteractor
40 import com.android.systemui.power.shared.model.WakeSleepReason.FOLD
41 import com.android.systemui.scene.shared.flag.SceneContainerFlag
42 import com.android.systemui.scene.shared.model.Scenes
43 import com.android.systemui.shade.data.repository.ShadeRepository
44 import com.android.systemui.util.kotlin.sample
45 import java.util.UUID
46 import javax.inject.Inject
47 import kotlin.time.Duration.Companion.milliseconds
48 import kotlin.time.Duration.Companion.seconds
49 import kotlinx.coroutines.CoroutineDispatcher
50 import kotlinx.coroutines.CoroutineScope
51 import kotlinx.coroutines.flow.Flow
52 import kotlinx.coroutines.flow.distinctUntilChanged
53 import kotlinx.coroutines.flow.filter
54 import kotlinx.coroutines.flow.filterNotNull
55 import kotlinx.coroutines.flow.map
56 import kotlinx.coroutines.flow.onStart
57 
58 @SysUISingleton
59 class FromLockscreenTransitionInteractor
60 @Inject
61 constructor(
62     override val transitionRepository: KeyguardTransitionRepository,
63     override val internalTransitionInteractor: InternalKeyguardTransitionInteractor,
64     transitionInteractor: KeyguardTransitionInteractor,
65     @Background private val scope: CoroutineScope,
66     @Application private val applicationScope: CoroutineScope,
67     @Background bgDispatcher: CoroutineDispatcher,
68     @Main mainDispatcher: CoroutineDispatcher,
69     keyguardInteractor: KeyguardInteractor,
70     private val shadeRepository: ShadeRepository,
71     powerInteractor: PowerInteractor,
72     private val communalSettingsInteractor: CommunalSettingsInteractor,
73     private val communalSceneInteractor: CommunalSceneInteractor,
74     private val swipeToDismissInteractor: SwipeToDismissInteractor,
75     keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
76 ) :
77     TransitionInteractor(
78         fromState = KeyguardState.LOCKSCREEN,
79         transitionInteractor = transitionInteractor,
80         mainDispatcher = mainDispatcher,
81         bgDispatcher = bgDispatcher,
82         powerInteractor = powerInteractor,
83         keyguardOcclusionInteractor = keyguardOcclusionInteractor,
84         keyguardInteractor = keyguardInteractor,
85     ) {
86 
87     override fun start() {
88         listenForLockscreenToGone()
89         listenForLockscreenToGoneDragging()
90         listenForLockscreenToOccludedOrDreaming()
91         listenForLockscreenToAodOrDozing()
92         listenForLockscreenToPrimaryBouncer()
93         listenForLockscreenToDreaming()
94         listenForLockscreenToPrimaryBouncerDragging()
95         listenForLockscreenToAlternateBouncer()
96         listenForLockscreenTransitionToCamera()
97         if (communalSettingsInteractor.isV2FlagEnabled()) {
98             listenForLockscreenToGlanceableHubV2()
99         }
100     }
101 
102     /**
103      * Whether we want the surface behind the keyguard visible for the transition from LOCKSCREEN,
104      * or null if we don't care and should just use a reasonable default.
105      *
106      * [KeyguardSurfaceBehindInteractor] will switch to this flow whenever a transition from
107      * LOCKSCREEN is running.
108      */
109     val surfaceBehindVisibility: Flow<Boolean?> =
110         transitionInteractor
111             .transition(
112                 edge = Edge.create(from = KeyguardState.LOCKSCREEN, to = Scenes.Gone),
113                 edgeWithoutSceneContainer =
114                     Edge.create(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GONE),
115             )
116             .map<TransitionStep, Boolean?> {
117                 true // Make the surface visible during LS -> GONE transitions.
118             }
119             .onStart {
120                 // Default to null ("don't care, use a reasonable default").
121                 emit(null)
122             }
123             .distinctUntilChanged()
124 
125     private fun listenForLockscreenTransitionToCamera() {
126         listenForTransitionToCamera(scope, keyguardInteractor)
127     }
128 
129     private fun listenForLockscreenToDreaming() {
130         if (KeyguardWmStateRefactor.isEnabled) {
131             return
132         }
133 
134         val invalidFromStates = setOf(KeyguardState.AOD, KeyguardState.DOZING)
135         scope.launch("$TAG#listenForLockscreenToDreaming") {
136             keyguardInteractor.isAbleToDream
137                 .filterRelevantKeyguardState()
138                 .sample(transitionInteractor.isFinishedIn(KeyguardState.LOCKSCREEN), ::Pair)
139                 .collect { (isAbleToDream, isOnLockscreen) ->
140                     val transitionInfo =
141                         internalTransitionInteractor.currentTransitionInfoInternal()
142                     val isTransitionInterruptible =
143                         transitionInfo.to == KeyguardState.LOCKSCREEN &&
144                             !invalidFromStates.contains(transitionInfo.from)
145                     if (isAbleToDream && (isOnLockscreen || isTransitionInterruptible)) {
146                         startTransitionTo(KeyguardState.DREAMING)
147                     }
148                 }
149         }
150     }
151 
152     private fun listenForLockscreenToPrimaryBouncer() {
153         if (SceneContainerFlag.isEnabled) return
154         scope.launch("$TAG#listenForLockscreenToPrimaryBouncer") {
155             keyguardInteractor.primaryBouncerShowing
156                 .filterRelevantKeyguardStateAnd { isBouncerShowing -> isBouncerShowing }
157                 .collect {
158                     startTransitionTo(
159                         KeyguardState.PRIMARY_BOUNCER,
160                         ownerReason = "#listenForLockscreenToPrimaryBouncer",
161                     )
162                 }
163         }
164     }
165 
166     private fun listenForLockscreenToAlternateBouncer() {
167         scope.launch("$TAG#listenForLockscreenToAlternateBouncer") {
168             keyguardInteractor.alternateBouncerShowing
169                 .filterRelevantKeyguardStateAnd { isAlternateBouncerShowing ->
170                     isAlternateBouncerShowing
171                 }
172                 .collect { pair -> startTransitionTo(KeyguardState.ALTERNATE_BOUNCER) }
173         }
174     }
175 
176     /* Starts transitions when manually dragging up the bouncer from the lockscreen. */
177     private fun listenForLockscreenToPrimaryBouncerDragging() {
178         if (SceneContainerFlag.isEnabled) return
179         var transitionId: UUID? = null
180         applicationScope.launch("$TAG#listenForLockscreenToPrimaryBouncerDragging") {
181             shadeRepository.legacyShadeExpansion.collect { shadeExpansion ->
182                 val statusBarState = keyguardInteractor.statusBarState.value
183                 val isKeyguardUnlocked = keyguardInteractor.isKeyguardDismissible.value
184                 val isKeyguardOccluded = keyguardInteractor.isKeyguardOccluded.value
185                 val startedStep = transitionInteractor.startedKeyguardTransitionStep.value
186 
187                 val id = transitionId
188                 val currentTransitionInfo =
189                     internalTransitionInteractor.currentTransitionInfoInternal()
190                 if (id != null) {
191                     if (startedStep.to == KeyguardState.PRIMARY_BOUNCER) {
192                         // An existing `id` means a transition is started, and calls to
193                         // `updateTransition` will control it until FINISHED or CANCELED
194                         var nextState =
195                             if (shadeExpansion == 0f) {
196                                 TransitionState.FINISHED
197                             } else if (shadeExpansion == 1f) {
198                                 TransitionState.CANCELED
199                             } else {
200                                 TransitionState.RUNNING
201                             }
202 
203                         // startTransition below will issue the CANCELED directly
204                         if (nextState != TransitionState.CANCELED) {
205                             transitionRepository.updateTransition(
206                                 id,
207                                 // This maps the shadeExpansion to a much faster curve, to match
208                                 // the existing logic
209                                 1f - MathUtils.constrainedMap(0f, 1f, 0.88f, 1f, shadeExpansion),
210                                 nextState,
211                             )
212                         }
213 
214                         if (
215                             nextState == TransitionState.CANCELED ||
216                                 nextState == TransitionState.FINISHED
217                         ) {
218                             transitionId = null
219                         }
220 
221                         // If canceled, just put the state back
222                         // TODO(b/278086361): This logic should happen in
223                         //  FromPrimaryBouncerInteractor.
224                         if (nextState == TransitionState.CANCELED) {
225                             transitionRepository.startTransition(
226                                 TransitionInfo(
227                                     ownerName =
228                                         "$name " + "(on behalf of FromPrimaryBouncerInteractor)",
229                                     from = KeyguardState.PRIMARY_BOUNCER,
230                                     to =
231                                         if (isKeyguardOccluded) KeyguardState.OCCLUDED
232                                         else KeyguardState.LOCKSCREEN,
233                                     modeOnCanceled = TransitionModeOnCanceled.REVERSE,
234                                     animator =
235                                         getDefaultAnimatorForTransitionsToState(
236                                                 KeyguardState.LOCKSCREEN
237                                             )
238                                             .apply { duration = 100L },
239                                 )
240                             )
241                         }
242                     }
243                 } else {
244                     // TODO (b/251849525): Remove statusbarstate check when that state is
245                     // integrated into KeyguardTransitionRepository
246                     if (
247                         // Use currentTransitionInfo to decide whether to start the transition.
248                         currentTransitionInfo.to == KeyguardState.LOCKSCREEN &&
249                             shadeExpansion > 0f &&
250                             shadeExpansion < 1f &&
251                             shadeRepository.legacyShadeTracking.value &&
252                             !isKeyguardUnlocked &&
253                             statusBarState == KEYGUARD
254                     ) {
255                         transitionId =
256                             startTransitionTo(
257                                 toState = KeyguardState.PRIMARY_BOUNCER,
258                                 animator = null, // transition will be manually controlled,
259                                 ownerReason = "#listenForLockscreenToPrimaryBouncerDragging",
260                             )
261                     }
262                 }
263             }
264         }
265 
266         // Ensure that transitionId is nulled out if external signals cause a PRIMARY_BOUNCER
267         // transition to be canceled.
268         scope.launch {
269             transitionInteractor.transitions
270                 .filter {
271                     it.transitionState == TransitionState.CANCELED &&
272                         it.to == KeyguardState.PRIMARY_BOUNCER
273                 }
274                 .collect { transitionId = null }
275         }
276     }
277 
278     fun dismissKeyguard() {
279         scope.launch("$TAG#dismissKeyguard") {
280             startTransitionTo(KeyguardState.GONE, ownerReason = "#dismissKeyguard()")
281         }
282     }
283 
284     private fun listenForLockscreenToGone() {
285         if (SceneContainerFlag.isEnabled) return
286         if (KeyguardWmStateRefactor.isEnabled) return
287         scope.launch("$TAG#listenForLockscreenToGone") {
288             keyguardInteractor.isKeyguardGoingAway
289                 .filterRelevantKeyguardStateAnd { isKeyguardGoingAway -> isKeyguardGoingAway }
290                 .collect {
291                     startTransitionTo(
292                         KeyguardState.GONE,
293                         modeOnCanceled = TransitionModeOnCanceled.RESET,
294                         ownerReason = "keyguard interactor says keyguard is going away",
295                     )
296                 }
297         }
298     }
299 
300     private fun listenForLockscreenToGoneDragging() {
301         if (SceneContainerFlag.isEnabled) return
302         if (KeyguardWmStateRefactor.isEnabled) {
303             // When the refactor is enabled, we no longer use isKeyguardGoingAway.
304             scope.launch("$TAG#listenForLockscreenToGoneDragging") {
305                 swipeToDismissInteractor.dismissFling
306                     .filterNotNull()
307                     .filterRelevantKeyguardState()
308                     .collect { _ ->
309                         startTransitionTo(KeyguardState.GONE, ownerReason = "dismissFling != null")
310                     }
311             }
312         }
313     }
314 
315     private fun listenForLockscreenToOccludedOrDreaming() {
316         if (KeyguardWmStateRefactor.isEnabled) {
317             scope.launch("$TAG#listenForLockscreenToOccludedOrDreaming") {
318                 keyguardOcclusionInteractor.showWhenLockedActivityInfo
319                     .filterRelevantKeyguardStateAnd { it.isOnTop }
320                     .collect { taskInfo ->
321                         startTransitionTo(
322                             if (taskInfo.isDream()) {
323                                 KeyguardState.DREAMING
324                             } else {
325                                 KeyguardState.OCCLUDED
326                             }
327                         )
328                     }
329             }
330         } else {
331             scope.launch("$TAG#listenForLockscreenToOccludedOrDreaming") {
332                 keyguardInteractor.isKeyguardOccluded
333                     .filterRelevantKeyguardStateAnd { isOccluded -> isOccluded }
334                     .collect { startTransitionTo(KeyguardState.OCCLUDED) }
335             }
336         }
337     }
338 
339     private fun listenForLockscreenToAodOrDozing() {
340         scope.launch("$TAG#listenForLockscreenToAodOrDozing") {
341             listenForSleepTransition(
342                 modeOnCanceledFromStartedStep = { startedStep ->
343                     if (
344                         keyguardInteractor.asleepKeyguardState.value == KeyguardState.AOD &&
345                             startedStep.from == KeyguardState.AOD
346                     ) {
347                         TransitionModeOnCanceled.REVERSE
348                     } else {
349                         TransitionModeOnCanceled.LAST_VALUE
350                     }
351                 }
352             )
353         }
354     }
355 
356     private fun listenForLockscreenToGlanceableHubV2() {
357         scope.launch {
358             communalSettingsInteractor.autoOpenEnabled
359                 .filterRelevantKeyguardStateAnd { shouldShow -> shouldShow }
360                 .collect {
361                     communalSceneInteractor.changeScene(
362                         newScene = CommunalScenes.Communal,
363                         loggingReason = "lockscreen to communal",
364                     )
365                 }
366         }
367     }
368 
369     override fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator {
370         return ValueAnimator().apply {
371             interpolator = Interpolators.LINEAR
372             duration =
373                 when (toState) {
374                     // Adds 100ms to the overall delay to workaround legacy setOccluded calls
375                     // being delayed in KeyguardViewMediator
376                     KeyguardState.DREAMING -> TO_DREAMING_DURATION + 100.milliseconds
377                     KeyguardState.OCCLUDED -> TO_OCCLUDED_DURATION
378                     KeyguardState.AOD ->
379                         if (powerInteractor.detailedWakefulness.value.lastSleepReason == FOLD) {
380                             TO_AOD_FOLD_DURATION
381                         } else {
382                             TO_AOD_DURATION
383                         }
384                     KeyguardState.DOZING -> TO_DOZING_DURATION
385                     KeyguardState.GLANCEABLE_HUB -> TO_GLANCEABLE_HUB_DURATION
386                     else -> DEFAULT_DURATION
387                 }.inWholeMilliseconds
388         }
389     }
390 
391     companion object {
392         private const val TAG = "FromLockscreenTransitionInteractor"
393         private val DEFAULT_DURATION = 400.milliseconds
394         val TO_DOZING_DURATION = 500.milliseconds
395         val TO_DREAMING_DURATION = 933.milliseconds
396         val TO_OCCLUDED_DURATION = 550.milliseconds
397         val TO_AOD_DURATION = 500.milliseconds
398         val TO_AOD_FOLD_DURATION = 1100.milliseconds
399         val TO_PRIMARY_BOUNCER_DURATION = DEFAULT_DURATION
400         val TO_GONE_DURATION = 633.milliseconds
401         val TO_GLANCEABLE_HUB_DURATION = 1.seconds
402     }
403 }
404