1 /* 2 * Copyright (C) 2021 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 android.app.admin.DevicePolicyManager; 20 import android.app.settings.SettingsEnums; 21 import android.content.Intent; 22 import android.hardware.SensorPrivacyManager; 23 import android.hardware.biometrics.BiometricAuthenticator; 24 import android.hardware.face.FaceManager; 25 import android.hardware.face.FaceSensorPropertiesInternal; 26 import android.os.Bundle; 27 import android.util.Log; 28 import android.view.View; 29 import android.widget.ImageView; 30 import android.widget.LinearLayout; 31 import android.widget.TextView; 32 33 import androidx.annotation.NonNull; 34 import androidx.annotation.Nullable; 35 import androidx.annotation.StringRes; 36 37 import com.android.settings.R; 38 import com.android.settings.Utils; 39 import com.android.settings.biometrics.BiometricEnrollActivity; 40 import com.android.settings.biometrics.BiometricEnrollIntroduction; 41 import com.android.settings.biometrics.BiometricUtils; 42 import com.android.settings.overlay.FeatureFactory; 43 import com.android.settings.password.ChooseLockSettingsHelper; 44 import com.android.settings.utils.SensorPrivacyManagerHelper; 45 import com.android.settingslib.RestrictedLockUtilsInternal; 46 47 import com.google.android.setupcompat.template.FooterButton; 48 import com.google.android.setupcompat.util.WizardManagerHelper; 49 import com.google.android.setupdesign.span.LinkSpan; 50 51 import java.util.List; 52 53 /** 54 * Provides introductory info about face unlock and prompts the user to agree before starting face 55 * enrollment. 56 */ 57 public class FaceEnrollIntroduction extends BiometricEnrollIntroduction { 58 private static final String TAG = "FaceEnrollIntroduction"; 59 60 private FaceManager mFaceManager; 61 private FaceFeatureProvider mFaceFeatureProvider; 62 @Nullable private FooterButton mPrimaryFooterButton; 63 @Nullable private FooterButton mSecondaryFooterButton; 64 @Nullable private SensorPrivacyManager mSensorPrivacyManager; 65 66 @Override onCancelButtonClick(View view)67 protected void onCancelButtonClick(View view) { 68 if (!BiometricUtils.tryStartingNextBiometricEnroll(this, ENROLL_NEXT_BIOMETRIC_REQUEST, 69 "cancel")) { 70 super.onCancelButtonClick(view); 71 } 72 } 73 74 @Override onSkipButtonClick(View view)75 protected void onSkipButtonClick(View view) { 76 if (!BiometricUtils.tryStartingNextBiometricEnroll(this, ENROLL_NEXT_BIOMETRIC_REQUEST, 77 "skip")) { 78 super.onSkipButtonClick(view); 79 } 80 } 81 82 @Override onEnrollmentSkipped(@ullable Intent data)83 protected void onEnrollmentSkipped(@Nullable Intent data) { 84 if (!BiometricUtils.tryStartingNextBiometricEnroll(this, ENROLL_NEXT_BIOMETRIC_REQUEST, 85 "skipped")) { 86 super.onEnrollmentSkipped(data); 87 } 88 } 89 90 @Override onFinishedEnrolling(@ullable Intent data)91 protected void onFinishedEnrolling(@Nullable Intent data) { 92 if (!BiometricUtils.tryStartingNextBiometricEnroll(this, ENROLL_NEXT_BIOMETRIC_REQUEST, 93 "finished")) { 94 super.onFinishedEnrolling(data); 95 } 96 } 97 98 @Override onCreate(Bundle savedInstanceState)99 protected void onCreate(Bundle savedInstanceState) { 100 super.onCreate(savedInstanceState); 101 102 // Apply extracted theme color to icons. 103 final ImageView iconGlasses = findViewById(R.id.icon_glasses); 104 final ImageView iconLooking = findViewById(R.id.icon_looking); 105 iconGlasses.getBackground().setColorFilter(getIconColorFilter()); 106 iconLooking.getBackground().setColorFilter(getIconColorFilter()); 107 108 // Set text for views with multiple variations. 109 final TextView infoMessageGlasses = findViewById(R.id.info_message_glasses); 110 final TextView infoMessageLooking = findViewById(R.id.info_message_looking); 111 final TextView howMessage = findViewById(R.id.how_message); 112 final TextView inControlTitle = findViewById(R.id.title_in_control); 113 final TextView inControlMessage = findViewById(R.id.message_in_control); 114 infoMessageGlasses.setText(getInfoMessageGlasses()); 115 infoMessageLooking.setText(getInfoMessageLooking()); 116 inControlTitle.setText(getInControlTitle()); 117 howMessage.setText(getHowMessage()); 118 inControlMessage.setText(getInControlMessage()); 119 120 // Set up and show the "less secure" info section if necessary. 121 if (getResources().getBoolean(R.bool.config_face_intro_show_less_secure)) { 122 final LinearLayout infoRowLessSecure = findViewById(R.id.info_row_less_secure); 123 final ImageView iconLessSecure = findViewById(R.id.icon_less_secure); 124 infoRowLessSecure.setVisibility(View.VISIBLE); 125 iconLessSecure.getBackground().setColorFilter(getIconColorFilter()); 126 } 127 128 // Set up and show the "require eyes" info section if necessary. 129 if (getResources().getBoolean(R.bool.config_face_intro_show_require_eyes)) { 130 final LinearLayout infoRowRequireEyes = findViewById(R.id.info_row_require_eyes); 131 final ImageView iconRequireEyes = findViewById(R.id.icon_require_eyes); 132 final TextView infoMessageRequireEyes = findViewById(R.id.info_message_require_eyes); 133 infoRowRequireEyes.setVisibility(View.VISIBLE); 134 iconRequireEyes.getBackground().setColorFilter(getIconColorFilter()); 135 infoMessageRequireEyes.setText(getInfoMessageRequireEyes()); 136 } 137 138 mFaceManager = Utils.getFaceManagerOrNull(this); 139 mFaceFeatureProvider = FeatureFactory.getFactory(getApplicationContext()) 140 .getFaceFeatureProvider(); 141 142 // This path is an entry point for SetNewPasswordController, e.g. 143 // adb shell am start -a android.app.action.SET_NEW_PASSWORD 144 if (mToken == null && BiometricUtils.containsGatekeeperPasswordHandle(getIntent())) { 145 if (generateChallengeOnCreate()) { 146 mFooterBarMixin.getPrimaryButton().setEnabled(false); 147 // We either block on generateChallenge, or need to gray out the "next" button until 148 // the challenge is ready. Let's just do this for now. 149 mFaceManager.generateChallenge(mUserId, (sensorId, userId, challenge) -> { 150 mToken = BiometricUtils.requestGatekeeperHat(this, getIntent(), mUserId, 151 challenge); 152 mSensorId = sensorId; 153 mChallenge = challenge; 154 mFooterBarMixin.getPrimaryButton().setEnabled(true); 155 }); 156 } 157 } 158 159 mSensorPrivacyManager = getApplicationContext() 160 .getSystemService(SensorPrivacyManager.class); 161 final SensorPrivacyManagerHelper helper = SensorPrivacyManagerHelper 162 .getInstance(getApplicationContext()); 163 final boolean cameraPrivacyEnabled = helper 164 .isSensorBlocked(SensorPrivacyManager.Sensors.CAMERA, mUserId); 165 Log.v(TAG, "cameraPrivacyEnabled : " + cameraPrivacyEnabled); 166 } 167 generateChallengeOnCreate()168 protected boolean generateChallengeOnCreate() { 169 return true; 170 } 171 172 @StringRes getInfoMessageGlasses()173 protected int getInfoMessageGlasses() { 174 return R.string.security_settings_face_enroll_introduction_info_glasses; 175 } 176 177 @StringRes getInfoMessageLooking()178 protected int getInfoMessageLooking() { 179 return R.string.security_settings_face_enroll_introduction_info_looking; 180 } 181 182 @StringRes getInfoMessageRequireEyes()183 protected int getInfoMessageRequireEyes() { 184 return R.string.security_settings_face_enroll_introduction_info_gaze; 185 } 186 187 @StringRes getHowMessage()188 protected int getHowMessage() { 189 return R.string.security_settings_face_enroll_introduction_how_message; 190 } 191 192 @StringRes getInControlTitle()193 protected int getInControlTitle() { 194 return R.string.security_settings_face_enroll_introduction_control_title; 195 } 196 197 @StringRes getInControlMessage()198 protected int getInControlMessage() { 199 return R.string.security_settings_face_enroll_introduction_control_message; 200 } 201 202 @Override isDisabledByAdmin()203 protected boolean isDisabledByAdmin() { 204 return RestrictedLockUtilsInternal.checkIfKeyguardFeaturesDisabled( 205 this, DevicePolicyManager.KEYGUARD_DISABLE_FACE, mUserId) != null; 206 } 207 208 @Override getLayoutResource()209 protected int getLayoutResource() { 210 return R.layout.face_enroll_introduction; 211 } 212 213 @Override getHeaderResDisabledByAdmin()214 protected int getHeaderResDisabledByAdmin() { 215 return R.string.security_settings_face_enroll_introduction_title_unlock_disabled; 216 } 217 218 @Override getHeaderResDefault()219 protected int getHeaderResDefault() { 220 return R.string.security_settings_face_enroll_introduction_title; 221 } 222 223 @Override getDescriptionResDisabledByAdmin()224 protected int getDescriptionResDisabledByAdmin() { 225 return R.string.security_settings_face_enroll_introduction_message_unlock_disabled; 226 } 227 228 @Override getCancelButton()229 protected FooterButton getCancelButton() { 230 if (mFooterBarMixin != null) { 231 return mFooterBarMixin.getSecondaryButton(); 232 } 233 return null; 234 } 235 236 @Override getNextButton()237 protected FooterButton getNextButton() { 238 if (mFooterBarMixin != null) { 239 return mFooterBarMixin.getPrimaryButton(); 240 } 241 return null; 242 } 243 244 @Override getErrorTextView()245 protected TextView getErrorTextView() { 246 return findViewById(R.id.error_text); 247 } 248 maxFacesEnrolled()249 private boolean maxFacesEnrolled() { 250 final boolean isSetupWizard = WizardManagerHelper.isAnySetupWizard(getIntent()); 251 if (mFaceManager != null) { 252 final List<FaceSensorPropertiesInternal> props = 253 mFaceManager.getSensorPropertiesInternal(); 254 // This will need to be updated for devices with multiple face sensors. 255 final int max = props.get(0).maxEnrollmentsPerUser; 256 final int numEnrolledFaces = mFaceManager.getEnrolledFaces(mUserId).size(); 257 final int maxFacesEnrollableIfSUW = getApplicationContext().getResources() 258 .getInteger(R.integer.suw_max_faces_enrollable); 259 if (isSetupWizard) { 260 return numEnrolledFaces >= maxFacesEnrollableIfSUW; 261 } else { 262 return numEnrolledFaces >= max; 263 } 264 } else { 265 return false; 266 } 267 } 268 269 //TODO: Refactor this to something that conveys it is used for getting a string ID. 270 @Override checkMaxEnrolled()271 protected int checkMaxEnrolled() { 272 if (mFaceManager != null) { 273 if (maxFacesEnrolled()) { 274 return R.string.face_intro_error_max; 275 } 276 } else { 277 return R.string.face_intro_error_unknown; 278 } 279 return 0; 280 } 281 282 @Override getChallenge(GenerateChallengeCallback callback)283 protected void getChallenge(GenerateChallengeCallback callback) { 284 mFaceManager = Utils.getFaceManagerOrNull(this); 285 if (mFaceManager == null) { 286 callback.onChallengeGenerated(0, 0, 0L); 287 return; 288 } 289 mFaceManager.generateChallenge(mUserId, callback::onChallengeGenerated); 290 } 291 292 @Override getExtraKeyForBiometric()293 protected String getExtraKeyForBiometric() { 294 return ChooseLockSettingsHelper.EXTRA_KEY_FOR_FACE; 295 } 296 297 @Override getEnrollingIntent()298 protected Intent getEnrollingIntent() { 299 Intent intent = new Intent(this, FaceEnrollEducation.class); 300 WizardManagerHelper.copyWizardManagerExtras(getIntent(), intent); 301 return intent; 302 } 303 304 @Override getConfirmLockTitleResId()305 protected int getConfirmLockTitleResId() { 306 return R.string.security_settings_face_preference_title; 307 } 308 309 @Override getMetricsCategory()310 public int getMetricsCategory() { 311 return SettingsEnums.FACE_ENROLL_INTRO; 312 } 313 314 @Override onClick(LinkSpan span)315 public void onClick(LinkSpan span) { 316 // TODO(b/110906762) 317 } 318 319 @Override getModality()320 public @BiometricAuthenticator.Modality int getModality() { 321 return BiometricAuthenticator.TYPE_FACE; 322 } 323 324 @Override onNextButtonClick(View view)325 protected void onNextButtonClick(View view) { 326 final boolean parentelConsentRequired = 327 getIntent() 328 .getBooleanExtra(BiometricEnrollActivity.EXTRA_REQUIRE_PARENTAL_CONSENT, false); 329 final boolean cameraPrivacyEnabled = SensorPrivacyManagerHelper 330 .getInstance(getApplicationContext()) 331 .isSensorBlocked(SensorPrivacyManager.Sensors.CAMERA, mUserId); 332 final boolean isSetupWizard = WizardManagerHelper.isAnySetupWizard(getIntent()); 333 final boolean isSettingUp = isSetupWizard || (parentelConsentRequired 334 && !WizardManagerHelper.isUserSetupComplete(this)); 335 if (cameraPrivacyEnabled && !isSettingUp) { 336 if (mSensorPrivacyManager == null) { 337 mSensorPrivacyManager = getApplicationContext() 338 .getSystemService(SensorPrivacyManager.class); 339 } 340 mSensorPrivacyManager.showSensorUseDialog(SensorPrivacyManager.Sensors.CAMERA); 341 } else { 342 super.onNextButtonClick(view); 343 } 344 } 345 346 @Override 347 @NonNull getPrimaryFooterButton()348 protected FooterButton getPrimaryFooterButton() { 349 if (mPrimaryFooterButton == null) { 350 mPrimaryFooterButton = new FooterButton.Builder(this) 351 .setText(R.string.security_settings_face_enroll_introduction_agree) 352 .setButtonType(FooterButton.ButtonType.OPT_IN) 353 .setListener(this::onNextButtonClick) 354 .setTheme(R.style.SudGlifButton_Primary) 355 .build(); 356 } 357 return mPrimaryFooterButton; 358 } 359 360 @Override 361 @NonNull getSecondaryFooterButton()362 protected FooterButton getSecondaryFooterButton() { 363 if (mSecondaryFooterButton == null) { 364 mSecondaryFooterButton = new FooterButton.Builder(this) 365 .setText(R.string.security_settings_face_enroll_introduction_no_thanks) 366 .setListener(this::onSkipButtonClick) 367 .setButtonType(FooterButton.ButtonType.NEXT) 368 .setTheme(R.style.SudGlifButton_Primary) 369 .build(); 370 } 371 return mSecondaryFooterButton; 372 } 373 374 @Override 375 @StringRes getAgreeButtonTextRes()376 protected int getAgreeButtonTextRes() { 377 return R.string.security_settings_fingerprint_enroll_introduction_agree; 378 } 379 380 @Override 381 @StringRes getMoreButtonTextRes()382 protected int getMoreButtonTextRes() { 383 return R.string.security_settings_face_enroll_introduction_more; 384 } 385 } 386