blob: 3beac0b1322e3658313f02f662706ca516181283 [file] [log] [blame]
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License
*/
package com.android.systemui.keyguard.domain.interactor
import android.animation.ValueAnimator
import com.android.systemui.animation.Interpolators
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
import com.android.systemui.keyguard.shared.model.DozeStateModel
import com.android.systemui.keyguard.shared.model.DozeStateModel.Companion.isDozeOff
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionInfo
import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
import kotlin.time.Duration
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
@SysUISingleton
class FromDreamingTransitionInteractor
@Inject
constructor(
@Application private val scope: CoroutineScope,
private val keyguardInteractor: KeyguardInteractor,
private val keyguardTransitionRepository: KeyguardTransitionRepository,
private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
) : TransitionInteractor(FromDreamingTransitionInteractor::class.simpleName!!) {
override fun start() {
listenForDreamingToLockscreen()
listenForDreamingToOccluded()
listenForDreamingToGone()
listenForDreamingToDozing()
}
private fun listenForDreamingToLockscreen() {
scope.launch {
keyguardInteractor.isAbleToDream
.sample(
combine(
keyguardInteractor.dozeTransitionModel,
keyguardTransitionInteractor.startedKeyguardTransitionStep,
::Pair
),
::toTriple
)
.collect { (isDreaming, dozeTransitionModel, lastStartedTransition) ->
if (
!isDreaming &&
isDozeOff(dozeTransitionModel.to) &&
lastStartedTransition.to == KeyguardState.DREAMING
) {
keyguardTransitionRepository.startTransition(
TransitionInfo(
name,
KeyguardState.DREAMING,
KeyguardState.LOCKSCREEN,
getAnimator(TO_LOCKSCREEN_DURATION),
)
)
}
}
}
}
private fun listenForDreamingToOccluded() {
scope.launch {
keyguardInteractor.isDreaming
// Add a slight delay, as dreaming and occluded events will arrive with a small gap
// in time. This prevents a transition to OCCLUSION happening prematurely.
.onEach { delay(50) }
.sample(
combine(
keyguardInteractor.isKeyguardOccluded,
keyguardTransitionInteractor.startedKeyguardTransitionStep,
::Pair,
),
::toTriple
)
.collect { (isDreaming, isOccluded, lastStartedTransition) ->
if (
isOccluded &&
!isDreaming &&
(lastStartedTransition.to == KeyguardState.DREAMING ||
lastStartedTransition.to == KeyguardState.LOCKSCREEN)
) {
// At the moment, checking for LOCKSCREEN state above provides a corrective
// action. There's no great signal to determine when the dream is ending
// and a transition to OCCLUDED is beginning directly. For now, the solution
// is DREAMING->LOCKSCREEN->OCCLUDED
keyguardTransitionRepository.startTransition(
TransitionInfo(
name,
lastStartedTransition.to,
KeyguardState.OCCLUDED,
getAnimator(),
)
)
}
}
}
}
private fun listenForDreamingToGone() {
scope.launch {
keyguardInteractor.biometricUnlockState.collect { biometricUnlockState ->
if (biometricUnlockState == BiometricUnlockModel.WAKE_AND_UNLOCK_FROM_DREAM) {
keyguardTransitionRepository.startTransition(
TransitionInfo(
name,
KeyguardState.DREAMING,
KeyguardState.GONE,
getAnimator(),
)
)
}
}
}
}
private fun listenForDreamingToDozing() {
scope.launch {
combine(
keyguardInteractor.dozeTransitionModel,
keyguardTransitionInteractor.finishedKeyguardState,
::Pair
)
.collect { (dozeTransitionModel, keyguardState) ->
if (
dozeTransitionModel.to == DozeStateModel.DOZE &&
keyguardState == KeyguardState.DREAMING
) {
keyguardTransitionRepository.startTransition(
TransitionInfo(
name,
KeyguardState.DREAMING,
KeyguardState.DOZING,
getAnimator(),
)
)
}
}
}
}
private fun getAnimator(duration: Duration = DEFAULT_DURATION): ValueAnimator {
return ValueAnimator().apply {
setInterpolator(Interpolators.LINEAR)
setDuration(duration.inWholeMilliseconds)
}
}
companion object {
private val DEFAULT_DURATION = 500.milliseconds
val TO_LOCKSCREEN_DURATION = 1183.milliseconds
}
}