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

Commit 299449e2 authored by Shawn Lin's avatar Shawn Lin Committed by Android (Google) Code Review
Browse files

Merge "[Biometric Onboarding & Edu] Support check enrolled fingerprint" into main

parents 16cc1a1f 4bfbb878
Loading
Loading
Loading
Loading
+25 −0
Original line number Diff line number Diff line
<!--
  ~ Copyright (C) 2016 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
  -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
        android:width="24dp"
        android:height="24dp"
        android:viewportWidth="960"
        android:viewportHeight="960"
        android:tint="?android:attr/colorControlNormal">
    <path
        android:pathData="M222,760L80,618L136,562L221,647L391,477L447,534L222,760ZM222,440L80,298L136,242L221,327L391,157L447,214L222,440ZM520,680L520,600L880,600L880,680L520,680ZM520,360L520,280L880,280L880,360L520,360Z"
        android:fillColor="#000000"/>
</vector>
+47 −0
Original line number Diff line number Diff line
<?xml version="1.0" encoding="utf-8"?>
<!--
  ~ Copyright (C) 2024 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.
  -->
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <com.android.settings.biometrics.fingerprint.UdfpsCheckEnrolledView
        android:id="@+id/udfps_check_enrolled_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_alignParentTop="true"
        android:layout_centerHorizontal="true">

        <!-- Fingerprint -->
        <ImageView
            android:id="@+id/udfps_fingerprint_sensor_view"
            android:contentDescription="@string/accessibility_fingerprint_label"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />

        <TextView
            android:id="@+id/udfps_fingerprint_sensor_message"
            android:layout_marginTop="80dp"
            android:layout_below="@id/udfps_fingerprint_sensor_view"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerHorizontal="true"
            android:textSize="18sp"
            android:textColor="@android:color/white"
            android:text="@string/fingerprint_check_enroll_touch_sensor"/>

    </com.android.settings.biometrics.fingerprint.UdfpsCheckEnrolledView>
</RelativeLayout>
 No newline at end of file
+6 −1
Original line number Diff line number Diff line
@@ -938,6 +938,8 @@
    <string name="security_settings_fingerprint_settings_preferences_category">When using Fingerprint Unlock</string>
    <!-- Title shown for work menu item that launches fingerprint settings or enrollment [CHAR LIMIT=22] -->
    <string name="security_settings_work_fingerprint_preference_title">Fingerprint for work</string>
    <!-- Preference to check enrolled fingerprints -->
    <string name="fingerprint_check_enrolled_title">Check enrolled fingerprints</string>
    <!-- Preference to add another fingerprint -->
    <string name="fingerprint_add_title">Add fingerprint</string>
    <!-- Message showing the current number of fingerprints set up. Shown for a menu item that launches fingerprint settings or enrollment. -->
@@ -991,7 +993,10 @@
    <string name="security_settings_fingerprint_v2_enroll_introduction_footer_message_consent_6">For best results, use a screen protector that\u2019s Made for Google certified. With other screen protectors, your child\u2019s fingerprint may not work.</string>
    <!-- Introduction detail message shown in fingerprint enrollment introduction to learn more about fingerprint [CHAR LIMIT=NONE]-->
    <string name="security_settings_fingerprint_v2_enroll_introduction_message_learn_more"></string>
    <!-- Description shown on the check enrolled fingerprint dialog -->
    <string name="fingerprint_check_enroll_touch_sensor">Touch the fingerprint sensor</string>
    <!-- Description shown on the check enrolled fingerprint dialog -->
    <string name="fingerprint_check_enroll_not_recognized">Fingerprint not recognized</string>
    <!-- Watch unlock enrollment and settings --><skip />
    <!-- Title shown for menu item that launches watch unlock settings. [CHAR LIMIT=40] -->
    <string name ="security_settings_activeunlock_preference_title">Watch Unlock</string>
+151 −0
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ import static android.app.admin.DevicePolicyResources.Strings.Settings.FINGERPRI
import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_FINGERPRINT_LAST_DELETE_MESSAGE;
import static android.app.admin.DevicePolicyResources.UNDEFINED;
import static android.hardware.biometrics.Flags.screenOffUnlockUdfps;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;

import static com.android.settings.Utils.SETTINGS_PACKAGE_NAME;
import static com.android.settings.Utils.isPrivateProfile;
@@ -40,6 +41,7 @@ import android.hardware.fingerprint.Fingerprint;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.Handler;
import android.os.UserHandle;
import android.os.UserManager;
@@ -49,8 +51,15 @@ import android.text.InputFilter;
import android.text.Spanned;
import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowInsets;
import android.view.WindowInsetsController;
import android.view.WindowManager;
import android.widget.ImeAwareEditText;
import android.widget.TextView;
import android.widget.Toast;

