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