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