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