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