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