• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 package com.android.systemui.keyguard.domain.interactor
17 
18 import com.android.app.tracing.coroutines.launchTraced as launch
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.dagger.qualifiers.Background
23 import com.android.systemui.keyguard.data.repository.DEFAULT_REVEAL_DURATION
24 import com.android.systemui.keyguard.data.repository.LightRevealScrimRepository
25 import com.android.systemui.keyguard.shared.model.Edge
26 import com.android.systemui.keyguard.shared.model.KeyguardState
27 import com.android.systemui.power.domain.interactor.PowerInteractor
28 import com.android.systemui.power.shared.model.ScreenPowerState
29 import com.android.systemui.power.shared.model.WakeSleepReason
30 import com.android.systemui.scene.shared.model.Scenes
31 import com.android.systemui.statusbar.LightRevealEffect
32 import com.android.systemui.util.kotlin.BooleanFlowOperators.anyOf
33 import com.android.systemui.util.kotlin.sample
34 import dagger.Lazy
35 import javax.inject.Inject
36 import kotlinx.coroutines.CoroutineDispatcher
37 import kotlinx.coroutines.CoroutineScope
38 import kotlinx.coroutines.flow.Flow
39 import kotlinx.coroutines.flow.filter
40 import kotlinx.coroutines.flow.flatMapLatest
41 import kotlinx.coroutines.flow.flowOf
42 import kotlinx.coroutines.flow.flowOn
43 
44 @SysUISingleton
45 class LightRevealScrimInteractor
46 @Inject
47 constructor(
48     private val transitionInteractor: KeyguardTransitionInteractor,
49     private val repository: LightRevealScrimRepository,
50     @Application private val scope: CoroutineScope,
51     private val scrimLogger: ScrimLogger,
52     private val powerInteractor: Lazy<PowerInteractor>,
53     @Background backgroundDispatcher: CoroutineDispatcher,
54 ) {
55     init {
56         listenForStartedKeyguardTransitionStep()
57     }
58 
59     private fun listenForStartedKeyguardTransitionStep() {
60         scope.launch {
61             transitionInteractor.startedKeyguardTransitionStep.collect {
62                 scrimLogger.d(TAG, "listenForStartedKeyguardTransitionStep", it)
63                 val animationDuration =
64                     if (it.to == KeyguardState.AOD && isLastSleepDueToFold) {
65                         // Do not animate the scrim when folding as we want to cover the screen
66                         // with the scrim immediately while displays are switching.
67                         // This is needed to play the fold to AOD animation which starts with
68                         // fully black screen (see FoldAodAnimationController)
69                         0L
70                     } else {
71                         DEFAULT_REVEAL_DURATION
72                     }
73 
74                 repository.startRevealAmountAnimator(
75                     willBeRevealedInState(it.to),
76                     duration = animationDuration,
77                 )
78             }
79         }
80     }
81 
82     private val isLastSleepDueToFold: Boolean
83         get() =
84             powerInteractor.get().detailedWakefulness.value.lastSleepReason == WakeSleepReason.FOLD
85 
86     /**
87      * Whenever a keyguard transition starts, sample the latest reveal effect from the repository
88      * and use that for the starting transition.
89      *
90      * We can't simply use the nextRevealEffect since the effect may change midway through a
91      * transition, but we don't want to change effects part way through. For example, if we're using
92      * a CircleReveal to animate a biometric unlock, but the biometric unlock mode changes to NONE
93      * from WAKE_AND_UNLOCK before the unlock animation ends, we don't want to end up switching to a
94      * LiftReveal.
95      */
96     val lightRevealEffect: Flow<LightRevealEffect> =
97         transitionInteractor.startedKeyguardTransitionStep.sample(repository.revealEffect)
98 
99     /** Limit the max alpha for the scrim to allow for some transparency */
100     val maxAlpha: Flow<Float> =
101         anyOf(
102                 transitionInteractor.isInTransition(
103                     edge = Edge.create(Scenes.Gone, KeyguardState.AOD),
104                     edgeWithoutSceneContainer = Edge.create(KeyguardState.GONE, KeyguardState.AOD),
105                 ),
106                 transitionInteractor.isInTransition(
107                     Edge.create(KeyguardState.OCCLUDED, KeyguardState.AOD)
108                 ),
109             )
110             .flatMapLatest { isInTransition ->
111                 // During transitions like GONE->AOD, surfaces like the launcher may be visible
112                 // until WM is told to hide them, which occurs at the end of the animation. Use an
113                 // opaque scrim until this transition is complete.
114                 if (isInTransition) {
115                     flowOf(1f)
116                 } else {
117                     repository.maxAlpha
118                 }
119             }
120             .flowOn(backgroundDispatcher)
121 
122     val revealAmount =
123         repository.revealAmount.filter {
124             // When the screen is off we do not want to keep producing frames as this is causing
125             // (invisible) jank. However, we need to still pass through 1f and 0f to ensure that the
126             // correct end states are respected even if the screen turned off (or was still off)
127             // when the animation finished
128             screenIsShowingContent() || it == 1f || it == 0f
129         }
130 
131     private fun screenIsShowingContent() =
132         powerInteractor.get().screenPowerState.value != ScreenPowerState.SCREEN_OFF &&
133             powerInteractor.get().screenPowerState.value != ScreenPowerState.SCREEN_TURNING_ON
134 
135     val isAnimating: Boolean
136         get() = repository.isAnimating
137 
138     /** If the wallpaper supports ambient mode, allow partial transparency */
139     fun setWallpaperSupportsAmbientMode(supportsAmbientMode: Boolean) {
140         repository.maxAlpha.value =
141             if (supportsAmbientMode) {
142                 0.54f
143             } else {
144                 1f
145             }
146     }
147 
148     /**
149      * Whether the light reveal scrim will be fully revealed (revealAmount = 1.0f) in the given
150      * state after the transition is complete. If false, scrim will be fully hidden.
151      */
152     private fun willBeRevealedInState(state: KeyguardState): Boolean {
153         return when (state) {
154             KeyguardState.OFF -> false
155             KeyguardState.DOZING -> false
156             KeyguardState.AOD -> false
157             KeyguardState.DREAMING -> true
158             KeyguardState.GLANCEABLE_HUB -> true
159             KeyguardState.ALTERNATE_BOUNCER -> true
160             KeyguardState.PRIMARY_BOUNCER -> true
161             KeyguardState.LOCKSCREEN -> true
162             KeyguardState.GONE -> true
163             KeyguardState.OCCLUDED -> true
164             KeyguardState.UNDEFINED -> true
165         }
166     }
167 
168     companion object {
169         val TAG = LightRevealScrimInteractor::class.simpleName!!
170     }
171 }
172