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

Commit f8b10452 authored by Austin Delgado's avatar Austin Delgado
Browse files

Add Biometric Prompt ConstraintLayout

Test: atest com.android.systemui.biometrics
Bug: 288175072
Flag: ACONFIG constraint_bp DEVELOPMENT
Change-Id: I69123e491303c3862288bd180eb812efc0d84a37
parent 028d0c4a
Loading
Loading
Loading
Loading
+244 −0
Original line number Diff line number Diff line
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">

    <ImageView
        android:id="@+id/logo"
        android:layout_width="@dimen/biometric_auth_icon_size"
        android:layout_height="@dimen/biometric_auth_icon_size"
        android:layout_gravity="center"
        android:scaleType="fitXY"
        android:visibility="gone" />

    <ImageView
        android:id="@+id/background"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:contentDescription="@string/biometric_dialog_empty_space_description"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <View
        android:id="@+id/panel"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:background="?android:attr/colorBackgroundFloating"
        android:clickable="true"
        android:clipToOutline="true"
        android:importantForAccessibility="no"
        android:paddingHorizontal="16dp"
        android:paddingVertical="16dp"
        android:visibility="visible"
        app:layout_constraintBottom_toTopOf="@+id/bottomGuideline"
        app:layout_constraintEnd_toStartOf="@+id/rightGuideline"
        app:layout_constraintStart_toStartOf="@+id/leftGuideline"
        app:layout_constraintTop_toTopOf="@+id/title" />

    <com.android.systemui.biometrics.BiometricPromptLottieViewWrapper
        android:id="@+id/biometric_icon"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.8"
        tools:srcCompat="@tools:sample/avatars" />

    <com.android.systemui.biometrics.BiometricPromptLottieViewWrapper
        android:id="@+id/biometric_icon_overlay"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_gravity="center"
        android:contentDescription="@null"
        android:scaleType="fitXY"
        app:layout_constraintBottom_toBottomOf="@+id/biometric_icon"
        app:layout_constraintEnd_toEndOf="@+id/biometric_icon"
        app:layout_constraintHorizontal_bias="1.0"
        app:layout_constraintStart_toStartOf="@+id/biometric_icon"
        app:layout_constraintTop_toTopOf="@+id/biometric_icon"
        app:layout_constraintVertical_bias="0.0" />

    <TextView
        android:id="@+id/title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:gravity="@integer/biometric_dialog_text_gravity"
        android:singleLine="true"
        android:marqueeRepeatLimit="1"
        android:ellipsize="marquee"
        style="@style/TextAppearance.AuthCredential.Title"
        app:layout_constraintBottom_toTopOf="@+id/subtitle"
        app:layout_constraintEnd_toEndOf="@+id/panel"
        app:layout_constraintStart_toStartOf="@+id/panel" />

    <TextView
        android:id="@+id/subtitle"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:gravity="@integer/biometric_dialog_text_gravity"
        android:singleLine="true"
        android:marqueeRepeatLimit="1"
        android:ellipsize="marquee"
        style="@style/TextAppearance.AuthCredential.Subtitle"
        app:layout_constraintBottom_toTopOf="@+id/description"
        app:layout_constraintEnd_toEndOf="@+id/panel"
        app:layout_constraintStart_toStartOf="@+id/panel" />

    <Space
        android:id="@+id/space_above_content"
        android:layout_width="match_parent"
        android:layout_height="@dimen/biometric_prompt_space_above_content"
        android:visibility="gone"
        app:layout_constraintTop_toBottomOf="@+id/subtitle"
        app:layout_constraintEnd_toEndOf="@+id/panel"
        app:layout_constraintStart_toStartOf="@+id/panel"/>

    <ScrollView
        android:id="@+id/customized_view_container"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:fillViewport="true"
        android:fadeScrollbars="false"
        android:gravity="center_vertical"
        android:orientation="vertical"
        android:paddingHorizontal="@dimen/biometric_prompt_content_container_padding_horizontal"
        android:scrollbars="vertical"
        android:visibility="gone"
        app:layout_constraintTop_toBottomOf="@+id/space_above_content"
        app:layout_constraintBottom_toTopOf="@+id/biometric_icon"
        app:layout_constraintEnd_toEndOf="@+id/panel"
        app:layout_constraintStart_toStartOf="@+id/panel"/>

    <TextView
        android:id="@+id/description"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="24dp"
        android:scrollbars="vertical"
        android:gravity="@integer/biometric_dialog_text_gravity"
        style="@style/TextAppearance.AuthCredential.Description"
        app:layout_constraintBottom_toTopOf="@+id/biometric_icon"
        app:layout_constraintEnd_toEndOf="@+id/panel"
        app:layout_constraintStart_toStartOf="@+id/panel" />

    <TextView
        android:id="@+id/indicator"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp"
        android:gravity="center_horizontal"
        android:textColor="@color/biometric_dialog_gray"
        android:textSize="12sp"
        android:accessibilityLiveRegion="polite"
        android:marqueeRepeatLimit="marquee_forever"
        android:scrollHorizontally="true"
        android:fadingEdge="horizontal"
        app:layout_constraintEnd_toEndOf="@+id/panel"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="@+id/panel"
        app:layout_constraintTop_toBottomOf="@+id/biometric_icon" />

    <!-- Negative Button, reserved for app -->
    <Button
        android:id="@+id/button_negative"
        style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical"
        android:layout_marginBottom="8dp"
        android:layout_marginLeft="8dp"
        android:ellipsize="end"
        android:maxLines="2"
        android:visibility="invisible"
        app:layout_constraintBottom_toBottomOf="@+id/panel"
        app:layout_constraintStart_toStartOf="@+id/panel" />

    <!-- Cancel Button, replaces negative button when biometric is accepted -->
    <Button
        android:id="@+id/button_cancel"
        style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical"
        android:layout_marginBottom="8dp"
        android:layout_marginLeft="8dp"
        android:text="@string/cancel"
        android:visibility="invisible"
        app:layout_constraintBottom_toBottomOf="@+id/panel"
        app:layout_constraintStart_toStartOf="@+id/panel" />

    <!-- "Use Credential" Button, replaces if device credential is allowed -->
    <Button
        android:id="@+id/button_use_credential"
        style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical"
        android:layout_marginBottom="8dp"
        android:layout_marginLeft="8dp"
        android:visibility="invisible"
        app:layout_constraintBottom_toBottomOf="@+id/panel"
        app:layout_constraintStart_toStartOf="@+id/panel" />

    <!-- Positive Button -->
    <Button
        android:id="@+id/button_confirm"
        style="@*android:style/Widget.DeviceDefault.Button.Colored"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical"
        android:layout_marginBottom="8dp"
        android:layout_marginRight="8dp"
        android:ellipsize="end"
        android:maxLines="2"
        android:text="@string/biometric_dialog_confirm"
        android:visibility="invisible"
        app:layout_constraintBottom_toBottomOf="@+id/panel"
        app:layout_constraintEnd_toEndOf="@+id/panel"
        tools:visibility="invisible" />

    <!-- Try Again Button -->
    <Button
        android:id="@+id/button_try_again"
        style="@*android:style/Widget.DeviceDefault.Button.Colored"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical"
        android:layout_marginBottom="8dp"
        android:layout_marginRight="8dp"
        android:ellipsize="end"
        android:maxLines="2"
        android:text="@string/biometric_dialog_try_again"
        android:visibility="invisible"
        app:layout_constraintBottom_toBottomOf="@+id/panel"
        app:layout_constraintEnd_toEndOf="@+id/panel" />

    <!-- Guidelines for setting panel border -->
    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/leftGuideline"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        app:layout_constraintGuide_begin="@dimen/biometric_dialog_border_padding" />

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/rightGuideline"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        app:layout_constraintGuide_end="@dimen/biometric_dialog_border_padding" />

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/bottomGuideline"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintGuide_end="@dimen/biometric_dialog_border_padding" />

