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.BiometricUnlockModel 25 import com.android.systemui.keyguard.shared.model.DozeStateModel 26 import com.android.systemui.keyguard.shared.model.DozeStateModel.Companion.isDozeOff 27 import com.android.systemui.keyguard.shared.model.KeyguardState 28 import com.android.systemui.keyguard.shared.model.TransitionInfo 29 import com.android.systemui.util.kotlin.sample 30 import javax.inject.Inject 31 import kotlin.time.Duration 32 import kotlin.time.Duration.Companion.milliseconds 33 import kotlinx.coroutines.CoroutineScope 34 import kotlinx.coroutines.delay 35 import kotlinx.coroutines.flow.collect 36 import kotlinx.coroutines.flow.combine 37 import kotlinx.coroutines.flow.onEach 38 import kotlinx.coroutines.launch 39 40 @SysUISingleton 41 class FromDreamingTransitionInteractor 42 @Inject 43 constructor( 44 @Application private val scope: CoroutineScope, 45 private val keyguardInteractor: KeyguardInteractor, 46 private val keyguardTransitionRepository: KeyguardTransitionRepository, 47 private val keyguardTransitionInteractor: KeyguardTransitionInteractor, 48 ) : TransitionInteractor(FromDreamingTransitionInteractor::class.simpleName!!) { 49 50 override fun start() { 51 listenForDreamingToLockscreen() 52 listenForDreamingToOccluded() 53 listenForDreamingToGone() 54 listenForDreamingToDozing() 55 } 56 57 private fun listenForDreamingToLockscreen() { 58 scope.launch { 59 keyguardInteractor.isAbleToDream 60 .sample( 61 combine( 62 keyguardInteractor.dozeTransitionModel, 63 keyguardTransitionInteractor.startedKeyguardTransitionStep, 64 ::Pair 65 ), 66 ::toTriple 67 ) 68 .collect { (isDreaming, dozeTransitionModel, lastStartedTransition) -> 69 if ( 70 !isDreaming && 71 isDozeOff(dozeTransitionModel.to) && 72 lastStartedTransition.to == KeyguardState.DREAMING 73 ) { 74 keyguardTransitionRepository.startTransition( 75 TransitionInfo( 76 name, 77 KeyguardState.DREAMING, 78 KeyguardState.LOCKSCREEN, 79 getAnimator(TO_LOCKSCREEN_DURATION), 80 ) 81 ) 82 } 83 } 84 } 85 } 86 87 private fun listenForDreamingToOccluded() { 88 scope.launch { 89 keyguardInteractor.isDreaming 90 // Add a slight delay, as dreaming and occluded events will arrive with a small gap 91 // in time. This prevents a transition to OCCLUSION happening prematurely. 92 .onEach { delay(50) } 93 .sample( 94 combine( 95 keyguardInteractor.isKeyguardOccluded, 96 keyguardTransitionInteractor.startedKeyguardTransitionStep, 97 ::Pair, 98 ), 99 ::toTriple 100 ) 101 .collect { (isDreaming, isOccluded, lastStartedTransition) -> 102 if ( 103 isOccluded && 104 !isDreaming && 105 (lastStartedTransition.to == KeyguardState.DREAMING || 106 lastStartedTransition.to == KeyguardState.LOCKSCREEN) 107 ) { 108 // At the moment, checking for LOCKSCREEN state above provides a corrective 109 // action. There's no great signal to determine when the dream is ending 110 // and a transition to OCCLUDED is beginning directly. For now, the solution 111 // is DREAMING->LOCKSCREEN->OCCLUDED 112 keyguardTransitionRepository.startTransition( 113 TransitionInfo( 114 name, 115 lastStartedTransition.to, 116 KeyguardState.OCCLUDED, 117 getAnimator(), 118 ) 119 ) 120 } 121 } 122 } 123 } 124 125 private fun listenForDreamingToGone() { 126 scope.launch { 127 keyguardInteractor.biometricUnlockState.collect { biometricUnlockState -> 128 if (biometricUnlockState == BiometricUnlockModel.WAKE_AND_UNLOCK_FROM_DREAM) { 129 keyguardTransitionRepository.startTransition( 130 TransitionInfo( 131 name, 132 KeyguardState.DREAMING, 133 KeyguardState.GONE, 134 getAnimator(), 135 ) 136 ) 137 } 138 } 139 } 140 } 141 142 private fun listenForDreamingToDozing() { 143 scope.launch { 144 combine( 145 keyguardInteractor.dozeTransitionModel, 146 keyguardTransitionInteractor.finishedKeyguardState, 147 ::Pair 148 ) 149 .collect { (dozeTransitionModel, keyguardState) -> 150 if ( 151 dozeTransitionModel.to == DozeStateModel.DOZE && 152 keyguardState == KeyguardState.DREAMING 153 ) { 154 keyguardTransitionRepository.startTransition( 155 TransitionInfo( 156 name, 157 KeyguardState.DREAMING, 158 KeyguardState.DOZING, 159 getAnimator(), 160 ) 161 ) 162 } 163 } 164 } 165 } 166 167 private fun getAnimator(duration: Duration = DEFAULT_DURATION): ValueAnimator { 168 return ValueAnimator().apply { 169 setInterpolator(Interpolators.LINEAR) 170 setDuration(duration.inWholeMilliseconds) 171 } 172 } 173 174 companion object { 175 private val DEFAULT_DURATION = 500.milliseconds 176 val TO_LOCKSCREEN_DURATION = 1183.milliseconds 177 } 178 } 179