1 /*
2  * 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 com.android.keyguard.logging.ScrimLogger
20 import com.android.systemui.dagger.SysUISingleton
21 import com.android.systemui.dagger.qualifiers.Application
22 import com.android.systemui.keyguard.data.repository.LightRevealScrimRepository
23 import com.android.systemui.keyguard.shared.model.KeyguardState
24 import com.android.systemui.power.domain.interactor.PowerInteractor
25 import com.android.systemui.power.shared.model.ScreenPowerState
26 import com.android.systemui.statusbar.LightRevealEffect
27 import com.android.systemui.util.kotlin.sample
28 import dagger.Lazy
29 import javax.inject.Inject
30 import kotlinx.coroutines.CoroutineScope
31 import kotlinx.coroutines.flow.Flow
32 import kotlinx.coroutines.flow.filter
33 import kotlinx.coroutines.launch
34 
35 @SysUISingleton
36 class LightRevealScrimInteractor
37 @Inject
38 constructor(
39     private val transitionInteractor: KeyguardTransitionInteractor,
40     private val lightRevealScrimRepository: LightRevealScrimRepository,
41     @Application private val scope: CoroutineScope,
42     private val scrimLogger: ScrimLogger,
43     private val powerInteractor: Lazy<PowerInteractor>,
44 ) {
45     init {
46         listenForStartedKeyguardTransitionStep()
47     }
48 
listenForStartedKeyguardTransitionStepnull49     private fun listenForStartedKeyguardTransitionStep() {
50         scope.launch {
51             transitionInteractor.startedKeyguardTransitionStep.collect {
52                 scrimLogger.d(TAG, "listenForStartedKeyguardTransitionStep", it)
53                 lightRevealScrimRepository.startRevealAmountAnimator(willBeRevealedInState(it.to))
54             }
55         }
56     }
57 
58     /**
59      * Whenever a keyguard transition starts, sample the latest reveal effect from the repository
60      * and use that for the starting transition.
61      *
62      * We can't simply use the nextRevealEffect since the effect may change midway through a
63      * transition, but we don't want to change effects part way through. For example, if we're using
64      * a CircleReveal to animate a biometric unlock, but the biometric unlock mode changes to NONE
65      * from WAKE_AND_UNLOCK before the unlock animation ends, we don't want to end up switching to a
66      * LiftReveal.
67      */
68     val lightRevealEffect: Flow<LightRevealEffect> =
69         transitionInteractor.startedKeyguardTransitionStep.sample(
70             lightRevealScrimRepository.revealEffect
71         )
72 
73     val revealAmount =
<lambda>null74         lightRevealScrimRepository.revealAmount.filter {
75             // When the screen is off we do not want to keep producing frames as this is causing
76             // (invisible) jank. However, we need to still pass through 1f and 0f to ensure that the
77             // correct end states are respected even if the screen turned off (or was still off)
78             // when the animation finished
79             screenIsShowingContent() || it == 1f || it == 0f
80         }
81 
screenIsShowingContentnull82     private fun screenIsShowingContent() =
83         powerInteractor.get().screenPowerState.value != ScreenPowerState.SCREEN_OFF &&
84             powerInteractor.get().screenPowerState.value != ScreenPowerState.SCREEN_TURNING_ON
85 
86     val isAnimating: Boolean
87         get() = lightRevealScrimRepository.isAnimating
88 
89     /**
90      * Whether the light reveal scrim will be fully revealed (revealAmount = 1.0f) in the given
91      * state after the transition is complete. If false, scrim will be fully hidden.
92      */
93     private fun willBeRevealedInState(state: KeyguardState): Boolean {
94         return when (state) {
95             KeyguardState.OFF -> false
96             KeyguardState.DOZING -> false
97             KeyguardState.AOD -> false
98             KeyguardState.DREAMING -> true
99             KeyguardState.DREAMING_LOCKSCREEN_HOSTED -> true
100             KeyguardState.GLANCEABLE_HUB -> true
101             KeyguardState.ALTERNATE_BOUNCER -> true
102             KeyguardState.PRIMARY_BOUNCER -> true
103             KeyguardState.LOCKSCREEN -> true
104             KeyguardState.GONE -> true
105             KeyguardState.OCCLUDED -> true
106             KeyguardState.UNDEFINED -> true
107         }
108     }
109 
110     companion object {
111         val TAG = LightRevealScrimInteractor::class.simpleName!!
112     }
113 }
114