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