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