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