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