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

Commit 6ad4493f authored by Austin Delgado's avatar Austin Delgado Committed by Android (Google) Code Review
Browse files

Merge "Add Biometric Prompt ConstraintLayout" into main

parents 9e45427b f8b10452
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