| /* |
| * Copyright (C) 2018 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License |
| */ |
| |
| package com.android.settings.biometrics.fingerprint; |
| |
| import static android.app.admin.DevicePolicyResources.Strings.Settings.FINGERPRINT_UNLOCK_DISABLED; |
| |
| import android.app.admin.DevicePolicyManager; |
| import android.app.settings.SettingsEnums; |
| import android.content.ActivityNotFoundException; |
| import android.content.Intent; |
| import android.hardware.biometrics.BiometricAuthenticator; |
| import android.hardware.fingerprint.FingerprintManager; |
| import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; |
| import android.os.Bundle; |
| import android.text.Html; |
| import android.text.method.LinkMovementMethod; |
| import android.util.Log; |
| import android.view.View; |
| import android.widget.ImageView; |
| import android.widget.ScrollView; |
| import android.widget.TextView; |
| |
| import androidx.annotation.NonNull; |
| import androidx.annotation.Nullable; |
| import androidx.annotation.StringRes; |
| |
| import com.android.internal.annotations.VisibleForTesting; |
| import com.android.settings.R; |
| import com.android.settings.Utils; |
| import com.android.settings.biometrics.BiometricEnrollIntroduction; |
| import com.android.settings.biometrics.BiometricUtils; |
| import com.android.settings.biometrics.GatekeeperPasswordProvider; |
| import com.android.settings.biometrics.MultiBiometricEnrollHelper; |
| import com.android.settings.flags.Flags; |
| import com.android.settings.overlay.FeatureFactory; |
| import com.android.settings.password.ChooseLockSettingsHelper; |
| import com.android.settingslib.HelpUtils; |
| import com.android.settingslib.RestrictedLockUtilsInternal; |
| |
| import com.google.android.setupcompat.template.FooterButton; |
| import com.google.android.setupcompat.util.WizardManagerHelper; |
| import com.google.android.setupdesign.span.LinkSpan; |
| import com.google.android.setupdesign.util.DeviceHelper; |
| |
| import java.util.List; |
| import java.util.UUID; |
| |
| public class FingerprintEnrollIntroduction extends BiometricEnrollIntroduction { |
| |
| private static final String TAG = "FingerprintIntro"; |
| |
| @VisibleForTesting |
| private FingerprintManager mFingerprintManager; |
| @Nullable private FooterButton mPrimaryFooterButton; |
| @Nullable private FooterButton mSecondaryFooterButton; |
| |
| private DevicePolicyManager mDevicePolicyManager; |
| private boolean mCanAssumeUdfps; |
| @Nullable |
| private UdfpsEnrollCalibrator mCalibrator; |
| |
| @Override |
| protected void onCreate(Bundle savedInstanceState) { |
| mFingerprintManager = getFingerprintManager(); |
| if (mFingerprintManager == null) { |
| Log.e(TAG, "Null FingerprintManager"); |
| finish(); |
| return; |
| } |
| |
| super.onCreate(savedInstanceState); |
| final FingerprintManager fingerprintManager = getSystemService(FingerprintManager.class); |
| final List<FingerprintSensorPropertiesInternal> props = |
| fingerprintManager.getSensorPropertiesInternal(); |
| mCanAssumeUdfps = props != null && props.size() == 1 && props.get(0).isAnyUdfpsType(); |
| |
| mDevicePolicyManager = getSystemService(DevicePolicyManager.class); |
| |
| if (Flags.udfpsEnrollCalibration()) { |
| mCalibrator = FeatureFactory.getFeatureFactory().getFingerprintFeatureProvider() |
| .getUdfpsEnrollCalibrator( |
| (savedInstanceState != null) |
| ? savedInstanceState.getParcelable( |
| KEY_CALIBRATOR_UUID, UUID.class) |
| : null |
| ); |
| } |
| |
| final ImageView iconFingerprint = findViewById(R.id.icon_fingerprint); |
| final ImageView iconDeviceLocked = findViewById(R.id.icon_device_locked); |
| final ImageView iconTrashCan = findViewById(R.id.icon_trash_can); |
| final ImageView iconInfo = findViewById(R.id.icon_info); |
| final ImageView iconShield = findViewById(R.id.icon_shield); |
| final ImageView iconLink = findViewById(R.id.icon_link); |
| iconFingerprint.getDrawable().setColorFilter(getIconColorFilter()); |
| iconDeviceLocked.getDrawable().setColorFilter(getIconColorFilter()); |
| iconTrashCan.getDrawable().setColorFilter(getIconColorFilter()); |
| iconInfo.getDrawable().setColorFilter(getIconColorFilter()); |
| iconShield.getDrawable().setColorFilter(getIconColorFilter()); |
| iconLink.getDrawable().setColorFilter(getIconColorFilter()); |
| |
| final TextView footerMessage2 = findViewById(R.id.footer_message_2); |
| final TextView footerMessage3 = findViewById(R.id.footer_message_3); |
| final TextView footerMessage4 = findViewById(R.id.footer_message_4); |
| final TextView footerMessage5 = findViewById(R.id.footer_message_5); |
| final TextView footerMessage6 = findViewById(R.id.footer_message_6); |
| footerMessage2.setText(getFooterMessage2()); |
| footerMessage3.setText(getFooterMessage3()); |
| footerMessage4.setText(getFooterMessage4()); |
| footerMessage5.setText(getFooterMessage5()); |
| footerMessage6.setText(getFooterMessage6()); |
| |
| final TextView footerLink = findViewById(R.id.footer_learn_more); |
| footerLink.setMovementMethod(LinkMovementMethod.getInstance()); |
| footerLink.setText(Html.fromHtml(getString(getFooterLearnMore()), |
| Html.FROM_HTML_MODE_LEGACY)); |
| |
| if (mCanAssumeUdfps) { |
| footerMessage6.setVisibility(View.VISIBLE); |
| iconShield.setVisibility(View.VISIBLE); |
| } else { |
| footerMessage6.setVisibility(View.GONE); |
| iconShield.setVisibility(View.GONE); |
| } |
| |
| final TextView footerTitle1 = findViewById(R.id.footer_title_1); |
| final TextView footerTitle2 = findViewById(R.id.footer_title_2); |
| footerTitle1.setText(getFooterTitle1()); |
| footerTitle2.setText(getFooterTitle2()); |
| |
| final ScrollView scrollView = |
| findViewById(com.google.android.setupdesign.R.id.sud_scroll_view); |
| scrollView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES); |
| |
| final Intent intent = getIntent(); |
| if (mFromSettingsSummary |
| && GatekeeperPasswordProvider.containsGatekeeperPasswordHandle(intent)) { |
| overridePendingTransition( |
| com.google.android.setupdesign.R.anim.sud_slide_next_in, |
| com.google.android.setupdesign.R.anim.sud_slide_next_out); |
| getNextButton().setEnabled(false); |
| getChallenge(((sensorId, userId, challenge) -> { |
| if (isFinishing()) { |
| // Do nothing if activity is finishing |
| Log.w(TAG, "activity finished before challenge callback launched."); |
| return; |
| } |
| |
| mSensorId = sensorId; |
| mChallenge = challenge; |
| final GatekeeperPasswordProvider provider = getGatekeeperPasswordProvider(); |
| mToken = provider.requestGatekeeperHat(intent, challenge, mUserId); |
| provider.removeGatekeeperPasswordHandle(intent, true); |
| getNextButton().setEnabled(true); |
| })); |
| } |
| } |
| |
| @Override |
| protected void onSaveInstanceState(Bundle outState) { |
| super.onSaveInstanceState(outState); |
| if (Flags.udfpsEnrollCalibration()) { |
| if (mCalibrator != null) { |
| outState.putSerializable(KEY_CALIBRATOR_UUID, mCalibrator.getUuid()); |
| } |
| } |
| } |
| |
| @Override |
| protected void initViews() { |
| setDescriptionText(getString( |
| R.string.security_settings_fingerprint_enroll_introduction_v3_message, |
| DeviceHelper.getDeviceName(this))); |
| |
| super.initViews(); |
| } |
| |
| @VisibleForTesting |
| @Nullable |
| protected FingerprintManager getFingerprintManager() { |
| return Utils.getFingerprintManagerOrNull(this); |
| } |
| |
| /** |
| * Returns the intent extra data for setResult(), null means nothing need to been sent back |
| */ |
| @Nullable |
| @Override |
| protected Intent getSetResultIntentExtra(@Nullable Intent activityResultIntent) { |
| Intent intent = super.getSetResultIntentExtra(activityResultIntent); |
| if (mFromSettingsSummary && mToken != null && mChallenge != -1L) { |
| if (intent == null) { |
| intent = new Intent(); |
| } |
| intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, mToken); |
| intent.putExtra(EXTRA_KEY_CHALLENGE, mChallenge); |
| } |
| return intent; |
| } |
| |
| @Override |
| protected void onCancelButtonClick(View view) { |
| if (!BiometricUtils.tryStartingNextBiometricEnroll( |
| this, ENROLL_NEXT_BIOMETRIC_REQUEST, "cancel")) { |
| super.onCancelButtonClick(view); |
| } |
| } |
| |
| @Override |
| protected void onSkipButtonClick(View view) { |
| if (!BiometricUtils.tryStartingNextBiometricEnroll( |
| this, ENROLL_NEXT_BIOMETRIC_REQUEST, "skipped")) { |
| super.onSkipButtonClick(view); |
| } |
| } |
| |
| @Override |
| protected void onFinishedEnrolling(@Nullable Intent data) { |
| if (!BiometricUtils.tryStartingNextBiometricEnroll( |
| this, ENROLL_NEXT_BIOMETRIC_REQUEST, "finished")) { |
| super.onFinishedEnrolling(data); |
| } |
| } |
| |
| @StringRes |
| int getNegativeButtonTextId() { |
| return R.string.security_settings_fingerprint_enroll_introduction_no_thanks; |
| } |
| |
| @StringRes |
| protected int getFooterTitle1() { |
| return R.string.security_settings_fingerprint_enroll_introduction_footer_title_1; |
| } |
| |
| @StringRes |
| protected int getFooterTitle2() { |
| return R.string.security_settings_fingerprint_enroll_introduction_footer_title_2; |
| } |
| |
| @StringRes |
| protected int getFooterMessage2() { |
| return R.string.security_settings_fingerprint_v2_enroll_introduction_footer_message_2; |
| } |
| |
| @StringRes |
| protected int getFooterMessage3() { |
| return R.string.security_settings_fingerprint_v2_enroll_introduction_footer_message_3; |
| } |
| |
| @StringRes |
| protected int getFooterMessage4() { |
| return R.string.security_settings_fingerprint_v2_enroll_introduction_footer_message_4; |
| } |
| |
| @StringRes |
| protected int getFooterMessage5() { |
| return R.string.security_settings_fingerprint_v2_enroll_introduction_footer_message_5; |
| } |
| |
| @StringRes |
| protected int getFooterMessage6() { |
| return R.string.security_settings_fingerprint_v2_enroll_introduction_footer_message_6; |
| } |
| |
| @StringRes |
| protected int getFooterLearnMore() { |
| return R.string.security_settings_fingerprint_v2_enroll_introduction_message_learn_more; |
| } |
| |
| @Override |
| protected boolean isDisabledByAdmin() { |
| return RestrictedLockUtilsInternal.checkIfKeyguardFeaturesDisabled( |
| this, DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT, mUserId) != null; |
| } |
| |
| @Override |
| protected int getLayoutResource() { |
| return R.layout.fingerprint_enroll_introduction; |
| } |
| |
| @Override |
| protected int getHeaderResDisabledByAdmin() { |
| return R.string.security_settings_fingerprint_enroll_introduction_title_unlock_disabled; |
| } |
| |
| @Override |
| protected int getHeaderResDefault() { |
| return R.string.security_settings_fingerprint_enroll_introduction_title; |
| } |
| |
| @Override |
| protected String getDescriptionDisabledByAdmin() { |
| return mDevicePolicyManager.getResources().getString( |
| FINGERPRINT_UNLOCK_DISABLED, |
| () -> getString(R.string.security_settings_fingerprint_enroll_introduction_message_unlock_disabled)); |
| } |
| |
| @Override |
| protected FooterButton getCancelButton() { |
| if (mFooterBarMixin != null) { |
| return mFooterBarMixin.getSecondaryButton(); |
| } |
| return null; |
| } |
| |
| @Override |
| protected FooterButton getNextButton() { |
| if (mFooterBarMixin != null) { |
| return mFooterBarMixin.getPrimaryButton(); |
| } |
| return null; |
| } |
| |
| @Override |
| protected TextView getErrorTextView() { |
| return findViewById(R.id.error_text); |
| } |
| |
| private boolean isFromSetupWizardSuggestAction(@Nullable Intent intent) { |
| return intent != null && intent.getBooleanExtra( |
| WizardManagerHelper.EXTRA_IS_SUW_SUGGESTED_ACTION_FLOW, false); |
| } |
| |
| @Override |
| protected int checkMaxEnrolled() { |
| final boolean isSetupWizard = WizardManagerHelper.isAnySetupWizard(getIntent()); |
| final boolean isDeferredSetupWizard = |
| WizardManagerHelper.isDeferredSetupWizard(getIntent()); |
| final boolean isPortalSetupWizard = |
| WizardManagerHelper.isPortalSetupWizard(getIntent()); |
| final boolean isFromSetupWizardSuggestAction = isFromSetupWizardSuggestAction(getIntent()); |
| if (mFingerprintManager != null) { |
| final List<FingerprintSensorPropertiesInternal> props = |
| mFingerprintManager.getSensorPropertiesInternal(); |
| // This will need to be updated for devices with multiple fingerprint sensors |
| final int max = props.get(0).maxEnrollmentsPerUser; |
| final int numEnrolledFingerprints = |
| mFingerprintManager.getEnrolledFingerprints(mUserId).size(); |
| final int maxFingerprintsEnrollableIfSUW = |
| getApplicationContext() |
| .getResources() |
| .getInteger(R.integer.suw_max_fingerprints_enrollable); |
| if (isSetupWizard && !isDeferredSetupWizard && !isPortalSetupWizard |
| && !isFromSetupWizardSuggestAction) { |
| if (numEnrolledFingerprints >= maxFingerprintsEnrollableIfSUW) { |
| return R.string.fingerprint_intro_error_max; |
| } else { |
| return 0; |
| } |
| } else if (numEnrolledFingerprints >= max) { |
| return R.string.fingerprint_intro_error_max; |
| } else { |
| return 0; |
| } |
| } else { |
| return R.string.fingerprint_intro_error_unknown; |
| } |
| } |
| |
| @Override |
| protected void getChallenge(GenerateChallengeCallback callback) { |
| mFingerprintManager.generateChallenge(mUserId, callback::onChallengeGenerated); |
| } |
| |
| @Override |
| protected String getExtraKeyForBiometric() { |
| return ChooseLockSettingsHelper.EXTRA_KEY_FOR_FINGERPRINT; |
| } |
| |
| @Override |
| protected Intent getEnrollingIntent() { |
| final Intent intent = new Intent(this, FingerprintEnrollFindSensor.class); |
| BiometricUtils.copyMultiBiometricExtras(getIntent(), intent); |
| if (BiometricUtils.containsGatekeeperPasswordHandle(getIntent())) { |
| intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, |
| BiometricUtils.getGatekeeperPasswordHandle(getIntent())); |
| } |
| if (Flags.udfpsEnrollCalibration()) { |
| if (mCalibrator != null) { |
| intent.putExtra(KEY_CALIBRATOR_UUID, mCalibrator.getUuid()); |
| } |
| } |
| return intent; |
| } |
| |
| @Override |
| protected int getConfirmLockTitleResId() { |
| return R.string.security_settings_fingerprint_preference_title; |
| } |
| |
| @Override |
| public int getMetricsCategory() { |
| return SettingsEnums.FINGERPRINT_ENROLL_INTRO; |
| } |
| |
| @Override |
| public void onClick(LinkSpan span) { |
| if ("url".equals(span.getLink())) { |
| String url = getString(R.string.help_url_fingerprint); |
| Intent intent = HelpUtils.getHelpIntent(this, url, getClass().getName()); |
| if (intent == null) { |
| Log.w(TAG, "Null help intent."); |
| return; |
| } |
| try { |
| // This needs to be startActivityForResult even though we do not care about the |
| // actual result because the help app needs to know about who invoked it. |
| startActivityForResult(intent, LEARN_MORE_REQUEST); |
| } catch (ActivityNotFoundException e) { |
| Log.w(TAG, "Activity was not found for intent, " + e); |
| } |
| } |
| } |
| |
| @Override |
| public @BiometricAuthenticator.Modality int getModality() { |
| return BiometricAuthenticator.TYPE_FINGERPRINT; |
| } |
| |
| @Override |
| @NonNull |
| protected FooterButton getPrimaryFooterButton() { |
| if (mPrimaryFooterButton == null) { |
| mPrimaryFooterButton = new FooterButton.Builder(this) |
| .setText(R.string.security_settings_fingerprint_enroll_introduction_agree) |
| .setListener(this::onNextButtonClick) |
| .setButtonType(FooterButton.ButtonType.OPT_IN) |
| .setTheme(com.google.android.setupdesign.R.style.SudGlifButton_Primary) |
| .build(); |
| } |
| return mPrimaryFooterButton; |
| } |
| |
| @Override |
| @NonNull |
| protected FooterButton getSecondaryFooterButton() { |
| if (mSecondaryFooterButton == null) { |
| mSecondaryFooterButton = new FooterButton.Builder(this) |
| .setText(getNegativeButtonTextId()) |
| .setListener(this::onSkipButtonClick) |
| .setButtonType(FooterButton.ButtonType.NEXT) |
| .setTheme(com.google.android.setupdesign.R.style.SudGlifButton_Primary) |
| .build(); |
| } |
| return mSecondaryFooterButton; |
| } |
| |
| @Override |
| @StringRes |
| protected int getAgreeButtonTextRes() { |
| return R.string.security_settings_fingerprint_enroll_introduction_agree; |
| } |
| |
| @Override |
| @StringRes |
| protected int getMoreButtonTextRes() { |
| return R.string.security_settings_face_enroll_introduction_more; |
| } |
| |
| @NonNull |
| protected static Intent setSkipPendingEnroll(@Nullable Intent data) { |
| if (data == null) { |
| data = new Intent(); |
| } |
| data.putExtra(MultiBiometricEnrollHelper.EXTRA_SKIP_PENDING_ENROLL, true); |
| return data; |
| } |
| } |