• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 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 package com.android.settings.biometrics.combination;
17 
18 import static android.app.Activity.RESULT_OK;
19 
20 import static com.android.settings.password.ChooseLockPattern.RESULT_FINISHED;
21 
22 import android.content.Context;
23 import android.content.Intent;
24 import android.hardware.biometrics.SensorProperties;
25 import android.hardware.face.FaceManager;
26 import android.hardware.face.FaceSensorPropertiesInternal;
27 import android.hardware.fingerprint.FingerprintManager;
28 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
29 import android.os.Bundle;
30 import android.os.UserHandle;
31 import android.text.TextUtils;
32 import android.util.Log;
33 
34 import androidx.annotation.NonNull;
35 import androidx.annotation.Nullable;
36 import androidx.annotation.StringRes;
37 import androidx.annotation.VisibleForTesting;
38 import androidx.preference.Preference;
39 
40 import com.android.settings.R;
41 import com.android.settings.Utils;
42 import com.android.settings.biometrics.BiometricEnrollBase;
43 import com.android.settings.biometrics.BiometricUtils;
44 import com.android.settings.core.SettingsBaseActivity;
45 import com.android.settings.dashboard.DashboardFragment;
46 import com.android.settings.password.ChooseLockGeneric;
47 import com.android.settings.password.ChooseLockSettingsHelper;
48 import com.android.settingslib.transition.SettingsTransitionHelper;
49 
50 /**
51  * Base fragment with the confirming credential functionality for combined biometrics settings.
52  */
53 public abstract class BiometricsSettingsBase extends DashboardFragment {
54 
55     @VisibleForTesting
56     static final int CONFIRM_REQUEST = 2001;
57     private static final int CHOOSE_LOCK_REQUEST = 2002;
58 
59     private static final String SAVE_STATE_CONFIRM_CREDETIAL = "confirm_credential";
60     private static final String DO_NOT_FINISH_ACTIVITY = "do_not_finish_activity";
61     @VisibleForTesting
62     static final String RETRY_PREFERENCE_KEY = "retry_preference_key";
63     @VisibleForTesting
64     static final String RETRY_PREFERENCE_BUNDLE = "retry_preference_bundle";
65 
66     protected int mUserId;
67     protected long mGkPwHandle;
68     private boolean mConfirmCredential;
69     @Nullable private FaceManager mFaceManager;
70     @Nullable private FingerprintManager mFingerprintManager;
71     // Do not finish() if choosing/confirming credential, or showing fp/face settings
72     private boolean mDoNotFinishActivity;
73     @Nullable private String mRetryPreferenceKey = null;
74     @Nullable private Bundle mRetryPreferenceExtra = null;
75 
76     @Override
onAttach(Context context)77     public void onAttach(Context context) {
78         super.onAttach(context);
79         mUserId = getActivity().getIntent().getIntExtra(Intent.EXTRA_USER_ID,
80                 UserHandle.myUserId());
81     }
82 
83     @Override
onCreate(Bundle savedInstanceState)84     public void onCreate(Bundle savedInstanceState) {
85         super.onCreate(savedInstanceState);
86         mFaceManager = Utils.getFaceManagerOrNull(getActivity());
87         mFingerprintManager = Utils.getFingerprintManagerOrNull(getActivity());
88 
89         if (BiometricUtils.containsGatekeeperPasswordHandle(getIntent())) {
90             mGkPwHandle = BiometricUtils.getGatekeeperPasswordHandle(getIntent());
91         }
92 
93         if (savedInstanceState != null) {
94             mConfirmCredential = savedInstanceState.getBoolean(SAVE_STATE_CONFIRM_CREDETIAL);
95             mDoNotFinishActivity = savedInstanceState.getBoolean(DO_NOT_FINISH_ACTIVITY);
96             mRetryPreferenceKey = savedInstanceState.getString(RETRY_PREFERENCE_KEY);
97             mRetryPreferenceExtra = savedInstanceState.getBundle(RETRY_PREFERENCE_BUNDLE);
98             if (savedInstanceState.containsKey(
99                     ChooseLockSettingsHelper.EXTRA_KEY_REQUEST_GK_PW_HANDLE)) {
100                 mGkPwHandle = savedInstanceState.getLong(
101                         ChooseLockSettingsHelper.EXTRA_KEY_REQUEST_GK_PW_HANDLE);
102             }
103         }
104 
105         if (mGkPwHandle == 0L && !mConfirmCredential) {
106             mConfirmCredential = true;
107             launchChooseOrConfirmLock();
108         }
109 
110         final Preference unlockPhonePreference = findPreference(getUnlockPhonePreferenceKey());
111         if (unlockPhonePreference != null) {
112             unlockPhonePreference.setSummary(getUseAnyBiometricSummary());
113         }
114 
115         final Preference useInAppsPreference = findPreference(getUseInAppsPreferenceKey());
116         if (useInAppsPreference != null) {
117             useInAppsPreference.setSummary(getUseClass2BiometricSummary());
118         }
119     }
120 
121     @Override
onResume()122     public void onResume() {
123         super.onResume();
124         if (!mConfirmCredential) {
125             mDoNotFinishActivity = false;
126         }
127     }
128 
129     @Override
onStop()130     public void onStop() {
131         super.onStop();
132         if (!getActivity().isChangingConfigurations() && !mDoNotFinishActivity) {
133             BiometricUtils.removeGatekeeperPasswordHandle(getActivity(), mGkPwHandle);
134             getActivity().finish();
135         }
136     }
137 
onRetryPreferenceTreeClick(Preference preference, final boolean retry)138     private boolean onRetryPreferenceTreeClick(Preference preference, final boolean retry) {
139         final String key = preference.getKey();
140         final Context context = requireActivity().getApplicationContext();
141 
142         // Generate challenge (and request LSS to create a HAT) each time the preference is clicked,
143         // since FingerprintSettings and FaceSettings revoke the challenge when finishing.
144         if (getFacePreferenceKey().equals(key)) {
145             mDoNotFinishActivity = true;
146             mFaceManager.generateChallenge(mUserId, (sensorId, userId, challenge) -> {
147                 try {
148                     final byte[] token = requestGatekeeperHat(context, mGkPwHandle, mUserId,
149                             challenge);
150                     final Bundle extras = preference.getExtras();
151                     extras.putByteArray(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, token);
152                     extras.putInt(BiometricEnrollBase.EXTRA_KEY_SENSOR_ID, sensorId);
153                     extras.putLong(BiometricEnrollBase.EXTRA_KEY_CHALLENGE, challenge);
154                     super.onPreferenceTreeClick(preference);
155                 } catch (IllegalStateException e) {
156                     if (retry) {
157                         mRetryPreferenceKey = preference.getKey();
158                         mRetryPreferenceExtra = preference.getExtras();
159                         mConfirmCredential = true;
160                         launchChooseOrConfirmLock();
161                     } else {
162                         Log.e(getLogTag(), "face generateChallenge fail", e);
163                         mDoNotFinishActivity = false;
164                     }
165                 }
166             });
167             return true;
168         } else if (getFingerprintPreferenceKey().equals(key)) {
169             mDoNotFinishActivity = true;
170             mFingerprintManager.generateChallenge(mUserId, (sensorId, userId, challenge) -> {
171                 try {
172                     final byte[] token = requestGatekeeperHat(context, mGkPwHandle, mUserId,
173                             challenge);
174                     final Bundle extras = preference.getExtras();
175                     extras.putByteArray(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, token);
176                     extras.putLong(BiometricEnrollBase.EXTRA_KEY_CHALLENGE, challenge);
177                     super.onPreferenceTreeClick(preference);
178                 } catch (IllegalStateException e) {
179                     if (retry) {
180                         mRetryPreferenceKey = preference.getKey();
181                         mRetryPreferenceExtra = preference.getExtras();
182                         mConfirmCredential = true;
183                         launchChooseOrConfirmLock();
184                     } else {
185                         Log.e(getLogTag(), "fingerprint generateChallenge fail", e);
186                         mDoNotFinishActivity = false;
187                     }
188                 }
189             });
190             return true;
191         }
192         return false;
193     }
194 
195     @VisibleForTesting
requestGatekeeperHat(@onNull Context context, long gkPwHandle, int userId, long challenge)196     protected byte[] requestGatekeeperHat(@NonNull Context context, long gkPwHandle, int userId,
197             long challenge) {
198         return BiometricUtils.requestGatekeeperHat(context, gkPwHandle, userId, challenge);
199     }
200 
201     @Override
onPreferenceTreeClick(Preference preference)202     public boolean onPreferenceTreeClick(Preference preference) {
203         return onRetryPreferenceTreeClick(preference, true)
204                 || super.onPreferenceTreeClick(preference);
205     }
206 
retryPreferenceKey(@onNull String key, @Nullable Bundle extras)207     private void retryPreferenceKey(@NonNull String key, @Nullable Bundle extras) {
208         final Preference preference = findPreference(key);
209         if (preference == null) {
210             Log.w(getLogTag(), ".retryPreferenceKey, fail to find " + key);
211             return;
212         }
213 
214         if (extras != null) {
215             preference.getExtras().putAll(extras);
216         }
217         onRetryPreferenceTreeClick(preference, false);
218     }
219 
220     @Override
onSaveInstanceState(Bundle outState)221     public void onSaveInstanceState(Bundle outState) {
222         super.onSaveInstanceState(outState);
223         outState.putBoolean(SAVE_STATE_CONFIRM_CREDETIAL, mConfirmCredential);
224         outState.putBoolean(DO_NOT_FINISH_ACTIVITY, mDoNotFinishActivity);
225         if (mGkPwHandle != 0L) {
226             outState.putLong(ChooseLockSettingsHelper.EXTRA_KEY_REQUEST_GK_PW_HANDLE, mGkPwHandle);
227         }
228         if (!TextUtils.isEmpty(mRetryPreferenceKey)) {
229             outState.putString(RETRY_PREFERENCE_KEY, mRetryPreferenceKey);
230             outState.putBundle(RETRY_PREFERENCE_BUNDLE, mRetryPreferenceExtra);
231         }
232     }
233 
234     @Override
onActivityResult(int requestCode, int resultCode, @Nullable Intent data)235     public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
236         super.onActivityResult(requestCode, resultCode, data);
237         if (requestCode == CONFIRM_REQUEST || requestCode == CHOOSE_LOCK_REQUEST) {
238             mConfirmCredential = false;
239             mDoNotFinishActivity = false;
240             if (resultCode == RESULT_FINISHED || resultCode == RESULT_OK) {
241                 if (BiometricUtils.containsGatekeeperPasswordHandle(data)) {
242                     mGkPwHandle = BiometricUtils.getGatekeeperPasswordHandle(data);
243                     if (!TextUtils.isEmpty(mRetryPreferenceKey)) {
244                         getActivity().overridePendingTransition(R.anim.sud_slide_next_in,
245                                 R.anim.sud_slide_next_out);
246                         retryPreferenceKey(mRetryPreferenceKey, mRetryPreferenceExtra);
247                     }
248                 } else {
249                     Log.d(getLogTag(), "Data null or GK PW missing.");
250                     finish();
251                 }
252             } else {
253                 Log.d(getLogTag(), "Password not confirmed.");
254                 finish();
255             }
256             mRetryPreferenceKey = null;
257             mRetryPreferenceExtra = null;
258         }
259     }
260 
261     /**
262      * Get the preference key of face for passing through credential data to face settings.
263      */
getFacePreferenceKey()264     public abstract String getFacePreferenceKey();
265 
266     /**
267      * Get the preference key of face for passing through credential data to face settings.
268      */
getFingerprintPreferenceKey()269     public abstract String getFingerprintPreferenceKey();
270 
271     /**
272      * @return The preference key of the "Unlock your phone" setting toggle.
273      */
getUnlockPhonePreferenceKey()274     public abstract String getUnlockPhonePreferenceKey();
275 
276     /**
277      * @return The preference key of the "Verify it's you in apps" setting toggle.
278      */
getUseInAppsPreferenceKey()279     public abstract String getUseInAppsPreferenceKey();
280 
281     @VisibleForTesting
launchChooseOrConfirmLock()282     protected void launchChooseOrConfirmLock() {
283         final ChooseLockSettingsHelper.Builder builder =
284                 new ChooseLockSettingsHelper.Builder(getActivity(), this)
285                         .setRequestCode(CONFIRM_REQUEST)
286                         .setTitle(getString(R.string.security_settings_biometric_preference_title))
287                         .setRequestGatekeeperPasswordHandle(true)
288                         .setForegroundOnly(true)
289                         .setReturnCredentials(true);
290         if (mUserId != UserHandle.USER_NULL) {
291             builder.setUserId(mUserId);
292         }
293         mDoNotFinishActivity = true;
294         final boolean launched = builder.show();
295 
296         if (!launched) {
297             Intent intent = BiometricUtils.getChooseLockIntent(getActivity(), getIntent());
298             intent.putExtra(ChooseLockGeneric.ChooseLockGenericFragment.HIDE_INSECURE_OPTIONS,
299                     true);
300             intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_REQUEST_GK_PW_HANDLE, true);
301             intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_BIOMETRICS, true);
302             intent.putExtra(SettingsBaseActivity.EXTRA_PAGE_TRANSITION_TYPE,
303                     SettingsTransitionHelper.TransitionType.TRANSITION_SLIDE);
304 
305             if (mUserId != UserHandle.USER_NULL) {
306                 intent.putExtra(Intent.EXTRA_USER_ID, mUserId);
307             }
308             startActivityForResult(intent, CHOOSE_LOCK_REQUEST);
309         }
310     }
311 
312     @NonNull
getUseAnyBiometricSummary()313     private String getUseAnyBiometricSummary() {
314         boolean isFaceAllowed = mFaceManager != null && mFaceManager.isHardwareDetected();
315         boolean isFingerprintAllowed =
316                 mFingerprintManager != null && mFingerprintManager.isHardwareDetected();
317 
318         @StringRes final int resId = getUseBiometricSummaryRes(isFaceAllowed, isFingerprintAllowed);
319         return resId == 0 ? "" : getString(resId);
320     }
321 
322     @NonNull
getUseClass2BiometricSummary()323     private String getUseClass2BiometricSummary() {
324         boolean isFaceAllowed = false;
325         if (mFaceManager != null) {
326             for (final FaceSensorPropertiesInternal sensorProps
327                     : mFaceManager.getSensorPropertiesInternal()) {
328                 if (sensorProps.sensorStrength == SensorProperties.STRENGTH_WEAK
329                         || sensorProps.sensorStrength == SensorProperties.STRENGTH_STRONG) {
330                     isFaceAllowed = true;
331                     break;
332                 }
333             }
334         }
335 
336         boolean isFingerprintAllowed = false;
337         if (mFingerprintManager != null) {
338             for (final FingerprintSensorPropertiesInternal sensorProps
339                     : mFingerprintManager.getSensorPropertiesInternal()) {
340                 if (sensorProps.sensorStrength == SensorProperties.STRENGTH_WEAK
341                         || sensorProps.sensorStrength == SensorProperties.STRENGTH_STRONG) {
342                     isFingerprintAllowed = true;
343                     break;
344                 }
345             }
346         }
347 
348         @StringRes final int resId = getUseBiometricSummaryRes(isFaceAllowed, isFingerprintAllowed);
349         return resId == 0 ? "" : getString(resId);
350     }
351 
352     @StringRes
getUseBiometricSummaryRes(boolean isFaceAllowed, boolean isFingerprintAllowed)353     private static int getUseBiometricSummaryRes(boolean isFaceAllowed,
354             boolean isFingerprintAllowed) {
355 
356         if (isFaceAllowed && isFingerprintAllowed) {
357             return R.string.biometric_settings_use_face_or_fingerprint_preference_summary;
358         } else if (isFaceAllowed) {
359             return R.string.biometric_settings_use_face_preference_summary;
360         } else if (isFingerprintAllowed) {
361             return R.string.biometric_settings_use_fingerprint_preference_summary;
362         } else {
363             return 0;
364         }
365     }
366 }
367