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