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