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
getIntent()111     public Intent getIntent() {
112         Intent modIntent = new Intent(super.getIntent());
113         modIntent.putExtra(EXTRA_SHOW_FRAGMENT, FingerprintSettingsFragment.class.getName());
114         return modIntent;
115     }
116 
117     @Override
isValidFragment(String fragmentName)118     protected boolean isValidFragment(String fragmentName) {
119         if (FingerprintSettingsFragment.class.getName().equals(fragmentName)) return true;
120         return false;
121     }
122 
123     @Override
onCreate(Bundle savedInstanceState)124     public void onCreate(Bundle savedInstanceState) {
125         super.onCreate(savedInstanceState);
126         CharSequence msg = getText(R.string.security_settings_fingerprint_preference_title);
127         setTitle(msg);
128     }
129 
130     public static class FingerprintSettingsFragment extends SettingsPreferenceFragment
131         implements OnPreferenceChangeListener {
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 
stopFingerprint()243         private void stopFingerprint() {
244             if (mFingerprintCancel != null && !mFingerprintCancel.isCanceled()) {
245                 mFingerprintCancel.cancel();
246             }
247             mFingerprintCancel = null;
248         }
249 
250         /**
251          * @param errMsgId
252          */
handleError(int errMsgId, CharSequence msg)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 
retryFingerprint()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
getMetricsCategory()289         public int getMetricsCategory() {
290             return MetricsEvent.FINGERPRINT;
291         }
292 
293         @Override
onCreate(Bundle savedInstanceState)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 
removeFingerprintPreference(int fingerprintId)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          */
createPreferenceHierarchy()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 
addFingerprintItemPreferences(PreferenceGroup root)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 
updateAddPreference()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 
genKey(int id)421         private static String genKey(int id) {
422             return KEY_FINGERPRINT_ITEM_PREFIX + "_" + id;
423         }
424 
425         @Override
onResume()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 
updatePreferences()436         private void updatePreferences() {
437             createPreferenceHierarchy();
438             retryFingerprint();
439         }
440 
441         @Override
onPause()442         public void onPause() {
443             super.onPause();
444             stopFingerprint();
445             if (mRemovalSidecar != null) {
446                 mRemovalSidecar.setListener(null);
447             }
448         }
449 
450         @Override
onSaveInstanceState(final Bundle outState)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
onPreferenceTreeClick(Preference pref)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 
showRenameDeleteDialog(final Fingerprint fp)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
onPreferenceChange(Preference preference, Object value)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
getHelpResource()506         protected int getHelpResource() {
507             return R.string.help_url_fingerprint;
508         }
509 
510         @Override
onActivityResult(int requestCode, int resultCode, Intent data)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
onDestroy()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 
getHighlightDrawable()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 
highlightFingerprintItem(int fpId)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 
launchChooseOrConfirmLock()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 
deleteFingerPrint(Fingerprint fingerPrint)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 
renameFingerPrint(int fingerId, String newName)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 
setDeleteInProgress(boolean deleteInProgress)633             public void setDeleteInProgress(boolean deleteInProgress) {
634                 mDeleteInProgress = deleteInProgress;
635             }
636             @Override
onCreateDialog(Bundle savedInstanceState)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 
enableDelete()704             public void enableDelete() {
705                 mDeleteInProgress = false;
706                 if (mAlertDialog != null) {
707                     mAlertDialog.getButton(AlertDialog.BUTTON_NEGATIVE).setEnabled(true);
708                 }
709             }
710 
onDeleteClick(DialogInterface dialog)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
onSaveInstanceState(Bundle outState)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
getMetricsCategory()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
getMetricsCategory()756             public int getMetricsCategory() {
757                 return MetricsEvent.DIALOG_FINGERPINT_DELETE_LAST;
758             }
759 
760             @Override
onCreateDialog(Bundle savedInstanceState)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 
FingerprintPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)798         public FingerprintPreference(Context context, AttributeSet attrs, int defStyleAttr,
799                 int defStyleRes) {
800             super(context, attrs, defStyleAttr, defStyleRes);
801         }
FingerprintPreference(Context context, AttributeSet attrs, int defStyleAttr)802         public FingerprintPreference(Context context, AttributeSet attrs, int defStyleAttr) {
803             super(context, attrs, defStyleAttr);
804         }
805 
FingerprintPreference(Context context, AttributeSet attrs)806         public FingerprintPreference(Context context, AttributeSet attrs) {
807             super(context, attrs);
808         }
809 
FingerprintPreference(Context context)810         public FingerprintPreference(Context context) {
811             super(context);
812         }
813 
getView()814         public View getView() { return mView; }
815 
setFingerprint(Fingerprint item)816         public void setFingerprint(Fingerprint item) {
817             mFingerprint = item;
818         }
819 
getFingerprint()820         public Fingerprint getFingerprint() {
821             return mFingerprint;
822         }
823 
824         @Override
onBindViewHolder(PreferenceViewHolder view)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 
LearnMoreSpan(String url)841         private LearnMoreSpan(String url) {
842             super(url);
843         }
844 
LearnMoreSpan(EnforcedAdmin admin)845         private LearnMoreSpan(EnforcedAdmin admin) {
846             super((String) null);
847             mEnforcedAdmin = admin;
848         }
849 
850         @Override
onClick(View widget)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
updateDrawState(TextPaint ds)867         public void updateDrawState(TextPaint ds) {
868             super.updateDrawState(ds);
869             ds.setUnderlineText(false);
870             ds.setTypeface(TYPEFACE_MEDIUM);
871         }
872 
linkify(CharSequence rawText, String uri, EnforcedAdmin admin)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 
getFingerprintPreferenceForUser(Context context, final int userId)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