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

Commit b3ee2315 authored by Kevin Chyn's avatar Kevin Chyn
Browse files

3/n: Make CDCA transparent and add BiometricPrompt

ConfirmDeviceCredentials now uses BiometricPrompt instead of
FingerprintManager

Bug: 111461540

Test: FRP does not display BiometricPrompt (as expected)
      adb shell settings put global device_provisioned 0 && adb shell am start -a android.app.action.CONFIRM_FRP_CREDENTIAL
Test: Using KeyguardManager API to launch, all corner cases seem OK
Test: Tested with work profile + one lock enabled/disabled, seems OK
Test: Enroll normal FP but not work FP, BiometricPromptDemo for both works
      OK
Test: Test CC on work version of BPD, then BP on normal version of BPD,
      both accept correct FP's (no regression from P)

Change-Id: Iacdaf76ab76971850212dc79513bfa3f4b89eb9a
parent 80ecfd95
Loading
Loading
Loading
Loading
+3 −1
Original line number Diff line number Diff line
@@ -80,6 +80,8 @@
    <uses-permission android:name="android.permission.OVERRIDE_WIFI_CONFIG" />
    <uses-permission android:name="android.permission.USE_FINGERPRINT" />
    <uses-permission android:name="android.permission.MANAGE_FINGERPRINT" />
    <uses-permission android:name="android.permission.USE_BIOMETRIC" />
    <uses-permission android:name="android.permission.USE_BIOMETRIC_INTERNAL" />
    <uses-permission android:name="android.permission.USER_ACTIVITY" />
    <uses-permission android:name="android.permission.CHANGE_APP_IDLE_STATE" />
    <uses-permission android:name="android.permission.PEERS_MAC_ADDRESS"/>
@@ -1451,7 +1453,7 @@
        <!-- Lock screen settings -->
        <activity android:name=".password.ConfirmDeviceCredentialActivity"
            android:exported="true"
            android:theme="@android:style/Theme.NoDisplay">
            android:theme="@android:style/Theme.Translucent.NoTitleBar">
            <intent-filter android:priority="1">
                <action android:name="android.app.action.CONFIRM_DEVICE_CREDENTIAL" />
                <action android:name="android.app.action.CONFIRM_FRP_CREDENTIAL" />
+24 −0
Original line number Diff line number Diff line
<?xml version="1.0" encoding="utf-8"?>
<!--
  ~ Copyright (C) 2018 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
  -->

<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:shareInterpolator="false"
    android:zAdjustment="top">
    <alpha android:fromAlpha="0.0" android:toAlpha="1.0"
        android:interpolator="@android:interpolator/linear_out_slow_in"
        android:duration="350"/>
</set>
 No newline at end of file
+24 −0
Original line number Diff line number Diff line
<?xml version="1.0" encoding="utf-8"?>
<!--
  ~ Copyright (C) 2018 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
  -->

<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:shareInterpolator="false"
    android:zAdjustment="top">
    <alpha android:fromAlpha="1.0" android:toAlpha="0.0"
        android:interpolator="@android:interpolator/linear_out_slow_in"
        android:duration="350" />
</set>
 No newline at end of file
+4 −0
Original line number Diff line number Diff line
@@ -3706,6 +3706,10 @@
    <!-- About phone settings screen, Safety Legal dialog title until the link is fully loaded -->
    <string name="settings_safetylegal_activity_loading">Loading\u2026</string>
    <!-- ConfirmDeviceCredential settings-->
    <!-- Button text shown on BiometricPrompt (system dialog that asks for biometric authentication) giving the user the option to use an alternate form of authentication (Pin/Pattern/Pass) [CHAR LIMIT=30] -->
    <string name="confirm_device_credential_use_alternate_method">Use alternate method</string>
    <!-- Lock Pattern settings -->
    <!-- Header on first screen of choose password/PIN flow [CHAR LIMIT=40] -->
    <string name="lockpassword_choose_your_screen_lock_header">Set screen lock</string>
+198 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2018 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.password;

import android.app.settings.SettingsEnums;
import android.content.DialogInterface;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricPrompt;
import android.hardware.biometrics.BiometricPrompt.AuthenticationCallback;
import android.hardware.biometrics.BiometricPrompt.AuthenticationResult;
import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.Handler;
import android.os.Looper;

import com.android.settings.core.InstrumentedFragment;

import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import java.util.concurrent.Executor;

/**
 * A fragment that wraps the BiometricPrompt and manages its lifecycle.
 */
public class BiometricFragment extends InstrumentedFragment {

    private static final String KEY_TITLE = "title";
    private static final String KEY_SUBTITLE = "subtitle";
    private static final String KEY_DESCRIPTION = "description";
    private static final String KEY_NEGATIVE_TEXT = "negative_text";

