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                 pref.setIcon(R.drawable.ic_fingerprint_24dp);
    361                 root.addPreference(pref);
    362                 pref.setOnPreferenceChangeListener(this);
    363             }
    364             Preference addPreference = new Preference(root.getContext());
    365             addPreference.setKey(KEY_FINGERPRINT_ADD);
    366             addPreference.setTitle(R.string.fingerprint_add_title);
    367             addPreference.setIcon(R.drawable.ic_add_24dp);
    368             root.addPreference(addPreference);
    369             addPreference.setOnPreferenceChangeListener(this);
    370             updateAddPreference();
    371         }
    372 
    373         private void updateAddPreference() {
    374             /* Disable preference if too many fingerprints added */
    375             final int max = getContext().getResources().getInteger(
    376                     com.android.internal.R.integer.config_fingerprintMaxTemplatesPerUser);
    377             boolean tooMany = mFingerprintManager.getEnrolledFingerprints(mUserId).size() >= max;
    378             CharSequence maxSummary = tooMany ?
    379                     getContext().getString(R.string.fingerprint_add_max, max) : "";
    380             Preference addPreference = findPreference(KEY_FINGERPRINT_ADD);
    381             addPreference.setSummary(maxSummary);
    382             addPreference.setEnabled(!tooMany);
    383         }
    384 
    385         private static String genKey(int id) {
    386             return KEY_FINGERPRINT_ITEM_PREFIX + "_" + id;
    387         }
    388 
    389         @Override
    390         public void onResume() {
    391             super.onResume();
    392             // Make sure we reload the preference hierarchy since fingerprints may be added,
    393             // deleted or renamed.
    394             updatePreferences();
    395         }
    396 
    397         private void updatePreferences() {
    398             createPreferenceHierarchy();
    399             retryFingerprint();
    400         }
    401 
    402         @Override
    403         public void onPause() {
    404             super.onPause();
    405             stopFingerprint();
    406         }
    407 
    408         @Override
    409         public void onSaveInstanceState(final Bundle outState) {
    410             outState.putByteArray(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN,
    411                     mToken);
    412             outState.putBoolean(KEY_LAUNCHED_CONFIRM, mLaunchedConfirm);
    413         }
    414 
    415         @Override
    416         public boolean onPreferenceTreeClick(Preference pref) {
    417             final String key = pref.getKey();
    418             if (KEY_FINGERPRINT_ADD.equals(key)) {
    419                 Intent intent = new Intent();
    420                 intent.setClassName("com.android.settings",
    421                         FingerprintEnrollEnrolling.class.getName());
    422                 intent.putExtra(Intent.EXTRA_USER_ID, mUserId);
    423                 intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, mToken);
    424                 startActivityForResult(intent, ADD_FINGERPRINT_REQUEST);
    425             } else if (pref instanceof FingerprintPreference) {
    426                 FingerprintPreference fpref = (FingerprintPreference) pref;
    427                 final Fingerprint fp =fpref.getFingerprint();
    428                 showRenameDeleteDialog(fp);
    429                 return super.onPreferenceTreeClick(pref);
    430             }
    431             return true;
    432         }
    433 
    434         private void showRenameDeleteDialog(final Fingerprint fp) {
    435             RenameDeleteDialog renameDeleteDialog = new RenameDeleteDialog();
    436             Bundle args = new Bundle();
    437             args.putParcelable("fingerprint", fp);
    438             renameDeleteDialog.setArguments(args);
    439             renameDeleteDialog.setTargetFragment(this, 0);
    440             renameDeleteDialog.show(getFragmentManager(), RenameDeleteDialog.class.getName());
    441         }
    442 
    443         @Override
    444         public boolean onPreferenceChange(Preference preference, Object value) {
    445             boolean result = true;
    446             final String key = preference.getKey();
    447             if (KEY_FINGERPRINT_ENABLE_KEYGUARD_TOGGLE.equals(key)) {
    448                 // TODO
    449             } else {
    450                 Log.v(TAG, "Unknown key:" + key);
    451             }
    452             return result;
    453         }
    454 
    455         @Override
    456         protected int getHelpResource() {
    457             return R.string.help_url_fingerprint;
    458         }
    459 
    460         @Override
    461         public void onActivityResult(int requestCode, int resultCode, Intent data) {
    462             super.onActivityResult(requestCode, resultCode, data);
    463             if (requestCode == CHOOSE_LOCK_GENERIC_REQUEST
    464                     || requestCode == CONFIRM_REQUEST) {
    465                 if (resultCode == RESULT_FINISHED || resultCode == RESULT_OK) {
    466                     // The lock pin/pattern/password was set. Start enrolling!
    467                     if (data != null) {
    468                         mToken = data.getByteArrayExtra(
    469                                 ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN);
    470                     }
    471                 }
    472             } else if (requestCode == ADD_FINGERPRINT_REQUEST) {
    473                 if (resultCode == RESULT_TIMEOUT) {
    474                     Activity activity = getActivity();
    475                     activity.setResult(RESULT_TIMEOUT);
    476                     activity.finish();
    477                 }
    478             }
    479 
    480             if (mToken == null) {
    481                 // Didn't get an authentication, finishing
    482                 getActivity().finish();
    483             }
    484         }
    485 
    486         @Override
    487         public void onDestroy() {
    488             super.onDestroy();
    489             if (getActivity().isFinishing()) {
    490                 int result = mFingerprintManager.postEnroll();
    491                 if (result < 0) {
    492                     Log.w(TAG, "postEnroll failed: result = " + result);
    493                 }
    494             }
    495         }
    496 
    497         private Drawable getHighlightDrawable() {
    498             if (mHighlightDrawable == null) {
    499                 final Activity activity = getActivity();
    500                 if (activity != null) {
    501                     mHighlightDrawable = activity.getDrawable(R.drawable.preference_highlight);
    502                 }
    503             }
    504             return mHighlightDrawable;
    505         }
    506 
    507         private void highlightFingerprintItem(int fpId) {
    508             String prefName = genKey(fpId);
    509             FingerprintPreference fpref = (FingerprintPreference) findPreference(prefName);
    510             final Drawable highlight = getHighlightDrawable();
    511             if (highlight != null) {
    512                 final View view = fpref.getView();
    513                 final int centerX = view.getWidth() / 2;
    514                 final int centerY = view.getHeight() / 2;
    515                 highlight.setHotspot(centerX, centerY);
    516                 view.setBackground(highlight);
    517                 view.setPressed(true);
    518                 view.setPressed(false);
    519                 mHandler.postDelayed(new Runnable() {
    520                     @Override
    521                     public void run() {
    522                         view.setBackground(null);
    523                     }
    524                 }, RESET_HIGHLIGHT_DELAY_MS);
    525             }
    526         }
    527 
    528         private void launchChooseOrConfirmLock() {
    529             Intent intent = new Intent();
    530             long challenge = mFingerprintManager.preEnroll();
    531             ChooseLockSettingsHelper helper = new ChooseLockSettingsHelper(getActivity(), this);
    532             if (!helper.launchConfirmationActivity(CONFIRM_REQUEST,
    533                     getString(R.string.security_settings_fingerprint_preference_title),
    534                     null, null, challenge, mUserId)) {
    535                 intent.setClassName("com.android.settings", ChooseLockGeneric.class.getName());
    536                 intent.putExtra(ChooseLockGeneric.ChooseLockGenericFragment.MINIMUM_QUALITY_KEY,
    537                         DevicePolicyManager.PASSWORD_QUALITY_SOMETHING);
    538                 intent.putExtra(ChooseLockGeneric.ChooseLockGenericFragment.HIDE_DISABLED_PREFS,
    539                         true);
    540                 intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_HAS_CHALLENGE, true);
    541                 intent.putExtra(Intent.EXTRA_USER_ID, mUserId);
    542                 intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE, challenge);
    543                 intent.putExtra(Intent.EXTRA_USER_ID, mUserId);
    544                 startActivityForResult(intent, CHOOSE_LOCK_GENERIC_REQUEST);
    545             }
    546         }
    547 
    548         private void deleteFingerPrint(Fingerprint fingerPrint) {
    549             mFingerprintManager.remove(fingerPrint, mUserId, mRemoveCallback);
    550         }
    551 
    552         private void renameFingerPrint(int fingerId, String newName) {
    553             mFingerprintManager.rename(fingerId, mUserId, newName);
    554             updatePreferences();
    555         }
    556 
    557         private final Runnable mFingerprintLockoutReset = new Runnable() {
    558             @Override
    559             public void run() {
    560                 mInFingerprintLockout = false;
    561                 retryFingerprint();
    562             }
    563         };
    564 
    565         public static class RenameDeleteDialog extends DialogFragment {
    566 
    567             private Fingerprint mFp;
    568             private EditText mDialogTextField;
    569             private String mFingerName;
    570             private Boolean mTextHadFocus;
    571             private int mTextSelectionStart;
    572             private int mTextSelectionEnd;
    573 
    574             @Override
    575             public Dialog onCreateDialog(Bundle savedInstanceState) {
    576                 mFp = getArguments().getParcelable("fingerprint");
    577                 if (savedInstanceState != null) {
    578                     mFingerName = savedInstanceState.getString("fingerName");
    579                     mTextHadFocus = savedInstanceState.getBoolean("textHadFocus");
    580                     mTextSelectionStart = savedInstanceState.getInt("startSelection");
    581                     mTextSelectionEnd = savedInstanceState.getInt("endSelection");
    582                 }
    583                 final AlertDialog alertDialog = new AlertDialog.Builder(getActivity())
    584                         .setView(R.layout.fingerprint_rename_dialog)
    585                         .setPositiveButton(R.string.security_settings_fingerprint_enroll_dialog_ok,
    586                                 new DialogInterface.OnClickListener() {
    587                                     @Override
    588                                     public void onClick(DialogInterface dialog, int which) {
    589                                         final String newName =
    590                                                 mDialogTextField.getText().toString();
    591                                         final CharSequence name = mFp.getName();
    592                                         if (!newName.equals(name)) {
    593                                             if (DEBUG) {
    594                                                 Log.v(TAG, "rename " + name + " to " + newName);
    595                                             }
    596                                             MetricsLogger.action(getContext(),
    597                                                     MetricsEvent.ACTION_FINGERPRINT_RENAME,
    598                                                     mFp.getFingerId());
    599                                             FingerprintSettingsFragment parent
    600                                                     = (FingerprintSettingsFragment)
    601                                                     getTargetFragment();
    602                                             parent.renameFingerPrint(mFp.getFingerId(),
    603                                                     newName);
    604                                         }
    605                                         dialog.dismiss();
    606                                     }
    607                                 })
    608                         .setNegativeButton(
    609                                 R.string.security_settings_fingerprint_enroll_dialog_delete,
    610                                 new DialogInterface.OnClickListener() {
    611                                     @Override
    612                                     public void onClick(DialogInterface dialog, int which) {
    613                                         onDeleteClick(dialog);
    614                                     }
    615                                 })
    616                         .create();
    617                 alertDialog.setOnShowListener(new DialogInterface.OnShowListener() {
    618                     @Override
    619                     public void onShow(DialogInterface dialog) {
    620                         mDialogTextField = (EditText) alertDialog.findViewById(
    621                                 R.id.fingerprint_rename_field);
    622                         CharSequence name = mFingerName == null ? mFp.getName() : mFingerName;
    623                         mDialogTextField.setText(name);
    624                         if (mTextHadFocus == null) {
    625                             mDialogTextField.selectAll();
    626                         } else {
    627                             mDialogTextField.setSelection(mTextSelectionStart, mTextSelectionEnd);
    628                         }
    629                     }
    630                 });
    631                 if (mTextHadFocus == null || mTextHadFocus) {
    632                     // Request the IME
    633                     alertDialog.getWindow().setSoftInputMode(
    634                             WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
    635                 }
    636                 return alertDialog;
    637             }
    638 
    639             private void onDeleteClick(DialogInterface dialog) {
    640                 if (DEBUG) Log.v(TAG, "Removing fpId=" + mFp.getFingerId());
    641                 MetricsLogger.action(getContext(), MetricsEvent.ACTION_FINGERPRINT_DELETE,
    642                         mFp.getFingerId());
    643                 FingerprintSettingsFragment parent
    644                         = (FingerprintSettingsFragment) getTargetFragment();
    645                 final boolean isProfileChallengeUser =
    646                         Utils.isManagedProfile(UserManager.get(getContext()), parent.mUserId);
    647                 if (parent.mFingerprintManager.getEnrolledFingerprints(parent.mUserId).size() > 1) {
    648                     parent.deleteFingerPrint(mFp);
    649                 } else {
    650                     ConfirmLastDeleteDialog lastDeleteDialog = new ConfirmLastDeleteDialog();
    651                     Bundle args = new Bundle();
    652                     args.putParcelable("fingerprint", mFp);
    653                     args.putBoolean("isProfileChallengeUser", isProfileChallengeUser);
    654                     lastDeleteDialog.setArguments(args);
    655                     lastDeleteDialog.setTargetFragment(getTargetFragment(), 0);
    656                     lastDeleteDialog.show(getFragmentManager(),
    657                             ConfirmLastDeleteDialog.class.getName());
    658                 }
    659                 dialog.dismiss();
    660             }
    661 
    662             @Override
    663             public void onSaveInstanceState(Bundle outState) {
    664                 super.onSaveInstanceState(outState);
    665                 if (mDialogTextField != null) {
    666                     outState.putString("fingerName", mDialogTextField.getText().toString());
    667                     outState.putBoolean("textHadFocus", mDialogTextField.hasFocus());
    668                     outState.putInt("startSelection", mDialogTextField.getSelectionStart());
    669                     outState.putInt("endSelection", mDialogTextField.getSelectionEnd());
    670                 }
    671             }
    672         }
    673 
    674         public static class ConfirmLastDeleteDialog extends DialogFragment {
    675 
    676             private Fingerprint mFp;
    677 
    678             @Override
    679             public Dialog onCreateDialog(Bundle savedInstanceState) {
    680                 mFp = getArguments().getParcelable("fingerprint");
    681                 final boolean isProfileChallengeUser =
    682                         getArguments().getBoolean("isProfileChallengeUser");
    683                 final AlertDialog alertDialog = new AlertDialog.Builder(getActivity())
    684                         .setTitle(R.string.fingerprint_last_delete_title)
    685                         .setMessage((isProfileChallengeUser)
    686                                 ? R.string.fingerprint_last_delete_message_profile_challenge
    687                                 : R.string.fingerprint_last_delete_message)
    688                         .setPositiveButton(R.string.fingerprint_last_delete_confirm,
    689                                 new DialogInterface.OnClickListener() {
    690                                     @Override
    691                                     public void onClick(DialogInterface dialog, int which) {
    692                                         FingerprintSettingsFragment parent
    693                                                 = (FingerprintSettingsFragment) getTargetFragment();
    694                                         parent.deleteFingerPrint(mFp);
    695                                         dialog.dismiss();
    696                                     }
    697                                 })
    698                         .setNegativeButton(
    699                                 R.string.cancel,
    700                                 new DialogInterface.OnClickListener() {
    701                                     @Override
    702                                     public void onClick(DialogInterface dialog, int which) {
    703                                         dialog.dismiss();
    704                                     }
    705                                 })
    706                         .create();
    707                 return alertDialog;
    708             }
    709         }
    710     }
    711 
    712     public static class FingerprintPreference extends Preference {
    713         private Fingerprint mFingerprint;
    714         private View mView;
    715 
    716         public FingerprintPreference(Context context, AttributeSet attrs, int defStyleAttr,
    717                 int defStyleRes) {
    718             super(context, attrs, defStyleAttr, defStyleRes);
    719         }
    720         public FingerprintPreference(Context context, AttributeSet attrs, int defStyleAttr) {
    721             super(context, attrs, defStyleAttr);
    722         }
    723 
    724         public FingerprintPreference(Context context, AttributeSet attrs) {
    725             super(context, attrs);
    726         }
    727 
    728         public FingerprintPreference(Context context) {
    729             super(context);
    730         }
    731 
    732         public View getView() { return mView; }
    733 
    734         public void setFingerprint(Fingerprint item) {
    735             mFingerprint = item;
    736         }
    737 
    738         public Fingerprint getFingerprint() {
    739             return mFingerprint;
    740         }
    741 
    742         @Override
    743         public void onBindViewHolder(PreferenceViewHolder view) {
    744             super.onBindViewHolder(view);
    745             mView = view.itemView;
    746         }
    747     };
    748 
    749     private static class LearnMoreSpan extends URLSpan {
    750 
    751         private static final Typeface TYPEFACE_MEDIUM =
    752                 Typeface.create("sans-serif-medium", Typeface.NORMAL);
    753 
    754         private static final String ANNOTATION_URL = "url";
    755         private static final String ANNOTATION_ADMIN_DETAILS = "admin_details";
    756 
    757         private EnforcedAdmin mEnforcedAdmin = null;
    758 
    759         private LearnMoreSpan(String url) {
    760             super(url);
    761         }
    762 
    763         private LearnMoreSpan(EnforcedAdmin admin) {
    764             super((String) null);
    765             mEnforcedAdmin = admin;
    766         }
    767 
    768         @Override
    769         public void onClick(View widget) {
    770             Context ctx = widget.getContext();
    771             if (mEnforcedAdmin != null) {
    772                 RestrictedLockUtils.sendShowAdminSupportDetailsIntent(ctx, mEnforcedAdmin);
    773             } else {
    774                 Intent intent = HelpUtils.getHelpIntent(ctx, getURL(), ctx.getClass().getName());
    775                 try {
    776                     widget.startActivityForResult(intent, 0);
    777                 } catch (ActivityNotFoundException e) {
    778                     Log.w(FingerprintSettingsFragment.TAG,
    779                             "Actvity was not found for intent, " + intent.toString());
    780                 }
    781             }
    782         }
    783 
    784         @Override
    785         public void updateDrawState(TextPaint ds) {
    786             super.updateDrawState(ds);
    787             ds.setUnderlineText(false);
    788             ds.setTypeface(TYPEFACE_MEDIUM);
    789         }
    790 
    791         public static CharSequence linkify(CharSequence rawText, String uri, EnforcedAdmin admin) {
    792             SpannableString msg = new SpannableString(rawText);
    793             Annotation[] spans = msg.getSpans(0, msg.length(), Annotation.class);
    794             SpannableStringBuilder builder = new SpannableStringBuilder(msg);
    795             for (Annotation annotation : spans) {
    796                 final String key = annotation.getValue();
    797                 int start = msg.getSpanStart(annotation);
    798                 int end = msg.getSpanEnd(annotation);
    799                 LearnMoreSpan link = null;
    800                 if (ANNOTATION_URL.equals(key)) {
    801                     link = new LearnMoreSpan(uri);
    802                 } else if (ANNOTATION_ADMIN_DETAILS.equals(key)) {
    803                     link = new LearnMoreSpan(admin);
    804                 }
    805                 if (link != null) {
    806                     builder.setSpan(link, start, end, msg.getSpanFlags(link));
    807                 }
    808             }
    809             return builder;
    810         }
    811     }
    812 
    813     public static Preference getFingerprintPreferenceForUser(Context context, final int userId) {
    814         FingerprintManager fpm = (FingerprintManager) context.getSystemService(
    815                 Context.FINGERPRINT_SERVICE);
    816         if (fpm == null || !fpm.isHardwareDetected()) {
    817             Log.v(TAG, "No fingerprint hardware detected!!");
    818             return null;
    819         }
    820         Preference fingerprintPreference = new Preference(context);
    821         fingerprintPreference.setKey(KEY_FINGERPRINT_SETTINGS);
    822         fingerprintPreference.setTitle(R.string.security_settings_fingerprint_preference_title);
    823         final List<Fingerprint> items = fpm.getEnrolledFingerprints(userId);
    824         final int fingerprintCount = items != null ? items.size() : 0;
    825         final String clazz;
    826         if (fingerprintCount > 0) {
    827             fingerprintPreference.setSummary(context.getResources().getQuantityString(
    828                     R.plurals.security_settings_fingerprint_preference_summary,
    829                     fingerprintCount, fingerprintCount));
    830             clazz = FingerprintSettings.class.getName();
    831         } else {
    832             fingerprintPreference.setSummary(
    833                     R.string.security_settings_fingerprint_preference_summary_none);
    834             clazz = FingerprintEnrollIntroduction.class.getName();
    835         }
    836         fingerprintPreference.setOnPreferenceClickListener(new OnPreferenceClickListener() {
    837             @Override
    838             public boolean onPreferenceClick(Preference preference) {
    839                 final Context context = preference.getContext();
    840                 final UserManager userManager = UserManager.get(context);
    841                 if (Utils.startQuietModeDialogIfNecessary(context, userManager,
    842                         userId)) {
    843                     return false;
    844                 }
    845                 Intent intent = new Intent();
    846                 intent.setClassName("com.android.settings", clazz);
    847                 intent.putExtra(Intent.EXTRA_USER_ID, userId);
    848                 context.startActivity(intent);
    849                 return true;
    850             }
    851         });
    852         return fingerprintPreference;
    853     }
    854 }
    855