1 /* <lambda>null2 * 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.content.res.Configuration 23 import android.hardware.fingerprint.FingerprintManager 24 import android.view.DisplayInfo 25 import android.view.Surface 26 import android.view.View 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 import com.android.systemui.unfold.compat.ScreenSizeFoldProvider 39 import com.android.systemui.unfold.updates.FoldProvider 40 41 /** Fingerprint only icon animator for BiometricPrompt. */ 42 open class AuthBiometricFingerprintIconController( 43 context: Context, 44 iconView: LottieAnimationView, 45 protected val iconViewOverlay: LottieAnimationView 46 ) : AuthIconController(context, iconView), FoldProvider.FoldCallback { 47 48 private var isDeviceFolded: Boolean = false 49 private val isSideFps: Boolean 50 private val isReverseDefaultRotation = 51 context.resources.getBoolean(com.android.internal.R.bool.config_reverseDefaultRotation) 52 private val screenSizeFoldProvider: ScreenSizeFoldProvider = ScreenSizeFoldProvider(context) 53 var iconLayoutParamSize: Pair<Int, Int> = Pair(1, 1) 54 set(value) { 55 if (field == value) { 56 return 57 } 58 iconViewOverlay.layoutParams.width = value.first 59 iconViewOverlay.layoutParams.height = value.second 60 iconView.layoutParams.width = value.first 61 iconView.layoutParams.height = value.second 62 field = value 63 } 64 65 init { 66 iconLayoutParamSize = Pair(context.resources.getDimensionPixelSize( 67 R.dimen.biometric_dialog_fingerprint_icon_width), 68 context.resources.getDimensionPixelSize( 69 R.dimen.biometric_dialog_fingerprint_icon_height)) 70 isSideFps = 71 (context.getSystemService(FINGERPRINT_SERVICE) as FingerprintManager?)?.let { fpm -> 72 fpm.sensorPropertiesInternal.any { it.isAnySidefpsType } 73 } ?: false 74 preloadAssets(context) 75 val displayInfo = DisplayInfo() 76 context.display?.getDisplayInfo(displayInfo) 77 if (isSideFps && getRotationFromDefault(displayInfo.rotation) == Surface.ROTATION_180) { 78 iconView.rotation = 180f 79 } 80 screenSizeFoldProvider.registerCallback(this, context.mainExecutor) 81 screenSizeFoldProvider.onConfigurationChange(context.resources.configuration) 82 } 83 84 private fun updateIconSideFps(@BiometricState lastState: Int, @BiometricState newState: Int) { 85 val displayInfo = DisplayInfo() 86 context.display?.getDisplayInfo(displayInfo) 87 val rotation = getRotationFromDefault(displayInfo.rotation) 88 val iconAnimation = getSideFpsAnimationForTransition(rotation) 89 val iconViewOverlayAnimation = 90 getSideFpsOverlayAnimationForTransition(lastState, newState, rotation) ?: return 91 92 if (!(lastState == STATE_AUTHENTICATING_ANIMATING_IN && newState == STATE_AUTHENTICATING)) { 93 iconView.setAnimation(iconAnimation) 94 iconViewOverlay.setAnimation(iconViewOverlayAnimation) 95 } 96 97 val iconContentDescription = getIconContentDescription(newState) 98 if (iconContentDescription != null) { 99 iconView.contentDescription = iconContentDescription 100 } 101 102 iconView.frame = 0 103 iconViewOverlay.frame = 0 104 if (shouldAnimateSfpsIconViewForTransition(lastState, newState)) { 105 iconView.playAnimation() 106 } 107 108 if (shouldAnimateIconViewOverlayForTransition(lastState, newState)) { 109 iconViewOverlay.playAnimation() 110 } 111 112 LottieColorUtils.applyDynamicColors(context, iconView) 113 LottieColorUtils.applyDynamicColors(context, iconViewOverlay) 114 } 115 116 private fun updateIconNormal(@BiometricState lastState: Int, @BiometricState newState: Int) { 117 val icon = getAnimationForTransition(lastState, newState) ?: return 118 119 if (!(lastState == STATE_AUTHENTICATING_ANIMATING_IN && newState == STATE_AUTHENTICATING)) { 120 iconView.setAnimation(icon) 121 } 122 123 val iconContentDescription = getIconContentDescription(newState) 124 if (iconContentDescription != null) { 125 iconView.contentDescription = iconContentDescription 126 } 127 128 iconView.frame = 0 129 if (shouldAnimateIconViewForTransition(lastState, newState)) { 130 iconView.playAnimation() 131 } 132 LottieColorUtils.applyDynamicColors(context, iconView) 133 } 134 135 override fun onConfigurationChanged(newConfig: Configuration) { 136 screenSizeFoldProvider.onConfigurationChange(newConfig) 137 } 138 139 override fun updateIcon(@BiometricState lastState: Int, @BiometricState newState: Int) { 140 if (isSideFps) { 141 updateIconSideFps(lastState, newState) 142 } else { 143 iconViewOverlay.visibility = View.GONE 144 updateIconNormal(lastState, newState) 145 } 146 } 147 148 private fun getIconContentDescription(@BiometricState newState: Int): CharSequence? { 149 val id = when (newState) { 150 STATE_IDLE, 151 STATE_AUTHENTICATING_ANIMATING_IN, 152 STATE_AUTHENTICATING, 153 STATE_PENDING_CONFIRMATION, 154 STATE_AUTHENTICATED -> R.string.security_settings_sfps_enroll_find_sensor_message 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 getSideFpsAnimationForTransition(rotation: Int): Int = when (rotation) { 234 Surface.ROTATION_90 -> if (isDeviceFolded) { 235 R.raw.biometricprompt_folded_base_topleft 236 } else { 237 R.raw.biometricprompt_portrait_base_topleft 238 } 239 Surface.ROTATION_270 -> if (isDeviceFolded) { 240 R.raw.biometricprompt_folded_base_bottomright 241 } else { 242 R.raw.biometricprompt_portrait_base_bottomright 243 } 244 else -> if (isDeviceFolded) { 245 R.raw.biometricprompt_folded_base_default 246 } else { 247 R.raw.biometricprompt_landscape_base 248 } 249 } 250 251 @RawRes 252 private fun getSideFpsOverlayAnimationForTransition( 253 @BiometricState oldState: Int, 254 @BiometricState newState: Int, 255 rotation: Int 256 ): Int? = when (newState) { 257 STATE_HELP, 258 STATE_ERROR -> { 259 when (rotation) { 260 Surface.ROTATION_0 -> R.raw.biometricprompt_fingerprint_to_error_landscape 261 Surface.ROTATION_90 -> 262 R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_topleft 263 Surface.ROTATION_180 -> 264 R.raw.biometricprompt_fingerprint_to_error_landscape 265 Surface.ROTATION_270 -> 266 R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_bottomright 267 else -> R.raw.biometricprompt_fingerprint_to_error_landscape 268 } 269 } 270 STATE_AUTHENTICATING_ANIMATING_IN, 271 STATE_AUTHENTICATING -> { 272 if (oldState == STATE_ERROR || oldState == STATE_HELP) { 273 when (rotation) { 274 Surface.ROTATION_0 -> 275 R.raw.biometricprompt_symbol_error_to_fingerprint_landscape 276 Surface.ROTATION_90 -> 277 R.raw.biometricprompt_symbol_error_to_fingerprint_portrait_topleft 278 Surface.ROTATION_180 -> 279 R.raw.biometricprompt_symbol_error_to_fingerprint_landscape 280 Surface.ROTATION_270 -> 281 R.raw.biometricprompt_symbol_error_to_fingerprint_portrait_bottomright 282 else -> R.raw.biometricprompt_symbol_error_to_fingerprint_landscape 283 } 284 } else { 285 when (rotation) { 286 Surface.ROTATION_0 -> R.raw.biometricprompt_fingerprint_to_error_landscape 287 Surface.ROTATION_90 -> 288 R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_topleft 289 Surface.ROTATION_180 -> 290 R.raw.biometricprompt_fingerprint_to_error_landscape 291 Surface.ROTATION_270 -> 292 R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_bottomright 293 else -> R.raw.biometricprompt_fingerprint_to_error_landscape 294 } 295 } 296 } 297 STATE_AUTHENTICATED -> { 298 if (oldState == STATE_ERROR || oldState == STATE_HELP) { 299 when (rotation) { 300 Surface.ROTATION_0 -> 301 R.raw.biometricprompt_symbol_error_to_success_landscape 302 Surface.ROTATION_90 -> 303 R.raw.biometricprompt_symbol_error_to_success_portrait_topleft 304 Surface.ROTATION_180 -> 305 R.raw.biometricprompt_symbol_error_to_success_landscape 306 Surface.ROTATION_270 -> 307 R.raw.biometricprompt_symbol_error_to_success_portrait_bottomright 308 else -> R.raw.biometricprompt_symbol_error_to_success_landscape 309 } 310 } else { 311 when (rotation) { 312 Surface.ROTATION_0 -> 313 R.raw.biometricprompt_symbol_fingerprint_to_success_landscape 314 Surface.ROTATION_90 -> 315 R.raw.biometricprompt_symbol_fingerprint_to_success_portrait_topleft 316 Surface.ROTATION_180 -> 317 R.raw.biometricprompt_symbol_fingerprint_to_success_landscape 318 Surface.ROTATION_270 -> 319 R.raw.biometricprompt_symbol_fingerprint_to_success_portrait_bottomright 320 else -> R.raw.biometricprompt_symbol_fingerprint_to_success_landscape 321 } 322 } 323 } 324 else -> null 325 } 326 327 private fun preloadAssets(context: Context) { 328 if (isSideFps) { 329 cacheLottieAssetsInContext( 330 context, 331 R.raw.biometricprompt_fingerprint_to_error_landscape, 332 R.raw.biometricprompt_folded_base_bottomright, 333 R.raw.biometricprompt_folded_base_default, 334 R.raw.biometricprompt_folded_base_topleft, 335 R.raw.biometricprompt_landscape_base, 336 R.raw.biometricprompt_portrait_base_bottomright, 337 R.raw.biometricprompt_portrait_base_topleft, 338 R.raw.biometricprompt_symbol_error_to_fingerprint_landscape, 339 R.raw.biometricprompt_symbol_error_to_fingerprint_portrait_bottomright, 340 R.raw.biometricprompt_symbol_error_to_fingerprint_portrait_topleft, 341 R.raw.biometricprompt_symbol_error_to_success_landscape, 342 R.raw.biometricprompt_symbol_error_to_success_portrait_bottomright, 343 R.raw.biometricprompt_symbol_error_to_success_portrait_topleft, 344 R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_bottomright, 345 R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_topleft, 346 R.raw.biometricprompt_symbol_fingerprint_to_success_landscape, 347 R.raw.biometricprompt_symbol_fingerprint_to_success_portrait_bottomright, 348 R.raw.biometricprompt_symbol_fingerprint_to_success_portrait_topleft 349 ) 350 } else { 351 cacheLottieAssetsInContext( 352 context, 353 R.raw.fingerprint_dialogue_error_to_fingerprint_lottie, 354 R.raw.fingerprint_dialogue_error_to_success_lottie, 355 R.raw.fingerprint_dialogue_fingerprint_to_error_lottie, 356 R.raw.fingerprint_dialogue_fingerprint_to_success_lottie 357 ) 358 } 359 } 360 361 override fun onFoldUpdated(isFolded: Boolean) { 362 isDeviceFolded = isFolded 363 } 364 } 365