    // Re-set by the application. Should be done upon orientation changes, etc
    private Executor mClientExecutor;
    private AuthenticationCallback mClientCallback;

    // Created/Initialized once and retained
    private final Handler mHandler = new Handler(Looper.getMainLooper());
    private PromptInfo mPromptInfo;
    private BiometricPrompt mBiometricPrompt;
    private CancellationSignal mCancellationSignal;

    private AuthenticationCallback mAuthenticationCallback =
            new AuthenticationCallback() {
        @Override
        public void onAuthenticationError(int error, @NonNull CharSequence message) {
            mClientExecutor.execute(() -> {
                mClientCallback.onAuthenticationError(error, message);
            });
            cleanup();
        }

        @Override
        public void onAuthenticationSucceeded(AuthenticationResult result) {
            mClientExecutor.execute(() -> {
                mClientCallback.onAuthenticationSucceeded(result);
            });
            cleanup();
        }
    };

    private final DialogInterface.OnClickListener mNegativeButtonListener =
            new DialogInterface.OnClickListener() {
        @Override
        public void onClick(DialogInterface dialog, int which) {
            mAuthenticationCallback.onAuthenticationError(
                    BiometricConstants.BIOMETRIC_ERROR_NEGATIVE_BUTTON,
                    mPromptInfo.getNegativeButtonText());
        }
    };

    public static BiometricFragment newInstance(PromptInfo info) {
        BiometricFragment biometricFragment = new BiometricFragment();
        biometricFragment.setArguments(info.getBundle());
        return biometricFragment;
    }

    public void setCallbacks(Executor executor, AuthenticationCallback callback) {
        mClientExecutor = executor;
        mClientCallback = callback;
    }

    public void cancel() {
        if (mCancellationSignal != null) {
            mCancellationSignal.cancel();
        }
        cleanup();
    }

    private void cleanup() {
        if (getActivity() != null) {
            getActivity().getSupportFragmentManager().beginTransaction().remove(this).commit();
        }
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setRetainInstance(true);

        mPromptInfo = new PromptInfo(getArguments());
        mBiometricPrompt = new BiometricPrompt.Builder(getContext())
            .setTitle(mPromptInfo.getTitle())
            .setUseDefaultTitle() // use default title if title is null/empty
            .setSubtitle(mPromptInfo.getSubtitle())
            .setDescription(mPromptInfo.getDescription())
            .setNegativeButton(mPromptInfo.getNegativeButtonText(), mClientExecutor,
                    mNegativeButtonListener)
            .build();
        mCancellationSignal = new CancellationSignal();

        // TODO: CC doesn't use crypto for now
        mBiometricPrompt.authenticate(mCancellationSignal, mClientExecutor,
                mAuthenticationCallback);
    }

    @Override
    public int getMetricsCategory() {
        return SettingsEnums.BIOMETRIC_FRAGMENT;
    }

    /**
     * A simple wrapper for BiometricPrompt.PromptInfo. Since we want to manage the lifecycle
     * of BiometricPrompt correctly, the information needs to be stored in here.
     */
    static class PromptInfo {
        private final Bundle mBundle;

        private PromptInfo(Bundle bundle) {
            mBundle = bundle;
        }

        Bundle getBundle() {
            return mBundle;
        }

        public CharSequence getTitle() {
            return mBundle.getCharSequence(KEY_TITLE);
        }

        public CharSequence getSubtitle() {
            return mBundle.getCharSequence(KEY_SUBTITLE);
        }

        public CharSequence getDescription() {
            return mBundle.getCharSequence(KEY_DESCRIPTION);
        }

        public CharSequence getNegativeButtonText() {
            return mBundle.getCharSequence(KEY_NEGATIVE_TEXT);
        }

        public static class Builder {
            private final Bundle mBundle = new Bundle();

            public Builder setTitle(@NonNull CharSequence title) {
                mBundle.putCharSequence(KEY_TITLE, title);
                return this;
            }

            public Builder setSubtitle(@Nullable CharSequence subtitle) {
                mBundle.putCharSequence(KEY_SUBTITLE, subtitle);
                return this;
            }

            public Builder setDescription(@Nullable CharSequence description) {
                mBundle.putCharSequence(KEY_DESCRIPTION, description);
                return this;
            }

            public Builder setNegativeButtonText(@NonNull CharSequence text) {
                mBundle.putCharSequence(KEY_NEGATIVE_TEXT, text);
                return this;
            }

            public PromptInfo build() {
                return new PromptInfo(mBundle);
            }
        }
    }
}
Loading