• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 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.face;
18 
19 import static android.app.Activity.RESULT_OK;
20 import static android.app.admin.DevicePolicyResources.Strings.Settings.FACE_SETTINGS_FOR_WORK_TITLE;
21 
22 import static com.android.settings.Utils.isPrivateProfile;
23 import static com.android.settings.biometrics.BiometricEnrollBase.BIOMETRIC_AUTH_REQUEST;
24 import static com.android.settings.biometrics.BiometricEnrollBase.CONFIRM_REQUEST;
25 import static com.android.settings.biometrics.BiometricEnrollBase.ENROLL_REQUEST;
26 import static com.android.settings.biometrics.BiometricEnrollBase.RESULT_FINISHED;
27 import static com.android.settings.biometrics.BiometricEnrollBase.RESULT_TIMEOUT;
28 
29 import android.app.admin.DevicePolicyManager;
30 import android.app.settings.SettingsEnums;
31 import android.content.Context;
32 import android.content.Intent;
33 import android.content.res.ResourceId;
34 import android.hardware.face.FaceManager;
35 import android.os.Bundle;
36 import android.os.UserHandle;
37 import android.os.UserManager;
38 import android.util.Log;
39 import android.widget.Button;
40 
41 import androidx.preference.Preference;
42 import androidx.preference.PreferenceCategory;
43 
44 import com.android.settings.R;
45 import com.android.settings.SettingsActivity;
46 import com.android.settings.Utils;
47 import com.android.settings.biometrics.BiometricEnrollBase;
48 import com.android.settings.biometrics.BiometricUtils;
49 import com.android.settings.biometrics.IdentityCheckBiometricErrorDialog;
50 import com.android.settings.dashboard.DashboardFragment;
51 import com.android.settings.flags.Flags;
52 import com.android.settings.overlay.FeatureFactory;
53 import com.android.settings.password.ChooseLockSettingsHelper;
54 import com.android.settings.password.ConfirmDeviceCredentialActivity;
55 import com.android.settings.search.BaseSearchIndexProvider;
56 import com.android.settingslib.RestrictedLockUtilsInternal;
57 import com.android.settingslib.core.AbstractPreferenceController;
58 import com.android.settingslib.search.SearchIndexable;
59 import com.android.settingslib.widget.LayoutPreference;
60 
61 import java.util.ArrayList;
62 import java.util.Arrays;
63 import java.util.List;
64 
65 /**
66  * Settings screen for face authentication.
67  */
68 @SearchIndexable
69 public class FaceSettings extends DashboardFragment {
70 
71     private static final String TAG = "FaceSettings";
72     private static final String KEY_TOKEN = "hw_auth_token";
73     private static final String KEY_CONFIRMING_PASSWORD = "confirming_password";
74     private static final String KEY_RE_ENROLL_FACE = "re_enroll_face_unlock";
75     private static final String KEY_BIOMETRICS_SUCCESSFULLY_AUTHENTICATED =
76             "biometrics_successfully_authenticated";
77 
78     private static final String PREF_KEY_FACE_DESCRIPTION = "security_settings_face_description";
79     private static final String PREF_KEY_DELETE_FACE_DATA =
80             "security_settings_face_delete_faces_container";
81     private static final String PREF_KEY_ENROLL_FACE_UNLOCK =
82             "security_settings_face_enroll_faces_container";
83     private static final String PREF_KEY_USE_FACE_TO_CATEGORY =
84             "biometric_settings_use_face_to";
85     private static final String PREF_KEY_FACE_ENROLLED_CATEGORY =
86             "security_settings_face_enrolled_category";
87     private static final String PREF_KEY_FACE_REMOVE =
88             "security_settings_face_remove";
89     private static final String PREF_KEY_FACE_ENROLL =
90             "security_settings_face_enroll";
91     public static final String SECURITY_SETTINGS_FACE_MANAGE_CATEGORY =
92             "security_settings_face_manage_category";
93 
94     private UserManager mUserManager;
95     private FaceManager mFaceManager;
96     private DevicePolicyManager mDevicePolicyManager;
97     private int mUserId;
98     private int mSensorId;
99     private long mChallenge;
100     private byte[] mToken;
101     private FaceSettingsAttentionPreferenceController mAttentionController;
102     private FaceSettingsRemoveButtonPreferenceController mRemoveController;
103     private FaceSettingsEnrollButtonPreferenceController mEnrollController;
104     private FaceSettingsLockscreenBypassPreferenceController mLockscreenController;
105     private List<AbstractPreferenceController> mControllers;
106 
107     private List<Preference> mTogglePreferences;
108     private Preference mRemoveButton;
109     private Preference mEnrollButton;
110     private PreferenceCategory mFaceEnrolledCategory;
111     private Preference mFaceRemoveButton;
112     private Preference mFaceEnrollButton;
113     private FaceFeatureProvider mFaceFeatureProvider;
114 
115     private boolean mConfirmingPassword;
116     private boolean mBiometricsAuthenticationRequested;
117 
118     private final FaceSettingsRemoveButtonPreferenceController.Listener mRemovalListener = () -> {
119 
120         // Disable the toggles until the user re-enrolls
121         for (Preference preference : mTogglePreferences) {
122             preference.setEnabled(false);
123         }
124 
125         // Hide the "remove" button and show the "set up face authentication" button.
126         updateFaceAddAndRemovePreference(false);
127     };
128 
129     private final FaceSettingsEnrollButtonPreferenceController.Listener mEnrollListener = intent ->
130             startActivityForResult(intent, ENROLL_REQUEST);
131 
132     /**
133      * @param context
134      * @return true if the Face hardware is detected.
135      */
isFaceHardwareDetected(Context context)136     public static boolean isFaceHardwareDetected(Context context) {
137         FaceManager manager = Utils.getFaceManagerOrNull(context);
138         boolean isHardwareDetected = false;
139         if (manager == null) {
140             Log.d(TAG, "FaceManager is null");
141         } else {
142             isHardwareDetected = manager.isHardwareDetected();
143             Log.d(TAG, "FaceManager is not null. Hardware detected: " + isHardwareDetected);
144         }
145         return manager != null && isHardwareDetected;
146     }
147 
148     @Override
getMetricsCategory()149     public int getMetricsCategory() {
150         return SettingsEnums.FACE;
151     }
152 
153     @Override
getPreferenceScreenResId()154     protected int getPreferenceScreenResId() {
155         return R.xml.security_settings_face;
156     }
157 
158     @Override
getLogTag()159     protected String getLogTag() {
160         return TAG;
161     }
162 
163     @Override
onSaveInstanceState(Bundle outState)164     public void onSaveInstanceState(Bundle outState) {
165         super.onSaveInstanceState(outState);
166         outState.putByteArray(KEY_TOKEN, mToken);
167         outState.putBoolean(KEY_CONFIRMING_PASSWORD, mConfirmingPassword);
168     }
169 
170     @Override
onCreate(Bundle savedInstanceState)171     public void onCreate(Bundle savedInstanceState) {
172         super.onCreate(savedInstanceState);
173 
174         final Context context = getPrefContext();
175         if (!isFaceHardwareDetected(context)) {
176             Log.w(TAG, "no faceManager, finish this");
177             finish();
178             return;
179         }
180 
181         mUserManager = context.getSystemService(UserManager.class);
182         mFaceManager = context.getSystemService(FaceManager.class);
183         mDevicePolicyManager = context.getSystemService(DevicePolicyManager.class);
184         mToken = getIntent().getByteArrayExtra(KEY_TOKEN);
185         mSensorId = getIntent().getIntExtra(BiometricEnrollBase.EXTRA_KEY_SENSOR_ID, -1);
186         mChallenge = getIntent().getLongExtra(BiometricEnrollBase.EXTRA_KEY_CHALLENGE, 0L);
187 
188         mUserId = getActivity().getIntent().getIntExtra(
189                 Intent.EXTRA_USER_ID, UserHandle.myUserId());
190         mFaceFeatureProvider = FeatureFactory.getFeatureFactory().getFaceFeatureProvider();
191 
192         if (mUserManager.getUserInfo(mUserId).isManagedProfile()) {
193             getActivity().setTitle(
194                     mDevicePolicyManager.getResources().getString(FACE_SETTINGS_FOR_WORK_TITLE,
195                             () -> getActivity().getResources().getString(
196                                     R.string.security_settings_face_profile_preference_title)));
197         } else if (isPrivateProfile(mUserId, getContext())) {
198             getActivity().setTitle(
199                     getActivity().getResources().getString(
200                     R.string.private_space_face_unlock_title));
201         }
202 
203         mLockscreenController = Utils.isMultipleBiometricsSupported(context)
204                 ? use(BiometricLockscreenBypassPreferenceController.class)
205                 : use(FaceSettingsLockscreenBypassPreferenceController.class);
206         mLockscreenController.setUserId(mUserId);
207 
208         final int descriptionResId = FeatureFactory.getFeatureFactory()
209                 .getFaceFeatureProvider().getFaceSettingsFeatureProvider()
210                 .getSettingPageDescription();
211         if (ResourceId.isValid(descriptionResId)) {
212             final Preference preference = findPreference(PREF_KEY_FACE_DESCRIPTION);
213             preference.setTitle(descriptionResId);
214             preference.setVisible(true);
215         }
216 
217         final PreferenceCategory managePref =
218                 findPreference(SECURITY_SETTINGS_FACE_MANAGE_CATEGORY);
219         Preference keyguardPref = findPreference(FaceSettingsKeyguardPreferenceController.KEY);
220         Preference appPref = findPreference(FaceSettingsAppPreferenceController.KEY);
221         Preference attentionPref = findPreference(FaceSettingsAttentionPreferenceController.KEY);
222         Preference confirmPref = findPreference(FaceSettingsConfirmPreferenceController.KEY);
223         Preference bypassPref =
224                 findPreference(mLockscreenController.getPreferenceKey());
225         mTogglePreferences = new ArrayList<>(
226                 Arrays.asList(keyguardPref, appPref, attentionPref, confirmPref, bypassPref));
227 
228         if (Flags.biometricsOnboardingEducation()) {
229             if (use(FaceSettingsKeyguardUnlockPreferenceController.class) != null) {
230                 Preference unlockKeyguard = findPreference(
231                         use(FaceSettingsKeyguardUnlockPreferenceController.class)
232                                 .getPreferenceKey());
233                 mTogglePreferences.add(unlockKeyguard);
234             }
235             if (use(FaceSettingsAppsPreferenceController.class) != null) {
236                 Preference appsPref = findPreference(
237                         use(FaceSettingsAppsPreferenceController.class).getPreferenceKey());
238                 mTogglePreferences.add(appsPref);
239             }
240         }
241 
242         if (RestrictedLockUtilsInternal.checkIfKeyguardFeaturesDisabled(
243                 getContext(), DevicePolicyManager.KEYGUARD_DISABLE_FACE, mUserId) != null) {
244             managePref.setTitle(getString(
245                     com.android.settingslib.widget.restricted.R.string.disabled_by_admin));
246         } else {
247             managePref.setTitle(R.string.security_settings_face_settings_preferences_category);
248         }
249 
250         mRemoveButton = findPreference(FaceSettingsRemoveButtonPreferenceController.KEY);
251         mEnrollButton = findPreference(FaceSettingsEnrollButtonPreferenceController.KEY);
252 
253         if (Flags.biometricsOnboardingEducation()) {
254             mFaceEnrolledCategory = findPreference(PREF_KEY_FACE_ENROLLED_CATEGORY);
255             mFaceRemoveButton = findPreference(PREF_KEY_FACE_REMOVE);
256             mFaceRemoveButton.setIcon(R.drawable.ic_face);
257             mFaceRemoveButton.setOnPreferenceClickListener(
258                     use(FaceSettingsRemoveButtonPreferenceController.class));
259             mFaceEnrollButton = findPreference(PREF_KEY_FACE_ENROLL);
260             mFaceEnrollButton.setIcon(R.drawable.ic_add_24dp);
261             mFaceEnrollButton.setOnPreferenceClickListener(
262                     use(FaceSettingsEnrollButtonPreferenceController.class));
263         }
264 
265         final boolean hasEnrolled = mFaceManager.hasEnrolledTemplates(mUserId);
266         updateFaceAddAndRemovePreference(hasEnrolled);
267 
268         // There is no better way to do this :/
269         for (AbstractPreferenceController controller : mControllers) {
270             if (controller instanceof FaceSettingsPreferenceController) {
271                 ((FaceSettingsPreferenceController) controller).setUserId(mUserId);
272             } else if (controller instanceof FaceSettingsEnrollButtonPreferenceController) {
273                 ((FaceSettingsEnrollButtonPreferenceController) controller).setUserId(mUserId);
274             } else if (controller instanceof FaceSettingsFooterPreferenceController) {
275                 ((FaceSettingsFooterPreferenceController) controller).setUserId(mUserId);
276             }
277         }
278         mRemoveController.setUserId(mUserId);
279 
280         // Don't show keyguard controller for work and private profile settings.
281         if (mUserManager.isManagedProfile(mUserId)
282                 || mUserManager.getUserInfo(mUserId).isPrivateProfile()) {
283             removePreference(FaceSettingsKeyguardPreferenceController.KEY);
284             removePreference(mLockscreenController.getPreferenceKey());
285         }
286 
287         if (savedInstanceState != null) {
288             mToken = savedInstanceState.getByteArray(KEY_TOKEN);
289             mConfirmingPassword = savedInstanceState.getBoolean(KEY_CONFIRMING_PASSWORD);
290         }
291 
292         if (Flags.biometricsOnboardingEducation()) {
293             final PreferenceCategory category =
294                     findPreference(PREF_KEY_USE_FACE_TO_CATEGORY);
295             category.setVisible(true);
296             use(FaceSettingsKeyguardUnlockPreferenceController.class).setUserId(mUserId);
297             use(FaceSettingsAppsPreferenceController.class).setUserId(mUserId);
298         }
299     }
300 
301     @Override
onStart()302     public void onStart() {
303         super.onStart();
304         final boolean hasEnrolled = mFaceManager.hasEnrolledTemplates(mUserId);
305         updateFaceAddAndRemovePreference(hasEnrolled);
306 
307         // When the user has face id registered but failed enrolling in device lock state,
308         // lead users directly to the confirm deletion dialog in Face Unlock settings.
309         if (hasEnrolled) {
310             final boolean isReEnrollFaceUnlock = getIntent().getBooleanExtra(
311                     FaceSettings.KEY_RE_ENROLL_FACE, false);
312             if (isReEnrollFaceUnlock) {
313                 if (Flags.biometricsOnboardingEducation()) {
314                     if (mFaceRemoveButton.isEnabled()) {
315                         mRemoveController.onPreferenceClick(mFaceRemoveButton);
316                     }
317                 } else {
318                     final Button removeBtn = ((LayoutPreference) mRemoveButton).findViewById(
319                             R.id.security_settings_face_settings_remove_button);
320                     if (removeBtn != null && removeBtn.isEnabled()) {
321                         mRemoveController.onClick(removeBtn);
322                     }
323                 }
324             }
325         }
326     }
327 
328     @Override
onResume()329     public void onResume() {
330         super.onResume();
331 
332         if (mToken == null && !mConfirmingPassword) {
333             final ChooseLockSettingsHelper.Builder builder =
334                     new ChooseLockSettingsHelper.Builder(getActivity(), this);
335             final boolean launched = builder.setRequestCode(CONFIRM_REQUEST)
336                     .setTitle(getString(R.string.security_settings_face_preference_title))
337                     .setRequestGatekeeperPasswordHandle(true)
338                     .setUserId(mUserId)
339                     .setForegroundOnly(true)
340                     .setReturnCredentials(true)
341                     .show();
342 
343             mConfirmingPassword = true;
344             if (!launched) {
345                 Log.e(TAG, "Password not set");
346                 finish();
347             }
348         } else {
349             mAttentionController.setToken(mToken);
350             mEnrollController.setToken(mToken);
351         }
352 
353         if (!mFaceFeatureProvider.isAttentionSupported(getContext())) {
354             removePreference(FaceSettingsAttentionPreferenceController.KEY);
355         }
356     }
357 
358     @Override
onActivityResult(int requestCode, int resultCode, Intent data)359     public void onActivityResult(int requestCode, int resultCode, Intent data) {
360         super.onActivityResult(requestCode, resultCode, data);
361 
362         if (mToken == null && !BiometricUtils.containsGatekeeperPasswordHandle(data)) {
363             Log.e(TAG, "No credential");
364             finish();
365         }
366 
367         if (requestCode == CONFIRM_REQUEST) {
368             if (resultCode == RESULT_FINISHED || resultCode == RESULT_OK) {
369                 // The pin/pattern/password was set.
370                 mFaceManager.generateChallenge(mUserId, (sensorId, userId, challenge) -> {
371                     mToken = BiometricUtils.requestGatekeeperHat(getPrefContext(), data, mUserId,
372                             challenge);
373                     mSensorId = sensorId;
374                     mChallenge = challenge;
375                     BiometricUtils.removeGatekeeperPasswordHandle(getPrefContext(), data);
376                     mAttentionController.setToken(mToken);
377                     mEnrollController.setToken(mToken);
378                     mConfirmingPassword = false;
379                 });
380 
381                 final boolean hasEnrolled = mFaceManager.hasEnrolledTemplates(mUserId);
382                 updateFaceAddAndRemovePreference(hasEnrolled);
383                 final Utils.BiometricStatus biometricAuthStatus =
384                         Utils.requestBiometricAuthenticationForMandatoryBiometrics(getActivity(),
385                                 mBiometricsAuthenticationRequested,
386                                 mUserId);
387                 if (biometricAuthStatus == Utils.BiometricStatus.OK) {
388                     Utils.launchBiometricPromptForMandatoryBiometrics(this,
389                             BIOMETRIC_AUTH_REQUEST,
390                             mUserId, true /* hideBackground */);
391                 } else if (biometricAuthStatus != Utils.BiometricStatus.NOT_ACTIVE) {
392                     IdentityCheckBiometricErrorDialog
393                             .showBiometricErrorDialogAndFinishActivityOnDismiss(getActivity(),
394                                     biometricAuthStatus);
395                 }
396             }
397         } else if (requestCode == ENROLL_REQUEST) {
398             if (resultCode == RESULT_TIMEOUT) {
399                 setResult(resultCode, data);
400                 finish();
401             }
402         } else if (requestCode == BIOMETRIC_AUTH_REQUEST) {
403             mBiometricsAuthenticationRequested = false;
404             if (resultCode != RESULT_OK) {
405                 if (resultCode == ConfirmDeviceCredentialActivity.BIOMETRIC_LOCKOUT_ERROR_RESULT) {
406                     IdentityCheckBiometricErrorDialog
407                             .showBiometricErrorDialogAndFinishActivityOnDismiss(getActivity(),
408                                     Utils.BiometricStatus.LOCKOUT);
409                 } else {
410                     finish();
411                 }
412             }
413         }
414     }
415 
416     @Override
onStop()417     public void onStop() {
418         super.onStop();
419 
420         if (!mEnrollController.isClicked() && !getActivity().isChangingConfigurations()
421                 && !mConfirmingPassword) {
422             // Revoke challenge and finish
423             if (mToken != null) {
424                 mFaceManager.revokeChallenge(mSensorId, mUserId, mChallenge);
425                 mToken = null;
426             }
427             // Let parent "Face & Fingerprint Unlock" can use this error code to close itself.
428             setResult(RESULT_TIMEOUT);
429             finish();
430         }
431     }
432 
433     @Override
getHelpResource()434     public int getHelpResource() {
435         return R.string.help_url_face;
436     }
437 
438     @Override
createPreferenceControllers(Context context)439     protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
440         if (!isFaceHardwareDetected(context)) {
441             return null;
442         }
443         mControllers = buildPreferenceControllers(context);
444         // There's no great way of doing this right now :/
445         for (AbstractPreferenceController controller : mControllers) {
446             if (controller instanceof FaceSettingsAttentionPreferenceController) {
447                 mAttentionController = (FaceSettingsAttentionPreferenceController) controller;
448             } else if (controller instanceof FaceSettingsRemoveButtonPreferenceController) {
449                 mRemoveController = (FaceSettingsRemoveButtonPreferenceController) controller;
450                 mRemoveController.setListener(mRemovalListener);
451                 mRemoveController.setActivity((SettingsActivity) getActivity());
452             } else if (controller instanceof FaceSettingsEnrollButtonPreferenceController) {
453                 mEnrollController = (FaceSettingsEnrollButtonPreferenceController) controller;
454                 mEnrollController.setListener(mEnrollListener);
455             }
456         }
457 
458         return mControllers;
459     }
460 
updateFaceAddAndRemovePreference(boolean hasEnrolled)461     private void updateFaceAddAndRemovePreference(boolean hasEnrolled) {
462         if (Flags.biometricsOnboardingEducation()) {
463             mFaceEnrolledCategory.setVisible(true);
464             mFaceRemoveButton.setVisible(hasEnrolled);
465             mFaceEnrollButton.setVisible(!hasEnrolled);
466         } else {
467             mEnrollButton.setVisible(!hasEnrolled);
468             mRemoveButton.setVisible(hasEnrolled);
469         }
470     }
471 
buildPreferenceControllers(Context context)472     private static List<AbstractPreferenceController> buildPreferenceControllers(Context context) {
473         final List<AbstractPreferenceController> controllers = new ArrayList<>();
474         controllers.add(new FaceSettingsKeyguardPreferenceController(context));
475         controllers.add(new FaceSettingsAppPreferenceController(context));
476         controllers.add(new FaceSettingsAttentionPreferenceController(context));
477         controllers.add(new FaceSettingsRemoveButtonPreferenceController(context));
478         controllers.add(new FaceSettingsConfirmPreferenceController(context));
479         controllers.add(new FaceSettingsEnrollButtonPreferenceController(context));
480         controllers.add(new FaceSettingsFooterPreferenceController(context));
481         return controllers;
482     }
483 
484     public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
485             new BaseSearchIndexProvider(R.xml.security_settings_face) {
486 
487                 @Override
488                 public List<AbstractPreferenceController> createPreferenceControllers(
489                         Context context) {
490                     if (isFaceHardwareDetected(context)) {
491                         return buildPreferenceControllers(context);
492                     } else {
493                         return null;
494                     }
495                 }
496 
497                 @Override
498                 protected boolean isPageSearchEnabled(Context context) {
499                     if (isFaceHardwareDetected(context)) {
500                         return hasEnrolledBiometrics(context);
501                     }
502 
503                     return false;
504                 }
505 
506                 @Override
507                 public List<String> getNonIndexableKeys(Context context) {
508                     final List<String> keys = super.getNonIndexableKeys(context);
509                     final boolean isFaceHardwareDetected = isFaceHardwareDetected(context);
510                     Log.d(TAG, "Get non indexable keys. isFaceHardwareDetected: "
511                             + isFaceHardwareDetected + ", size:" + keys.size());
512                     if (isFaceHardwareDetected) {
513                         final boolean hasEnrolled = hasEnrolledBiometrics(context);
514                         keys.add(hasEnrolled ? PREF_KEY_ENROLL_FACE_UNLOCK
515                                 : PREF_KEY_DELETE_FACE_DATA);
516                     }
517 
518                     if (!isAttentionSupported(context)) {
519                         keys.add(FaceSettingsAttentionPreferenceController.KEY);
520                     }
521 
522                     return keys;
523                 }
524 
525                 private boolean isAttentionSupported(Context context) {
526                     FaceFeatureProvider featureProvider =
527                             FeatureFactory.getFeatureFactory().getFaceFeatureProvider();
528                     return featureProvider.isAttentionSupported(context);
529                 }
530 
531                 private boolean hasEnrolledBiometrics(Context context) {
532                     final FaceManager faceManager = Utils.getFaceManagerOrNull(context);
533                     if (faceManager != null) {
534                         return faceManager.hasEnrolledTemplates(UserHandle.myUserId());
535                     }
536                     return false;
537                 }
538             };
539 }
540