/* * 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; import static com.android.settings.Utils.SETTINGS_PACKAGE_NAME; import android.annotation.SuppressLint; import android.content.Intent; import android.content.res.ColorStateList; import android.graphics.Color; import android.os.Bundle; import android.os.UserHandle; import android.text.TextUtils; import android.util.Log; import android.view.View; import android.widget.LinearLayout; import android.widget.TextView; import androidx.annotation.ColorInt; import androidx.annotation.Nullable; import com.android.settings.R; import com.android.settings.SetupWizardUtils; import com.android.settings.Utils; import com.android.settings.biometrics.fingerprint.FingerprintEnrollEnrolling; import com.android.settings.core.InstrumentedActivity; import com.android.settings.overlay.FeatureFactory; import com.android.settings.password.ChooseLockSettingsHelper; import com.android.settingslib.activityembedding.ActivityEmbeddingUtils; import com.android.systemui.unfold.compat.ScreenSizeFoldProvider; import com.android.systemui.unfold.updates.FoldProvider; import com.google.android.setupcompat.template.FooterBarMixin; import com.google.android.setupcompat.template.FooterButton; import com.google.android.setupcompat.util.WizardManagerHelper; import com.google.android.setupdesign.GlifLayout; import com.google.android.setupdesign.util.ThemeHelper; /** * Base activity for all biometric enrollment steps. */ public abstract class BiometricEnrollBase extends InstrumentedActivity { private static final String TAG = "BiometricEnrollBase"; public static final String EXTRA_FROM_SETTINGS_SUMMARY = "from_settings_summary"; public static final String EXTRA_KEY_LAUNCHED_CONFIRM = "launched_confirm_lock"; public static final String EXTRA_KEY_REQUIRE_VISION = "accessibility_vision"; public static final String EXTRA_KEY_REQUIRE_DIVERSITY = "accessibility_diversity"; public static final String EXTRA_KEY_SENSOR_ID = "sensor_id"; public static final String EXTRA_KEY_CHALLENGE = "challenge"; public static final String EXTRA_KEY_MODALITY = "sensor_modality"; public static final String EXTRA_KEY_NEXT_LAUNCHED = "next_launched"; public static final String EXTRA_FINISHED_ENROLL_FACE = "finished_enrolling_face"; public static final String EXTRA_FINISHED_ENROLL_FINGERPRINT = "finished_enrolling_fingerprint"; public static final String EXTRA_LAUNCHED_POSTURE_GUIDANCE = "launched_posture_guidance"; /** * Used by the choose fingerprint wizard to indicate the wizard is * finished, and each activity in the wizard should finish. *

* Previously, each activity in the wizard would finish itself after * starting the next activity. However, this leads to broken 'Back' * behavior. So, now an activity does not finish itself until it gets this * result. * * This must be the same as * {@link com.android.settings.password.ChooseLockPattern#RESULT_FINISHED} */ public static final int RESULT_FINISHED = RESULT_FIRST_USER; /** * Used by the enrolling screen during setup wizard to skip over setting up fingerprint, which * will be useful if the user accidentally entered this flow. */ public static final int RESULT_SKIP = RESULT_FIRST_USER + 1; /** * Like {@link #RESULT_FINISHED} except this one indicates enrollment failed because the * device was left idle. This is used to clear the credential token to require the user to * re-enter their pin/pattern/password before continuing. */ public static final int RESULT_TIMEOUT = RESULT_FIRST_USER + 2; /** * Used by consent screens to indicate that consent was granted. Extras, such as * EXTRA_KEY_MODALITY, will be included in the result to provide details about the * consent that was granted. */ public static final int RESULT_CONSENT_GRANTED = RESULT_FIRST_USER + 3; /** * Used by consent screens to indicate that consent was denied. Extras, such as * EXTRA_KEY_MODALITY, will be included in the result to provide details about the * consent that was not granted. */ public static final int RESULT_CONSENT_DENIED = RESULT_FIRST_USER + 4; public static final int CHOOSE_LOCK_GENERIC_REQUEST = 1; public static final int BIOMETRIC_FIND_SENSOR_REQUEST = 2; public static final int LEARN_MORE_REQUEST = 3; public static final int CONFIRM_REQUEST = 4; public static final int ENROLL_REQUEST = 5; /** * Request code when starting another biometric enrollment from within a biometric flow. For * example, when starting fingerprint enroll after face enroll. */ public static final int ENROLL_NEXT_BIOMETRIC_REQUEST = 6; public static final int REQUEST_POSTURE_GUIDANCE = 7; protected boolean mLaunchedConfirmLock; protected boolean mLaunchedPostureGuidance; protected boolean mNextLaunched; protected byte[] mToken; protected int mUserId; protected int mSensorId; @BiometricUtils.DevicePostureInt protected int mDevicePostureState; protected long mChallenge; protected boolean mFromSettingsSummary; protected FooterBarMixin mFooterBarMixin; protected boolean mShouldSetFooterBarBackground = true; @Nullable protected ScreenSizeFoldProvider mScreenSizeFoldProvider; @Nullable protected Intent mPostureGuidanceIntent = null; @Nullable protected FoldProvider.FoldCallback mFoldCallback = null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setTheme(SetupWizardUtils.getTheme(this, getIntent())); ThemeHelper.trySetDynamicColor(this); mChallenge = getIntent().getLongExtra(EXTRA_KEY_CHALLENGE, -1L); mSensorId = getIntent().getIntExtra(EXTRA_KEY_SENSOR_ID, -1); // Don't need to retrieve the HAT if it already exists. In some cases, the extras do not // contain EXTRA_KEY_CHALLENGE_TOKEN but contain EXTRA_KEY_GK_PW, in which case enrollment // classes may request a HAT to be created (as opposed to being passed in) if (mToken == null) { mToken = getIntent().getByteArrayExtra( ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN); } mFromSettingsSummary = getIntent().getBooleanExtra(EXTRA_FROM_SETTINGS_SUMMARY, false); if (savedInstanceState != null) { if (mToken == null) { mLaunchedConfirmLock = savedInstanceState.getBoolean(EXTRA_KEY_LAUNCHED_CONFIRM); mToken = savedInstanceState.getByteArray( ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN); mFromSettingsSummary = savedInstanceState.getBoolean(EXTRA_FROM_SETTINGS_SUMMARY, false); mChallenge = savedInstanceState.getLong(EXTRA_KEY_CHALLENGE); mSensorId = savedInstanceState.getInt(EXTRA_KEY_SENSOR_ID); } mLaunchedPostureGuidance = savedInstanceState.getBoolean( EXTRA_LAUNCHED_POSTURE_GUIDANCE); mNextLaunched = savedInstanceState.getBoolean(EXTRA_KEY_NEXT_LAUNCHED); } mUserId = getIntent().getIntExtra(Intent.EXTRA_USER_ID, UserHandle.myUserId()); mPostureGuidanceIntent = FeatureFactory.getFeatureFactory() .getFaceFeatureProvider().getPostureGuidanceIntent(getApplicationContext()); // Remove the existing split screen dialog. BiometricsSplitScreenDialog dialog = (BiometricsSplitScreenDialog) getSupportFragmentManager() .findFragmentByTag(BiometricsSplitScreenDialog.class.getName()); if (dialog != null) { getSupportFragmentManager().beginTransaction().remove(dialog).commit(); } } @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putBoolean(EXTRA_KEY_LAUNCHED_CONFIRM, mLaunchedConfirmLock); outState.putByteArray(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, mToken); outState.putBoolean(EXTRA_FROM_SETTINGS_SUMMARY, mFromSettingsSummary); outState.putLong(EXTRA_KEY_CHALLENGE, mChallenge); outState.putInt(EXTRA_KEY_SENSOR_ID, mSensorId); outState.putBoolean(EXTRA_LAUNCHED_POSTURE_GUIDANCE, mLaunchedPostureGuidance); outState.putBoolean(EXTRA_KEY_NEXT_LAUNCHED, mNextLaunched); } @Override protected void onPostCreate(@Nullable Bundle savedInstanceState) { super.onPostCreate(savedInstanceState); initViews(); if (mShouldSetFooterBarBackground) { @SuppressLint("VisibleForTests") final LinearLayout buttonContainer = mFooterBarMixin != null ? mFooterBarMixin.getButtonContainer() : null; if (buttonContainer != null) { buttonContainer.setBackgroundColor(getBackgroundColor()); } } } @Override public void onAttachedToWindow() { super.onAttachedToWindow(); getWindow().setStatusBarColor(getBackgroundColor()); } @Override protected void onStop() { super.onStop(); if (mScreenSizeFoldProvider != null && mFoldCallback != null) { mScreenSizeFoldProvider.unregisterCallback(mFoldCallback); } mScreenSizeFoldProvider = null; mFoldCallback = null; if (!isChangingConfigurations() && shouldFinishWhenBackgrounded() && !BiometricUtils.isAnyMultiBiometricFlow(this)) { setResult(RESULT_TIMEOUT); finish(); } } protected boolean launchPostureGuidance() { if (mPostureGuidanceIntent == null || mLaunchedPostureGuidance) { return false; } BiometricUtils.copyMultiBiometricExtras(getIntent(), mPostureGuidanceIntent); startActivityForResult(mPostureGuidanceIntent, REQUEST_POSTURE_GUIDANCE); mLaunchedPostureGuidance = true; overridePendingTransition(0 /* no enter anim */, 0 /* no exit anim */); return mLaunchedPostureGuidance; } protected boolean shouldFinishWhenBackgrounded() { return !WizardManagerHelper.isAnySetupWizard(getIntent()); } protected void initViews() { getWindow().setStatusBarColor(Color.TRANSPARENT); } protected GlifLayout getLayout() { return (GlifLayout) findViewById(R.id.setup_wizard_layout); } protected void setHeaderText(int resId, boolean force) { TextView layoutTitle = getLayout().getHeaderTextView(); CharSequence previousTitle = layoutTitle.getText(); CharSequence title = getText(resId); if (previousTitle != title || force) { if (!TextUtils.isEmpty(previousTitle)) { layoutTitle.setAccessibilityLiveRegion(View.ACCESSIBILITY_LIVE_REGION_POLITE); } getLayout().setHeaderText(title); getLayout().getHeaderTextView().setContentDescription(title); setTitle(title); } } protected void setHeaderText(int resId) { setHeaderText(resId, false /* force */); getLayout().getHeaderTextView().setContentDescription(getText(resId)); } protected void setHeaderText(CharSequence title) { getLayout().setHeaderText(title); getLayout().getHeaderTextView().setContentDescription(title); } protected void setDescriptionText(int resId) { CharSequence previousDescription = getLayout().getDescriptionText(); CharSequence description = getString(resId); // Prevent a11y for re-reading the same string if (!TextUtils.equals(previousDescription, description)) { getLayout().setDescriptionText(resId); } } protected void setDescriptionText(CharSequence descriptionText) { getLayout().setDescriptionText(descriptionText); } protected FooterButton getNextButton() { if (mFooterBarMixin != null) { return mFooterBarMixin.getPrimaryButton(); } return null; } protected void onNextButtonClick(View view) { } protected Intent getFingerprintEnrollingIntent() { Intent intent = new Intent(); intent.setClassName(SETTINGS_PACKAGE_NAME, FingerprintEnrollEnrolling.class.getName()); intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, mToken); intent.putExtra(EXTRA_FROM_SETTINGS_SUMMARY, mFromSettingsSummary); intent.putExtra(EXTRA_KEY_CHALLENGE, mChallenge); intent.putExtra(EXTRA_KEY_SENSOR_ID, mSensorId); BiometricUtils.copyMultiBiometricExtras(getIntent(), intent); if (mUserId != UserHandle.USER_NULL) { intent.putExtra(Intent.EXTRA_USER_ID, mUserId); } return intent; } protected void launchConfirmLock(int titleResId) { Log.d(TAG, "launchConfirmLock"); final ChooseLockSettingsHelper.Builder builder = new ChooseLockSettingsHelper.Builder(this); builder.setRequestCode(CONFIRM_REQUEST) .setTitle(getString(titleResId)) .setRequestGatekeeperPasswordHandle(true) .setForegroundOnly(true) .setReturnCredentials(true); if (mUserId != UserHandle.USER_NULL) { builder.setUserId(mUserId); } final boolean launched = builder.show(); if (!launched) { // This shouldn't happen, as we should only end up at this step if a lock thingy is // already set. finish(); } else { mLaunchedConfirmLock = true; } } @ColorInt public int getBackgroundColor() { final ColorStateList stateList = Utils.getColorAttr(this, android.R.attr.windowBackground); return stateList != null ? stateList.getDefaultColor() : Color.TRANSPARENT; } protected boolean shouldShowSplitScreenDialog() { return isInMultiWindowMode() && !ActivityEmbeddingUtils.isActivityEmbedded(this); } }