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