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