• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * 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 
18 package com.android.systemui.biometrics.ui.binder
19 
20 import android.content.res.Resources
21 import android.util.Log
22 import androidx.lifecycle.Lifecycle
23 import androidx.lifecycle.repeatOnLifecycle
24 import com.airbnb.lottie.LottieAnimationView
25 import com.android.app.tracing.coroutines.launchTraced as launch
26 import com.android.settingslib.widget.LottieColorUtils
27 import com.android.systemui.Flags.bpColors
28 import com.android.systemui.biometrics.ui.viewmodel.PromptIconViewModel
29 import com.android.systemui.biometrics.ui.viewmodel.PromptIconViewModel.AuthType
30 import com.android.systemui.biometrics.ui.viewmodel.PromptViewModel
31 import com.android.systemui.lifecycle.repeatWhenAttached
32 import com.android.systemui.res.R
33 import com.android.systemui.util.kotlin.Quad
34 import com.android.systemui.util.kotlin.Utils.Companion.toQuint
35 import com.android.systemui.util.kotlin.sample
36 import kotlinx.coroutines.flow.combine
37 
38 private const val TAG = "PromptIconViewBinder"
39 
40 /** Sub-binder for [BiometricPromptLayout.iconView]. */
41 object PromptIconViewBinder {
42     /** Binds [BiometricPromptLayout.iconView] to [PromptIconViewModel]. */
43     @JvmStatic
44     fun bind(iconView: LottieAnimationView, promptViewModel: PromptViewModel) {
45         val viewModel = promptViewModel.iconViewModel
46         iconView.repeatWhenAttached {
47             repeatOnLifecycle(Lifecycle.State.STARTED) {
48                 viewModel.onConfigurationChanged(iconView.context.resources.configuration)
49 
50                 launch {
51                     viewModel.iconAsset
52                         .sample(
53                             combine(
54                                 viewModel.activeAuthType,
55                                 viewModel.shouldAnimateIconView,
56                                 viewModel.shouldLoopIconView,
57                                 viewModel.showingError,
58                                 ::Quad,
59                             ),
60                             ::toQuint,
61                         )
62                         .collect {
63                             (
64                                 iconAsset,
65                                 activeAuthType,
66                                 shouldAnimateIconView,
67                                 shouldLoopIconView,
68                                 showingError) ->
69                             if (iconAsset != -1) {
70                                 iconView.updateAsset(
71                                     "iconAsset",
72                                     iconAsset,
73                                     shouldAnimateIconView,
74                                     shouldLoopIconView,
75                                     activeAuthType,
76                                 )
77                                 viewModel.setPreviousIconWasError(showingError)
78                             }
79                         }
80                 }
81 
82                 launch {
83                     viewModel.iconViewRotation.collect { rotation -> iconView.rotation = rotation }
84                 }
85 
86                 launch {
87                     viewModel.contentDescriptionId.collect { id ->
88                         if (id != -1) {
89                             iconView.contentDescription = iconView.context.getString(id)
90                         }
91                     }
92                 }
93             }
94         }
95     }
96 }
97 
LottieAnimationViewnull98 fun LottieAnimationView.updateAsset(
99     type: String,
100     asset: Int,
101     shouldAnimateIconView: Boolean,
102     shouldLoopIconView: Boolean,
103     activeAuthType: AuthType,
104 ) {
105     setFailureListener(type, asset, activeAuthType)
106     pauseAnimation()
107     setAnimation(asset)
108     if (animatingFromSfpsAuthenticating(asset)) {
109         // Skipping to error / success / unlock segment of animation
110         setMinFrame(158)
111     } else {
112         frame = 0
113     }
114     if (shouldAnimateIconView) {
115         loop(shouldLoopIconView)
116         playAnimation()
117     }
118     LottieColorUtils.applyDynamicColors(context, this)
119     if (bpColors()) {
120         LottieColorUtils.applyMaterialColor(context, this)
121     }
122 }
123 
animatingFromSfpsAuthenticatingnull124 private fun animatingFromSfpsAuthenticating(asset: Int): Boolean =
125     asset in sfpsFpToErrorAssets || asset in sfpsFpToUnlockAssets || asset in sfpsFpToSuccessAssets
126 
127 private val sfpsFpToErrorAssets: List<Int> =
128     listOf(
129         R.raw.biometricprompt_sfps_fingerprint_to_error,
130         R.raw.biometricprompt_sfps_fingerprint_to_error_90,
131         R.raw.biometricprompt_sfps_fingerprint_to_error_180,
132         R.raw.biometricprompt_sfps_fingerprint_to_error_270,
133         R.raw.biometricprompt_sfps_rear_display_fingerprint_to_error,
134         R.raw.biometricprompt_sfps_rear_display_fingerprint_to_error_90,
135         R.raw.biometricprompt_sfps_rear_display_fingerprint_to_error_180,
136         R.raw.biometricprompt_sfps_rear_display_fingerprint_to_error_270,
137     )
138 
139 private val sfpsFpToUnlockAssets: List<Int> =
140     listOf(
141         R.raw.biometricprompt_sfps_fingerprint_to_unlock,
142         R.raw.biometricprompt_sfps_fingerprint_to_unlock_90,
143         R.raw.biometricprompt_sfps_fingerprint_to_unlock_180,
144         R.raw.biometricprompt_sfps_fingerprint_to_unlock_270,
145         R.raw.biometricprompt_sfps_rear_display_fingerprint_to_unlock,
146         R.raw.biometricprompt_sfps_rear_display_fingerprint_to_unlock_90,
147         R.raw.biometricprompt_sfps_rear_display_fingerprint_to_unlock_180,
148         R.raw.biometricprompt_sfps_rear_display_fingerprint_to_unlock_270,
149     )
150 
151 private val sfpsFpToSuccessAssets: List<Int> =
152     listOf(
153         R.raw.biometricprompt_sfps_fingerprint_to_success,
154         R.raw.biometricprompt_sfps_fingerprint_to_success_90,
155         R.raw.biometricprompt_sfps_fingerprint_to_success_180,
156         R.raw.biometricprompt_sfps_fingerprint_to_success_270,
157         R.raw.biometricprompt_sfps_rear_display_fingerprint_to_success,
158         R.raw.biometricprompt_sfps_rear_display_fingerprint_to_success_90,
159         R.raw.biometricprompt_sfps_rear_display_fingerprint_to_success_180,
160         R.raw.biometricprompt_sfps_rear_display_fingerprint_to_success_270,
161     )
162 
163 private fun LottieAnimationView.setFailureListener(type: String, asset: Int, authType: AuthType) {
164     val assetName =
165         try {
166             context.resources.getResourceEntryName(asset)
167         } catch (e: Resources.NotFoundException) {
168             "Asset $asset not found"
169         }
170 
171     setFailureListener { result: Throwable? ->
172         Log.d(
173             TAG,
174             "Collecting $type | " +
175                 "activeAuthType = $authType | " +
176                 "Invalid resource id: $asset, " +
177                 "name $assetName",
178             result,
179         )
180     }
181 }
182