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

Commit 188b2bdd authored by Grace Cheng's avatar Grace Cheng
Browse files

Fix race condition between PromptIconViewBinder and BiometricSizeBinder

Sets BiometricPrompt view invisible until all resizing is complete. Adds
new MutableStateFlow updated on iconView onCompositionLoadedListener,
and combines this new flow with the BiometricSizeBinder viewModel.size
collect call, to force the resizing logic to wait for the iconAsset to
update first to account for it in the prompt size. Reworking of
ag/25666425 to avoid CTS failures the original CL caused

Flag: NONE
Fixes: 313605043
Bug: 317291041
Test: atest CtsBiometricsTestCases
Test: (manual) manually verified prompt appears only once and in the correct size, no resizing visible to the user
Change-Id: Ic3cbf1785737c19c5434030d45a73b2435328a71
parent ae34ea14
Loading
Loading
Loading
Loading
+8 −1
Original line number Diff line number Diff line
@@ -77,6 +77,13 @@ object BiometricViewBinder {
        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
         */
        view.visibility = View.INVISIBLE
        val accessibilityManager = view.context.getSystemService(AccessibilityManager::class.java)!!

        val textColorError =
@@ -102,7 +109,7 @@ object BiometricViewBinder {
            iconView,
            iconOverlayView,
            view.getUpdatedFingerprintAffordanceSize(),
            viewModel.iconViewModel
            viewModel
        )

        val indicatorMessageView = view.requireViewById<TextView>(R.id.indicator)
+18 −2
Original line number Diff line number Diff line
@@ -30,7 +30,6 @@ import androidx.core.animation.addListener
import androidx.core.view.doOnLayout
import androidx.core.view.isGone
import androidx.lifecycle.lifecycleScope
import com.android.systemui.res.R
import com.android.systemui.biometrics.AuthPanelController
import com.android.systemui.biometrics.Utils
import com.android.systemui.biometrics.ui.BiometricPromptLayout
@@ -41,6 +40,8 @@ import com.android.systemui.biometrics.ui.viewmodel.isMedium
import com.android.systemui.biometrics.ui.viewmodel.isNullOrNotSmall
import com.android.systemui.biometrics.ui.viewmodel.isSmall
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.res.R
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.launch

/** Helper for [BiometricViewBinder] to handle resize transitions. */
@@ -93,7 +94,20 @@ object BiometricViewSizeBinder {
            view.repeatWhenAttached {
                var currentSize: PromptSize? = null
                lifecycleScope.launch {
                    viewModel.size.collect { size ->
                    /**
                     * 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 isIconViewLoaded once constraint
                     *   layout is implemented
                     */
                    combine(viewModel.isIconViewLoaded, viewModel.size, ::Pair).collect {
                        (isIconViewLoaded, size) ->
                        if (!isIconViewLoaded) {
                            return@collect
                        }

                        // prepare for animated size transitions
                        for (v in viewsToHideWhenSmall) {
                            v.showTextOrHide(forceHide = size.isSmall)
@@ -198,6 +212,8 @@ object BiometricViewSizeBinder {
                            }

                            currentSize = size
                            view.visibility = View.VISIBLE
                            viewModel.setIsIconViewLoaded(false)
                            notifyAccessibilityChanged()
                        }
                    }
+36 −14
Original line number Diff line number Diff line
@@ -26,6 +26,7 @@ import com.airbnb.lottie.LottieAnimationView
import com.android.settingslib.widget.LottieColorUtils
import com.android.systemui.biometrics.ui.viewmodel.PromptIconViewModel
import com.android.systemui.biometrics.ui.viewmodel.PromptIconViewModel.AuthType
import com.android.systemui.biometrics.ui.viewmodel.PromptViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.util.kotlin.Utils.Companion.toQuad
import com.android.systemui.util.kotlin.Utils.Companion.toQuint
@@ -45,8 +46,9 @@ object PromptIconViewBinder {
        iconView: LottieAnimationView,
        iconOverlayView: LottieAnimationView,
        iconViewLayoutParamSizeOverride: Pair<Int, Int>?,
        viewModel: PromptIconViewModel
        promptViewModel: PromptViewModel
    ) {
        val viewModel = promptViewModel.iconViewModel
        iconView.repeatWhenAttached {
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                viewModel.onConfigurationChanged(iconView.context.resources.configuration)
@@ -71,25 +73,45 @@ object PromptIconViewBinder {
                    }

                launch {
                    var width: Int
                    var height: Int
                    viewModel.activeAuthType.collect { activeAuthType ->
                        if (iconViewLayoutParamSizeOverride == null) {
                            val width: Int
                            val height: Int
                        when (activeAuthType) {
                            AuthType.Fingerprint,
                            AuthType.Coex -> {
                                width = viewModel.fingerprintIconWidth
                                height = viewModel.fingerprintIconHeight

                                /**
                                 * 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
                                 */
                                iconView.removeAllLottieOnCompositionLoadedListener()
                                iconView.addLottieOnCompositionLoadedListener {
                                    promptViewModel.setIsIconViewLoaded(true)
                                }
                            }
                            AuthType.Face -> {
                                width = viewModel.faceIconWidth
                                height = viewModel.faceIconHeight
                                /**
                                 * Set to true by default since face icon is a drawable, which
                                 * doesn't have a LottieOnCompositionLoadedListener equivalent.
                                 *
                                 * TODO(b/318569643): To be updated once face assets are updated
                                 *   from drawables
                                 */
                                promptViewModel.setIsIconViewLoaded(true)
                            }
                        }

                        if (iconViewLayoutParamSizeOverride == null) {
                            iconView.layoutParams.width = width
                            iconView.layoutParams.height = height

                            iconOverlayView.layoutParams.width = width
                            iconOverlayView.layoutParams.height = height
                        }
+22 −0
Original line number Diff line number Diff line
@@ -192,6 +192,28 @@ constructor(
    val iconViewModel: PromptIconViewModel =
        PromptIconViewModel(this, displayStateInteractor, promptSelectorInteractor)

    private val _isIconViewLoaded = MutableStateFlow(false)

    /**
     * For prompts with an iconView, false until the prompt's iconView animation has been loaded in
     * the view, otherwise true by default. Used for BiometricViewSizeBinder to wait for the icon
     * asset to be loaded before determining the prompt size.
     */
    val isIconViewLoaded: Flow<Boolean> =
        combine(credentialKind, _isIconViewLoaded.asStateFlow()) { credentialKind, isIconViewLoaded
            ->
            if (credentialKind is PromptKind.Biometric) {
                isIconViewLoaded
            } else {
                true
            }
        }

    // Sets whether the prompt's iconView animation has been loaded in the view yet.
    fun setIsIconViewLoaded(iconViewLoaded: Boolean) {
        _isIconViewLoaded.value = iconViewLoaded
    }

    /** Padding for prompt UI elements */
    val promptPadding: Flow<Rect> =
        combine(size, displayStateInteractor.currentRotation) { size, rotation ->