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