blob: 911861ddde47b93f85fac2f5d6d7fd5715e5e6a2 [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.KeyguardState
import com.android.systemui.keyguard.shared.model.StatusBarState.KEYGUARD
import com.android.systemui.keyguard.shared.model.TransitionInfo
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.WakefulnessState
import com.android.systemui.shade.data.repository.ShadeRepository
import com.android.systemui.util.kotlin.sample
import java.util.UUID
import javax.inject.Inject
import kotlin.time.Duration
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.launch
@SysUISingleton
class FromLockscreenTransitionInteractor
@Inject
constructor(
@Application private val scope: CoroutineScope,
private val keyguardInteractor: KeyguardInteractor,
private val shadeRepository: ShadeRepository,
private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
private val keyguardTransitionRepository: KeyguardTransitionRepository,
) : TransitionInteractor(FromLockscreenTransitionInteractor::class.simpleName!!) {
override fun start() {
listenForLockscreenToGone()
listenForLockscreenToOccluded()
listenForLockscreenToCamera()
listenForLockscreenToAodOrDozing()
listenForLockscreenToPrimaryBouncer()
listenForLockscreenToDreaming()
listenForLockscreenToPrimaryBouncerDragging()
listenForLockscreenToAlternateBouncer()
}
private fun listenForLockscreenToDreaming() {
scope.launch {
keyguardInteractor.isAbleToDream
.sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
.collect { pair ->
val (isAbleToDream, lastStartedTransition) = pair
if (isAbleToDream && lastStartedTransition.to == KeyguardState.LOCKSCREEN) {
keyguardTransitionRepository.startTransition(
TransitionInfo(
name,
KeyguardState.LOCKSCREEN,
KeyguardState.DREAMING,
getAnimator(TO_DREAMING_DURATION),
)
)
}
}
}
}
private fun listenForLockscreenToPrimaryBouncer() {
scope.launch {
keyguardInteractor.primaryBouncerShowing
.sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
.collect { pair ->
val (isBouncerShowing, lastStartedTransitionStep) = pair
if (
isBouncerShowing && lastStartedTransitionStep.to == KeyguardState.LOCKSCREEN
) {
keyguardTransitionRepository.startTransition(
TransitionInfo(
ownerName = name,
from = KeyguardState.LOCKSCREEN,
to = KeyguardState.PRIMARY_BOUNCER,
animator = getAnimator(),
)
)
}
}
}
}
private fun listenForLockscreenToAlternateBouncer() {
scope.launch {
keyguardInteractor.alternateBouncerShowing
.sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
.collect { pair ->
val (isAlternateBouncerShowing, lastStartedTransitionStep) = pair
if (
isAlternateBouncerShowing &&
lastStartedTransitionStep.to == KeyguardState.LOCKSCREEN
) {
keyguardTransitionRepository.startTransition(
TransitionInfo(
ownerName = name,
from = KeyguardState.LOCKSCREEN,
to = KeyguardState.ALTERNATE_BOUNCER,
animator = getAnimator(),
)
)
}
}
}
}
/* Starts transitions when manually dragging up the bouncer from the lockscreen. */
private fun listenForLockscreenToPrimaryBouncerDragging() {
var transitionId: UUID? = null
scope.launch {
shadeRepository.shadeModel
.sample(
combine(
keyguardTransitionInteractor.startedKeyguardTransitionStep,
keyguardInteractor.statusBarState,
keyguardInteractor.isKeyguardUnlocked,
::toTriple
),
::toQuad
)
.collect { (shadeModel, keyguardState, statusBarState, isKeyguardUnlocked) ->
val id = transitionId
if (id != null) {
if (keyguardState.to == KeyguardState.PRIMARY_BOUNCER) {
// An existing `id` means a transition is started, and calls to
// `updateTransition` will control it until FINISHED or CANCELED
var nextState =
if (shadeModel.expansionAmount == 0f) {
TransitionState.FINISHED
} else if (shadeModel.expansionAmount == 1f) {
TransitionState.CANCELED
} else {
TransitionState.RUNNING
}
keyguardTransitionRepository.updateTransition(
id,
1f - shadeModel.expansionAmount,
nextState,
)
if (
nextState == TransitionState.CANCELED ||
nextState == TransitionState.FINISHED
) {
transitionId = null
}
// If canceled, just put the state back
if (nextState == TransitionState.CANCELED) {
keyguardTransitionRepository.startTransition(
TransitionInfo(
ownerName = name,
from = KeyguardState.PRIMARY_BOUNCER,
to = KeyguardState.LOCKSCREEN,
animator = getAnimator(0.milliseconds)
)
)
}
}
} else {
// TODO (b/251849525): Remove statusbarstate check when that state is
// integrated into KeyguardTransitionRepository
if (
keyguardState.to == KeyguardState.LOCKSCREEN &&
shadeModel.isUserDragging &&
!isKeyguardUnlocked &&
statusBarState == KEYGUARD
) {
transitionId =
keyguardTransitionRepository.startTransition(
TransitionInfo(
ownerName = name,
from = KeyguardState.LOCKSCREEN,
to = KeyguardState.PRIMARY_BOUNCER,
animator = null,
)
)
}
}
}
}
}
private fun listenForLockscreenToGone() {
scope.launch {
keyguardInteractor.isKeyguardGoingAway
.sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
.collect { pair ->
val (isKeyguardGoingAway, lastStartedStep) = pair
if (isKeyguardGoingAway && lastStartedStep.to == KeyguardState.LOCKSCREEN) {
keyguardTransitionRepository.startTransition(
TransitionInfo(
name,
KeyguardState.LOCKSCREEN,
KeyguardState.GONE,
getAnimator(),
)
)
}
}
}
}
private fun listenForLockscreenToOccluded() {
scope.launch {
keyguardInteractor.isKeyguardOccluded
.sample(
combine(
keyguardTransitionInteractor.finishedKeyguardState,
keyguardInteractor.isDreaming,
::Pair
),
::toTriple
)
.collect { (isOccluded, keyguardState, isDreaming) ->
if (isOccluded && !isDreaming && keyguardState == KeyguardState.LOCKSCREEN) {
keyguardTransitionRepository.startTransition(
TransitionInfo(
name,
keyguardState,
KeyguardState.OCCLUDED,
getAnimator(TO_OCCLUDED_DURATION),
)
)
}
}
}
}
/** This signal may come in before the occlusion signal, and can provide a custom transition */
private fun listenForLockscreenToCamera() {
scope.launch {
keyguardInteractor.onCameraLaunchDetected
.sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
.collect { (_, lastStartedStep) ->
// DREAMING/AOD/OFF may trigger on the first power button push, so include this
// state in order to cancel and correct the transition
if (
lastStartedStep.to == KeyguardState.LOCKSCREEN ||
lastStartedStep.to == KeyguardState.DREAMING ||
lastStartedStep.to == KeyguardState.DOZING ||
lastStartedStep.to == KeyguardState.AOD ||
lastStartedStep.to == KeyguardState.OFF
) {
keyguardTransitionRepository.startTransition(
TransitionInfo(
name,
KeyguardState.LOCKSCREEN,
KeyguardState.OCCLUDED,
getAnimator(TO_OCCLUDED_DURATION),
)
)
}
}
}
}
private fun listenForLockscreenToAodOrDozing() {
scope.launch {
keyguardInteractor.wakefulnessModel
.sample(
combine(
keyguardTransitionInteractor.startedKeyguardTransitionStep,
keyguardInteractor.isAodAvailable,
::Pair
),
::toTriple
)
.collect { (wakefulnessState, lastStartedStep, isAodAvailable) ->
if (
lastStartedStep.to == KeyguardState.LOCKSCREEN &&
wakefulnessState.state == WakefulnessState.STARTING_TO_SLEEP
) {
keyguardTransitionRepository.startTransition(
TransitionInfo(
name,
KeyguardState.LOCKSCREEN,
if (isAodAvailable) {
KeyguardState.AOD
} else {
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_DREAMING_DURATION = 933.milliseconds
val TO_OCCLUDED_DURATION = 450.milliseconds
}
}