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