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