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.systemui.animation.Interpolators 21 import com.android.systemui.dagger.SysUISingleton 22 import com.android.systemui.dagger.qualifiers.Application 23 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository 24 import com.android.systemui.keyguard.shared.model.KeyguardState 25 import com.android.systemui.keyguard.shared.model.StatusBarState.KEYGUARD 26 import com.android.systemui.keyguard.shared.model.TransitionInfo 27 import com.android.systemui.keyguard.shared.model.TransitionState 28 import com.android.systemui.keyguard.shared.model.WakefulnessState 29 import com.android.systemui.shade.data.repository.ShadeRepository 30 import com.android.systemui.util.kotlin.sample 31 import java.util.UUID 32 import javax.inject.Inject 33 import kotlin.time.Duration 34 import kotlin.time.Duration.Companion.milliseconds 35 import kotlinx.coroutines.CoroutineScope 36 import kotlinx.coroutines.flow.combine 37 import kotlinx.coroutines.launch 38 39 @SysUISingleton 40 class FromLockscreenTransitionInteractor 41 @Inject 42 constructor( 43 @Application private val scope: CoroutineScope, 44 private val keyguardInteractor: KeyguardInteractor, 45 private val shadeRepository: ShadeRepository, 46 private val keyguardTransitionInteractor: KeyguardTransitionInteractor, 47 private val keyguardTransitionRepository: KeyguardTransitionRepository, 48 ) : TransitionInteractor(FromLockscreenTransitionInteractor::class.simpleName!!) { 49 50 override fun start() { 51 listenForLockscreenToGone() 52 listenForLockscreenToOccluded() 53 listenForLockscreenToCamera() 54 listenForLockscreenToAodOrDozing() 55 listenForLockscreenToPrimaryBouncer() 56 listenForLockscreenToDreaming() 57 listenForLockscreenToPrimaryBouncerDragging() 58 listenForLockscreenToAlternateBouncer() 59 } 60 61 private fun listenForLockscreenToDreaming() { 62 scope.launch { 63 keyguardInteractor.isAbleToDream 64 .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair) 65 .collect { pair -> 66 val (isAbleToDream, lastStartedTransition) = pair 67 if ( 68 isAbleToDream && 69 lastStartedTransition.to == KeyguardState.LOCKSCREEN && 70 lastStartedTransition.from != KeyguardState.AOD 71 ) { 72 keyguardTransitionRepository.startTransition( 73 TransitionInfo( 74 name, 75 KeyguardState.LOCKSCREEN, 76 KeyguardState.DREAMING, 77 getAnimator(TO_DREAMING_DURATION), 78 ) 79 ) 80 } 81 } 82 } 83 } 84 85 private fun listenForLockscreenToPrimaryBouncer() { 86 scope.launch { 87 keyguardInteractor.primaryBouncerShowing 88 .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair) 89 .collect { pair -> 90 val (isBouncerShowing, lastStartedTransitionStep) = pair 91 if ( 92 isBouncerShowing && lastStartedTransitionStep.to == KeyguardState.LOCKSCREEN 93 ) { 94 keyguardTransitionRepository.startTransition( 95 TransitionInfo( 96 ownerName = name, 97 from = KeyguardState.LOCKSCREEN, 98 to = KeyguardState.PRIMARY_BOUNCER, 99 animator = getAnimator(), 100 ) 101 ) 102 } 103 } 104 } 105 } 106 107 private fun listenForLockscreenToAlternateBouncer() { 108 scope.launch { 109 keyguardInteractor.alternateBouncerShowing 110 .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair) 111 .collect { pair -> 112 val (isAlternateBouncerShowing, lastStartedTransitionStep) = pair 113 if ( 114 isAlternateBouncerShowing && 115 lastStartedTransitionStep.to == KeyguardState.LOCKSCREEN 116 ) { 117 keyguardTransitionRepository.startTransition( 118 TransitionInfo( 119 ownerName = name, 120 from = KeyguardState.LOCKSCREEN, 121 to = KeyguardState.ALTERNATE_BOUNCER, 122 animator = getAnimator(), 123 ) 124 ) 125 } 126 } 127 } 128 } 129 130 /* Starts transitions when manually dragging up the bouncer from the lockscreen. */ 131 private fun listenForLockscreenToPrimaryBouncerDragging() { 132 var transitionId: UUID? = null 133 scope.launch { 134 shadeRepository.shadeModel 135 .sample( 136 combine( 137 keyguardTransitionInteractor.startedKeyguardTransitionStep, 138 keyguardInteractor.statusBarState, 139 keyguardInteractor.isKeyguardUnlocked, 140 ::toTriple 141 ), 142 ::toQuad 143 ) 144 .collect { (shadeModel, keyguardState, statusBarState, isKeyguardUnlocked) -> 145 val id = transitionId 146 if (id != null) { 147 if (keyguardState.to == KeyguardState.PRIMARY_BOUNCER) { 148 // An existing `id` means a transition is started, and calls to 149 // `updateTransition` will control it until FINISHED or CANCELED 150 var nextState = 151 if (shadeModel.expansionAmount == 0f) { 152 TransitionState.FINISHED 153 } else if (shadeModel.expansionAmount == 1f) { 154 TransitionState.CANCELED 155 } else { 156 TransitionState.RUNNING 157 } 158 keyguardTransitionRepository.updateTransition( 159 id, 160 1f - shadeModel.expansionAmount, 161 nextState, 162 ) 163 164 if ( 165 nextState == TransitionState.CANCELED || 166 nextState == TransitionState.FINISHED 167 ) { 168 transitionId = null 169 } 170 171 // If canceled, just put the state back 172 if (nextState == TransitionState.CANCELED) { 173 keyguardTransitionRepository.startTransition( 174 TransitionInfo( 175 ownerName = name, 176 from = KeyguardState.PRIMARY_BOUNCER, 177 to = KeyguardState.LOCKSCREEN, 178 animator = getAnimator(0.milliseconds) 179 ) 180 ) 181 } 182 } 183 } else { 184 // TODO (b/251849525): Remove statusbarstate check when that state is 185 // integrated into KeyguardTransitionRepository 186 if ( 187 keyguardState.to == KeyguardState.LOCKSCREEN && 188 shadeModel.isUserDragging && 189 !isKeyguardUnlocked && 190 statusBarState == KEYGUARD 191 ) { 192 transitionId = 193 keyguardTransitionRepository.startTransition( 194 TransitionInfo( 195 ownerName = name, 196 from = KeyguardState.LOCKSCREEN, 197 to = KeyguardState.PRIMARY_BOUNCER, 198 animator = null, 199 ) 200 ) 201 } 202 } 203 } 204 } 205 } 206 207 private fun listenForLockscreenToGone() { 208 scope.launch { 209 keyguardInteractor.isKeyguardGoingAway 210 .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair) 211 .collect { pair -> 212 val (isKeyguardGoingAway, lastStartedStep) = pair 213 if (isKeyguardGoingAway && lastStartedStep.to == KeyguardState.LOCKSCREEN) { 214 keyguardTransitionRepository.startTransition( 215 TransitionInfo( 216 name, 217 KeyguardState.LOCKSCREEN, 218 KeyguardState.GONE, 219 getAnimator(), 220 ) 221 ) 222 } 223 } 224 } 225 } 226 227 private fun listenForLockscreenToOccluded() { 228 scope.launch { 229 keyguardInteractor.isKeyguardOccluded 230 .sample( 231 combine( 232 keyguardTransitionInteractor.finishedKeyguardState, 233 keyguardInteractor.isDreaming, 234 ::Pair 235 ), 236 ::toTriple 237 ) 238 .collect { (isOccluded, keyguardState, isDreaming) -> 239 if (isOccluded && !isDreaming && keyguardState == KeyguardState.LOCKSCREEN) { 240 keyguardTransitionRepository.startTransition( 241 TransitionInfo( 242 name, 243 keyguardState, 244 KeyguardState.OCCLUDED, 245 getAnimator(TO_OCCLUDED_DURATION), 246 ) 247 ) 248 } 249 } 250 } 251 } 252 253 /** This signal may come in before the occlusion signal, and can provide a custom transition */ 254 private fun listenForLockscreenToCamera() { 255 scope.launch { 256 keyguardInteractor.onCameraLaunchDetected 257 .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair) 258 .collect { (_, lastStartedStep) -> 259 // DREAMING/AOD/OFF may trigger on the first power button push, so include this 260 // state in order to cancel and correct the transition 261 if ( 262 lastStartedStep.to == KeyguardState.LOCKSCREEN || 263 lastStartedStep.to == KeyguardState.DREAMING || 264 lastStartedStep.to == KeyguardState.DOZING || 265 lastStartedStep.to == KeyguardState.AOD || 266 lastStartedStep.to == KeyguardState.OFF 267 ) { 268 keyguardTransitionRepository.startTransition( 269 TransitionInfo( 270 name, 271 KeyguardState.LOCKSCREEN, 272 KeyguardState.OCCLUDED, 273 getAnimator(TO_OCCLUDED_DURATION), 274 ) 275 ) 276 } 277 } 278 } 279 } 280 281 private fun listenForLockscreenToAodOrDozing() { 282 scope.launch { 283 keyguardInteractor.wakefulnessModel 284 .sample( 285 combine( 286 keyguardTransitionInteractor.startedKeyguardTransitionStep, 287 keyguardInteractor.isAodAvailable, 288 ::Pair 289 ), 290 ::toTriple 291 ) 292 .collect { (wakefulnessState, lastStartedStep, isAodAvailable) -> 293 if ( 294 lastStartedStep.to == KeyguardState.LOCKSCREEN && 295 wakefulnessState.state == WakefulnessState.STARTING_TO_SLEEP 296 ) { 297 keyguardTransitionRepository.startTransition( 298 TransitionInfo( 299 name, 300 KeyguardState.LOCKSCREEN, 301 if (isAodAvailable) { 302 KeyguardState.AOD 303 } else { 304 KeyguardState.DOZING 305 }, 306 getAnimator(), 307 ) 308 ) 309 } 310 } 311 } 312 } 313 314 private fun getAnimator(duration: Duration = DEFAULT_DURATION): ValueAnimator { 315 return ValueAnimator().apply { 316 setInterpolator(Interpolators.LINEAR) 317 setDuration(duration.inWholeMilliseconds) 318 } 319 } 320 321 companion object { 322 private val DEFAULT_DURATION = 500.milliseconds 323 val TO_DREAMING_DURATION = 933.milliseconds 324 val TO_OCCLUDED_DURATION = 450.milliseconds 325 } 326 } 327