1 /* 2 * Copyright (C) 2015 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.fingerprint; 18 19 20 import static android.app.admin.DevicePolicyResources.Strings.Settings.FINGERPRINT_UNLOCK_DISABLED_EXPLANATION; 21 import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_FINGERPRINT_LAST_DELETE_MESSAGE; 22 import static android.app.admin.DevicePolicyResources.UNDEFINED; 23 24 import static com.android.settings.Utils.SETTINGS_PACKAGE_NAME; 25 import static com.android.settings.Utils.isPrivateProfile; 26 import static com.android.settings.biometrics.BiometricEnrollBase.EXTRA_FROM_SETTINGS_SUMMARY; 27 import static com.android.settings.biometrics.BiometricEnrollBase.EXTRA_KEY_CHALLENGE; 28 29 import android.app.Activity; 30 import android.app.Dialog; 31 import android.app.admin.DevicePolicyManager; 32 import android.app.settings.SettingsEnums; 33 import android.content.Context; 34 import android.content.DialogInterface; 35 import android.content.Intent; 36 import android.graphics.drawable.Drawable; 37 import android.hardware.fingerprint.Fingerprint; 38 import android.hardware.fingerprint.FingerprintManager; 39 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; 40 import android.os.Bundle; 41 import android.os.Handler; 42 import android.os.UserHandle; 43 import android.os.UserManager; 44 import android.text.InputFilter; 45 import android.text.Spanned; 46 import android.text.TextUtils; 47 import android.util.FeatureFlagUtils; 48 import android.util.Log; 49 import android.view.View; 50 import android.widget.ImeAwareEditText; 51 import android.widget.Toast; 52 53 import androidx.annotation.NonNull; 54 import androidx.annotation.Nullable; 55 import androidx.annotation.VisibleForTesting; 56 import androidx.appcompat.app.AlertDialog; 57 import androidx.preference.Preference; 58 import androidx.preference.Preference.OnPreferenceChangeListener; 59 import androidx.preference.PreferenceCategory; 60 import androidx.preference.PreferenceGroup; 61 import androidx.preference.PreferenceScreen; 62 import androidx.preference.PreferenceViewHolder; 63 import androidx.preference.TwoStatePreference; 64 65 import com.android.internal.widget.LockPatternUtils; 66 import com.android.settings.R; 67 import com.android.settings.SubSettings; 68 import com.android.settings.Utils; 69 import com.android.settings.biometrics.BiometricEnrollBase; 70 import com.android.settings.biometrics.BiometricUtils; 71 import com.android.settings.biometrics.GatekeeperPasswordProvider; 72 import com.android.settings.biometrics2.ui.model.EnrollmentRequest; 73 import com.android.settings.biometrics2.ui.view.FingerprintEnrollmentActivity; 74 import com.android.settings.core.SettingsBaseActivity; 75 import com.android.settings.core.instrumentation.InstrumentedDialogFragment; 76 import com.android.settings.dashboard.DashboardFragment; 77 import com.android.settings.overlay.FeatureFactory; 78 import com.android.settings.password.ChooseLockGeneric; 79 import com.android.settings.password.ChooseLockSettingsHelper; 80 import com.android.settings.search.BaseSearchIndexProvider; 81 import com.android.settingslib.HelpUtils; 82 import com.android.settingslib.RestrictedLockUtils; 83 import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; 84 import com.android.settingslib.RestrictedLockUtilsInternal; 85 import com.android.settingslib.RestrictedSwitchPreference; 86 import com.android.settingslib.core.AbstractPreferenceController; 87 import com.android.settingslib.search.SearchIndexable; 88 import com.android.settingslib.transition.SettingsTransitionHelper; 89 import com.android.settingslib.widget.FooterPreference; 90 import com.android.settingslib.widget.TwoTargetPreference; 91 92 import com.google.android.setupdesign.util.DeviceHelper; 93 94 import java.util.ArrayList; 95 import java.util.Collections; 96 import java.util.HashMap; 97 import java.util.List; 98 99 /** 100 * Settings screen for fingerprints 101 */ 102 public class FingerprintSettings extends SubSettings { 103 104 private static final String TAG = "FingerprintSettings"; 105 106 private static final long LOCKOUT_DURATION = 30000; // time we have to wait for fp to reset, ms 107 108 public static final String ANNOTATION_URL = "url"; 109 public static final String ANNOTATION_ADMIN_DETAILS = "admin_details"; 110 111 private static final int RESULT_FINISHED = BiometricEnrollBase.RESULT_FINISHED; 112 private static final int RESULT_SKIP = BiometricEnrollBase.RESULT_SKIP; 113 private static final int RESULT_TIMEOUT = BiometricEnrollBase.RESULT_TIMEOUT; 114 115 @Override getIntent()116 public Intent getIntent() { 117 Intent modIntent = new Intent(super.getIntent()); 118 modIntent.putExtra(EXTRA_SHOW_FRAGMENT, FingerprintSettingsFragment.class.getName()); 119 return modIntent; 120 } 121 122 @Override isValidFragment(String fragmentName)123 protected boolean isValidFragment(String fragmentName) { 124 if (FingerprintSettingsFragment.class.getName().equals(fragmentName)) return true; 125 return false; 126 } 127 128 @Override onCreate(Bundle savedInstanceState)129 public void onCreate(Bundle savedInstanceState) { 130 super.onCreate(savedInstanceState); 131 final int userId = getIntent().getIntExtra(Intent.EXTRA_USER_ID, UserHandle.myUserId()); 132 CharSequence msg = getText(isPrivateProfile(userId, this) 133 ? R.string.private_space_fingerprint_unlock_title 134 : R.string.security_settings_fingerprint_preference_title); 135 setTitle(msg); 136 } 137 138 /** 139 * @param context 140 * @return true if the Fingerprint hardware is detected. 141 */ isFingerprintHardwareDetected(Context context)142 public static boolean isFingerprintHardwareDetected(Context context) { 143 FingerprintManager manager = Utils.getFingerprintManagerOrNull(context); 144 boolean isHardwareDetected = false; 145 if (manager == null) { 146 Log.d(TAG, "FingerprintManager is null"); 147 } else { 148 isHardwareDetected = manager.isHardwareDetected(); 149 Log.d(TAG, "FingerprintManager is not null. Hardware detected: " + isHardwareDetected); 150 } 151 return manager != null && isHardwareDetected; 152 } 153 154 155 /** 156 * 157 */ 158 @SearchIndexable 159 public static class FingerprintSettingsFragment extends DashboardFragment 160 implements OnPreferenceChangeListener, FingerprintPreference.OnDeleteClickListener { 161 162 public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = 163 new BaseSearchIndexProvider(R.xml.security_settings_fingerprint) { 164 @Override 165 public List<AbstractPreferenceController> 166 createPreferenceControllers(Context context) { 167 return createThePreferenceControllers(context); 168 } 169 }; 170 createThePreferenceControllers(Context context)171 private static List<AbstractPreferenceController> createThePreferenceControllers(Context 172 context) { 173 final List<AbstractPreferenceController> controllers = new ArrayList<>(); 174 FingerprintManager manager = Utils.getFingerprintManagerOrNull(context); 175 if (manager == null || !manager.isHardwareDetected()) { 176 return null; 177 } 178 if (manager.isPowerbuttonFps()) { 179 controllers.add( 180 new FingerprintUnlockCategoryController( 181 context, 182 KEY_FINGERPRINT_UNLOCK_CATEGORY 183 )); 184 controllers.add( 185 new FingerprintSettingsRequireScreenOnToAuthPreferenceController( 186 context, 187 KEY_REQUIRE_SCREEN_ON_TO_AUTH 188 )); 189 } 190 controllers.add(new FingerprintsEnrolledCategoryPreferenceController(context, 191 KEY_FINGERPRINTS_ENROLLED_CATEGORY)); 192 return controllers; 193 } 194 195 private static class FooterColumn { 196 CharSequence mTitle = null; 197 CharSequence mLearnMoreOverrideText = null; 198 View.OnClickListener mLearnMoreClickListener = null; 199 } 200 201 private static final int RESET_HIGHLIGHT_DELAY_MS = 500; 202 203 private static final String TAG = "FingerprintSettings"; 204 private static final String KEY_FINGERPRINT_ITEM_PREFIX = "key_fingerprint_item"; 205 @VisibleForTesting 206 static final String KEY_FINGERPRINT_ADD = "key_fingerprint_add"; 207 private static final String KEY_FINGERPRINT_ENABLE_KEYGUARD_TOGGLE = 208 "fingerprint_enable_keyguard_toggle"; 209 private static final String KEY_LAUNCHED_CONFIRM = "launched_confirm"; 210 private static final String KEY_HAS_FIRST_ENROLLED = "has_first_enrolled"; 211 private static final String KEY_IS_ENROLLING = "is_enrolled"; 212 @VisibleForTesting 213 static final String KEY_REQUIRE_SCREEN_ON_TO_AUTH = 214 "security_settings_require_screen_on_to_auth"; 215 private static final String KEY_FINGERPRINTS_ENROLLED_CATEGORY = 216 "security_settings_fingerprints_enrolled"; 217 private static final String KEY_FINGERPRINT_UNLOCK_CATEGORY = 218 "security_settings_fingerprint_unlock_category"; 219 private static final String KEY_FINGERPRINT_UNLOCK_FOOTER = 220 "security_settings_fingerprint_footer"; 221 222 private static final int MSG_REFRESH_FINGERPRINT_TEMPLATES = 1000; 223 private static final int MSG_FINGER_AUTH_SUCCESS = 1001; 224 private static final int MSG_FINGER_AUTH_FAIL = 1002; 225 private static final int MSG_FINGER_AUTH_ERROR = 1003; 226 private static final int MSG_FINGER_AUTH_HELP = 1004; 227 228 private static final int CONFIRM_REQUEST = 101; 229 @VisibleForTesting 230 static final int CHOOSE_LOCK_GENERIC_REQUEST = 102; 231 @VisibleForTesting 232 static final int ADD_FINGERPRINT_REQUEST = 10; 233 private static final int AUTO_ADD_FIRST_FINGERPRINT_REQUEST = 11; 234 235 protected static final boolean DEBUG = false; 236 237 private List<AbstractPreferenceController> mControllers; 238 private FingerprintUnlockCategoryController 239 mFingerprintUnlockCategoryPreferenceController; 240 private FingerprintSettingsRequireScreenOnToAuthPreferenceController 241 mRequireScreenOnToAuthPreferenceController; 242 private Preference mAddFingerprintPreference; 243 private RestrictedSwitchPreference mRequireScreenOnToAuthPreference; 244 private PreferenceCategory mFingerprintsEnrolledCategory; 245 private PreferenceCategory mFingerprintUnlockCategory; 246 private PreferenceCategory mFingerprintUnlockFooter; 247 248 private FingerprintManager mFingerprintManager; 249 private FingerprintUpdater mFingerprintUpdater; 250 private List<FingerprintSensorPropertiesInternal> mSensorProperties; 251 private boolean mInFingerprintLockout; 252 private byte[] mToken; 253 private boolean mLaunchedConfirm; 254 private boolean mHasFirstEnrolled = true; 255 private Drawable mHighlightDrawable; 256 private int mUserId; 257 private final List<FooterColumn> mFooterColumns = new ArrayList<>(); 258 private boolean mIsEnrolling; 259 260 private long mChallenge; 261 262 private static final String TAG_AUTHENTICATE_SIDECAR = "authenticate_sidecar"; 263 private static final String TAG_REMOVAL_SIDECAR = "removal_sidecar"; 264 private FingerprintAuthenticateSidecar mAuthenticateSidecar; 265 private FingerprintRemoveSidecar mRemovalSidecar; 266 private HashMap<Integer, String> mFingerprintsRenaming; 267 268 @Nullable 269 private UdfpsEnrollCalibrator mCalibrator; 270 271 FingerprintAuthenticateSidecar.Listener mAuthenticateListener = 272 new FingerprintAuthenticateSidecar.Listener() { 273 @Override 274 public void onAuthenticationSucceeded( 275 FingerprintManager.AuthenticationResult result) { 276 int fingerId = result.getFingerprint().getBiometricId(); 277 mHandler.obtainMessage(MSG_FINGER_AUTH_SUCCESS, fingerId, 0).sendToTarget(); 278 } 279 280 @Override 281 public void onAuthenticationFailed() { 282 mHandler.obtainMessage(MSG_FINGER_AUTH_FAIL).sendToTarget(); 283 } 284 285 @Override 286 public void onAuthenticationError(int errMsgId, CharSequence errString) { 287 mHandler.obtainMessage(MSG_FINGER_AUTH_ERROR, errMsgId, 0, errString) 288 .sendToTarget(); 289 } 290 291 @Override 292 public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) { 293 mHandler.obtainMessage(MSG_FINGER_AUTH_HELP, helpMsgId, 0, helpString) 294 .sendToTarget(); 295 } 296 }; 297 298 FingerprintRemoveSidecar.Listener mRemovalListener = 299 new FingerprintRemoveSidecar.Listener() { 300 public void onRemovalSucceeded(Fingerprint fingerprint) { 301 mHandler.obtainMessage(MSG_REFRESH_FINGERPRINT_TEMPLATES, 302 fingerprint.getBiometricId(), 0).sendToTarget(); 303 updateDialog(); 304 } 305 306 public void onRemovalError(Fingerprint fp, int errMsgId, 307 CharSequence errString) { 308 final Activity activity = getActivity(); 309 if (activity != null) { 310 Toast.makeText(activity, errString, Toast.LENGTH_SHORT); 311 } 312 updateDialog(); 313 } 314 315 private void updateDialog() { 316 RenameDialog renameDialog = (RenameDialog) getFragmentManager(). 317 findFragmentByTag(RenameDialog.class.getName()); 318 if (renameDialog != null) { 319 renameDialog.enableDelete(); 320 } 321 } 322 }; 323 324 private final Handler mHandler = new Handler() { 325 @Override 326 public void handleMessage(android.os.Message msg) { 327 switch (msg.what) { 328 case MSG_REFRESH_FINGERPRINT_TEMPLATES: 329 removeFingerprintPreference(msg.arg1); 330 updateAddPreference(); 331 if (isSfps()) { 332 updateFingerprintUnlockCategoryVisibility(); 333 } 334 updatePreferences(); 335 break; 336 case MSG_FINGER_AUTH_SUCCESS: 337 highlightFingerprintItem(msg.arg1); 338 retryFingerprint(); 339 break; 340 case MSG_FINGER_AUTH_FAIL: 341 // No action required... fingerprint will allow up to 5 of these 342 break; 343 case MSG_FINGER_AUTH_ERROR: 344 handleError(msg.arg1 /* errMsgId */, (CharSequence) msg.obj /* errStr */); 345 break; 346 case MSG_FINGER_AUTH_HELP: { 347 // Not used 348 } 349 break; 350 } 351 } 352 }; 353 354 /** 355 * 356 */ handleError(int errMsgId, CharSequence msg)357 protected void handleError(int errMsgId, CharSequence msg) { 358 switch (errMsgId) { 359 case FingerprintManager.FINGERPRINT_ERROR_CANCELED: 360 case FingerprintManager.FINGERPRINT_ERROR_USER_CANCELED: 361 // Only happens if we get preempted by another activity, or canceled by the 362 // user (e.g. swipe up to home). Ignored. 363 return; 364 case FingerprintManager.FINGERPRINT_ERROR_LOCKOUT: 365 mInFingerprintLockout = true; 366 // We've been locked out. Reset after 30s. 367 if (!mHandler.hasCallbacks(mFingerprintLockoutReset)) { 368 mHandler.postDelayed(mFingerprintLockoutReset, 369 LOCKOUT_DURATION); 370 } 371 break; 372 case FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT: 373 mInFingerprintLockout = true; 374 break; 375 } 376 377 if (mInFingerprintLockout) { 378 // Activity can be null on a screen rotation. 379 final Activity activity = getActivity(); 380 if (activity != null) { 381 Toast.makeText(activity, msg, Toast.LENGTH_SHORT).show(); 382 } 383 } 384 retryFingerprint(); // start again 385 } 386 retryFingerprint()387 private void retryFingerprint() { 388 if (isUdfps()) { 389 // Do not authenticate for UDFPS devices. 390 return; 391 } 392 393 if (mRemovalSidecar.inProgress() 394 || 0 == mFingerprintManager.getEnrolledFingerprints(mUserId).size()) { 395 return; 396 } 397 // Don't start authentication if ChooseLockGeneric is showing, otherwise if the user 398 // is in FP lockout, a toast will show on top 399 if (mLaunchedConfirm) { 400 return; 401 } 402 if (!mInFingerprintLockout) { 403 mAuthenticateSidecar.startAuthentication(mUserId); 404 mAuthenticateSidecar.setListener(mAuthenticateListener); 405 } 406 } 407 408 @Override getMetricsCategory()409 public int getMetricsCategory() { 410 return SettingsEnums.FINGERPRINT; 411 } 412 413 @Override onCreate(Bundle savedInstanceState)414 public void onCreate(Bundle savedInstanceState) { 415 super.onCreate(savedInstanceState); 416 417 Activity activity = getActivity(); 418 mFingerprintManager = Utils.getFingerprintManagerOrNull(activity); 419 mFingerprintUpdater = new FingerprintUpdater(activity, mFingerprintManager); 420 mSensorProperties = mFingerprintManager.getSensorPropertiesInternal(); 421 422 mToken = getIntent().getByteArrayExtra( 423 ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN); 424 mChallenge = activity.getIntent() 425 .getLongExtra(BiometricEnrollBase.EXTRA_KEY_CHALLENGE, -1L); 426 427 mAuthenticateSidecar = (FingerprintAuthenticateSidecar) 428 getFragmentManager().findFragmentByTag(TAG_AUTHENTICATE_SIDECAR); 429 if (mAuthenticateSidecar == null) { 430 mAuthenticateSidecar = new FingerprintAuthenticateSidecar(); 431 getFragmentManager().beginTransaction() 432 .add(mAuthenticateSidecar, TAG_AUTHENTICATE_SIDECAR).commit(); 433 } 434 mAuthenticateSidecar.setFingerprintManager(mFingerprintManager); 435 436 mRemovalSidecar = (FingerprintRemoveSidecar) 437 getFragmentManager().findFragmentByTag(TAG_REMOVAL_SIDECAR); 438 if (mRemovalSidecar == null) { 439 mRemovalSidecar = new FingerprintRemoveSidecar(); 440 getFragmentManager().beginTransaction() 441 .add(mRemovalSidecar, TAG_REMOVAL_SIDECAR).commit(); 442 } 443 mRemovalSidecar.setFingerprintUpdater(mFingerprintUpdater); 444 mRemovalSidecar.setListener(mRemovalListener); 445 446 RenameDialog renameDialog = (RenameDialog) getFragmentManager(). 447 findFragmentByTag(RenameDialog.class.getName()); 448 if (renameDialog != null) { 449 renameDialog.setDeleteInProgress(mRemovalSidecar.inProgress()); 450 } 451 452 mFingerprintsRenaming = new HashMap<Integer, String>(); 453 mUserId = getActivity().getIntent().getIntExtra( 454 Intent.EXTRA_USER_ID, UserHandle.myUserId()); 455 mHasFirstEnrolled = mFingerprintManager.hasEnrolledFingerprints(mUserId); 456 457 if (savedInstanceState != null) { 458 mFingerprintsRenaming = (HashMap<Integer, String>) 459 savedInstanceState.getSerializable("mFingerprintsRenaming"); 460 mToken = savedInstanceState.getByteArray( 461 ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN); 462 mLaunchedConfirm = savedInstanceState.getBoolean( 463 KEY_LAUNCHED_CONFIRM, false); 464 mIsEnrolling = savedInstanceState.getBoolean(KEY_IS_ENROLLING, mIsEnrolling); 465 mHasFirstEnrolled = savedInstanceState.getBoolean(KEY_HAS_FIRST_ENROLLED, 466 mHasFirstEnrolled); 467 } 468 469 // (mLaunchedConfirm or mIsEnrolling) means that we are waiting an activity result. 470 if (!mLaunchedConfirm && !mIsEnrolling) { 471 // Need to authenticate a session token if none 472 if (mToken == null) { 473 mLaunchedConfirm = true; 474 launchChooseOrConfirmLock(); 475 } else if (!mHasFirstEnrolled) { 476 mIsEnrolling = true; 477 addFirstFingerprint(null); 478 } 479 } 480 final PreferenceScreen root = getPreferenceScreen(); 481 root.removeAll(); 482 addPreferencesFromResource(getPreferenceScreenResId()); 483 updateFooterColumns(activity); 484 } 485 updateFooterColumns(@onNull Activity activity)486 private void updateFooterColumns(@NonNull Activity activity) { 487 final EnforcedAdmin admin = RestrictedLockUtilsInternal.checkIfKeyguardFeaturesDisabled( 488 activity, DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT, mUserId); 489 final Intent helpIntent = HelpUtils.getHelpIntent( 490 activity, getString(getHelpResource()), activity.getClass().getName()); 491 final View.OnClickListener learnMoreClickListener = (v) -> 492 activity.startActivityForResult(helpIntent, 0); 493 494 mFooterColumns.clear(); 495 if (admin != null) { 496 final DevicePolicyManager devicePolicyManager = 497 getSystemService(DevicePolicyManager.class); 498 final FooterColumn column1 = new FooterColumn(); 499 column1.mTitle = devicePolicyManager.getResources().getString( 500 FINGERPRINT_UNLOCK_DISABLED_EXPLANATION, 501 () -> getString( 502 R.string.security_fingerprint_disclaimer_lockscreen_disabled_1 503 ) 504 ); 505 column1.mLearnMoreClickListener = (v) -> RestrictedLockUtils 506 .sendShowAdminSupportDetailsIntent(activity, admin); 507 column1.mLearnMoreOverrideText = getText(R.string.admin_support_more_info); 508 mFooterColumns.add(column1); 509 510 final FooterColumn column2 = new FooterColumn(); 511 column2.mTitle = getText( 512 R.string.security_fingerprint_disclaimer_lockscreen_disabled_2 513 ); 514 if (isSfps()) { 515 column2.mLearnMoreOverrideText = getText( 516 R.string.security_settings_fingerprint_settings_footer_learn_more); 517 } 518 column2.mLearnMoreClickListener = learnMoreClickListener; 519 mFooterColumns.add(column2); 520 } else { 521 final FooterColumn column = new FooterColumn(); 522 column.mTitle = getString(isPrivateProfile() 523 ? R.string.private_space_fingerprint_enroll_introduction_message 524 : R.string.security_settings_fingerprint_enroll_introduction_v3_message, 525 DeviceHelper.getDeviceName(getActivity())); 526 column.mLearnMoreClickListener = learnMoreClickListener; 527 column.mLearnMoreOverrideText = getText( 528 R.string.security_settings_fingerprint_settings_footer_learn_more); 529 mFooterColumns.add(column); 530 } 531 } 532 isUdfps()533 private boolean isUdfps() { 534 for (FingerprintSensorPropertiesInternal prop : mSensorProperties) { 535 if (prop.isAnyUdfpsType()) { 536 return true; 537 } 538 } 539 return false; 540 } 541 isSfps()542 private boolean isSfps() { 543 mFingerprintManager = Utils.getFingerprintManagerOrNull(getActivity()); 544 if (mFingerprintManager != null) { 545 mSensorProperties = mFingerprintManager.getSensorPropertiesInternal(); 546 for (FingerprintSensorPropertiesInternal prop : mSensorProperties) { 547 if (prop.isAnySidefpsType()) { 548 return true; 549 } 550 } 551 } 552 return false; 553 } 554 removeFingerprintPreference(int fingerprintId)555 protected void removeFingerprintPreference(int fingerprintId) { 556 String name = genKey(fingerprintId); 557 Preference prefToRemove = findPreference(name); 558 if (prefToRemove != null) { 559 if (!getPreferenceScreen().removePreference(prefToRemove)) { 560 Log.w(TAG, "Failed to remove preference with key " + name); 561 } 562 } else { 563 Log.w(TAG, "Can't find preference to remove: " + name); 564 } 565 } 566 567 /** 568 * Important! 569 * 570 * Don't forget to update the SecuritySearchIndexProvider if you are doing any change in the 571 * logic or adding/removing preferences here. 572 */ createPreferenceHierarchy()573 private PreferenceScreen createPreferenceHierarchy() { 574 PreferenceScreen root = getPreferenceScreen(); 575 addFingerprintPreferences(root); 576 setPreferenceScreen(root); 577 return root; 578 } 579 addFingerprintPreferences(PreferenceGroup root)580 private void addFingerprintPreferences(PreferenceGroup root) { 581 final String fpPrefKey = addFingerprintItemPreferences(root); 582 for (AbstractPreferenceController controller : mControllers) { 583 if (controller instanceof FingerprintSettingsPreferenceController) { 584 ((FingerprintSettingsPreferenceController) controller).setUserId(mUserId); 585 } else if (controller instanceof FingerprintUnlockCategoryController) { 586 ((FingerprintUnlockCategoryController) controller).setUserId(mUserId); 587 } 588 } 589 590 // This needs to be after setting ids, otherwise 591 // |mRequireScreenOnToAuthPreferenceController.isChecked| is always checking the primary 592 // user instead of the user with |mUserId|. 593 if (isSfps()) { 594 scrollToPreference(fpPrefKey); 595 addFingerprintUnlockCategory(); 596 } 597 createFooterPreference(root); 598 } 599 addFingerprintItemPreferences(PreferenceGroup root)600 private String addFingerprintItemPreferences(PreferenceGroup root) { 601 mFingerprintsEnrolledCategory = findPreference(KEY_FINGERPRINTS_ENROLLED_CATEGORY); 602 if (mFingerprintsEnrolledCategory != null) { 603 mFingerprintsEnrolledCategory.removeAll(); 604 } 605 606 String keyToReturn = KEY_FINGERPRINT_ADD; 607 final List<Fingerprint> items = mFingerprintManager.getEnrolledFingerprints(mUserId); 608 final int fingerprintCount = items.size(); 609 for (int i = 0; i < fingerprintCount; i++) { 610 final Fingerprint item = items.get(i); 611 FingerprintPreference pref = new FingerprintPreference(root.getContext(), 612 this /* onDeleteClickListener */); 613 String key = genKey(item.getBiometricId()); 614 if (i == 0) { 615 keyToReturn = key; 616 } 617 pref.setKey(key); 618 pref.setTitle(item.getName()); 619 pref.setFingerprint(item); 620 pref.setPersistent(false); 621 pref.setIcon(R.drawable.ic_fingerprint_24dp); 622 if (mRemovalSidecar.isRemovingFingerprint(item.getBiometricId())) { 623 pref.setEnabled(false); 624 } 625 if (mFingerprintsRenaming.containsKey(item.getBiometricId())) { 626 pref.setTitle(mFingerprintsRenaming.get(item.getBiometricId())); 627 } 628 mFingerprintsEnrolledCategory.addPreference(pref); 629 pref.setOnPreferenceChangeListener(this); 630 } 631 mAddFingerprintPreference = findPreference(KEY_FINGERPRINT_ADD); 632 setupAddFingerprintPreference(); 633 return keyToReturn; 634 } 635 setupAddFingerprintPreference()636 private void setupAddFingerprintPreference() { 637 mAddFingerprintPreference.setOnPreferenceChangeListener(this); 638 updateAddPreference(); 639 } 640 addFingerprintUnlockCategory()641 private void addFingerprintUnlockCategory() { 642 mFingerprintUnlockCategory = findPreference(KEY_FINGERPRINT_UNLOCK_CATEGORY); 643 setupFingerprintUnlockCategoryPreferences(); 644 final Preference restToUnlockPreference = FeatureFactory.getFeatureFactory() 645 .getFingerprintFeatureProvider() 646 .getSfpsRestToUnlockFeature(getContext()) 647 .getRestToUnlockPreference(getContext()); 648 if (restToUnlockPreference != null) { 649 // Use custom featured preference if any. 650 mRequireScreenOnToAuthPreference.setTitle(restToUnlockPreference.getTitle()); 651 mRequireScreenOnToAuthPreference.setSummary(restToUnlockPreference.getSummary()); 652 mRequireScreenOnToAuthPreference.setChecked( 653 ((TwoStatePreference) restToUnlockPreference).isChecked()); 654 mRequireScreenOnToAuthPreference.setOnPreferenceChangeListener( 655 restToUnlockPreference.getOnPreferenceChangeListener()); 656 } 657 updateFingerprintUnlockCategoryVisibility(); 658 } 659 updateFingerprintUnlockCategoryVisibility()660 private void updateFingerprintUnlockCategoryVisibility() { 661 final boolean mFingerprintUnlockCategoryAvailable = 662 mFingerprintUnlockCategoryPreferenceController.isAvailable(); 663 if (mFingerprintUnlockCategory.isVisible() != mFingerprintUnlockCategoryAvailable) { 664 mFingerprintUnlockCategory.setVisible( 665 mFingerprintUnlockCategoryAvailable); 666 } 667 } 668 setupFingerprintUnlockCategoryPreferences()669 private void setupFingerprintUnlockCategoryPreferences() { 670 mRequireScreenOnToAuthPreference = findPreference(KEY_REQUIRE_SCREEN_ON_TO_AUTH); 671 mRequireScreenOnToAuthPreference.setChecked( 672 mRequireScreenOnToAuthPreferenceController.isChecked()); 673 mRequireScreenOnToAuthPreference.setOnPreferenceChangeListener( 674 (preference, newValue) -> { 675 final boolean isChecked = ((TwoStatePreference) preference).isChecked(); 676 mRequireScreenOnToAuthPreferenceController.setChecked(!isChecked); 677 return true; 678 }); 679 } 680 updateAddPreference()681 private void updateAddPreference() { 682 if (getActivity() == null) { 683 return; // Activity went away 684 } 685 686 mAddFingerprintPreference = findPreference(KEY_FINGERPRINT_ADD); 687 688 if (mAddFingerprintPreference == null) { 689 return; // b/275519315 Skip if updateAddPreference() invoke before addPreference() 690 } 691 692 /* Disable preference if too many fingerprints added */ 693 final int max = getContext().getResources().getInteger( 694 com.android.internal.R.integer.config_fingerprintMaxTemplatesPerUser); 695 boolean tooMany = mFingerprintManager.getEnrolledFingerprints(mUserId).size() >= max; 696 // retryFingerprint() will be called when remove finishes 697 // need to disable enroll or have a way to determine if enroll is in progress 698 final boolean removalInProgress = mRemovalSidecar.inProgress(); 699 final boolean isDeviceOwnerBlockingAuth = 700 RestrictedLockUtilsInternal.checkIfKeyguardFeaturesDisabled( 701 getContext(), DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT, 702 mUserId) != null; 703 704 CharSequence maxSummary = tooMany ? 705 getContext().getString(R.string.fingerprint_add_max, max) : ""; 706 mAddFingerprintPreference.setSummary(maxSummary); 707 mAddFingerprintPreference.setEnabled(!isDeviceOwnerBlockingAuth 708 && !tooMany && !removalInProgress && mToken != null); 709 } 710 createFooterPreference(PreferenceGroup root)711 private void createFooterPreference(PreferenceGroup root) { 712 final Context context = getActivity(); 713 if (context == null) { 714 return; 715 } 716 mFingerprintUnlockFooter = findPreference(KEY_FINGERPRINT_UNLOCK_FOOTER); 717 if (mFingerprintUnlockFooter != null) { 718 mFingerprintUnlockFooter.removeAll(); 719 } 720 for (int i = 0; i < mFooterColumns.size(); ++i) { 721 final FooterColumn column = mFooterColumns.get(i); 722 final FooterPreference footer = new FooterPreference.Builder(context) 723 .setTitle(column.mTitle).build(); 724 if (i > 0) { 725 footer.setIconVisibility(View.GONE); 726 } 727 if (column.mLearnMoreClickListener != null) { 728 footer.setLearnMoreAction(column.mLearnMoreClickListener); 729 if (!TextUtils.isEmpty(column.mLearnMoreOverrideText)) { 730 footer.setLearnMoreText(column.mLearnMoreOverrideText); 731 } 732 } 733 mFingerprintUnlockFooter.addPreference(footer); 734 } 735 } 736 genKey(int id)737 private static String genKey(int id) { 738 return KEY_FINGERPRINT_ITEM_PREFIX + "_" + id; 739 } 740 741 @Override onResume()742 public void onResume() { 743 super.onResume(); 744 mInFingerprintLockout = false; 745 // Make sure we reload the preference hierarchy since fingerprints may be added, 746 // deleted or renamed. 747 updatePreferences(); 748 if (mRemovalSidecar != null) { 749 mRemovalSidecar.setListener(mRemovalListener); 750 } 751 752 mCalibrator = FeatureFactory.getFeatureFactory().getFingerprintFeatureProvider() 753 .getUdfpsEnrollCalibrator(getActivity().getApplicationContext(), null, null); 754 } 755 updatePreferences()756 private void updatePreferences() { 757 createPreferenceHierarchy(); 758 retryFingerprint(); 759 } 760 761 @Override onPause()762 public void onPause() { 763 super.onPause(); 764 if (mRemovalSidecar != null) { 765 mRemovalSidecar.setListener(null); 766 } 767 if (mAuthenticateSidecar != null) { 768 mAuthenticateSidecar.setListener(null); 769 mAuthenticateSidecar.stopAuthentication(); 770 mHandler.removeCallbacks(mFingerprintLockoutReset); 771 } 772 } 773 774 @Override onStop()775 public void onStop() { 776 super.onStop(); 777 if (!getActivity().isChangingConfigurations() && !mLaunchedConfirm && !mIsEnrolling) { 778 setResult(RESULT_TIMEOUT); 779 getActivity().finish(); 780 } 781 } 782 783 @Override getPreferenceScreenResId()784 protected int getPreferenceScreenResId() { 785 return R.xml.security_settings_fingerprint; 786 } 787 788 @Override getLogTag()789 protected String getLogTag() { 790 return TAG; 791 } 792 793 @Override onSaveInstanceState(final Bundle outState)794 public void onSaveInstanceState(final Bundle outState) { 795 outState.putByteArray(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, 796 mToken); 797 outState.putBoolean(KEY_LAUNCHED_CONFIRM, mLaunchedConfirm); 798 outState.putSerializable("mFingerprintsRenaming", mFingerprintsRenaming); 799 outState.putBoolean(KEY_IS_ENROLLING, mIsEnrolling); 800 outState.putBoolean(KEY_HAS_FIRST_ENROLLED, mHasFirstEnrolled); 801 } 802 803 @Override onPreferenceTreeClick(Preference pref)804 public boolean onPreferenceTreeClick(Preference pref) { 805 final String key = pref.getKey(); 806 if (KEY_FINGERPRINT_ADD.equals(key)) { 807 mIsEnrolling = true; 808 Intent intent = new Intent(); 809 if (FeatureFlagUtils.isEnabled(getContext(), 810 FeatureFlagUtils.SETTINGS_BIOMETRICS2_ENROLLMENT)) { 811 intent.setClassName(SETTINGS_PACKAGE_NAME, 812 FingerprintEnrollmentActivity.InternalActivity.class.getName()); 813 intent.putExtra(EnrollmentRequest.EXTRA_SKIP_FIND_SENSOR, true); 814 } else { 815 intent.setClassName(SETTINGS_PACKAGE_NAME, 816 FingerprintEnrollEnrolling.class.getName()); 817 } 818 intent.putExtra(Intent.EXTRA_USER_ID, mUserId); 819 intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, mToken); 820 if (mCalibrator != null) { 821 intent.putExtras(mCalibrator.getExtrasForNextIntent()); 822 } 823 startActivityForResult(intent, ADD_FINGERPRINT_REQUEST); 824 } else if (pref instanceof FingerprintPreference) { 825 FingerprintPreference fpref = (FingerprintPreference) pref; 826 final Fingerprint fp = fpref.getFingerprint(); 827 showRenameDialog(fp); 828 } 829 return super.onPreferenceTreeClick(pref); 830 } 831 832 @Override onDeleteClick(FingerprintPreference p)833 public void onDeleteClick(FingerprintPreference p) { 834 final boolean hasMultipleFingerprint = 835 mFingerprintManager.getEnrolledFingerprints(mUserId).size() > 1; 836 final Fingerprint fp = p.getFingerprint(); 837 838 if (hasMultipleFingerprint) { 839 if (mRemovalSidecar.inProgress()) { 840 Log.d(TAG, "Fingerprint delete in progress, skipping"); 841 return; 842 } 843 DeleteFingerprintDialog.newInstance(fp, this /* target */) 844 .show(getFragmentManager(), DeleteFingerprintDialog.class.getName()); 845 } else { 846 ConfirmLastDeleteDialog lastDeleteDialog = new ConfirmLastDeleteDialog(); 847 final boolean isProfileChallengeUser = 848 UserManager.get(getContext()).isManagedProfile(mUserId); 849 final Bundle args = new Bundle(); 850 args.putParcelable("fingerprint", fp); 851 args.putBoolean("isProfileChallengeUser", isProfileChallengeUser); 852 lastDeleteDialog.setArguments(args); 853 lastDeleteDialog.setTargetFragment(this, 0); 854 lastDeleteDialog.show(getFragmentManager(), 855 ConfirmLastDeleteDialog.class.getName()); 856 } 857 } 858 showRenameDialog(final Fingerprint fp)859 private void showRenameDialog(final Fingerprint fp) { 860 RenameDialog renameDialog = new RenameDialog(); 861 Bundle args = new Bundle(); 862 if (mFingerprintsRenaming.containsKey(fp.getBiometricId())) { 863 final Fingerprint f = new Fingerprint( 864 mFingerprintsRenaming.get(fp.getBiometricId()), 865 fp.getGroupId(), fp.getBiometricId(), fp.getDeviceId()); 866 args.putParcelable("fingerprint", f); 867 } else { 868 args.putParcelable("fingerprint", fp); 869 } 870 renameDialog.setOnDismissListener((dialogInterface) -> { 871 retryFingerprint(); 872 }); 873 renameDialog.setDeleteInProgress(mRemovalSidecar.inProgress()); 874 renameDialog.setArguments(args); 875 renameDialog.setTargetFragment(this, 0); 876 renameDialog.show(getFragmentManager(), RenameDialog.class.getName()); 877 mAuthenticateSidecar.stopAuthentication(); 878 } 879 880 @Override onPreferenceChange(Preference preference, Object value)881 public boolean onPreferenceChange(Preference preference, Object value) { 882 boolean result = true; 883 final String key = preference.getKey(); 884 if (KEY_FINGERPRINT_ENABLE_KEYGUARD_TOGGLE.equals(key)) { 885 // TODO 886 } else { 887 Log.v(TAG, "Unknown key:" + key); 888 } 889 return result; 890 } 891 892 @Override getHelpResource()893 public int getHelpResource() { 894 return R.string.help_url_fingerprint; 895 } 896 897 @Override createPreferenceControllers(Context context)898 protected List<AbstractPreferenceController> createPreferenceControllers(Context context) { 899 if (!isFingerprintHardwareDetected(context)) { 900 Log.e(TAG, "Fingerprint hardware is not detected"); 901 mControllers = Collections.emptyList(); 902 return null; 903 } 904 905 mControllers = buildPreferenceControllers(context); 906 return mControllers; 907 } 908 buildPreferenceControllers(Context context)909 private List<AbstractPreferenceController> buildPreferenceControllers(Context context) { 910 final List<AbstractPreferenceController> controllers = 911 createThePreferenceControllers(context); 912 if (isSfps()) { 913 for (AbstractPreferenceController controller : controllers) { 914 if (controller.getPreferenceKey() == KEY_FINGERPRINT_UNLOCK_CATEGORY) { 915 mFingerprintUnlockCategoryPreferenceController = 916 (FingerprintUnlockCategoryController) controller; 917 } else if (controller.getPreferenceKey() == KEY_REQUIRE_SCREEN_ON_TO_AUTH) { 918 mRequireScreenOnToAuthPreferenceController = 919 (FingerprintSettingsRequireScreenOnToAuthPreferenceController) 920 controller; 921 } 922 923 } 924 } 925 return controllers; 926 } 927 928 @Override onActivityResult(int requestCode, int resultCode, Intent data)929 public void onActivityResult(int requestCode, int resultCode, Intent data) { 930 super.onActivityResult(requestCode, resultCode, data); 931 if (requestCode == CONFIRM_REQUEST || requestCode == CHOOSE_LOCK_GENERIC_REQUEST) { 932 mLaunchedConfirm = false; 933 if (resultCode == RESULT_FINISHED || resultCode == RESULT_OK) { 934 if (BiometricUtils.containsGatekeeperPasswordHandle(data)) { 935 if (!mHasFirstEnrolled && !mIsEnrolling) { 936 final Activity activity = getActivity(); 937 if (activity != null) { 938 // Apply pending transition for auto adding first fingerprint case 939 activity.overridePendingTransition( 940 com.google.android.setupdesign.R.anim.sud_slide_next_in, 941 com.google.android.setupdesign.R.anim.sud_slide_next_out); 942 } 943 944 // To have smoother animation, change flow to let next visible activity 945 // to generateChallenge, then pass it back through activity result. 946 // Token and challenge will be updated later through the activity result 947 // of AUTO_ADD_FIRST_FINGERPRINT_REQUEST. 948 mIsEnrolling = true; 949 addFirstFingerprint( 950 BiometricUtils.getGatekeeperPasswordHandle(data)); 951 } else { 952 mFingerprintManager.generateChallenge(mUserId, 953 (sensorId, userId, challenge) -> { 954 final Activity activity = getActivity(); 955 if (activity == null || activity.isFinishing()) { 956 // Stop everything 957 Log.w(TAG, "activity detach or finishing"); 958 return; 959 } 960 961 final GatekeeperPasswordProvider provider = 962 new GatekeeperPasswordProvider( 963 new LockPatternUtils(activity)); 964 mToken = provider.requestGatekeeperHat(data, challenge, 965 mUserId); 966 mChallenge = challenge; 967 provider.removeGatekeeperPasswordHandle(data, false); 968 updateAddPreference(); 969 }); 970 } 971 } else { 972 Log.d(TAG, "Data null or GK PW missing"); 973 finish(); 974 } 975 } else { 976 Log.d(TAG, "Password not confirmed"); 977 finish(); 978 } 979 } else if (requestCode == ADD_FINGERPRINT_REQUEST) { 980 mIsEnrolling = false; 981 if (resultCode == RESULT_TIMEOUT) { 982 Activity activity = getActivity(); 983 activity.setResult(resultCode); 984 activity.finish(); 985 } 986 } else if (requestCode == AUTO_ADD_FIRST_FINGERPRINT_REQUEST) { 987 if (resultCode != RESULT_FINISHED) { 988 Log.d(TAG, "Add first fingerprint, fail or null data, result:" + resultCode); 989 if (resultCode == BiometricEnrollBase.RESULT_TIMEOUT) { 990 // If "Fingerprint Unlock" is closed because of timeout, notify result code 991 // back because "Face & Fingerprint Unlock" has to close itself for timeout 992 // case. 993 setResult(resultCode); 994 } 995 finish(); 996 return; 997 } 998 999 if (mToken == null && data != null) { 1000 mToken = data.getByteArrayExtra( 1001 ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN); 1002 } 1003 if (mToken == null) { 1004 Log.w(TAG, "Add first fingerprint, null token"); 1005 finish(); 1006 return; 1007 } 1008 1009 if (mChallenge == -1L && data != null) { 1010 mChallenge = data.getLongExtra(EXTRA_KEY_CHALLENGE, -1L); 1011 } 1012 if (mChallenge == -1L) { 1013 Log.w(TAG, "Add first fingerprint, invalid challenge"); 1014 finish(); 1015 return; 1016 } 1017 1018 mIsEnrolling = false; 1019 mHasFirstEnrolled = true; 1020 updateAddPreference(); 1021 } 1022 } 1023 1024 @Override onDestroy()1025 public void onDestroy() { 1026 super.onDestroy(); 1027 if (getActivity().isFinishing()) { 1028 mFingerprintManager.revokeChallenge(mUserId, mChallenge); 1029 } 1030 } 1031 getHighlightDrawable()1032 private Drawable getHighlightDrawable() { 1033 if (mHighlightDrawable == null) { 1034 final Activity activity = getActivity(); 1035 if (activity != null) { 1036 mHighlightDrawable = activity.getDrawable(R.drawable.preference_highlight); 1037 } 1038 } 1039 return mHighlightDrawable; 1040 } 1041 highlightFingerprintItem(int fpId)1042 private void highlightFingerprintItem(int fpId) { 1043 String prefName = genKey(fpId); 1044 FingerprintPreference fpref = (FingerprintPreference) findPreference(prefName); 1045 final Drawable highlight = getHighlightDrawable(); 1046 if (highlight != null && fpref != null) { 1047 final View view = fpref.getView(); 1048 if (view == null) { 1049 // FingerprintPreference is not bound to UI yet, so view is null. 1050 return; 1051 } 1052 final int centerX = view.getWidth() / 2; 1053 final int centerY = view.getHeight() / 2; 1054 highlight.setHotspot(centerX, centerY); 1055 view.setBackground(highlight); 1056 view.setPressed(true); 1057 view.setPressed(false); 1058 mHandler.postDelayed(new Runnable() { 1059 @Override 1060 public void run() { 1061 view.setBackground(null); 1062 } 1063 }, RESET_HIGHLIGHT_DELAY_MS); 1064 } 1065 } 1066 launchChooseOrConfirmLock()1067 private void launchChooseOrConfirmLock() { 1068 final Intent intent = new Intent(); 1069 final ChooseLockSettingsHelper.Builder builder = 1070 new ChooseLockSettingsHelper.Builder(getActivity(), this); 1071 final boolean launched = builder.setRequestCode(CONFIRM_REQUEST) 1072 .setTitle(getString(R.string.security_settings_fingerprint_preference_title)) 1073 .setRequestGatekeeperPasswordHandle(true) 1074 .setUserId(mUserId) 1075 .setForegroundOnly(true) 1076 .setReturnCredentials(true) 1077 .show(); 1078 1079 if (!launched) { 1080 // TODO: This should be cleaned up. ChooseLockGeneric should provide a way of 1081 // specifying arguments/requests, instead of relying on callers setting extras. 1082 intent.setClassName(SETTINGS_PACKAGE_NAME, ChooseLockGeneric.class.getName()); 1083 intent.putExtra(ChooseLockGeneric.ChooseLockGenericFragment.HIDE_INSECURE_OPTIONS, 1084 true); 1085 intent.putExtra(Intent.EXTRA_USER_ID, mUserId); 1086 intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_REQUEST_GK_PW_HANDLE, true); 1087 intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_FINGERPRINT, true); 1088 startActivityForResult(intent, CHOOSE_LOCK_GENERIC_REQUEST); 1089 } 1090 } 1091 addFirstFingerprint(@ullable Long gkPwHandle)1092 private void addFirstFingerprint(@Nullable Long gkPwHandle) { 1093 Intent intent = new Intent(); 1094 intent.setClassName(SETTINGS_PACKAGE_NAME, 1095 FeatureFlagUtils.isEnabled(getActivity(), 1096 FeatureFlagUtils.SETTINGS_BIOMETRICS2_ENROLLMENT) 1097 ? FingerprintEnrollmentActivity.InternalActivity.class.getName() 1098 : FingerprintEnrollIntroductionInternal.class.getName() 1099 ); 1100 1101 intent.putExtra(EXTRA_FROM_SETTINGS_SUMMARY, true); 1102 intent.putExtra(SettingsBaseActivity.EXTRA_PAGE_TRANSITION_TYPE, 1103 SettingsTransitionHelper.TransitionType.TRANSITION_SLIDE); 1104 1105 intent.putExtra(Intent.EXTRA_USER_ID, mUserId); 1106 if (gkPwHandle != null) { 1107 intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, 1108 gkPwHandle.longValue()); 1109 } else { 1110 intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, mToken); 1111 intent.putExtra(BiometricEnrollBase.EXTRA_KEY_CHALLENGE, mChallenge); 1112 } 1113 startActivityForResult(intent, AUTO_ADD_FIRST_FINGERPRINT_REQUEST); 1114 } 1115 1116 @VisibleForTesting deleteFingerPrint(Fingerprint fingerPrint)1117 void deleteFingerPrint(Fingerprint fingerPrint) { 1118 mRemovalSidecar.startRemove(fingerPrint, mUserId); 1119 String name = genKey(fingerPrint.getBiometricId()); 1120 Preference prefToRemove = findPreference(name); 1121 if (prefToRemove != null) { 1122 prefToRemove.setEnabled(false); 1123 } 1124 updateAddPreference(); 1125 } 1126 renameFingerPrint(int fingerId, String newName)1127 private void renameFingerPrint(int fingerId, String newName) { 1128 mFingerprintManager.rename(fingerId, mUserId, newName); 1129 if (!TextUtils.isEmpty(newName)) { 1130 mFingerprintsRenaming.put(fingerId, newName); 1131 } 1132 updatePreferences(); 1133 } 1134 1135 private final Runnable mFingerprintLockoutReset = new Runnable() { 1136 @Override 1137 public void run() { 1138 mInFingerprintLockout = false; 1139 retryFingerprint(); 1140 } 1141 }; 1142 isPrivateProfile()1143 private boolean isPrivateProfile() { 1144 return Utils.isPrivateProfile(mUserId, getContext()); 1145 } 1146 1147 public static class DeleteFingerprintDialog extends InstrumentedDialogFragment 1148 implements DialogInterface.OnClickListener { 1149 1150 private static final String KEY_FINGERPRINT = "fingerprint"; 1151 private Fingerprint mFp; 1152 private AlertDialog mAlertDialog; 1153 newInstance(Fingerprint fp, FingerprintSettingsFragment target)1154 public static DeleteFingerprintDialog newInstance(Fingerprint fp, 1155 FingerprintSettingsFragment target) { 1156 final DeleteFingerprintDialog dialog = new DeleteFingerprintDialog(); 1157 final Bundle bundle = new Bundle(); 1158 bundle.putParcelable(KEY_FINGERPRINT, fp); 1159 dialog.setArguments(bundle); 1160 dialog.setTargetFragment(target, 0 /* requestCode */); 1161 return dialog; 1162 } 1163 1164 @Override getMetricsCategory()1165 public int getMetricsCategory() { 1166 return SettingsEnums.DIALOG_FINGERPINT_EDIT; 1167 } 1168 1169 @Override onCreateDialog(Bundle savedInstanceState)1170 public Dialog onCreateDialog(Bundle savedInstanceState) { 1171 mFp = getArguments().getParcelable(KEY_FINGERPRINT); 1172 final String title = getString(R.string.fingerprint_delete_title, mFp.getName()); 1173 final String message = 1174 getString(R.string.fingerprint_v2_delete_message, mFp.getName()); 1175 1176 mAlertDialog = new AlertDialog.Builder(getActivity()) 1177 .setTitle(title) 1178 .setMessage(message) 1179 .setPositiveButton( 1180 R.string.security_settings_fingerprint_enroll_dialog_delete, 1181 this /* onClickListener */) 1182 .setNegativeButton(R.string.cancel, null /* onClickListener */) 1183 .create(); 1184 return mAlertDialog; 1185 } 1186 1187 @Override onClick(DialogInterface dialog, int which)1188 public void onClick(DialogInterface dialog, int which) { 1189 if (which == DialogInterface.BUTTON_POSITIVE) { 1190 final int fingerprintId = mFp.getBiometricId(); 1191 Log.v(TAG, "Removing fpId=" + fingerprintId); 1192 mMetricsFeatureProvider.action(getContext(), 1193 SettingsEnums.ACTION_FINGERPRINT_DELETE, 1194 fingerprintId); 1195 FingerprintSettingsFragment parent 1196 = (FingerprintSettingsFragment) getTargetFragment(); 1197 parent.deleteFingerPrint(mFp); 1198 } 1199 } 1200 } 1201 getFilters()1202 private static InputFilter[] getFilters() { 1203 InputFilter filter = new InputFilter() { 1204 @Override 1205 public CharSequence filter(CharSequence source, int start, int end, 1206 Spanned dest, int dstart, int dend) { 1207 for (int index = start; index < end; index++) { 1208 final char c = source.charAt(index); 1209 // KXMLSerializer does not allow these characters, 1210 // see KXmlSerializer.java:162. 1211 if (c < 0x20) { 1212 return ""; 1213 } 1214 } 1215 return null; 1216 } 1217 }; 1218 return new InputFilter[]{filter}; 1219 } 1220 1221 public static class RenameDialog extends InstrumentedDialogFragment { 1222 1223 private Fingerprint mFp; 1224 private ImeAwareEditText mDialogTextField; 1225 private AlertDialog mAlertDialog; 1226 private @Nullable DialogInterface.OnDismissListener mDismissListener; 1227 private boolean mDeleteInProgress; 1228 setDeleteInProgress(boolean deleteInProgress)1229 public void setDeleteInProgress(boolean deleteInProgress) { 1230 mDeleteInProgress = deleteInProgress; 1231 } 1232 1233 @Override onCancel(DialogInterface dialog)1234 public void onCancel(DialogInterface dialog) { 1235 super.onCancel(dialog); 1236 if (mDismissListener != null) { 1237 mDismissListener.onDismiss(dialog); 1238 } 1239 } 1240 1241 @Override onCreateDialog(Bundle savedInstanceState)1242 public Dialog onCreateDialog(Bundle savedInstanceState) { 1243 mFp = getArguments().getParcelable("fingerprint"); 1244 final String fingerName; 1245 final int textSelectionStart; 1246 final int textSelectionEnd; 1247 if (savedInstanceState != null) { 1248 fingerName = savedInstanceState.getString("fingerName"); 1249 textSelectionStart = savedInstanceState.getInt("startSelection", -1); 1250 textSelectionEnd = savedInstanceState.getInt("endSelection", -1); 1251 } else { 1252 fingerName = null; 1253 textSelectionStart = -1; 1254 textSelectionEnd = -1; 1255 } 1256 mAlertDialog = new AlertDialog.Builder(getActivity()) 1257 .setView(R.layout.fingerprint_rename_dialog) 1258 .setPositiveButton(R.string.security_settings_fingerprint_enroll_dialog_ok, 1259 new DialogInterface.OnClickListener() { 1260 @Override 1261 public void onClick(DialogInterface dialog, int which) { 1262 final String newName = 1263 mDialogTextField.getText().toString(); 1264 final CharSequence name = mFp.getName(); 1265 if (!TextUtils.equals(newName, name)) { 1266 Log.d(TAG, "rename " + name + " to " + newName); 1267 mMetricsFeatureProvider.action(getContext(), 1268 SettingsEnums.ACTION_FINGERPRINT_RENAME, 1269 mFp.getBiometricId()); 1270 FingerprintSettingsFragment parent 1271 = (FingerprintSettingsFragment) 1272 getTargetFragment(); 1273 parent.renameFingerPrint(mFp.getBiometricId(), 1274 newName); 1275 } 1276 if (mDismissListener != null) { 1277 mDismissListener.onDismiss(dialog); 1278 } 1279 dialog.dismiss(); 1280 } 1281 }) 1282 .create(); 1283 mAlertDialog.setOnShowListener(new DialogInterface.OnShowListener() { 1284 @Override 1285 public void onShow(DialogInterface dialog) { 1286 mDialogTextField = mAlertDialog.findViewById(R.id.fingerprint_rename_field); 1287 CharSequence name = fingerName == null ? mFp.getName() : fingerName; 1288 mDialogTextField.setText(name); 1289 mDialogTextField.setFilters(getFilters()); 1290 if (textSelectionStart != -1 && textSelectionEnd != -1) { 1291 mDialogTextField.setSelection(textSelectionStart, textSelectionEnd); 1292 } else { 1293 mDialogTextField.selectAll(); 1294 } 1295 if (mDeleteInProgress) { 1296 mAlertDialog.getButton(AlertDialog.BUTTON_NEGATIVE).setEnabled(false); 1297 } 1298 mDialogTextField.requestFocus(); 1299 mDialogTextField.scheduleShowSoftInput(); 1300 } 1301 }); 1302 return mAlertDialog; 1303 } 1304 setOnDismissListener(@onNull DialogInterface.OnDismissListener listener)1305 public void setOnDismissListener(@NonNull DialogInterface.OnDismissListener listener) { 1306 mDismissListener = listener; 1307 } 1308 enableDelete()1309 public void enableDelete() { 1310 mDeleteInProgress = false; 1311 if (mAlertDialog != null) { 1312 mAlertDialog.getButton(AlertDialog.BUTTON_NEGATIVE).setEnabled(true); 1313 } 1314 } 1315 1316 @Override onSaveInstanceState(Bundle outState)1317 public void onSaveInstanceState(Bundle outState) { 1318 super.onSaveInstanceState(outState); 1319 if (mDialogTextField != null) { 1320 outState.putString("fingerName", mDialogTextField.getText().toString()); 1321 outState.putInt("startSelection", mDialogTextField.getSelectionStart()); 1322 outState.putInt("endSelection", mDialogTextField.getSelectionEnd()); 1323 } 1324 } 1325 1326 @Override getMetricsCategory()1327 public int getMetricsCategory() { 1328 return SettingsEnums.DIALOG_FINGERPINT_EDIT; 1329 } 1330 } 1331 1332 public static class ConfirmLastDeleteDialog extends InstrumentedDialogFragment { 1333 1334 private Fingerprint mFp; 1335 1336 @Override getMetricsCategory()1337 public int getMetricsCategory() { 1338 return SettingsEnums.DIALOG_FINGERPINT_DELETE_LAST; 1339 } 1340 1341 @Override onCreateDialog(Bundle savedInstanceState)1342 public Dialog onCreateDialog(Bundle savedInstanceState) { 1343 mFp = getArguments().getParcelable("fingerprint"); 1344 final boolean isProfileChallengeUser = 1345 getArguments().getBoolean("isProfileChallengeUser"); 1346 1347 final String title = getString(R.string.fingerprint_delete_title, mFp.getName()); 1348 final String message = 1349 getString(R.string.fingerprint_v2_delete_message, mFp.getName()); 1350 1351 DevicePolicyManager devicePolicyManager = 1352 getContext().getSystemService(DevicePolicyManager.class); 1353 String messageId = 1354 isProfileChallengeUser ? WORK_PROFILE_FINGERPRINT_LAST_DELETE_MESSAGE 1355 : UNDEFINED; 1356 int defaultMessageId = isProfileChallengeUser 1357 ? R.string.fingerprint_last_delete_message_profile_challenge 1358 : R.string.fingerprint_last_delete_message; 1359 1360 final AlertDialog alertDialog = new AlertDialog.Builder(getActivity()) 1361 .setTitle(title) 1362 .setMessage(devicePolicyManager.getResources().getString( 1363 messageId, 1364 () -> message + "\n\n" + getContext().getString(defaultMessageId))) 1365 .setPositiveButton( 1366 R.string.security_settings_fingerprint_enroll_dialog_delete, 1367 new DialogInterface.OnClickListener() { 1368 @Override 1369 public void onClick(DialogInterface dialog, int which) { 1370 FingerprintSettingsFragment parent 1371 = (FingerprintSettingsFragment) getTargetFragment(); 1372 parent.deleteFingerPrint(mFp); 1373 dialog.dismiss(); 1374 } 1375 }) 1376 .setNegativeButton( 1377 R.string.cancel, 1378 new DialogInterface.OnClickListener() { 1379 @Override 1380 public void onClick(DialogInterface dialog, int which) { 1381 dialog.dismiss(); 1382 } 1383 }) 1384 .create(); 1385 return alertDialog; 1386 } 1387 } 1388 } 1389 1390 public static class FingerprintPreference extends TwoTargetPreference { 1391 1392 private final OnDeleteClickListener mOnDeleteClickListener; 1393 1394 private Fingerprint mFingerprint; 1395 private View mView; 1396 private View mDeleteView; 1397 1398 public interface OnDeleteClickListener { onDeleteClick(FingerprintPreference p)1399 void onDeleteClick(FingerprintPreference p); 1400 } 1401 FingerprintPreference(Context context, OnDeleteClickListener onDeleteClickListener)1402 public FingerprintPreference(Context context, OnDeleteClickListener onDeleteClickListener) { 1403 super(context); 1404 mOnDeleteClickListener = onDeleteClickListener; 1405 } 1406 getView()1407 public View getView() { 1408 return mView; 1409 } 1410 setFingerprint(Fingerprint item)1411 public void setFingerprint(Fingerprint item) { 1412 mFingerprint = item; 1413 } 1414 getFingerprint()1415 public Fingerprint getFingerprint() { 1416 return mFingerprint; 1417 } 1418 1419 @Override getSecondTargetResId()1420 protected int getSecondTargetResId() { 1421 return R.layout.preference_widget_delete; 1422 } 1423 1424 @Override onBindViewHolder(PreferenceViewHolder view)1425 public void onBindViewHolder(PreferenceViewHolder view) { 1426 super.onBindViewHolder(view); 1427 mView = view.itemView; 1428 mDeleteView = view.itemView.findViewById(R.id.delete_button); 1429 if (mFingerprint != null) { 1430 mDeleteView.setContentDescription( 1431 mDeleteView.getContentDescription() 1432 + " " + mFingerprint.getName().toString()); 1433 } 1434 mDeleteView.setOnClickListener(v -> { 1435 if (mOnDeleteClickListener != null) { 1436 mOnDeleteClickListener.onDeleteClick(FingerprintPreference.this); 1437 } 1438 }); 1439 } 1440 } 1441 } 1442