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.launch
22 import com.android.systemui.Flags
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.keyguard.shared.model.TransitionModeOnCanceled
30 import com.android.systemui.power.domain.interactor.PowerInteractor
31 import com.android.systemui.scene.shared.flag.SceneContainerFlag
32 import com.android.systemui.util.kotlin.BooleanFlowOperators.allOf
33 import com.android.systemui.util.kotlin.BooleanFlowOperators.not
34 import javax.inject.Inject
35 import kotlin.time.Duration.Companion.milliseconds
36 import kotlin.time.Duration.Companion.seconds
37 import kotlinx.coroutines.CoroutineDispatcher
38 import kotlinx.coroutines.CoroutineScope
39 import kotlinx.coroutines.flow.collectLatest
40 import kotlinx.coroutines.launch
41 import kotlinx.coroutines.withContext
42 
43 @SysUISingleton
44 class FromGlanceableHubTransitionInteractor
45 @Inject
46 constructor(
47     @Background private val scope: CoroutineScope,
48     @Main mainDispatcher: CoroutineDispatcher,
49     @Background bgDispatcher: CoroutineDispatcher,
50     private val glanceableHubTransitions: GlanceableHubTransitions,
51     keyguardInteractor: KeyguardInteractor,
52     override val transitionRepository: KeyguardTransitionRepository,
53     transitionInteractor: KeyguardTransitionInteractor,
54     powerInteractor: PowerInteractor,
55     keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
56 ) :
57     TransitionInteractor(
58         fromState = KeyguardState.GLANCEABLE_HUB,
59         transitionInteractor = transitionInteractor,
60         mainDispatcher = mainDispatcher,
61         bgDispatcher = bgDispatcher,
62         powerInteractor = powerInteractor,
63         keyguardOcclusionInteractor = keyguardOcclusionInteractor,
64         keyguardInteractor = keyguardInteractor,
65     ) {
66 
67     override fun start() {
68         // TODO(b/336576536): Check if adaptation for scene framework is needed
69         if (SceneContainerFlag.isEnabled) return
70         if (!Flags.communalHub()) {
71             return
72         }
73         listenForHubToLockscreenOrDreaming()
74         listenForHubToDozing()
75         listenForHubToPrimaryBouncer()
76         listenForHubToAlternateBouncer()
77         listenForHubToOccluded()
78         listenForHubToGone()
79     }
80 
81     override fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator {
82         return ValueAnimator().apply {
83             interpolator = Interpolators.LINEAR
84             duration =
85                 when (toState) {
86                     KeyguardState.LOCKSCREEN -> TO_LOCKSCREEN_DURATION
87                     KeyguardState.OCCLUDED -> TO_OCCLUDED_DURATION
88                     else -> DEFAULT_DURATION
89                 }.inWholeMilliseconds
90         }
91     }
92 
93     /**
94      * Listens for the glanceable hub transition to lock screen and directly drives the keyguard
95      * transition.
96      */
97     private fun listenForHubToLockscreenOrDreaming() {
98         scope.launch("$TAG#listenForGlanceableHubToLockscreenOrDream") {
99             keyguardInteractor.isDreaming.collectLatest { dreaming ->
100                 withContext(mainDispatcher) {
101                     val toState =
102                         if (dreaming) {
103                             KeyguardState.DREAMING
104                         } else {
105                             KeyguardState.LOCKSCREEN
106                         }
107                     glanceableHubTransitions.listenForGlanceableHubTransition(
108                         transitionOwnerName = TAG,
109                         fromState = KeyguardState.GLANCEABLE_HUB,
110                         toState = toState,
111                     )
112                 }
113             }
114         }
115     }
116 
117     private fun listenForHubToPrimaryBouncer() {
118         scope.launch("$TAG#listenForHubToPrimaryBouncer") {
119             keyguardInteractor.primaryBouncerShowing
120                 .filterRelevantKeyguardStateAnd { primaryBouncerShowing -> primaryBouncerShowing }
121                 .collect { startTransitionTo(KeyguardState.PRIMARY_BOUNCER) }
122         }
123     }
124 
125     private fun listenForHubToAlternateBouncer() {
126         scope.launch("$TAG#listenForHubToAlternateBouncer") {
127             keyguardInteractor.alternateBouncerShowing
128                 .filterRelevantKeyguardStateAnd { alternateBouncerShowing ->
129                     alternateBouncerShowing
130                 }
131                 .collect { pair -> startTransitionTo(KeyguardState.ALTERNATE_BOUNCER) }
132         }
133     }
134 
135     private fun listenForHubToDozing() {
136         scope.launch {
137             powerInteractor.isAsleep
138                 .filterRelevantKeyguardStateAnd { isAsleep -> isAsleep }
139                 .collect {
140                     startTransitionTo(
141                         toState = KeyguardState.DOZING,
142                         modeOnCanceled = TransitionModeOnCanceled.LAST_VALUE,
143                     )
144                 }
145         }
146     }
147 
148     private fun listenForHubToOccluded() {
149         if (KeyguardWmStateRefactor.isEnabled) {
150             scope.launch {
151                 keyguardOcclusionInteractor.isShowWhenLockedActivityOnTop
152                     .filterRelevantKeyguardStateAnd { onTop -> onTop }
153                     .collect { maybeStartTransitionToOccludedOrInsecureCamera() }
154             }
155         } else {
156             scope.launch {
157                 allOf(keyguardInteractor.isKeyguardOccluded, not(keyguardInteractor.isDreaming))
158                     .filterRelevantKeyguardStateAnd { isOccludedAndNotDreaming ->
159                         isOccludedAndNotDreaming
160                     }
161                     .collect { isOccludedAndNotDreaming ->
162                         startTransitionTo(KeyguardState.OCCLUDED)
163                     }
164             }
165         }
166     }
167 
168     private fun listenForHubToGone() {
169         scope.launch {
170             keyguardInteractor.isKeyguardGoingAway
171                 .filterRelevantKeyguardStateAnd { isKeyguardGoingAway -> isKeyguardGoingAway }
172                 .collect { startTransitionTo(KeyguardState.GONE) }
173         }
174     }
175 
176     companion object {
177         const val TAG = "FromGlanceableHubTransitionInteractor"
178         val DEFAULT_DURATION = 1.seconds
179         val TO_LOCKSCREEN_DURATION = DEFAULT_DURATION
180         val TO_OCCLUDED_DURATION = 450.milliseconds
181     }
182 }
183