• 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 import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
22 
23 import static com.android.settings.biometrics.BiometricEnrollBase.CONFIRM_REQUEST;
24 import static com.android.settings.biometrics.BiometricEnrollBase.ENROLL_REQUEST;
25 import static com.android.settings.biometrics.BiometricEnrollBase.RESULT_FINISHED;
26 import static com.android.settings.biometrics.BiometricEnrollBase.RESULT_TIMEOUT;
27 
28 import android.app.admin.DevicePolicyManager;
29 import android.app.settings.SettingsEnums;
30 import android.content.Context;
31 import android.content.Intent;
32 import android.hardware.face.FaceManager;
33 import android.os.Bundle;
34 import android.os.UserHandle;
35 import android.os.UserManager;
36 import android.util.Log;
37 import android.widget.Button;
38 
39 import androidx.preference.Preference;
40 
41 import com.android.settings.R;
42 import com.android.settings.SettingsActivity;
43 import com.android.settings.Utils;
44 import com.android.settings.biometrics.BiometricEnrollBase;
45 import com.android.settings.biometrics.BiometricUtils;
46 import com.android.settings.biometrics.BiometricsSplitScreenDialog;
47 import com.android.settings.dashboard.DashboardFragment;
48 import com.android.settings.overlay.FeatureFactory;
49 import com.android.settings.password.ChooseLockSettingsHelper;
50 import com.android.settings.search.BaseSearchIndexProvider;
51 import com.android.settingslib.activityembedding.ActivityEmbeddingUtils;
52 import com.android.settingslib.core.AbstractPreferenceController;
53 import com.android.settingslib.search.SearchIndexable;
54 import com.android.settingslib.widget.LayoutPreference;
55 
56 import java.util.ArrayList;
57 import java.util.Arrays;
58 import java.util.List;
59 
60 /**
61  * Settings screen for face authentication.
62  */
63 @SearchIndexable
64 public class FaceSettings extends DashboardFragment {
65 
66     private static final String TAG = "FaceSettings";
67     private static final String KEY_TOKEN = "hw_auth_token";
68     private static final String KEY_RE_ENROLL_FACE = "re_enroll_face_unlock";
69 
70     private static final String PREF_KEY_DELETE_FACE_DATA =
71             "security_settings_face_delete_faces_container";
72     private static final String PREF_KEY_ENROLL_FACE_UNLOCK =
73             "security_settings_face_enroll_faces_container";
74 
75     private UserManager mUserManager;
76     private FaceManager mFaceManager;
77     private DevicePolicyManager mDevicePolicyManager;
78     private int mUserId;
79     private int mSensorId;
80     private long mChallenge;
81     private byte[] mToken;
82     private FaceSettingsAttentionPreferenceController mAttentionController;
83     private FaceSettingsRemoveButtonPreferenceController mRemoveController;
84     private FaceSettingsEnrollButtonPreferenceController mEnrollController;
85     private FaceSettingsLockscreenBypassPreferenceController mLockscreenController;
86     private List<AbstractPreferenceController> mControllers;
87 
88     private List<Preference> mTogglePreferences;
89     private Preference mRemoveButton;
90     private Preference mEnrollButton;
91     private FaceFeatureProvider mFaceFeatureProvider;
92 
93     private boolean mConfirmingPassword;
94 
95     private final FaceSettingsRemoveButtonPreferenceController.Listener mRemovalListener = () -> {
96 
97         // Disable the toggles until the user re-enrolls
98         for (Preference preference : mTogglePreferences) {
99             preference.setEnabled(false);
100         }
101 
102         // Hide the "remove" button and show the "set up face authentication" button.
103         mRemoveButton.setVisible(false);
104         mEnrollButton.setVisible(true);
105     };
106 
107     private final FaceSettingsEnrollButtonPreferenceController.Listener mEnrollListener =
108             new FaceSettingsEnrollButtonPreferenceController.Listener() {
109                 @Override
110                 public boolean onShowSplitScreenDialog() {
111                     if (getActivity().isInMultiWindowMode()
112                             && !ActivityEmbeddingUtils.isActivityEmbedded(getActivity())) {
113                         // If it's in split mode, show the error dialog.
114                         BiometricsSplitScreenDialog.newInstance(TYPE_FACE).show(
115                                 getActivity().getSupportFragmentManager(),
116                                 BiometricsSplitScreenDialog.class.getName());
117                         return true;
118                     }
119                     return false;
120                 }
121 
122                 @Override
123                 public void onStartEnrolling(Intent intent) {
124                     FaceSettings.this.startActivityForResult(intent, ENROLL_REQUEST);
125                 }
126             };
127 
128     /**
129      * @param context
130      * @return true if the Face hardware is detected.
131      */
isFaceHardwareDetected(Context context)132     public static boolean isFaceHardwareDetected(Context context) {
133         FaceManager manager = Utils.getFaceManagerOrNull(context);
134         boolean isHardwareDetected = false;
135         if (manager == null) {
136             Log.d(TAG, "FaceManager is null");
137         } else {
138             isHardwareDetected = manager.isHardwareDetected();
139             Log.d(TAG, "FaceManager is not null. Hardware detected: " + isHardwareDetected);
140         }
141         return manager != null && isHardwareDetected;
142     }
143 
144     @Override
getMetricsCategory()145     public int getMetricsCategory() {
146         return SettingsEnums.FACE;
147     }
148 
149     @Override
getPreferenceScreenResId()150     protected int getPreferenceScreenResId() {
151         return R.xml.security_settings_face;
152     }
153 
154     @Override
getLogTag()155     protected String getLogTag() {
156         return TAG;
157     }
158 
159     @Override
onSaveInstanceState(Bundle outState)160     public void onSaveInstanceState(Bundle outState) {
161         super.onSaveInstanceState(outState);
162         outState.putByteArray(KEY_TOKEN, mToken);
163     }
164 
165     @Override
onCreate(Bundle savedInstanceState)166     public void onCreate(Bundle savedInstanceState) {
167         super.onCreate(savedInstanceState);
168 
169         final Context context = getPrefContext();
170         if (!isFaceHardwareDetected(context)) {
171             Log.w(TAG, "no faceManager, finish this");
172             finish();
173             return;
174         }
175 
176         mUserManager = context.getSystemService(UserManager.class);
177         mFaceManager = context.getSystemService(FaceManager.class);
178         mDevicePolicyManager = context.getSystemService(DevicePolicyManager.class);
179         mToken = getIntent().getByteArrayExtra(KEY_TOKEN);
180         mSensorId = getIntent().getIntExtra(BiometricEnrollBase.EXTRA_KEY_SENSOR_ID, -1);
181         mChallenge = getIntent().getLongExtra(BiometricEnrollBase.EXTRA_KEY_CHALLENGE, 0L);
182 
183         mUserId = getActivity().getIntent().getIntExtra(
184                 Intent.EXTRA_USER_ID, UserHandle.myUserId());
185         mFaceFeatureProvider = FeatureFactory.getFactory(getContext()).getFaceFeatureProvider();
186 
187         if (mUserManager.getUserInfo(mUserId).isManagedProfile()) {
188             getActivity().setTitle(
189                     mDevicePolicyManager.getResources().getString(FACE_SETTINGS_FOR_WORK_TITLE,
190                             () -> getActivity().getResources().getString(
191                                     R.string.security_settings_face_profile_preference_title)));
192         }
193 
194         mLockscreenController = Utils.isMultipleBiometricsSupported(context)
195                 ? use(BiometricLockscreenBypassPreferenceController.class)
196                 : use(FaceSettingsLockscreenBypassPreferenceController.class);
197         mLockscreenController.setUserId(mUserId);
198 
199         Preference keyguardPref = findPreference(FaceSettingsKeyguardPreferenceController.KEY);
200         Preference appPref = findPreference(FaceSettingsAppPreferenceController.KEY);
201         Preference attentionPref = findPreference(FaceSettingsAttentionPreferenceController.KEY);
202         Preference confirmPref = findPreference(FaceSettingsConfirmPreferenceController.KEY);
203         Preference bypassPref =
204                 findPreference(mLockscreenController.getPreferenceKey());
205         mTogglePreferences = new ArrayList<>(
206                 Arrays.asList(keyguardPref, appPref, attentionPref, confirmPref, bypassPref));
207 
208         mRemoveButton = findPreference(FaceSettingsRemoveButtonPreferenceController.KEY);
209         mEnrollButton = findPreference(FaceSettingsEnrollButtonPreferenceController.KEY);
210 
211         final boolean hasEnrolled = mFaceManager.hasEnrolledTemplates(mUserId);
212         mEnrollButton.setVisible(!hasEnrolled);
213         mRemoveButton.setVisible(hasEnrolled);
214 
215         // There is no better way to do this :/
216         for (AbstractPreferenceController controller : mControllers) {
217             if (controller instanceof FaceSettingsPreferenceController) {
218                 ((FaceSettingsPreferenceController) controller).setUserId(mUserId);
219             } else if (controller instanceof FaceSettingsEnrollButtonPreferenceController) {
220                 ((FaceSettingsEnrollButtonPreferenceController) controller).setUserId(mUserId);
221             }
222         }
223         mRemoveController.setUserId(mUserId);
224 
225         // Don't show keyguard controller for work profile settings.
226         if (mUserManager.isManagedProfile(mUserId)) {
227             removePreference(FaceSettingsKeyguardPreferenceController.KEY);
228             removePreference(mLockscreenController.getPreferenceKey());
229         }
230 
231         if (savedInstanceState != null) {
232             mToken = savedInstanceState.getByteArray(KEY_TOKEN);
233         }
234     }
235 
236     @Override
onStart()237     public void onStart() {
238         super.onStart();
239         final boolean hasEnrolled = mFaceManager.hasEnrolledTemplates(mUserId);
240         mEnrollButton.setVisible(!hasEnrolled);
241         mRemoveButton.setVisible(hasEnrolled);
242 
243         // When the user has face id registered but failed enrolling in device lock state,
244         // lead users directly to the confirm deletion dialog in Face Unlock settings.
245         if (hasEnrolled) {
246             final boolean isReEnrollFaceUnlock = getIntent().getBooleanExtra(
247                     FaceSettings.KEY_RE_ENROLL_FACE, false);
248             if (isReEnrollFaceUnlock) {
249                 final Button removeBtn = ((LayoutPreference) mRemoveButton).findViewById(
250                         R.id.security_settings_face_settings_remove_button);
251                 if (removeBtn != null && removeBtn.isEnabled()) {
252                     mRemoveController.onClick(removeBtn);
253                 }
254             }
255         }
256     }
257 
258     @Override
onResume()259     public void onResume() {
260         super.onResume();
261 
262         if (mToken == null && !mConfirmingPassword) {
263             final ChooseLockSettingsHelper.Builder builder =
264                     new ChooseLockSettingsHelper.Builder(getActivity(), this);
265             final boolean launched = builder.setRequestCode(CONFIRM_REQUEST)
266                     .setTitle(getString(R.string.security_settings_face_preference_title))
267                     .setRequestGatekeeperPasswordHandle(true)
268                     .setUserId(mUserId)
269                     .setForegroundOnly(true)
270                     .setReturnCredentials(true)
271                     .show();
272 
273             mConfirmingPassword = true;
274             if (!launched) {
275                 Log.e(TAG, "Password not set");
276                 finish();
277             }
278         } else {
279             mAttentionController.setToken(mToken);
280             mEnrollController.setToken(mToken);
281         }
282 
283         if (!mFaceFeatureProvider.isAttentionSupported(getContext())) {
284             removePreference(FaceSettingsAttentionPreferenceController.KEY);
285         }
286     }
287 
288     @Override
onActivityResult(int requestCode, int resultCode, Intent data)289     public void onActivityResult(int requestCode, int resultCode, Intent data) {
290         super.onActivityResult(requestCode, resultCode, data);
291 
292         if (mToken == null && !BiometricUtils.containsGatekeeperPasswordHandle(data)) {
293             Log.e(TAG, "No credential");
294             finish();
295         }
296 
297         if (requestCode == CONFIRM_REQUEST) {
298             if (resultCode == RESULT_FINISHED || resultCode == RESULT_OK) {
299                 // The pin/pattern/password was set.
300                 mFaceManager.generateChallenge(mUserId, (sensorId, userId, challenge) -> {
301                     mToken = BiometricUtils.requestGatekeeperHat(getPrefContext(), data, mUserId,
302                             challenge);
303                     mSensorId = sensorId;
304                     mChallenge = challenge;
305                     BiometricUtils.removeGatekeeperPasswordHandle(getPrefContext(), data);
306                     mAttentionController.setToken(mToken);
307                     mEnrollController.setToken(mToken);
308                     mConfirmingPassword = false;
309                 });
310 
311                 final boolean hasEnrolled = mFaceManager.hasEnrolledTemplates(mUserId);
312                 mEnrollButton.setVisible(!hasEnrolled);
313                 mRemoveButton.setVisible(hasEnrolled);
314             }
315         } else if (requestCode == ENROLL_REQUEST) {
316             if (resultCode == RESULT_TIMEOUT) {
317                 setResult(resultCode, data);
318                 finish();
319             }
320         }
321     }
322 
323     @Override
onStop()324     public void onStop() {
325         super.onStop();
326 
327         if (!mEnrollController.isClicked() && !getActivity().isChangingConfigurations()
328                 && !mConfirmingPassword) {
329             // Revoke challenge and finish
330             if (mToken != null) {
331                 mFaceManager.revokeChallenge(mSensorId, mUserId, mChallenge);
332                 mToken = null;
333             }
334             // Let parent "Face & Fingerprint Unlock" can use this error code to close itself.
335             setResult(RESULT_TIMEOUT);
336             finish();
337         }
338     }
339 
340     @Override
getHelpResource()341     public int getHelpResource() {
342         return R.string.help_url_face;
343     }
344 
345     @Override
createPreferenceControllers(Context context)346     protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
347         if (!isFaceHardwareDetected(context)) {
348             return null;
349         }
350         mControllers = buildPreferenceControllers(context);
351         // There's no great way of doing this right now :/
352         for (AbstractPreferenceController controller : mControllers) {
353             if (controller instanceof FaceSettingsAttentionPreferenceController) {
354                 mAttentionController = (FaceSettingsAttentionPreferenceController) controller;
355             } else if (controller instanceof FaceSettingsRemoveButtonPreferenceController) {
356                 mRemoveController = (FaceSettingsRemoveButtonPreferenceController) controller;
357                 mRemoveController.setListener(mRemovalListener);
358                 mRemoveController.setActivity((SettingsActivity) getActivity());
359             } else if (controller instanceof FaceSettingsEnrollButtonPreferenceController) {
360                 mEnrollController = (FaceSettingsEnrollButtonPreferenceController) controller;
361                 mEnrollController.setListener(mEnrollListener);
362             }
363         }
364 
365         return mControllers;
366     }
367 
buildPreferenceControllers(Context context)368     private static List<AbstractPreferenceController> buildPreferenceControllers(Context context) {
369         final List<AbstractPreferenceController> controllers = new ArrayList<>();
370         controllers.add(new FaceSettingsKeyguardPreferenceController(context));
371         controllers.add(new FaceSettingsAppPreferenceController(context));
372         controllers.add(new FaceSettingsAttentionPreferenceController(context));
373         controllers.add(new FaceSettingsRemoveButtonPreferenceController(context));
374         controllers.add(new FaceSettingsConfirmPreferenceController(context));
375         controllers.add(new FaceSettingsEnrollButtonPreferenceController(context));
376         return controllers;
377     }
378 
379     public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
380             new BaseSearchIndexProvider(R.xml.security_settings_face) {
381 
382                 @Override
383                 public List<AbstractPreferenceController> createPreferenceControllers(
384                         Context context) {
385                     if (isFaceHardwareDetected(context)) {
386                         return buildPreferenceControllers(context);
387                     } else {
388                         return null;
389                     }
390                 }
391 
392                 @Override
393                 protected boolean isPageSearchEnabled(Context context) {
394                     if (isFaceHardwareDetected(context)) {
395                         return hasEnrolledBiometrics(context);
396                     }
397 
398                     return false;
399                 }
400 
401                 @Override
402                 public List<String> getNonIndexableKeys(Context context) {
403                     final List<String> keys = super.getNonIndexableKeys(context);
404                     final boolean isFaceHardwareDetected = isFaceHardwareDetected(context);
405                     Log.d(TAG, "Get non indexable keys. isFaceHardwareDetected: "
406                             + isFaceHardwareDetected + ", size:" + keys.size());
407                     if (isFaceHardwareDetected) {
408                         final boolean hasEnrolled = hasEnrolledBiometrics(context);
409                         keys.add(hasEnrolled ? PREF_KEY_ENROLL_FACE_UNLOCK
410                                 : PREF_KEY_DELETE_FACE_DATA);
411                     }
412 
413                     if (!isAttentionSupported(context)) {
414                         keys.add(FaceSettingsAttentionPreferenceController.KEY);
415                     }
416 
417                     return keys;
418                 }
419 
420                 private boolean isAttentionSupported(Context context) {
421                     FaceFeatureProvider featureProvider = FeatureFactory.getFactory(
422                             context).getFaceFeatureProvider();
423                     boolean isAttentionSupported = false;
424                     if (featureProvider != null) {
425                         isAttentionSupported = featureProvider.isAttentionSupported(context);
426                     }
427                     return isAttentionSupported;
428                 }
429 
430                 private boolean hasEnrolledBiometrics(Context context) {
431                     final FaceManager faceManager = Utils.getFaceManagerOrNull(context);
432                     if (faceManager != null) {
433                         return faceManager.hasEnrolledTemplates(UserHandle.myUserId());
434                     }
435                     return false;
436                 }
437             };
438 }
439