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 @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.systemui.R 26 import com.android.systemui.dagger.SysUISingleton 27 import com.android.systemui.keyguard.shared.model.BiometricUnlockModel 28 import com.android.systemui.keyguard.shared.model.BiometricUnlockSource 29 import com.android.systemui.keyguard.shared.model.WakeSleepReason.TAP 30 import com.android.systemui.statusbar.CircleReveal 31 import com.android.systemui.statusbar.LiftReveal 32 import com.android.systemui.statusbar.LightRevealEffect 33 import com.android.systemui.statusbar.PowerButtonReveal 34 import javax.inject.Inject 35 import kotlin.math.max 36 import kotlinx.coroutines.ExperimentalCoroutinesApi 37 import kotlinx.coroutines.channels.awaitClose 38 import kotlinx.coroutines.flow.Flow 39 import kotlinx.coroutines.flow.callbackFlow 40 import kotlinx.coroutines.flow.combine 41 import kotlinx.coroutines.flow.distinctUntilChanged 42 import kotlinx.coroutines.flow.filter 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 49 /** 50 * Encapsulates state relevant to the light reveal scrim, the view used to reveal/hide screen 51 * contents during transitions between DOZE or AOD and lockscreen/unlocked. 52 */ 53 interface LightRevealScrimRepository { 54 55 /** 56 * The reveal effect that should be used for the next lock/unlock. We switch between either the 57 * biometric unlock effect (if wake and unlocking) or the non-biometric effect, and position it 58 * at the current screen position of the appropriate sensor. 59 */ 60 val revealEffect: Flow<LightRevealEffect> 61 62 val revealAmount: Flow<Float> 63 64 fun startRevealAmountAnimator(reveal: Boolean) 65 } 66 67 @SysUISingleton 68 class LightRevealScrimRepositoryImpl 69 @Inject 70 constructor( 71 keyguardRepository: KeyguardRepository, 72 val context: Context, 73 ) : LightRevealScrimRepository { 74 75 /** The reveal effect used if the device was locked/unlocked via the power button. */ 76 private val powerButtonRevealEffect: Flow<LightRevealEffect?> = 77 flowOf( 78 PowerButtonReveal( 79 context.resources 80 .getDimensionPixelSize(R.dimen.physical_power_button_center_screen_location_y) 81 .toFloat() 82 ) 83 ) 84 85 private val tapRevealEffect: Flow<LightRevealEffect?> = 86 keyguardRepository.lastDozeTapToWakePosition.map { 87 it?.let { constructCircleRevealFromPoint(it) } 88 } 89 90 /** 91 * Reveal effect to use for a fingerprint unlock. This is reconstructed if the fingerprint 92 * sensor location on the screen (in pixels) changes due to configuration changes. 93 */ 94 private val fingerprintRevealEffect: Flow<LightRevealEffect?> = 95 keyguardRepository.fingerprintSensorLocation.map { 96 it?.let { constructCircleRevealFromPoint(it) } 97 } 98 99 /** 100 * Reveal effect to use for a face unlock. This is reconstructed if the face sensor/front camera 101 * location on the screen (in pixels) changes due to configuration changes. 102 */ 103 private val faceRevealEffect: Flow<LightRevealEffect?> = 104 keyguardRepository.faceSensorLocation.map { it?.let { constructCircleRevealFromPoint(it) } } 105 106 /** 107 * The reveal effect we'll use for the next biometric unlock animation. We switch between the 108 * fingerprint/face unlock effect flows depending on the biometric unlock source. 109 */ 110 private val biometricRevealEffect: Flow<LightRevealEffect?> = 111 keyguardRepository.biometricUnlockSource.flatMapLatest { source -> 112 when (source) { 113 BiometricUnlockSource.FINGERPRINT_SENSOR -> fingerprintRevealEffect 114 BiometricUnlockSource.FACE_SENSOR -> faceRevealEffect 115 else -> flowOf(null) 116 } 117 } 118 119 /** The reveal effect we'll use for the next non-biometric unlock (tap, power button, etc). */ 120 private val nonBiometricRevealEffect: Flow<LightRevealEffect?> = 121 keyguardRepository.wakefulness 122 .filter { it.isStartingToWake() || it.isStartingToSleep() } 123 .flatMapLatest { wakefulnessModel -> 124 when { 125 wakefulnessModel.isTransitioningFromPowerButton() -> powerButtonRevealEffect 126 wakefulnessModel.isWakingFrom(TAP) -> tapRevealEffect 127 else -> flowOf(LiftReveal) 128 } 129 } 130 131 private val revealAmountAnimator = ValueAnimator.ofFloat(0f, 1f).apply { duration = 500 } 132 133 override val revealAmount: Flow<Float> = callbackFlow { 134 val updateListener = 135 Animator.AnimatorUpdateListener { 136 trySend((it as ValueAnimator).animatedValue as Float) 137 } 138 revealAmountAnimator.addUpdateListener(updateListener) 139 awaitClose { revealAmountAnimator.removeUpdateListener(updateListener) } 140 } 141 142 override fun startRevealAmountAnimator(reveal: Boolean) { 143 if (reveal) revealAmountAnimator.start() else revealAmountAnimator.reverse() 144 } 145 146 override val revealEffect = 147 combine( 148 keyguardRepository.biometricUnlockState, 149 biometricRevealEffect, 150 nonBiometricRevealEffect 151 ) { biometricUnlockState, biometricReveal, nonBiometricReveal -> 152 153 // Use the biometric reveal for any flavor of wake and unlocking. 154 when (biometricUnlockState) { 155 BiometricUnlockModel.WAKE_AND_UNLOCK, 156 BiometricUnlockModel.WAKE_AND_UNLOCK_PULSING, 157 BiometricUnlockModel.WAKE_AND_UNLOCK_FROM_DREAM -> biometricReveal 158 else -> nonBiometricReveal 159 } 160 ?: DEFAULT_REVEAL_EFFECT 161 } 162 .distinctUntilChanged() 163 164 private fun constructCircleRevealFromPoint(point: Point): LightRevealEffect { 165 return with(point) { 166 val display = checkNotNull(context.display) 167 CircleReveal( 168 x, 169 y, 170 startRadius = 0, 171 endRadius = 172 max(max(x, display.width - x), max(y, display.height - y)), 173 ) 174 } 175 } 176 } 177