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