import androidx.annotation.NonNull;
@@ -76,6 +85,7 @@ import com.android.settings.biometrics.IdentityCheckBiometricErrorDialog;
import com.android.settings.core.SettingsBaseActivity;
import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.flags.Flags;
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.password.ChooseLockGeneric;
import com.android.settings.password.ChooseLockSettingsHelper;
@@ -235,6 +245,9 @@ public class FingerprintSettings extends SubSettings {

        private static final String TAG = "FingerprintSettings";
        private static final String KEY_FINGERPRINT_ITEM_PREFIX = "key_fingerprint_item";

        private static final String KEY_FINGERPRINT_CHECK_ENROLLED =
                "key_fingerprint_check_enrolled";
        @VisibleForTesting
        static final String KEY_FINGERPRINT_ADD = "key_fingerprint_add";
        private static final String KEY_FINGERPRINT_ENABLE_KEYGUARD_TOGGLE =
@@ -691,6 +704,17 @@ public class FingerprintSettings extends SubSettings {
                mFingerprintsEnrolledCategory.addPreference(pref);
                pref.setOnPreferenceChangeListener(this);
            }
            if (Flags.biometricsOnboardingEducation() && isUdfps() && fingerprintCount > 0) {
                // Setup check enrolled fingerprints preference
                Preference pref = new Preference(root.getContext());
                pref.setKey(KEY_FINGERPRINT_CHECK_ENROLLED);
                pref.setTitle(root.getContext().getString(
                        R.string.fingerprint_check_enrolled_title));
                pref.setIcon(R.drawable.ic_check_list_24dp);
                pref.setVisible(true);
                mFingerprintsEnrolledCategory.addPreference(pref);
                pref.setOnPreferenceChangeListener(this);
            }
            mAddFingerprintPreference = findPreference(KEY_FINGERPRINT_ADD);
            setupAddFingerprintPreference();
            return keyToReturn;
@@ -916,6 +940,8 @@ public class FingerprintSettings extends SubSettings {
                FingerprintPreference fpref = (FingerprintPreference) pref;
                final Fingerprint fp = fpref.getFingerprint();
                showRenameDialog(fp);
            } else if (KEY_FINGERPRINT_CHECK_ENROLLED.equals(key)) {
                showCheckEnrolledDialog();
            }
            return super.onPreferenceTreeClick(pref);
        }
