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