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