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