</androidx.constraintlayout.widget.ConstraintLayout>
+48 −25
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;

import static com.android.internal.jank.InteractionJankMonitor.CUJ_BIOMETRIC_PROMPT_TRANSITION;
import static com.android.systemui.Flags.constraintBp;

import android.animation.Animator;
import android.annotation.IntDef;
@@ -57,6 +58,7 @@ import android.widget.ScrollView;
import android.window.OnBackInvokedCallback;
import android.window.OnBackInvokedDispatcher;

import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.core.view.AccessibilityDelegateCompat;
import androidx.core.view.ViewCompat;
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
@@ -153,7 +155,7 @@ public class AuthContainerView extends LinearLayout
    @Nullable private Spaghetti mBiometricView;
    @Nullable private View mCredentialView;
    private final AuthPanelController mPanelController;
    private final FrameLayout mFrameLayout;
    private final ViewGroup mLayout;
    private final ImageView mBackgroundView;
    private final ScrollView mBiometricScrollView;
    private final View mPanelView;
@@ -339,11 +341,16 @@ public class AuthContainerView extends LinearLayout
        mBiometricCallback = new BiometricCallback();

        final LayoutInflater layoutInflater = LayoutInflater.from(mContext);
        mFrameLayout = (FrameLayout) layoutInflater.inflate(
        if (constraintBp()) {
            mLayout = (ConstraintLayout) layoutInflater.inflate(
                    R.layout.biometric_prompt_constraint_layout, this, false /* attachToRoot */);
        } else {
            mLayout = (FrameLayout) layoutInflater.inflate(
                    R.layout.auth_container_view, this, false /* attachToRoot */);
        addView(mFrameLayout);
        mBiometricScrollView = mFrameLayout.findViewById(R.id.biometric_scrollview);
        mBackgroundView = mFrameLayout.findViewById(R.id.background);
        }
        mBiometricScrollView = mLayout.findViewById(R.id.biometric_scrollview);
        addView(mLayout);
        mBackgroundView = mLayout.findViewById(R.id.background);
        ViewCompat.setAccessibilityDelegate(mBackgroundView, new AccessibilityDelegateCompat() {
            @Override
            public void onInitializeAccessibilityNodeInfo(View host,
@@ -358,7 +365,7 @@ public class AuthContainerView extends LinearLayout
            }
        });

        mPanelView = mFrameLayout.findViewById(R.id.panel);
        mPanelView = mLayout.findViewById(R.id.panel);
        mPanelController = new AuthPanelController(mContext, mPanelView);
        mBackgroundExecutor = bgExecutor;
        mInteractionJankMonitor = jankMonitor;
@@ -402,6 +409,14 @@ public class AuthContainerView extends LinearLayout
                    new BiometricModalities(fpProps, faceProps),
                    config.mOpPackageName);

            if (constraintBp()) {
                mBiometricView = BiometricViewBinder.bind(mLayout, viewModel, null,
                        // TODO(b/201510778): This uses the wrong timeout in some cases
                        getJankListener(mLayout, TRANSIT,
                                BiometricViewSizeBinder.ANIMATE_MEDIUM_TO_LARGE_DURATION_MS),
                        mBackgroundView, mBiometricCallback, mApplicationCoroutineScope,
                        vibratorHelper);
            } else {
                final BiometricPromptLayout view = (BiometricPromptLayout) layoutInflater.inflate(
                        R.layout.biometric_prompt_layout, null, false);
                mBiometricView = BiometricViewBinder.bind(view, viewModel, mPanelController,
@@ -416,6 +431,9 @@ public class AuthContainerView extends LinearLayout
                    view.setUdfpsAdapter(new UdfpsDialogMeasureAdapter(view, fpProps),
                            config.mScaleProvider);
                }
            }
        } else if (constraintBp() && Utils.isDeviceCredentialAllowed(mConfig.mPromptInfo)) {
            addCredentialView(true, false);
        } else {
            mPromptSelectorInteractorProvider.get().resetPrompt();
        }
