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

Commit 8444136c authored by Diya Bera's avatar Diya Bera Committed by Android (Google) Code Review
Browse files

Merge "(1/N) Biometric error dialog" into main

parents 59ed7010 5335e26b
Loading
Loading
Loading
Loading
+54 −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.
 -->
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:paddingLeft="24dp"
    android:paddingRight="24dp">
    <ImageView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center_horizontal"
        android:src="@drawable/ic_settings_safety_center"
        android:paddingTop="24dp"/>
    <TextView
        android:id="@id/title"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center_horizontal"
        android:textColor="?android:attr/textColorPrimary"
        android:textAppearance="?android:attr/textAppearanceLarge"
        android:paddingTop="16dp"/>
    <TextView
        android:id="@+id/description_1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textAppearance="?android:attr/textAppearanceSmall"
        android:textColor="?android:attr/textColorSecondary"
        android:paddingTop="16dp"
        android:lineSpacingMultiplier="1.2"/>
    <TextView
        android:id="@+id/description_2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textAppearance="?android:attr/textAppearanceSmall"
        android:textColor="?android:attr/textColorSecondary"
        android:lineSpacingMultiplier="1.2"
        android:paddingTop="16dp"/>
</LinearLayout>
 No newline at end of file
+22 −0
Original line number Diff line number Diff line
@@ -938,6 +938,28 @@
    <string name="security_settings_fingerprint_multiple_face_watch_preference_summary">Face, fingerprints, and <xliff:g id="watch" example="Dani's Watch">%s</xliff:g> added</string>
    <!-- Description for mandatory biometrics prompt-->
    <string name="mandatory_biometrics_prompt_description">Identity Check is on and requires a biometric</string>
    <!-- Text for link to identity check settings [CHAR LIMIT=100] -->
    <string name="go_to_settings">Go to Settings</string>
    <!-- Dialog title when identity check auth is requested but biometrics is in lockout state [CHAR LIMIT=NONE] -->
    <string name="identity_check_lockout_error_title">Identity Check is on and can’t verify it’s you</string>
    <!-- Dialog title when identity check auth is requested but biometric hardware has an error. [CHAR LIMIT=NONE] -->
    <string name="identity_check_lockout_error_description_1">Biometrics failed too many times. Lock and unlock your device to retry.</string>
    <!-- Dialog message when identity check auth is requested but biometrics is in lockout state. "Go to Settings" launches theft protection settings, in case user wishes to disable the feature. [CHAR LIMIT=NONE] -->
    <string name="identity_check_lockout_error_description_2">You can manage Identity Check in theft protection settings. Go to Settings</string>
    <!-- Dialog title when identity check auth is requested but biometric hardware has an error. [CHAR LIMIT=100] -->
    <string name="identity_check_general_error_title">Biometric required to continue</string>
    <!-- Dialog message when identity check auth is requested but biometric hardware has an error. [CHAR LIMIT=NONE] -->
    <string name="identity_check_general_error_description_1">Identity Check is on and requires a biometric, but your face or fingerprint sensor is unavailable\n<ul><li>Check that your camera is on and try again</li>\n<li>You can turn off Identity Check using your Google Account</li></ul></string>
    <!-- Biometric error dialog button text for user to dismiss the dialog. [CHAR LIMIT=20] -->
    <string name="identity_check_biometric_error_cancel">Cancel</string>
    <!-- Biometric error dialog button text for user to acknowledge the message. [CHAR LIMIT=20] -->
    <string name="identity_check_biometric_error_ok">OK</string>
    <!-- Biometric error dialog button text to launch identity check settings. [CHAR LIMIT=80] -->
    <string name="go_to_identity_check">Go to identity check</string>
    <!-- Biometric error dialog button text to lock screen and recover biometric lockout state. [CHAR LIMIT=60] -->
    <string name="identity_check_lockout_error_lock_screen">Lock screen</string>
    <!-- Action for opening identity check settings page [CHAR LIMIT=NONE] [DO NOT TRANSLATE] -->
    <string name="identity_check_settings_action"></string>
    <!-- RemoteAuth unlock enrollment and settings --><skip />
    <!-- Title shown for menu item that launches watch unlock settings. [CHAR LIMIT=40] -->
    <string name ="security_settings_remoteauth_preference_title">Remote Authenticator Unlock</string>
+209 −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.development;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.admin.DevicePolicyManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ResolveInfo;
import android.os.Bundle;
import android.provider.Settings;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.TextPaint;
import android.text.method.LinkMovementMethod;
import android.text.style.ClickableSpan;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.TextView;

import androidx.fragment.app.FragmentActivity;

import com.android.settings.R;
import com.android.settings.Utils;
import com.android.settings.core.instrumentation.InstrumentedDialogFragment;

