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