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 17 package com.android.systemui.keyguard.data.repository 18 19 import android.content.Context 20 import android.graphics.Point 21 import androidx.core.animation.Animator 22 import androidx.core.animation.ValueAnimator 23 import com.android.keyguard.logging.ScrimLogger 24 import com.android.systemui.dagger.SysUISingleton 25 import com.android.systemui.keyguard.shared.model.BiometricUnlockMode 26 import com.android.systemui.keyguard.shared.model.BiometricUnlockSource 27 import com.android.systemui.power.data.repository.PowerRepository 28 import com.android.systemui.power.shared.model.WakeSleepReason 29 import com.android.systemui.power.shared.model.WakeSleepReason.TAP 30 import com.android.systemui.res.R 31 import com.android.systemui.shade.ShadeDisplayAware 32 import com.android.systemui.statusbar.CircleReveal 33 import com.android.systemui.statusbar.LiftReveal 34 import com.android.systemui.statusbar.LightRevealEffect 35 import com.android.systemui.statusbar.PowerButtonReveal 36 import javax.inject.Inject 37 import kotlin.math.max 38 import kotlinx.coroutines.channels.awaitClose 39 import kotlinx.coroutines.flow.Flow 40 import kotlinx.coroutines.flow.MutableStateFlow 41 import kotlinx.coroutines.flow.callbackFlow 42 import kotlinx.coroutines.flow.distinctUntilChanged 43 import kotlinx.coroutines.flow.flatMapLatest 44 import kotlinx.coroutines.flow.flowOf 45 import kotlinx.coroutines.flow.map 46 47 val DEFAULT_REVEAL_EFFECT = LiftReveal 48 const val DEFAULT_REVEAL_DURATION = 500L 49 50 /** 51 * Encapsulates state relevant to the light reveal scrim, the view used to reveal/hide screen 52 * contents during transitions between DOZE or AOD and lockscreen/unlocked. 53 */ 54 interface LightRevealScrimRepository { 55 56 /** 57 * The reveal effect that should be used for the next lock/unlock. We switch between either the 58 * biometric unlock effect (if wake and unlocking) or the non-biometric effect, and position it 59 * at the current screen position of the appropriate sensor. 60 */ 61 val revealEffect: Flow<LightRevealEffect> 62 63 val revealAmount: Flow<Float> 64 65 val isAnimating: Boolean 66 67 /** Limit the max alpha for the scrim to allow for some transparency */ 68 val maxAlpha: MutableStateFlow<Float> 69 70 fun startRevealAmountAnimator(reveal: Boolean, duration: Long = DEFAULT_REVEAL_DURATION) 71 } 72 73 @SysUISingleton 74 class LightRevealScrimRepositoryImpl 75 @Inject 76 constructor( 77 keyguardRepository: KeyguardRepository, 78 @ShadeDisplayAware val context: Context, 79 powerRepository: PowerRepository, 80 private val scrimLogger: ScrimLogger, 81 ) : LightRevealScrimRepository { 82 companion object { 83 val TAG = LightRevealScrimRepository::class.simpleName!! 84 val DEFAULT_MAX_ALPHA = 1f 85 } 86 87 /** The reveal effect used if the device was locked/unlocked via the power button. */ 88 private val powerButtonRevealEffect: Flow<LightRevealEffect> = 89 flowOf( 90 PowerButtonReveal( 91 context.resources 92 .getDimensionPixelSize(R.dimen.physical_power_button_center_screen_location_y) 93 .toFloat() 94 ) 95 ) 96 97 private val tapRevealEffect: Flow<LightRevealEffect> = <lambda>null98 keyguardRepository.lastDozeTapToWakePosition.map { 99 it?.let { constructCircleRevealFromPoint(it) } ?: DEFAULT_REVEAL_EFFECT 100 } 101 102 /** 103 * Reveal effect to use for a fingerprint unlock. This is reconstructed if the fingerprint 104 * sensor location on the screen (in pixels) changes due to configuration changes. 105 */ 106 private val fingerprintRevealEffect: Flow<LightRevealEffect> = <lambda>null107 keyguardRepository.fingerprintSensorLocation.map { 108 it?.let { constructCircleRevealFromPoint(it) } ?: DEFAULT_REVEAL_EFFECT 109 } 110 111 /** 112 * Reveal effect to use for a face unlock. This is reconstructed if the face sensor/front camera 113 * location on the screen (in pixels) changes due to configuration changes. 114 */ 115 private val faceRevealEffect: Flow<LightRevealEffect> = <lambda>null116 keyguardRepository.faceSensorLocation.map { 117 it?.let { constructCircleRevealFromPoint(it) } ?: DEFAULT_REVEAL_EFFECT 118 } 119 120 /** The reveal effect we'll use for the next non-biometric unlock (tap, power button, etc). */ 121 private val nonBiometricRevealEffect: Flow<LightRevealEffect> = wakefulnessModelnull122 powerRepository.wakefulness.flatMapLatest { wakefulnessModel -> 123 when { 124 wakefulnessModel.isAwakeOrAsleepFrom(WakeSleepReason.POWER_BUTTON) -> 125 powerButtonRevealEffect 126 wakefulnessModel.isAwakeFrom(TAP) -> tapRevealEffect 127 else -> flowOf(LiftReveal) 128 } 129 } 130 131 private val revealAmountAnimator = ValueAnimator.ofFloat(0f, 1f) 132 133 override val maxAlpha: MutableStateFlow<Float> = MutableStateFlow(DEFAULT_MAX_ALPHA) 134 <lambda>null135 override val revealAmount: Flow<Float> = callbackFlow { 136 val updateListener = 137 Animator.AnimatorUpdateListener { 138 val value = (it as ValueAnimator).animatedValue as Float 139 trySend(value) 140 141 if (value <= 0.0f || value >= 1.0f) { 142 scrimLogger.d(TAG, "revealAmount", value) 143 } 144 } 145 revealAmountAnimator.addUpdateListener(updateListener) 146 awaitClose { revealAmountAnimator.removeUpdateListener(updateListener) } 147 } 148 override val isAnimating: Boolean 149 get() = revealAmountAnimator.isRunning 150 151 private var willBeOrIsRevealed: Boolean? = null 152 startRevealAmountAnimatornull153 override fun startRevealAmountAnimator(reveal: Boolean, duration: Long) { 154 if (reveal == willBeOrIsRevealed) return 155 willBeOrIsRevealed = reveal 156 revealAmountAnimator.duration = duration 157 if (reveal && !revealAmountAnimator.isRunning) { 158 revealAmountAnimator.start() 159 } else { 160 revealAmountAnimator.reverse() 161 } 162 scrimLogger.d(TAG, "startRevealAmountAnimator, reveal", reveal) 163 } 164 165 override val revealEffect: Flow<LightRevealEffect> = 166 keyguardRepository.biometricUnlockState biometricUnlockStatenull167 .flatMapLatest { biometricUnlockState -> 168 // Use the biometric reveal for any flavor of wake and unlocking. 169 when (biometricUnlockState.mode) { 170 BiometricUnlockMode.WAKE_AND_UNLOCK, 171 BiometricUnlockMode.WAKE_AND_UNLOCK_PULSING, 172 BiometricUnlockMode.WAKE_AND_UNLOCK_FROM_DREAM -> { 173 if (biometricUnlockState.source == BiometricUnlockSource.FACE_SENSOR) { 174 faceRevealEffect 175 } else { 176 fingerprintRevealEffect 177 } 178 } 179 else -> nonBiometricRevealEffect 180 } 181 } 182 .distinctUntilChanged() 183 constructCircleRevealFromPointnull184 private fun constructCircleRevealFromPoint(point: Point): LightRevealEffect { 185 return with(point) { 186 val display = checkNotNull(context.display) 187 CircleReveal( 188 x, 189 y, 190 startRadius = 0, 191 endRadius = max(max(x, display.width - x), max(y, display.height - y)), 192 ) 193 } 194 } 195 } 196