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.fingerprint; 18 19 import static android.app.admin.DevicePolicyResources.Strings.Settings.FINGERPRINT_UNLOCK_DISABLED; 20 21 import android.app.admin.DevicePolicyManager; 22 import android.app.settings.SettingsEnums; 23 import android.content.ActivityNotFoundException; 24 import android.content.Intent; 25 import android.hardware.biometrics.BiometricAuthenticator; 26 import android.hardware.fingerprint.FingerprintManager; 27 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; 28 import android.os.Bundle; 29 import android.text.Html; 30 import android.text.method.LinkMovementMethod; 31 import android.util.Log; 32 import android.view.View; 33 import android.widget.ImageView; 34 import android.widget.ScrollView; 35 import android.widget.TextView; 36 37 import androidx.annotation.NonNull; 38 import androidx.annotation.Nullable; 39 import androidx.annotation.StringRes; 40 41 import com.android.internal.annotations.VisibleForTesting; 42 import com.android.settings.R; 43 import com.android.settings.Utils; 44 import com.android.settings.biometrics.BiometricEnrollIntroduction; 45 import com.android.settings.biometrics.BiometricUtils; 46 import com.android.settings.biometrics.GatekeeperPasswordProvider; 47 import com.android.settings.biometrics.MultiBiometricEnrollHelper; 48 import com.android.settings.flags.Flags; 49 import com.android.settings.overlay.FeatureFactory; 50 import com.android.settings.password.ChooseLockSettingsHelper; 51 import com.android.settingslib.HelpUtils; 52 import com.android.settingslib.RestrictedLockUtilsInternal; 53 54 import com.google.android.setupcompat.template.FooterButton; 55 import com.google.android.setupcompat.util.WizardManagerHelper; 56 import com.google.android.setupdesign.span.LinkSpan; 57 import com.google.android.setupdesign.util.DeviceHelper; 58 59 import java.util.List; 60 61 public class FingerprintEnrollIntroduction extends BiometricEnrollIntroduction { 62 private static final String TAG = "FingerprintIntro"; 63 64 @VisibleForTesting 65 private FingerprintManager mFingerprintManager; 66 @Nullable private FooterButton mPrimaryFooterButton; 67 @Nullable private FooterButton mSecondaryFooterButton; 68 69 private DevicePolicyManager mDevicePolicyManager; 70 private boolean mCanAssumeUdfps; 71 @Nullable 72 protected UdfpsEnrollCalibrator mCalibrator; 73 74 @Override onCreate(Bundle savedInstanceState)75 protected void onCreate(Bundle savedInstanceState) { 76 mFingerprintManager = getFingerprintManager(); 77 if (mFingerprintManager == null) { 78 Log.e(TAG, "Null FingerprintManager"); 79 finish(); 80 return; 81 } 82 83 super.onCreate(savedInstanceState); 84 final FingerprintManager fingerprintManager = getSystemService(FingerprintManager.class); 85 final List<FingerprintSensorPropertiesInternal> props = 86 fingerprintManager.getSensorPropertiesInternal(); 87 mCanAssumeUdfps = props != null && props.size() == 1 && props.get(0).isAnyUdfpsType(); 88 89 mDevicePolicyManager = getSystemService(DevicePolicyManager.class); 90 91 if (Flags.udfpsEnrollCalibration()) { 92 mCalibrator = FeatureFactory.getFeatureFactory().getFingerprintFeatureProvider() 93 .getUdfpsEnrollCalibrator(getApplicationContext(), savedInstanceState, null); 94 } 95 96 final ImageView iconFingerprint = findViewById(R.id.icon_fingerprint); 97 final ImageView iconDeviceLocked = findViewById(R.id.icon_device_locked); 98 final ImageView iconTrashCan = findViewById(R.id.icon_trash_can); 99 final ImageView iconInfo = findViewById(R.id.icon_info); 100 final ImageView iconShield = findViewById(R.id.icon_shield); 101 final ImageView iconLink = findViewById(R.id.icon_link); 102 iconFingerprint.getDrawable().setColorFilter(getIconColorFilter()); 103 iconDeviceLocked.getDrawable().setColorFilter(getIconColorFilter()); 104 iconTrashCan.getDrawable().setColorFilter(getIconColorFilter()); 105 iconInfo.getDrawable().setColorFilter(getIconColorFilter()); 106 iconShield.getDrawable().setColorFilter(getIconColorFilter()); 107 iconLink.getDrawable().setColorFilter(getIconColorFilter()); 108 109 final TextView footerMessage2 = findViewById(R.id.footer_message_2); 110 final TextView footerMessage3 = findViewById(R.id.footer_message_3); 111 final TextView footerMessage4 = findViewById(R.id.footer_message_4); 112 final TextView footerMessage5 = findViewById(R.id.footer_message_5); 113 final TextView footerMessage6 = findViewById(R.id.footer_message_6); 114 footerMessage2.setText(getFooterMessage2()); 115 footerMessage3.setText(getFooterMessage3()); 116 footerMessage4.setText(getFooterMessage4()); 117 footerMessage5.setText(getFooterMessage5()); 118 footerMessage6.setText(getFooterMessage6()); 119 120 final TextView footerLink = findViewById(R.id.footer_learn_more); 121 footerLink.setMovementMethod(LinkMovementMethod.getInstance()); 122 footerLink.setText(Html.fromHtml(getString(getFooterLearnMore()), 123 Html.FROM_HTML_MODE_LEGACY)); 124 125 if (mCanAssumeUdfps) { 126 footerMessage6.setVisibility(View.VISIBLE); 127 iconShield.setVisibility(View.VISIBLE); 128 } else { 129 footerMessage6.setVisibility(View.GONE); 130 iconShield.setVisibility(View.GONE); 131 } 132 133 final TextView footerTitle1 = findViewById(R.id.footer_title_1); 134 final TextView footerTitle2 = findViewById(R.id.footer_title_2); 135 footerTitle1.setText(getFooterTitle1()); 136 footerTitle2.setText(getFooterTitle2()); 137 138 final ScrollView scrollView = 139 findViewById(com.google.android.setupdesign.R.id.sud_scroll_view); 140 scrollView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES); 141 142 final Intent intent = getIntent(); 143 if (mFromSettingsSummary 144 && GatekeeperPasswordProvider.containsGatekeeperPasswordHandle(intent)) { 145 overridePendingTransition( 146 com.google.android.setupdesign.R.anim.sud_slide_next_in, 147 com.google.android.setupdesign.R.anim.sud_slide_next_out); 148 getNextButton().setEnabled(false); 149 getChallenge(((sensorId, userId, challenge) -> { 150 if (isFinishing()) { 151 // Do nothing if activity is finishing 152 Log.w(TAG, "activity finished before challenge callback launched."); 153 return; 154 } 155 156 mSensorId = sensorId; 157 mChallenge = challenge; 158 final GatekeeperPasswordProvider provider = getGatekeeperPasswordProvider(); 159 mToken = provider.requestGatekeeperHat(intent, challenge, mUserId); 160 provider.removeGatekeeperPasswordHandle(intent, true); 161 getNextButton().setEnabled(true); 162 })); 163 } 164 } 165 166 @Override onSaveInstanceState(Bundle outState)167 protected void onSaveInstanceState(Bundle outState) { 168 super.onSaveInstanceState(outState); 169 if (Flags.udfpsEnrollCalibration()) { 170 if (mCalibrator != null) { 171 mCalibrator.onSaveInstanceState(outState); 172 } 173 } 174 } 175 176 @Override initViews()177 protected void initViews() { 178 setDescriptionText(getString( 179 isPrivateProfile() 180 ? R.string.private_space_fingerprint_enroll_introduction_message 181 : R.string.security_settings_fingerprint_enroll_introduction_v3_message, 182 DeviceHelper.getDeviceName(this))); 183 184 super.initViews(); 185 } 186 187 @VisibleForTesting 188 @Nullable getFingerprintManager()189 protected FingerprintManager getFingerprintManager() { 190 return Utils.getFingerprintManagerOrNull(this); 191 } 192 193 /** 194 * Returns the intent extra data for setResult(), null means nothing need to been sent back 195 */ 196 @Nullable 197 @Override getSetResultIntentExtra(@ullable Intent activityResultIntent)198 protected Intent getSetResultIntentExtra(@Nullable Intent activityResultIntent) { 199 Intent intent = super.getSetResultIntentExtra(activityResultIntent); 200 if (mFromSettingsSummary && mToken != null && mChallenge != -1L) { 201 if (intent == null) { 202 intent = new Intent(); 203 } 204 intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, mToken); 205 intent.putExtra(EXTRA_KEY_CHALLENGE, mChallenge); 206 } 207 return intent; 208 } 209 210 @Override onCancelButtonClick(View view)211 protected void onCancelButtonClick(View view) { 212 if (!BiometricUtils.tryStartingNextBiometricEnroll( 213 this, ENROLL_NEXT_BIOMETRIC_REQUEST, "cancel")) { 214 super.onCancelButtonClick(view); 215 } 216 } 217 218 @Override onSkipButtonClick(View view)219 protected void onSkipButtonClick(View view) { 220 if (!BiometricUtils.tryStartingNextBiometricEnroll( 221 this, ENROLL_NEXT_BIOMETRIC_REQUEST, "skipped")) { 222 super.onSkipButtonClick(view); 223 } 224 } 225 226 @Override onFinishedEnrolling(@ullable Intent data)227 protected void onFinishedEnrolling(@Nullable Intent data) { 228 if (!BiometricUtils.tryStartingNextBiometricEnroll( 229 this, ENROLL_NEXT_BIOMETRIC_REQUEST, "finished")) { 230 super.onFinishedEnrolling(data); 231 } 232 } 233 234 @StringRes getNegativeButtonTextId()235 int getNegativeButtonTextId() { 236 return R.string.security_settings_fingerprint_enroll_introduction_no_thanks; 237 } 238 239 @StringRes getFooterTitle1()240 protected int getFooterTitle1() { 241 return R.string.security_settings_fingerprint_enroll_introduction_footer_title_1; 242 } 243 244 @StringRes getFooterTitle2()245 protected int getFooterTitle2() { 246 return R.string.security_settings_fingerprint_enroll_introduction_footer_title_2; 247 } 248 249 @StringRes getFooterMessage2()250 protected int getFooterMessage2() { 251 return R.string.security_settings_fingerprint_v2_enroll_introduction_footer_message_2; 252 } 253 254 @StringRes getFooterMessage3()255 protected int getFooterMessage3() { 256 return R.string.security_settings_fingerprint_v2_enroll_introduction_footer_message_3; 257 } 258 259 @StringRes getFooterMessage4()260 protected int getFooterMessage4() { 261 return R.string.security_settings_fingerprint_v2_enroll_introduction_footer_message_4; 262 } 263 264 @StringRes getFooterMessage5()265 protected int getFooterMessage5() { 266 if (isPrivateProfile()) { 267 return R.string.private_space_fingerprint_enroll_introduction_footer_message; 268 } 269 return R.string.security_settings_fingerprint_v2_enroll_introduction_footer_message_5; 270 } 271 272 @StringRes getFooterMessage6()273 protected int getFooterMessage6() { 274 return R.string.security_settings_fingerprint_v2_enroll_introduction_footer_message_6; 275 } 276 277 @StringRes getFooterLearnMore()278 protected int getFooterLearnMore() { 279 return R.string.security_settings_fingerprint_v2_enroll_introduction_message_learn_more; 280 } 281 282 @Override isDisabledByAdmin()283 protected boolean isDisabledByAdmin() { 284 return RestrictedLockUtilsInternal.checkIfKeyguardFeaturesDisabled( 285 this, DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT, mUserId) != null; 286 } 287 288 @Override getLayoutResource()289 protected int getLayoutResource() { 290 return R.layout.fingerprint_enroll_introduction; 291 } 292 293 @Override getHeaderResDisabledByAdmin()294 protected int getHeaderResDisabledByAdmin() { 295 return R.string.security_settings_fingerprint_enroll_introduction_title_unlock_disabled; 296 } 297 298 @Override getHeaderResDefault()299 protected int getHeaderResDefault() { 300 if (isPrivateProfile()) { 301 return R.string.private_space_fingerprint_enroll_introduction_title; 302 } 303 return R.string.security_settings_fingerprint_enroll_introduction_title; 304 } 305 306 @Override getDescriptionDisabledByAdmin()307 protected String getDescriptionDisabledByAdmin() { 308 return mDevicePolicyManager.getResources().getString( 309 FINGERPRINT_UNLOCK_DISABLED, 310 () -> getString(R.string.security_settings_fingerprint_enroll_introduction_message_unlock_disabled)); 311 } 312 313 @Override getCancelButton()314 protected FooterButton getCancelButton() { 315 if (mFooterBarMixin != null) { 316 return mFooterBarMixin.getSecondaryButton(); 317 } 318 return null; 319 } 320 321 @Override getNextButton()322 protected FooterButton getNextButton() { 323 if (mFooterBarMixin != null) { 324 return mFooterBarMixin.getPrimaryButton(); 325 } 326 return null; 327 } 328 329 @Override getErrorTextView()330 protected TextView getErrorTextView() { 331 return findViewById(R.id.error_text); 332 } 333 isFromSetupWizardSuggestAction(@ullable Intent intent)334 private boolean isFromSetupWizardSuggestAction(@Nullable Intent intent) { 335 return intent != null && intent.getBooleanExtra( 336 WizardManagerHelper.EXTRA_IS_SUW_SUGGESTED_ACTION_FLOW, false); 337 } 338 339 @Override checkMaxEnrolled()340 protected int checkMaxEnrolled() { 341 final boolean isSetupWizard = WizardManagerHelper.isAnySetupWizard(getIntent()); 342 final boolean isDeferredSetupWizard = 343 WizardManagerHelper.isDeferredSetupWizard(getIntent()); 344 final boolean isPortalSetupWizard = 345 WizardManagerHelper.isPortalSetupWizard(getIntent()); 346 final boolean isFromSetupWizardSuggestAction = isFromSetupWizardSuggestAction(getIntent()); 347 if (mFingerprintManager != null) { 348 final List<FingerprintSensorPropertiesInternal> props = 349 mFingerprintManager.getSensorPropertiesInternal(); 350 // This will need to be updated for devices with multiple fingerprint sensors 351 if (props == null || props.isEmpty()) { 352 return R.string.fingerprint_intro_error_unknown; 353 } 354 final int max = props.get(0).maxEnrollmentsPerUser; 355 final int numEnrolledFingerprints = 356 mFingerprintManager.getEnrolledFingerprints(mUserId).size(); 357 final int maxFingerprintsEnrollableIfSUW = 358 getApplicationContext() 359 .getResources() 360 .getInteger(R.integer.suw_max_fingerprints_enrollable); 361 if (isSetupWizard && !isDeferredSetupWizard && !isPortalSetupWizard 362 && !isFromSetupWizardSuggestAction) { 363 if (numEnrolledFingerprints >= maxFingerprintsEnrollableIfSUW) { 364 return R.string.fingerprint_intro_error_max; 365 } else { 366 return 0; 367 } 368 } else if (numEnrolledFingerprints >= max) { 369 return R.string.fingerprint_intro_error_max; 370 } else { 371 return 0; 372 } 373 } else { 374 return R.string.fingerprint_intro_error_unknown; 375 } 376 } 377 378 @Override getChallenge(GenerateChallengeCallback callback)379 protected void getChallenge(GenerateChallengeCallback callback) { 380 mFingerprintManager.generateChallenge(mUserId, callback::onChallengeGenerated); 381 } 382 383 @Override getExtraKeyForBiometric()384 protected String getExtraKeyForBiometric() { 385 return ChooseLockSettingsHelper.EXTRA_KEY_FOR_FINGERPRINT; 386 } 387 388 @Override getEnrollingIntent()389 protected Intent getEnrollingIntent() { 390 final Intent intent = new Intent(this, FingerprintEnrollFindSensor.class); 391 BiometricUtils.copyMultiBiometricExtras(getIntent(), intent); 392 if (BiometricUtils.containsGatekeeperPasswordHandle(getIntent())) { 393 intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, 394 BiometricUtils.getGatekeeperPasswordHandle(getIntent())); 395 } 396 if (Flags.udfpsEnrollCalibration()) { 397 if (mCalibrator != null) { 398 intent.putExtras(mCalibrator.getExtrasForNextIntent()); 399 } 400 } 401 intent.putExtra(BiometricUtils.EXTRA_ENROLL_REASON, 402 getIntent().getIntExtra(BiometricUtils.EXTRA_ENROLL_REASON, -1)); 403 return intent; 404 } 405 406 @Override getConfirmLockTitleResId()407 protected int getConfirmLockTitleResId() { 408 return R.string.security_settings_fingerprint_preference_title; 409 } 410 411 @Override getMetricsCategory()412 public int getMetricsCategory() { 413 return SettingsEnums.FINGERPRINT_ENROLL_INTRO; 414 } 415 416 @Override onClick(LinkSpan span)417 public void onClick(LinkSpan span) { 418 if ("url".equals(span.getLink())) { 419 String url = getString(R.string.help_url_fingerprint); 420 Intent intent = HelpUtils.getHelpIntent(this, url, getClass().getName()); 421 if (intent == null) { 422 Log.w(TAG, "Null help intent."); 423 return; 424 } 425 try { 426 // This needs to be startActivityForResult even though we do not care about the 427 // actual result because the help app needs to know about who invoked it. 428 startActivityForResult(intent, LEARN_MORE_REQUEST); 429 } catch (ActivityNotFoundException e) { 430 Log.w(TAG, "Activity was not found for intent, " + e); 431 } 432 } 433 } 434 435 @Override getModality()436 public @BiometricAuthenticator.Modality int getModality() { 437 return BiometricAuthenticator.TYPE_FINGERPRINT; 438 } 439 440 @Override 441 @NonNull getPrimaryFooterButton()442 protected FooterButton getPrimaryFooterButton() { 443 if (mPrimaryFooterButton == null) { 444 mPrimaryFooterButton = new FooterButton.Builder(this) 445 .setText(R.string.security_settings_fingerprint_enroll_introduction_agree) 446 .setListener(this::onNextButtonClick) 447 .setButtonType(FooterButton.ButtonType.OPT_IN) 448 .setTheme(com.google.android.setupdesign.R.style.SudGlifButton_Primary) 449 .build(); 450 } 451 return mPrimaryFooterButton; 452 } 453 454 @Override 455 @NonNull getSecondaryFooterButton()456 protected FooterButton getSecondaryFooterButton() { 457 if (mSecondaryFooterButton == null) { 458 mSecondaryFooterButton = new FooterButton.Builder(this) 459 .setText(getNegativeButtonTextId()) 460 .setListener(this::onSkipButtonClick) 461 .setButtonType(FooterButton.ButtonType.NEXT) 462 .setTheme(com.google.android.setupdesign.R.style.SudGlifButton_Primary) 463 .build(); 464 } 465 return mSecondaryFooterButton; 466 } 467 468 @Override 469 @StringRes getAgreeButtonTextRes()470 protected int getAgreeButtonTextRes() { 471 return R.string.security_settings_fingerprint_enroll_introduction_agree; 472 } 473 474 @Override 475 @StringRes getMoreButtonTextRes()476 protected int getMoreButtonTextRes() { 477 return R.string.security_settings_face_enroll_introduction_more; 478 } 479 480 @NonNull setSkipPendingEnroll(@ullable Intent data)481 protected static Intent setSkipPendingEnroll(@Nullable Intent data) { 482 if (data == null) { 483 data = new Intent(); 484 } 485 data.putExtra(MultiBiometricEnrollHelper.EXTRA_SKIP_PENDING_ENROLL, true); 486 return data; 487 } 488 isPrivateProfile()489 private boolean isPrivateProfile() { 490 return Utils.isPrivateProfile(mUserId, getApplicationContext()); 491 } 492 } 493