1 /* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.settings.password; 18 19 import android.app.Activity; 20 import android.app.admin.DevicePolicyManager; 21 import android.app.settings.SettingsEnums; 22 import android.content.DialogInterface; 23 import android.hardware.biometrics.BiometricConstants; 24 import android.hardware.biometrics.BiometricPrompt; 25 import android.hardware.biometrics.BiometricPrompt.AuthenticationCallback; 26 import android.hardware.biometrics.BiometricPrompt.AuthenticationResult; 27 import android.hardware.biometrics.IBiometricConfirmDeviceCredentialCallback; 28 import android.os.Bundle; 29 import android.os.CancellationSignal; 30 import android.os.Handler; 31 import android.os.Looper; 32 import android.util.Log; 33 34 import androidx.annotation.NonNull; 35 36 import com.android.internal.widget.LockPatternUtils; 37 import com.android.settings.R; 38 import com.android.settings.core.InstrumentedFragment; 39 import com.android.settings.overlay.FeatureFactory; 40 41 import java.util.concurrent.Executor; 42 43 /** 44 * A fragment that wraps the BiometricPrompt and manages its lifecycle. 45 */ 46 public class BiometricFragment extends InstrumentedFragment { 47 48 private static final String TAG = "ConfirmDeviceCredential/BiometricFragment"; 49 50 // Re-set by the application. Should be done upon orientation changes, etc 51 private Executor mClientExecutor; 52 private AuthenticationCallback mClientCallback; 53 54 // Re-settable by the application. 55 private int mUserId; 56 57 // Created/Initialized once and retained 58 private Bundle mBundle; 59 private BiometricPrompt mBiometricPrompt; 60 private CancellationSignal mCancellationSignal; 61 private boolean mAuthenticating; 62 63 private AuthenticationCallback mAuthenticationCallback = 64 new AuthenticationCallback() { 65 @Override 66 public void onAuthenticationError(int error, @NonNull CharSequence message) { 67 mAuthenticating = false; 68 mClientExecutor.execute(() -> { 69 mClientCallback.onAuthenticationError(error, message); 70 }); 71 cleanup(); 72 } 73 74 @Override 75 public void onAuthenticationSucceeded(AuthenticationResult result) { 76 mAuthenticating = false; 77 mClientExecutor.execute(() -> { 78 mClientCallback.onAuthenticationSucceeded(result); 79 }); 80 cleanup(); 81 } 82 }; 83 84 private final DialogInterface.OnClickListener mNegativeButtonListener = 85 new DialogInterface.OnClickListener() { 86 @Override 87 public void onClick(DialogInterface dialog, int which) { 88 mAuthenticationCallback.onAuthenticationError( 89 BiometricConstants.BIOMETRIC_ERROR_NEGATIVE_BUTTON, 90 mBundle.getString(BiometricPrompt.KEY_NEGATIVE_TEXT)); 91 } 92 }; 93 94 // TODO(b/123378871): Remove when moved. 95 private final IBiometricConfirmDeviceCredentialCallback mCancelCallback 96 = new IBiometricConfirmDeviceCredentialCallback.Stub() { 97 @Override 98 public void cancel() { 99 final Activity activity = getActivity(); 100 if (activity != null) { 101 activity.finish(); 102 } else { 103 Log.e(TAG, "Activity null!"); 104 } 105 } 106 }; 107 108 /** 109 * @param bundle Bundle passed from {@link BiometricPrompt.Builder#buildIntent()} 110 * @return 111 */ newInstance(Bundle bundle)112 public static BiometricFragment newInstance(Bundle bundle) { 113 BiometricFragment biometricFragment = new BiometricFragment(); 114 biometricFragment.setArguments(bundle); 115 return biometricFragment; 116 } 117 setCallbacks(Executor executor, AuthenticationCallback callback)118 public void setCallbacks(Executor executor, AuthenticationCallback callback) { 119 mClientExecutor = executor; 120 mClientCallback = callback; 121 } 122 setUser(int userId)123 public void setUser(int userId) { 124 mUserId = userId; 125 } 126 cancel()127 public void cancel() { 128 if (mCancellationSignal != null) { 129 mCancellationSignal.cancel(); 130 } 131 cleanup(); 132 } 133 cleanup()134 private void cleanup() { 135 if (getActivity() != null) { 136 getActivity().getSupportFragmentManager().beginTransaction().remove(this).commit(); 137 } 138 } 139 isAuthenticating()140 boolean isAuthenticating() { 141 return mAuthenticating; 142 } 143 144 @Override onCreate(Bundle savedInstanceState)145 public void onCreate(Bundle savedInstanceState) { 146 super.onCreate(savedInstanceState); 147 setRetainInstance(true); 148 149 mBundle = getArguments(); 150 final BiometricPrompt.Builder builder = new BiometricPrompt.Builder(getContext()) 151 .setTitle(mBundle.getString(BiometricPrompt.KEY_TITLE)) 152 .setUseDefaultTitle() // use default title if title is null/empty 153 .setFromConfirmDeviceCredential() 154 .setSubtitle(mBundle.getString(BiometricPrompt.KEY_SUBTITLE)) 155 .setDescription(mBundle.getString(BiometricPrompt.KEY_DESCRIPTION)) 156 .setConfirmationRequired( 157 mBundle.getBoolean(BiometricPrompt.KEY_REQUIRE_CONFIRMATION, true)); 158 159 final LockPatternUtils lockPatternUtils = FeatureFactory.getFactory( 160 getContext()) 161 .getSecurityFeatureProvider() 162 .getLockPatternUtils(getContext()); 163 164 switch (lockPatternUtils.getKeyguardStoredPasswordQuality(mUserId)) { 165 case DevicePolicyManager.PASSWORD_QUALITY_SOMETHING: 166 builder.setNegativeButton(getResources().getString( 167 R.string.confirm_device_credential_pattern), 168 mClientExecutor, mNegativeButtonListener); 169 break; 170 case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC: 171 case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX: 172 builder.setNegativeButton(getResources().getString( 173 R.string.confirm_device_credential_pin), 174 mClientExecutor, mNegativeButtonListener); 175 break; 176 case DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC: 177 case DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC: 178 case DevicePolicyManager.PASSWORD_QUALITY_COMPLEX: 179 case DevicePolicyManager.PASSWORD_QUALITY_MANAGED: 180 builder.setNegativeButton(getResources().getString( 181 R.string.confirm_device_credential_password), 182 mClientExecutor, mNegativeButtonListener); 183 break; 184 } 185 186 mBiometricPrompt = builder.build(); 187 mCancellationSignal = new CancellationSignal(); 188 189 // TODO: CC doesn't use crypto for now 190 mAuthenticating = true; 191 mBiometricPrompt.authenticateUser(mCancellationSignal, mClientExecutor, 192 mAuthenticationCallback, mUserId, mCancelCallback); 193 } 194 195 @Override getMetricsCategory()196 public int getMetricsCategory() { 197 return SettingsEnums.BIOMETRIC_FRAGMENT; 198 } 199 } 200 201