1 /*
2  * Copyright (C) 2023 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.ui.viewmodel
18 
19 import android.content.Context
20 import androidx.annotation.ColorInt
21 import com.android.settingslib.Utils.getColorAttrDefaultColor
22 import com.android.systemui.R
23 import com.android.systemui.keyguard.domain.interactor.BurnInOffsets
24 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
25 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
26 import com.android.systemui.keyguard.domain.interactor.UdfpsKeyguardInteractor
27 import com.android.systemui.keyguard.shared.model.KeyguardState
28 import com.android.systemui.keyguard.shared.model.StatusBarState
29 import com.android.wm.shell.animation.Interpolators
30 import javax.inject.Inject
31 import kotlin.math.roundToInt
32 import kotlinx.coroutines.ExperimentalCoroutinesApi
33 import kotlinx.coroutines.flow.Flow
34 import kotlinx.coroutines.flow.combine
35 import kotlinx.coroutines.flow.flatMapLatest
36 import kotlinx.coroutines.flow.map
37 import kotlinx.coroutines.flow.merge
38 
39 /** View-model for UDFPS lockscreen views. */
40 @ExperimentalCoroutinesApi
41 open class UdfpsLockscreenViewModel(
42     context: Context,
43     lockscreenColorResId: Int,
44     alternateBouncerColorResId: Int,
45     transitionInteractor: KeyguardTransitionInteractor,
46     udfpsKeyguardInteractor: UdfpsKeyguardInteractor,
47     keyguardInteractor: KeyguardInteractor,
48 ) {
49     private val toLockscreen: Flow<TransitionViewModel> =
50         transitionInteractor.anyStateToLockscreenTransition.map {
51             TransitionViewModel(
52                 alpha =
53                     if (it.from == KeyguardState.AOD) {
54                         it.value // animate
55                     } else {
56                         1f
57                     },
58                 scale = 1f,
59                 color = getColorAttrDefaultColor(context, lockscreenColorResId),
60             )
61         }
62 
63     private val toAlternateBouncer: Flow<TransitionViewModel> =
64         keyguardInteractor.statusBarState.flatMapLatest { statusBarState ->
65             transitionInteractor.anyStateToAlternateBouncerTransition.map {
66                 TransitionViewModel(
67                     alpha = 1f,
68                     scale =
69                         if (visibleInKeyguardState(it.from, statusBarState)) {
70                             1f
71                         } else {
72                             Interpolators.FAST_OUT_SLOW_IN.getInterpolation(it.value)
73                         },
74                     color = getColorAttrDefaultColor(context, alternateBouncerColorResId),
75                 )
76             }
77         }
78 
79     private val fadeOut: Flow<TransitionViewModel> =
80         keyguardInteractor.statusBarState.flatMapLatest { statusBarState ->
81             merge(
82                     transitionInteractor.anyStateToGoneTransition,
83                     transitionInteractor.anyStateToAodTransition,
84                     transitionInteractor.anyStateToOccludedTransition,
85                     transitionInteractor.anyStateToPrimaryBouncerTransition,
86                     transitionInteractor.anyStateToDreamingTransition,
87                 )
88                 .map {
89                     TransitionViewModel(
90                         alpha =
91                             if (visibleInKeyguardState(it.from, statusBarState)) {
92                                 1f - it.value
93                             } else {
94                                 0f
95                             },
96                         scale = 1f,
97                         color =
98                             if (it.from == KeyguardState.ALTERNATE_BOUNCER) {
99                                 getColorAttrDefaultColor(context, alternateBouncerColorResId)
100                             } else {
101                                 getColorAttrDefaultColor(context, lockscreenColorResId)
102                             },
103                     )
104                 }
105         }
106 
107     private fun visibleInKeyguardState(
108         state: KeyguardState,
109         statusBarState: StatusBarState
110     ): Boolean {
111         return when (state) {
112             KeyguardState.OFF,
113             KeyguardState.DOZING,
114             KeyguardState.DREAMING,
115             KeyguardState.DREAMING_LOCKSCREEN_HOSTED,
116             KeyguardState.AOD,
117             KeyguardState.PRIMARY_BOUNCER,
118             KeyguardState.GONE,
119             KeyguardState.OCCLUDED -> false
120             KeyguardState.LOCKSCREEN -> statusBarState == StatusBarState.KEYGUARD
121             KeyguardState.ALTERNATE_BOUNCER -> true
122         }
123     }
124 
125     private val keyguardStateTransition =
126         merge(
127             toAlternateBouncer,
128             toLockscreen,
129             fadeOut,
130         )
131 
132     private val dialogHideAffordancesAlphaMultiplier: Flow<Float> =
133         udfpsKeyguardInteractor.dialogHideAffordancesRequest.map { hideAffordances ->
134             if (hideAffordances) {
135                 0f
136             } else {
137                 1f
138             }
139         }
140 
141     private val alphaMultiplier: Flow<Float> =
142         combine(
143             transitionInteractor.startedKeyguardState,
144             dialogHideAffordancesAlphaMultiplier,
145             udfpsKeyguardInteractor.shadeExpansion,
146             udfpsKeyguardInteractor.qsProgress,
147         ) { startedKeyguardState, dialogHideAffordancesAlphaMultiplier, shadeExpansion, qsProgress
148             ->
149             if (startedKeyguardState == KeyguardState.ALTERNATE_BOUNCER) {
150                 1f
151             } else {
152                 dialogHideAffordancesAlphaMultiplier * (1f - shadeExpansion) * (1f - qsProgress)
153             }
154         }
155 
156     val transition: Flow<TransitionViewModel> =
157         combine(
158             alphaMultiplier,
159             keyguardStateTransition,
160         ) { alphaMultiplier, keyguardStateTransition ->
161             TransitionViewModel(
162                 alpha = keyguardStateTransition.alpha * alphaMultiplier,
163                 scale = keyguardStateTransition.scale,
164                 color = keyguardStateTransition.color,
165             )
166         }
167     val visible: Flow<Boolean> = transition.map { it.alpha != 0f }
168 }
169 
170 @ExperimentalCoroutinesApi
171 class FingerprintViewModel
172 @Inject
173 constructor(
174     val context: Context,
175     transitionInteractor: KeyguardTransitionInteractor,
176     interactor: UdfpsKeyguardInteractor,
177     keyguardInteractor: KeyguardInteractor,
178 ) :
179     UdfpsLockscreenViewModel(
180         context,
181         android.R.attr.textColorPrimary,
182         com.android.internal.R.attr.materialColorOnPrimaryFixed,
183         transitionInteractor,
184         interactor,
185         keyguardInteractor,
186     ) {
187     val dozeAmount: Flow<Float> = interactor.dozeAmount
188     val burnInOffsets: Flow<BurnInOffsets> = interactor.burnInOffsets
189 
190     // Padding between the fingerprint icon and its bounding box in pixels.
191     val padding: Flow<Int> =
192         interactor.scaleForResolution.map { scale ->
193             (context.resources.getDimensionPixelSize(R.dimen.lock_icon_padding) * scale)
194                 .roundToInt()
195         }
196 }
197 
198 @ExperimentalCoroutinesApi
199 class BackgroundViewModel
200 @Inject
201 constructor(
202     val context: Context,
203     transitionInteractor: KeyguardTransitionInteractor,
204     interactor: UdfpsKeyguardInteractor,
205     keyguardInteractor: KeyguardInteractor,
206 ) :
207     UdfpsLockscreenViewModel(
208         context,
209         com.android.internal.R.attr.colorSurface,
210         com.android.internal.R.attr.materialColorPrimaryFixed,
211         transitionInteractor,
212         interactor,
213         keyguardInteractor,
214     )
215 
216 data class TransitionViewModel(
217     val alpha: Float,
218     val scale: Float,
219     @ColorInt val color: Int,
220 )
221