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