/** Initializes and shows biometric error dialogs related to identity check. */
public class BiometricErrorDialog extends InstrumentedDialogFragment {
    private static final String TAG = "BiometricErrorDialog";

    private static final String KEY_ERROR_CODE = "key_error_code";
    private String mActionIdentityCheckSettings = Settings.ACTION_SETTINGS;
    @Nullable private BroadcastReceiver mBroadcastReceiver;

    @NonNull
    @Override
    public Dialog onCreateDialog(
            @Nullable Bundle savedInstanceState) {
        final AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(getActivity());
        final LayoutInflater inflater = getActivity().getLayoutInflater();
        final boolean isLockoutError = getArguments().getString(KEY_ERROR_CODE).equals(
                Utils.BiometricStatus.LOCKOUT.name());
        final View customView = inflater.inflate(R.layout.biometric_lockout_error_dialog,
                null);
        final String identityCheckSettingsAction = getActivity().getString(
                R.string.identity_check_settings_action);
        mActionIdentityCheckSettings = identityCheckSettingsAction.isEmpty()
                ? mActionIdentityCheckSettings : identityCheckSettingsAction;
        Log.d(TAG, mActionIdentityCheckSettings);
        setTitle(customView, isLockoutError);
        setBody(customView, isLockoutError);
        alertDialogBuilder.setView(customView);
        setPositiveButton(alertDialogBuilder, isLockoutError);
        setNegativeButton(alertDialogBuilder, isLockoutError);

        if (isLockoutError) {
            mBroadcastReceiver = new BroadcastReceiver() {
                @Override
                public void onReceive(Context context, Intent intent) {
                    if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
                        dismiss();
                    }
                }
            };
            getContext().registerReceiver(mBroadcastReceiver,
                    new IntentFilter(Intent.ACTION_SCREEN_OFF));
        }

