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 com.android.settings.Utils.SETTINGS_PACKAGE_NAME; 21 22 import android.app.Activity; 23 import android.app.Dialog; 24 import android.app.admin.DevicePolicyManager; 25 import android.app.settings.SettingsEnums; 26 import android.content.Context; 27 import android.content.DialogInterface; 28 import android.content.Intent; 29 import android.graphics.drawable.Drawable; 30 import android.hardware.fingerprint.Fingerprint; 31 import android.hardware.fingerprint.FingerprintManager; 32 import android.os.Bundle; 33 import android.os.Handler; 34 import android.os.UserHandle; 35 import android.os.UserManager; 36 import android.text.TextUtils; 37 import android.util.Log; 38 import android.view.View; 39 import android.widget.Toast; 40 41 import androidx.annotation.VisibleForTesting; 42 import androidx.appcompat.app.AlertDialog; 43 import androidx.preference.Preference; 44 import androidx.preference.Preference.OnPreferenceChangeListener; 45 import androidx.preference.PreferenceGroup; 46 import androidx.preference.PreferenceScreen; 47 import androidx.preference.PreferenceViewHolder; 48 49 import com.android.settings.R; 50 import com.android.settings.SettingsPreferenceFragment; 51 import com.android.settings.SubSettings; 52 import com.android.settings.Utils; 53 import com.android.settings.biometrics.BiometricEnrollBase; 54 import com.android.settings.core.instrumentation.InstrumentedDialogFragment; 55 import com.android.settings.password.ChooseLockGeneric; 56 import com.android.settings.password.ChooseLockSettingsHelper; 57 import com.android.settings.utils.AnnotationSpan; 58 import com.android.settings.widget.ImeAwareEditText; 59 import com.android.settingslib.HelpUtils; 60 import com.android.settingslib.RestrictedLockUtils; 61 import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; 62 import com.android.settingslib.RestrictedLockUtilsInternal; 63 import com.android.settingslib.TwoTargetPreference; 64 import com.android.settingslib.widget.FooterPreference; 65 66 import java.util.HashMap; 67 import java.util.List; 68 69 /** 70 * Settings screen for fingerprints 71 */ 72 public class FingerprintSettings extends SubSettings { 73 74 private static final String TAG = "FingerprintSettings"; 75 76 private static final long LOCKOUT_DURATION = 30000; // time we have to wait for fp to reset, ms 77 78 public static final String ANNOTATION_URL = "url"; 79 public static final String ANNOTATION_ADMIN_DETAILS = "admin_details"; 80 81 private static final int RESULT_FINISHED = BiometricEnrollBase.RESULT_FINISHED; 82 private static final int RESULT_SKIP = BiometricEnrollBase.RESULT_SKIP; 83 private static final int RESULT_TIMEOUT = BiometricEnrollBase.RESULT_TIMEOUT; 84 85 @Override getIntent()86 public Intent getIntent() { 87 Intent modIntent = new Intent(super.getIntent()); 88 modIntent.putExtra(EXTRA_SHOW_FRAGMENT, FingerprintSettingsFragment.class.getName()); 89 return modIntent; 90 } 91 92 @Override isValidFragment(String fragmentName)93 protected boolean isValidFragment(String fragmentName) { 94 if (FingerprintSettingsFragment.class.getName().equals(fragmentName)) return true; 95 return false; 96 } 97 98 @Override onCreate(Bundle savedInstanceState)99 public void onCreate(Bundle savedInstanceState) { 100 super.onCreate(savedInstanceState); 101 CharSequence msg = getText(R.string.security_settings_fingerprint_preference_title); 102 setTitle(msg); 103 } 104 105 public static class FingerprintSettingsFragment extends SettingsPreferenceFragment 106 implements OnPreferenceChangeListener, FingerprintPreference.OnDeleteClickListener { 107 private static final int RESET_HIGHLIGHT_DELAY_MS = 500; 108 109 private static final String TAG = "FingerprintSettings"; 110 private static final String KEY_FINGERPRINT_ITEM_PREFIX = "key_fingerprint_item"; 111 private static final String KEY_FINGERPRINT_ADD = "key_fingerprint_add"; 112 private static final String KEY_FINGERPRINT_ENABLE_KEYGUARD_TOGGLE = 113 "fingerprint_enable_keyguard_toggle"; 114 private static final String KEY_LAUNCHED_CONFIRM = "launched_confirm"; 115 116 private static final int MSG_REFRESH_FINGERPRINT_TEMPLATES = 1000; 117 private static final int MSG_FINGER_AUTH_SUCCESS = 1001; 118 private static final int MSG_FINGER_AUTH_FAIL = 1002; 119 private static final int MSG_FINGER_AUTH_ERROR = 1003; 120 private static final int MSG_FINGER_AUTH_HELP = 1004; 121 122 private static final int CONFIRM_REQUEST = 101; 123 private static final int CHOOSE_LOCK_GENERIC_REQUEST = 102; 124 125 private static final int ADD_FINGERPRINT_REQUEST = 10; 126 127 protected static final boolean DEBUG = false; 128 129 private FingerprintManager mFingerprintManager; 130 private boolean mInFingerprintLockout; 131 private byte[] mToken; 132 private boolean mLaunchedConfirm; 133 private Drawable mHighlightDrawable; 134 private int mUserId; 135 136 private static final String TAG_AUTHENTICATE_SIDECAR = "authenticate_sidecar"; 137 private static final String TAG_REMOVAL_SIDECAR = "removal_sidecar"; 138 private FingerprintAuthenticateSidecar mAuthenticateSidecar; 139 private FingerprintRemoveSidecar mRemovalSidecar; 140 private HashMap<Integer, String> mFingerprintsRenaming; 141 142 FingerprintAuthenticateSidecar.Listener mAuthenticateListener = 143 new FingerprintAuthenticateSidecar.Listener() { 144 @Override 145 public void onAuthenticationSucceeded( 146 FingerprintManager.AuthenticationResult result) { 147 int fingerId = result.getFingerprint().getBiometricId(); 148 mHandler.obtainMessage(MSG_FINGER_AUTH_SUCCESS, fingerId, 0).sendToTarget(); 149 } 150 151 @Override 152 public void onAuthenticationFailed() { 153 mHandler.obtainMessage(MSG_FINGER_AUTH_FAIL).sendToTarget(); 154 } 155 156 @Override 157 public void onAuthenticationError(int errMsgId, CharSequence errString) { 158 mHandler.obtainMessage(MSG_FINGER_AUTH_ERROR, errMsgId, 0, errString) 159 .sendToTarget(); 160 } 161 162 @Override 163 public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) { 164 mHandler.obtainMessage(MSG_FINGER_AUTH_HELP, helpMsgId, 0, helpString) 165 .sendToTarget(); 166 } 167 }; 168 169 FingerprintRemoveSidecar.Listener mRemovalListener = 170 new FingerprintRemoveSidecar.Listener() { 171 public void onRemovalSucceeded(Fingerprint fingerprint) { 172 mHandler.obtainMessage(MSG_REFRESH_FINGERPRINT_TEMPLATES, 173 fingerprint.getBiometricId(), 0).sendToTarget(); 174 updateDialog(); 175 } 176 177 public void onRemovalError(Fingerprint fp, int errMsgId, CharSequence errString) { 178 final Activity activity = getActivity(); 179 if (activity != null) { 180 Toast.makeText(activity, errString, Toast.LENGTH_SHORT); 181 } 182 updateDialog(); 183 } 184 185 private void updateDialog() { 186 RenameDialog renameDialog = (RenameDialog) getFragmentManager(). 187 findFragmentByTag(RenameDialog.class.getName()); 188 if (renameDialog != null) { 189 renameDialog.enableDelete(); 190 } 191 } 192 }; 193 194 private final Handler mHandler = new Handler() { 195 @Override 196 public void handleMessage(android.os.Message msg) { 197 switch (msg.what) { 198 case MSG_REFRESH_FINGERPRINT_TEMPLATES: 199 removeFingerprintPreference(msg.arg1); 200 updateAddPreference(); 201 retryFingerprint(); 202 break; 203 case MSG_FINGER_AUTH_SUCCESS: 204 highlightFingerprintItem(msg.arg1); 205 retryFingerprint(); 206 break; 207 case MSG_FINGER_AUTH_FAIL: 208 // No action required... fingerprint will allow up to 5 of these 209 break; 210 case MSG_FINGER_AUTH_ERROR: 211 handleError(msg.arg1 /* errMsgId */, (CharSequence) msg.obj /* errStr */ ); 212 break; 213 case MSG_FINGER_AUTH_HELP: { 214 // Not used 215 } 216 break; 217 } 218 } 219 }; 220 221 /** 222 * @param errMsgId 223 */ handleError(int errMsgId, CharSequence msg)224 protected void handleError(int errMsgId, CharSequence msg) { 225 switch (errMsgId) { 226 case FingerprintManager.FINGERPRINT_ERROR_CANCELED: 227 return; // Only happens if we get preempted by another activity. Ignored. 228 case FingerprintManager.FINGERPRINT_ERROR_LOCKOUT: 229 mInFingerprintLockout = true; 230 // We've been locked out. Reset after 30s. 231 if (!mHandler.hasCallbacks(mFingerprintLockoutReset)) { 232 mHandler.postDelayed(mFingerprintLockoutReset, 233 LOCKOUT_DURATION); 234 } 235 break; 236 case FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT: 237 mInFingerprintLockout = true; 238 break; 239 } 240 241 if (mInFingerprintLockout) { 242 // Activity can be null on a screen rotation. 243 final Activity activity = getActivity(); 244 if (activity != null) { 245 Toast.makeText(activity, msg , Toast.LENGTH_SHORT).show(); 246 } 247 } 248 retryFingerprint(); // start again 249 } 250 retryFingerprint()251 private void retryFingerprint() { 252 if (mRemovalSidecar.inProgress() 253 || 0 == mFingerprintManager.getEnrolledFingerprints(mUserId).size()) { 254 return; 255 } 256 // Don't start authentication if ChooseLockGeneric is showing, otherwise if the user 257 // is in FP lockout, a toast will show on top 258 if (mLaunchedConfirm) { 259 return; 260 } 261 if (!mInFingerprintLockout) { 262 mAuthenticateSidecar.startAuthentication(mUserId); 263 mAuthenticateSidecar.setListener(mAuthenticateListener); 264 } 265 } 266 267 @Override getMetricsCategory()268 public int getMetricsCategory() { 269 return SettingsEnums.FINGERPRINT; 270 } 271 272 @Override onCreate(Bundle savedInstanceState)273 public void onCreate(Bundle savedInstanceState) { 274 super.onCreate(savedInstanceState); 275 276 Activity activity = getActivity(); 277 mFingerprintManager = Utils.getFingerprintManagerOrNull(activity); 278 279 mToken = getIntent().getByteArrayExtra( 280 ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN); 281 282 mAuthenticateSidecar = (FingerprintAuthenticateSidecar) 283 getFragmentManager().findFragmentByTag(TAG_AUTHENTICATE_SIDECAR); 284 if (mAuthenticateSidecar == null) { 285 mAuthenticateSidecar = new FingerprintAuthenticateSidecar(); 286 getFragmentManager().beginTransaction() 287 .add(mAuthenticateSidecar, TAG_AUTHENTICATE_SIDECAR).commit(); 288 } 289 mAuthenticateSidecar.setFingerprintManager(mFingerprintManager); 290 291 mRemovalSidecar = (FingerprintRemoveSidecar) 292 getFragmentManager().findFragmentByTag(TAG_REMOVAL_SIDECAR); 293 if (mRemovalSidecar == null) { 294 mRemovalSidecar = new FingerprintRemoveSidecar(); 295 getFragmentManager().beginTransaction() 296 .add(mRemovalSidecar, TAG_REMOVAL_SIDECAR).commit(); 297 } 298 mRemovalSidecar.setFingerprintManager(mFingerprintManager); 299 mRemovalSidecar.setListener(mRemovalListener); 300 301 RenameDialog renameDialog = (RenameDialog) getFragmentManager(). 302 findFragmentByTag(RenameDialog.class.getName()); 303 if (renameDialog != null) { 304 renameDialog.setDeleteInProgress(mRemovalSidecar.inProgress()); 305 } 306 307 mFingerprintsRenaming = new HashMap<Integer, String>(); 308 309 if (savedInstanceState != null) { 310 mFingerprintsRenaming = (HashMap<Integer, String>) 311 savedInstanceState.getSerializable("mFingerprintsRenaming"); 312 mToken = savedInstanceState.getByteArray( 313 ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN); 314 mLaunchedConfirm = savedInstanceState.getBoolean( 315 KEY_LAUNCHED_CONFIRM, false); 316 } 317 mUserId = getActivity().getIntent().getIntExtra( 318 Intent.EXTRA_USER_ID, UserHandle.myUserId()); 319 320 // Need to authenticate a session token if none 321 if (mToken == null && mLaunchedConfirm == false) { 322 mLaunchedConfirm = true; 323 launchChooseOrConfirmLock(); 324 } 325 326 final FooterPreference pref = mFooterPreferenceMixin.createFooterPreference(); 327 final EnforcedAdmin admin = RestrictedLockUtilsInternal.checkIfKeyguardFeaturesDisabled( 328 activity, DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT, mUserId); 329 final AnnotationSpan.LinkInfo adminLinkInfo = new AnnotationSpan.LinkInfo( 330 ANNOTATION_ADMIN_DETAILS, (view) -> { 331 RestrictedLockUtils.sendShowAdminSupportDetailsIntent(activity, admin); 332 }); 333 final Intent helpIntent = HelpUtils.getHelpIntent( 334 activity, getString(getHelpResource()), activity.getClass().getName()); 335 final AnnotationSpan.LinkInfo linkInfo = new AnnotationSpan.LinkInfo( 336 activity, ANNOTATION_URL, helpIntent); 337 pref.setTitle(AnnotationSpan.linkify(getText(admin != null 338 ? R.string 339 .security_settings_fingerprint_enroll_disclaimer_lockscreen_disabled 340 : R.string.security_settings_fingerprint_enroll_disclaimer), 341 linkInfo, adminLinkInfo)); 342 } 343 removeFingerprintPreference(int fingerprintId)344 protected void removeFingerprintPreference(int fingerprintId) { 345 String name = genKey(fingerprintId); 346 Preference prefToRemove = findPreference(name); 347 if (prefToRemove != null) { 348 if (!getPreferenceScreen().removePreference(prefToRemove)) { 349 Log.w(TAG, "Failed to remove preference with key " + name); 350 } 351 } else { 352 Log.w(TAG, "Can't find preference to remove: " + name); 353 } 354 } 355 356 /** 357 * Important! 358 * 359 * Don't forget to update the SecuritySearchIndexProvider if you are doing any change in the 360 * logic or adding/removing preferences here. 361 */ createPreferenceHierarchy()362 private PreferenceScreen createPreferenceHierarchy() { 363 PreferenceScreen root = getPreferenceScreen(); 364 if (root != null) { 365 root.removeAll(); 366 } 367 addPreferencesFromResource(R.xml.security_settings_fingerprint); 368 root = getPreferenceScreen(); 369 addFingerprintItemPreferences(root); 370 setPreferenceScreen(root); 371 return root; 372 } 373 addFingerprintItemPreferences(PreferenceGroup root)374 private void addFingerprintItemPreferences(PreferenceGroup root) { 375 root.removeAll(); 376 final List<Fingerprint> items = mFingerprintManager.getEnrolledFingerprints(mUserId); 377 final int fingerprintCount = items.size(); 378 for (int i = 0; i < fingerprintCount; i++) { 379 final Fingerprint item = items.get(i); 380 FingerprintPreference pref = new FingerprintPreference(root.getContext(), 381 this /* onDeleteClickListener */); 382 pref.setKey(genKey(item.getBiometricId())); 383 pref.setTitle(item.getName()); 384 pref.setFingerprint(item); 385 pref.setPersistent(false); 386 pref.setIcon(R.drawable.ic_fingerprint_24dp); 387 if (mRemovalSidecar.isRemovingFingerprint(item.getBiometricId())) { 388 pref.setEnabled(false); 389 } 390 if (mFingerprintsRenaming.containsKey(item.getBiometricId())) { 391 pref.setTitle(mFingerprintsRenaming.get(item.getBiometricId())); 392 } 393 root.addPreference(pref); 394 pref.setOnPreferenceChangeListener(this); 395 } 396 Preference addPreference = new Preference(root.getContext()); 397 addPreference.setKey(KEY_FINGERPRINT_ADD); 398 addPreference.setTitle(R.string.fingerprint_add_title); 399 addPreference.setIcon(R.drawable.ic_add_24dp); 400 root.addPreference(addPreference); 401 addPreference.setOnPreferenceChangeListener(this); 402 updateAddPreference(); 403 } 404 updateAddPreference()405 private void updateAddPreference() { 406 if (getActivity() == null) return; // Activity went away 407 408 /* Disable preference if too many fingerprints added */ 409 final int max = getContext().getResources().getInteger( 410 com.android.internal.R.integer.config_fingerprintMaxTemplatesPerUser); 411 boolean tooMany = mFingerprintManager.getEnrolledFingerprints(mUserId).size() >= max; 412 // retryFingerprint() will be called when remove finishes 413 // need to disable enroll or have a way to determine if enroll is in progress 414 final boolean removalInProgress = mRemovalSidecar.inProgress(); 415 CharSequence maxSummary = tooMany ? 416 getContext().getString(R.string.fingerprint_add_max, max) : ""; 417 Preference addPreference = findPreference(KEY_FINGERPRINT_ADD); 418 addPreference.setSummary(maxSummary); 419 addPreference.setEnabled(!tooMany && !removalInProgress); 420 } 421 genKey(int id)422 private static String genKey(int id) { 423 return KEY_FINGERPRINT_ITEM_PREFIX + "_" + id; 424 } 425 426 @Override onResume()427 public void onResume() { 428 super.onResume(); 429 mInFingerprintLockout = false; 430 // Make sure we reload the preference hierarchy since fingerprints may be added, 431 // deleted or renamed. 432 updatePreferences(); 433 if (mRemovalSidecar != null) { 434 mRemovalSidecar.setListener(mRemovalListener); 435 } 436 } 437 updatePreferences()438 private void updatePreferences() { 439 createPreferenceHierarchy(); 440 retryFingerprint(); 441 } 442 443 @Override onPause()444 public void onPause() { 445 super.onPause(); 446 if (mRemovalSidecar != null) { 447 mRemovalSidecar.setListener(null); 448 } 449 if (mAuthenticateSidecar != null) { 450 mAuthenticateSidecar.setListener(null); 451 mAuthenticateSidecar.stopAuthentication(); 452 } 453 } 454 455 @Override onSaveInstanceState(final Bundle outState)456 public void onSaveInstanceState(final Bundle outState) { 457 outState.putByteArray(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, 458 mToken); 459 outState.putBoolean(KEY_LAUNCHED_CONFIRM, mLaunchedConfirm); 460 outState.putSerializable("mFingerprintsRenaming", mFingerprintsRenaming); 461 } 462 463 @Override onPreferenceTreeClick(Preference pref)464 public boolean onPreferenceTreeClick(Preference pref) { 465 final String key = pref.getKey(); 466 if (KEY_FINGERPRINT_ADD.equals(key)) { 467 Intent intent = new Intent(); 468 intent.setClassName(SETTINGS_PACKAGE_NAME, 469 FingerprintEnrollEnrolling.class.getName()); 470 intent.putExtra(Intent.EXTRA_USER_ID, mUserId); 471 intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, mToken); 472 startActivityForResult(intent, ADD_FINGERPRINT_REQUEST); 473 } else if (pref instanceof FingerprintPreference) { 474 FingerprintPreference fpref = (FingerprintPreference) pref; 475 final Fingerprint fp = fpref.getFingerprint(); 476 showRenameDialog(fp); 477 } 478 return super.onPreferenceTreeClick(pref); 479 } 480 481 @Override onDeleteClick(FingerprintPreference p)482 public void onDeleteClick(FingerprintPreference p) { 483 final boolean hasMultipleFingerprint = 484 mFingerprintManager.getEnrolledFingerprints(mUserId).size() > 1; 485 final Fingerprint fp = p.getFingerprint(); 486 487 if (hasMultipleFingerprint) { 488 if (mRemovalSidecar.inProgress()) { 489 Log.d(TAG, "Fingerprint delete in progress, skipping"); 490 return; 491 } 492 DeleteFingerprintDialog.newInstance(fp, this /* target */) 493 .show(getFragmentManager(), DeleteFingerprintDialog.class.getName()); 494 } else { 495 ConfirmLastDeleteDialog lastDeleteDialog = new ConfirmLastDeleteDialog(); 496 final boolean isProfileChallengeUser = 497 UserManager.get(getContext()).isManagedProfile(mUserId); 498 final Bundle args = new Bundle(); 499 args.putParcelable("fingerprint", fp); 500 args.putBoolean("isProfileChallengeUser", isProfileChallengeUser); 501 lastDeleteDialog.setArguments(args); 502 lastDeleteDialog.setTargetFragment(this, 0); 503 lastDeleteDialog.show(getFragmentManager(), 504 ConfirmLastDeleteDialog.class.getName()); 505 } 506 } 507 showRenameDialog(final Fingerprint fp)508 private void showRenameDialog(final Fingerprint fp) { 509 RenameDialog renameDialog = new RenameDialog(); 510 Bundle args = new Bundle(); 511 if (mFingerprintsRenaming.containsKey(fp.getBiometricId())) { 512 final Fingerprint f = new Fingerprint(mFingerprintsRenaming.get(fp.getBiometricId()), 513 fp.getGroupId(), fp.getBiometricId(), fp.getDeviceId()); 514 args.putParcelable("fingerprint", f); 515 } else { 516 args.putParcelable("fingerprint", fp); 517 } 518 renameDialog.setDeleteInProgress(mRemovalSidecar.inProgress()); 519 renameDialog.setArguments(args); 520 renameDialog.setTargetFragment(this, 0); 521 renameDialog.show(getFragmentManager(), RenameDialog.class.getName()); 522 } 523 524 @Override onPreferenceChange(Preference preference, Object value)525 public boolean onPreferenceChange(Preference preference, Object value) { 526 boolean result = true; 527 final String key = preference.getKey(); 528 if (KEY_FINGERPRINT_ENABLE_KEYGUARD_TOGGLE.equals(key)) { 529 // TODO 530 } else { 531 Log.v(TAG, "Unknown key:" + key); 532 } 533 return result; 534 } 535 536 @Override getHelpResource()537 public int getHelpResource() { 538 return R.string.help_url_fingerprint; 539 } 540 541 @Override onActivityResult(int requestCode, int resultCode, Intent data)542 public void onActivityResult(int requestCode, int resultCode, Intent data) { 543 super.onActivityResult(requestCode, resultCode, data); 544 if (requestCode == CHOOSE_LOCK_GENERIC_REQUEST 545 || requestCode == CONFIRM_REQUEST) { 546 mLaunchedConfirm = false; 547 if (resultCode == RESULT_FINISHED || resultCode == RESULT_OK) { 548 // The lock pin/pattern/password was set. Start enrolling! 549 if (data != null) { 550 mToken = data.getByteArrayExtra( 551 ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN); 552 } 553 } 554 } else if (requestCode == ADD_FINGERPRINT_REQUEST) { 555 if (resultCode == RESULT_TIMEOUT) { 556 Activity activity = getActivity(); 557 activity.setResult(RESULT_TIMEOUT); 558 activity.finish(); 559 } 560 } 561 562 if (mToken == null) { 563 // Didn't get an authentication, finishing 564 getActivity().finish(); 565 } 566 } 567 568 @Override onDestroy()569 public void onDestroy() { 570 super.onDestroy(); 571 if (getActivity().isFinishing()) { 572 int result = mFingerprintManager.postEnroll(); 573 if (result < 0) { 574 Log.w(TAG, "postEnroll failed: result = " + result); 575 } 576 } 577 } 578 getHighlightDrawable()579 private Drawable getHighlightDrawable() { 580 if (mHighlightDrawable == null) { 581 final Activity activity = getActivity(); 582 if (activity != null) { 583 mHighlightDrawable = activity.getDrawable(R.drawable.preference_highlight); 584 } 585 } 586 return mHighlightDrawable; 587 } 588 highlightFingerprintItem(int fpId)589 private void highlightFingerprintItem(int fpId) { 590 String prefName = genKey(fpId); 591 FingerprintPreference fpref = (FingerprintPreference) findPreference(prefName); 592 final Drawable highlight = getHighlightDrawable(); 593 if (highlight != null && fpref != null) { 594 final View view = fpref.getView(); 595 if (view == null) { 596 // FingerprintPreference is not bound to UI yet, so view is null. 597 return; 598 } 599 final int centerX = view.getWidth() / 2; 600 final int centerY = view.getHeight() / 2; 601 highlight.setHotspot(centerX, centerY); 602 view.setBackground(highlight); 603 view.setPressed(true); 604 view.setPressed(false); 605 mHandler.postDelayed(new Runnable() { 606 @Override 607 public void run() { 608 view.setBackground(null); 609 } 610 }, RESET_HIGHLIGHT_DELAY_MS); 611 } 612 } 613 launchChooseOrConfirmLock()614 private void launchChooseOrConfirmLock() { 615 Intent intent = new Intent(); 616 long challenge = mFingerprintManager.preEnroll(); 617 ChooseLockSettingsHelper helper = new ChooseLockSettingsHelper(getActivity(), this); 618 if (!helper.launchConfirmationActivity(CONFIRM_REQUEST, 619 getString(R.string.security_settings_fingerprint_preference_title), 620 null, null, challenge, mUserId, true /* foregroundOnly */)) { 621 intent.setClassName(SETTINGS_PACKAGE_NAME, ChooseLockGeneric.class.getName()); 622 intent.putExtra(ChooseLockGeneric.ChooseLockGenericFragment.MINIMUM_QUALITY_KEY, 623 DevicePolicyManager.PASSWORD_QUALITY_SOMETHING); 624 intent.putExtra(ChooseLockGeneric.ChooseLockGenericFragment.HIDE_DISABLED_PREFS, 625 true); 626 intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_HAS_CHALLENGE, true); 627 intent.putExtra(Intent.EXTRA_USER_ID, mUserId); 628 intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE, challenge); 629 intent.putExtra(Intent.EXTRA_USER_ID, mUserId); 630 startActivityForResult(intent, CHOOSE_LOCK_GENERIC_REQUEST); 631 } 632 } 633 634 @VisibleForTesting deleteFingerPrint(Fingerprint fingerPrint)635 void deleteFingerPrint(Fingerprint fingerPrint) { 636 mRemovalSidecar.startRemove(fingerPrint, mUserId); 637 String name = genKey(fingerPrint.getBiometricId()); 638 Preference prefToRemove = findPreference(name); 639 prefToRemove.setEnabled(false); 640 updateAddPreference(); 641 } 642 renameFingerPrint(int fingerId, String newName)643 private void renameFingerPrint(int fingerId, String newName) { 644 mFingerprintManager.rename(fingerId, mUserId, newName); 645 if (!TextUtils.isEmpty(newName)) { 646 mFingerprintsRenaming.put(fingerId, newName); 647 } 648 updatePreferences(); 649 } 650 651 private final Runnable mFingerprintLockoutReset = new Runnable() { 652 @Override 653 public void run() { 654 mInFingerprintLockout = false; 655 retryFingerprint(); 656 } 657 }; 658 659 public static class DeleteFingerprintDialog extends InstrumentedDialogFragment 660 implements DialogInterface.OnClickListener { 661 662 private static final String KEY_FINGERPRINT = "fingerprint"; 663 private Fingerprint mFp; 664 private AlertDialog mAlertDialog; 665 newInstance(Fingerprint fp, FingerprintSettingsFragment target)666 public static DeleteFingerprintDialog newInstance(Fingerprint fp, 667 FingerprintSettingsFragment target) { 668 final DeleteFingerprintDialog dialog = new DeleteFingerprintDialog(); 669 final Bundle bundle = new Bundle(); 670 bundle.putParcelable(KEY_FINGERPRINT, fp); 671 dialog.setArguments(bundle); 672 dialog.setTargetFragment(target, 0 /* requestCode */); 673 return dialog; 674 } 675 676 @Override getMetricsCategory()677 public int getMetricsCategory() { 678 return SettingsEnums.DIALOG_FINGERPINT_EDIT; 679 } 680 681 @Override onCreateDialog(Bundle savedInstanceState)682 public Dialog onCreateDialog(Bundle savedInstanceState) { 683 mFp = getArguments().getParcelable(KEY_FINGERPRINT); 684 final String title = getString(R.string.fingerprint_delete_title, mFp.getName()); 685 686 mAlertDialog = new AlertDialog.Builder(getActivity()) 687 .setTitle(title) 688 .setMessage(R.string.fingerprint_delete_message) 689 .setPositiveButton( 690 R.string.security_settings_fingerprint_enroll_dialog_delete, 691 this /* onClickListener */) 692 .setNegativeButton(R.string.cancel, null /* onClickListener */) 693 .create(); 694 return mAlertDialog; 695 } 696 697 @Override onClick(DialogInterface dialog, int which)698 public void onClick(DialogInterface dialog, int which) { 699 if (which == DialogInterface.BUTTON_POSITIVE) { 700 final int fingerprintId = mFp.getBiometricId(); 701 Log.v(TAG, "Removing fpId=" + fingerprintId); 702 mMetricsFeatureProvider.action(getContext(), 703 SettingsEnums.ACTION_FINGERPRINT_DELETE, 704 fingerprintId); 705 FingerprintSettingsFragment parent 706 = (FingerprintSettingsFragment) getTargetFragment(); 707 parent.deleteFingerPrint(mFp); 708 } 709 } 710 } 711 712 public static class RenameDialog extends InstrumentedDialogFragment { 713 714 private Fingerprint mFp; 715 private ImeAwareEditText mDialogTextField; 716 private AlertDialog mAlertDialog; 717 private boolean mDeleteInProgress; 718 setDeleteInProgress(boolean deleteInProgress)719 public void setDeleteInProgress(boolean deleteInProgress) { 720 mDeleteInProgress = deleteInProgress; 721 } 722 723 @Override onCreateDialog(Bundle savedInstanceState)724 public Dialog onCreateDialog(Bundle savedInstanceState) { 725 mFp = getArguments().getParcelable("fingerprint"); 726 final String fingerName; 727 final int textSelectionStart; 728 final int textSelectionEnd; 729 if (savedInstanceState != null) { 730 fingerName = savedInstanceState.getString("fingerName"); 731 textSelectionStart = savedInstanceState.getInt("startSelection", -1); 732 textSelectionEnd = savedInstanceState.getInt("endSelection", -1); 733 } else { 734 fingerName = null; 735 textSelectionStart = -1; 736 textSelectionEnd = -1; 737 } 738 mAlertDialog = new AlertDialog.Builder(getActivity()) 739 .setView(R.layout.fingerprint_rename_dialog) 740 .setPositiveButton(R.string.security_settings_fingerprint_enroll_dialog_ok, 741 new DialogInterface.OnClickListener() { 742 @Override 743 public void onClick(DialogInterface dialog, int which) { 744 final String newName = 745 mDialogTextField.getText().toString(); 746 final CharSequence name = mFp.getName(); 747 if (!TextUtils.equals(newName, name)) { 748 Log.d(TAG, "rename " + name + " to " + newName); 749 mMetricsFeatureProvider.action(getContext(), 750 SettingsEnums.ACTION_FINGERPRINT_RENAME, 751 mFp.getBiometricId()); 752 FingerprintSettingsFragment parent 753 = (FingerprintSettingsFragment) 754 getTargetFragment(); 755 parent.renameFingerPrint(mFp.getBiometricId(), 756 newName); 757 } 758 dialog.dismiss(); 759 } 760 }) 761 .create(); 762 mAlertDialog.setOnShowListener(new DialogInterface.OnShowListener() { 763 @Override 764 public void onShow(DialogInterface dialog) { 765 mDialogTextField = mAlertDialog.findViewById(R.id.fingerprint_rename_field); 766 CharSequence name = fingerName == null ? mFp.getName() : fingerName; 767 mDialogTextField.setText(name); 768 if (textSelectionStart != -1 && textSelectionEnd != -1) { 769 mDialogTextField.setSelection(textSelectionStart, textSelectionEnd); 770 } else { 771 mDialogTextField.selectAll(); 772 } 773 if (mDeleteInProgress) { 774 mAlertDialog.getButton(AlertDialog.BUTTON_NEGATIVE).setEnabled(false); 775 } 776 mDialogTextField.requestFocus(); 777 mDialogTextField.scheduleShowSoftInput(); 778 } 779 }); 780 return mAlertDialog; 781 } 782 enableDelete()783 public void enableDelete() { 784 mDeleteInProgress = false; 785 if (mAlertDialog != null) { 786 mAlertDialog.getButton(AlertDialog.BUTTON_NEGATIVE).setEnabled(true); 787 } 788 } 789 790 @Override onSaveInstanceState(Bundle outState)791 public void onSaveInstanceState(Bundle outState) { 792 super.onSaveInstanceState(outState); 793 if (mDialogTextField != null) { 794 outState.putString("fingerName", mDialogTextField.getText().toString()); 795 outState.putInt("startSelection", mDialogTextField.getSelectionStart()); 796 outState.putInt("endSelection", mDialogTextField.getSelectionEnd()); 797 } 798 } 799 800 @Override getMetricsCategory()801 public int getMetricsCategory() { 802 return SettingsEnums.DIALOG_FINGERPINT_EDIT; 803 } 804 } 805 806 public static class ConfirmLastDeleteDialog extends InstrumentedDialogFragment { 807 808 private Fingerprint mFp; 809 810 @Override getMetricsCategory()811 public int getMetricsCategory() { 812 return SettingsEnums.DIALOG_FINGERPINT_DELETE_LAST; 813 } 814 815 @Override onCreateDialog(Bundle savedInstanceState)816 public Dialog onCreateDialog(Bundle savedInstanceState) { 817 mFp = getArguments().getParcelable("fingerprint"); 818 final boolean isProfileChallengeUser = 819 getArguments().getBoolean("isProfileChallengeUser"); 820 final AlertDialog alertDialog = new AlertDialog.Builder(getActivity()) 821 .setTitle(R.string.fingerprint_last_delete_title) 822 .setMessage((isProfileChallengeUser) 823 ? R.string.fingerprint_last_delete_message_profile_challenge 824 : R.string.fingerprint_last_delete_message) 825 .setPositiveButton(R.string.fingerprint_last_delete_confirm, 826 new DialogInterface.OnClickListener() { 827 @Override 828 public void onClick(DialogInterface dialog, int which) { 829 FingerprintSettingsFragment parent 830 = (FingerprintSettingsFragment) getTargetFragment(); 831 parent.deleteFingerPrint(mFp); 832 dialog.dismiss(); 833 } 834 }) 835 .setNegativeButton( 836 R.string.cancel, 837 new DialogInterface.OnClickListener() { 838 @Override 839 public void onClick(DialogInterface dialog, int which) { 840 dialog.dismiss(); 841 } 842 }) 843 .create(); 844 return alertDialog; 845 } 846 } 847 } 848 849 public static class FingerprintPreference extends TwoTargetPreference { 850 851 private final OnDeleteClickListener mOnDeleteClickListener; 852 853 private Fingerprint mFingerprint; 854 private View mView; 855 private View mDeleteView; 856 857 public interface OnDeleteClickListener { onDeleteClick(FingerprintPreference p)858 void onDeleteClick(FingerprintPreference p); 859 } 860 FingerprintPreference(Context context, OnDeleteClickListener onDeleteClickListener)861 public FingerprintPreference(Context context, OnDeleteClickListener onDeleteClickListener) { 862 super(context); 863 mOnDeleteClickListener = onDeleteClickListener; 864 } 865 getView()866 public View getView() { 867 return mView; 868 } 869 setFingerprint(Fingerprint item)870 public void setFingerprint(Fingerprint item) { 871 mFingerprint = item; 872 } 873 getFingerprint()874 public Fingerprint getFingerprint() { 875 return mFingerprint; 876 } 877 878 @Override getSecondTargetResId()879 protected int getSecondTargetResId() { 880 return R.layout.preference_widget_delete; 881 } 882 883 @Override onBindViewHolder(PreferenceViewHolder view)884 public void onBindViewHolder(PreferenceViewHolder view) { 885 super.onBindViewHolder(view); 886 mView = view.itemView; 887 mDeleteView = view.itemView.findViewById(R.id.delete_button); 888 mDeleteView.setOnClickListener(new View.OnClickListener() { 889 @Override 890 public void onClick(View v) { 891 if (mOnDeleteClickListener != null) { 892 mOnDeleteClickListener.onDeleteClick(FingerprintPreference.this); 893 } 894 } 895 }); 896 } 897 } 898 } 899