@@ -477,7 +495,7 @@ public class AuthContainerView extends LinearLayout
        vm.setAnimateContents(animateContents);
        ((CredentialView) mCredentialView).init(vm, this, mPanelController, animatePanel);

        mFrameLayout.addView(mCredentialView);
        mLayout.addView(mCredentialView);
    }

    @Override
@@ -488,8 +506,10 @@ public class AuthContainerView extends LinearLayout

    @Override
    public void onOrientationChanged() {
        if (!constraintBp()) {
            maybeUpdatePositionForUdfps(true /* invalidate */);
        }
    }

    @Override
    public void onAttachedToWindow() {
@@ -502,8 +522,9 @@ public class AuthContainerView extends LinearLayout
        mWakefulnessLifecycle.addObserver(this);
        mPanelInteractionDetector.enable(
                () -> animateAway(AuthDialogCallback.DISMISSED_USER_CANCELED));

        if (Utils.isBiometricAllowed(mConfig.mPromptInfo)) {
        if (constraintBp()) {
            // Do nothing on attachment with constraintLayout
        } else if (Utils.isBiometricAllowed(mConfig.mPromptInfo)) {
            mBiometricScrollView.addView(mBiometricView.asView());
        } else if (Utils.isDeviceCredentialAllowed(mConfig.mPromptInfo)) {
            addCredentialView(true /* animatePanel */, false /* animateContents */);
@@ -512,7 +533,9 @@ public class AuthContainerView extends LinearLayout
                    + mConfig.mPromptInfo.getAuthenticators());
        }

        if (!constraintBp()) {
            maybeUpdatePositionForUdfps(false /* invalidate */);
        }

        if (mConfig.mSkipIntro) {
            mContainerState = STATE_SHOWING;
+12 −4
Original line number Diff line number Diff line
@@ -41,6 +41,7 @@ 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
@@ -70,9 +71,9 @@ object BiometricViewBinder {
    @SuppressLint("ClickableViewAccessibility")
    @JvmStatic
    fun bind(
        view: BiometricPromptLayout,
        view: View,
        viewModel: PromptViewModel,
        panelViewController: AuthPanelController,
        panelViewController: AuthPanelController?,
        jankListener: BiometricJankListener,
        backgroundView: View,
        legacyCallback: Spaghetti.Callback,
@@ -112,11 +113,18 @@ object BiometricViewBinder {
        val iconOverlayView = view.requireViewById<LottieAnimationView>(R.id.biometric_icon_overlay)
        val iconView = view.requireViewById<LottieAnimationView>(R.id.biometric_icon)

        val iconSizeOverride =
            if (constraintBp()) {
                viewModel.fingerprintAffordanceSize
            } else {
                (view as BiometricPromptLayout).updatedFingerprintAffordanceSize
            }

        PromptIconViewBinder.bind(
            iconView,
            iconOverlayView,
            view.getUpdatedFingerprintAffordanceSize(),
            viewModel
            iconSizeOverride,
            viewModel,
        )

        val indicatorMessageView = view.requireViewById<TextView>(R.id.indicator)
+358 −134

File changed.

Preview size limit exceeded, changes collapsed.

+22 −0
Original line number Diff line number Diff line
@@ -17,13 +17,17 @@

package com.android.systemui.biometrics.ui.binder

import android.graphics.Rect
import android.graphics.drawable.Animatable2
import android.graphics.drawable.AnimatedVectorDrawable
import android.graphics.drawable.Drawable
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.airbnb.lottie.LottieAnimationView
import com.android.settingslib.widget.LottieColorUtils
import com.android.systemui.Flags.constraintBp
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
@@ -118,6 +122,24 @@ object PromptIconViewBinder {
                    }
                }

                launch {
                    viewModel.iconPosition.collect { position ->
                        if (constraintBp() && position != Rect()) {
                            val iconParams = iconView.layoutParams as ConstraintLayout.LayoutParams

                            if (position.left != -1) {
                                iconParams.endToEnd = ConstraintSet.UNSET
                                iconParams.leftMargin = position.left
                            }
                            if (position.top != -1) {
                                iconParams.bottomToBottom = ConstraintSet.UNSET
                                iconParams.topMargin = position.top
                            }
                            iconView.layoutParams = iconParams
                        }
                    }
                }

                launch {
                    viewModel.iconAsset
                        .sample(
Loading