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.systemui.keyguard.KeyguardWmStateRefactor
22 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
23 import com.android.systemui.keyguard.shared.model.KeyguardState
24 import com.android.systemui.keyguard.shared.model.TransitionInfo
25 import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
26 import com.android.systemui.keyguard.shared.model.TransitionStep
27 import com.android.systemui.power.domain.interactor.PowerInteractor
28 import com.android.systemui.util.kotlin.sample
29 import java.util.UUID
30 import kotlinx.coroutines.CoroutineDispatcher
31 import kotlinx.coroutines.CoroutineScope
32 import kotlinx.coroutines.flow.Flow
33 import kotlinx.coroutines.flow.filter
34 import kotlinx.coroutines.flow.flowOn
35 import kotlinx.coroutines.flow.map
36 import kotlinx.coroutines.launch
37 
38 /**
39  * Each TransitionInteractor is responsible for determining under which conditions to notify
40  * [KeyguardTransitionRepository] to signal a transition. When (and if) the transition occurs is
41  * determined by [KeyguardTransitionRepository].
42  *
43  * [name] field should be a unique identifiable string representing this state, used primarily for
44  * logging
45  *
46  * MUST list implementing classes in dagger module [StartKeyguardTransitionModule] and also in the
47  * 'when' clause of [KeyguardTransitionCoreStartable]
48  */
49 sealed class TransitionInteractor(
50     val fromState: KeyguardState,
51     val transitionInteractor: KeyguardTransitionInteractor,
52     val mainDispatcher: CoroutineDispatcher,
53     val bgDispatcher: CoroutineDispatcher,
54     val powerInteractor: PowerInteractor,
55     val keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
56     val keyguardInteractor: KeyguardInteractor,
57 ) {
58     val name = this::class.simpleName ?: "UnknownTransitionInteractor"
59     abstract val transitionRepository: KeyguardTransitionRepository
60     abstract fun start()
61 
62     /* Use background dispatcher for all [KeyguardTransitionInteractor] flows. Necessary because
63      * the [sample] utility internally runs a collect on the Unconfined dispatcher, resulting
64      * in continuations on the main thread. We don't want that for classes that inherit from this.
65      */
66     val startedKeyguardTransitionStep =
67         transitionInteractor.startedKeyguardTransitionStep.flowOn(bgDispatcher)
68     // The following are MutableSharedFlows, and do not require flowOn
69     val startedKeyguardState = transitionInteractor.startedKeyguardState
70     val finishedKeyguardState = transitionInteractor.finishedKeyguardState
71     val currentKeyguardState = transitionInteractor.currentKeyguardState
72 
73     suspend fun startTransitionTo(
74         toState: KeyguardState,
75         animator: ValueAnimator? = getDefaultAnimatorForTransitionsToState(toState),
76         modeOnCanceled: TransitionModeOnCanceled = TransitionModeOnCanceled.LAST_VALUE,
77         // Even more information about why the owner started this transition, if this is a dangerous
78         // transition (*cough* occlusion) where you'd be sad to not have all the info you can get in
79         // a bugreport.
80         ownerReason: String = "",
81     ): UUID? {
82         if (fromState != transitionInteractor.currentTransitionInfoInternal.value.to) {
83             Log.e(
84                 name,
85                 "Ignoring startTransition: This interactor asked to transition from " +
86                     "$fromState -> $toState, but we last transitioned to " +
87                     "${transitionInteractor.currentTransitionInfoInternal.value.to}, not " +
88                     "$fromState. This should never happen - check currentTransitionInfoInternal " +
89                     "or use filterRelevantKeyguardState before starting transitions."
90             )
91 
92             if (fromState == transitionInteractor.finishedKeyguardState.replayCache.last()) {
93                 Log.e(
94                     name,
95                     "This transition would not have been ignored prior to ag/26681239, since we " +
96                         "are FINISHED in $fromState (but have since started another transition). " +
97                         "If ignoring this transition has caused a regression, fix it by ensuring " +
98                         "that transitions are exclusively started from the most recently started " +
99                         "state."
100                 )
101             }
102             return null
103         }
104 
105         return transitionRepository.startTransition(
106             TransitionInfo(
107                 name + if (ownerReason.isNotBlank()) "($ownerReason)" else "",
108                 fromState,
109                 toState,
110                 animator,
111                 modeOnCanceled,
112             )
113         )
114     }
115 
116     /**
117      * Check whether we need to transition to [KeyguardState.OCCLUDED], based on the presence of a
118      * SHOW_WHEN_LOCKED activity, or back to [KeyguardState.GONE], for some power button launch
119      * gesture cases. If so, start the transition.
120      *
121      * Returns true if a transition was started, false otherwise.
122      */
123     suspend fun maybeStartTransitionToOccludedOrInsecureCamera(): Boolean {
124         // The refactor is required for the occlusion interactor to work.
125         KeyguardWmStateRefactor.isUnexpectedlyInLegacyMode()
126 
127         // Check if we should start a transition from the power gesture.
128         if (keyguardOcclusionInteractor.shouldTransitionFromPowerButtonGesture()) {
129             // See if we handled the insecure power gesture. If not, then we'll be launching the
130             // secure camera. Once KeyguardWmStateRefactor is fully enabled, we can clean up this
131             // code path by pulling maybeHandleInsecurePowerGesture() into this conditional.
132             if (!maybeHandleInsecurePowerGesture()) {
133                 // Otherwise, the double tap gesture occurred while not GONE and not dismissable,
134                 // which means we will launch the secure camera, which OCCLUDES the keyguard.
135                 startTransitionTo(
136                     KeyguardState.OCCLUDED,
137                     ownerReason = "Power button gesture on lockscreen"
138                 )
139             }
140 
141             return true
142         } else if (keyguardOcclusionInteractor.showWhenLockedActivityInfo.value.isOnTop) {
143             // A SHOW_WHEN_LOCKED activity is on top of the task stack. Transition to OCCLUDED so
144             // it's visible.
145             // TODO(b/307976454) - Centralize transition to DREAMING here.
146             startTransitionTo(
147                 KeyguardState.OCCLUDED,
148                 ownerReason = "SHOW_WHEN_LOCKED activity on top"
149             )
150 
151             return true
152         } else {
153             // No transition needed, let the interactor figure out where to go.
154             return false
155         }
156     }
157 
158     /**
159      * Transition to [KeyguardState.GONE] for the insecure power button launch gesture, if the
160      * conditions to do so are met.
161      *
162      * Called from [FromAodTransitionInteractor] if [KeyguardWmStateRefactor] is not enabled, or
163      * [maybeStartTransitionToOccludedOrInsecureCamera] if it's enabled.
164      */
165     @Deprecated("Will be merged into maybeStartTransitionToOccludedOrInsecureCamera")
166     suspend fun maybeHandleInsecurePowerGesture(): Boolean {
167         if (keyguardOcclusionInteractor.shouldTransitionFromPowerButtonGesture()) {
168             if (keyguardInteractor.isKeyguardDismissible.value) {
169                 startTransitionTo(
170                     KeyguardState.GONE,
171                     ownerReason = "Power button gesture while keyguard is dismissible"
172                 )
173 
174                 return true
175             } else if (keyguardOcclusionInteractor.occludingActivityWillDismissKeyguard.value) {
176                 // The double tap gesture occurred while not GONE (AOD/LOCKSCREEN/etc.), but the
177                 // keyguard is dismissable. The activity launch will dismiss the keyguard, so we
178                 // should transition to GONE.
179                 startTransitionTo(
180                     KeyguardState.GONE,
181                     ownerReason = "Power button gesture on dismissable keyguard"
182                 )
183 
184                 return true
185             }
186         }
187 
188         return false
189     }
190 
191     /**
192      * Transition to the appropriate state when the device goes to sleep while in [from].
193      *
194      * We could also just use [fromState], but it's more readable in the From*TransitionInteractor
195      * if you're explicitly declaring which state you're listening from. If you passed in the wrong
196      * state, [startTransitionTo] would complain anyway.
197      */
198     suspend fun listenForSleepTransition(
199         modeOnCanceledFromStartedStep: (TransitionStep) -> TransitionModeOnCanceled = {
200             TransitionModeOnCanceled.LAST_VALUE
201         }
202     ) {
203         powerInteractor.isAsleep
204             .filter { isAsleep -> isAsleep }
205             .filterRelevantKeyguardState()
206             .sample(startedKeyguardTransitionStep)
207             .map(modeOnCanceledFromStartedStep)
208             .collect { modeOnCanceled ->
209                 startTransitionTo(
210                     toState = transitionInteractor.asleepKeyguardState.value,
211                     modeOnCanceled = modeOnCanceled,
212                     ownerReason = "Sleep transition triggered"
213                 )
214             }
215     }
216 
217     /** This signal may come in before the occlusion signal, and can provide a custom transition */
218     fun listenForTransitionToCamera(
219         scope: CoroutineScope,
220         keyguardInteractor: KeyguardInteractor,
221     ) {
222         if (!KeyguardWmStateRefactor.isEnabled) {
223             scope.launch {
224                 keyguardInteractor.onCameraLaunchDetected.filterRelevantKeyguardState().collect {
225                     if (!maybeHandleInsecurePowerGesture()) {
226                         startTransitionTo(
227                             toState = KeyguardState.OCCLUDED,
228                             modeOnCanceled = TransitionModeOnCanceled.RESET,
229                             ownerReason = "keyguardInteractor.onCameraLaunchDetected",
230                         )
231                     }
232                 }
233             }
234         }
235     }
236 
237     /**
238      * Whether we're in the KeyguardState relevant to this From*TransitionInteractor (which we know
239      * from [fromState]).
240      *
241      * This uses [KeyguardTransitionInteractor.currentTransitionInfoInternal], which is more up to
242      * date than [startedKeyguardState] as it does not wait for the emission of the first STARTED
243      * step.
244      */
245     fun inOrTransitioningToRelevantKeyguardState(): Boolean {
246         return transitionInteractor.currentTransitionInfoInternal.value.to == fromState
247     }
248 
249     /**
250      * Filters emissions whenever we're not in a KeyguardState relevant to this
251      * From*TransitionInteractor (which we know from [fromState]).
252      */
253     fun <T> Flow<T>.filterRelevantKeyguardState(): Flow<T> {
254         return filter { inOrTransitioningToRelevantKeyguardState() }
255     }
256 
257     /**
258      * Filters emissions whenever we're not in a KeyguardState relevant to this
259      * From*TransitionInteractor (which we know from [fromState]).
260      */
261     fun <T> Flow<T>.filterRelevantKeyguardStateAnd(predicate: (T) -> Boolean): Flow<T> {
262         return filter { inOrTransitioningToRelevantKeyguardState() && predicate.invoke(it) }
263     }
264 
265     /**
266      * Returns a ValueAnimator to be used for transitions to [toState], if one is not explicitly
267      * passed to [startTransitionTo].
268      */
269     abstract fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator?
270 }
271