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