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