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