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

Commit 0d1ba636 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.

Flag: NONE
Fixes: 313605043
Test: (manual) manually verified prompt appears only once and in the correct size, no resizing visible to the user
Change-Id: I6b25acbf779c7f2b80b4f2fe8693c6021dbf2903
parent 8a83c56f
Loading
Loading
Loading
Loading
+7 −0
Original line number Original line Diff line number Diff line
@@ -403,6 +403,13 @@ public class AuthContainerView extends LinearLayout


            final BiometricPromptLayout view = (BiometricPromptLayout) layoutInflater.inflate(
            final BiometricPromptLayout view = (BiometricPromptLayout) layoutInflater.inflate(
                    R.layout.biometric_prompt_layout, null, false);
                    R.layout.biometric_prompt_layout, null, false);
            /**
             * 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.setVisibility(View.INVISIBLE);
            mBiometricView = BiometricViewBinder.bind(view, viewModel, mPanelController,
            mBiometricView = BiometricViewBinder.bind(view, viewModel, mPanelController,
                    // TODO(b/201510778): This uses the wrong timeout in some cases
                    // TODO(b/201510778): This uses the wrong timeout in some cases
                    getJankListener(view, TRANSIT,
                    getJankListener(view, TRANSIT,
+7 −1
Original line number Original line Diff line number Diff line
@@ -97,7 +97,13 @@ object BiometricViewBinder {


        val iconOverlayView = view.requireViewById<LottieAnimationView>(R.id.biometric_icon_overlay)
        val iconOverlayView = view.requireViewById<LottieAnimationView>(R.id.biometric_icon_overlay)
        val iconView = view.requireViewById<LottieAnimationView>(R.id.biometric_icon)
        val iconView = view.requireViewById<LottieAnimationView>(R.id.biometric_icon)

        /**
         * 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.addLottieOnCompositionLoadedListener { viewModel.setIsIconViewLoaded(true) }
        PromptIconViewBinder.bind(
        PromptIconViewBinder.bind(
            iconView,
            iconView,
            iconOverlayView,
            iconOverlayView,
+19 −3
Original line number Original line Diff line number Diff line
@@ -30,7 +30,6 @@ import androidx.core.animation.addListener
import androidx.core.view.doOnLayout
import androidx.core.view.doOnLayout
import androidx.core.view.isGone
import androidx.core.view.isGone
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.lifecycleScope
import com.android.systemui.res.R
import com.android.systemui.biometrics.AuthPanelController
import com.android.systemui.biometrics.AuthPanelController
import com.android.systemui.biometrics.Utils
import com.android.systemui.biometrics.Utils
import com.android.systemui.biometrics.ui.BiometricPromptLayout
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.isNullOrNotSmall
import com.android.systemui.biometrics.ui.viewmodel.isSmall
import com.android.systemui.biometrics.ui.viewmodel.isSmall
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.res.R
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.launch
import kotlinx.coroutines.launch


/** Helper for [BiometricViewBinder] to handle resize transitions. */
/** Helper for [BiometricViewBinder] to handle resize transitions. */
@@ -92,8 +93,22 @@ object BiometricViewSizeBinder {
            // TODO(b/251476085): migrate the legacy panel controller and simplify this
            // TODO(b/251476085): migrate the legacy panel controller and simplify this
            view.repeatWhenAttached {
            view.repeatWhenAttached {
                var currentSize: PromptSize? = null
                var currentSize: PromptSize? = null

                lifecycleScope.launch {
                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
                        // prepare for animated size transitions
                        for (v in viewsToHideWhenSmall) {
                        for (v in viewsToHideWhenSmall) {
                            v.showTextOrHide(forceHide = size.isSmall)
                            v.showTextOrHide(forceHide = size.isSmall)
@@ -196,8 +211,9 @@ object BiometricViewSizeBinder {
                                    }
                                    }
                                }
                                }
                            }
                            }

                            currentSize = size
                            currentSize = size
                            view.visibility = View.VISIBLE
                            viewModel.setIsIconViewLoaded(false)
                            notifyAccessibilityChanged()
                            notifyAccessibilityChanged()
                        }
                        }
                    }
                    }
+22 −0
Original line number Original line Diff line number Diff line
@@ -192,6 +192,28 @@ constructor(
    val iconViewModel: PromptIconViewModel =
    val iconViewModel: PromptIconViewModel =
        PromptIconViewModel(this, displayStateInteractor, promptSelectorInteractor)
        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 */
    /** Padding for prompt UI elements */
    val promptPadding: Flow<Rect> =
    val promptPadding: Flow<Rect> =
        combine(size, displayStateInteractor.currentRotation) { size, rotation ->
        combine(size, displayStateInteractor.currentRotation) { size, rotation ->