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