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