/* * 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.util.Log 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.compose.ui.platform.ComposeView import androidx.core.view.AccessibilityDelegateCompat import androidx.core.view.ViewCompat import androidx.core.view.accessibility.AccessibilityNodeInfoCompat import androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat 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.app.tracing.coroutines.launchTraced as launch import com.android.compose.theme.PlatformTheme import com.android.systemui.biometrics.BiometricAuthIconAssets import com.android.systemui.biometrics.Utils.ellipsize 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.NegativeButtonState import com.android.systemui.biometrics.ui.PositiveButtonState import com.android.systemui.biometrics.ui.PromptSize import com.android.systemui.biometrics.ui.view.BiometricPromptFallbackView import com.android.systemui.biometrics.ui.viewmodel.FingerprintStartMode import com.android.systemui.biometrics.ui.viewmodel.PromptFallbackViewModel import com.android.systemui.biometrics.ui.viewmodel.PromptMessage import com.android.systemui.biometrics.ui.viewmodel.PromptViewModel import com.android.systemui.common.ui.view.onTouchListener import com.android.systemui.deviceentry.ui.binder.UdfpsAccessibilityOverlayBinder import com.android.systemui.deviceentry.ui.view.UdfpsAccessibilityOverlay import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.res.R import com.android.systemui.statusbar.VibratorHelper import com.google.android.msdl.domain.MSDLPlayer import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.delay import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map private const val TAG = "BiometricViewBinder" /** Top-most view binder for BiometricPrompt views. */ object BiometricViewBinder { const val MAX_LOGO_DESCRIPTION_CHARACTER_NUMBER = 30 /** Binds a Biometric Prompt View to a [PromptViewModel]. */ @SuppressLint("ClickableViewAccessibility") @JvmStatic fun bind( view: View, viewModel: PromptViewModel, jankListener: BiometricJankListener, backgroundView: View, legacyCallback: Spaghetti.Callback, applicationScope: CoroutineScope, vibratorHelper: VibratorHelper, msdlPlayer: MSDLPlayer, promptFallbackViewModelFactory: PromptFallbackViewModel.Factory, ): Spaghetti { val accessibilityManager = view.context.getSystemService(AccessibilityManager::class.java)!! val textColorError = view.resources.getColor(R.color.biometric_dialog_error, view.context.theme) val attributes = view.context.obtainStyledAttributes( R.style.TextAppearance_AuthCredential_Indicator, intArrayOf(android.R.attr.textColor), ) val textColorHint = attributes.getColor(0, 0) attributes.recycle() 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 = view.requireViewById( R.id.biometric_prompt_udfps_accessibility_overlay ) // set selected to enable marquee unless a screen reader is enabled titleView.isSelected = !accessibilityManager.isEnabled || !accessibilityManager.isTouchExplorationEnabled subtitleView.isSelected = !accessibilityManager.isEnabled || !accessibilityManager.isTouchExplorationEnabled val iconView = view.requireViewById(R.id.biometric_icon) val indicatorMessageView = view.requireViewById(R.id.indicator) // Negative-side (left) buttons val negativeButton = view.requireViewById