/* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.systemui.biometrics import android.content.Context import android.graphics.drawable.Drawable import android.util.Log import com.airbnb.lottie.LottieAnimationView import com.android.systemui.R import com.android.systemui.biometrics.AuthBiometricView.BiometricState import com.android.systemui.biometrics.AuthBiometricView.STATE_AUTHENTICATED import com.android.systemui.biometrics.AuthBiometricView.STATE_AUTHENTICATING import com.android.systemui.biometrics.AuthBiometricView.STATE_AUTHENTICATING_ANIMATING_IN import com.android.systemui.biometrics.AuthBiometricView.STATE_ERROR import com.android.systemui.biometrics.AuthBiometricView.STATE_HELP import com.android.systemui.biometrics.AuthBiometricView.STATE_IDLE import com.android.systemui.biometrics.AuthBiometricView.STATE_PENDING_CONFIRMATION private const val TAG = "AuthBiometricFaceIconController" /** Face only icon animator for BiometricPrompt. */ class AuthBiometricFaceIconController( context: Context, iconView: LottieAnimationView ) : AuthIconController(context, iconView) { // false = dark to light, true = light to dark private var lastPulseLightToDark = false @BiometricState private var state = 0 init { val size = context.resources.getDimensionPixelSize(R.dimen.biometric_dialog_face_icon_size) iconView.layoutParams.width = size iconView.layoutParams.height = size showStaticDrawable(R.drawable.face_dialog_pulse_dark_to_light) } private fun startPulsing() { lastPulseLightToDark = false animateIcon(R.drawable.face_dialog_pulse_dark_to_light, true) } private fun pulseInNextDirection() { val iconRes = if (lastPulseLightToDark) { R.drawable.face_dialog_pulse_dark_to_light } else { R.drawable.face_dialog_pulse_light_to_dark } animateIcon(iconRes, true /* repeat */) lastPulseLightToDark = !lastPulseLightToDark } override fun handleAnimationEnd(drawable: Drawable) { if (state == STATE_AUTHENTICATING || state == STATE_HELP) { pulseInNextDirection() } } override fun updateIcon(@BiometricState oldState: Int, @BiometricState newState: Int) { val lastStateIsErrorIcon = (oldState == STATE_ERROR || oldState == STATE_HELP) if (newState == STATE_AUTHENTICATING_ANIMATING_IN) { showStaticDrawable(R.drawable.face_dialog_pulse_dark_to_light) iconView.contentDescription = context.getString( R.string.biometric_dialog_face_icon_description_authenticating ) } else if (newState == STATE_AUTHENTICATING) { startPulsing() iconView.contentDescription = context.getString( R.string.biometric_dialog_face_icon_description_authenticating ) } else if (oldState == STATE_PENDING_CONFIRMATION && newState == STATE_AUTHENTICATED) { animateIconOnce(R.drawable.face_dialog_dark_to_checkmark) iconView.contentDescription = context.getString( R.string.biometric_dialog_face_icon_description_confirmed ) } else if (lastStateIsErrorIcon && newState == STATE_IDLE) { animateIconOnce(R.drawable.face_dialog_error_to_idle) iconView.contentDescription = context.getString( R.string.biometric_dialog_face_icon_description_idle ) } else if (lastStateIsErrorIcon && newState == STATE_AUTHENTICATED) { animateIconOnce(R.drawable.face_dialog_dark_to_checkmark) iconView.contentDescription = context.getString( R.string.biometric_dialog_face_icon_description_authenticated ) } else if (newState == STATE_ERROR && oldState != STATE_ERROR) { animateIconOnce(R.drawable.face_dialog_dark_to_error) iconView.contentDescription = context.getString( R.string.keyguard_face_failed ) } else if (oldState == STATE_AUTHENTICATING && newState == STATE_AUTHENTICATED) { animateIconOnce(R.drawable.face_dialog_dark_to_checkmark) iconView.contentDescription = context.getString( R.string.biometric_dialog_face_icon_description_authenticated ) } else if (newState == STATE_PENDING_CONFIRMATION) { animateIconOnce(R.drawable.face_dialog_wink_from_dark) iconView.contentDescription = context.getString( R.string.biometric_dialog_face_icon_description_authenticated ) } else if (newState == STATE_IDLE) { showStaticDrawable(R.drawable.face_dialog_idle_static) iconView.contentDescription = context.getString( R.string.biometric_dialog_face_icon_description_idle ) } else { Log.w(TAG, "Unhandled state: $newState") } state = newState } }