@@ -968,6 +994,16 @@ public class FingerprintSettings extends SubSettings {
            mAuthenticateSidecar.stopAuthentication();
        }

        private void showCheckEnrolledDialog() {
            final CheckEnrolledDialog checkEnrolledDialog = new CheckEnrolledDialog();
            final Bundle args = new Bundle();
            args.putInt(CheckEnrolledDialog.KEY_USER_ID, mUserId);
            args.putParcelable(CheckEnrolledDialog.KEY_SENSOR_PROPERTIES, mSensorProperties.get(0));
            checkEnrolledDialog.setArguments(args);
            checkEnrolledDialog.setTargetFragment(this, 0);
            checkEnrolledDialog.show(getFragmentManager(), CheckEnrolledDialog.class.getName());
        }

        @Override
        public boolean onPreferenceChange(Preference preference, Object value) {
            boolean result = true;
@@ -1344,6 +1380,121 @@ public class FingerprintSettings extends SubSettings {
            return new InputFilter[]{filter};
        }

        public static class CheckEnrolledDialog extends InstrumentedDialogFragment {

            private static final String KEY_USER_ID = "user_id";
            private static final String KEY_SENSOR_PROPERTIES = "sensor_properties";
            private int mUserId;
            private @Nullable CancellationSignal mCancellationSignal;
            private @Nullable FingerprintSensorPropertiesInternal mSensorPropertiesInternal;

            @Override
            public @NonNull View onCreateView(
                    @NonNull LayoutInflater inflater,
                    @Nullable ViewGroup container,
                    @Nullable Bundle savedInstanceState) {
                return inflater.inflate(
                        R.layout.fingerprint_check_enrolled_dialog, container, false);
            }

            @Override
            public @NonNull Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
                final Dialog dialog = super.onCreateDialog(savedInstanceState);
                if (dialog != null) {
                    mUserId = getArguments().getInt(KEY_USER_ID);
                    mSensorPropertiesInternal =
                            getArguments().getParcelable(KEY_SENSOR_PROPERTIES);

                    // Remove the default dialog title bar
                    dialog.getWindow().requestFeature(Window.FEATURE_NO_TITLE);

                    dialog.setOnShowListener(dialogInterface -> {
                        final UdfpsCheckEnrolledView v =
                                dialog.findViewById(R.id.udfps_check_enrolled_view);
                        v.setSensorProperties(mSensorPropertiesInternal);
                    });
                }

                return dialog;
            }

            @Override
            public void onStart() {
                super.onStart();
                if (getDialog() == null) {
                    return;
                }

                final Dialog dialog = getDialog();
                Window window = dialog.getWindow();
                WindowManager.LayoutParams params = window.getAttributes();

                // Make the dialog fullscreen
                params.width = WindowManager.LayoutParams.MATCH_PARENT;
                params.height = WindowManager.LayoutParams.MATCH_PARENT;
                params.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
                params.setFitInsetsTypes(0);
                window.setAttributes(params);
                window.getDecorView().getWindowInsetsController().hide(
                        WindowInsets.Type.statusBars());
                window.getDecorView().getWindowInsetsController().setSystemBarsBehavior(
                        WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
                window.setBackgroundDrawableResource(android.R.color.black);

                final TextView message =
                        dialog.findViewById(R.id.udfps_fingerprint_sensor_message);
                final Vibrator vibrator = getContext().getSystemService(Vibrator.class);
                final FingerprintManager fpm = Utils.getFingerprintManagerOrNull(getContext());
                mCancellationSignal = new CancellationSignal();
                fpm.authenticate(
                        null /* crypto */,
                        mCancellationSignal,
                        new FingerprintManager.AuthenticationCallback() {
                            @Override
                            public void onAuthenticationError(
                                    int errorCode, @NonNull CharSequence errString) {
                                dialog.dismiss();
                            }

                            @Override
                            public void onAuthenticationSucceeded(
                                    @NonNull FingerprintManager.AuthenticationResult result) {
                                int fingerId = result.getFingerprint().getBiometricId();
                                FingerprintSettingsFragment parent =
                                        (FingerprintSettingsFragment) getTargetFragment();
                                parent.highlightFingerprintItem(fingerId);
                                dialog.dismiss();
                            }

                            @Override
                            public void onAuthenticationFailed() {
                                vibrator.vibrate(
                                        VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK));
                                message.setText(R.string.fingerprint_check_enroll_not_recognized);
                                message.postDelayed(() -> {
                                    message.setText(R.string.fingerprint_check_enroll_touch_sensor);
                                }, 2000);
                            }
                        },
                        null /* handler */,
                        mUserId);
            }

            @Override
            public void onDismiss(@NonNull DialogInterface dialog) {
                super.onDismiss(dialog);
                if (mCancellationSignal != null) {
                    mCancellationSignal.cancel();
                    mCancellationSignal = null;
                }
            }

            @Override
            public int getMetricsCategory() {
                return 0;
            }
        }

        public static class RenameDialog extends InstrumentedDialogFragment {

            private Fingerprint mFp;
+186 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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.settings.biometrics.fingerprint;

import android.content.Context;
import android.graphics.Rect;
import android.graphics.RectF;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.util.AttributeSet;
import android.util.Log;
import android.util.RotationUtils;
import android.view.DisplayInfo;
import android.view.Surface;
import android.widget.ImageView;
import android.widget.RelativeLayout;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.android.settings.R;
import com.android.systemui.biometrics.UdfpsUtils;
import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams;

/**
 * View corresponding with fingerprint_check_enrolled_dialog.xml
 */
public class UdfpsCheckEnrolledView extends RelativeLayout {
    private static final String TAG = "UdfpsCheckEnrolledView";
    @NonNull
    private final UdfpsFingerprintDrawable mFingerprintDrawable;
    private ImageView mFingerprintView;
    private UdfpsUtils mUdfpsUtils;

    private @Nullable Rect mSensorRect;
    private @Nullable UdfpsOverlayParams mOverlayParams;
    private @Nullable FingerprintSensorPropertiesInternal mSensorProperties;


    public UdfpsCheckEnrolledView(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        mFingerprintDrawable = new UdfpsFingerprintDrawable(mContext, attrs);
        mUdfpsUtils = new UdfpsUtils();
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        mFingerprintView = findViewById(R.id.udfps_fingerprint_sensor_view);
        mFingerprintView.setImageDrawable(mFingerprintDrawable);
    }

    /**
     * setup SensorProperties
     */
    public void setSensorProperties(@Nullable FingerprintSensorPropertiesInternal properties) {
        mSensorProperties = properties;
        updateOverlayParams();
    }

    private void onSensorRectUpdated() {
        updateDimensions();

        if (mSensorRect == null || mOverlayParams == null) {
            Log.e(TAG, "Fail to onSensorRectUpdated, mSensorRect/mOverlayParams null");
            return;
        }

        // Updates sensor rect in relation to the overlay view
        mSensorRect.set(0, 0,
                mOverlayParams.getSensorBounds().width(),
                mOverlayParams.getSensorBounds().height());
        mFingerprintDrawable.onSensorRectUpdated(new RectF(mSensorRect));
    }

    private void updateDimensions() {
        if (mOverlayParams == null) {
            Log.e(TAG, "Fail to updateDimensions for " + this + ", mOverlayParams null");
            return;
        }
        // Original sensorBounds assume portrait mode.
        final Rect rotatedBounds = new Rect(mOverlayParams.getSensorBounds());
        int rotation = mOverlayParams.getRotation();
        if (rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270) {
            RotationUtils.rotateBounds(
                    rotatedBounds,
                    mOverlayParams.getNaturalDisplayWidth(),
                    mOverlayParams.getNaturalDisplayHeight(),
                    rotation
            );
        }

        RelativeLayout parent = ((RelativeLayout) getParent());
        if (parent == null) {
            Log.e(TAG, "Fail to updateDimensions for " + this + ", parent null");
            return;
        }
        final int[] coords = parent.getLocationOnScreen();
        final int parentLeft = coords[0];
        final int parentTop = coords[1];
        final int parentRight = parentLeft + parent.getWidth();

        // Update container view LayoutParams
        RelativeLayout.LayoutParams checkEnrolledViewLp =
                new RelativeLayout.LayoutParams(getWidth(), getHeight());
        checkEnrolledViewLp.addRule(RelativeLayout.ALIGN_PARENT_TOP);
        if (rotation == Surface.ROTATION_90) {
            checkEnrolledViewLp.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
            checkEnrolledViewLp.width =
                    rotatedBounds.width() + 2 * (parentRight - rotatedBounds.right);
        } else {
            checkEnrolledViewLp.addRule(RelativeLayout.ALIGN_PARENT_LEFT);
            checkEnrolledViewLp.width = rotatedBounds.width() + 2 * rotatedBounds.left;
        }
        setLayoutParams(checkEnrolledViewLp);

        // Update fingerprint view LayoutParams
        RelativeLayout.LayoutParams fingerprintViewLp = new RelativeLayout.LayoutParams(
                rotatedBounds.width(), rotatedBounds.height());
        fingerprintViewLp.addRule(RelativeLayout.ALIGN_PARENT_TOP);
        fingerprintViewLp.topMargin = rotatedBounds.top - parentTop;
        if (rotation == Surface.ROTATION_90) {
            fingerprintViewLp.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
            fingerprintViewLp.rightMargin = parentRight - rotatedBounds.right;
        } else {
            fingerprintViewLp.addRule(RelativeLayout.ALIGN_PARENT_LEFT);
            fingerprintViewLp.leftMargin = rotatedBounds.left - parentLeft;
        }
        mFingerprintView.setLayoutParams(fingerprintViewLp);
    }

    private void updateOverlayParams() {

        if (mSensorProperties == null) {
            android.util.Log.e(TAG, "There is no sensor info!");
            return;
        }

        DisplayInfo displayInfo = new DisplayInfo();
        if (getDisplay() == null) {
            android.util.Log.e(TAG, "Can not get display");
            return;
        }
        getDisplay().getDisplayInfo(displayInfo);
        Rect udfpsBounds = mSensorProperties.getLocation().getRect();
        float scaleFactor = mUdfpsUtils.getScaleFactor(displayInfo);
        udfpsBounds.scale(scaleFactor);

        final Rect overlayBounds = new Rect(
                0, /* left */
                displayInfo.getNaturalHeight() / 2, /* top */
                displayInfo.getNaturalWidth(), /* right */
                displayInfo.getNaturalHeight() /* botom */);

        mOverlayParams = new UdfpsOverlayParams(
                udfpsBounds,
                overlayBounds,
                displayInfo.getNaturalWidth(),
                displayInfo.getNaturalHeight(),
                scaleFactor,
                displayInfo.rotation,
                mSensorProperties.sensorType);

        post(() -> {
            if (mOverlayParams == null) {
                Log.e(TAG, "Fail to updateOverlayParams, mOverlayParams null");
                return;
            }
            mSensorRect = new Rect(mOverlayParams.getSensorBounds());
            onSensorRectUpdated();
        });
    }
}
Loading