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