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 package com.android.systemui.biometrics
18 
19 import android.annotation.RawRes
20 import android.content.Context
21 import android.content.Context.FINGERPRINT_SERVICE
22 import android.hardware.fingerprint.FingerprintManager
23 import android.view.DisplayInfo
24 import android.view.Surface
25 import android.view.View
26 import androidx.annotation.VisibleForTesting
27 import com.airbnb.lottie.LottieAnimationView
28 import com.android.settingslib.widget.LottieColorUtils
29 import com.android.systemui.R
30 import com.android.systemui.biometrics.AuthBiometricView.BiometricState
31 import com.android.systemui.biometrics.AuthBiometricView.STATE_AUTHENTICATED
32 import com.android.systemui.biometrics.AuthBiometricView.STATE_AUTHENTICATING
33 import com.android.systemui.biometrics.AuthBiometricView.STATE_AUTHENTICATING_ANIMATING_IN
34 import com.android.systemui.biometrics.AuthBiometricView.STATE_ERROR
35 import com.android.systemui.biometrics.AuthBiometricView.STATE_HELP
36 import com.android.systemui.biometrics.AuthBiometricView.STATE_IDLE
37 import com.android.systemui.biometrics.AuthBiometricView.STATE_PENDING_CONFIRMATION
38 
39 /** Fingerprint only icon animator for BiometricPrompt.  */
40 open class AuthBiometricFingerprintIconController(
41         context: Context,
42         iconView: LottieAnimationView,
43         protected val iconViewOverlay: LottieAnimationView
44 ) : AuthIconController(context, iconView) {
45 
46     private val isSideFps: Boolean
47     private val isReverseDefaultRotation =
48             context.resources.getBoolean(com.android.internal.R.bool.config_reverseDefaultRotation)
49 
50     var iconLayoutParamSize: Pair<Int, Int> = Pair(1, 1)
51         set(value) {
52             if (field == value) {
53                 return
54             }
55             iconViewOverlay.layoutParams.width = value.first
56             iconViewOverlay.layoutParams.height = value.second
57             iconView.layoutParams.width = value.first
58             iconView.layoutParams.height = value.second
59             field = value
60         }
61 
62     init {
63         iconLayoutParamSize = Pair(context.resources.getDimensionPixelSize(
64                 R.dimen.biometric_dialog_fingerprint_icon_width),
65                 context.resources.getDimensionPixelSize(
66                         R.dimen.biometric_dialog_fingerprint_icon_height))
67         isSideFps =
68             (context.getSystemService(FINGERPRINT_SERVICE) as FingerprintManager?)?.let { fpm ->
69                 fpm.sensorPropertiesInternal.any { it.isAnySidefpsType }
70             } ?: false
71         preloadAssets(context)
72         val displayInfo = DisplayInfo()
73         context.display?.getDisplayInfo(displayInfo)
74         if (isSideFps && getRotationFromDefault(displayInfo.rotation) == Surface.ROTATION_180) {
75             iconView.rotation = 180f
76         }
77     }
78 
79     private fun updateIconSideFps(@BiometricState lastState: Int, @BiometricState newState: Int) {
80         val displayInfo = DisplayInfo()
81         context.display?.getDisplayInfo(displayInfo)
82         val rotation = getRotationFromDefault(displayInfo.rotation)
83         val iconViewOverlayAnimation =
84                 getSideFpsOverlayAnimationForTransition(lastState, newState, rotation) ?: return
85 
86         if (!(lastState == STATE_AUTHENTICATING_ANIMATING_IN && newState == STATE_AUTHENTICATING)) {
87             iconViewOverlay.setAnimation(iconViewOverlayAnimation)
88         }
89 
90         val iconContentDescription = getIconContentDescription(newState)
91         if (iconContentDescription != null) {
92             iconView.contentDescription = iconContentDescription
93         }
94 
95         iconView.frame = 0
96         iconViewOverlay.frame = 0
97         if (shouldAnimateSfpsIconViewForTransition(lastState, newState)) {
98             iconView.playAnimation()
99         }
100 
101         if (shouldAnimateIconViewOverlayForTransition(lastState, newState)) {
102             iconViewOverlay.playAnimation()
103         }
104 
105         LottieColorUtils.applyDynamicColors(context, iconView)
106         LottieColorUtils.applyDynamicColors(context, iconViewOverlay)
107     }
108 
109     private fun updateIconNormal(@BiometricState lastState: Int, @BiometricState newState: Int) {
110         val icon = getAnimationForTransition(lastState, newState) ?: return
111 
112         if (!(lastState == STATE_AUTHENTICATING_ANIMATING_IN && newState == STATE_AUTHENTICATING)) {
113             iconView.setAnimation(icon)
114         }
115 
116         val iconContentDescription = getIconContentDescription(newState)
117         if (iconContentDescription != null) {
118             iconView.contentDescription = iconContentDescription
119         }
120 
121         iconView.frame = 0
122         if (shouldAnimateIconViewForTransition(lastState, newState)) {
123             iconView.playAnimation()
124         }
125         LottieColorUtils.applyDynamicColors(context, iconView)
126     }
127 
128     override fun updateIcon(@BiometricState lastState: Int, @BiometricState newState: Int) {
129         if (isSideFps) {
130             updateIconSideFps(lastState, newState)
131         } else {
132             iconViewOverlay.visibility = View.GONE
133             updateIconNormal(lastState, newState)
134         }
135     }
136 
137     @VisibleForTesting
138     fun getIconContentDescription(@BiometricState newState: Int): CharSequence? {
139         val id = when (newState) {
140             STATE_IDLE,
141             STATE_AUTHENTICATING_ANIMATING_IN,
142             STATE_AUTHENTICATING,
143             STATE_AUTHENTICATED ->
144                 if (isSideFps) {
145                     R.string.security_settings_sfps_enroll_find_sensor_message
146                 } else {
147                     R.string.fingerprint_dialog_touch_sensor
148                 }
149             STATE_PENDING_CONFIRMATION ->
150                 if (isSideFps) {
151                     R.string.security_settings_sfps_enroll_find_sensor_message
152                 } else {
153                     R.string.fingerprint_dialog_authenticated_confirmation
154                 }
155             STATE_ERROR,
156             STATE_HELP -> R.string.biometric_dialog_try_again
157             else -> null
158         }
159         return if (id != null) context.getString(id) else null
160     }
161 
162     protected open fun shouldAnimateIconViewForTransition(
163             @BiometricState oldState: Int,
164             @BiometricState newState: Int
165     ) = when (newState) {
166         STATE_HELP,
167         STATE_ERROR -> true
168         STATE_AUTHENTICATING_ANIMATING_IN,
169         STATE_AUTHENTICATING -> oldState == STATE_ERROR || oldState == STATE_HELP
170         STATE_AUTHENTICATED -> true
171         else -> false
172     }
173 
174     private fun shouldAnimateSfpsIconViewForTransition(
175             @BiometricState oldState: Int,
176             @BiometricState newState: Int
177     ) = when (newState) {
178         STATE_HELP,
179         STATE_ERROR -> true
180         STATE_AUTHENTICATING_ANIMATING_IN,
181         STATE_AUTHENTICATING ->
182             oldState == STATE_ERROR || oldState == STATE_HELP || oldState == STATE_IDLE
183         STATE_AUTHENTICATED -> true
184         else -> false
185     }
186 
187     protected open fun shouldAnimateIconViewOverlayForTransition(
188             @BiometricState oldState: Int,
189             @BiometricState newState: Int
190     ) = when (newState) {
191         STATE_HELP,
192         STATE_ERROR -> true
193         STATE_AUTHENTICATING_ANIMATING_IN,
194         STATE_AUTHENTICATING -> oldState == STATE_ERROR || oldState == STATE_HELP
195         STATE_AUTHENTICATED -> true
196         else -> false
197     }
198 
199     @RawRes
200     protected open fun getAnimationForTransition(
201             @BiometricState oldState: Int,
202             @BiometricState newState: Int
203     ): Int? {
204         val id = when (newState) {
205             STATE_HELP,
206             STATE_ERROR -> {
207                 R.raw.fingerprint_dialogue_fingerprint_to_error_lottie
208             }
209             STATE_AUTHENTICATING_ANIMATING_IN,
210             STATE_AUTHENTICATING -> {
211                 if (oldState == STATE_ERROR || oldState == STATE_HELP) {
212                     R.raw.fingerprint_dialogue_error_to_fingerprint_lottie
213                 } else {
214                     R.raw.fingerprint_dialogue_fingerprint_to_error_lottie
215                 }
216             }
217             STATE_AUTHENTICATED -> {
218                 if (oldState == STATE_ERROR || oldState == STATE_HELP) {
219                     R.raw.fingerprint_dialogue_error_to_success_lottie
220                 } else {
221                     R.raw.fingerprint_dialogue_fingerprint_to_success_lottie
222                 }
223             }
224             else -> return null
225         }
226         return if (id != null) return id else null
227     }
228 
229     private fun getRotationFromDefault(rotation: Int): Int =
230             if (isReverseDefaultRotation) (rotation + 1) % 4 else rotation
231 
232     @RawRes
233     private fun getSideFpsOverlayAnimationForTransition(
234             @BiometricState oldState: Int,
235             @BiometricState newState: Int,
236             rotation: Int
237     ): Int? = when (newState) {
238         STATE_HELP,
239         STATE_ERROR -> {
240             when (rotation) {
241                 Surface.ROTATION_0 -> R.raw.biometricprompt_fingerprint_to_error_landscape
242                 Surface.ROTATION_90 ->
243                     R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_topleft
244                 Surface.ROTATION_180 ->
245                     R.raw.biometricprompt_fingerprint_to_error_landscape
246                 Surface.ROTATION_270 ->
247                     R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_bottomright
248                 else -> R.raw.biometricprompt_fingerprint_to_error_landscape
249             }
250         }
251         STATE_AUTHENTICATING_ANIMATING_IN,
252         STATE_AUTHENTICATING -> {
253             if (oldState == STATE_ERROR || oldState == STATE_HELP) {
254                 when (rotation) {
255                     Surface.ROTATION_0 ->
256                         R.raw.biometricprompt_symbol_error_to_fingerprint_landscape
257                     Surface.ROTATION_90 ->
258                         R.raw.biometricprompt_symbol_error_to_fingerprint_portrait_topleft
259                     Surface.ROTATION_180 ->
260                         R.raw.biometricprompt_symbol_error_to_fingerprint_landscape
261                     Surface.ROTATION_270 ->
262                         R.raw.biometricprompt_symbol_error_to_fingerprint_portrait_bottomright
263                     else -> R.raw.biometricprompt_symbol_error_to_fingerprint_landscape
264                 }
265             } else {
266                 when (rotation) {
267                     Surface.ROTATION_0 -> R.raw.biometricprompt_fingerprint_to_error_landscape
268                     Surface.ROTATION_90 ->
269                         R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_topleft
270                     Surface.ROTATION_180 ->
271                         R.raw.biometricprompt_fingerprint_to_error_landscape
272                     Surface.ROTATION_270 ->
273                         R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_bottomright
274                     else -> R.raw.biometricprompt_fingerprint_to_error_landscape
275                 }
276             }
277         }
278         STATE_AUTHENTICATED -> {
279             if (oldState == STATE_ERROR || oldState == STATE_HELP) {
280                 when (rotation) {
281                     Surface.ROTATION_0 ->
282                         R.raw.biometricprompt_symbol_error_to_success_landscape
283                     Surface.ROTATION_90 ->
284                         R.raw.biometricprompt_symbol_error_to_success_portrait_topleft
285                     Surface.ROTATION_180 ->
286                         R.raw.biometricprompt_symbol_error_to_success_landscape
287                     Surface.ROTATION_270 ->
288                         R.raw.biometricprompt_symbol_error_to_success_portrait_bottomright
289                     else -> R.raw.biometricprompt_symbol_error_to_success_landscape
290                 }
291             } else {
292                 when (rotation) {
293                     Surface.ROTATION_0 ->
294                         R.raw.biometricprompt_symbol_fingerprint_to_success_landscape
295                     Surface.ROTATION_90 ->
296                         R.raw.biometricprompt_symbol_fingerprint_to_success_portrait_topleft
297                     Surface.ROTATION_180 ->
298                         R.raw.biometricprompt_symbol_fingerprint_to_success_landscape
299                     Surface.ROTATION_270 ->
300                         R.raw.biometricprompt_symbol_fingerprint_to_success_portrait_bottomright
301                     else -> R.raw.biometricprompt_symbol_fingerprint_to_success_landscape
302                 }
303             }
304         }
305         else -> null
306     }
307 
308     private fun preloadAssets(context: Context) {
309         if (isSideFps) {
310             cacheLottieAssetsInContext(
311                 context,
312                 R.raw.biometricprompt_fingerprint_to_error_landscape,
313                 R.raw.biometricprompt_folded_base_bottomright,
314                 R.raw.biometricprompt_folded_base_default,
315                 R.raw.biometricprompt_folded_base_topleft,
316                 R.raw.biometricprompt_landscape_base,
317                 R.raw.biometricprompt_portrait_base_bottomright,
318                 R.raw.biometricprompt_portrait_base_topleft,
319                 R.raw.biometricprompt_symbol_error_to_fingerprint_landscape,
320                 R.raw.biometricprompt_symbol_error_to_fingerprint_portrait_bottomright,
321                 R.raw.biometricprompt_symbol_error_to_fingerprint_portrait_topleft,
322                 R.raw.biometricprompt_symbol_error_to_success_landscape,
323                 R.raw.biometricprompt_symbol_error_to_success_portrait_bottomright,
324                 R.raw.biometricprompt_symbol_error_to_success_portrait_topleft,
325                 R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_bottomright,
326                 R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_topleft,
327                 R.raw.biometricprompt_symbol_fingerprint_to_success_landscape,
328                 R.raw.biometricprompt_symbol_fingerprint_to_success_portrait_bottomright,
329                 R.raw.biometricprompt_symbol_fingerprint_to_success_portrait_topleft
330             )
331         } else {
332             cacheLottieAssetsInContext(
333                 context,
334                 R.raw.fingerprint_dialogue_error_to_fingerprint_lottie,
335                 R.raw.fingerprint_dialogue_error_to_success_lottie,
336                 R.raw.fingerprint_dialogue_fingerprint_to_error_lottie,
337                 R.raw.fingerprint_dialogue_fingerprint_to_success_lottie
338             )
339         }
340     }
341 }
342