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