diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 8cfd9b597a9..927fa394740 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -2800,20 +2800,6 @@ - - - - - - mSensorPropertiesCache; - - public FingerprintRepository(@NonNull FingerprintManager fingerprintManager) { - mFingerprintManager = fingerprintManager; - mFingerprintManager.addAuthenticatorsRegisteredCallback( - new IFingerprintAuthenticatorsRegisteredCallback.Stub() { - @Override - public void onAllAuthenticatorsRegistered( - List sensors) { - mSensorPropertiesCache = sensors; - } - }); - } - - /** - * The first sensor type is UDFPS sensor or not - */ - public boolean canAssumeUdfps() { - FingerprintSensorPropertiesInternal prop = getFirstFingerprintSensorPropertiesInternal(); - return prop != null && prop.isAnyUdfpsType(); - } - - /** - * The first sensor type is Side fps sensor or not - */ - public boolean canAssumeSfps() { - FingerprintSensorPropertiesInternal prop = getFirstFingerprintSensorPropertiesInternal(); - return prop != null && prop.isAnySidefpsType(); - } - - /** - * Get max possible number of fingerprints for a user - */ - public int getMaxFingerprints() { - FingerprintSensorPropertiesInternal prop = getFirstFingerprintSensorPropertiesInternal(); - return prop != null ? prop.maxEnrollmentsPerUser : 0; - } - - /** - * Get number of fingerprints that this user enrolled. - */ - public int getNumOfEnrolledFingerprintsSize(int userId) { - final List list = mFingerprintManager.getEnrolledFingerprints(userId); - return list != null ? list.size() : 0; - } - - /** - * Get maximum possible fingerprints in setup wizard flow - */ - public int getMaxFingerprintsInSuw(@NonNull Resources resources) { - return resources.getInteger(R.integer.suw_max_fingerprints_enrollable); - } - - /** - * Gets the first FingerprintSensorPropertiesInternal from FingerprintManager - */ - @Nullable - public FingerprintSensorPropertiesInternal getFirstFingerprintSensorPropertiesInternal() { - final List props = mSensorPropertiesCache; - if (props == null) { - // Handle this case if it really happens - Log.e(TAG, "Sensor properties cache is null"); - return null; - } - return props.size() > 0 ? props.get(0) : null; - } - - /** - * Call FingerprintManager to generate challenge for first sensor - */ - public void generateChallenge(int userId, - @NonNull FingerprintManager.GenerateChallengeCallback callback) { - mFingerprintManager.generateChallenge(userId, callback); - } - - /** - * Get parental consent required or not during enrollment process - */ - public boolean isParentalConsentRequired(@NonNull Context context) { - return ParentalControlsUtils.parentConsentRequired(context, TYPE_FINGERPRINT) != null; - } - - /** - * Get fingerprint is disable by admin or not - */ - public boolean isDisabledByAdmin(@NonNull Context context, int userId) { - return RestrictedLockUtilsInternal.checkIfKeyguardFeaturesDisabled( - context, DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT, userId) != null; - } - - /** - * Get fingerprint enroll stage threshold - */ - public float getEnrollStageThreshold(int index) { - return mFingerprintManager.getEnrollStageThreshold(index); - } - - /** - * Get fingerprint enroll stage count - */ - public int getEnrollStageCount() { - return mFingerprintManager.getEnrollStageCount(); - } -} diff --git a/src/com/android/settings/biometrics2/factory/BiometricsRepositoryProvider.java b/src/com/android/settings/biometrics2/factory/BiometricsRepositoryProvider.java deleted file mode 100644 index fdc5745e926..00000000000 --- a/src/com/android/settings/biometrics2/factory/BiometricsRepositoryProvider.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (C) 2022 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.biometrics2.factory; - -import android.app.Application; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.android.settings.biometrics2.data.repository.FingerprintRepository; - -/** - * Interface for BiometricsRepositoryProvider - */ -public interface BiometricsRepositoryProvider { - - /** - * Get FingerprintRepository - */ - @Nullable - FingerprintRepository getFingerprintRepository(@NonNull Application application); -} diff --git a/src/com/android/settings/biometrics2/factory/BiometricsRepositoryProviderImpl.java b/src/com/android/settings/biometrics2/factory/BiometricsRepositoryProviderImpl.java deleted file mode 100644 index 22409c849d7..00000000000 --- a/src/com/android/settings/biometrics2/factory/BiometricsRepositoryProviderImpl.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (C) 2022 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.biometrics2.factory; - -import android.app.Application; -import android.hardware.fingerprint.FingerprintManager; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.android.settings.Utils; -import com.android.settings.biometrics2.data.repository.FingerprintRepository; - -/** - * Implementation for BiometricsRepositoryProvider - */ -public class BiometricsRepositoryProviderImpl implements BiometricsRepositoryProvider { - - private static volatile FingerprintRepository sFingerprintRepository; - - /** - * Get FingerprintRepository - */ - @Nullable - @Override - public FingerprintRepository getFingerprintRepository(@NonNull Application application) { - final FingerprintManager fingerprintManager = - Utils.getFingerprintManagerOrNull(application); - if (fingerprintManager == null) { - return null; - } - if (sFingerprintRepository == null) { - synchronized (FingerprintRepository.class) { - if (sFingerprintRepository == null) { - sFingerprintRepository = new FingerprintRepository(fingerprintManager); - } - } - } - return sFingerprintRepository; - } -} diff --git a/src/com/android/settings/biometrics2/factory/BiometricsViewModelFactory.java b/src/com/android/settings/biometrics2/factory/BiometricsViewModelFactory.java deleted file mode 100644 index 516f47170eb..00000000000 --- a/src/com/android/settings/biometrics2/factory/BiometricsViewModelFactory.java +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright (C) 2022 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.biometrics2.factory; - -import android.app.Application; -import android.util.Log; - -import androidx.annotation.NonNull; -import androidx.lifecycle.ViewModel; -import androidx.lifecycle.ViewModelProvider; -import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory; -import androidx.lifecycle.viewmodel.CreationExtras; - -import com.android.internal.widget.LockPatternUtils; -import com.android.settings.biometrics.fingerprint.FingerprintUpdater; -import com.android.settings.biometrics2.data.repository.FingerprintRepository; -import com.android.settings.biometrics2.ui.model.CredentialModel; -import com.android.settings.biometrics2.ui.model.EnrollmentRequest; -import com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel; -import com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel.ChallengeGenerator; -import com.android.settings.biometrics2.ui.viewmodel.DeviceFoldedViewModel; -import com.android.settings.biometrics2.ui.viewmodel.DeviceRotationViewModel; -import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollEnrollingViewModel; -import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollErrorDialogViewModel; -import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollFindSensorViewModel; -import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollFinishViewModel; -import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollIntroViewModel; -import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollProgressViewModel; -import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollmentViewModel; -import com.android.settings.overlay.FeatureFactory; -import com.android.systemui.unfold.compat.ScreenSizeFoldProvider; - -/** - * View model factory for biometric enrollment fragment - */ -public class BiometricsViewModelFactory implements ViewModelProvider.Factory { - - private static final String TAG = "BiometricsViewModelFactory"; - - public static final CreationExtras.Key CHALLENGE_GENERATOR_KEY = - new CreationExtras.Key() {}; - public static final CreationExtras.Key ENROLLMENT_REQUEST_KEY = - new CreationExtras.Key() {}; - public static final CreationExtras.Key CREDENTIAL_MODEL_KEY = - new CreationExtras.Key() {}; - - @NonNull - @Override - @SuppressWarnings("unchecked") - public T create(@NonNull Class modelClass, - @NonNull CreationExtras extras) { - final Application application = extras.get(AndroidViewModelFactory.APPLICATION_KEY); - - if (application == null) { - Log.w(TAG, "create, null application"); - return create(modelClass); - } - final FeatureFactory featureFactory = FeatureFactory.getFeatureFactory(); - final BiometricsRepositoryProvider provider = - featureFactory.getBiometricsRepositoryProvider(); - - if (modelClass.isAssignableFrom(AutoCredentialViewModel.class)) { - final LockPatternUtils lockPatternUtils = - featureFactory.getSecurityFeatureProvider().getLockPatternUtils(application); - final ChallengeGenerator challengeGenerator = extras.get(CHALLENGE_GENERATOR_KEY); - final CredentialModel credentialModel = extras.get(CREDENTIAL_MODEL_KEY); - if (challengeGenerator != null && credentialModel != null) { - return (T) new AutoCredentialViewModel(application, lockPatternUtils, - challengeGenerator, credentialModel); - } - } else if (modelClass.isAssignableFrom(DeviceFoldedViewModel.class)) { - return (T) new DeviceFoldedViewModel(new ScreenSizeFoldProvider(application), - application.getMainExecutor()); - } else if (modelClass.isAssignableFrom(DeviceRotationViewModel.class)) { - return (T) new DeviceRotationViewModel(application); - } else if (modelClass.isAssignableFrom(FingerprintEnrollFindSensorViewModel.class)) { - final EnrollmentRequest request = extras.get(ENROLLMENT_REQUEST_KEY); - if (request != null) { - return (T) new FingerprintEnrollFindSensorViewModel(application, request.isSuw()); - } - } else if (modelClass.isAssignableFrom(FingerprintEnrollIntroViewModel.class)) { - final FingerprintRepository repository = provider.getFingerprintRepository(application); - final EnrollmentRequest request = extras.get(ENROLLMENT_REQUEST_KEY); - final CredentialModel credentialModel = extras.get(CREDENTIAL_MODEL_KEY); - if (repository != null && request != null && credentialModel != null) { - return (T) new FingerprintEnrollIntroViewModel(application, repository, request, - credentialModel.getUserId()); - } - } else if (modelClass.isAssignableFrom(FingerprintEnrollmentViewModel.class)) { - final FingerprintRepository repository = provider.getFingerprintRepository(application); - final EnrollmentRequest request = extras.get(ENROLLMENT_REQUEST_KEY); - if (repository != null && request != null) { - return (T) new FingerprintEnrollmentViewModel(application, repository, request); - } - } else if (modelClass.isAssignableFrom(FingerprintEnrollProgressViewModel.class)) { - final CredentialModel credentialModel = extras.get(CREDENTIAL_MODEL_KEY); - if (credentialModel != null) { - return (T) new FingerprintEnrollProgressViewModel(application, - new FingerprintUpdater(application), credentialModel.getUserId()); - } - } else if (modelClass.isAssignableFrom(FingerprintEnrollEnrollingViewModel.class)) { - final CredentialModel credentialModel = extras.get(CREDENTIAL_MODEL_KEY); - final FingerprintRepository fingerprint = provider.getFingerprintRepository( - application); - if (fingerprint != null && credentialModel != null) { - return (T) new FingerprintEnrollEnrollingViewModel(application, - credentialModel.getUserId(), fingerprint); - } - } else if (modelClass.isAssignableFrom(FingerprintEnrollFinishViewModel.class)) { - final CredentialModel credentialModel = extras.get(CREDENTIAL_MODEL_KEY); - final EnrollmentRequest request = extras.get(ENROLLMENT_REQUEST_KEY); - final FingerprintRepository fingerprint = provider.getFingerprintRepository( - application); - if (fingerprint != null && credentialModel != null && request != null) { - return (T) new FingerprintEnrollFinishViewModel(application, - credentialModel.getUserId(), request, fingerprint); - } - } else if (modelClass.isAssignableFrom(FingerprintEnrollErrorDialogViewModel.class)) { - final EnrollmentRequest request = extras.get(ENROLLMENT_REQUEST_KEY); - if (request != null) { - return (T) new FingerprintEnrollErrorDialogViewModel(application, request.isSuw()); - } - } - return create(modelClass); - } -} diff --git a/src/com/android/settings/biometrics2/ui/model/CredentialModel.kt b/src/com/android/settings/biometrics2/ui/model/CredentialModel.kt deleted file mode 100644 index 53507330b45..00000000000 --- a/src/com/android/settings/biometrics2/ui/model/CredentialModel.kt +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright (C) 2023 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.biometrics2.ui.model - -import android.content.Intent.EXTRA_USER_ID -import android.os.Bundle -import android.os.UserHandle -import androidx.annotation.VisibleForTesting -import com.android.settings.biometrics.BiometricEnrollBase.EXTRA_KEY_CHALLENGE -import com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN -import com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE -import java.time.Clock - -/** - * Secret credential data including - * 1. userId - * 2. challenge - * 3. token - * 4. gkPwHandle - */ -class CredentialModel(bundle: Bundle?, private val clock: Clock) { - - private val mInitMillis = clock.millis() - - /** userId for this credential */ - val userId: Int = (bundle ?: Bundle()).getInt(EXTRA_USER_ID, UserHandle.myUserId()) - - private var clearGkPwHandleMillis: Long? = null - - /** Gatekeeper password handle */ - var gkPwHandle: Long = (bundle ?: Bundle()).getLong(EXTRA_KEY_GK_PW_HANDLE, INVALID_GK_PW_HANDLE) - private set - - val isValidGkPwHandle: Boolean - get() = gkPwHandle != INVALID_GK_PW_HANDLE - - /** Clear gatekeeper password handle data */ - fun clearGkPwHandle() { - clearGkPwHandleMillis = clock.millis() - gkPwHandle = INVALID_GK_PW_HANDLE - } - - /** Check user id is valid or not */ - val isValidUserId: Boolean - get() = userId != UserHandle.USER_NULL - - private var updateChallengeMillis: Long? = null - - var challenge: Long = (bundle ?: Bundle()).getLong(EXTRA_KEY_CHALLENGE, INVALID_CHALLENGE) - set(value) { - updateChallengeMillis = clock.millis() - field = value - } - - val isValidChallenge: Boolean - get() = challenge != INVALID_CHALLENGE - - private var updateTokenMillis: Long? = null - - /** Challenge token */ - var token: ByteArray? = (bundle ?: Bundle()).getByteArray(EXTRA_KEY_CHALLENGE_TOKEN) - set(value) { - updateTokenMillis = clock.millis() - field = value - } - - val isValidToken: Boolean - get() = token != null - - /** Returns a string representation of the object */ - override fun toString(): String { - val gkPwHandleLen = "$gkPwHandle".length - val tokenLen = token?.size ?: 0 - val challengeLen = "$challenge".length - return (javaClass.simpleName + ":{initMillis:$mInitMillis" - + ", userId:$userId" - + ", challenge:{len:$challengeLen" - + ", updateMillis:$updateChallengeMillis}" - + ", token:{len:$tokenLen, isValid:$isValidToken" - + ", updateMillis:$updateTokenMillis}" - + ", gkPwHandle:{len:$gkPwHandleLen, isValid:$isValidGkPwHandle" - + ", clearMillis:$clearGkPwHandleMillis}" - + " }") - } - - companion object { - /** Default value for an invalid challenge */ - @VisibleForTesting - const val INVALID_CHALLENGE = -1L - - /** Default value if GkPwHandle is invalid */ - @VisibleForTesting - const val INVALID_GK_PW_HANDLE = 0L - } -} diff --git a/src/com/android/settings/biometrics2/ui/model/EnrollmentProgress.kt b/src/com/android/settings/biometrics2/ui/model/EnrollmentProgress.kt deleted file mode 100644 index 7b35a680a13..00000000000 --- a/src/com/android/settings/biometrics2/ui/model/EnrollmentProgress.kt +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2023 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.biometrics2.ui.model - -/** Biometric Enrollment progress */ -class EnrollmentProgress(val steps: Int, val remaining: Int) { - - val isInitialStep: Boolean - get() = steps == INITIAL_STEPS - - override fun toString(): String { - return ("${javaClass.simpleName}@${Integer.toHexString(hashCode())}" - + "{steps:$steps, remaining:$remaining}") - } - - companion object { - const val INITIAL_STEPS = -1 - const val INITIAL_REMAINING = 0 - } -} diff --git a/src/com/android/settings/biometrics2/ui/model/EnrollmentRequest.kt b/src/com/android/settings/biometrics2/ui/model/EnrollmentRequest.kt deleted file mode 100644 index 4696c625c50..00000000000 --- a/src/com/android/settings/biometrics2/ui/model/EnrollmentRequest.kt +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright (C) 2023 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.biometrics2.ui.model - -import android.content.Context -import android.content.Intent -import android.os.Bundle -import com.android.settings.SetupWizardUtils -import com.android.settings.biometrics.BiometricEnrollActivity.EXTRA_SKIP_INTRO -import com.google.android.setupcompat.util.WizardManagerHelper -import com.google.android.setupcompat.util.WizardManagerHelper.EXTRA_IS_SUW_SUGGESTED_ACTION_FLOW - -/** - * Biometric enrollment generic intent data, which includes - * 1. isSuw - * 2. isAfterSuwOrSuwSuggestedAction - * 3. theme - * 4. isFromSettingsSummery - * 5. isSkipIntro - * 6. isSkipFindSensor - * 7. a helper method, getSetupWizardExtras - */ -class EnrollmentRequest( - intent: Intent, - context: Context, - isSetupActivity: Boolean -) { - val isSuw: Boolean = isSetupActivity && WizardManagerHelper.isAnySetupWizard(intent) - - val isAfterSuwOrSuwSuggestedAction = (isSetupActivity - && (WizardManagerHelper.isDeferredSetupWizard(intent) - || WizardManagerHelper.isPortalSetupWizard(intent) - || intent.getBooleanExtra(EXTRA_IS_SUW_SUGGESTED_ACTION_FLOW, false))) - - private val _suwExtras = getSuwExtras(isSuw, intent) - - val isSkipIntro = intent.getBooleanExtra(EXTRA_SKIP_INTRO, false) - - val isSkipFindSensor = intent.getBooleanExtra(EXTRA_SKIP_FIND_SENSOR, false) - - val theme = SetupWizardUtils.getTheme(context, intent) - - val suwExtras: Bundle - get() = Bundle(_suwExtras) - - /** - * Returns a string representation of the object - */ - override fun toString(): String { - return (javaClass.simpleName + ":{isSuw:" + isSuw - + ", isAfterSuwOrSuwSuggestedAction:" + isAfterSuwOrSuwSuggestedAction - + "}") - } - - companion object { - const val EXTRA_SKIP_FIND_SENSOR = "skip_find_sensor" - private fun getSuwExtras(isSuw: Boolean, intent: Intent): Bundle { - val toIntent = Intent() - if (isSuw) { - SetupWizardUtils.copySetupExtras(intent, toIntent) - } - return toIntent.extras ?: Bundle() - } - } -} diff --git a/src/com/android/settings/biometrics2/ui/model/EnrollmentStatusMessage.kt b/src/com/android/settings/biometrics2/ui/model/EnrollmentStatusMessage.kt deleted file mode 100644 index 6dd0c5ca375..00000000000 --- a/src/com/android/settings/biometrics2/ui/model/EnrollmentStatusMessage.kt +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (C) 2023 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.biometrics2.ui.model - -/** Enrolling status message (help or error) */ -class EnrollmentStatusMessage(val msgId: Int, string: CharSequence?) { - - /** Status string */ - val str: CharSequence = string ?: "" - - override fun toString(): String { - return "${javaClass.simpleName}@${Integer.toHexString(hashCode())}{id:$msgId, str:$str}" - } -} diff --git a/src/com/android/settings/biometrics2/ui/model/FingerprintEnrollIntroStatus.kt b/src/com/android/settings/biometrics2/ui/model/FingerprintEnrollIntroStatus.kt deleted file mode 100644 index 4cbaffaebe0..00000000000 --- a/src/com/android/settings/biometrics2/ui/model/FingerprintEnrollIntroStatus.kt +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (C) 2023 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.biometrics2.ui.model - - -enum class FingerprintEnrollable { - // Unconfirmed case, this value is invalid, and view shall bypass this value - FINGERPRINT_ENROLLABLE_UNKNOWN, - // User is allowed to enrolled a new fingerprint - FINGERPRINT_ENROLLABLE_OK, - // User is not allowed to enroll because the number has reached maximum - FINGERPRINT_ENROLLABLE_ERROR_REACH_MAX -} - -/** - * Fingerprint onboarding introduction page data, it contains following information which needs - * to be passed from view model to view. - * 1. mEnrollableStatus: User is allowed to enroll a new fingerprint or not. - * 2. mHasScrollToBottom: User has scrolled to the bottom of this page or not. - */ -class FingerprintEnrollIntroStatus( - private val mHasScrollToBottom: Boolean, - /** Enrollable status. It means that user is allowed to enroll a new fingerprint or not. */ - val enrollableStatus: FingerprintEnrollable -) { - /** Get info for this onboarding introduction page has scrolled to bottom or not */ - fun hasScrollToBottom(): Boolean { - return mHasScrollToBottom - } - - override fun toString(): String { - return ("${javaClass.simpleName}@${Integer.toHexString(hashCode())}" - + "{scrollToBottom:$mHasScrollToBottom" - + ", enrollableStatus:$enrollableStatus}") - } -} diff --git a/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollEnrollingIconTouchDialog.kt b/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollEnrollingIconTouchDialog.kt deleted file mode 100644 index 9f99d173fb2..00000000000 --- a/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollEnrollingIconTouchDialog.kt +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2023 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.biometrics2.ui.view - -import android.app.Dialog -import android.content.Context -import android.content.DialogInterface -import android.os.Bundle -import androidx.appcompat.app.AlertDialog -import androidx.fragment.app.DialogFragment -import com.android.settings.R - -/** - * Icon Touch dialog - */ -class FingerprintEnrollEnrollingIconTouchDialog : DialogFragment() { - override fun onCreateDialog(savedInstanceState: Bundle?): Dialog = - requireActivity().bindFingerprintEnrollEnrollingIconTouchDialog() -} - -fun Context.bindFingerprintEnrollEnrollingIconTouchDialog(): AlertDialog = - AlertDialog.Builder(this, R.style.Theme_AlertDialog) - .setTitle(R.string.security_settings_fingerprint_enroll_touch_dialog_title) - .setMessage(R.string.security_settings_fingerprint_enroll_touch_dialog_message) - .setPositiveButton(R.string.security_settings_fingerprint_enroll_dialog_ok) { - dialog: DialogInterface?, _: Int -> dialog?.dismiss() - } - .create() \ No newline at end of file diff --git a/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollEnrollingRfpsFragment.kt b/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollEnrollingRfpsFragment.kt deleted file mode 100644 index 2530628c94d..00000000000 --- a/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollEnrollingRfpsFragment.kt +++ /dev/null @@ -1,513 +0,0 @@ -/* - * Copyright (C) 2023 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.biometrics2.ui.view - -import android.animation.Animator -import android.animation.ObjectAnimator -import android.content.Context -import android.graphics.PorterDuff -import android.graphics.drawable.Animatable2 -import android.graphics.drawable.AnimatedVectorDrawable -import android.graphics.drawable.Drawable -import android.graphics.drawable.LayerDrawable -import android.hardware.fingerprint.FingerprintManager.ENROLL_ENROLL -import android.os.Bundle -import android.text.TextUtils -import android.util.Log -import android.view.LayoutInflater -import android.view.MotionEvent -import android.view.View -import android.view.ViewGroup -import android.view.animation.AnimationUtils.loadInterpolator -import android.view.animation.Interpolator -import android.widget.ProgressBar -import android.widget.TextView -import androidx.activity.OnBackPressedCallback -import androidx.fragment.app.Fragment -import androidx.fragment.app.FragmentActivity -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.Observer -import androidx.lifecycle.ViewModelProvider -import androidx.lifecycle.lifecycleScope -import androidx.lifecycle.repeatOnLifecycle -import com.android.settings.R -import com.android.settings.biometrics2.ui.model.EnrollmentProgress -import com.android.settings.biometrics2.ui.model.EnrollmentStatusMessage -import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollEnrollingViewModel -import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollErrorDialogViewModel -import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollProgressViewModel -import com.google.android.setupcompat.template.FooterBarMixin -import com.google.android.setupcompat.template.FooterButton -import com.google.android.setupdesign.GlifLayout -import kotlinx.coroutines.launch - -/** - * Fragment is used to handle enrolling process for rfps - */ -class FingerprintEnrollEnrollingRfpsFragment : Fragment() { - - private var _enrollingViewModel: FingerprintEnrollEnrollingViewModel? = null - private val enrollingViewModel: FingerprintEnrollEnrollingViewModel - get() = _enrollingViewModel!! - - private var _progressViewModel: FingerprintEnrollProgressViewModel? = null - private val progressViewModel: FingerprintEnrollProgressViewModel - get() = _progressViewModel!! - - private var _errorDialogViewModel: FingerprintEnrollErrorDialogViewModel? = null - private val errorDialogViewModel: FingerprintEnrollErrorDialogViewModel - get() = _errorDialogViewModel!! - - private var fastOutSlowInInterpolator: Interpolator? = null - private var linearOutSlowInInterpolator: Interpolator? = null - private var fastOutLinearInInterpolator: Interpolator? = null - - private var isAnimationCancelled = false - - private var enrollingView: GlifLayout? = null - private val progressBar: ProgressBar - get() = enrollingView!!.findViewById(R.id.fingerprint_progress_bar)!! - - private var progressAnim: ObjectAnimator? = null - - private val errorText: TextView - get() = enrollingView!!.findViewById(R.id.error_text)!! - - private val iconAnimationDrawable: AnimatedVectorDrawable? - get() = (progressBar.background as LayerDrawable) - .findDrawableByLayerId(R.id.fingerprint_animation) as AnimatedVectorDrawable? - - private val iconBackgroundBlinksDrawable: AnimatedVectorDrawable? - get() = (progressBar.background as LayerDrawable) - .findDrawableByLayerId(R.id.fingerprint_background) as AnimatedVectorDrawable? - - private var iconTouchCount = 0 - - private val touchAgainRunnable = Runnable { - showError( - // Use enrollingView to getString to prevent activity is missing during rotation - enrollingView!!.context.getString( - R.string.security_settings_fingerprint_enroll_lift_touch_again - ) - ) - } - - private val onSkipClickListener = View.OnClickListener { _: View? -> - enrollingViewModel.setOnSkipPressed() - cancelEnrollment(true) - } - - private var enrollingCancelSignal: Any? = null - - private val progressObserver = Observer { progress: EnrollmentProgress? -> - if (progress != null && progress.steps >= 0) { - onEnrollmentProgressChange(progress) - } - } - - private val helpMessageObserver = Observer { helpMessage: EnrollmentStatusMessage? -> - helpMessage?.let { onEnrollmentHelp(it) } - } - - private val errorMessageObserver = Observer { errorMessage: EnrollmentStatusMessage? -> - Log.d(TAG, "errorMessageObserver($errorMessage)") - errorMessage?.let { onEnrollmentError(it) } - } - - private val canceledSignalObserver = Observer { canceledSignal: Any? -> - canceledSignal?.let { onEnrollmentCanceled(it) } - } - - private val onBackPressedCallback: OnBackPressedCallback = - object : OnBackPressedCallback(true) { - override fun handleOnBackPressed() { - isEnabled = false - enrollingViewModel.setOnBackPressed() - cancelEnrollment(true) - } - } - - override fun onAttach(context: Context) { - ViewModelProvider(requireActivity()).let { provider -> - _enrollingViewModel = provider[FingerprintEnrollEnrollingViewModel::class.java] - _progressViewModel = provider[FingerprintEnrollProgressViewModel::class.java] - _errorDialogViewModel = provider[FingerprintEnrollErrorDialogViewModel::class.java] - } - super.onAttach(context) - requireActivity().onBackPressedDispatcher.addCallback(onBackPressedCallback) - } - - override fun onDetach() { - onBackPressedCallback.isEnabled = false - super.onDetach() - } - - override fun onCreateView( - inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle? - ): View { - enrollingView = inflater.inflate( - R.layout.fingerprint_enroll_enrolling, container, false - ) as GlifLayout - return enrollingView!! - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - iconAnimationDrawable!!.registerAnimationCallback(iconAnimationCallback) - - progressBar.setOnTouchListener { _: View?, event: MotionEvent -> - if (event.actionMasked == MotionEvent.ACTION_DOWN) { - iconTouchCount++ - if (iconTouchCount == ICON_TOUCH_COUNT_SHOW_UNTIL_DIALOG_SHOWN) { - showIconTouchDialog() - } else { - progressBar.postDelayed( - showDialogRunnable, - ICON_TOUCH_DURATION_UNTIL_DIALOG_SHOWN - ) - } - } else if (event.actionMasked == MotionEvent.ACTION_CANCEL - || event.actionMasked == MotionEvent.ACTION_UP - ) { - progressBar.removeCallbacks(showDialogRunnable) - } - true - } - - requireActivity().bindFingerprintEnrollEnrollingRfpsView( - view = enrollingView!!, - onSkipClickListener = onSkipClickListener - ) - - fastOutSlowInInterpolator = - loadInterpolator(requireContext(), android.R.interpolator.fast_out_slow_in) - linearOutSlowInInterpolator = - loadInterpolator(requireContext(), android.R.interpolator.linear_out_slow_in) - fastOutLinearInInterpolator = - loadInterpolator(requireContext(), android.R.interpolator.fast_out_linear_in) - - lifecycleScope.launch { - repeatOnLifecycle(Lifecycle.State.STARTED) { - errorDialogViewModel.triggerRetryFlow.collect { retryEnrollment() } - } - } - } - - private fun retryEnrollment() { - isAnimationCancelled = false - startIconAnimation() - startEnrollment() - - clearError() - updateProgress(false /* animate */, progressViewModel.progressLiveData.value!!) - updateTitleAndDescription() - } - - override fun onStart() { - super.onStart() - - val isEnrolling = progressViewModel.isEnrolling - val isErrorDialogShown = errorDialogViewModel.isDialogShown - Log.d(TAG, "onStart(), isEnrolling:$isEnrolling, isErrorDialog:$isErrorDialogShown") - if (!isErrorDialogShown) { - isAnimationCancelled = false - startIconAnimation() - startEnrollment() - } - - updateProgress(false /* animate */, progressViewModel.progressLiveData.value!!) - updateTitleAndDescription() - } - - private fun startIconAnimation() { - iconAnimationDrawable?.start() - } - - private fun stopIconAnimation() { - isAnimationCancelled = true - iconAnimationDrawable?.stop() - } - - override fun onStop() { - stopIconAnimation() - removeEnrollmentObservers() - val isEnrolling = progressViewModel.isEnrolling - val isConfigChange = requireActivity().isChangingConfigurations - Log.d(TAG, "onStop(), enrolling:$isEnrolling isConfigChange:$isConfigChange") - if (isEnrolling && !isConfigChange) { - cancelEnrollment(false) - } - super.onStop() - } - - private fun removeEnrollmentObservers() { - progressViewModel.errorMessageLiveData.removeObserver(errorMessageObserver) - progressViewModel.progressLiveData.removeObserver(progressObserver) - progressViewModel.helpMessageLiveData.removeObserver(helpMessageObserver) - } - - private fun cancelEnrollment(waitForLastCancelErrMsg: Boolean) { - if (!progressViewModel.isEnrolling) { - Log.d(TAG, "cancelEnrollment(), failed because isEnrolling is false") - return - } - removeEnrollmentObservers() - if (waitForLastCancelErrMsg) { - progressViewModel.canceledSignalLiveData.observe(this, canceledSignalObserver) - } else { - enrollingCancelSignal = null - } - val cancelResult: Boolean = progressViewModel.cancelEnrollment() - if (!cancelResult) { - Log.e(TAG, "cancelEnrollment(), failed to cancel enrollment") - } - } - - private fun startEnrollment() { - enrollingCancelSignal = progressViewModel.startEnrollment(ENROLL_ENROLL) - if (enrollingCancelSignal == null) { - Log.e(TAG, "startEnrollment(), failed") - } else { - Log.d(TAG, "startEnrollment(), success") - } - progressViewModel.progressLiveData.observe(this, progressObserver) - progressViewModel.helpMessageLiveData.observe(this, helpMessageObserver) - progressViewModel.errorMessageLiveData.observe(this, errorMessageObserver) - } - - private fun onEnrollmentHelp(helpMessage: EnrollmentStatusMessage) { - Log.d(TAG, "onEnrollmentHelp($helpMessage)") - val helpStr: CharSequence = helpMessage.str - if (!TextUtils.isEmpty(helpStr)) { - errorText.removeCallbacks(touchAgainRunnable) - showError(helpStr) - } - } - - private fun onEnrollmentError(errorMessage: EnrollmentStatusMessage) { - stopIconAnimation() - - cancelEnrollment(true) - lifecycleScope.launch { - Log.d(TAG, "newDialog $errorMessage") - errorDialogViewModel.newDialog(errorMessage.msgId) - } - } - - private fun onEnrollmentCanceled(canceledSignal: Any) { - Log.d( - TAG, - "onEnrollmentCanceled enrolling:$enrollingCancelSignal, canceled:$canceledSignal" - ) - if (enrollingCancelSignal === canceledSignal) { - progressViewModel.canceledSignalLiveData.removeObserver(canceledSignalObserver) - progressViewModel.clearProgressLiveData() - if (enrollingViewModel.onBackPressed) { - enrollingViewModel.onCancelledDueToOnBackPressed() - } else if (enrollingViewModel.onSkipPressed) { - enrollingViewModel.onCancelledDueToOnSkipPressed() - } - } - } - - private fun onEnrollmentProgressChange(progress: EnrollmentProgress) { - updateProgress(true /* animate */, progress) - updateTitleAndDescription() - animateFlash() - errorText.removeCallbacks(touchAgainRunnable) - errorText.postDelayed(touchAgainRunnable, HINT_TIMEOUT_DURATION.toLong()) - } - - private fun updateProgress(animate: Boolean, enrollmentProgress: EnrollmentProgress) { - val progress = getProgress(enrollmentProgress) - Log.d(TAG, "updateProgress($animate, $enrollmentProgress), old:${progressBar.progress}" - + ", new:$progress") - - // Only clear the error when progress has been made. - // TODO (b/234772728) Add tests. - if (progressBar.progress < progress) { - clearError() - } - if (animate) { - animateProgress(progress) - } else { - progressBar.progress = progress - if (progress >= PROGRESS_BAR_MAX) { - delayedFinishRunnable.run() - } - } - } - - private fun getProgress(progress: EnrollmentProgress): Int { - if (progress.steps == -1) { - return 0 - } - val displayProgress = 0.coerceAtLeast(progress.steps + 1 - progress.remaining) - return PROGRESS_BAR_MAX * displayProgress / (progress.steps + 1) - } - - private fun showError(error: CharSequence) { - errorText.text = error - if (errorText.visibility == View.INVISIBLE) { - errorText.visibility = View.VISIBLE - errorText.translationY = enrollingView!!.context.resources.getDimensionPixelSize( - R.dimen.fingerprint_error_text_appear_distance - ).toFloat() - errorText.alpha = 0f - errorText.animate() - .alpha(1f) - .translationY(0f) - .setDuration(200) - .setInterpolator(linearOutSlowInInterpolator) - .start() - } else { - errorText.animate().cancel() - errorText.alpha = 1f - errorText.translationY = 0f - } - if (isResumed && enrollingViewModel.isAccessibilityEnabled) { - enrollingViewModel.vibrateError(javaClass.simpleName + "::showError") - } - } - - private fun clearError() { - if (errorText.visibility == View.VISIBLE) { - errorText.animate() - .alpha(0f) - .translationY( - resources.getDimensionPixelSize( - R.dimen.fingerprint_error_text_disappear_distance - ).toFloat() - ) - .setDuration(100) - .setInterpolator(fastOutLinearInInterpolator) - .withEndAction { errorText.visibility = View.INVISIBLE } - .start() - } - } - - private fun animateProgress(progress: Int) { - progressAnim?.cancel() - val anim = ObjectAnimator.ofInt( - progressBar /* target */, - "progress" /* propertyName */, - progressBar.progress /* values[0] */, - progress /* values[1] */ - ) - anim.addListener(progressAnimationListener) - anim.interpolator = fastOutSlowInInterpolator - anim.setDuration(ANIMATION_DURATION) - anim.start() - progressAnim = anim - } - - private fun animateFlash() { - iconBackgroundBlinksDrawable?.start() - } - - private fun updateTitleAndDescription() { - val progressLiveData: EnrollmentProgress = progressViewModel.progressLiveData.value!! - GlifLayoutHelper(activity!!, enrollingView!!).setDescriptionText( - enrollingView!!.context.getString( - if (progressLiveData.steps == -1) - R.string.security_settings_fingerprint_enroll_start_message - else - R.string.security_settings_fingerprint_enroll_repeat_message - ) - ) - } - - private fun showIconTouchDialog() { - iconTouchCount = 0 - enrollingViewModel.showIconTouchDialog() - } - - private val showDialogRunnable = Runnable { showIconTouchDialog() } - - private val progressAnimationListener: Animator.AnimatorListener = - object : Animator.AnimatorListener { - override fun onAnimationStart(animation: Animator) { - startIconAnimation() - } - - override fun onAnimationRepeat(animation: Animator) {} - override fun onAnimationEnd(animation: Animator) { - stopIconAnimation() - if (progressBar.progress >= PROGRESS_BAR_MAX) { - progressBar.postDelayed(delayedFinishRunnable, ANIMATION_DURATION) - } - } - - override fun onAnimationCancel(animation: Animator) {} - } - - // Give the user a chance to see progress completed before jumping to the next stage. - private val delayedFinishRunnable = Runnable { enrollingViewModel.onEnrollingDone() } - - private val iconAnimationCallback: Animatable2.AnimationCallback = - object : Animatable2.AnimationCallback() { - override fun onAnimationEnd(d: Drawable) { - if (isAnimationCancelled) { - return - } - - // Start animation after it has ended. - progressBar.post { startIconAnimation() } - } - } - - companion object { - private const val DEBUG = false - private const val TAG = "FingerprintEnrollEnrollingRfpsFragment" - private const val PROGRESS_BAR_MAX = 10000 - private const val ANIMATION_DURATION = 250L - private const val ICON_TOUCH_DURATION_UNTIL_DIALOG_SHOWN: Long = 500 - private const val ICON_TOUCH_COUNT_SHOW_UNTIL_DIALOG_SHOWN = 3 - - /** - * If we don't see progress during this time, we show an error message to remind the users that - * they need to lift the finger and touch again. - */ - private const val HINT_TIMEOUT_DURATION = 2500 - } -} - -fun FragmentActivity.bindFingerprintEnrollEnrollingRfpsView( - view: GlifLayout, - onSkipClickListener: View.OnClickListener -) { - GlifLayoutHelper(this, view).let { - it.setDescriptionText( - getString( - R.string.security_settings_fingerprint_enroll_start_message - ) - ) - it.setHeaderText(R.string.security_settings_fingerprint_enroll_repeat_title) - } - - view.findViewById(R.id.fingerprint_progress_bar)!! - .progressBackgroundTintMode = PorterDuff.Mode.SRC - - view.getMixin(FooterBarMixin::class.java).secondaryButton = - FooterButton.Builder(this) - .setText(R.string.security_settings_fingerprint_enroll_enrolling_skip) - .setListener(onSkipClickListener) - .setButtonType(FooterButton.ButtonType.SKIP) - .setTheme(com.google.android.setupdesign.R.style.SudGlifButton_Secondary) - .build() -} diff --git a/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollEnrollingSfpsFragment.kt b/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollEnrollingSfpsFragment.kt deleted file mode 100644 index 7faeeac986b..00000000000 --- a/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollEnrollingSfpsFragment.kt +++ /dev/null @@ -1,669 +0,0 @@ -/* - * Copyright (C) 2023 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.biometrics2.ui.view - -import android.animation.Animator -import android.animation.ObjectAnimator -import android.annotation.RawRes -import android.content.Context -import android.content.res.ColorStateList -import android.content.res.Configuration -import android.graphics.PorterDuff -import android.graphics.PorterDuffColorFilter -import android.hardware.fingerprint.FingerprintManager.ENROLL_ENROLL -import android.os.Bundle -import android.util.Log -import android.view.LayoutInflater -import android.view.MotionEvent -import android.view.View -import android.view.ViewGroup -import android.view.animation.AccelerateDecelerateInterpolator -import android.view.animation.AnimationUtils -import android.view.animation.Interpolator -import android.widget.ProgressBar -import android.widget.RelativeLayout -import androidx.activity.OnBackPressedCallback -import androidx.fragment.app.Fragment -import androidx.fragment.app.FragmentActivity -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.Observer -import androidx.lifecycle.ViewModelProvider -import androidx.lifecycle.lifecycleScope -import androidx.lifecycle.repeatOnLifecycle -import com.airbnb.lottie.LottieAnimationView -import com.airbnb.lottie.LottieComposition -import com.airbnb.lottie.LottieCompositionFactory -import com.airbnb.lottie.LottieProperty -import com.airbnb.lottie.model.KeyPath -import com.android.settings.R -import com.android.settings.biometrics2.ui.model.EnrollmentProgress -import com.android.settings.biometrics2.ui.model.EnrollmentStatusMessage -import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollEnrollingViewModel -import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollErrorDialogViewModel -import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollProgressViewModel -import com.google.android.setupcompat.template.FooterBarMixin -import com.google.android.setupcompat.template.FooterButton -import com.google.android.setupdesign.GlifLayout -import com.google.android.setupdesign.template.DescriptionMixin -import com.google.android.setupdesign.template.HeaderMixin -import kotlin.math.roundToInt -import kotlinx.coroutines.launch - -/** - * Fragment is used to handle enrolling process for sfps - */ -class FingerprintEnrollEnrollingSfpsFragment : Fragment() { - - private var _enrollingViewModel: FingerprintEnrollEnrollingViewModel? = null - private val enrollingViewModel: FingerprintEnrollEnrollingViewModel - get() = _enrollingViewModel!! - - private var _progressViewModel: FingerprintEnrollProgressViewModel? = null - private val progressViewModel: FingerprintEnrollProgressViewModel - get() = _progressViewModel!! - - private var _errorDialogViewModel: FingerprintEnrollErrorDialogViewModel? = null - private val errorDialogViewModel: FingerprintEnrollErrorDialogViewModel - get() = _errorDialogViewModel!! - - private val fastOutSlowInInterpolator: Interpolator - get() = AnimationUtils.loadInterpolator( - activity, - androidx.appcompat.R.interpolator.fast_out_slow_in, - ) - - private var enrollingView: GlifLayout? = null - - private val progressBar: ProgressBar - get() = enrollingView!!.findViewById(R.id.fingerprint_progress_bar)!! - - private var progressAnim: ObjectAnimator? = null - - private val progressAnimationListener: Animator.AnimatorListener = - object : Animator.AnimatorListener { - override fun onAnimationStart(animation: Animator) {} - override fun onAnimationRepeat(animation: Animator) {} - override fun onAnimationEnd(animation: Animator) { - if (progressBar.progress >= PROGRESS_BAR_MAX) { - progressBar.postDelayed(delayedFinishRunnable, PROGRESS_ANIMATION_DURATION) - } - } - - override fun onAnimationCancel(animation: Animator) {} - } - - private val illustrationLottie: LottieAnimationView - get() = enrollingView!!.findViewById(R.id.illustration_lottie)!! - - private var haveShownSfpsNoAnimationLottie = false - private var haveShownSfpsCenterLottie = false - private var haveShownSfpsTipLottie = false - private var haveShownSfpsLeftEdgeLottie = false - private var haveShownSfpsRightEdgeLottie = false - - private var helpAnimation: ObjectAnimator? = null - - private var iconTouchCount = 0 - - private val showIconTouchDialogRunnable = Runnable { showIconTouchDialog() } - - private var enrollingCancelSignal: Any? = null - - // Give the user a chance to see progress completed before jumping to the next stage. - private val delayedFinishRunnable = Runnable { enrollingViewModel.onEnrollingDone() } - - private val onSkipClickListener = View.OnClickListener { _: View? -> - enrollingViewModel.setOnSkipPressed() - cancelEnrollment(true) - } - - private val progressObserver = Observer { progress: EnrollmentProgress? -> - if (progress != null && progress.steps >= 0) { - onEnrollmentProgressChange(progress) - } - } - - private val helpMessageObserver = Observer { helpMessage: EnrollmentStatusMessage? -> - helpMessage?.let { onEnrollmentHelp(it) } - } - - private val errorMessageObserver = Observer { errorMessage: EnrollmentStatusMessage? -> - Log.d(TAG, "errorMessageObserver($errorMessage)") - errorMessage?.let { onEnrollmentError(it) } - } - - private val canceledSignalObserver = Observer { canceledSignal: Any? -> - Log.d(TAG, "canceledSignalObserver($canceledSignal)") - canceledSignal?.let { onEnrollmentCanceled(it) } - } - - private val onBackPressedCallback: OnBackPressedCallback = - object : OnBackPressedCallback(true) { - override fun handleOnBackPressed() { - isEnabled = false - enrollingViewModel.setOnBackPressed() - cancelEnrollment(true) - } - } - - override fun onAttach(context: Context) { - ViewModelProvider(requireActivity()).let { provider -> - _enrollingViewModel = provider[FingerprintEnrollEnrollingViewModel::class.java] - _progressViewModel = provider[FingerprintEnrollProgressViewModel::class.java] - _errorDialogViewModel = provider[FingerprintEnrollErrorDialogViewModel::class.java] - } - super.onAttach(context) - requireActivity().onBackPressedDispatcher.addCallback(onBackPressedCallback) - } - - override fun onDetach() { - onBackPressedCallback.isEnabled = false - super.onDetach() - } - - override fun onCreateView( - inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle? - ): View? { - enrollingView = inflater.inflate( - R.layout.sfps_enroll_enrolling, - container, false - ) as GlifLayout - return enrollingView - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - requireActivity().bindFingerprintEnrollEnrollingSfpsView( - view = enrollingView!!, - onSkipClickListener = onSkipClickListener - ) - - // setHelpAnimation() - helpAnimation = ObjectAnimator.ofFloat( - enrollingView!!.findViewById(R.id.progress_lottie)!!, - "translationX" /* propertyName */, - 0f, - HELP_ANIMATION_TRANSLATION_X, - -1 * HELP_ANIMATION_TRANSLATION_X, - HELP_ANIMATION_TRANSLATION_X, - 0f - ).also { - it.interpolator = AccelerateDecelerateInterpolator() - it.setDuration(HELP_ANIMATION_DURATION) - it.setAutoCancel(false) - } - - progressBar.setOnTouchListener { _: View?, event: MotionEvent -> - if (event.actionMasked == MotionEvent.ACTION_DOWN) { - iconTouchCount++ - if (iconTouchCount == ICON_TOUCH_COUNT_SHOW_UNTIL_DIALOG_SHOWN) { - showIconTouchDialog() - } else { - progressBar.postDelayed( - showIconTouchDialogRunnable, - ICON_TOUCH_DURATION_UNTIL_DIALOG_SHOWN - ) - } - } else if (event.actionMasked == MotionEvent.ACTION_CANCEL - || event.actionMasked == MotionEvent.ACTION_UP - ) { - progressBar.removeCallbacks(showIconTouchDialogRunnable) - } - true - } - - lifecycleScope.launch { - repeatOnLifecycle(Lifecycle.State.STARTED) { - errorDialogViewModel.triggerRetryFlow.collect { retryEnrollment() } - } - } - } - - private fun retryEnrollment() { - startEnrollment() - updateProgress(false /* animate */, progressViewModel.progressLiveData.value!!) - } - - override fun onStart() { - super.onStart() - val isEnrolling = progressViewModel.isEnrolling - val isErrorDialogShown = errorDialogViewModel.isDialogShown - Log.d(TAG, "onStart(), isEnrolling:$isEnrolling, isErrorDialog:$isErrorDialogShown") - if (!isErrorDialogShown) { - startEnrollment() - } - - updateProgress(false /* animate */, progressViewModel.progressLiveData.value!!) - progressViewModel.helpMessageLiveData.value.let { - if (it != null) { - onEnrollmentHelp(it) - } else { - clearError() - updateTitleAndDescription() - } - } - } - - override fun onStop() { - removeEnrollmentObservers() - val isEnrolling = progressViewModel.isEnrolling - val isConfigChange = requireActivity().isChangingConfigurations - Log.d(TAG, "onStop(), enrolling:$isEnrolling isConfigChange:$isConfigChange") - if (isEnrolling && !isConfigChange) { - cancelEnrollment(false) - } - super.onStop() - } - - private fun removeEnrollmentObservers() { - progressViewModel.errorMessageLiveData.removeObserver(errorMessageObserver) - progressViewModel.progressLiveData.removeObserver(progressObserver) - progressViewModel.helpMessageLiveData.removeObserver(helpMessageObserver) - } - - private fun cancelEnrollment(waitForLastCancelErrMsg: Boolean) { - if (!progressViewModel.isEnrolling) { - Log.d(TAG, "cancelEnrollment(), failed because isEnrolling is false") - return - } - removeEnrollmentObservers() - if (waitForLastCancelErrMsg) { - progressViewModel.canceledSignalLiveData.observe(this, canceledSignalObserver) - } else { - enrollingCancelSignal = null - } - val cancelResult: Boolean = progressViewModel.cancelEnrollment() - if (!cancelResult) { - Log.e(TAG, "cancelEnrollment(), failed to cancel enrollment") - } - } - - private fun startEnrollment() { - enrollingCancelSignal = progressViewModel.startEnrollment(ENROLL_ENROLL) - if (enrollingCancelSignal == null) { - Log.e(TAG, "startEnrollment(), failed") - } else { - Log.d(TAG, "startEnrollment(), success") - } - progressViewModel.progressLiveData.observe(this, progressObserver) - progressViewModel.helpMessageLiveData.observe(this, helpMessageObserver) - progressViewModel.errorMessageLiveData.observe(this, errorMessageObserver) - } - - private fun configureEnrollmentStage(description: CharSequence, @RawRes lottie: Int) { - GlifLayoutHelper(requireActivity(), enrollingView!!).setDescriptionText(description) - LottieCompositionFactory.fromRawRes(activity, lottie) - .addListener { c: LottieComposition -> - illustrationLottie.setComposition(c) - illustrationLottie.visibility = View.VISIBLE - illustrationLottie.playAnimation() - } - } - - private val currentSfpsStage: Int - get() { - val progressLiveData: EnrollmentProgress = - progressViewModel.progressLiveData.value - ?: return STAGE_UNKNOWN - val progressSteps: Int = progressLiveData.steps - progressLiveData.remaining - return if (progressSteps < getStageThresholdSteps(0)) { - SFPS_STAGE_NO_ANIMATION - } else if (progressSteps < getStageThresholdSteps(1)) { - SFPS_STAGE_CENTER - } else if (progressSteps < getStageThresholdSteps(2)) { - SFPS_STAGE_FINGERTIP - } else if (progressSteps < getStageThresholdSteps(3)) { - SFPS_STAGE_LEFT_EDGE - } else { - SFPS_STAGE_RIGHT_EDGE - } - } - - private fun onEnrollmentHelp(helpMessage: EnrollmentStatusMessage) { - Log.d(TAG, "onEnrollmentHelp($helpMessage)") - val helpStr: CharSequence = helpMessage.str - if (helpStr.isNotEmpty()) { - showError(helpStr) - } - } - - private fun onEnrollmentError(errorMessage: EnrollmentStatusMessage) { - cancelEnrollment(true) - lifecycleScope.launch { - Log.d(TAG, "newDialog $errorMessage") - errorDialogViewModel.newDialog(errorMessage.msgId) - } - } - - private fun onEnrollmentCanceled(canceledSignal: Any) { - Log.d( - TAG, - "onEnrollmentCanceled enrolling:$enrollingCancelSignal, canceled:$canceledSignal" - ) - if (enrollingCancelSignal === canceledSignal) { - progressViewModel.canceledSignalLiveData.removeObserver(canceledSignalObserver) - progressViewModel.clearProgressLiveData() - if (enrollingViewModel.onBackPressed) { - enrollingViewModel.onCancelledDueToOnBackPressed() - } else if (enrollingViewModel.onSkipPressed) { - enrollingViewModel.onCancelledDueToOnSkipPressed() - } - } - } - - private fun announceEnrollmentProgress(announcement: CharSequence) { - enrollingViewModel.sendAccessibilityEvent(announcement) - } - - private fun onEnrollmentProgressChange(progress: EnrollmentProgress) { - updateProgress(true /* animate */, progress) - if (enrollingViewModel.isAccessibilityEnabled) { - val percent: Int = - ((progress.steps - progress.remaining).toFloat() / progress.steps.toFloat() * 100).toInt() - val announcement: CharSequence = getString( - R.string.security_settings_sfps_enroll_progress_a11y_message, percent - ) - announceEnrollmentProgress(announcement) - illustrationLottie.contentDescription = - getString(R.string.security_settings_sfps_animation_a11y_label, percent) - } - updateTitleAndDescription() - } - - private fun updateProgress(animate: Boolean, enrollmentProgress: EnrollmentProgress) { - if (!progressViewModel.isEnrolling) { - Log.d(TAG, "Enrollment not started yet") - return - } - - val progress = getProgress(enrollmentProgress) - Log.d(TAG, "updateProgress($animate, $enrollmentProgress), old:${progressBar.progress}" - + ", new:$progress") - - // Only clear the error when progress has been made. - // TODO (b/234772728) Add tests. - if (progressBar.progress < progress) { - clearError() - } - if (animate) { - animateProgress(progress) - } else { - progressBar.progress = progress - if (progress >= PROGRESS_BAR_MAX) { - delayedFinishRunnable.run() - } - } - } - - private fun getProgress(progress: EnrollmentProgress): Int { - if (progress.steps == -1) { - return 0 - } - val displayProgress = 0.coerceAtLeast(progress.steps + 1 - progress.remaining) - return PROGRESS_BAR_MAX * displayProgress / (progress.steps + 1) - } - - private fun showError(error: CharSequence) { - enrollingView!!.let { - it.headerText = error - it.headerTextView.contentDescription = error - GlifLayoutHelper(requireActivity(), it).setDescriptionText("") - } - - if (isResumed && !helpAnimation!!.isRunning) { - helpAnimation!!.start() - } - applySfpsErrorDynamicColors(true) - if (isResumed && enrollingViewModel.isAccessibilityEnabled) { - enrollingViewModel.vibrateError(javaClass.simpleName + "::showError") - } - } - - private fun clearError() { - applySfpsErrorDynamicColors(false) - } - - private fun animateProgress(progress: Int) { - progressAnim?.cancel() - progressAnim = ObjectAnimator.ofInt( - progressBar, - "progress", - progressBar.progress, - progress - ).also { - it.addListener(progressAnimationListener) - it.interpolator = fastOutSlowInInterpolator - it.setDuration(PROGRESS_ANIMATION_DURATION) - it.start() - } - } - - /** - * Applies dynamic colors corresponding to showing or clearing errors on the progress bar - * and finger lottie for SFPS - */ - private fun applySfpsErrorDynamicColors(isError: Boolean) { - progressBar.applyProgressBarDynamicColor(requireContext(), isError) - illustrationLottie.applyLottieDynamicColor(requireContext(), isError) - } - - private fun getStageThresholdSteps(index: Int): Int { - val progressLiveData: EnrollmentProgress? = - progressViewModel.progressLiveData.value - if (progressLiveData == null || progressLiveData.steps == -1) { - Log.w(TAG, "getStageThresholdSteps: Enrollment not started yet") - return 1 - } - return (progressLiveData.steps - * enrollingViewModel.getEnrollStageThreshold(index)).roundToInt() - } - - private fun updateTitleAndDescription() { - val helper = GlifLayoutHelper(requireActivity(), enrollingView!!) - if (enrollingViewModel.isAccessibilityEnabled) { - enrollingViewModel.clearTalkback() - helper.glifLayout.descriptionTextView.accessibilityLiveRegion = - View.ACCESSIBILITY_LIVE_REGION_POLITE - } - val stage = currentSfpsStage - if (DEBUG) { - Log.d( - TAG, "updateTitleAndDescription, stage:" + stage - + ", noAnimation:" + haveShownSfpsNoAnimationLottie - + ", center:" + haveShownSfpsCenterLottie - + ", tip:" + haveShownSfpsTipLottie - + ", leftEdge:" + haveShownSfpsLeftEdgeLottie - + ", rightEdge:" + haveShownSfpsRightEdgeLottie - ) - } - when (stage) { - SFPS_STAGE_NO_ANIMATION -> { - helper.setHeaderText(R.string.security_settings_fingerprint_enroll_repeat_title) - if (!haveShownSfpsNoAnimationLottie) { - haveShownSfpsNoAnimationLottie = true - illustrationLottie.contentDescription = - getString(R.string.security_settings_sfps_animation_a11y_label, 0) - configureEnrollmentStage( - getString(R.string.security_settings_sfps_enroll_start_message), - R.raw.sfps_lottie_no_animation - ) - } - } - - SFPS_STAGE_CENTER -> { - helper.setHeaderText(R.string.security_settings_sfps_enroll_finger_center_title) - if (!haveShownSfpsCenterLottie) { - haveShownSfpsCenterLottie = true - configureEnrollmentStage( - getString(R.string.security_settings_sfps_enroll_start_message), - R.raw.sfps_lottie_pad_center - ) - } - } - - SFPS_STAGE_FINGERTIP -> { - helper.setHeaderText(R.string.security_settings_sfps_enroll_fingertip_title) - if (!haveShownSfpsTipLottie) { - haveShownSfpsTipLottie = true - configureEnrollmentStage("", R.raw.sfps_lottie_tip) - } - } - - SFPS_STAGE_LEFT_EDGE -> { - helper.setHeaderText(R.string.security_settings_sfps_enroll_left_edge_title) - if (!haveShownSfpsLeftEdgeLottie) { - haveShownSfpsLeftEdgeLottie = true - configureEnrollmentStage("", R.raw.sfps_lottie_left_edge) - } - } - - SFPS_STAGE_RIGHT_EDGE -> { - helper.setHeaderText(R.string.security_settings_sfps_enroll_right_edge_title) - if (!haveShownSfpsRightEdgeLottie) { - haveShownSfpsRightEdgeLottie = true - configureEnrollmentStage("", R.raw.sfps_lottie_right_edge) - } - } - - STAGE_UNKNOWN -> { - // Don't use BiometricEnrollBase#setHeaderText, since that invokes setTitle, - // which gets announced for a11y upon entering the page. For SFPS, we want to - // announce a different string for a11y upon entering the page. - helper.setHeaderText(R.string.security_settings_sfps_enroll_find_sensor_title) - helper.setDescriptionText( - getString(R.string.security_settings_sfps_enroll_start_message) - ) - val description: CharSequence = getString( - R.string.security_settings_sfps_enroll_find_sensor_message - ) - helper.glifLayout.headerTextView.contentDescription = description - helper.activity.title = description - } - - else -> { - helper.setHeaderText(R.string.security_settings_sfps_enroll_find_sensor_title) - helper.setDescriptionText( - getString(R.string.security_settings_sfps_enroll_start_message) - ) - val description: CharSequence = getString( - R.string.security_settings_sfps_enroll_find_sensor_message - ) - helper.glifLayout.headerTextView.contentDescription = description - helper.activity.title = description - } - } - } - - private fun showIconTouchDialog() { - iconTouchCount = 0 - enrollingViewModel.showIconTouchDialog() - } - - companion object { - private val TAG = FingerprintEnrollEnrollingSfpsFragment::class.java.simpleName - private const val DEBUG = false - private const val PROGRESS_BAR_MAX = 10000 - private const val HELP_ANIMATION_DURATION = 550L - private const val HELP_ANIMATION_TRANSLATION_X = 40f - private const val PROGRESS_ANIMATION_DURATION = 250L - private const val ICON_TOUCH_DURATION_UNTIL_DIALOG_SHOWN: Long = 500 - private const val ICON_TOUCH_COUNT_SHOW_UNTIL_DIALOG_SHOWN = 3 - private const val STAGE_UNKNOWN = -1 - private const val SFPS_STAGE_NO_ANIMATION = 0 - private const val SFPS_STAGE_CENTER = 1 - private const val SFPS_STAGE_FINGERTIP = 2 - private const val SFPS_STAGE_LEFT_EDGE = 3 - private const val SFPS_STAGE_RIGHT_EDGE = 4 - } -} - -fun FragmentActivity.bindFingerprintEnrollEnrollingSfpsView( - view: GlifLayout, - onSkipClickListener: View.OnClickListener -) { - GlifLayoutHelper(this, view).setDescriptionText( - getString(R.string.security_settings_fingerprint_enroll_start_message) - ) - - view.getMixin(FooterBarMixin::class.java).secondaryButton = FooterButton.Builder(this) - .setText(R.string.security_settings_fingerprint_enroll_enrolling_skip) - .setListener(onSkipClickListener) - .setButtonType(FooterButton.ButtonType.SKIP) - .setTheme(com.google.android.setupdesign.R.style.SudGlifButton_Secondary) - .build() - - view.findViewById(R.id.fingerprint_progress_bar)!!.progressBackgroundTintMode = - PorterDuff.Mode.SRC - - view.findViewById(R.id.fingerprint_progress_bar)!! - .applyProgressBarDynamicColor(this, false) - - view.findViewById(R.id.illustration_lottie)!! - .applyLottieDynamicColor(this, false) - - view.maybeHideSfpsText(resources.configuration.orientation) -} - -private fun ProgressBar.applyProgressBarDynamicColor(context: Context, isError: Boolean) { - progressTintList = ColorStateList.valueOf( - context.getColor( - if (isError) - R.color.sfps_enrollment_progress_bar_error_color - else - R.color.sfps_enrollment_progress_bar_fill_color - ) - ) - progressTintMode = PorterDuff.Mode.SRC - invalidate() -} - -fun LottieAnimationView.applyLottieDynamicColor(context: Context, isError: Boolean) { - addValueCallback( - KeyPath(".blue100", "**"), - LottieProperty.COLOR_FILTER - ) { - PorterDuffColorFilter( - context.getColor( - if (isError) - R.color.sfps_enrollment_fp_error_color - else - R.color.sfps_enrollment_fp_captured_color - ), - PorterDuff.Mode.SRC_ATOP - ) - } - invalidate() -} - -fun GlifLayout.maybeHideSfpsText(@Configuration.Orientation orientation: Int) { - val headerMixin: HeaderMixin = getMixin(HeaderMixin::class.java) - val descriptionMixin: DescriptionMixin = getMixin(DescriptionMixin::class.java) - - val isLandscape = (orientation == Configuration.ORIENTATION_LANDSCAPE) - headerMixin.setAutoTextSizeEnabled(isLandscape) - if (isLandscape) { - headerMixin.textView.minLines = 0 - headerMixin.textView.maxLines = 10 - descriptionMixin.textView.minLines = 0 - descriptionMixin.textView.maxLines = 10 - } else { - headerMixin.textView.setLines(4) - // hide the description - descriptionMixin.textView.setLines(0) - } -} diff --git a/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollEnrollingUdfpsFragment.kt b/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollEnrollingUdfpsFragment.kt deleted file mode 100644 index 7e754aca17b..00000000000 --- a/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollEnrollingUdfpsFragment.kt +++ /dev/null @@ -1,707 +0,0 @@ -/* - * Copyright (C) 2023 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.biometrics2.ui.view - -import android.annotation.RawRes -import android.content.Context -import android.hardware.biometrics.BiometricFingerprintConstants -import android.hardware.fingerprint.FingerprintManager.ENROLL_ENROLL -import android.hardware.fingerprint.FingerprintSensorPropertiesInternal -import android.os.Bundle -import android.util.Log -import android.view.LayoutInflater -import android.view.Surface -import android.view.Surface.ROTATION_270 -import android.view.Surface.ROTATION_90 -import android.view.View -import android.view.ViewGroup -import android.widget.Button -import android.widget.ImageView -import android.widget.RelativeLayout -import android.widget.TextView -import androidx.activity.OnBackPressedCallback -import androidx.fragment.app.Fragment -import androidx.fragment.app.FragmentActivity -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.Observer -import androidx.lifecycle.ViewModelProvider -import androidx.lifecycle.lifecycleScope -import androidx.lifecycle.repeatOnLifecycle -import com.airbnb.lottie.LottieAnimationView -import com.airbnb.lottie.LottieComposition -import com.airbnb.lottie.LottieCompositionFactory -import com.android.settings.R -import com.android.settings.biometrics2.ui.model.EnrollmentProgress -import com.android.settings.biometrics2.ui.model.EnrollmentStatusMessage -import com.android.settings.biometrics2.ui.viewmodel.DeviceRotationViewModel -import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollEnrollingViewModel -import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollErrorDialogViewModel -import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollProgressViewModel -import com.android.settings.biometrics2.ui.widget.UdfpsEnrollView -import com.android.settingslib.display.DisplayDensityUtils -import kotlinx.coroutines.launch -import kotlin.math.roundToInt - -/** - * Fragment is used to handle enrolling process for udfps - */ -class FingerprintEnrollEnrollingUdfpsFragment : Fragment() { - - private var _enrollingViewModel: FingerprintEnrollEnrollingViewModel? = null - private val enrollingViewModel: FingerprintEnrollEnrollingViewModel - get() = _enrollingViewModel!! - - private var _rotationViewModel: DeviceRotationViewModel? = null - private val rotationViewModel: DeviceRotationViewModel - get() = _rotationViewModel!! - - private var _progressViewModel: FingerprintEnrollProgressViewModel? = null - private val progressViewModel: FingerprintEnrollProgressViewModel - get() = _progressViewModel!! - - private var _errorDialogViewModel: FingerprintEnrollErrorDialogViewModel? = null - private val errorDialogViewModel: FingerprintEnrollErrorDialogViewModel - get() = _errorDialogViewModel!! - - private var illustrationLottie: LottieAnimationView? = null - - private var haveShownTipLottie = false - private var haveShownLeftEdgeLottie = false - private var haveShownRightEdgeLottie = false - private var haveShownCenterLottie = false - private var haveShownGuideLottie = false - - private var enrollingView: RelativeLayout? = null - - private val titleText: TextView - get() = enrollingView!!.findViewById(R.id.suc_layout_title)!! - - private val subTitleText: TextView - get() = enrollingView!!.findViewById(R.id.sud_layout_subtitle)!! - - private val udfpsEnrollView: UdfpsEnrollView - get() = enrollingView!!.findViewById(R.id.udfps_animation_view)!! - - private val skipBtn: Button - get() = enrollingView!!.findViewById(R.id.skip_btn)!! - - private val icon: ImageView - get() = enrollingView!!.findViewById(R.id.sud_layout_icon)!! - - private val shouldShowLottie: Boolean - get() { - val displayDensity = DisplayDensityUtils(requireContext()) - val currentDensityIndex: Int = displayDensity.currentIndexForDefaultDisplay - val currentDensity: Int = - displayDensity.defaultDisplayDensityValues[currentDensityIndex] - val defaultDensity: Int = displayDensity.defaultDensityForDefaultDisplay - return defaultDensity == currentDensity - } - - private val isAccessibilityEnabled - get() = enrollingViewModel.isAccessibilityEnabled - - private var rotation = -1 - - private var enrollingCancelSignal: Any? = null - - private val onSkipClickListener = View.OnClickListener { _: View? -> - enrollingViewModel.setOnSkipPressed() - cancelEnrollment(true) // TODO Add test after b/273640000 fixed - } - - private val progressObserver = Observer { progress: EnrollmentProgress? -> - if (progress != null && progress.steps >= 0) { - onEnrollmentProgressChange(progress) - } - } - - private val helpMessageObserver = Observer { helpMessage: EnrollmentStatusMessage? -> - Log.d(TAG, "helpMessageObserver($helpMessage)") - helpMessage?.let { onEnrollmentHelp(it) } - } - - private val errorMessageObserver = Observer { errorMessage: EnrollmentStatusMessage? -> - Log.d(TAG, "errorMessageObserver($errorMessage)") - errorMessage?.let { onEnrollmentError(it) } - } - - private val canceledSignalObserver = Observer { canceledSignal: Any? -> - Log.d(TAG, "canceledSignalObserver($canceledSignal)") - canceledSignal?.let { onEnrollmentCanceled(it) } - } - - private val acquireObserver = - Observer { isAcquiredGood: Boolean? -> isAcquiredGood?.let { onAcquired(it) } } - - private val pointerDownObserver = - Observer { sensorId: Int? -> sensorId?.let { onPointerDown(it) } } - - private val pointerUpObserver = - Observer { sensorId: Int? -> sensorId?.let { onPointerUp(it) } } - - private val rotationObserver = - Observer { rotation: Int? -> rotation?.let { onRotationChanged(it) } } - - private val onBackPressedCallback: OnBackPressedCallback = - object : OnBackPressedCallback(true) { - override fun handleOnBackPressed() { - isEnabled = false - enrollingViewModel.setOnBackPressed() - cancelEnrollment(true) - } - } - - // Give the user a chance to see progress completed before jumping to the next stage. - private val delayedFinishRunnable = Runnable { enrollingViewModel.onEnrollingDone() } - - override fun onAttach(context: Context) { - ViewModelProvider(requireActivity()).let { provider -> - _enrollingViewModel = provider[FingerprintEnrollEnrollingViewModel::class.java] - _rotationViewModel = provider[DeviceRotationViewModel::class.java] - _progressViewModel = provider[FingerprintEnrollProgressViewModel::class.java] - _errorDialogViewModel = provider[FingerprintEnrollErrorDialogViewModel::class.java] - } - super.onAttach(context) - requireActivity().onBackPressedDispatcher.addCallback(onBackPressedCallback) - } - - override fun onDetach() { - onBackPressedCallback.isEnabled = false - super.onDetach() - } - - override fun onCreateView( - inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle? - ): View = (inflater.inflate( - R.layout.udfps_enroll_enrolling_v2, container, false - ) as RelativeLayout).also { - enrollingView = it - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - rotation = rotationViewModel.liveData.value!! - updateIllustrationLottie(rotation) - - requireActivity().bindFingerprintEnrollEnrollingUdfpsView( - view = enrollingView!!, - sensorProperties = enrollingViewModel.firstFingerprintSensorPropertiesInternal!!, - rotation = rotation, - onSkipClickListener = onSkipClickListener, - ) - - lifecycleScope.launch { - repeatOnLifecycle(Lifecycle.State.STARTED) { - errorDialogViewModel.triggerRetryFlow.collect { retryEnrollment() } - } - } - } - - private fun retryEnrollment() { - reattachUdfpsEnrollView() - - startEnrollment() - - updateProgress(false /* animate */, progressViewModel.progressLiveData.value!!) - progressViewModel.helpMessageLiveData.value.let { - if (it != null) { - onEnrollmentHelp(it) - } else { - updateTitleAndDescription() - } - } - } - - override fun onStart() { - super.onStart() - val isEnrolling = progressViewModel.isEnrolling - val isErrorDialogShown = errorDialogViewModel.isDialogShown - Log.d(TAG, "onStart(), isEnrolling:$isEnrolling, isErrorDialog:$isErrorDialogShown") - if (!isErrorDialogShown) { - startEnrollment() - } - - updateProgress(false /* animate */, progressViewModel.progressLiveData.value!!) - progressViewModel.helpMessageLiveData.value.let { - if (it != null) { - onEnrollmentHelp(it) - } else { - updateTitleAndDescription() - } - } - } - - private fun reattachUdfpsEnrollView() { - enrollingView!!.let { - val newUdfpsView = LayoutInflater.from(requireActivity()).inflate( - R.layout.udfps_enroll_enrolling_v2_udfps_view, - null - ) - val index = it.indexOfChild(udfpsEnrollView) - val lp = udfpsEnrollView.layoutParams - - it.removeView(udfpsEnrollView) - it.addView(newUdfpsView, index, lp) - udfpsEnrollView.setSensorProperties( - enrollingViewModel.firstFingerprintSensorPropertiesInternal - ) - } - - // Clear lottie status - haveShownTipLottie = false - haveShownLeftEdgeLottie = false - haveShownRightEdgeLottie = false - haveShownCenterLottie = false - haveShownGuideLottie = false - illustrationLottie?.let { - it.contentDescription = "" - it.visibility = View.GONE - } - } - - override fun onResume() { - super.onResume() - rotationViewModel.liveData.observe(this, rotationObserver) - } - - override fun onPause() { - rotationViewModel.liveData.removeObserver(rotationObserver) - super.onPause() - } - - override fun onStop() { - removeEnrollmentObservers() - val isEnrolling = progressViewModel.isEnrolling - val isConfigChange = requireActivity().isChangingConfigurations - Log.d(TAG, "onStop(), enrolling:$isEnrolling isConfigChange:$isConfigChange") - if (isEnrolling && !isConfigChange) { - cancelEnrollment(false) - } - super.onStop() - } - - private fun removeEnrollmentObservers() { - progressViewModel.errorMessageLiveData.removeObserver(errorMessageObserver) - progressViewModel.progressLiveData.removeObserver(progressObserver) - progressViewModel.helpMessageLiveData.removeObserver(helpMessageObserver) - progressViewModel.acquireLiveData.removeObserver(acquireObserver) - progressViewModel.pointerDownLiveData.removeObserver(pointerDownObserver) - progressViewModel.pointerUpLiveData.removeObserver(pointerUpObserver) - } - - private fun cancelEnrollment(waitForLastCancelErrMsg: Boolean) { - if (!progressViewModel.isEnrolling) { - Log.d(TAG, "cancelEnrollment(), failed because isEnrolling is false") - return - } - removeEnrollmentObservers() - if (waitForLastCancelErrMsg) { - progressViewModel.canceledSignalLiveData.observe(this, canceledSignalObserver) - } else { - enrollingCancelSignal = null - } - val cancelResult: Boolean = progressViewModel.cancelEnrollment() - if (!cancelResult) { - Log.e(TAG, "cancelEnrollment(), failed to cancel enrollment") - } - } - - private fun startEnrollment() { - enrollingCancelSignal = progressViewModel.startEnrollment(ENROLL_ENROLL) - if (enrollingCancelSignal == null) { - Log.e(TAG, "startEnrollment(), failed") - } else { - Log.d(TAG, "startEnrollment(), success") - } - progressViewModel.progressLiveData.observe(this, progressObserver) - progressViewModel.helpMessageLiveData.observe(this, helpMessageObserver) - progressViewModel.errorMessageLiveData.observe(this, errorMessageObserver) - progressViewModel.acquireLiveData.observe(this, acquireObserver) - progressViewModel.pointerDownLiveData.observe(this, pointerDownObserver) - progressViewModel.pointerUpLiveData.observe(this, pointerUpObserver) - } - - private fun updateProgress(animate: Boolean, enrollmentProgress: EnrollmentProgress) { - if (!progressViewModel.isEnrolling) { - Log.d(TAG, "Enrollment not started yet") - return - } - - val progress = getProgress(enrollmentProgress) - Log.d(TAG, "updateProgress($animate, $enrollmentProgress), progress:$progress") - - if (enrollmentProgress.steps != -1) { - udfpsEnrollView.onEnrollmentProgress( - enrollmentProgress.remaining, - enrollmentProgress.steps - ) - } - - if (progress >= PROGRESS_BAR_MAX) { - if (animate) { - // Wait animations to finish, then proceed to next page - activity!!.mainThreadHandler.postDelayed(delayedFinishRunnable, 400L) - } else { - delayedFinishRunnable.run() - } - } - } - - private fun getProgress(progress: EnrollmentProgress): Int { - if (progress.steps == -1) { - return 0 - } - val displayProgress = 0.coerceAtLeast(progress.steps + 1 - progress.remaining) - return PROGRESS_BAR_MAX * displayProgress / (progress.steps + 1) - } - - private fun updateTitleAndDescription() { - Log.d(TAG, "updateTitleAndDescription($currentStage)") - when (currentStage) { - STAGE_CENTER -> { - titleText.setText(R.string.security_settings_fingerprint_enroll_repeat_title) - if (isAccessibilityEnabled || illustrationLottie == null) { - subTitleText.setText(R.string.security_settings_udfps_enroll_start_message) - } else if (!haveShownCenterLottie) { - haveShownCenterLottie = true - // Note: Update string reference when differentiate in between udfps & sfps - illustrationLottie!!.contentDescription = getString(R.string.security_settings_sfps_enroll_finger_center_title) - configureEnrollmentStage(R.raw.udfps_center_hint_lottie) - } - } - - STAGE_GUIDED -> { - titleText.setText(R.string.security_settings_fingerprint_enroll_repeat_title) - if (isAccessibilityEnabled || illustrationLottie == null) { - subTitleText.setText( - R.string.security_settings_udfps_enroll_repeat_a11y_message - ) - } else if (!haveShownGuideLottie) { - haveShownGuideLottie = true - illustrationLottie!!.contentDescription = - getString(R.string.security_settings_fingerprint_enroll_repeat_message) - // TODO(b/228100413) Could customize guided lottie animation - configureEnrollmentStage(R.raw.udfps_center_hint_lottie) - } - } - - STAGE_FINGERTIP -> { - titleText.setText(R.string.security_settings_udfps_enroll_fingertip_title) - if (!haveShownTipLottie && illustrationLottie != null) { - haveShownTipLottie = true - illustrationLottie!!.contentDescription = - getString(R.string.security_settings_udfps_tip_fingerprint_help) - configureEnrollmentStage(R.raw.udfps_tip_hint_lottie) - } - } - - STAGE_LEFT_EDGE -> { - titleText.setText(R.string.security_settings_udfps_enroll_left_edge_title) - if (!haveShownLeftEdgeLottie && illustrationLottie != null) { - haveShownLeftEdgeLottie = true - illustrationLottie!!.contentDescription = - getString(R.string.security_settings_udfps_side_fingerprint_help) - configureEnrollmentStage(R.raw.udfps_left_edge_hint_lottie) - } else if (illustrationLottie == null) { - if (isStageHalfCompleted) { - subTitleText.setText( - R.string.security_settings_fingerprint_enroll_repeat_message - ) - } else { - subTitleText.setText(R.string.security_settings_udfps_enroll_edge_message) - } - } - } - - STAGE_RIGHT_EDGE -> { - titleText.setText(R.string.security_settings_udfps_enroll_right_edge_title) - if (!haveShownRightEdgeLottie && illustrationLottie != null) { - haveShownRightEdgeLottie = true - illustrationLottie!!.contentDescription = - getString(R.string.security_settings_udfps_side_fingerprint_help) - configureEnrollmentStage(R.raw.udfps_right_edge_hint_lottie) - } else if (illustrationLottie == null) { - if (isStageHalfCompleted) { - subTitleText.setText( - R.string.security_settings_fingerprint_enroll_repeat_message - ) - } else { - subTitleText.setText(R.string.security_settings_udfps_enroll_edge_message) - } - } - } - - STAGE_UNKNOWN -> { - titleText.setText(R.string.security_settings_fingerprint_enroll_udfps_title) - subTitleText.setText(R.string.security_settings_udfps_enroll_start_message) - val description: CharSequence = getString( - R.string.security_settings_udfps_enroll_a11y - ) - requireActivity().title = description - } - - else -> { - titleText.setText(R.string.security_settings_fingerprint_enroll_udfps_title) - subTitleText.setText(R.string.security_settings_udfps_enroll_start_message) - val description: CharSequence = getString( - R.string.security_settings_udfps_enroll_a11y - ) - requireActivity().title = description - } - } - } - - private fun updateIllustrationLottie(@Surface.Rotation rotation: Int) { - if (rotation == ROTATION_90 || rotation == ROTATION_270) { - illustrationLottie = null - } else if (shouldShowLottie) { - illustrationLottie = - enrollingView!!.findViewById(R.id.illustration_lottie) - } - } - - private val currentStage: Int - get() { - val progress = progressViewModel.progressLiveData.value!! - if (progress.steps == -1) { - return STAGE_UNKNOWN - } - val progressSteps: Int = progress.steps - progress.remaining - return if (progressSteps < getStageThresholdSteps(0)) { - STAGE_CENTER - } else if (progressSteps < getStageThresholdSteps(1)) { - STAGE_GUIDED - } else if (progressSteps < getStageThresholdSteps(2)) { - STAGE_FINGERTIP - } else if (progressSteps < getStageThresholdSteps(3)) { - STAGE_LEFT_EDGE - } else { - STAGE_RIGHT_EDGE - } - } - - private val isStageHalfCompleted: Boolean - get() { - val progress: EnrollmentProgress = progressViewModel.progressLiveData.value!! - if (progress.steps == -1) { - return false - } - val progressSteps: Int = progress.steps - progress.remaining - var prevThresholdSteps = 0 - for (i in 0 until enrollingViewModel.getEnrollStageCount()) { - val thresholdSteps = getStageThresholdSteps(i) - if (progressSteps in prevThresholdSteps until thresholdSteps) { - val adjustedProgress = progressSteps - prevThresholdSteps - val adjustedThreshold = thresholdSteps - prevThresholdSteps - return adjustedProgress >= adjustedThreshold / 2 - } - prevThresholdSteps = thresholdSteps - } - - // After last enrollment step. - return true - } - - private fun getStageThresholdSteps(index: Int): Int { - val progress: EnrollmentProgress = progressViewModel.progressLiveData.value!! - if (progress.steps == -1) { - Log.w(TAG, "getStageThresholdSteps: Enrollment not started yet") - return 1 - } - return (progress.steps * enrollingViewModel.getEnrollStageThreshold(index)).roundToInt() - } - - private fun configureEnrollmentStage(@RawRes lottie: Int) { - subTitleText.text = "" - LottieCompositionFactory.fromRawRes(activity, lottie) - .addListener { c: LottieComposition -> - illustrationLottie?.let { - it.setComposition(c) - it.visibility = View.VISIBLE - it.playAnimation() - } - } - } - - private fun onEnrollmentProgressChange(progress: EnrollmentProgress) { - updateProgress(true /* animate */, progress) - updateTitleAndDescription() - if (isAccessibilityEnabled) { - val steps: Int = progress.steps - val remaining: Int = progress.remaining - val percent = ((steps - remaining).toFloat() / steps.toFloat() * 100).toInt() - val announcement: CharSequence = activity!!.getString( - R.string.security_settings_udfps_enroll_progress_a11y_message, percent - ) - enrollingViewModel.sendAccessibilityEvent(announcement) - } - } - - private fun onEnrollmentHelp(helpMessage: EnrollmentStatusMessage) { - Log.d(TAG, "onEnrollmentHelp($helpMessage)") - val helpStr: CharSequence = helpMessage.str - if (helpStr.isNotEmpty()) { - showError(helpStr) - udfpsEnrollView.onEnrollmentHelp() - } - } - - private fun onEnrollmentError(errorMessage: EnrollmentStatusMessage) { - cancelEnrollment(true) - lifecycleScope.launch { - Log.d(TAG, "newDialog $errorMessage") - errorDialogViewModel.newDialog(errorMessage.msgId) - } - } - - private fun onEnrollmentCanceled(canceledSignal: Any) { - Log.d( - TAG, - "onEnrollmentCanceled enrolling:$enrollingCancelSignal, canceled:$canceledSignal" - ) - if (enrollingCancelSignal === canceledSignal) { - progressViewModel.canceledSignalLiveData.removeObserver(canceledSignalObserver) - progressViewModel.clearProgressLiveData() - if (enrollingViewModel.onBackPressed) { - enrollingViewModel.onCancelledDueToOnBackPressed() - } else if (enrollingViewModel.onSkipPressed) { - enrollingViewModel.onCancelledDueToOnSkipPressed() - } - } - } - - private fun onAcquired(isAcquiredGood: Boolean) { - udfpsEnrollView.onAcquired(isAcquiredGood) - } - - private fun onPointerDown(sensorId: Int) { - udfpsEnrollView.onPointerDown(sensorId) - } - - private fun onPointerUp(sensorId: Int) { - udfpsEnrollView.onPointerUp(sensorId) - } - - private fun showError(error: CharSequence) { - titleText.text = error - titleText.contentDescription = error - subTitleText.contentDescription = "" - } - - private fun onRotationChanged(newRotation: Int) { - if ((newRotation + 2) % 4 == rotation) { - rotation = newRotation - requireContext().configLayout(newRotation, titleText, subTitleText, icon, skipBtn) - } - } - - companion object { - private val TAG = "FingerprintEnrollEnrollingUdfpsFragment" - private const val PROGRESS_BAR_MAX = 10000 - private const val STAGE_UNKNOWN = -1 - private const val STAGE_CENTER = 0 - private const val STAGE_GUIDED = 1 - private const val STAGE_FINGERTIP = 2 - private const val STAGE_LEFT_EDGE = 3 - private const val STAGE_RIGHT_EDGE = 4 - } -} - - -fun FragmentActivity.bindFingerprintEnrollEnrollingUdfpsView( - view: RelativeLayout, - sensorProperties: FingerprintSensorPropertiesInternal, - @Surface.Rotation rotation: Int, - onSkipClickListener: View.OnClickListener -) { - view.findViewById(R.id.udfps_animation_view)!!.setSensorProperties( - sensorProperties - ) - - val titleText = view.findViewById(R.id.suc_layout_title)!! - val subTitleText = view.findViewById(R.id.sud_layout_subtitle)!! - val icon = view.findViewById(R.id.sud_layout_icon)!! - val skipBtn = view.findViewById