• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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