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.util.Log;
32 
33 import androidx.annotation.NonNull;
34 import androidx.annotation.Nullable;
35 import androidx.annotation.StringRes;
36 import androidx.preference.Preference;
37 
38 import com.android.settings.R;
39 import com.android.settings.Utils;
40 import com.android.settings.biometrics.BiometricEnrollBase;
41 import com.android.settings.biometrics.BiometricUtils;
42 import com.android.settings.core.SettingsBaseActivity;
43 import com.android.settings.dashboard.DashboardFragment;
44 import com.android.settings.password.ChooseLockGeneric;
45 import com.android.settings.password.ChooseLockSettingsHelper;
46 import com.android.settingslib.transition.SettingsTransitionHelper;
47 
48 /**
49  * Base fragment with the confirming credential functionality for combined biometrics settings.
50  */
51 public abstract class BiometricsSettingsBase extends DashboardFragment {
52 
53     private static final int CONFIRM_REQUEST = 2001;
54     private static final int CHOOSE_LOCK_REQUEST = 2002;
55 
56     private static final String SAVE_STATE_CONFIRM_CREDETIAL = "confirm_credential";
57     private static final String DO_NOT_FINISH_ACTIVITY = "do_not_finish_activity";
58 
59     protected int mUserId;
60     protected long mGkPwHandle;
61     private boolean mConfirmCredential;
62     @Nullable private FaceManager mFaceManager;
63     @Nullable private FingerprintManager mFingerprintManager;
64     // Do not finish() if choosing/confirming credential, or showing fp/face settings
65     private boolean mDoNotFinishActivity;
66 
67     @Override
onAttach(Context context)68     public void onAttach(Context context) {
69         super.onAttach(context);
70         mUserId = getActivity().getIntent().getIntExtra(Intent.EXTRA_USER_ID,
71                 UserHandle.myUserId());
72     }
73 
74     @Override
onCreate(Bundle savedInstanceState)75     public void onCreate(Bundle savedInstanceState) {
76         super.onCreate(savedInstanceState);
77         mFaceManager = Utils.getFaceManagerOrNull(getActivity());
78         mFingerprintManager = Utils.getFingerprintManagerOrNull(getActivity());
79 
80         if (BiometricUtils.containsGatekeeperPasswordHandle(getIntent())) {
81             mGkPwHandle = BiometricUtils.getGatekeeperPasswordHandle(getIntent());
82         }
83 
84         if (savedInstanceState != null) {
85             mConfirmCredential = savedInstanceState.getBoolean(SAVE_STATE_CONFIRM_CREDETIAL);
86             mDoNotFinishActivity = savedInstanceState.getBoolean(DO_NOT_FINISH_ACTIVITY);
87             if (savedInstanceState.containsKey(
88                     ChooseLockSettingsHelper.EXTRA_KEY_REQUEST_GK_PW_HANDLE)) {
89                 mGkPwHandle = savedInstanceState.getLong(
90                         ChooseLockSettingsHelper.EXTRA_KEY_REQUEST_GK_PW_HANDLE);
91             }
92         }
93 
94         if (mGkPwHandle == 0L && !mConfirmCredential) {
95             mConfirmCredential = true;
96             launchChooseOrConfirmLock();
97         }
98 
99         final Preference unlockPhonePreference = findPreference(getUnlockPhonePreferenceKey());
100         if (unlockPhonePreference != null) {
101             unlockPhonePreference.setSummary(getUseAnyBiometricSummary());
102         }
103 
104         final Preference useInAppsPreference = findPreference(getUseInAppsPreferenceKey());
105         if (useInAppsPreference != null) {
106             useInAppsPreference.setSummary(getUseClass2BiometricSummary());
107         }
108     }
109 
110     @Override
onResume()111     public void onResume() {
112         super.onResume();
113         if (!mConfirmCredential) {
114             mDoNotFinishActivity = false;
115         }
116     }
117 
118     @Override
onStop()119     public void onStop() {
120         super.onStop();
121         if (!getActivity().isChangingConfigurations() && !mDoNotFinishActivity) {
122             BiometricUtils.removeGatekeeperPasswordHandle(getActivity(), mGkPwHandle);
123             getActivity().finish();
124         }
125     }
126 
127     @Override
onPreferenceTreeClick(Preference preference)128     public boolean onPreferenceTreeClick(Preference preference) {
129         final String key = preference.getKey();
130 
131         // Generate challenge (and request LSS to create a HAT) each time the preference is clicked,
132         // since FingerprintSettings and FaceSettings revoke the challenge when finishing.
133         if (getFacePreferenceKey().equals(key)) {
134             mDoNotFinishActivity = true;
135             mFaceManager.generateChallenge(mUserId, (sensorId, userId, challenge) -> {
136                 final byte[] token = BiometricUtils.requestGatekeeperHat(getActivity(), mGkPwHandle,
137                         mUserId, challenge);
138                 final Bundle extras = preference.getExtras();
139                 extras.putByteArray(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, token);
140                 extras.putInt(BiometricEnrollBase.EXTRA_KEY_SENSOR_ID, sensorId);
141                 extras.putLong(BiometricEnrollBase.EXTRA_KEY_CHALLENGE, challenge);
142                 super.onPreferenceTreeClick(preference);
143             });
144 
145             return true;
146         } else if (getFingerprintPreferenceKey().equals(key)) {
147             mDoNotFinishActivity = true;
148             mFingerprintManager.generateChallenge(mUserId, (sensorId, userId, challenge) -> {
149                 final byte[] token = BiometricUtils.requestGatekeeperHat(getActivity(), mGkPwHandle,
150                         mUserId, challenge);
151                 final Bundle extras = preference.getExtras();
152                 extras.putByteArray(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, token);
153                 extras.putLong(BiometricEnrollBase.EXTRA_KEY_CHALLENGE, challenge);
154                 super.onPreferenceTreeClick(preference);
155             });
156 
157             return true;
158         }
159 
160         return super.onPreferenceTreeClick(preference);
161     }
162 
163     @Override
onSaveInstanceState(Bundle outState)164     public void onSaveInstanceState(Bundle outState) {
165         super.onSaveInstanceState(outState);
166         outState.putBoolean(SAVE_STATE_CONFIRM_CREDETIAL, mConfirmCredential);
167         outState.putBoolean(DO_NOT_FINISH_ACTIVITY, mDoNotFinishActivity);
168         if (mGkPwHandle != 0L) {
169             outState.putLong(ChooseLockSettingsHelper.EXTRA_KEY_REQUEST_GK_PW_HANDLE, mGkPwHandle);
170         }
171     }
172 
173     @Override
onActivityResult(int requestCode, int resultCode, @Nullable Intent data)174     public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
175         super.onActivityResult(requestCode, resultCode, data);
176         if (requestCode == CONFIRM_REQUEST || requestCode == CHOOSE_LOCK_REQUEST) {
177             mConfirmCredential = false;
178             mDoNotFinishActivity = false;
179             if (resultCode == RESULT_FINISHED || resultCode == RESULT_OK) {
180                 if (BiometricUtils.containsGatekeeperPasswordHandle(data)) {
181                     mGkPwHandle = BiometricUtils.getGatekeeperPasswordHandle(data);
182                 } else {
183                     Log.d(getLogTag(), "Data null or GK PW missing.");
184                     finish();
185                 }
186             } else {
187                 Log.d(getLogTag(), "Password not confirmed.");
188                 finish();
189             }
190         }
191     }
192 
193     /**
194      * Get the preference key of face for passing through credential data to face settings.
195      */
getFacePreferenceKey()196     public abstract String getFacePreferenceKey();
197 
198     /**
199      * Get the preference key of face for passing through credential data to face settings.
200      */
getFingerprintPreferenceKey()201     public abstract String getFingerprintPreferenceKey();
202 
203     /**
204      * @return The preference key of the "Unlock your phone" setting toggle.
205      */
getUnlockPhonePreferenceKey()206     public abstract String getUnlockPhonePreferenceKey();
207 
208     /**
209      * @return The preference key of the "Verify it's you in apps" setting toggle.
210      */
getUseInAppsPreferenceKey()211     public abstract String getUseInAppsPreferenceKey();
212 
launchChooseOrConfirmLock()213     private void launchChooseOrConfirmLock() {
214         final ChooseLockSettingsHelper.Builder builder =
215                 new ChooseLockSettingsHelper.Builder(getActivity(), this)
216                         .setRequestCode(CONFIRM_REQUEST)
217                         .setTitle(getString(R.string.security_settings_biometric_preference_title))
218                         .setRequestGatekeeperPasswordHandle(true)
219                         .setForegroundOnly(true)
220                         .setReturnCredentials(true);
221         if (mUserId != UserHandle.USER_NULL) {
222             builder.setUserId(mUserId);
223         }
224         mDoNotFinishActivity = true;
225         final boolean launched = builder.show();
226 
227         if (!launched) {
228             Intent intent = BiometricUtils.getChooseLockIntent(getActivity(), getIntent());
229             intent.putExtra(ChooseLockGeneric.ChooseLockGenericFragment.HIDE_INSECURE_OPTIONS,
230                     true);
231             intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_REQUEST_GK_PW_HANDLE, true);
232             intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_BIOMETRICS, true);
233             intent.putExtra(SettingsBaseActivity.EXTRA_PAGE_TRANSITION_TYPE,
234                     SettingsTransitionHelper.TransitionType.TRANSITION_SLIDE);
235 
236             if (mUserId != UserHandle.USER_NULL) {
237                 intent.putExtra(Intent.EXTRA_USER_ID, mUserId);
238             }
239             startActivityForResult(intent, CHOOSE_LOCK_REQUEST);
240         }
241     }
242 
243     @NonNull
getUseAnyBiometricSummary()244     private String getUseAnyBiometricSummary() {
245         boolean isFaceAllowed = mFaceManager != null && mFaceManager.isHardwareDetected();
246         boolean isFingerprintAllowed =
247                 mFingerprintManager != null && mFingerprintManager.isHardwareDetected();
248 
249         @StringRes final int resId = getUseBiometricSummaryRes(isFaceAllowed, isFingerprintAllowed);
250         return resId == 0 ? "" : getString(resId);
251     }
252 
253     @NonNull
getUseClass2BiometricSummary()254     private String getUseClass2BiometricSummary() {
255         boolean isFaceAllowed = false;
256         if (mFaceManager != null) {
257             for (final FaceSensorPropertiesInternal sensorProps
258                     : mFaceManager.getSensorPropertiesInternal()) {
259                 if (sensorProps.sensorStrength == SensorProperties.STRENGTH_WEAK
260                         || sensorProps.sensorStrength == SensorProperties.STRENGTH_STRONG) {
261                     isFaceAllowed = true;
262                     break;
263                 }
264             }
265         }
266 
267         boolean isFingerprintAllowed = false;
268         if (mFingerprintManager != null) {
269             for (final FingerprintSensorPropertiesInternal sensorProps
270                     : mFingerprintManager.getSensorPropertiesInternal()) {
271                 if (sensorProps.sensorStrength == SensorProperties.STRENGTH_WEAK
272                         || sensorProps.sensorStrength == SensorProperties.STRENGTH_STRONG) {
273                     isFingerprintAllowed = true;
274                     break;
275                 }
276             }
277         }
278 
279         @StringRes final int resId = getUseBiometricSummaryRes(isFaceAllowed, isFingerprintAllowed);
280         return resId == 0 ? "" : getString(resId);
281     }
282 
283     @StringRes
getUseBiometricSummaryRes(boolean isFaceAllowed, boolean isFingerprintAllowed)284     private static int getUseBiometricSummaryRes(boolean isFaceAllowed,
285             boolean isFingerprintAllowed) {
286 
287         if (isFaceAllowed && isFingerprintAllowed) {
288             return R.string.biometric_settings_use_face_or_fingerprint_preference_summary;
289         } else if (isFaceAllowed) {
290             return R.string.biometric_settings_use_face_preference_summary;
291         } else if (isFingerprintAllowed) {
292             return R.string.biometric_settings_use_fingerprint_preference_summary;
293         } else {
294             return 0;
295         }
296     }
297 }
298