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.biometrics.face; 18 19 import static android.app.Activity.RESULT_OK; 20 import static android.app.admin.DevicePolicyResources.Strings.Settings.FACE_SETTINGS_FOR_WORK_TITLE; 21 22 import static com.android.settings.Utils.isPrivateProfile; 23 import static com.android.settings.biometrics.BiometricEnrollBase.BIOMETRIC_AUTH_REQUEST; 24 import static com.android.settings.biometrics.BiometricEnrollBase.CONFIRM_REQUEST; 25 import static com.android.settings.biometrics.BiometricEnrollBase.ENROLL_REQUEST; 26 import static com.android.settings.biometrics.BiometricEnrollBase.RESULT_FINISHED; 27 import static com.android.settings.biometrics.BiometricEnrollBase.RESULT_TIMEOUT; 28 29 import android.app.admin.DevicePolicyManager; 30 import android.app.settings.SettingsEnums; 31 import android.content.Context; 32 import android.content.Intent; 33 import android.content.res.ResourceId; 34 import android.hardware.face.FaceManager; 35 import android.os.Bundle; 36 import android.os.UserHandle; 37 import android.os.UserManager; 38 import android.util.Log; 39 import android.widget.Button; 40 41 import androidx.preference.Preference; 42 import androidx.preference.PreferenceCategory; 43 44 import com.android.settings.R; 45 import com.android.settings.SettingsActivity; 46 import com.android.settings.Utils; 47 import com.android.settings.biometrics.BiometricEnrollBase; 48 import com.android.settings.biometrics.BiometricUtils; 49 import com.android.settings.biometrics.IdentityCheckBiometricErrorDialog; 50 import com.android.settings.dashboard.DashboardFragment; 51 import com.android.settings.flags.Flags; 52 import com.android.settings.overlay.FeatureFactory; 53 import com.android.settings.password.ChooseLockSettingsHelper; 54 import com.android.settings.password.ConfirmDeviceCredentialActivity; 55 import com.android.settings.search.BaseSearchIndexProvider; 56 import com.android.settingslib.RestrictedLockUtilsInternal; 57 import com.android.settingslib.core.AbstractPreferenceController; 58 import com.android.settingslib.search.SearchIndexable; 59 import com.android.settingslib.widget.LayoutPreference; 60 61 import java.util.ArrayList; 62 import java.util.Arrays; 63 import java.util.List; 64 65 /** 66 * Settings screen for face authentication. 67 */ 68 @SearchIndexable 69 public class FaceSettings extends DashboardFragment { 70 71 private static final String TAG = "FaceSettings"; 72 private static final String KEY_TOKEN = "hw_auth_token"; 73 private static final String KEY_CONFIRMING_PASSWORD = "confirming_password"; 74 private static final String KEY_RE_ENROLL_FACE = "re_enroll_face_unlock"; 75 private static final String KEY_BIOMETRICS_SUCCESSFULLY_AUTHENTICATED = 76 "biometrics_successfully_authenticated"; 77 78 private static final String PREF_KEY_FACE_DESCRIPTION = "security_settings_face_description"; 79 private static final String PREF_KEY_DELETE_FACE_DATA = 80 "security_settings_face_delete_faces_container"; 81 private static final String PREF_KEY_ENROLL_FACE_UNLOCK = 82 "security_settings_face_enroll_faces_container"; 83 private static final String PREF_KEY_USE_FACE_TO_CATEGORY = 84 "biometric_settings_use_face_to"; 85 private static final String PREF_KEY_FACE_ENROLLED_CATEGORY = 86 "security_settings_face_enrolled_category"; 87 private static final String PREF_KEY_FACE_REMOVE = 88 "security_settings_face_remove"; 89 private static final String PREF_KEY_FACE_ENROLL = 90 "security_settings_face_enroll"; 91 public static final String SECURITY_SETTINGS_FACE_MANAGE_CATEGORY = 92 "security_settings_face_manage_category"; 93 94 private UserManager mUserManager; 95 private FaceManager mFaceManager; 96 private DevicePolicyManager mDevicePolicyManager; 97 private int mUserId; 98 private int mSensorId; 99 private long mChallenge; 100 private byte[] mToken; 101 private FaceSettingsAttentionPreferenceController mAttentionController; 102 private FaceSettingsRemoveButtonPreferenceController mRemoveController; 103 private FaceSettingsEnrollButtonPreferenceController mEnrollController; 104 private FaceSettingsLockscreenBypassPreferenceController mLockscreenController; 105 private List<AbstractPreferenceController> mControllers; 106 107 private List<Preference> mTogglePreferences; 108 private Preference mRemoveButton; 109 private Preference mEnrollButton; 110 private PreferenceCategory mFaceEnrolledCategory; 111 private Preference mFaceRemoveButton; 112 private Preference mFaceEnrollButton; 113 private FaceFeatureProvider mFaceFeatureProvider; 114 115 private boolean mConfirmingPassword; 116 private boolean mBiometricsAuthenticationRequested; 117 118 private final FaceSettingsRemoveButtonPreferenceController.Listener mRemovalListener = () -> { 119 120 // Disable the toggles until the user re-enrolls 121 for (Preference preference : mTogglePreferences) { 122 preference.setEnabled(false); 123 } 124 125 // Hide the "remove" button and show the "set up face authentication" button. 126 updateFaceAddAndRemovePreference(false); 127 }; 128 129 private final FaceSettingsEnrollButtonPreferenceController.Listener mEnrollListener = intent -> 130 startActivityForResult(intent, ENROLL_REQUEST); 131 132 /** 133 * @param context 134 * @return true if the Face hardware is detected. 135 */ isFaceHardwareDetected(Context context)136 public static boolean isFaceHardwareDetected(Context context) { 137 FaceManager manager = Utils.getFaceManagerOrNull(context); 138 boolean isHardwareDetected = false; 139 if (manager == null) { 140 Log.d(TAG, "FaceManager is null"); 141 } else { 142 isHardwareDetected = manager.isHardwareDetected(); 143 Log.d(TAG, "FaceManager is not null. Hardware detected: " + isHardwareDetected); 144 } 145 return manager != null && isHardwareDetected; 146 } 147 148 @Override getMetricsCategory()149 public int getMetricsCategory() { 150 return SettingsEnums.FACE; 151 } 152 153 @Override getPreferenceScreenResId()154 protected int getPreferenceScreenResId() { 155 return R.xml.security_settings_face; 156 } 157 158 @Override getLogTag()159 protected String getLogTag() { 160 return TAG; 161 } 162 163 @Override onSaveInstanceState(Bundle outState)164 public void onSaveInstanceState(Bundle outState) { 165 super.onSaveInstanceState(outState); 166 outState.putByteArray(KEY_TOKEN, mToken); 167 outState.putBoolean(KEY_CONFIRMING_PASSWORD, mConfirmingPassword); 168 } 169 170 @Override onCreate(Bundle savedInstanceState)171 public void onCreate(Bundle savedInstanceState) { 172 super.onCreate(savedInstanceState); 173 174 final Context context = getPrefContext(); 175 if (!isFaceHardwareDetected(context)) { 176 Log.w(TAG, "no faceManager, finish this"); 177 finish(); 178 return; 179 } 180 181 mUserManager = context.getSystemService(UserManager.class); 182 mFaceManager = context.getSystemService(FaceManager.class); 183 mDevicePolicyManager = context.getSystemService(DevicePolicyManager.class); 184 mToken = getIntent().getByteArrayExtra(KEY_TOKEN); 185 mSensorId = getIntent().getIntExtra(BiometricEnrollBase.EXTRA_KEY_SENSOR_ID, -1); 186 mChallenge = getIntent().getLongExtra(BiometricEnrollBase.EXTRA_KEY_CHALLENGE, 0L); 187 188 mUserId = getActivity().getIntent().getIntExtra( 189 Intent.EXTRA_USER_ID, UserHandle.myUserId()); 190 mFaceFeatureProvider = FeatureFactory.getFeatureFactory().getFaceFeatureProvider(); 191 192 if (mUserManager.getUserInfo(mUserId).isManagedProfile()) { 193 getActivity().setTitle( 194 mDevicePolicyManager.getResources().getString(FACE_SETTINGS_FOR_WORK_TITLE, 195 () -> getActivity().getResources().getString( 196 R.string.security_settings_face_profile_preference_title))); 197 } else if (isPrivateProfile(mUserId, getContext())) { 198 getActivity().setTitle( 199 getActivity().getResources().getString( 200 R.string.private_space_face_unlock_title)); 201 } 202 203 mLockscreenController = Utils.isMultipleBiometricsSupported(context) 204 ? use(BiometricLockscreenBypassPreferenceController.class) 205 : use(FaceSettingsLockscreenBypassPreferenceController.class); 206 mLockscreenController.setUserId(mUserId); 207 208 final int descriptionResId = FeatureFactory.getFeatureFactory() 209 .getFaceFeatureProvider().getFaceSettingsFeatureProvider() 210 .getSettingPageDescription(); 211 if (ResourceId.isValid(descriptionResId)) { 212 final Preference preference = findPreference(PREF_KEY_FACE_DESCRIPTION); 213 preference.setTitle(descriptionResId); 214 preference.setVisible(true); 215 } 216 217 final PreferenceCategory managePref = 218 findPreference(SECURITY_SETTINGS_FACE_MANAGE_CATEGORY); 219 Preference keyguardPref = findPreference(FaceSettingsKeyguardPreferenceController.KEY); 220 Preference appPref = findPreference(FaceSettingsAppPreferenceController.KEY); 221 Preference attentionPref = findPreference(FaceSettingsAttentionPreferenceController.KEY); 222 Preference confirmPref = findPreference(FaceSettingsConfirmPreferenceController.KEY); 223 Preference bypassPref = 224 findPreference(mLockscreenController.getPreferenceKey()); 225 mTogglePreferences = new ArrayList<>( 226 Arrays.asList(keyguardPref, appPref, attentionPref, confirmPref, bypassPref)); 227 228 if (Flags.biometricsOnboardingEducation()) { 229 if (use(FaceSettingsKeyguardUnlockPreferenceController.class) != null) { 230 Preference unlockKeyguard = findPreference( 231 use(FaceSettingsKeyguardUnlockPreferenceController.class) 232 .getPreferenceKey()); 233 mTogglePreferences.add(unlockKeyguard); 234 } 235 if (use(FaceSettingsAppsPreferenceController.class) != null) { 236 Preference appsPref = findPreference( 237 use(FaceSettingsAppsPreferenceController.class).getPreferenceKey()); 238 mTogglePreferences.add(appsPref); 239 } 240 } 241 242 if (RestrictedLockUtilsInternal.checkIfKeyguardFeaturesDisabled( 243 getContext(), DevicePolicyManager.KEYGUARD_DISABLE_FACE, mUserId) != null) { 244 managePref.setTitle(getString( 245 com.android.settingslib.widget.restricted.R.string.disabled_by_admin)); 246 } else { 247 managePref.setTitle(R.string.security_settings_face_settings_preferences_category); 248 } 249 250 mRemoveButton = findPreference(FaceSettingsRemoveButtonPreferenceController.KEY); 251 mEnrollButton = findPreference(FaceSettingsEnrollButtonPreferenceController.KEY); 252 253 if (Flags.biometricsOnboardingEducation()) { 254 mFaceEnrolledCategory = findPreference(PREF_KEY_FACE_ENROLLED_CATEGORY); 255 mFaceRemoveButton = findPreference(PREF_KEY_FACE_REMOVE); 256 mFaceRemoveButton.setIcon(R.drawable.ic_face); 257 mFaceRemoveButton.setOnPreferenceClickListener( 258 use(FaceSettingsRemoveButtonPreferenceController.class)); 259 mFaceEnrollButton = findPreference(PREF_KEY_FACE_ENROLL); 260 mFaceEnrollButton.setIcon(R.drawable.ic_add_24dp); 261 mFaceEnrollButton.setOnPreferenceClickListener( 262 use(FaceSettingsEnrollButtonPreferenceController.class)); 263 } 264 265 final boolean hasEnrolled = mFaceManager.hasEnrolledTemplates(mUserId); 266 updateFaceAddAndRemovePreference(hasEnrolled); 267 268 // There is no better way to do this :/ 269 for (AbstractPreferenceController controller : mControllers) { 270 if (controller instanceof FaceSettingsPreferenceController) { 271 ((FaceSettingsPreferenceController) controller).setUserId(mUserId); 272 } else if (controller instanceof FaceSettingsEnrollButtonPreferenceController) { 273 ((FaceSettingsEnrollButtonPreferenceController) controller).setUserId(mUserId); 274 } else if (controller instanceof FaceSettingsFooterPreferenceController) { 275 ((FaceSettingsFooterPreferenceController) controller).setUserId(mUserId); 276 } 277 } 278 mRemoveController.setUserId(mUserId); 279 280 // Don't show keyguard controller for work and private profile settings. 281 if (mUserManager.isManagedProfile(mUserId) 282 || mUserManager.getUserInfo(mUserId).isPrivateProfile()) { 283 removePreference(FaceSettingsKeyguardPreferenceController.KEY); 284 removePreference(mLockscreenController.getPreferenceKey()); 285 } 286 287 if (savedInstanceState != null) { 288 mToken = savedInstanceState.getByteArray(KEY_TOKEN); 289 mConfirmingPassword = savedInstanceState.getBoolean(KEY_CONFIRMING_PASSWORD); 290 } 291 292 if (Flags.biometricsOnboardingEducation()) { 293 final PreferenceCategory category = 294 findPreference(PREF_KEY_USE_FACE_TO_CATEGORY); 295 category.setVisible(true); 296 use(FaceSettingsKeyguardUnlockPreferenceController.class).setUserId(mUserId); 297 use(FaceSettingsAppsPreferenceController.class).setUserId(mUserId); 298 } 299 } 300 301 @Override onStart()302 public void onStart() { 303 super.onStart(); 304 final boolean hasEnrolled = mFaceManager.hasEnrolledTemplates(mUserId); 305 updateFaceAddAndRemovePreference(hasEnrolled); 306 307 // When the user has face id registered but failed enrolling in device lock state, 308 // lead users directly to the confirm deletion dialog in Face Unlock settings. 309 if (hasEnrolled) { 310 final boolean isReEnrollFaceUnlock = getIntent().getBooleanExtra( 311 FaceSettings.KEY_RE_ENROLL_FACE, false); 312 if (isReEnrollFaceUnlock) { 313 if (Flags.biometricsOnboardingEducation()) { 314 if (mFaceRemoveButton.isEnabled()) { 315 mRemoveController.onPreferenceClick(mFaceRemoveButton); 316 } 317 } else { 318 final Button removeBtn = ((LayoutPreference) mRemoveButton).findViewById( 319 R.id.security_settings_face_settings_remove_button); 320 if (removeBtn != null && removeBtn.isEnabled()) { 321 mRemoveController.onClick(removeBtn); 322 } 323 } 324 } 325 } 326 } 327 328 @Override onResume()329 public void onResume() { 330 super.onResume(); 331 332 if (mToken == null && !mConfirmingPassword) { 333 final ChooseLockSettingsHelper.Builder builder = 334 new ChooseLockSettingsHelper.Builder(getActivity(), this); 335 final boolean launched = builder.setRequestCode(CONFIRM_REQUEST) 336 .setTitle(getString(R.string.security_settings_face_preference_title)) 337 .setRequestGatekeeperPasswordHandle(true) 338 .setUserId(mUserId) 339 .setForegroundOnly(true) 340 .setReturnCredentials(true) 341 .show(); 342 343 mConfirmingPassword = true; 344 if (!launched) { 345 Log.e(TAG, "Password not set"); 346 finish(); 347 } 348 } else { 349 mAttentionController.setToken(mToken); 350 mEnrollController.setToken(mToken); 351 } 352 353 if (!mFaceFeatureProvider.isAttentionSupported(getContext())) { 354 removePreference(FaceSettingsAttentionPreferenceController.KEY); 355 } 356 } 357 358 @Override onActivityResult(int requestCode, int resultCode, Intent data)359 public void onActivityResult(int requestCode, int resultCode, Intent data) { 360 super.onActivityResult(requestCode, resultCode, data); 361 362 if (mToken == null && !BiometricUtils.containsGatekeeperPasswordHandle(data)) { 363 Log.e(TAG, "No credential"); 364 finish(); 365 } 366 367 if (requestCode == CONFIRM_REQUEST) { 368 if (resultCode == RESULT_FINISHED || resultCode == RESULT_OK) { 369 // The pin/pattern/password was set. 370 mFaceManager.generateChallenge(mUserId, (sensorId, userId, challenge) -> { 371 mToken = BiometricUtils.requestGatekeeperHat(getPrefContext(), data, mUserId, 372 challenge); 373 mSensorId = sensorId; 374 mChallenge = challenge; 375 BiometricUtils.removeGatekeeperPasswordHandle(getPrefContext(), data); 376 mAttentionController.setToken(mToken); 377 mEnrollController.setToken(mToken); 378 mConfirmingPassword = false; 379 }); 380 381 final boolean hasEnrolled = mFaceManager.hasEnrolledTemplates(mUserId); 382 updateFaceAddAndRemovePreference(hasEnrolled); 383 final Utils.BiometricStatus biometricAuthStatus = 384 Utils.requestBiometricAuthenticationForMandatoryBiometrics(getActivity(), 385 mBiometricsAuthenticationRequested, 386 mUserId); 387 if (biometricAuthStatus == Utils.BiometricStatus.OK) { 388 Utils.launchBiometricPromptForMandatoryBiometrics(this, 389 BIOMETRIC_AUTH_REQUEST, 390 mUserId, true /* hideBackground */); 391 } else if (biometricAuthStatus != Utils.BiometricStatus.NOT_ACTIVE) { 392 IdentityCheckBiometricErrorDialog 393 .showBiometricErrorDialogAndFinishActivityOnDismiss(getActivity(), 394 biometricAuthStatus); 395 } 396 } 397 } else if (requestCode == ENROLL_REQUEST) { 398 if (resultCode == RESULT_TIMEOUT) { 399 setResult(resultCode, data); 400 finish(); 401 } 402 } else if (requestCode == BIOMETRIC_AUTH_REQUEST) { 403 mBiometricsAuthenticationRequested = false; 404 if (resultCode != RESULT_OK) { 405 if (resultCode == ConfirmDeviceCredentialActivity.BIOMETRIC_LOCKOUT_ERROR_RESULT) { 406 IdentityCheckBiometricErrorDialog 407 .showBiometricErrorDialogAndFinishActivityOnDismiss(getActivity(), 408 Utils.BiometricStatus.LOCKOUT); 409 } else { 410 finish(); 411 } 412 } 413 } 414 } 415 416 @Override onStop()417 public void onStop() { 418 super.onStop(); 419 420 if (!mEnrollController.isClicked() && !getActivity().isChangingConfigurations() 421 && !mConfirmingPassword) { 422 // Revoke challenge and finish 423 if (mToken != null) { 424 mFaceManager.revokeChallenge(mSensorId, mUserId, mChallenge); 425 mToken = null; 426 } 427 // Let parent "Face & Fingerprint Unlock" can use this error code to close itself. 428 setResult(RESULT_TIMEOUT); 429 finish(); 430 } 431 } 432 433 @Override getHelpResource()434 public int getHelpResource() { 435 return R.string.help_url_face; 436 } 437 438 @Override createPreferenceControllers(Context context)439 protected List<AbstractPreferenceController> createPreferenceControllers(Context context) { 440 if (!isFaceHardwareDetected(context)) { 441 return null; 442 } 443 mControllers = buildPreferenceControllers(context); 444 // There's no great way of doing this right now :/ 445 for (AbstractPreferenceController controller : mControllers) { 446 if (controller instanceof FaceSettingsAttentionPreferenceController) { 447 mAttentionController = (FaceSettingsAttentionPreferenceController) controller; 448 } else if (controller instanceof FaceSettingsRemoveButtonPreferenceController) { 449 mRemoveController = (FaceSettingsRemoveButtonPreferenceController) controller; 450 mRemoveController.setListener(mRemovalListener); 451 mRemoveController.setActivity((SettingsActivity) getActivity()); 452 } else if (controller instanceof FaceSettingsEnrollButtonPreferenceController) { 453 mEnrollController = (FaceSettingsEnrollButtonPreferenceController) controller; 454 mEnrollController.setListener(mEnrollListener); 455 } 456 } 457 458 return mControllers; 459 } 460 updateFaceAddAndRemovePreference(boolean hasEnrolled)461 private void updateFaceAddAndRemovePreference(boolean hasEnrolled) { 462 if (Flags.biometricsOnboardingEducation()) { 463 mFaceEnrolledCategory.setVisible(true); 464 mFaceRemoveButton.setVisible(hasEnrolled); 465 mFaceEnrollButton.setVisible(!hasEnrolled); 466 } else { 467 mEnrollButton.setVisible(!hasEnrolled); 468 mRemoveButton.setVisible(hasEnrolled); 469 } 470 } 471 buildPreferenceControllers(Context context)472 private static List<AbstractPreferenceController> buildPreferenceControllers(Context context) { 473 final List<AbstractPreferenceController> controllers = new ArrayList<>(); 474 controllers.add(new FaceSettingsKeyguardPreferenceController(context)); 475 controllers.add(new FaceSettingsAppPreferenceController(context)); 476 controllers.add(new FaceSettingsAttentionPreferenceController(context)); 477 controllers.add(new FaceSettingsRemoveButtonPreferenceController(context)); 478 controllers.add(new FaceSettingsConfirmPreferenceController(context)); 479 controllers.add(new FaceSettingsEnrollButtonPreferenceController(context)); 480 controllers.add(new FaceSettingsFooterPreferenceController(context)); 481 return controllers; 482 } 483 484 public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = 485 new BaseSearchIndexProvider(R.xml.security_settings_face) { 486 487 @Override 488 public List<AbstractPreferenceController> createPreferenceControllers( 489 Context context) { 490 if (isFaceHardwareDetected(context)) { 491 return buildPreferenceControllers(context); 492 } else { 493 return null; 494 } 495 } 496 497 @Override 498 protected boolean isPageSearchEnabled(Context context) { 499 if (isFaceHardwareDetected(context)) { 500 return hasEnrolledBiometrics(context); 501 } 502 503 return false; 504 } 505 506 @Override 507 public List<String> getNonIndexableKeys(Context context) { 508 final List<String> keys = super.getNonIndexableKeys(context); 509 final boolean isFaceHardwareDetected = isFaceHardwareDetected(context); 510 Log.d(TAG, "Get non indexable keys. isFaceHardwareDetected: " 511 + isFaceHardwareDetected + ", size:" + keys.size()); 512 if (isFaceHardwareDetected) { 513 final boolean hasEnrolled = hasEnrolledBiometrics(context); 514 keys.add(hasEnrolled ? PREF_KEY_ENROLL_FACE_UNLOCK 515 : PREF_KEY_DELETE_FACE_DATA); 516 } 517 518 if (!isAttentionSupported(context)) { 519 keys.add(FaceSettingsAttentionPreferenceController.KEY); 520 } 521 522 return keys; 523 } 524 525 private boolean isAttentionSupported(Context context) { 526 FaceFeatureProvider featureProvider = 527 FeatureFactory.getFeatureFactory().getFaceFeatureProvider(); 528 return featureProvider.isAttentionSupported(context); 529 } 530 531 private boolean hasEnrolledBiometrics(Context context) { 532 final FaceManager faceManager = Utils.getFaceManagerOrNull(context); 533 if (faceManager != null) { 534 return faceManager.hasEnrolledTemplates(UserHandle.myUserId()); 535 } 536 return false; 537 } 538 }; 539 } 540