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 static android.hardware.biometrics.BiometricConstants.BIOMETRIC_ERROR_USER_CANCELED; 20 21 import android.app.settings.SettingsEnums; 22 import android.hardware.biometrics.BiometricManager; 23 import android.hardware.biometrics.BiometricPrompt; 24 import android.hardware.biometrics.BiometricPrompt.AuthenticationCallback; 25 import android.hardware.biometrics.BiometricPrompt.AuthenticationResult; 26 import android.hardware.biometrics.PromptInfo; 27 import android.multiuser.Flags; 28 import android.os.Bundle; 29 import android.os.CancellationSignal; 30 import android.text.TextUtils; 31 32 import androidx.annotation.NonNull; 33 34 import com.android.settings.core.InstrumentedFragment; 35 36 import java.util.concurrent.Executor; 37 38 /** 39 * A fragment that wraps the BiometricPrompt and manages its lifecycle. 40 */ 41 public class BiometricFragment extends InstrumentedFragment { 42 43 private static final String TAG = "ConfirmDeviceCredential/BiometricFragment"; 44 45 private static final String KEY_PROMPT_INFO = "prompt_info"; 46 47 // Re-set by the application. Should be done upon orientation changes, etc 48 private Executor mClientExecutor; 49 private AuthenticationCallback mClientCallback; 50 51 // Re-settable by the application. 52 private int mUserId; 53 54 // Created/Initialized once and retained 55 private BiometricPrompt mBiometricPrompt; 56 private CancellationSignal mCancellationSignal; 57 58 private AuthenticationCallback mAuthenticationCallback = 59 new AuthenticationCallback() { 60 @Override 61 public void onAuthenticationError(int error, @NonNull CharSequence message) { 62 mClientExecutor.execute(() -> { 63 mClientCallback.onAuthenticationError(error, message); 64 }); 65 cleanup(); 66 } 67 68 @Override 69 public void onAuthenticationSucceeded(AuthenticationResult result) { 70 mClientExecutor.execute(() -> { 71 mClientCallback.onAuthenticationSucceeded(result); 72 }); 73 cleanup(); 74 } 75 76 @Override 77 public void onAuthenticationFailed() { 78 mClientExecutor.execute(() -> { 79 mClientCallback.onAuthenticationFailed(); 80 }); 81 } 82 83 @Override 84 public void onSystemEvent(int event) { 85 mClientExecutor.execute(() -> { 86 mClientCallback.onSystemEvent(event); 87 }); 88 } 89 }; 90 91 /** 92 * @param promptInfo 93 * @return 94 */ newInstance(PromptInfo promptInfo)95 public static BiometricFragment newInstance(PromptInfo promptInfo) { 96 BiometricFragment biometricFragment = new BiometricFragment(); 97 final Bundle bundle = new Bundle(); 98 bundle.putParcelable(KEY_PROMPT_INFO, promptInfo); 99 biometricFragment.setArguments(bundle); 100 return biometricFragment; 101 } 102 setCallbacks(Executor executor, AuthenticationCallback callback)103 public void setCallbacks(Executor executor, AuthenticationCallback callback) { 104 mClientExecutor = executor; 105 mClientCallback = callback; 106 } 107 setUser(int userId)108 public void setUser(int userId) { 109 mUserId = userId; 110 } 111 cancel()112 public void cancel() { 113 if (mCancellationSignal != null) { 114 mCancellationSignal.cancel(); 115 } 116 cleanup(); 117 } 118 cleanup()119 private void cleanup() { 120 if (getActivity() != null) { 121 getActivity().getSupportFragmentManager().beginTransaction().remove(this) 122 .commitAllowingStateLoss(); 123 } 124 } 125 126 @Override onCreate(Bundle savedInstanceState)127 public void onCreate(Bundle savedInstanceState) { 128 super.onCreate(savedInstanceState); 129 setRetainInstance(true); 130 131 final Bundle bundle = getArguments(); 132 final PromptInfo promptInfo = bundle.getParcelable(KEY_PROMPT_INFO); 133 BiometricPrompt.Builder promptBuilder = new BiometricPrompt.Builder(getContext()) 134 .setTitle(promptInfo.getTitle()) 135 .setUseDefaultTitle() // use default title if title is null/empty 136 .setAllowedAuthenticators(promptInfo.getAuthenticators()) 137 .setSubtitle(promptInfo.getSubtitle()) 138 .setDescription(promptInfo.getDescription()) 139 .setTextForDeviceCredential( 140 promptInfo.getDeviceCredentialTitle(), 141 promptInfo.getDeviceCredentialSubtitle(), 142 promptInfo.getDeviceCredentialDescription()) 143 .setConfirmationRequired(promptInfo.isConfirmationRequested()) 144 .setDisallowBiometricsIfPolicyExists( 145 promptInfo.isDisallowBiometricsIfPolicyExists()) 146 .setShowEmergencyCallButton(promptInfo.isShowEmergencyCallButton()) 147 .setReceiveSystemEvents(true) 148 .setRealCallerForConfirmDeviceCredentialActivity( 149 promptInfo.getRealCallerForConfirmDeviceCredentialActivity()); 150 if (promptInfo.getLogoRes() != 0){ 151 promptBuilder.setLogoRes(promptInfo.getLogoRes()); 152 } 153 String logoDescription = promptInfo.getLogoDescription(); 154 if (!TextUtils.isEmpty(logoDescription)) { 155 promptBuilder.setLogoDescription(logoDescription); 156 } 157 158 if (android.os.Flags.allowPrivateProfile() && Flags.enablePrivateSpaceFeatures() 159 && Flags.enableBiometricsToUnlockPrivateSpace()) { 160 promptBuilder = promptBuilder.setAllowBackgroundAuthentication(true /* allow */, 161 promptInfo.shouldUseParentProfileForDeviceCredential()); 162 } else { 163 promptBuilder = promptBuilder.setAllowBackgroundAuthentication(true /* allow */); 164 } 165 166 // Check if the default subtitle should be used if subtitle is null/empty 167 if (promptInfo.isUseDefaultSubtitle()) { 168 promptBuilder.setUseDefaultSubtitle(); 169 } 170 171 if ((promptInfo.getAuthenticators() 172 & BiometricManager.Authenticators.DEVICE_CREDENTIAL) == 0) { 173 promptBuilder.setNegativeButton(promptInfo.getNegativeButtonText(), 174 getContext().getMainExecutor(), 175 (dialog, which) -> mAuthenticationCallback.onAuthenticationError( 176 BIOMETRIC_ERROR_USER_CANCELED, 177 null /* errString */)); 178 } 179 mBiometricPrompt = promptBuilder.build(); 180 } 181 182 @Override onResume()183 public void onResume() { 184 super.onResume(); 185 186 if (mCancellationSignal == null) { 187 mCancellationSignal = new CancellationSignal(); 188 mBiometricPrompt.authenticateUser(mCancellationSignal, mClientExecutor, 189 mAuthenticationCallback, mUserId); 190 } 191 } 192 193 @Override getMetricsCategory()194 public int getMetricsCategory() { 195 return SettingsEnums.BIOMETRIC_FRAGMENT; 196 } 197 } 198