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