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