        return alertDialogBuilder.create();
    }

    @Override
    public void dismiss() {
        super.dismiss();
        if (mBroadcastReceiver != null) {
            getContext().unregisterReceiver(mBroadcastReceiver);
            mBroadcastReceiver = null;
        }
    }

    /**
     * Shows an error dialog to prompt the user to resolve biometric errors for identity check.
     * @param fragmentActivity calling activity
     * @param errorCode refers to the biometric error
     */
    public static BiometricErrorDialog showBiometricErrorDialog(FragmentActivity fragmentActivity,
            Utils.BiometricStatus errorCode) {
        final BiometricErrorDialog biometricErrorDialog = new BiometricErrorDialog();
        final Bundle args = new Bundle();
        args.putCharSequence(KEY_ERROR_CODE, errorCode.name());
        biometricErrorDialog.setArguments(args);
        biometricErrorDialog.show(fragmentActivity.getSupportFragmentManager(),
                BiometricErrorDialog.class.getName());
        return biometricErrorDialog;
    }

    private void setTitle(View view, boolean lockout) {
        final TextView titleTextView = view.findViewById(R.id.title);
        if (lockout) {
            titleTextView.setText(R.string.identity_check_lockout_error_title);
        } else {
            titleTextView.setText(R.string.identity_check_general_error_title);
        }
    }

    private void setBody(View view, boolean lockout) {
        final TextView textView1 = view.findViewById(R.id.description_1);
        final TextView textView2 = view.findViewById(R.id.description_2);

        if (lockout) {
            textView1.setText(R.string.identity_check_lockout_error_description_1);
            textView2.setText(getClickableDescriptionForLockoutError());
            textView2.setMovementMethod(LinkMovementMethod.getInstance());
        } else {
            textView1.setText(R.string.identity_check_general_error_description_1);
            textView2.setVisibility(View.GONE);
        }
    }

    private SpannableString getClickableDescriptionForLockoutError() {
        final String description = getResources().getString(
                R.string.identity_check_lockout_error_description_2);
        final SpannableString spannableString = new SpannableString(description);
        final ClickableSpan clickableSpan = new ClickableSpan() {
            @Override
            public void onClick(View textView) {
                dismiss();
                final Intent autoLockSettingsIntent = new Intent(mActionIdentityCheckSettings);
                final ResolveInfo autoLockSettingsInfo = getActivity().getPackageManager()
                        .resolveActivity(autoLockSettingsIntent, 0 /* flags */);
                if (autoLockSettingsInfo != null) {
                    startActivity(autoLockSettingsIntent);
                } else {
                    Log.e(TAG, "Auto lock settings intent could not be resolved.");
                }
            }
            @Override
            public void updateDrawState(TextPaint ds) {
                super.updateDrawState(ds);
                ds.setUnderlineText(true);
            }
        };
        final String goToSettings = getActivity().getString(R.string.go_to_settings);
        spannableString.setSpan(clickableSpan, description.indexOf(goToSettings),
                description.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);

        return spannableString;
    }

    private void setPositiveButton(AlertDialog.Builder alertDialogBuilder, boolean lockout) {
        if (lockout) {
            DevicePolicyManager devicePolicyManager = (DevicePolicyManager)
                    getContext().getSystemService(Context.DEVICE_POLICY_SERVICE);
            alertDialogBuilder.setPositiveButton(R.string.identity_check_lockout_error_lock_screen,
                    (dialog, which) -> {
                        dialog.dismiss();
                        devicePolicyManager.lockNow();
                    });
        } else {
            alertDialogBuilder.setPositiveButton(R.string.identity_check_biometric_error_ok,
                    (dialog, which) -> dialog.dismiss());
        }
    }

    private void setNegativeButton(AlertDialog.Builder alertDialogBuilder, boolean lockout) {
        if (lockout) {
            alertDialogBuilder.setNegativeButton(R.string.identity_check_biometric_error_cancel,
                    (dialog, which) -> dialog.dismiss());
        } else {
            alertDialogBuilder.setNegativeButton(R.string.go_to_identity_check,
                    (dialog, which) -> {
                        final Intent autoLockSettingsIntent = new Intent(
                                mActionIdentityCheckSettings);
                        final ResolveInfo autoLockSettingsInfo = getActivity().getPackageManager()
                                .resolveActivity(autoLockSettingsIntent, 0 /* flags */);
                        if (autoLockSettingsInfo != null) {
                            startActivity(autoLockSettingsIntent);
                        } else {
                            Log.e(TAG, "Identity check settings intent could not be resolved.");
                        }
                    });
        }
    }

    @Override
    public int getMetricsCategory() {
        return 0;
    }
}
+7 −0
Original line number Diff line number Diff line
@@ -76,6 +76,7 @@ import com.android.settings.development.graphicsdriver.GraphicsDriverEnableAngle
import com.android.settings.development.qstile.DevelopmentTiles;
import com.android.settings.development.storage.SharedDataPreferenceController;
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.password.ConfirmDeviceCredentialActivity;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settings.search.actionbar.SearchMenuController;
import com.android.settings.widget.SettingsMainSwitchBar;
@@ -377,6 +378,8 @@ public class DevelopmentSettingsDashboardFragment extends RestrictedDashboardFra
                            userId, false /* hideBackground */);
                } else if (biometricAuthStatus != Utils.BiometricStatus.NOT_ACTIVE) {
                    mSwitchBar.setChecked(false);
                    BiometricErrorDialog.showBiometricErrorDialog(
                            getActivity(), biometricAuthStatus);
                } else {
                    //Reset biometrics once enable dialog is shown
                    mIsBiometricsAuthenticated = false;
@@ -559,6 +562,10 @@ public class DevelopmentSettingsDashboardFragment extends RestrictedDashboardFra
            if (resultCode == RESULT_OK) {
                mIsBiometricsAuthenticated = true;
                mSwitchBar.setChecked(true);
            } else if (resultCode
                    == ConfirmDeviceCredentialActivity.BIOMETRIC_LOCKOUT_ERROR_RESULT) {
               BiometricErrorDialog.showBiometricErrorDialog(getActivity(),
                       Utils.BiometricStatus.LOCKOUT);
            }
        }
        for (AbstractPreferenceController controller : mPreferenceControllers) {
+6 −0
Original line number Diff line number Diff line
@@ -43,6 +43,7 @@ import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.BiometricPrompt;
import android.hardware.biometrics.BiometricPrompt.AuthenticationCallback;
import android.hardware.biometrics.Flags;
import android.hardware.biometrics.PromptInfo;
import android.os.Bundle;
import android.os.Handler;
@@ -82,6 +83,7 @@ public class ConfirmDeviceCredentialActivity extends FragmentActivity {
            "biometric_prompt_negative_button_text";
    public static final String BIOMETRIC_PROMPT_HIDE_BACKGROUND =
            "biometric_prompt_hide_background";
    public static final int BIOMETRIC_LOCKOUT_ERROR_RESULT = 100;

    public static class InternalActivity extends ConfirmDeviceCredentialActivity {
    }
@@ -129,6 +131,10 @@ public class ConfirmDeviceCredentialActivity extends FragmentActivity {
                        showConfirmCredentials();
                    } else {
                        Log.i(TAG, "Finishing, device credential not requested");
                        if (Flags.mandatoryBiometrics()
                                && errorCode == BiometricPrompt.BIOMETRIC_ERROR_LOCKOUT_PERMANENT) {
                            setResult(BIOMETRIC_LOCKOUT_ERROR_RESULT);
                        }
                        finish();
                    }
                }
Loading