Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 520eb00f authored by Grace Cheng's avatar Grace Cheng Committed by Android (Google) Code Review
Browse files

Merge "Migrating all BiometricPrompt iconController logic" into main

parents 6f7ee7ed 32ee306d
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -33,6 +33,10 @@ data class BiometricModalities(
    val hasFingerprint: Boolean
        get() = fingerprintProperties != null

    /** If SFPS authentication is available. */
    val hasSfps: Boolean
        get() = hasFingerprint && fingerprintProperties!!.isAnySidefpsType

    /** If fingerprint authentication is available (and [faceProperties] is non-null). */
    val hasFace: Boolean
        get() = faceProperties != null
+0 −118
Original line number Diff line number Diff line
/*
 * 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.res.R
import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState

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

    private var state: BiometricState = BiometricState.STATE_IDLE

    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 == BiometricState.STATE_AUTHENTICATING || state == BiometricState.STATE_HELP) {
            pulseInNextDirection()
        }
    }

    override fun updateIcon(oldState: BiometricState, newState: BiometricState) {
        val lastStateIsErrorIcon = (oldState == BiometricState.STATE_ERROR || oldState == BiometricState.STATE_HELP)
        if (newState == BiometricState.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 == BiometricState.STATE_AUTHENTICATING) {
            startPulsing()
            iconView.contentDescription = context.getString(
                    R.string.biometric_dialog_face_icon_description_authenticating
            )
        } else if (oldState == BiometricState.STATE_PENDING_CONFIRMATION && newState == BiometricState.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 == BiometricState.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 == BiometricState.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 == BiometricState.STATE_ERROR && oldState != BiometricState.STATE_ERROR) {
            animateIconOnce(R.drawable.face_dialog_dark_to_error)
            iconView.contentDescription = context.getString(
                    R.string.keyguard_face_failed
            )
        } else if (oldState == BiometricState.STATE_AUTHENTICATING && newState == BiometricState.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 == BiometricState.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 == BiometricState.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
    }
}
+0 −67
Original line number Diff line number Diff line
/*
 * 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.annotation.RawRes
import android.content.Context
import com.airbnb.lottie.LottieAnimationView
import com.android.systemui.res.R
import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState
import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState.STATE_AUTHENTICATED
import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState.STATE_ERROR
import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState.STATE_HELP
import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState.STATE_PENDING_CONFIRMATION

/** Face/Fingerprint combined icon animator for BiometricPrompt. */
open class AuthBiometricFingerprintAndFaceIconController(
    context: Context,
    iconView: LottieAnimationView,
    iconViewOverlay: LottieAnimationView,
) : AuthBiometricFingerprintIconController(context, iconView, iconViewOverlay) {

    override val actsAsConfirmButton: Boolean = true

    override fun shouldAnimateIconViewForTransition(
            oldState: BiometricState,
            newState: BiometricState
    ): Boolean = when (newState) {
        STATE_PENDING_CONFIRMATION -> true
        else -> super.shouldAnimateIconViewForTransition(oldState, newState)
    }

    @RawRes
    override fun getAnimationForTransition(
        oldState: BiometricState,
        newState: BiometricState
    ): Int? = when (newState) {
        STATE_AUTHENTICATED -> {
           if (oldState == STATE_PENDING_CONFIRMATION) {
               R.raw.fingerprint_dialogue_unlocked_to_checkmark_success_lottie
           } else {
               super.getAnimationForTransition(oldState, newState)
           }
        }
        STATE_PENDING_CONFIRMATION -> {
            if (oldState == STATE_ERROR || oldState == STATE_HELP) {
                R.raw.fingerprint_dialogue_error_to_unlock_lottie
            } else {
                R.raw.fingerprint_dialogue_fingerprint_to_unlock_lottie
            }
        }
        else -> super.getAnimationForTransition(oldState, newState)
    }
}
+0 −342
Original line number Diff line number Diff line
/*
 * 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.annotation.RawRes
import android.content.Context
import android.content.Context.FINGERPRINT_SERVICE
import android.hardware.fingerprint.FingerprintManager
import android.view.DisplayInfo
import android.view.Surface
import android.view.View
import androidx.annotation.VisibleForTesting
import com.airbnb.lottie.LottieAnimationView
import com.android.settingslib.widget.LottieColorUtils
import com.android.systemui.res.R
import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState
import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState.STATE_AUTHENTICATED
import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState.STATE_AUTHENTICATING
import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState.STATE_AUTHENTICATING_ANIMATING_IN
import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState.STATE_ERROR
import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState.STATE_HELP
import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState.STATE_IDLE
import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState.STATE_PENDING_CONFIRMATION


/** Fingerprint only icon animator for BiometricPrompt.  */
open class AuthBiometricFingerprintIconController(
        context: Context,
        iconView: LottieAnimationView,
        protected val iconViewOverlay: LottieAnimationView
) : AuthIconController(context, iconView) {

    private val isSideFps: Boolean
    private val isReverseDefaultRotation =
            context.resources.getBoolean(com.android.internal.R.bool.config_reverseDefaultRotation)

    var iconLayoutParamSize: Pair<Int, Int> = Pair(1, 1)
        set(value) {
            if (field == value) {
                return
            }
            iconViewOverlay.layoutParams.width = value.first
            iconViewOverlay.layoutParams.height = value.second
            iconView.layoutParams.width = value.first
            iconView.layoutParams.height = value.second
            field = value
        }

    init {
        iconLayoutParamSize = Pair(context.resources.getDimensionPixelSize(
                R.dimen.biometric_dialog_fingerprint_icon_width),
                context.resources.getDimensionPixelSize(
                        R.dimen.biometric_dialog_fingerprint_icon_height))
        isSideFps =
            (context.getSystemService(FINGERPRINT_SERVICE) as FingerprintManager?)?.let { fpm ->
                fpm.sensorPropertiesInternal.any { it.isAnySidefpsType }
            } ?: false
        preloadAssets(context)
        val displayInfo = DisplayInfo()
        context.display?.getDisplayInfo(displayInfo)
        if (isSideFps && getRotationFromDefault(displayInfo.rotation) == Surface.ROTATION_180) {
            iconView.rotation = 180f
        }
    }

    private fun updateIconSideFps(lastState: BiometricState, newState: BiometricState) {
        val displayInfo = DisplayInfo()
        context.display?.getDisplayInfo(displayInfo)
        val rotation = getRotationFromDefault(displayInfo.rotation)
        val iconViewOverlayAnimation =
                getSideFpsOverlayAnimationForTransition(lastState, newState, rotation) ?: return

        if (!(lastState == STATE_AUTHENTICATING_ANIMATING_IN && newState == STATE_AUTHENTICATING)) {
            iconViewOverlay.setAnimation(iconViewOverlayAnimation)
        }

        val iconContentDescription = getIconContentDescription(newState)
        if (iconContentDescription != null) {
            iconView.contentDescription = iconContentDescription
        }

        iconView.frame = 0
        iconViewOverlay.frame = 0
        if (shouldAnimateSfpsIconViewForTransition(lastState, newState)) {
            iconView.playAnimation()
        }

        if (shouldAnimateIconViewOverlayForTransition(lastState, newState)) {
            iconViewOverlay.playAnimation()
        }

        LottieColorUtils.applyDynamicColors(context, iconView)
        LottieColorUtils.applyDynamicColors(context, iconViewOverlay)
    }

    private fun updateIconNormal(lastState: BiometricState, newState: BiometricState) {
        val icon = getAnimationForTransition(lastState, newState) ?: return

        if (!(lastState == STATE_AUTHENTICATING_ANIMATING_IN && newState == STATE_AUTHENTICATING)) {
            iconView.setAnimation(icon)
        }

        val iconContentDescription = getIconContentDescription(newState)
        if (iconContentDescription != null) {
            iconView.contentDescription = iconContentDescription
        }

        iconView.frame = 0
        if (shouldAnimateIconViewForTransition(lastState, newState)) {
            iconView.playAnimation()
        }
        LottieColorUtils.applyDynamicColors(context, iconView)
    }

    override fun updateIcon(lastState: BiometricState, newState: BiometricState) {
        if (isSideFps) {
            updateIconSideFps(lastState, newState)
        } else {
            iconViewOverlay.visibility = View.GONE
            updateIconNormal(lastState, newState)
        }
    }

    @VisibleForTesting
    fun getIconContentDescription(newState: BiometricState): CharSequence? {
        val id = when (newState) {
            STATE_IDLE,
            STATE_AUTHENTICATING_ANIMATING_IN,
            STATE_AUTHENTICATING,
            STATE_AUTHENTICATED ->
                if (isSideFps) {
                    R.string.security_settings_sfps_enroll_find_sensor_message
                } else {
                    R.string.fingerprint_dialog_touch_sensor
                }
            STATE_PENDING_CONFIRMATION ->
                if (isSideFps) {
                    R.string.security_settings_sfps_enroll_find_sensor_message
                } else {
                    R.string.fingerprint_dialog_authenticated_confirmation
                }
            STATE_ERROR,
            STATE_HELP -> R.string.biometric_dialog_try_again
            else -> null
        }
        return if (id != null) context.getString(id) else null
    }

    protected open fun shouldAnimateIconViewForTransition(
            oldState: BiometricState,
            newState: BiometricState
    ) = when (newState) {
        STATE_HELP,
        STATE_ERROR -> true
        STATE_AUTHENTICATING_ANIMATING_IN,
        STATE_AUTHENTICATING -> oldState == STATE_ERROR || oldState == STATE_HELP
        STATE_AUTHENTICATED -> true
        else -> false
    }

    private fun shouldAnimateSfpsIconViewForTransition(
            oldState: BiometricState,
            newState: BiometricState
    ) = when (newState) {
        STATE_HELP,
        STATE_ERROR -> true
        STATE_AUTHENTICATING_ANIMATING_IN,
        STATE_AUTHENTICATING ->
            oldState == STATE_ERROR || oldState == STATE_HELP || oldState == STATE_IDLE
        STATE_AUTHENTICATED -> true
        else -> false
    }

    protected open fun shouldAnimateIconViewOverlayForTransition(
            oldState: BiometricState,
            newState: BiometricState
    ) = when (newState) {
        STATE_HELP,
        STATE_ERROR -> true
        STATE_AUTHENTICATING_ANIMATING_IN,
        STATE_AUTHENTICATING -> oldState == STATE_ERROR || oldState == STATE_HELP
        STATE_AUTHENTICATED -> true
        else -> false
    }

    @RawRes
    protected open fun getAnimationForTransition(
            oldState: BiometricState,
            newState: BiometricState
    ): Int? {
        val id = when (newState) {
            STATE_HELP,
            STATE_ERROR -> {
                R.raw.fingerprint_dialogue_fingerprint_to_error_lottie
            }
            STATE_AUTHENTICATING_ANIMATING_IN,
            STATE_AUTHENTICATING -> {
                if (oldState == STATE_ERROR || oldState == STATE_HELP) {
                    R.raw.fingerprint_dialogue_error_to_fingerprint_lottie
                } else {
                    R.raw.fingerprint_dialogue_fingerprint_to_error_lottie
                }
            }
            STATE_AUTHENTICATED -> {
                if (oldState == STATE_ERROR || oldState == STATE_HELP) {
                    R.raw.fingerprint_dialogue_error_to_success_lottie
                } else {
                    R.raw.fingerprint_dialogue_fingerprint_to_success_lottie
                }
            }
            else -> return null
        }
        return if (id != null) return id else null
    }

    private fun getRotationFromDefault(rotation: Int): Int =
            if (isReverseDefaultRotation) (rotation + 1) % 4 else rotation

    @RawRes
    private fun getSideFpsOverlayAnimationForTransition(
            oldState: BiometricState,
            newState: BiometricState,
            rotation: Int
    ): Int? = when (newState) {
        STATE_HELP,
        STATE_ERROR -> {
            when (rotation) {
                Surface.ROTATION_0 -> R.raw.biometricprompt_fingerprint_to_error_landscape
                Surface.ROTATION_90 ->
                    R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_topleft
                Surface.ROTATION_180 ->
                    R.raw.biometricprompt_fingerprint_to_error_landscape
                Surface.ROTATION_270 ->
                    R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_bottomright
                else -> R.raw.biometricprompt_fingerprint_to_error_landscape
            }
        }
        STATE_AUTHENTICATING_ANIMATING_IN,
        STATE_AUTHENTICATING -> {
            if (oldState == STATE_ERROR || oldState == STATE_HELP) {
                when (rotation) {
                    Surface.ROTATION_0 ->
                        R.raw.biometricprompt_symbol_error_to_fingerprint_landscape
                    Surface.ROTATION_90 ->
                        R.raw.biometricprompt_symbol_error_to_fingerprint_portrait_topleft
                    Surface.ROTATION_180 ->
                        R.raw.biometricprompt_symbol_error_to_fingerprint_landscape
                    Surface.ROTATION_270 ->
                        R.raw.biometricprompt_symbol_error_to_fingerprint_portrait_bottomright
                    else -> R.raw.biometricprompt_symbol_error_to_fingerprint_landscape
                }
            } else {
                when (rotation) {
                    Surface.ROTATION_0 -> R.raw.biometricprompt_fingerprint_to_error_landscape
                    Surface.ROTATION_90 ->
                        R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_topleft
                    Surface.ROTATION_180 ->
                        R.raw.biometricprompt_fingerprint_to_error_landscape
                    Surface.ROTATION_270 ->
                        R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_bottomright
                    else -> R.raw.biometricprompt_fingerprint_to_error_landscape
                }
            }
        }
        STATE_AUTHENTICATED -> {
            if (oldState == STATE_ERROR || oldState == STATE_HELP) {
                when (rotation) {
                    Surface.ROTATION_0 ->
                        R.raw.biometricprompt_symbol_error_to_success_landscape
                    Surface.ROTATION_90 ->
                        R.raw.biometricprompt_symbol_error_to_success_portrait_topleft
                    Surface.ROTATION_180 ->
                        R.raw.biometricprompt_symbol_error_to_success_landscape
                    Surface.ROTATION_270 ->
                        R.raw.biometricprompt_symbol_error_to_success_portrait_bottomright
                    else -> R.raw.biometricprompt_symbol_error_to_success_landscape
                }
            } else {
                when (rotation) {
                    Surface.ROTATION_0 ->
                        R.raw.biometricprompt_symbol_fingerprint_to_success_landscape
                    Surface.ROTATION_90 ->
                        R.raw.biometricprompt_symbol_fingerprint_to_success_portrait_topleft
                    Surface.ROTATION_180 ->
                        R.raw.biometricprompt_symbol_fingerprint_to_success_landscape
                    Surface.ROTATION_270 ->
                        R.raw.biometricprompt_symbol_fingerprint_to_success_portrait_bottomright
                    else -> R.raw.biometricprompt_symbol_fingerprint_to_success_landscape
                }
            }
        }
        else -> null
    }

    private fun preloadAssets(context: Context) {
        if (isSideFps) {
            cacheLottieAssetsInContext(
                context,
                R.raw.biometricprompt_fingerprint_to_error_landscape,
                R.raw.biometricprompt_folded_base_bottomright,
                R.raw.biometricprompt_folded_base_default,
                R.raw.biometricprompt_folded_base_topleft,
                R.raw.biometricprompt_landscape_base,
                R.raw.biometricprompt_portrait_base_bottomright,
                R.raw.biometricprompt_portrait_base_topleft,
                R.raw.biometricprompt_symbol_error_to_fingerprint_landscape,
                R.raw.biometricprompt_symbol_error_to_fingerprint_portrait_bottomright,
                R.raw.biometricprompt_symbol_error_to_fingerprint_portrait_topleft,
                R.raw.biometricprompt_symbol_error_to_success_landscape,
                R.raw.biometricprompt_symbol_error_to_success_portrait_bottomright,
                R.raw.biometricprompt_symbol_error_to_success_portrait_topleft,
                R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_bottomright,
                R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_topleft,
                R.raw.biometricprompt_symbol_fingerprint_to_success_landscape,
                R.raw.biometricprompt_symbol_fingerprint_to_success_portrait_bottomright,
                R.raw.biometricprompt_symbol_fingerprint_to_success_portrait_topleft
            )
        } else {
            cacheLottieAssetsInContext(
                context,
                R.raw.fingerprint_dialogue_error_to_fingerprint_lottie,
                R.raw.fingerprint_dialogue_error_to_success_lottie,
                R.raw.fingerprint_dialogue_fingerprint_to_error_lottie,
                R.raw.fingerprint_dialogue_fingerprint_to_success_lottie
            )
        }
    }
}
+0 −100

File deleted.

Preview size limit exceeded, changes collapsed.

Loading