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 17package com.android.settings.fingerprint; 18 19 20import android.annotation.Nullable; 21import android.app.Activity; 22import android.app.AlertDialog; 23import android.app.Dialog; 24import android.app.DialogFragment; 25import android.app.admin.DevicePolicyManager; 26import android.content.ActivityNotFoundException; 27import android.content.Context; 28import android.content.DialogInterface; 29import android.content.Intent; 30import android.graphics.Typeface; 31import android.graphics.drawable.Drawable; 32import android.hardware.fingerprint.Fingerprint; 33import android.hardware.fingerprint.FingerprintManager; 34import android.hardware.fingerprint.FingerprintManager.AuthenticationCallback; 35import android.hardware.fingerprint.FingerprintManager.AuthenticationResult; 36import android.hardware.fingerprint.FingerprintManager.RemovalCallback; 37import android.os.Bundle; 38import android.os.CancellationSignal; 39import android.os.Handler; 40import android.preference.Preference; 41import android.preference.Preference.OnPreferenceChangeListener; 42import android.preference.PreferenceGroup; 43import android.preference.PreferenceScreen; 44import android.text.Annotation; 45import android.text.SpannableString; 46import android.text.SpannableStringBuilder; 47import android.text.TextPaint; 48import android.text.method.LinkMovementMethod; 49import android.text.style.URLSpan; 50import android.util.AttributeSet; 51import android.util.Log; 52import android.view.LayoutInflater; 53import android.view.View; 54import android.view.WindowManager; 55import android.widget.EditText; 56import android.widget.TextView; 57import android.widget.Toast; 58 59import com.android.internal.logging.MetricsLogger; 60import com.android.settings.ChooseLockGeneric; 61import com.android.settings.ChooseLockSettingsHelper; 62import com.android.settings.HelpUtils; 63import com.android.settings.R; 64import com.android.settings.SettingsPreferenceFragment; 65import com.android.settings.SubSettings; 66 67import java.util.List; 68 69/** 70 * Settings screen for fingerprints 71 */ 72public class FingerprintSettings extends SubSettings { 73 74 /** 75 * Used by the choose fingerprint wizard to indicate the wizard is 76 * finished, and each activity in the wizard should finish. 77 * <p> 78 * Previously, each activity in the wizard would finish itself after 79 * starting the next activity. However, this leads to broken 'Back' 80 * behavior. So, now an activity does not finish itself until it gets this 81 * result. 82 */ 83 protected static final int RESULT_FINISHED = RESULT_FIRST_USER; 84 85 /** 86 * Used by the enrolling screen during setup wizard to skip over setting up fingerprint, which 87 * will be useful if the user accidentally entered this flow. 88 */ 89 protected static final int RESULT_SKIP = RESULT_FIRST_USER + 1; 90 91 /** 92 * Like {@link #RESULT_FINISHED} except this one indicates enrollment failed because the 93 * device was left idle. This is used to clear the credential token to require the user to 94 * re-enter their pin/pattern/password before continuing. 95 */ 96 protected static final int RESULT_TIMEOUT = RESULT_FIRST_USER + 2; 97 98 private static final long LOCKOUT_DURATION = 30000; // time we have to wait for fp to reset, ms 99 100 @Override 101 public Intent getIntent() { 102 Intent modIntent = new Intent(super.getIntent()); 103 modIntent.putExtra(EXTRA_SHOW_FRAGMENT, FingerprintSettingsFragment.class.getName()); 104 return modIntent; 105 } 106 107 @Override 108 protected boolean isValidFragment(String fragmentName) { 109 if (FingerprintSettingsFragment.class.getName().equals(fragmentName)) return true; 110 return false; 111 } 112 113 @Override 114 public void onCreate(Bundle savedInstanceState) { 115 super.onCreate(savedInstanceState); 116 CharSequence msg = getText(R.string.security_settings_fingerprint_preference_title); 117 setTitle(msg); 118 } 119 120 public static class FingerprintSettingsFragment extends SettingsPreferenceFragment 121 implements OnPreferenceChangeListener { 122 private static final int MAX_RETRY_ATTEMPTS = 20; 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 CancellationSignal mFingerprintCancel; 147 private boolean mInFingerprintLockout; 148 private byte[] mToken; 149 private boolean mLaunchedConfirm; 150 private Drawable mHighlightDrawable; 151 152 private AuthenticationCallback mAuthCallback = new AuthenticationCallback() { 153 @Override 154 public void onAuthenticationSucceeded(AuthenticationResult result) { 155 int fingerId = result.getFingerprint().getFingerId(); 156 mHandler.obtainMessage(MSG_FINGER_AUTH_SUCCESS, fingerId, 0).sendToTarget(); 157 } 158 159 @Override 160 public void onAuthenticationFailed() { 161 mHandler.obtainMessage(MSG_FINGER_AUTH_FAIL).sendToTarget(); 162 }; 163 164 @Override 165 public void onAuthenticationError(int errMsgId, CharSequence errString) { 166 mHandler.obtainMessage(MSG_FINGER_AUTH_ERROR, errMsgId, 0, errString) 167 .sendToTarget(); 168 } 169 170 @Override 171 public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) { 172 mHandler.obtainMessage(MSG_FINGER_AUTH_HELP, helpMsgId, 0, helpString) 173 .sendToTarget(); 174 } 175 }; 176 private RemovalCallback mRemoveCallback = new RemovalCallback() { 177 178 @Override 179 public void onRemovalSucceeded(Fingerprint fingerprint) { 180 mHandler.obtainMessage(MSG_REFRESH_FINGERPRINT_TEMPLATES, 181 fingerprint.getFingerId(), 0).sendToTarget(); 182 } 183 184 @Override 185 public void onRemovalError(Fingerprint fp, int errMsgId, CharSequence errString) { 186 final Activity activity = getActivity(); 187 if (activity != null) { 188 Toast.makeText(activity, errString, Toast.LENGTH_SHORT); 189 } 190 } 191 }; 192 private final Handler mHandler = new Handler() { 193 @Override 194 public void handleMessage(android.os.Message msg) { 195 switch (msg.what) { 196 case MSG_REFRESH_FINGERPRINT_TEMPLATES: 197 removeFingerprintPreference(msg.arg1); 198 updateAddPreference(); 199 retryFingerprint(); 200 break; 201 case MSG_FINGER_AUTH_SUCCESS: 202 mFingerprintCancel = null; 203 highlightFingerprintItem(msg.arg1); 204 retryFingerprint(); 205 break; 206 case MSG_FINGER_AUTH_FAIL: 207 // No action required... fingerprint will allow up to 5 of these 208 break; 209 case MSG_FINGER_AUTH_ERROR: 210 handleError(msg.arg1 /* errMsgId */, (CharSequence) msg.obj /* errStr */ ); 211 break; 212 case MSG_FINGER_AUTH_HELP: { 213 // Not used 214 } 215 break; 216 } 217 }; 218 }; 219 220 private void stopFingerprint() { 221 if (mFingerprintCancel != null && !mFingerprintCancel.isCanceled()) { 222 mFingerprintCancel.cancel(); 223 } 224 mFingerprintCancel = null; 225 } 226 227 /** 228 * @param errMsgId 229 */ 230 protected void handleError(int errMsgId, CharSequence msg) { 231 mFingerprintCancel = null; 232 switch (errMsgId) { 233 case FingerprintManager.FINGERPRINT_ERROR_CANCELED: 234 return; // Only happens if we get preempted by another activity. Ignored. 235 case FingerprintManager.FINGERPRINT_ERROR_LOCKOUT: 236 mInFingerprintLockout = true; 237 // We've been locked out. Reset after 30s. 238 if (!mHandler.hasCallbacks(mFingerprintLockoutReset)) { 239 mHandler.postDelayed(mFingerprintLockoutReset, 240 LOCKOUT_DURATION); 241 } 242 // Fall through to show message 243 default: 244 // Activity can be null on a screen rotation. 245 final Activity activity = getActivity(); 246 if (activity != null) { 247 Toast.makeText(activity, msg , Toast.LENGTH_SHORT); 248 } 249 break; 250 } 251 retryFingerprint(); // start again 252 } 253 254 private void retryFingerprint() { 255 if (!mInFingerprintLockout) { 256 mFingerprintCancel = new CancellationSignal(); 257 mFingerprintManager.authenticate(null, mFingerprintCancel, 0 /* flags */, 258 mAuthCallback, null); 259 } 260 } 261 262 @Override 263 protected int getMetricsCategory() { 264 return MetricsLogger.FINGERPRINT; 265 } 266 267 @Override 268 public void onCreate(Bundle savedInstanceState) { 269 super.onCreate(savedInstanceState); 270 if (savedInstanceState != null) { 271 mToken = savedInstanceState.getByteArray( 272 ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN); 273 mLaunchedConfirm = savedInstanceState.getBoolean( 274 KEY_LAUNCHED_CONFIRM, false); 275 } 276 277 Activity activity = getActivity(); 278 mFingerprintManager = (FingerprintManager) activity.getSystemService( 279 Context.FINGERPRINT_SERVICE); 280 281 // Need to authenticate a session token if none 282 if (mToken == null && mLaunchedConfirm == false) { 283 mLaunchedConfirm = true; 284 launchChooseOrConfirmLock(); 285 } 286 } 287 288 @Override 289 public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { 290 super.onViewCreated(view, savedInstanceState); 291 TextView v = (TextView) LayoutInflater.from(view.getContext()).inflate( 292 R.layout.fingerprint_settings_footer, null); 293 v.setText(LearnMoreSpan.linkify(getText(isFingerprintDisabled() 294 ? R.string.security_settings_fingerprint_enroll_disclaimer_lockscreen_disabled 295 : R.string.security_settings_fingerprint_enroll_disclaimer), 296 getString(getHelpResource()))); 297 v.setMovementMethod(new LinkMovementMethod()); 298 getListView().addFooterView(v); 299 getListView().setFooterDividersEnabled(false); 300 } 301 302 private boolean isFingerprintDisabled() { 303 final DevicePolicyManager dpm = 304 (DevicePolicyManager) getSystemService(Context.DEVICE_POLICY_SERVICE); 305 return dpm != null && (dpm.getKeyguardDisabledFeatures(null) 306 & DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT) != 0; 307 } 308 309 protected void removeFingerprintPreference(int fingerprintId) { 310 String name = genKey(fingerprintId); 311 Preference prefToRemove = findPreference(name); 312 if (prefToRemove != null) { 313 if (!getPreferenceScreen().removePreference(prefToRemove)) { 314 Log.w(TAG, "Failed to remove preference with key " + name); 315 } 316 } else { 317 Log.w(TAG, "Can't find preference to remove: " + name); 318 } 319 } 320 321 /** 322 * Important! 323 * 324 * Don't forget to update the SecuritySearchIndexProvider if you are doing any change in the 325 * logic or adding/removing preferences here. 326 */ 327 private PreferenceScreen createPreferenceHierarchy() { 328 PreferenceScreen root = getPreferenceScreen(); 329 if (root != null) { 330 root.removeAll(); 331 } 332 addPreferencesFromResource(R.xml.security_settings_fingerprint); 333 root = getPreferenceScreen(); 334 addFingerprintItemPreferences(root); 335 return root; 336 } 337 338 private void addFingerprintItemPreferences(PreferenceGroup root) { 339 root.removeAll(); 340 final List<Fingerprint> items = mFingerprintManager.getEnrolledFingerprints(); 341 final int fingerprintCount = items.size(); 342 for (int i = 0; i < fingerprintCount; i++) { 343 final Fingerprint item = items.get(i); 344 FingerprintPreference pref = new FingerprintPreference(root.getContext()); 345 pref.setKey(genKey(item.getFingerId())); 346 pref.setTitle(item.getName()); 347 pref.setFingerprint(item); 348 pref.setPersistent(false); 349 root.addPreference(pref); 350 pref.setOnPreferenceChangeListener(this); 351 } 352 Preference addPreference = new Preference(root.getContext()); 353 addPreference.setKey(KEY_FINGERPRINT_ADD); 354 addPreference.setTitle(R.string.fingerprint_add_title); 355 addPreference.setIcon(R.drawable.ic_add_24dp); 356 root.addPreference(addPreference); 357 addPreference.setOnPreferenceChangeListener(this); 358 updateAddPreference(); 359 } 360 361 private void updateAddPreference() { 362 /* Disable preference if too many fingerprints added */ 363 final int max = getContext().getResources().getInteger( 364 com.android.internal.R.integer.config_fingerprintMaxTemplatesPerUser); 365 boolean tooMany = mFingerprintManager.getEnrolledFingerprints().size() >= max; 366 CharSequence maxSummary = tooMany ? 367 getContext().getString(R.string.fingerprint_add_max, max) : ""; 368 Preference addPreference = findPreference(KEY_FINGERPRINT_ADD); 369 addPreference.setSummary(maxSummary); 370 addPreference.setEnabled(!tooMany); 371 } 372 373 private static String genKey(int id) { 374 return KEY_FINGERPRINT_ITEM_PREFIX + "_" + id; 375 } 376 377 @Override 378 public void onResume() { 379 super.onResume(); 380 // Make sure we reload the preference hierarchy since fingerprints may be added, 381 // deleted or renamed. 382 updatePreferences(); 383 } 384 385 private void updatePreferences() { 386 createPreferenceHierarchy(); 387 retryFingerprint(); 388 } 389 390 @Override 391 public void onPause() { 392 super.onPause(); 393 stopFingerprint(); 394 } 395 396 @Override 397 public void onSaveInstanceState(final Bundle outState) { 398 outState.putByteArray(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, 399 mToken); 400 outState.putBoolean(KEY_LAUNCHED_CONFIRM, mLaunchedConfirm); 401 } 402 403 @Override 404 public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference pref) { 405 final String key = pref.getKey(); 406 if (KEY_FINGERPRINT_ADD.equals(key)) { 407 Intent intent = new Intent(); 408 intent.setClassName("com.android.settings", 409 FingerprintEnrollEnrolling.class.getName()); 410 intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, mToken); 411 startActivityForResult(intent, ADD_FINGERPRINT_REQUEST); 412 } else if (pref instanceof FingerprintPreference) { 413 FingerprintPreference fpref = (FingerprintPreference) pref; 414 final Fingerprint fp =fpref.getFingerprint(); 415 showRenameDeleteDialog(fp); 416 return super.onPreferenceTreeClick(preferenceScreen, pref); 417 } 418 return true; 419 } 420 421 private void showRenameDeleteDialog(final Fingerprint fp) { 422 RenameDeleteDialog renameDeleteDialog = new RenameDeleteDialog(); 423 Bundle args = new Bundle(); 424 args.putParcelable("fingerprint", fp); 425 renameDeleteDialog.setArguments(args); 426 renameDeleteDialog.setTargetFragment(this, 0); 427 renameDeleteDialog.show(getFragmentManager(), RenameDeleteDialog.class.getName()); 428 } 429 430 @Override 431 public boolean onPreferenceChange(Preference preference, Object value) { 432 boolean result = true; 433 final String key = preference.getKey(); 434 if (KEY_FINGERPRINT_ENABLE_KEYGUARD_TOGGLE.equals(key)) { 435 // TODO 436 } else { 437 Log.v(TAG, "Unknown key:" + key); 438 } 439 return result; 440 } 441 442 @Override 443 protected int getHelpResource() { 444 return R.string.help_url_fingerprint; 445 } 446 447 @Override 448 public void onActivityResult(int requestCode, int resultCode, Intent data) { 449 super.onActivityResult(requestCode, resultCode, data); 450 if (requestCode == CHOOSE_LOCK_GENERIC_REQUEST 451 || requestCode == CONFIRM_REQUEST) { 452 if (resultCode == RESULT_FINISHED || resultCode == RESULT_OK) { 453 // The lock pin/pattern/password was set. Start enrolling! 454 if (data != null) { 455 mToken = data.getByteArrayExtra( 456 ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN); 457 } 458 } 459 } else if (requestCode == ADD_FINGERPRINT_REQUEST) { 460 if (resultCode == RESULT_TIMEOUT) { 461 Activity activity = getActivity(); 462 activity.setResult(RESULT_TIMEOUT); 463 activity.finish(); 464 } 465 } 466 467 if (mToken == null) { 468 // Didn't get an authentication, finishing 469 getActivity().finish(); 470 } 471 } 472 473 @Override 474 public void onDestroy() { 475 super.onDestroy(); 476 if (getActivity().isFinishing()) { 477 int result = mFingerprintManager.postEnroll(); 478 if (result < 0) { 479 Log.w(TAG, "postEnroll failed: result = " + result); 480 } 481 } 482 } 483 484 private Drawable getHighlightDrawable() { 485 if (mHighlightDrawable == null) { 486 final Activity activity = getActivity(); 487 if (activity != null) { 488 mHighlightDrawable = activity.getDrawable(R.drawable.preference_highlight); 489 } 490 } 491 return mHighlightDrawable; 492 } 493 494 private void highlightFingerprintItem(int fpId) { 495 String prefName = genKey(fpId); 496 FingerprintPreference fpref = (FingerprintPreference) findPreference(prefName); 497 final Drawable highlight = getHighlightDrawable(); 498 if (highlight != null) { 499 final View view = fpref.getView(); 500 final int centerX = view.getWidth() / 2; 501 final int centerY = view.getHeight() / 2; 502 highlight.setHotspot(centerX, centerY); 503 view.setBackground(highlight); 504 view.setPressed(true); 505 view.setPressed(false); 506 mHandler.postDelayed(new Runnable() { 507 @Override 508 public void run() { 509 view.setBackground(null); 510 } 511 }, RESET_HIGHLIGHT_DELAY_MS); 512 } 513 } 514 515 private void launchChooseOrConfirmLock() { 516 Intent intent = new Intent(); 517 long challenge = mFingerprintManager.preEnroll(); 518 ChooseLockSettingsHelper helper = new ChooseLockSettingsHelper(getActivity(), this); 519 if (!helper.launchConfirmationActivity(CONFIRM_REQUEST, 520 getString(R.string.security_settings_fingerprint_preference_title), 521 null, null, challenge)) { 522 intent.setClassName("com.android.settings", ChooseLockGeneric.class.getName()); 523 intent.putExtra(ChooseLockGeneric.ChooseLockGenericFragment.MINIMUM_QUALITY_KEY, 524 DevicePolicyManager.PASSWORD_QUALITY_SOMETHING); 525 intent.putExtra(ChooseLockGeneric.ChooseLockGenericFragment.HIDE_DISABLED_PREFS, 526 true); 527 intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_HAS_CHALLENGE, true); 528 intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE, challenge); 529 startActivityForResult(intent, CHOOSE_LOCK_GENERIC_REQUEST); 530 } 531 } 532 533 private void deleteFingerPrint(Fingerprint fingerPrint) { 534 mFingerprintManager.remove(fingerPrint, mRemoveCallback); 535 } 536 537 private void renameFingerPrint(int fingerId, String newName) { 538 mFingerprintManager.rename(fingerId, newName); 539 updatePreferences(); 540 } 541 542 private final Runnable mFingerprintLockoutReset = new Runnable() { 543 @Override 544 public void run() { 545 mInFingerprintLockout = false; 546 retryFingerprint(); 547 } 548 }; 549 550 public static class RenameDeleteDialog extends DialogFragment { 551 552 private Fingerprint mFp; 553 private EditText mDialogTextField; 554 private String mFingerName; 555 private Boolean mTextHadFocus; 556 private int mTextSelectionStart; 557 private int mTextSelectionEnd; 558 559 @Override 560 public Dialog onCreateDialog(Bundle savedInstanceState) { 561 mFp = getArguments().getParcelable("fingerprint"); 562 if (savedInstanceState != null) { 563 mFingerName = savedInstanceState.getString("fingerName"); 564 mTextHadFocus = savedInstanceState.getBoolean("textHadFocus"); 565 mTextSelectionStart = savedInstanceState.getInt("startSelection"); 566 mTextSelectionEnd = savedInstanceState.getInt("endSelection"); 567 } 568 final AlertDialog alertDialog = new AlertDialog.Builder(getActivity()) 569 .setView(R.layout.fingerprint_rename_dialog) 570 .setPositiveButton(R.string.security_settings_fingerprint_enroll_dialog_ok, 571 new DialogInterface.OnClickListener() { 572 @Override 573 public void onClick(DialogInterface dialog, int which) { 574 final String newName = 575 mDialogTextField.getText().toString(); 576 final CharSequence name = mFp.getName(); 577 if (!newName.equals(name)) { 578 if (DEBUG) { 579 Log.v(TAG, "rename " + name + " to " + newName); 580 } 581 MetricsLogger.action(getContext(), 582 MetricsLogger.ACTION_FINGERPRINT_RENAME, 583 mFp.getFingerId()); 584 FingerprintSettingsFragment parent 585 = (FingerprintSettingsFragment) 586 getTargetFragment(); 587 parent.renameFingerPrint(mFp.getFingerId(), 588 newName); 589 } 590 dialog.dismiss(); 591 } 592 }) 593 .setNegativeButton( 594 R.string.security_settings_fingerprint_enroll_dialog_delete, 595 new DialogInterface.OnClickListener() { 596 @Override 597 public void onClick(DialogInterface dialog, int which) { 598 onDeleteClick(dialog); 599 } 600 }) 601 .create(); 602 alertDialog.setOnShowListener(new DialogInterface.OnShowListener() { 603 @Override 604 public void onShow(DialogInterface dialog) { 605 mDialogTextField = (EditText) alertDialog.findViewById( 606 R.id.fingerprint_rename_field); 607 CharSequence name = mFingerName == null ? mFp.getName() : mFingerName; 608 mDialogTextField.setText(name); 609 if (mTextHadFocus == null) { 610 mDialogTextField.selectAll(); 611 } else { 612 mDialogTextField.setSelection(mTextSelectionStart, mTextSelectionEnd); 613 } 614 } 615 }); 616 if (mTextHadFocus == null || mTextHadFocus) { 617 // Request the IME 618 alertDialog.getWindow().setSoftInputMode( 619 WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE); 620 } 621 return alertDialog; 622 } 623 624 private void onDeleteClick(DialogInterface dialog) { 625 if (DEBUG) Log.v(TAG, "Removing fpId=" + mFp.getFingerId()); 626 MetricsLogger.action(getContext(), MetricsLogger.ACTION_FINGERPRINT_DELETE, 627 mFp.getFingerId()); 628 FingerprintSettingsFragment parent 629 = (FingerprintSettingsFragment) getTargetFragment(); 630 if (parent.mFingerprintManager.getEnrolledFingerprints().size() > 1) { 631 parent.deleteFingerPrint(mFp); 632 } else { 633 ConfirmLastDeleteDialog lastDeleteDialog = new ConfirmLastDeleteDialog(); 634 Bundle args = new Bundle(); 635 args.putParcelable("fingerprint", mFp); 636 lastDeleteDialog.setArguments(args); 637 lastDeleteDialog.setTargetFragment(getTargetFragment(), 0); 638 lastDeleteDialog.show(getFragmentManager(), 639 ConfirmLastDeleteDialog.class.getName()); 640 } 641 dialog.dismiss(); 642 } 643 644 @Override 645 public void onSaveInstanceState(Bundle outState) { 646 super.onSaveInstanceState(outState); 647 if (mDialogTextField != null) { 648 outState.putString("fingerName", mDialogTextField.getText().toString()); 649 outState.putBoolean("textHadFocus", mDialogTextField.hasFocus()); 650 outState.putInt("startSelection", mDialogTextField.getSelectionStart()); 651 outState.putInt("endSelection", mDialogTextField.getSelectionEnd()); 652 } 653 } 654 } 655 656 public static class ConfirmLastDeleteDialog extends DialogFragment { 657 658 private Fingerprint mFp; 659 660 @Override 661 public Dialog onCreateDialog(Bundle savedInstanceState) { 662 mFp = getArguments().getParcelable("fingerprint"); 663 final AlertDialog alertDialog = new AlertDialog.Builder(getActivity()) 664 .setTitle(R.string.fingerprint_last_delete_title) 665 .setMessage(R.string.fingerprint_last_delete_message) 666 .setPositiveButton(R.string.fingerprint_last_delete_confirm, 667 new DialogInterface.OnClickListener() { 668 @Override 669 public void onClick(DialogInterface dialog, int which) { 670 FingerprintSettingsFragment parent 671 = (FingerprintSettingsFragment) getTargetFragment(); 672 parent.deleteFingerPrint(mFp); 673 dialog.dismiss(); 674 } 675 }) 676 .setNegativeButton( 677 R.string.cancel, 678 new DialogInterface.OnClickListener() { 679 @Override 680 public void onClick(DialogInterface dialog, int which) { 681 dialog.dismiss(); 682 } 683 }) 684 .create(); 685 return alertDialog; 686 } 687 } 688 } 689 690 public static class FingerprintPreference extends Preference { 691 private Fingerprint mFingerprint; 692 private View mView; 693 694 public FingerprintPreference(Context context, AttributeSet attrs, int defStyleAttr, 695 int defStyleRes) { 696 super(context, attrs, defStyleAttr, defStyleRes); 697 } 698 public FingerprintPreference(Context context, AttributeSet attrs, int defStyleAttr) { 699 this(context, attrs, defStyleAttr, 0); 700 } 701 702 public FingerprintPreference(Context context, AttributeSet attrs) { 703 this(context, attrs, com.android.internal.R.attr.preferenceStyle); 704 } 705 706 public FingerprintPreference(Context context) { 707 this(context, null); 708 } 709 710 public View getView() { return mView; } 711 712 public void setFingerprint(Fingerprint item) { 713 mFingerprint = item; 714 } 715 716 public Fingerprint getFingerprint() { 717 return mFingerprint; 718 } 719 720 @Override 721 protected void onBindView(View view) { 722 super.onBindView(view); 723 mView = view; 724 } 725 }; 726 727 private static class LearnMoreSpan extends URLSpan { 728 729 private static final Typeface TYPEFACE_MEDIUM = 730 Typeface.create("sans-serif-medium", Typeface.NORMAL); 731 732 private LearnMoreSpan(String url) { 733 super(url); 734 } 735 736 @Override 737 public void onClick(View widget) { 738 Context ctx = widget.getContext(); 739 Intent intent = HelpUtils.getHelpIntent(ctx, getURL(), ctx.getClass().getName()); 740 try { 741 ((Activity) ctx).startActivityForResult(intent, 0); 742 } catch (ActivityNotFoundException e) { 743 Log.w(FingerprintSettingsFragment.TAG, 744 "Actvity was not found for intent, " + intent.toString()); 745 } 746 } 747 748 @Override 749 public void updateDrawState(TextPaint ds) { 750 super.updateDrawState(ds); 751 ds.setUnderlineText(false); 752 ds.setTypeface(TYPEFACE_MEDIUM); 753 } 754 755 public static CharSequence linkify(CharSequence rawText, String uri) { 756 SpannableString msg = new SpannableString(rawText); 757 Annotation[] spans = msg.getSpans(0, msg.length(), Annotation.class); 758 SpannableStringBuilder builder = new SpannableStringBuilder(msg); 759 for (Annotation annotation : spans) { 760 int start = msg.getSpanStart(annotation); 761 int end = msg.getSpanEnd(annotation); 762 LearnMoreSpan link = new LearnMoreSpan(uri); 763 builder.setSpan(link, start, end, msg.getSpanFlags(link)); 764 } 765 return builder; 766 } 767 } 768} 769