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