/* * Copyright (C) 2023 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.ui.binder import android.animation.Animator import android.annotation.SuppressLint import android.content.Context import android.hardware.biometrics.BiometricAuthenticator import android.hardware.biometrics.BiometricConstants import android.hardware.biometrics.BiometricPrompt import android.hardware.biometrics.Flags import android.hardware.face.FaceManager import android.text.method.ScrollingMovementMethod import android.util.Log import android.view.HapticFeedbackConstants import android.view.MotionEvent import android.view.View import android.view.View.IMPORTANT_FOR_ACCESSIBILITY_NO import android.view.accessibility.AccessibilityManager import android.widget.Button import android.widget.ImageView import android.widget.LinearLayout import android.widget.TextView import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle import com.airbnb.lottie.LottieAnimationView import com.airbnb.lottie.LottieCompositionFactory import com.android.systemui.Flags.constraintBp import com.android.systemui.biometrics.AuthPanelController import com.android.systemui.biometrics.shared.model.BiometricModalities import com.android.systemui.biometrics.shared.model.BiometricModality import com.android.systemui.biometrics.shared.model.PromptKind import com.android.systemui.biometrics.shared.model.asBiometricModality import com.android.systemui.biometrics.ui.BiometricPromptLayout import com.android.systemui.biometrics.ui.viewmodel.FingerprintStartMode import com.android.systemui.biometrics.ui.viewmodel.PromptMessage import com.android.systemui.biometrics.ui.viewmodel.PromptSize import com.android.systemui.biometrics.ui.viewmodel.PromptViewModel import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.res.R import com.android.systemui.statusbar.VibratorHelper import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.delay import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch private const val TAG = "BiometricViewBinder" private const val MAX_LOGO_DESCRIPTION_CHARACTER_NUMBER = 30 /** Top-most view binder for BiometricPrompt views. */ object BiometricViewBinder { /** Binds a [BiometricPromptLayout] to a [PromptViewModel]. */ @SuppressLint("ClickableViewAccessibility") @JvmStatic fun bind( view: View, viewModel: PromptViewModel, panelViewController: AuthPanelController?, jankListener: BiometricJankListener, backgroundView: View, legacyCallback: Spaghetti.Callback, applicationScope: CoroutineScope, vibratorHelper: VibratorHelper, ): Spaghetti { /** * View is only set visible in BiometricViewSizeBinder once PromptSize is determined that * accounts for iconView size, to prevent prompt resizing being visible to the user. * * TODO(b/288175072): May be able to remove this once constraint layout is implemented */ if (!constraintBp()) { view.visibility = View.INVISIBLE } val accessibilityManager = view.context.getSystemService(AccessibilityManager::class.java)!! val textColorError = view.resources.getColor(R.color.biometric_dialog_error, view.context.theme) val textColorHint = view.resources.getColor(R.color.biometric_dialog_gray, view.context.theme) val logoView = view.requireViewById(R.id.logo) val logoDescriptionView = view.requireViewById(R.id.logo_description) val titleView = view.requireViewById(R.id.title) val subtitleView = view.requireViewById(R.id.subtitle) val descriptionView = view.requireViewById(R.id.description) val customizedViewContainer = view.requireViewById(R.id.customized_view_container) val udfpsGuidanceView = if (constraintBp()) { view.requireViewById(R.id.panel) } else { backgroundView } // set selected to enable marquee unless a screen reader is enabled titleView.isSelected = !accessibilityManager.isEnabled || !accessibilityManager.isTouchExplorationEnabled subtitleView.isSelected = !accessibilityManager.isEnabled || !accessibilityManager.isTouchExplorationEnabled descriptionView.movementMethod = ScrollingMovementMethod() val iconOverlayView = view.requireViewById(R.id.biometric_icon_overlay) val iconView = view.requireViewById(R.id.biometric_icon) val iconSizeOverride = if (constraintBp()) { null } else { (view as BiometricPromptLayout).updatedFingerprintAffordanceSize } val indicatorMessageView = view.requireViewById(R.id.indicator) // Negative-side (left) buttons val negativeButton = view.requireViewById