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 com.android.app.animation.Interpolators
21 import com.android.systemui.Flags.restartDreamOnUnocclude
22 import com.android.systemui.communal.domain.interactor.CommunalInteractor
23 import com.android.systemui.dagger.SysUISingleton
24 import com.android.systemui.dagger.qualifiers.Background
25 import com.android.systemui.dagger.qualifiers.Main
26 import com.android.systemui.keyguard.KeyguardWmStateRefactor
27 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
28 import com.android.systemui.keyguard.shared.model.KeyguardState
29 import com.android.systemui.power.domain.interactor.PowerInteractor
30 import com.android.systemui.scene.shared.flag.SceneContainerFlag
31 import com.android.systemui.util.kotlin.Utils.Companion.sample
32 import com.android.systemui.util.kotlin.sample
33 import javax.inject.Inject
34 import kotlin.time.Duration.Companion.milliseconds
35 import kotlinx.coroutines.CoroutineDispatcher
36 import kotlinx.coroutines.CoroutineScope
37 import kotlinx.coroutines.launch
38 
39 @SysUISingleton
40 class FromOccludedTransitionInteractor
41 @Inject
42 constructor(
43     override val transitionRepository: KeyguardTransitionRepository,
44     transitionInteractor: KeyguardTransitionInteractor,
45     @Background private val scope: CoroutineScope,
46     @Background bgDispatcher: CoroutineDispatcher,
47     @Main mainDispatcher: CoroutineDispatcher,
48     keyguardInteractor: KeyguardInteractor,
49     powerInteractor: PowerInteractor,
50     private val communalInteractor: CommunalInteractor,
51     keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
52 ) :
53     TransitionInteractor(
54         fromState = KeyguardState.OCCLUDED,
55         transitionInteractor = transitionInteractor,
56         mainDispatcher = mainDispatcher,
57         bgDispatcher = bgDispatcher,
58         powerInteractor = powerInteractor,
59         keyguardOcclusionInteractor = keyguardOcclusionInteractor,
60         keyguardInteractor = keyguardInteractor,
61     ) {
62 
63     override fun start() {
64         listenForOccludedToLockscreenOrHub()
65         listenForOccludedToDreaming()
66         listenForOccludedToAsleep()
67         listenForOccludedToGone()
68         listenForOccludedToAlternateBouncer()
69         listenForOccludedToPrimaryBouncer()
70     }
71 
72     private fun listenForOccludedToPrimaryBouncer() {
73         scope.launch {
74             keyguardInteractor.primaryBouncerShowing
75                 .filterRelevantKeyguardStateAnd { isBouncerShowing -> isBouncerShowing }
76                 .collect { startTransitionTo(KeyguardState.PRIMARY_BOUNCER) }
77         }
78     }
79 
80     private fun listenForOccludedToDreaming() {
81         scope.launch {
82             keyguardInteractor.isAbleToDream
83                 .filterRelevantKeyguardStateAnd { isAbleToDream -> isAbleToDream }
84                 .collect { startTransitionTo(KeyguardState.DREAMING) }
85         }
86     }
87 
88     private fun listenForOccludedToLockscreenOrHub() {
89         if (KeyguardWmStateRefactor.isEnabled) {
90             scope.launch {
91                 keyguardOcclusionInteractor.isShowWhenLockedActivityOnTop
92                     .filterRelevantKeyguardStateAnd { onTop -> !onTop }
93                     .sample(
94                         communalInteractor.isIdleOnCommunal,
95                         communalInteractor.showCommunalFromOccluded,
96                         communalInteractor.dreamFromOccluded,
97                     )
98                     .collect { (_, isIdleOnCommunal, showCommunalFromOccluded, dreamFromOccluded) ->
99                         startTransitionToLockscreenOrHub(
100                             isIdleOnCommunal,
101                             showCommunalFromOccluded,
102                             dreamFromOccluded
103                         )
104                     }
105             }
106         } else {
107             scope.launch {
108                 keyguardInteractor.isKeyguardOccluded
109                     .sample(
110                         keyguardInteractor.isKeyguardShowing,
111                         communalInteractor.isIdleOnCommunal,
112                         communalInteractor.showCommunalFromOccluded,
113                         communalInteractor.dreamFromOccluded,
114                     )
115                     .filterRelevantKeyguardStateAnd { (isOccluded, isShowing, _, _, _) ->
116                         !isOccluded && isShowing
117                     }
118                     .collect { (_, _, isIdleOnCommunal, showCommunalFromOccluded, dreamFromOccluded)
119                         ->
120                         startTransitionToLockscreenOrHub(
121                             isIdleOnCommunal,
122                             showCommunalFromOccluded,
123                             dreamFromOccluded
124                         )
125                     }
126             }
127         }
128     }
129 
130     private suspend fun FromOccludedTransitionInteractor.startTransitionToLockscreenOrHub(
131         isIdleOnCommunal: Boolean,
132         showCommunalFromOccluded: Boolean,
133         dreamFromOccluded: Boolean,
134     ) {
135         if (restartDreamOnUnocclude() && dreamFromOccluded) {
136             startTransitionTo(KeyguardState.DREAMING)
137         } else if (isIdleOnCommunal || showCommunalFromOccluded) {
138             // TODO(b/336576536): Check if adaptation for scene framework is needed
139             if (SceneContainerFlag.isEnabled) return
140             startTransitionTo(KeyguardState.GLANCEABLE_HUB)
141         } else {
142             startTransitionTo(KeyguardState.LOCKSCREEN)
143         }
144     }
145 
146     private fun listenForOccludedToGone() {
147         // TODO(b/336576536): Check if adaptation for scene framework is needed
148         if (SceneContainerFlag.isEnabled) return
149         if (KeyguardWmStateRefactor.isEnabled) {
150             // We don't think OCCLUDED to GONE is possible. You should always have to go via a
151             // *_BOUNCER state to end up GONE. Launching an activity over a dismissable keyguard
152             // dismisses it, and even "extend unlock" doesn't unlock the device in the background.
153             // If we're wrong - sorry, add it back here.
154             return
155         }
156 
157         scope.launch {
158             keyguardInteractor.isKeyguardOccluded
159                 .sample(keyguardInteractor.isKeyguardShowing, ::Pair)
160                 .filterRelevantKeyguardStateAnd { (occluded, showing) -> !occluded && !showing }
161                 .collect {
162                     // Occlusion signals come from the framework, and should interrupt any
163                     // existing transition
164                     startTransitionTo(KeyguardState.GONE)
165                 }
166         }
167     }
168 
169     private fun listenForOccludedToAsleep() {
170         scope.launch { listenForSleepTransition() }
171     }
172 
173     private fun listenForOccludedToAlternateBouncer() {
174         scope.launch {
175             keyguardInteractor.alternateBouncerShowing
176                 .filterRelevantKeyguardStateAnd { isAlternateBouncerShowing ->
177                     isAlternateBouncerShowing
178                 }
179                 .collect { startTransitionTo(KeyguardState.ALTERNATE_BOUNCER) }
180         }
181     }
182 
183     override fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator {
184         return ValueAnimator().apply {
185             interpolator =
186                 when (toState) {
187                     KeyguardState.ALTERNATE_BOUNCER -> Interpolators.FAST_OUT_SLOW_IN
188                     else -> Interpolators.LINEAR
189                 }
190 
191             duration =
192                 when (toState) {
193                     KeyguardState.LOCKSCREEN -> TO_LOCKSCREEN_DURATION
194                     KeyguardState.GLANCEABLE_HUB -> TO_GLANCEABLE_HUB_DURATION
195                     else -> DEFAULT_DURATION
196                 }.inWholeMilliseconds
197         }
198     }
199 
200     companion object {
201         const val TAG = "FromOccludedTransitionInteractor"
202         private val DEFAULT_DURATION = 500.milliseconds
203         val TO_LOCKSCREEN_DURATION = 933.milliseconds
204         val TO_GLANCEABLE_HUB_DURATION = 250.milliseconds
205         val TO_AOD_DURATION = DEFAULT_DURATION
206         val TO_DOZING_DURATION = DEFAULT_DURATION
207     }
208 }
209