diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintErrorDialog.java b/src/com/android/settings/biometrics/fingerprint/FingerprintErrorDialog.java index 870e1bb3b99..8f604e21605 100644 --- a/src/com/android/settings/biometrics/fingerprint/FingerprintErrorDialog.java +++ b/src/com/android/settings/biometrics/fingerprint/FingerprintErrorDialog.java @@ -123,7 +123,10 @@ public class FingerprintErrorDialog extends InstrumentedDialogFragment { dialog.show(fragmentManager, FingerprintErrorDialog.class.getName()); } - private static int getErrorMessage(int errMsgId) { + /** + * Gets dialog message as error id inside {@link FingerprintManager} + */ + public static int getErrorMessage(int errMsgId) { switch (errMsgId) { case FingerprintManager.FINGERPRINT_ERROR_TIMEOUT: // This message happens when the underlying crypto layer decides to revoke @@ -137,7 +140,10 @@ public class FingerprintErrorDialog extends InstrumentedDialogFragment { } } - private static int getErrorTitle(int errMsgId) { + /** + * Gets dialog title as error id inside {@link FingerprintManager} + */ + public static int getErrorTitle(int errMsgId) { switch (errMsgId) { case FingerprintManager.FINGERPRINT_ERROR_UNABLE_TO_PROCESS: return R.string diff --git a/src/com/android/settings/biometrics2/data/repository/FingerprintRepository.java b/src/com/android/settings/biometrics2/data/repository/FingerprintRepository.java index 8f432e61062..6a9892ab6a3 100644 --- a/src/com/android/settings/biometrics2/data/repository/FingerprintRepository.java +++ b/src/com/android/settings/biometrics2/data/repository/FingerprintRepository.java @@ -98,8 +98,11 @@ public class FingerprintRepository { return resources.getInteger(R.integer.suw_max_fingerprints_enrollable); } + /** + * Gets the first FingerprintSensorPropertiesInternal from FingerprintManager + */ @Nullable - private FingerprintSensorPropertiesInternal getFirstFingerprintSensorPropertiesInternal() { + public FingerprintSensorPropertiesInternal getFirstFingerprintSensorPropertiesInternal() { final List props = mSensorPropertiesCache; if (props == null) { // Handle this case if it really happens diff --git a/src/com/android/settings/biometrics2/factory/BiometricsViewModelFactory.java b/src/com/android/settings/biometrics2/factory/BiometricsViewModelFactory.java index 7bf9d538a86..19971a78ba5 100644 --- a/src/com/android/settings/biometrics2/factory/BiometricsViewModelFactory.java +++ b/src/com/android/settings/biometrics2/factory/BiometricsViewModelFactory.java @@ -113,13 +113,14 @@ public class BiometricsViewModelFactory implements ViewModelProvider.Factory { new FingerprintUpdater(application), userId); } } else if (modelClass.isAssignableFrom(FingerprintEnrollEnrollingViewModel.class)) { + final Integer userId = extras.get(USER_ID_KEY); final FingerprintRepository fingerprint = provider.getFingerprintRepository( application); final AccessibilityRepository accessibility = provider.getAccessibilityRepository( application); final VibratorRepository vibrator = provider.getVibratorRepository(application); if (fingerprint != null && accessibility != null && vibrator != null) { - return (T) new FingerprintEnrollEnrollingViewModel(application, fingerprint, + return (T) new FingerprintEnrollEnrollingViewModel(application, userId, fingerprint, accessibility, vibrator); } } diff --git a/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollEnrollingErrorDialog.java b/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollEnrollingErrorDialog.java new file mode 100644 index 00000000000..ad34e740d98 --- /dev/null +++ b/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollEnrollingErrorDialog.java @@ -0,0 +1,95 @@ +/* + * Copyright 2021 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 static com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollEnrollingViewModel.ErrorDialogData; +import static com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollEnrollingViewModel.FINGERPRINT_ERROR_DIALOG_ACTION_RESTART; +import static com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollEnrollingViewModel.FINGERPRINT_ERROR_DIALOG_ACTION_SET_RESULT_FINISH; +import static com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollEnrollingViewModel.FINGERPRINT_ERROR_DIALOG_ACTION_SET_RESULT_TIMEOUT; + +import android.app.Dialog; +import android.app.settings.SettingsEnums; +import android.content.Context; +import android.hardware.biometrics.BiometricConstants; +import android.os.Bundle; + +import androidx.appcompat.app.AlertDialog; +import androidx.lifecycle.ViewModelProvider; + +import com.android.settings.R; +import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollEnrollingViewModel; +import com.android.settings.core.instrumentation.InstrumentedDialogFragment; + +/** + * Fingerprint error dialog, will be shown when an error occurs during fingerprint enrollment. + */ +public class FingerprintEnrollEnrollingErrorDialog extends InstrumentedDialogFragment { + + private FingerprintEnrollEnrollingViewModel mViewModel; + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + final ErrorDialogData data = mViewModel.getErrorDialogLiveData().getValue(); + final int errMsgId = data.getErrMsgId(); + final boolean canAssumeUdfps = mViewModel.canAssumeUdfps(); + final boolean wasTimeout = errMsgId == BiometricConstants.BIOMETRIC_ERROR_TIMEOUT; + + builder.setTitle(data.getErrTitle()) + .setMessage(data.getErrMsg()) + .setCancelable(false); + if (wasTimeout && canAssumeUdfps) { + builder.setPositiveButton( + R.string.security_settings_fingerprint_enroll_dialog_try_again, + (dialog, which) -> { + dialog.dismiss(); + mViewModel.onErrorDialogAction(FINGERPRINT_ERROR_DIALOG_ACTION_RESTART); + }); + builder.setNegativeButton( + R.string.security_settings_fingerprint_enroll_dialog_ok, + (dialog, which) -> { + dialog.dismiss(); + mViewModel.onErrorDialogAction( + FINGERPRINT_ERROR_DIALOG_ACTION_SET_RESULT_TIMEOUT); + }); + } else { + builder.setPositiveButton( + R.string.security_settings_fingerprint_enroll_dialog_ok, + (dialog, which) -> { + dialog.dismiss(); + mViewModel.onErrorDialogAction(wasTimeout + ? FINGERPRINT_ERROR_DIALOG_ACTION_SET_RESULT_TIMEOUT + : FINGERPRINT_ERROR_DIALOG_ACTION_SET_RESULT_FINISH); + }); + } + final AlertDialog dialog = builder.create(); + dialog.setCanceledOnTouchOutside(false); + return dialog; + } + + @Override + public int getMetricsCategory() { + return SettingsEnums.DIALOG_FINGERPINT_ERROR; + } + + @Override + public void onAttach(Context context) { + mViewModel = new ViewModelProvider(getActivity()).get( + FingerprintEnrollEnrollingViewModel.class); + super.onAttach(context); + } +} diff --git a/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollEnrollingRfpsFragment.java b/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollEnrollingRfpsFragment.java index 30b66a2d4d0..30823ea792a 100644 --- a/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollEnrollingRfpsFragment.java +++ b/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollEnrollingRfpsFragment.java @@ -16,15 +16,19 @@ package com.android.settings.biometrics2.ui.view; +import static android.hardware.fingerprint.FingerprintManager.ENROLL_ENROLL; + +import android.animation.Animator; +import android.animation.ObjectAnimator; import android.app.Activity; import android.content.Context; -import android.content.res.Configuration; 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.os.Bundle; +import android.text.TextUtils; import android.util.Log; import android.view.LayoutInflater; import android.view.MotionEvent; @@ -35,18 +39,22 @@ import android.view.animation.Interpolator; import android.widget.ProgressBar; import android.widget.TextView; +import androidx.annotation.IdRes; import androidx.annotation.NonNull; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentActivity; +import androidx.lifecycle.Observer; import androidx.lifecycle.ViewModelProvider; +import androidx.transition.Transition; +import androidx.transition.TransitionSet; import com.android.settings.R; -import com.android.settings.biometrics.BiometricUtils; +import com.android.settings.biometrics.fingerprint.FingerprintErrorDialog; 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.FingerprintEnrollProgressViewModel; -import com.android.settingslib.display.DisplayDensityUtils; import com.airbnb.lottie.LottieAnimationView; import com.google.android.setupcompat.template.FooterBarMixin; @@ -59,9 +67,16 @@ import com.google.android.setupdesign.GlifLayout; public class FingerprintEnrollEnrollingRfpsFragment extends Fragment { private static final String TAG = FingerprintEnrollEnrollingRfpsFragment.class.getSimpleName(); + private static final boolean DEBUG = false; + private static final int PROGRESS_BAR_MAX = 10000; private static final long ICON_TOUCH_DURATION_UNTIL_DIALOG_SHOWN = 500; private static final int 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 static final int HINT_TIMEOUT_DURATION = 2500; private FingerprintEnrollEnrollingViewModel mEnrollingViewModel; @@ -73,8 +88,9 @@ public class FingerprintEnrollEnrollingRfpsFragment extends Fragment { private Interpolator mFastOutLinearInInterpolator; private boolean mAnimationCancelled; - private View mView; + private GlifLayout mView; private ProgressBar mProgressBar; + private ObjectAnimator mProgressAnim; private TextView mErrorText; private FooterBarMixin mFooterBarMixin; private AnimatedVectorDrawable mIconAnimationDrawable; @@ -90,8 +106,45 @@ public class FingerprintEnrollEnrollingRfpsFragment extends Fragment { private boolean mHaveShownSfpsLeftEdgeLottie; private boolean mHaveShownSfpsRightEdgeLottie; - private final View.OnClickListener mOnSkipClickListener = - (v) -> mEnrollingViewModel.onSkipButtonClick(); + private final View.OnClickListener mOnSkipClickListener = v -> { + mProgressViewModel.cancelEnrollment(); + mEnrollingViewModel.onSkipButtonClick(); + }; + + private final Observer mProgressObserver = progress -> { + if (DEBUG) { + Log.d(TAG, "mProgressObserver(" + progress + ")"); + } + if (progress != null) { + onEnrollmentProgressChange(progress); + } + }; + + private final Observer mHelpMessageObserver = helpMessage -> { + if (DEBUG) { + Log.d(TAG, "mHelpMessageObserver(" + helpMessage + ")"); + } + if (helpMessage != null) { + onEnrollmentHelp(helpMessage); + } + }; + private final Observer mErrorMessageObserver = errorMessage -> { + if (DEBUG) { + Log.d(TAG, "mErrorMessageObserver(" + errorMessage + ")"); + } + if (errorMessage != null) { + onEnrollmentError(errorMessage); + } + }; + private final Observer mAcquireObserver = isAcquiredGood -> { + // TODO + }; + private final Observer mPointerDownObserver = sensorId -> { + // TODO + }; + private final Observer mPointerUpObserver = sensorId -> { + // TODO + }; private int mIconTouchCount; @@ -103,14 +156,48 @@ public class FingerprintEnrollEnrollingRfpsFragment extends Fragment { mRotationViewModel = provider.get(DeviceRotationViewModel.class); mProgressViewModel = provider.get(FingerprintEnrollProgressViewModel.class); super.onAttach(context); + final TransitionSet transitionSet = (TransitionSet) getSharedElementEnterTransition(); + if (transitionSet != null) { + transitionSet.addListener(new Transition.TransitionListener() { + @Override + public void onTransitionStart(@NonNull Transition transition) { + } + + @Override + public void onTransitionEnd(@NonNull Transition transition) { + transition.removeListener(this); + mAnimationCancelled = false; + startIconAnimation(); + } + + @Override + public void onTransitionCancel(@NonNull Transition transition) { + } + + @Override + public void onTransitionPause(@NonNull Transition transition) { + } + + @Override + public void onTransitionResume(@NonNull Transition transition) { + } + }); + } } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + mEnrollingViewModel.restoreSavedState(savedInstanceState); mIsAccessibilityEnabled = mEnrollingViewModel.isAccessibilityEnabled(); } + @Override + public void onSaveInstanceState(@NonNull Bundle outState) { + mEnrollingViewModel.onSaveInstanceState(outState); + super.onSaveInstanceState(outState); + } + @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { @@ -118,24 +205,25 @@ public class FingerprintEnrollEnrollingRfpsFragment extends Fragment { return mView; } - private View initRfpsLayout(LayoutInflater inflater, ViewGroup container) { - final View containView = inflater.inflate(R.layout.sfps_enroll_enrolling, container, false); + private GlifLayout initRfpsLayout(LayoutInflater inflater, ViewGroup container) { + final GlifLayout containView = (GlifLayout) inflater.inflate( + R.layout.fingerprint_enroll_enrolling, container, false); + final Activity activity = getActivity(); - final GlifLayoutHelper glifLayoutHelper = new GlifLayoutHelper(activity, - (GlifLayout) containView); - glifLayoutHelper.setDescriptionText( - R.string.security_settings_fingerprint_enroll_start_message); + final GlifLayoutHelper glifLayoutHelper = new GlifLayoutHelper(activity, containView); + glifLayoutHelper.setDescriptionText(getString( + R.string.security_settings_fingerprint_enroll_start_message)); glifLayoutHelper.setHeaderText(R.string.security_settings_fingerprint_enroll_repeat_title); - mShouldShowLottie = shouldShowLottie(); - boolean isLandscape = BiometricUtils.isReverseLandscape(activity) - || BiometricUtils.isLandscape(activity); - updateOrientation((isLandscape - ? Configuration.ORIENTATION_LANDSCAPE : Configuration.ORIENTATION_PORTRAIT)); +// mShouldShowLottie = shouldShowLottie(); // TODO move this call into updateOrientation()? +// boolean isLandscape = BiometricUtils.isReverseLandscape(activity) +// || BiometricUtils.isLandscape(activity); +// updateOrientation(containView, (isLandscape +// ? Configuration.ORIENTATION_LANDSCAPE : Configuration.ORIENTATION_PORTRAIT)); mErrorText = containView.findViewById(R.id.error_text); mProgressBar = containView.findViewById(R.id.fingerprint_progress_bar); - mFooterBarMixin = ((GlifLayout) containView).getMixin(FooterBarMixin.class); + mFooterBarMixin = containView.getMixin(FooterBarMixin.class); mFooterBarMixin.setSecondaryButton( new FooterButton.Builder(activity) .setText(R.string.security_settings_fingerprint_enroll_enrolling_skip) @@ -145,15 +233,12 @@ public class FingerprintEnrollEnrollingRfpsFragment extends Fragment { .build() ); - final LayerDrawable fingerprintDrawable = mProgressBar != null - ? (LayerDrawable) mProgressBar.getBackground() : null; - if (fingerprintDrawable != null) { - mIconAnimationDrawable = (AnimatedVectorDrawable) - fingerprintDrawable.findDrawableByLayerId(R.id.fingerprint_animation); - mIconBackgroundBlinksDrawable = (AnimatedVectorDrawable) - fingerprintDrawable.findDrawableByLayerId(R.id.fingerprint_background); - mIconAnimationDrawable.registerAnimationCallback(mIconAnimationCallback); - } + final LayerDrawable fingerprintDrawable = (LayerDrawable) mProgressBar.getBackground(); + mIconAnimationDrawable = (AnimatedVectorDrawable) + fingerprintDrawable.findDrawableByLayerId(R.id.fingerprint_animation); + mIconBackgroundBlinksDrawable = (AnimatedVectorDrawable) + fingerprintDrawable.findDrawableByLayerId(R.id.fingerprint_background); + mIconAnimationDrawable.registerAnimationCallback(mIconAnimationCallback); mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator( activity, android.R.interpolator.fast_out_slow_in); @@ -162,58 +247,34 @@ public class FingerprintEnrollEnrollingRfpsFragment extends Fragment { mFastOutLinearInInterpolator = AnimationUtils.loadInterpolator( activity, android.R.interpolator.fast_out_linear_in); - if (mProgressBar != null) { - mProgressBar.setProgressBackgroundTintMode(PorterDuff.Mode.SRC); - mProgressBar.setOnTouchListener((v, event) -> { - if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { - mIconTouchCount++; - if (mIconTouchCount == ICON_TOUCH_COUNT_SHOW_UNTIL_DIALOG_SHOWN) { - showIconTouchDialog(); - } else { - mProgressBar.postDelayed(mShowDialogRunnable, - ICON_TOUCH_DURATION_UNTIL_DIALOG_SHOWN); - } - } else if (event.getActionMasked() == MotionEvent.ACTION_CANCEL - || event.getActionMasked() == MotionEvent.ACTION_UP) { - mProgressBar.removeCallbacks(mShowDialogRunnable); + mProgressBar.setProgressBackgroundTintMode(PorterDuff.Mode.SRC); + mProgressBar.setOnTouchListener((v, event) -> { + if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { + mIconTouchCount++; + if (mIconTouchCount == ICON_TOUCH_COUNT_SHOW_UNTIL_DIALOG_SHOWN) { + showIconTouchDialog(); + } else { + mProgressBar.postDelayed(mShowDialogRunnable, + ICON_TOUCH_DURATION_UNTIL_DIALOG_SHOWN); } - return true; - }); - } + } else if (event.getActionMasked() == MotionEvent.ACTION_CANCEL + || event.getActionMasked() == MotionEvent.ACTION_UP) { + mProgressBar.removeCallbacks(mShowDialogRunnable); + } + return true; + }); return containView; } - private void updateOrientation(int orientation) { - switch (orientation) { - case Configuration.ORIENTATION_LANDSCAPE: { - mIllustrationLottie = null; - break; - } - case Configuration.ORIENTATION_PORTRAIT: { - if (mShouldShowLottie) { - mIllustrationLottie = mView.findViewById(R.id.illustration_lottie); - } - break; - } - default: - Log.e(TAG, "Error unhandled configuration change"); - break; - } - } - - private void updateTitleAndDescription() { - final Activity activity = getActivity(); - final GlifLayoutHelper glifLayoutHelper = new GlifLayoutHelper(activity, - (GlifLayout) mView); - - EnrollmentProgress progressLiveData = mProgressViewModel.getProgressLiveData().getValue(); - if (progressLiveData == null || progressLiveData.getSteps() == -1) { - glifLayoutHelper.setDescriptionText( - R.string.security_settings_fingerprint_enroll_start_message); - } else { - glifLayoutHelper.setDescriptionText( - R.string.security_settings_fingerprint_enroll_repeat_message); + @Override + public void onStart() { + super.onStart(); + startEnrollment(); + updateProgress(false /* animate */, mProgressViewModel.getProgressLiveData().getValue()); + updateTitleAndDescription(); + if (true /* TODO check mRestoring */) { + startIconAnimation(); } } @@ -230,25 +291,243 @@ public class FingerprintEnrollEnrollingRfpsFragment extends Fragment { } } + private void onCancelEnrollment(@IdRes int errorMsgId) { + // showErrorDialog() will cause onWindowFocusChanged(false), set mIsCanceled to false + // before showErrorDialog() to prevent that another error dialog is triggered again. +// TODO mIsCanceled = true; +// TODO mIsOrientationChanged = false; + mEnrollingViewModel.showErrorDialog(new FingerprintEnrollEnrollingViewModel.ErrorDialogData( + mView.getContext().getString(FingerprintErrorDialog.getErrorMessage(errorMsgId)), + mView.getContext().getString(FingerprintErrorDialog.getErrorTitle(errorMsgId)), + errorMsgId + )); + cancelEnrollment(); + stopIconAnimation(); + } + + @Override + public void onStop() { + stopIconAnimation(); + removeEnrollmentObserver(); + if (!getActivity().isChangingConfigurations()) { + mProgressViewModel.cancelEnrollment(); + } + super.onStop(); + } + + private void removeEnrollmentObserver() { + mProgressViewModel.getProgressLiveData().removeObserver(mProgressObserver); + mProgressViewModel.getHelpMessageLiveData().removeObserver(mHelpMessageObserver); + mProgressViewModel.getErrorMessageLiveData().removeObserver(mErrorMessageObserver); + mProgressViewModel.getAcquireLiveData().removeObserver(mAcquireObserver); + mProgressViewModel.getPointerDownLiveData().removeObserver(mPointerDownObserver); + mProgressViewModel.getPointerUpLiveData().removeObserver(mPointerUpObserver); + } + + private void cancelEnrollment() { + removeEnrollmentObserver(); + mProgressViewModel.cancelEnrollment(); + } + + private void startEnrollment() { + mProgressViewModel.getProgressLiveData().observe(this, mProgressObserver); + mProgressViewModel.getHelpMessageLiveData().observe(this, mHelpMessageObserver); + mProgressViewModel.getErrorMessageLiveData().observe(this, mErrorMessageObserver); + mProgressViewModel.getAcquireLiveData().observe(this, mAcquireObserver); + mProgressViewModel.getPointerDownLiveData().observe(this, mPointerDownObserver); + mProgressViewModel.getPointerUpLiveData().observe(this, mPointerUpObserver); + mProgressViewModel.startEnrollment(ENROLL_ENROLL); + } + + private void onEnrollmentHelp(@NonNull EnrollmentStatusMessage helpMessage) { + final CharSequence helpStr = helpMessage.getStr(); + if (!TextUtils.isEmpty(helpStr)) { + mErrorText.removeCallbacks(mTouchAgainRunnable); + showError(helpStr); + } + } + + private void onEnrollmentError(@NonNull EnrollmentStatusMessage errorMessage) { + onCancelEnrollment(errorMessage.getMsgId()); + } + + private void onEnrollmentProgressChange(@NonNull EnrollmentProgress progress) { + updateProgress(true /* animate */, progress); + updateTitleAndDescription(); + animateFlash(); + mErrorText.removeCallbacks(mTouchAgainRunnable); + mErrorText.postDelayed(mTouchAgainRunnable, HINT_TIMEOUT_DURATION); + } + + private void updateProgress(boolean animate, @NonNull EnrollmentProgress enrollmentProgress) { + if (!mProgressViewModel.isEnrolling()) { + Log.d(TAG, "Enrollment not started yet"); + return; + } + + final int progress = getProgress(enrollmentProgress); + // Only clear the error when progress has been made. + // TODO (b/234772728) Add tests. + if (mProgressBar != null && mProgressBar.getProgress() < progress) { + clearError(); + } + + if (animate) { + animateProgress(progress); + } else { + if (mProgressBar != null) { + mProgressBar.setProgress(progress); + } + if (progress >= PROGRESS_BAR_MAX) { + mDelayedFinishRunnable.run(); + } + } + } + + private int getProgress(@NonNull EnrollmentProgress progress) { + if (progress.getSteps() == -1) { + return 0; + } + int displayProgress = Math.max(0, progress.getSteps() + 1 - progress.getRemaining()); + return PROGRESS_BAR_MAX * displayProgress / (progress.getSteps() + 1); + } + + private void showError(CharSequence error) { + mErrorText.setText(error); + if (mErrorText.getVisibility() == View.INVISIBLE) { + mErrorText.setVisibility(View.VISIBLE); + mErrorText.setTranslationY(mView.getContext().getResources().getDimensionPixelSize( + R.dimen.fingerprint_error_text_appear_distance)); + mErrorText.setAlpha(0f); + mErrorText.animate() + .alpha(1f) + .translationY(0f) + .setDuration(200) + .setInterpolator(mLinearOutSlowInInterpolator) + .start(); + } else { + mErrorText.animate().cancel(); + mErrorText.setAlpha(1f); + mErrorText.setTranslationY(0f); + } + if (isResumed() && mEnrollingViewModel.isAccessibilityEnabled()) { + mEnrollingViewModel.vibrateError(getClass().getSimpleName() + "::showError"); + } + } + + private void clearError() { + if (mErrorText.getVisibility() == View.VISIBLE) { + mErrorText.animate() + .alpha(0f) + .translationY(getResources().getDimensionPixelSize( + R.dimen.fingerprint_error_text_disappear_distance)) + .setDuration(100) + .setInterpolator(mFastOutLinearInInterpolator) + .withEndAction(() -> mErrorText.setVisibility(View.INVISIBLE)) + .start(); + } + } + + + @Override + public void onDestroy() { + // TODO stopListenOrientationEvent(); + super.onDestroy(); + } + + private void animateProgress(int progress) { + if (mProgressAnim != null) { + mProgressAnim.cancel(); + } + ObjectAnimator anim = ObjectAnimator.ofInt(mProgressBar, "progress", + mProgressBar.getProgress(), progress); + anim.addListener(mProgressAnimationListener); + anim.setInterpolator(mFastOutSlowInInterpolator); + anim.setDuration(250); + anim.start(); + mProgressAnim = anim; + } + + private final Runnable mTouchAgainRunnable = new Runnable() { + @Override + public void run() { + // Use mView to getString to prevent activity is missing during rotation + showError(mView.getContext().getString( + R.string.security_settings_fingerprint_enroll_lift_touch_again)); + } + }; + +// private void updateOrientation(@NonNull GlifLayout glifLayout, int orientation) { +// switch (orientation) { +// case Configuration.ORIENTATION_LANDSCAPE: { +// mIllustrationLottie = null; +// break; +// } +// case Configuration.ORIENTATION_PORTRAIT: { +// if (shouldShowLottie()) { +// mIllustrationLottie = glifLayout.findViewById(R.id.illustration_lottie); +// } +// break; +// } +// default: +// Log.e(TAG, "Error unhandled configuration change"); +// break; +// } +// } + + private void animateFlash() { + if (mIconBackgroundBlinksDrawable != null) { + mIconBackgroundBlinksDrawable.start(); + } + } + + private void updateTitleAndDescription() { + final EnrollmentProgress progressLiveData = + mProgressViewModel.getProgressLiveData().getValue(); + new GlifLayoutHelper(getActivity(), mView).setDescriptionText(mView.getContext().getString( + progressLiveData == null || progressLiveData.getSteps() == -1 + ? R.string.security_settings_fingerprint_enroll_start_message + : R.string.security_settings_fingerprint_enroll_repeat_message)); + } + private void showIconTouchDialog() { mIconTouchCount = 0; //TODO EnrollingActivity should observe live data and add dialog fragment mEnrollingViewModel.onIconTouchDialogShow(); } - private boolean shouldShowLottie() { - DisplayDensityUtils displayDensity = new DisplayDensityUtils(getContext()); - int currentDensityIndex = displayDensity.getCurrentIndexForDefaultDisplay(); - final int currentDensity = displayDensity.getDefaultDisplayDensityValues() - [currentDensityIndex]; - final int defaultDensity = displayDensity.getDefaultDensityForDefaultDisplay(); - return defaultDensity == currentDensity; - } + private final Runnable mShowDialogRunnable = () -> showIconTouchDialog(); - private final Runnable mShowDialogRunnable = new Runnable() { + private final Animator.AnimatorListener mProgressAnimationListener = + new Animator.AnimatorListener() { + + @Override + public void onAnimationStart(Animator animation) { + startIconAnimation(); + } + + @Override + public void onAnimationRepeat(Animator animation) { } + + @Override + public void onAnimationEnd(Animator animation) { + stopIconAnimation(); + + if (mProgressBar.getProgress() >= PROGRESS_BAR_MAX) { + mProgressBar.postDelayed(mDelayedFinishRunnable, 250L); + } + } + + @Override + public void onAnimationCancel(Animator animation) { } + }; + + // Give the user a chance to see progress completed before jumping to the next stage. + private final Runnable mDelayedFinishRunnable = new Runnable() { @Override public void run() { - showIconTouchDialog(); + mEnrollingViewModel.onSkipButtonClick(); + /* TODO launchFinish(); */ } }; diff --git a/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollEnrollingSfpsFragment.java b/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollEnrollingSfpsFragment.java index ddeb465216e..57b86654da5 100644 --- a/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollEnrollingSfpsFragment.java +++ b/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollEnrollingSfpsFragment.java @@ -16,15 +16,20 @@ package com.android.settings.biometrics2.ui.view; +import static android.hardware.fingerprint.FingerprintManager.ENROLL_ENROLL; + +import android.animation.Animator; +import android.animation.ObjectAnimator; import android.annotation.RawRes; import android.app.Activity; import android.content.Context; +import android.content.res.ColorStateList; import android.content.res.Configuration; import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; import android.graphics.drawable.Animatable2; import android.graphics.drawable.AnimatedVectorDrawable; import android.graphics.drawable.Drawable; -import android.graphics.drawable.LayerDrawable; import android.os.Bundle; import android.util.Log; import android.view.LayoutInflater; @@ -39,11 +44,14 @@ import android.widget.TextView; import androidx.annotation.NonNull; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentActivity; +import androidx.lifecycle.Observer; import androidx.lifecycle.ViewModelProvider; +import androidx.transition.Transition; +import androidx.transition.TransitionSet; import com.android.settings.R; -import com.android.settings.biometrics.BiometricUtils; 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.FingerprintEnrollProgressViewModel; @@ -51,6 +59,8 @@ import com.android.settingslib.display.DisplayDensityUtils; import com.airbnb.lottie.LottieAnimationView; import com.airbnb.lottie.LottieCompositionFactory; +import com.airbnb.lottie.LottieProperty; +import com.airbnb.lottie.model.KeyPath; import com.google.android.setupcompat.template.FooterBarMixin; import com.google.android.setupcompat.template.FooterButton; import com.google.android.setupdesign.GlifLayout; @@ -64,9 +74,9 @@ public class FingerprintEnrollEnrollingSfpsFragment extends Fragment { private static final String TAG = FingerprintEnrollEnrollingSfpsFragment.class.getSimpleName(); + private static final int PROGRESS_BAR_MAX = 10000; private static final long ICON_TOUCH_DURATION_UNTIL_DIALOG_SHOWN = 500; private static final int ICON_TOUCH_COUNT_SHOW_UNTIL_DIALOG_SHOWN = 3; - private static final int HINT_TIMEOUT_DURATION = 2500; private static final int STAGE_UNKNOWN = -1; private static final int SFPS_STAGE_NO_ANIMATION = 0; @@ -84,8 +94,9 @@ public class FingerprintEnrollEnrollingSfpsFragment extends Fragment { private Interpolator mFastOutLinearInInterpolator; private boolean mAnimationCancelled; - private View mView; + private GlifLayout mView; private ProgressBar mProgressBar; + private ObjectAnimator mProgressAnim; private TextView mErrorText; private FooterBarMixin mFooterBarMixin; private AnimatedVectorDrawable mIconAnimationDrawable; @@ -103,6 +114,24 @@ public class FingerprintEnrollEnrollingSfpsFragment extends Fragment { private final View.OnClickListener mOnSkipClickListener = (v) -> mEnrollingViewModel.onSkipButtonClick(); + private final Observer mProgressObserver = progress -> { + // TODO + }; + private final Observer mHelpMessageObserver = helpMessage -> { + // TODO + }; + private final Observer mErrorMessageObserver = errorMessage -> { + // TODO + }; + private final Observer mAcquireObserver = isAcquiredGood -> { + // TODO + }; + private final Observer mPointerDownObserver = sensorId -> { + // TODO + }; + private final Observer mPointerUpObserver = sensorId -> { + // TODO + }; private int mIconTouchCount; @@ -114,41 +143,73 @@ public class FingerprintEnrollEnrollingSfpsFragment extends Fragment { mRotationViewModel = provider.get(DeviceRotationViewModel.class); mProgressViewModel = provider.get(FingerprintEnrollProgressViewModel.class); super.onAttach(context); + final TransitionSet transitionSet = (TransitionSet) getSharedElementEnterTransition(); + if (transitionSet != null) { + transitionSet.addListener(new Transition.TransitionListener() { + @Override + public void onTransitionStart(@NonNull Transition transition) { + + } + + @Override + public void onTransitionEnd(@NonNull Transition transition) { + transition.removeListener(this); + mAnimationCancelled = false; + startIconAnimation(); + } + + @Override + public void onTransitionCancel(@NonNull Transition transition) { + + } + + @Override + public void onTransitionPause(@NonNull Transition transition) { + + } + + @Override + public void onTransitionResume(@NonNull Transition transition) { + + } + }); + } } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + mEnrollingViewModel.restoreSavedState(savedInstanceState); mIsAccessibilityEnabled = mEnrollingViewModel.isAccessibilityEnabled(); } + @Override + public void onSaveInstanceState(@NonNull Bundle outState) { + mEnrollingViewModel.onSaveInstanceState(outState); + super.onSaveInstanceState(outState); + } + @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { mView = initSfpsLayout(inflater, container); - final Configuration config = getActivity().getResources().getConfiguration(); - maybeHideSfpsText(config); return mView; } - private View initSfpsLayout(LayoutInflater inflater, ViewGroup container) { - final View containView = inflater.inflate(R.layout.sfps_enroll_enrolling, container, false); + private GlifLayout initSfpsLayout(LayoutInflater inflater, ViewGroup container) { + final GlifLayout containView = (GlifLayout) inflater.inflate(R.layout.sfps_enroll_enrolling, + container, false); final Activity activity = getActivity(); - final GlifLayoutHelper glifLayoutHelper = new GlifLayoutHelper(activity, - (GlifLayout) containView); - glifLayoutHelper.setDescriptionText( - R.string.security_settings_fingerprint_enroll_start_message); - updateTitleAndDescription(); - mShouldShowLottie = shouldShowLottie(); - boolean isLandscape = BiometricUtils.isReverseLandscape(activity) - || BiometricUtils.isLandscape(activity); - updateOrientation((isLandscape - ? Configuration.ORIENTATION_LANDSCAPE : Configuration.ORIENTATION_PORTRAIT)); + new GlifLayoutHelper(activity, containView).setDescriptionText( + getString(R.string.security_settings_fingerprint_enroll_start_message)); + + mShouldShowLottie = shouldShowLottie(); // Move shouldShowLottie into updateOrientation()? + mIllustrationLottie = containView.findViewById(R.id.illustration_lottie); mErrorText = containView.findViewById(R.id.error_text); mProgressBar = containView.findViewById(R.id.fingerprint_progress_bar); - mFooterBarMixin = ((GlifLayout) containView).getMixin(FooterBarMixin.class); + mFooterBarMixin = containView.getMixin(FooterBarMixin.class); mFooterBarMixin.setSecondaryButton( new FooterButton.Builder(activity) .setText(R.string.security_settings_fingerprint_enroll_enrolling_skip) @@ -158,16 +219,6 @@ public class FingerprintEnrollEnrollingSfpsFragment extends Fragment { .build() ); - final LayerDrawable fingerprintDrawable = mProgressBar != null - ? (LayerDrawable) mProgressBar.getBackground() : null; - if (fingerprintDrawable != null) { - mIconAnimationDrawable = (AnimatedVectorDrawable) - fingerprintDrawable.findDrawableByLayerId(R.id.fingerprint_animation); - mIconBackgroundBlinksDrawable = (AnimatedVectorDrawable) - fingerprintDrawable.findDrawableByLayerId(R.id.fingerprint_background); - mIconAnimationDrawable.registerAnimationCallback(mIconAnimationCallback); - } - mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator( activity, android.R.interpolator.fast_out_slow_in); mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator( @@ -175,37 +226,161 @@ public class FingerprintEnrollEnrollingSfpsFragment extends Fragment { mFastOutLinearInInterpolator = AnimationUtils.loadInterpolator( activity, android.R.interpolator.fast_out_linear_in); - if (mProgressBar != null) { - mProgressBar.setProgressBackgroundTintMode(PorterDuff.Mode.SRC); - mProgressBar.setOnTouchListener((v, event) -> { - if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { - mIconTouchCount++; - if (mIconTouchCount == ICON_TOUCH_COUNT_SHOW_UNTIL_DIALOG_SHOWN) { - showIconTouchDialog(); - } else { - mProgressBar.postDelayed(mShowDialogRunnable, - ICON_TOUCH_DURATION_UNTIL_DIALOG_SHOWN); - } - } else if (event.getActionMasked() == MotionEvent.ACTION_CANCEL - || event.getActionMasked() == MotionEvent.ACTION_UP) { - mProgressBar.removeCallbacks(mShowDialogRunnable); + mProgressBar.setProgressBackgroundTintMode(PorterDuff.Mode.SRC); + mProgressBar.setOnTouchListener((v, event) -> { + if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { + mIconTouchCount++; + if (mIconTouchCount == ICON_TOUCH_COUNT_SHOW_UNTIL_DIALOG_SHOWN) { + showIconTouchDialog(); + } else { + mProgressBar.postDelayed(mShowDialogRunnable, + ICON_TOUCH_DURATION_UNTIL_DIALOG_SHOWN); } - return true; - }); - } + } else if (event.getActionMasked() == MotionEvent.ACTION_CANCEL + || event.getActionMasked() == MotionEvent.ACTION_UP) { + mProgressBar.removeCallbacks(mShowDialogRunnable); + } + return true; + }); + maybeHideSfpsText(activity.getResources().getConfiguration()); return containView; } - private void updateTitleAndDescription() { + @Override + public void onStart() { + super.onStart(); + startEnrollment(); + updateProgress(false /* animate */); + updateTitleAndDescription(new GlifLayoutHelper(getActivity(), mView)); + if (true /* TODO mRestoring */) { + startIconAnimation(); + } + } - final Activity activity = getActivity(); - final GlifLayoutHelper glifLayoutHelper = new GlifLayoutHelper(activity, - (GlifLayout) mView); + @Override + public void onStop() { + stopIconAnimation(); + mProgressViewModel.getProgressLiveData().removeObserver(mProgressObserver); + mProgressViewModel.getHelpMessageLiveData().removeObserver(mHelpMessageObserver); + mProgressViewModel.getErrorMessageLiveData().removeObserver(mErrorMessageObserver); + mProgressViewModel.getAcquireLiveData().removeObserver(mAcquireObserver); + mProgressViewModel.getPointerDownLiveData().removeObserver(mPointerDownObserver); + mProgressViewModel.getPointerUpLiveData().removeObserver(mPointerUpObserver); + if (!getActivity().isChangingConfigurations()) { + mProgressViewModel.cancelEnrollment(); + } + super.onStop(); + } + private void startEnrollment() { + mProgressViewModel.getProgressLiveData().observe(this, mProgressObserver); + mProgressViewModel.getHelpMessageLiveData().observe(this, mHelpMessageObserver); + mProgressViewModel.getErrorMessageLiveData().observe(this, mErrorMessageObserver); + mProgressViewModel.getAcquireLiveData().observe(this, mAcquireObserver); + mProgressViewModel.getPointerDownLiveData().observe(this, mPointerDownObserver); + mProgressViewModel.getPointerUpLiveData().observe(this, mPointerUpObserver); + mProgressViewModel.startEnrollment(ENROLL_ENROLL); + } + + private void updateProgress(boolean animate) { + if (!mProgressViewModel.isEnrolling()) { + Log.d(TAG, "Enrollment not started yet"); + return; + } + + final EnrollmentProgress enrollmentProgress = + mProgressViewModel.getProgressLiveData().getValue(); + final int progress = getProgress(enrollmentProgress); + // Only clear the error when progress has been made. + // TODO (b/234772728) Add tests. + if (mProgressBar != null && mProgressBar.getProgress() < progress) { + clearError(); + } + + if (animate) { + animateProgress(progress); + } else { + if (mProgressBar != null) { + mProgressBar.setProgress(progress); + } + if (progress >= PROGRESS_BAR_MAX) { + mDelayedFinishRunnable.run(); + } + } + } + + private int getProgress(@NonNull EnrollmentProgress progress) { + if (progress.getSteps() == -1) { + return 0; + } + int displayProgress = Math.max(0, progress.getSteps() + 1 - progress.getRemaining()); + return PROGRESS_BAR_MAX * displayProgress / (progress.getSteps() + 1); + } + + private void clearError() { + applySfpsErrorDynamicColors(false); + } + + /** + * Applies dynamic colors corresponding to showing or clearing errors on the progress bar + * and finger lottie for SFPS + */ + private void applySfpsErrorDynamicColors(boolean isError) { + applyProgressBarDynamicColor(isError); + if (mIllustrationLottie != null) { + applyLottieDynamicColor(isError); + } + } + + private void applyProgressBarDynamicColor(boolean isError) { + final Context context = getActivity().getApplicationContext(); + int error_color = context.getColor(R.color.sfps_enrollment_progress_bar_error_color); + int progress_bar_fill_color = context.getColor( + R.color.sfps_enrollment_progress_bar_fill_color); + ColorStateList fillColor = ColorStateList.valueOf( + isError ? error_color : progress_bar_fill_color); + mProgressBar.setProgressTintList(fillColor); + mProgressBar.setProgressTintMode(PorterDuff.Mode.SRC); + mProgressBar.invalidate(); + } + + private void applyLottieDynamicColor(boolean isError) { + final Context context = getActivity().getApplicationContext(); + int error_color = context.getColor(R.color.sfps_enrollment_fp_error_color); + int fp_captured_color = context.getColor(R.color.sfps_enrollment_fp_captured_color); + int color = isError ? error_color : fp_captured_color; + mIllustrationLottie.addValueCallback( + new KeyPath(".blue100", "**"), + LottieProperty.COLOR_FILTER, + frameInfo -> new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_ATOP) + ); + mIllustrationLottie.invalidate(); + } + + @Override + public void onDestroy() { + // TODO stopListenOrientationEvent(); + super.onDestroy(); + } + + private void animateProgress(int progress) { + if (mProgressAnim != null) { + mProgressAnim.cancel(); + } + ObjectAnimator anim = ObjectAnimator.ofInt(mProgressBar, "progress", + mProgressBar.getProgress(), progress); + anim.addListener(mProgressAnimationListener); + anim.setInterpolator(mFastOutSlowInInterpolator); + anim.setDuration(250); + anim.start(); + mProgressAnim = anim; + } + + private void updateTitleAndDescription(@NonNull GlifLayoutHelper glifLayoutHelper) { if (mIsAccessibilityEnabled) { mEnrollingViewModel.clearTalkback(); - ((GlifLayout) mView).getDescriptionTextView().setAccessibilityLiveRegion( + glifLayoutHelper.getGlifLayout().getDescriptionTextView().setAccessibilityLiveRegion( View.ACCESSIBILITY_LIVE_REGION_POLITE); } switch (getCurrentSfpsStage()) { @@ -273,12 +448,13 @@ public class FingerprintEnrollEnrollingSfpsFragment extends Fragment { // announce a different string for a11y upon entering the page. glifLayoutHelper.setHeaderText( R.string.security_settings_sfps_enroll_find_sensor_title); - glifLayoutHelper.setDescriptionText( - R.string.security_settings_sfps_enroll_start_message); + glifLayoutHelper.setDescriptionText(getString( + R.string.security_settings_sfps_enroll_start_message)); final CharSequence description = getString( R.string.security_settings_sfps_enroll_find_sensor_message); - ((GlifLayout) mView).getHeaderTextView().setContentDescription(description); - activity.setTitle(description); + glifLayoutHelper.getGlifLayout().getHeaderTextView().setContentDescription( + description); + glifLayoutHelper.getActivity().setTitle(description); break; } @@ -327,8 +503,8 @@ public class FingerprintEnrollEnrollingSfpsFragment extends Fragment { } private int getStageThresholdSteps(int index) { - - EnrollmentProgress progressLiveData = mProgressViewModel.getProgressLiveData().getValue(); + final EnrollmentProgress progressLiveData = + mProgressViewModel.getProgressLiveData().getValue(); if (progressLiveData == null || progressLiveData.getSteps() == -1) { Log.w(TAG, "getStageThresholdSteps: Enrollment not started yet"); @@ -338,7 +514,7 @@ public class FingerprintEnrollEnrollingSfpsFragment extends Fragment { * mEnrollingViewModel.getEnrollStageThreshold(index)); } - private void updateOrientation(int orientation) { + private void updateOrientation() { mIllustrationLottie = mView.findViewById(R.id.illustration_lottie); } @@ -372,9 +548,7 @@ public class FingerprintEnrollEnrollingSfpsFragment extends Fragment { } private void configureEnrollmentStage(CharSequence description, @RawRes int lottie) { - final GlifLayoutHelper glifLayoutHelper = new GlifLayoutHelper(getActivity(), - (GlifLayout) mView); - glifLayoutHelper.setDescriptionText(description); + new GlifLayoutHelper(getActivity(), mView).setDescriptionText(description); LottieCompositionFactory.fromRawRes(getActivity(), lottie) .addListener((c) -> { mIllustrationLottie.setComposition(c); @@ -390,6 +564,38 @@ public class FingerprintEnrollEnrollingSfpsFragment extends Fragment { } }; + private final Animator.AnimatorListener mProgressAnimationListener = + new Animator.AnimatorListener() { + + @Override + public void onAnimationStart(Animator animation) { + startIconAnimation(); + } + + @Override + public void onAnimationRepeat(Animator animation) { } + + @Override + public void onAnimationEnd(Animator animation) { + stopIconAnimation(); + + if (mProgressBar.getProgress() >= PROGRESS_BAR_MAX) { + mProgressBar.postDelayed(mDelayedFinishRunnable, 250L); + } + } + + @Override + public void onAnimationCancel(Animator animation) { } + }; + + // Give the user a chance to see progress completed before jumping to the next stage. + private final Runnable mDelayedFinishRunnable = new Runnable() { + @Override + public void run() { + /* TODO launchFinish(); */ + } + }; + private final Animatable2.AnimationCallback mIconAnimationCallback = new Animatable2.AnimationCallback() { @Override diff --git a/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollEnrollingUdfpsFragment.java b/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollEnrollingUdfpsFragment.java index 89b061f695f..18a7c259c83 100644 --- a/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollEnrollingUdfpsFragment.java +++ b/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollEnrollingUdfpsFragment.java @@ -16,37 +16,39 @@ package com.android.settings.biometrics2.ui.view; +import static android.hardware.fingerprint.FingerprintManager.ENROLL_ENROLL; + import android.annotation.RawRes; import android.app.Activity; import android.content.Context; import android.content.res.Configuration; -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.os.Bundle; import android.text.TextUtils; import android.util.Log; import android.view.LayoutInflater; -import android.view.MotionEvent; import android.view.Surface; import android.view.View; import android.view.ViewGroup; import android.view.animation.AnimationUtils; import android.view.animation.Interpolator; import android.widget.LinearLayout; -import android.widget.ProgressBar; import android.widget.TextView; import androidx.annotation.NonNull; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentActivity; +import androidx.lifecycle.Observer; import androidx.lifecycle.ViewModelProvider; +import androidx.transition.Transition; +import androidx.transition.TransitionSet; import com.android.settings.R; import com.android.settings.biometrics.BiometricUtils; 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.FingerprintEnrollProgressViewModel; @@ -67,6 +69,7 @@ public class FingerprintEnrollEnrollingUdfpsFragment extends Fragment { private static final String TAG = FingerprintEnrollEnrollingUdfpsFragment.class.getSimpleName(); + private static final int PROGRESS_BAR_MAX = 10000; private static final long ICON_TOUCH_DURATION_UNTIL_DIALOG_SHOWN = 500; private static final int ICON_TOUCH_COUNT_SHOW_UNTIL_DIALOG_SHOWN = 3; private static final int HINT_TIMEOUT_DURATION = 2500; @@ -94,8 +97,7 @@ public class FingerprintEnrollEnrollingUdfpsFragment extends Fragment { private boolean mHaveShownUdfpsCenterLottie; private boolean mHaveShownUdfpsGuideLottie; - private View mView; - private ProgressBar mProgressBar; + private GlifLayout mView; private TextView mErrorText; private FooterBarMixin mFooterBarMixin; private AnimatedVectorDrawable mIconAnimationDrawable; @@ -106,6 +108,24 @@ public class FingerprintEnrollEnrollingUdfpsFragment extends Fragment { private final View.OnClickListener mOnSkipClickListener = (v) -> mEnrollingViewModel.onSkipButtonClick(); + private final Observer mProgressObserver = progress -> { + // TODO + }; + private final Observer mHelpMessageObserver = helpMessage -> { + // TODO + }; + private final Observer mErrorMessageObserver = errorMessage -> { + // TODO + }; + private final Observer mAcquireObserver = isAcquiredGood -> { + // TODO + }; + private final Observer mPointerDownObserver = sensorId -> { + // TODO + }; + private final Observer mPointerUpObserver = sensorId -> { + // TODO + }; private int mIconTouchCount; @@ -117,14 +137,53 @@ public class FingerprintEnrollEnrollingUdfpsFragment extends Fragment { mRotationViewModel = provider.get(DeviceRotationViewModel.class); mProgressViewModel = provider.get(FingerprintEnrollProgressViewModel.class); super.onAttach(context); + final TransitionSet transitionSet = (TransitionSet) getSharedElementEnterTransition(); + if (transitionSet != null) { + transitionSet.addListener(new Transition.TransitionListener() { + @Override + public void onTransitionStart(@NonNull Transition transition) { + + } + + @Override + public void onTransitionEnd(@NonNull Transition transition) { + transition.removeListener(this); + startEnrollment(); + mAnimationCancelled = false; + startIconAnimation(); + } + + @Override + public void onTransitionCancel(@NonNull Transition transition) { + + } + + @Override + public void onTransitionPause(@NonNull Transition transition) { + + } + + @Override + public void onTransitionResume(@NonNull Transition transition) { + + } + }); + } } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + mEnrollingViewModel.restoreSavedState(savedInstanceState); mIsAccessibilityEnabled = mEnrollingViewModel.isAccessibilityEnabled(); } + @Override + public void onSaveInstanceState(@NonNull Bundle outState) { + mEnrollingViewModel.onSaveInstanceState(outState); + super.onSaveInstanceState(outState); + } + @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { @@ -132,46 +191,44 @@ public class FingerprintEnrollEnrollingUdfpsFragment extends Fragment { return mView; } - private View initUdfpsLayout(LayoutInflater inflater, ViewGroup container) { - final View containView = inflater.inflate(R.layout.udfps_enroll_enrolling, container, - false); + private GlifLayout initUdfpsLayout(LayoutInflater inflater, ViewGroup container) { + final GlifLayout containView = (GlifLayout) inflater.inflate( + R.layout.udfps_enroll_enrolling, container, false); - final Activity activity = getActivity(); - final GlifLayoutHelper glifLayoutHelper = new GlifLayoutHelper(activity, - (GlifLayout) containView); final int rotation = mRotationViewModel.getLiveData().getValue(); - final boolean isLayoutRtl = (TextUtils.getLayoutDirectionFromLocale( - Locale.getDefault()) == View.LAYOUT_DIRECTION_RTL); - - - //TODO implement b/20653554 if (rotation == Surface.ROTATION_90) { + final boolean isLayoutRtl = (TextUtils.getLayoutDirectionFromLocale( + Locale.getDefault()) == View.LAYOUT_DIRECTION_RTL); final LinearLayout layoutContainer = containView.findViewById( R.id.layout_container); + layoutContainer.setPaddingRelative( + (int) getResources().getDimension(R.dimen.rotation_90_enroll_padding_start), + 0, + isLayoutRtl ? 0 : (int) getResources().getDimension( + R.dimen.rotation_90_enroll_padding_end), + 0); final LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams( LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT); lp.setMarginEnd((int) getResources().getDimension( R.dimen.rotation_90_enroll_margin_end)); - layoutContainer.setPaddingRelative((int) getResources().getDimension( - R.dimen.rotation_90_enroll_padding_start), 0, isLayoutRtl - ? 0 : (int) getResources().getDimension( - R.dimen.rotation_90_enroll_padding_end), 0); layoutContainer.setLayoutParams(lp); containView.setLayoutParams(lp); } - glifLayoutHelper.setDescriptionText(R.string.security_settings_udfps_enroll_start_message); + + final Activity activity = getActivity(); + new GlifLayoutHelper(activity, containView).setDescriptionText( + getString(R.string.security_settings_udfps_enroll_start_message)); updateTitleAndDescription(); mShouldShowLottie = shouldShowLottie(); boolean isLandscape = BiometricUtils.isReverseLandscape(activity) || BiometricUtils.isLandscape(activity); - updateOrientation((isLandscape + updateOrientation(containView, (isLandscape ? Configuration.ORIENTATION_LANDSCAPE : Configuration.ORIENTATION_PORTRAIT)); mErrorText = containView.findViewById(R.id.error_text); - mProgressBar = containView.findViewById(R.id.fingerprint_progress_bar); - mFooterBarMixin = ((GlifLayout) containView).getMixin(FooterBarMixin.class); + mFooterBarMixin = containView.getMixin(FooterBarMixin.class); mFooterBarMixin.setSecondaryButton( new FooterButton.Builder(activity) .setText(R.string.security_settings_fingerprint_enroll_enrolling_skip) @@ -181,16 +238,6 @@ public class FingerprintEnrollEnrollingUdfpsFragment extends Fragment { .build() ); - final LayerDrawable fingerprintDrawable = mProgressBar != null - ? (LayerDrawable) mProgressBar.getBackground() : null; - if (fingerprintDrawable != null) { - mIconAnimationDrawable = (AnimatedVectorDrawable) - fingerprintDrawable.findDrawableByLayerId(R.id.fingerprint_animation); - mIconBackgroundBlinksDrawable = (AnimatedVectorDrawable) - fingerprintDrawable.findDrawableByLayerId(R.id.fingerprint_background); - mIconAnimationDrawable.registerAnimationCallback(mIconAnimationCallback); - } - mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator( activity, android.R.interpolator.fast_out_slow_in); mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator( @@ -198,41 +245,100 @@ public class FingerprintEnrollEnrollingUdfpsFragment extends Fragment { mFastOutLinearInInterpolator = AnimationUtils.loadInterpolator( activity, android.R.interpolator.fast_out_linear_in); - if (mProgressBar != null) { - mProgressBar.setProgressBackgroundTintMode(PorterDuff.Mode.SRC); - mProgressBar.setOnTouchListener((v, event) -> { - if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { - mIconTouchCount++; - if (mIconTouchCount == ICON_TOUCH_COUNT_SHOW_UNTIL_DIALOG_SHOWN) { - showIconTouchDialog(); - } else { - mProgressBar.postDelayed(mShowDialogRunnable, - ICON_TOUCH_DURATION_UNTIL_DIALOG_SHOWN); - } - } else if (event.getActionMasked() == MotionEvent.ACTION_CANCEL - || event.getActionMasked() == MotionEvent.ACTION_UP) { - mProgressBar.removeCallbacks(mShowDialogRunnable); - } - return true; - }); + return containView; + } + + @Override + public void onStart() { + super.onStart(); + if (true /* TODO mRestoring && !mIsCanceled */) { + startEnrollment(); + } + updateProgress(false /* animate */, mProgressViewModel.getProgressLiveData().getValue()); + updateTitleAndDescription(); + if (true /* TODO mRestoring */) { + startIconAnimation(); + } + } + + @Override + public void onStop() { + stopIconAnimation(); + removeEnrollmentObserver(); + if (!getActivity().isChangingConfigurations()) { + mProgressViewModel.cancelEnrollment(); + } + super.onStop(); + } + + private void removeEnrollmentObserver() { + mProgressViewModel.getProgressLiveData().removeObserver(mProgressObserver); + mProgressViewModel.getHelpMessageLiveData().removeObserver(mHelpMessageObserver); + mProgressViewModel.getErrorMessageLiveData().removeObserver(mErrorMessageObserver); + mProgressViewModel.getAcquireLiveData().removeObserver(mAcquireObserver); + mProgressViewModel.getPointerDownLiveData().removeObserver(mPointerDownObserver); + mProgressViewModel.getPointerUpLiveData().removeObserver(mPointerUpObserver); + } + + private void startEnrollment() { + mProgressViewModel.getProgressLiveData().observe(this, mProgressObserver); + mProgressViewModel.getHelpMessageLiveData().observe(this, mHelpMessageObserver); + mProgressViewModel.getErrorMessageLiveData().observe(this, mErrorMessageObserver); + mProgressViewModel.getAcquireLiveData().observe(this, mAcquireObserver); + mProgressViewModel.getPointerDownLiveData().observe(this, mPointerDownObserver); + mProgressViewModel.getPointerUpLiveData().observe(this, mPointerUpObserver); + mProgressViewModel.startEnrollment(ENROLL_ENROLL); + } + + private void updateProgress(boolean animate, @NonNull EnrollmentProgress enrollmentProgress) { + if (!mProgressViewModel.isEnrolling()) { + Log.d(TAG, "Enrollment not started yet"); + return; } - return containView; + final int progress = getProgress(enrollmentProgress); + + if (animate) { + animateProgress(progress); + } else if (progress >= PROGRESS_BAR_MAX) { + mDelayedFinishRunnable.run(); + } + } + + private int getProgress(@NonNull EnrollmentProgress progress) { + if (progress.getSteps() == -1) { + return 0; + } + int displayProgress = Math.max(0, progress.getSteps() + 1 - progress.getRemaining()); + return PROGRESS_BAR_MAX * displayProgress / (progress.getSteps() + 1); + } + + @Override + public void onDestroy() { + // TODO stopListenOrientationEvent(); + super.onDestroy(); + } + + private void animateProgress(int progress) { + // UDFPS animations are owned by SystemUI + if (progress >= PROGRESS_BAR_MAX) { + // Wait for any animations in SysUI to finish, then proceed to next page + getActivity().getMainThreadHandler().postDelayed(mDelayedFinishRunnable, 400L); + } } private void updateTitleAndDescription() { final Activity activity = getActivity(); - final GlifLayoutHelper glifLayoutHelper = new GlifLayoutHelper(activity, - (GlifLayout) mView); + final GlifLayoutHelper glifLayoutHelper = new GlifLayoutHelper(activity, mView); switch (getCurrentStage()) { case STAGE_CENTER: glifLayoutHelper.setHeaderText( R.string.security_settings_fingerprint_enroll_repeat_title); if (mIsAccessibilityEnabled || mIllustrationLottie == null) { - glifLayoutHelper.setDescriptionText( - R.string.security_settings_udfps_enroll_start_message); + glifLayoutHelper.setDescriptionText(getString( + R.string.security_settings_udfps_enroll_start_message)); } else if (!mHaveShownUdfpsCenterLottie && mIllustrationLottie != null) { mHaveShownUdfpsCenterLottie = true; // Note: Update string reference when differentiate in between udfps & sfps @@ -247,8 +353,8 @@ public class FingerprintEnrollEnrollingUdfpsFragment extends Fragment { glifLayoutHelper.setHeaderText( R.string.security_settings_fingerprint_enroll_repeat_title); if (mIsAccessibilityEnabled || mIllustrationLottie == null) { - glifLayoutHelper.setDescriptionText( - R.string.security_settings_udfps_enroll_repeat_a11y_message); + glifLayoutHelper.setDescriptionText(getString( + R.string.security_settings_udfps_enroll_repeat_a11y_message)); } else if (!mHaveShownUdfpsGuideLottie && mIllustrationLottie != null) { mHaveShownUdfpsGuideLottie = true; mIllustrationLottie.setContentDescription( @@ -280,11 +386,11 @@ public class FingerprintEnrollEnrollingUdfpsFragment extends Fragment { configureEnrollmentStage("", R.raw.udfps_left_edge_hint_lottie); } else if (mIllustrationLottie == null) { if (isStageHalfCompleted()) { - glifLayoutHelper.setDescriptionText( - R.string.security_settings_fingerprint_enroll_repeat_message); + glifLayoutHelper.setDescriptionText(getString( + R.string.security_settings_fingerprint_enroll_repeat_message)); } else { - glifLayoutHelper.setDescriptionText( - R.string.security_settings_udfps_enroll_edge_message); + glifLayoutHelper.setDescriptionText(getString( + R.string.security_settings_udfps_enroll_edge_message)); } } break; @@ -300,11 +406,11 @@ public class FingerprintEnrollEnrollingUdfpsFragment extends Fragment { } else if (mIllustrationLottie == null) { if (isStageHalfCompleted()) { - glifLayoutHelper.setDescriptionText( - R.string.security_settings_fingerprint_enroll_repeat_message); + glifLayoutHelper.setDescriptionText(getString( + R.string.security_settings_fingerprint_enroll_repeat_message)); } else { - glifLayoutHelper.setDescriptionText( - R.string.security_settings_udfps_enroll_edge_message); + glifLayoutHelper.setDescriptionText(getString( + R.string.security_settings_udfps_enroll_edge_message)); } } break; @@ -317,11 +423,11 @@ public class FingerprintEnrollEnrollingUdfpsFragment extends Fragment { // announce a different string for a11y upon entering the page. glifLayoutHelper.setHeaderText( R.string.security_settings_fingerprint_enroll_udfps_title); - glifLayoutHelper.setDescriptionText( - R.string.security_settings_udfps_enroll_start_message); + glifLayoutHelper.setDescriptionText(getString( + R.string.security_settings_udfps_enroll_start_message)); final CharSequence description = getString( R.string.security_settings_udfps_enroll_a11y); - ((GlifLayout) mView).getHeaderTextView().setContentDescription(description); + mView.getHeaderTextView().setContentDescription(description); activity.setTitle(description); break; @@ -337,7 +443,7 @@ public class FingerprintEnrollEnrollingUdfpsFragment extends Fragment { return defaultDensity == currentDensity; } - private void updateOrientation(int orientation) { + private void updateOrientation(@NonNull GlifLayout glifLayout, int orientation) { switch (orientation) { case Configuration.ORIENTATION_LANDSCAPE: { mIllustrationLottie = null; @@ -345,7 +451,7 @@ public class FingerprintEnrollEnrollingUdfpsFragment extends Fragment { } case Configuration.ORIENTATION_PORTRAIT: { if (mShouldShowLottie) { - mIllustrationLottie = mView.findViewById(R.id.illustration_lottie); + mIllustrationLottie = glifLayout.findViewById(R.id.illustration_lottie); } break; } @@ -390,7 +496,6 @@ public class FingerprintEnrollEnrollingUdfpsFragment extends Fragment { } private boolean isStageHalfCompleted() { - EnrollmentProgress progressLiveData = mProgressViewModel.getProgressLiveData().getValue(); if (progressLiveData == null || progressLiveData.getSteps() == -1) { return false; @@ -431,9 +536,7 @@ public class FingerprintEnrollEnrollingUdfpsFragment extends Fragment { } private void configureEnrollmentStage(CharSequence description, @RawRes int lottie) { - final GlifLayoutHelper glifLayoutHelper = new GlifLayoutHelper(getActivity(), - (GlifLayout) mView); - glifLayoutHelper.setDescriptionText(description); + new GlifLayoutHelper(getActivity(), mView).setDescriptionText(description); LottieCompositionFactory.fromRawRes(getActivity(), lottie) .addListener((c) -> { mIllustrationLottie.setComposition(c); @@ -449,6 +552,14 @@ public class FingerprintEnrollEnrollingUdfpsFragment extends Fragment { } }; + // Give the user a chance to see progress completed before jumping to the next stage. + private final Runnable mDelayedFinishRunnable = new Runnable() { + @Override + public void run() { + /* TODO launchFinish(); */ + } + }; + private final Animatable2.AnimationCallback mIconAnimationCallback = new Animatable2.AnimationCallback() { @Override @@ -458,13 +569,14 @@ public class FingerprintEnrollEnrollingUdfpsFragment extends Fragment { } // Start animation after it has ended. + /* TODO check mProgressBar? mProgressBar.post(new Runnable() { @Override public void run() { startIconAnimation(); } }); + */ } }; - } diff --git a/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollFindRfpsFragment.java b/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollFindRfpsFragment.java index d7537847f44..fd181d4be85 100644 --- a/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollFindRfpsFragment.java +++ b/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollFindRfpsFragment.java @@ -21,6 +21,7 @@ import static android.view.View.OnClickListener; import android.app.Activity; import android.content.Context; +import android.hardware.fingerprint.FingerprintManager; import android.os.Bundle; import android.util.Log; import android.view.LayoutInflater; @@ -37,6 +38,7 @@ import androidx.lifecycle.ViewModelProvider; import com.android.settings.R; import com.android.settings.biometrics.fingerprint.FingerprintFindSensorAnimation; import com.android.settings.biometrics2.ui.model.EnrollmentProgress; +import com.android.settings.biometrics2.ui.model.EnrollmentStatusMessage; import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollFindSensorViewModel; import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollProgressViewModel; @@ -64,7 +66,7 @@ public class FingerprintEnrollFindRfpsFragment extends Fragment { private static final String TAG = "FingerprintEnrollFindRfpsFragment"; private FingerprintEnrollFindSensorViewModel mViewModel; - private FingerprintEnrollProgressViewModel mPorgressViewModel; + private FingerprintEnrollProgressViewModel mProgressViewModel; private View mView; private GlifLayout mGlifLayout; @@ -77,7 +79,21 @@ public class FingerprintEnrollFindRfpsFragment extends Fragment { Log.d(TAG, "mProgressObserver(" + progress + ")"); } if (progress != null && !progress.isInitialStep()) { - mViewModel.onStartButtonClick(); + stopLookingForFingerprint(true); + } + }; + + private final Observer mLastCancelMessageObserver = errorMessage -> { + if (DEBUG) { + Log.d(TAG, "mErrorMessageObserver(" + errorMessage + ")"); + } + if (errorMessage != null) { + if (errorMessage.getMsgId() == FingerprintManager.FINGERPRINT_ERROR_CANCELED) { + mProgressViewModel.clearProgressLiveData(); + mViewModel.onStartButtonClick(); + } else { + Log.e(TAG, "mErrorMessageObserver(" + errorMessage + ")"); + } } }; @@ -151,40 +167,48 @@ public class FingerprintEnrollFindRfpsFragment extends Fragment { @Override public void onStop() { super.onStop(); + final boolean isEnrolling = mProgressViewModel.isEnrolling(); if (DEBUG) { - Log.d(TAG, "onStop(), stop looking for fingerprint, animation exist:" + Log.d(TAG, "onStop(), current enrolling: " + isEnrolling + ", animation exist:" + (mAnimation != null)); } - stopLookingForFingerprint(); + if (!isEnrolling) { + stopLookingForFingerprint(false); + } } private void startLookingForFingerprint() { - if (mPorgressViewModel.isEnrolling()) { + if (mProgressViewModel.isEnrolling()) { Log.d(TAG, "startLookingForFingerprint(), failed because isEnrolling is true before" + " starting"); return; } - mPorgressViewModel.clearProgressLiveData(); - mPorgressViewModel.getProgressLiveData().observe(this, mProgressObserver); - final boolean startResult = mPorgressViewModel.startEnrollment(ENROLL_FIND_SENSOR); + mProgressViewModel.clearProgressLiveData(); + mProgressViewModel.getProgressLiveData().observe(this, mProgressObserver); + final boolean startResult = mProgressViewModel.startEnrollment(ENROLL_FIND_SENSOR); if (!startResult) { Log.e(TAG, "startLookingForFingerprint(), failed to start enrollment"); } } - private void stopLookingForFingerprint() { - if (!mPorgressViewModel.isEnrolling()) { + private void stopLookingForFingerprint(boolean waitForLastCancelErrMsg) { + if (!mProgressViewModel.isEnrolling()) { Log.d(TAG, "stopLookingForFingerprint(), failed because isEnrolling is false before" + " stopping"); return; } - mPorgressViewModel.getProgressLiveData().removeObserver(mProgressObserver); - final boolean cancelResult = mPorgressViewModel.cancelEnrollment(); + mProgressViewModel.getProgressLiveData().removeObserver(mProgressObserver); + final boolean cancelResult = mProgressViewModel.cancelEnrollment(); if (!cancelResult) { Log.e(TAG, "stopLookingForFingerprint(), failed to cancel enrollment"); } + + if (waitForLastCancelErrMsg) { + mProgressViewModel.getErrorMessageLiveData().observe(this, + mLastCancelMessageObserver); + } } @Override @@ -203,7 +227,7 @@ public class FingerprintEnrollFindRfpsFragment extends Fragment { final FragmentActivity activity = getActivity(); final ViewModelProvider provider = new ViewModelProvider(activity); mViewModel = provider.get(FingerprintEnrollFindSensorViewModel.class); - mPorgressViewModel = provider.get(FingerprintEnrollProgressViewModel.class); + mProgressViewModel = provider.get(FingerprintEnrollProgressViewModel.class); super.onAttach(context); } } diff --git a/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollFindSfpsFragment.java b/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollFindSfpsFragment.java index c363f04e1e5..aad503e4eda 100644 --- a/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollFindSfpsFragment.java +++ b/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollFindSfpsFragment.java @@ -98,6 +98,7 @@ public class FingerprintEnrollFindSfpsFragment extends Fragment { Log.d(TAG, "mProgressObserver(" + progress + ")"); } if (progress != null && !progress.isInitialStep()) { + mProgressViewModel.clearProgressLiveData(); mViewModel.onStartButtonClick(); } }; diff --git a/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollFinishFragment.java b/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollFinishFragment.java index 02aa5f280ca..3f98d255405 100644 --- a/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollFinishFragment.java +++ b/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollFinishFragment.java @@ -85,15 +85,15 @@ public class FingerprintEnrollFinishFragment extends Fragment { (GlifLayout) mView); glifLayoutHelper.setHeaderText(R.string.security_settings_fingerprint_enroll_finish_title); - glifLayoutHelper.setDescriptionText( - R.string.security_settings_fingerprint_enroll_finish_v2_message); + glifLayoutHelper.setDescriptionText(getString( + R.string.security_settings_fingerprint_enroll_finish_v2_message)); final int maxEnrollments = mFingerprintEnrollFinishViewModel.getMaxFingerprints(); final int enrolled = mFingerprintEnrollFinishViewModel.getNumOfEnrolledFingerprintsSize(); if (mCanAssumeSfps) { if (enrolled < maxEnrollments) { - glifLayoutHelper.setDescriptionText(R.string - .security_settings_fingerprint_enroll_finish_v2_add_fingerprint_message); + glifLayoutHelper.setDescriptionText(getString(R.string + .security_settings_fingerprint_enroll_finish_v2_add_fingerprint_message)); } } diff --git a/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollmentActivity.java b/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollmentActivity.java index dddd641e7cd..e12e982fd79 100644 --- a/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollmentActivity.java +++ b/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollmentActivity.java @@ -26,6 +26,7 @@ import static com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewMo import static com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel.CREDENTIAL_FAIL_NEED_TO_CONFIRM_LOCK; import static com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel.CREDENTIAL_IS_GENERATING_CHALLENGE; import static com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel.CREDENTIAL_VALID; +import static com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollEnrollingViewModel.ErrorDialogData; import static com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollFindSensorViewModel.FINGERPRINT_ENROLL_FIND_SENSOR_ACTION_DIALOG; import static com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollFindSensorViewModel.FINGERPRINT_ENROLL_FIND_SENSOR_ACTION_SKIP; import static com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollFindSensorViewModel.FINGERPRINT_ENROLL_FIND_SENSOR_ACTION_START; @@ -63,8 +64,8 @@ import androidx.lifecycle.viewmodel.MutableCreationExtras; import com.android.settings.R; import com.android.settings.Utils; import com.android.settings.biometrics.BiometricEnrollBase; -import com.android.settings.biometrics.fingerprint.FingerprintEnrollEnrolling; -import com.android.settings.biometrics.fingerprint.SetupFingerprintEnrollEnrolling; +import com.android.settings.biometrics.fingerprint.FingerprintEnrollFinish; +import com.android.settings.biometrics.fingerprint.SetupFingerprintEnrollFinish; import com.android.settings.biometrics2.data.repository.FingerprintRepository; import com.android.settings.biometrics2.factory.BiometricsViewModelFactory; import com.android.settings.biometrics2.ui.model.CredentialModel; @@ -72,6 +73,7 @@ import com.android.settings.biometrics2.ui.model.EnrollmentRequest; import com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel; import com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel.FingerprintChallengeGenerator; import com.android.settings.biometrics2.ui.viewmodel.DeviceFoldedViewModel; +import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollEnrollingViewModel; import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollFindSensorViewModel; import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollIntroViewModel; import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollProgressViewModel; @@ -88,11 +90,16 @@ public class FingerprintEnrollmentActivity extends FragmentActivity { private static final boolean DEBUG = false; private static final String TAG = "FingerprintEnrollmentActivity"; - private static final String INTRO_TAG = "enroll-intro"; - private static final String FIND_UDFPS_TAG = "enroll-find-udfps"; - private static final String FIND_SFPS_TAG = "enroll-find-sfps"; - private static final String FIND_RFPS_TAG = "enroll-find-rfps"; + private static final String INTRO_TAG = "intro"; + private static final String FIND_UDFPS_TAG = "find-udfps"; + private static final String FIND_SFPS_TAG = "find-sfps"; + private static final String FIND_RFPS_TAG = "find-rfps"; + private static final String ENROLLING_UDFPS_TAG = "enrolling-udfps"; + private static final String ENROLLING_SFPS_TAG = "enrolling-sfps"; + private static final String ENROLLING_RFPS_TAG = "enrolling-rfps"; + private static final String FINISH_TAG = "finish"; private static final String SKIP_SETUP_FIND_FPS_DIALOG_TAG = "skip-setup-dialog"; + private static final String ENROLLING_ERROR_DIALOG_TAG = "enrolling-error-dialog"; protected static final int LAUNCH_CONFIRM_LOCK_ACTIVITY = 1; @@ -115,6 +122,22 @@ public class FingerprintEnrollmentActivity extends FragmentActivity { onFindSensorAction(action); } }; + private final Observer mEnrollingDoneObserver = isDone -> { + if (DEBUG) { + Log.d(TAG, "mEnrollingDoneObserver(" + isDone + ")"); + } + if (isDone != null) { + onEnrollingDone(isDone); + } + }; + private final Observer mEnrollingErrorDialogObserver = data -> { + if (DEBUG) { + Log.d(TAG, "mEnrollingErrorDialogObserver(" + data + ")"); + } + if (data != null) { + startEnrollingErrorDialog(); + } + }; private final ActivityResultCallback mNextActivityResultCallback = result -> mViewModel.onContinueEnrollActivityResult(result, mAutoCredentialViewModel.getUserId()); @@ -154,6 +177,9 @@ public class FingerprintEnrollmentActivity extends FragmentActivity { } else { final FragmentManager manager = getSupportFragmentManager(); String[] tags = new String[] { + ENROLLING_UDFPS_TAG, + ENROLLING_SFPS_TAG, + ENROLLING_RFPS_TAG, FIND_UDFPS_TAG, FIND_SFPS_TAG, FIND_RFPS_TAG, @@ -169,7 +195,12 @@ public class FingerprintEnrollmentActivity extends FragmentActivity { } if (tag.equals(INTRO_TAG)) { attachIntroViewModel(); - } else { // FIND_UDFPS_TAG, FIND_SFPS_TAG, FIND_RFPS_TAG + } else if (tag.equals(FIND_UDFPS_TAG) || tag.equals(FIND_SFPS_TAG) + || tag.equals(FIND_RFPS_TAG)) { + attachFindSensorViewModel(); + attachIntroViewModel(); + } else { // ENROLLING_UDFPS_TAG, ENROLLING_SFPS_TAG, ENROLLING_RFPS_TAG + attachEnrollingViewModel(); attachFindSensorViewModel(); attachIntroViewModel(); } @@ -205,35 +236,29 @@ public class FingerprintEnrollmentActivity extends FragmentActivity { // We need to make sure token is valid before entering find sensor page private void startFindSensorFragment() { - attachFindSensorViewModel(); - if (mViewModel.canAssumeUdfps()) { - // UDFPS does not need to start real fingerprint enrolling during finding sensor - startFindFpsFragmentWithProgressViewModel(FingerprintEnrollFindUdfpsFragment.class, - FIND_UDFPS_TAG, false /* initProgressViewModel */); - } else if (mViewModel.canAssumeSfps()) { - startFindFpsFragmentWithProgressViewModel(FingerprintEnrollFindSfpsFragment.class, - FIND_SFPS_TAG, true /* initProgressViewModel */); - } else { - startFindFpsFragmentWithProgressViewModel(FingerprintEnrollFindRfpsFragment.class, - FIND_RFPS_TAG, true /* initProgressViewModel */); - } - } + // Always setToken into progressViewModel even it is not necessary action for UDFPS + mViewModelProvider.get(FingerprintEnrollProgressViewModel.class) + .setToken(mAutoCredentialViewModel.getToken()); - private void startFindFpsFragmentWithProgressViewModel( - @NonNull Class findFpsClass, @NonNull String tag, - boolean initProgressViewModel) { - if (initProgressViewModel) { - final FingerprintEnrollProgressViewModel progressViewModel = - mViewModelProvider.get(FingerprintEnrollProgressViewModel.class); - progressViewModel.setToken(mAutoCredentialViewModel.getToken()); + attachFindSensorViewModel(); + + final String tag; + final Class fragmentClass; + if (mViewModel.canAssumeUdfps()) { + tag = FIND_UDFPS_TAG; + fragmentClass = FingerprintEnrollFindUdfpsFragment.class; + } else if (mViewModel.canAssumeSfps()) { + tag = FIND_SFPS_TAG; + fragmentClass = FingerprintEnrollFindSfpsFragment.class; + } else { + tag = FIND_RFPS_TAG; + fragmentClass = FingerprintEnrollFindRfpsFragment.class; } - final FingerprintEnrollFindSensorViewModel findSensorViewModel = - mViewModelProvider.get(FingerprintEnrollFindSensorViewModel.class); getSupportFragmentManager().beginTransaction() .setReorderingAllowed(true) .setCustomAnimations(R.anim.sud_slide_next_in, R.anim.sud_slide_next_out, R.anim.sud_slide_back_in, R.anim.sud_slide_back_out) - .replace(R.id.fragment_container_view, findFpsClass, null, tag) + .replace(R.id.fragment_container_view, fragmentClass, null, tag) .addToBackStack(tag) .commit(); } @@ -248,11 +273,68 @@ public class FingerprintEnrollmentActivity extends FragmentActivity { findSensorViewModel.getActionLiveData().observe(this, mFindSensorActionObserver); } + private void startEnrollingFragment() { + // Always setToken into progressViewModel even it is not necessary action for SFPS or RFPS + mViewModelProvider.get(FingerprintEnrollProgressViewModel.class) + .setToken(mAutoCredentialViewModel.getToken()); + + attachEnrollingViewModel(); + + final String tag; + final Class fragmentClass; + if (mViewModel.canAssumeUdfps()) { + tag = ENROLLING_UDFPS_TAG; + fragmentClass = FingerprintEnrollEnrollingUdfpsFragment.class; + } else if (mViewModel.canAssumeSfps()) { + tag = ENROLLING_SFPS_TAG; + fragmentClass = FingerprintEnrollEnrollingSfpsFragment.class; + } else { + tag = ENROLLING_RFPS_TAG; + fragmentClass = FingerprintEnrollEnrollingRfpsFragment.class; + } + getSupportFragmentManager().beginTransaction() + .setReorderingAllowed(true) + .setCustomAnimations(R.anim.sud_slide_next_in, R.anim.sud_slide_next_out, + R.anim.sud_slide_back_in, R.anim.sud_slide_back_out) + .replace(R.id.fragment_container_view, fragmentClass, null, tag) + .addToBackStack(tag) + .commit(); + } + + private void attachEnrollingViewModel() { + final FingerprintEnrollEnrollingViewModel enrollingViewModel = + mViewModelProvider.get(FingerprintEnrollEnrollingViewModel.class); + enrollingViewModel.clearBackPressedData(); + enrollingViewModel.getErrorDialogLiveData().observe(this, mEnrollingErrorDialogObserver); + + final FingerprintEnrollProgressViewModel progressViewModel = + mViewModelProvider.get(FingerprintEnrollProgressViewModel.class); + progressViewModel.getDoneLiveData().observe(this, mEnrollingDoneObserver); + } + + private void startFinishActivity() { + final boolean isSuw = mViewModel.getRequest().isSuw(); + if (!mViewModel.isWaitingActivityResult().compareAndSet(false, true)) { + Log.w(TAG, "startNext, isSuw:" + isSuw + ", fail to set isWaiting flag"); + } + Intent intent = new Intent(this, isSuw + ? SetupFingerprintEnrollFinish.class + : FingerprintEnrollFinish.class); + intent.putExtras(mAutoCredentialViewModel.createCredentialIntentExtra()); + intent.putExtras(mViewModel.getNextActivityBaseIntentExtras()); + mNextActivityLauncher.launch(intent); + } + private void startSkipSetupFindFpsDialog() { new SkipSetupFindFpsDialog().show(getSupportFragmentManager(), SKIP_SETUP_FIND_FPS_DIALOG_TAG); } + private void startEnrollingErrorDialog() { + new FingerprintEnrollEnrollingErrorDialog().show(getSupportFragmentManager(), + ENROLLING_ERROR_DIALOG_TAG); + } + private void onGenerateChallengeFailed(@NonNull Boolean ignoredBoolean) { onSetActivityResult(new ActivityResult(RESULT_CANCELED, null)); } @@ -365,26 +447,43 @@ public class FingerprintEnrollmentActivity extends FragmentActivity { return; } case FINGERPRINT_ENROLL_FIND_SENSOR_ACTION_START: { - final boolean isSuw = mViewModel.getRequest().isSuw(); - if (!mViewModel.isWaitingActivityResult().compareAndSet(false, true)) { - Log.w(TAG, "startNext, isSuw:" + isSuw + ", fail to set isWaiting flag"); - } - Intent intent = new Intent(this, isSuw - ? SetupFingerprintEnrollEnrolling.class - : FingerprintEnrollEnrolling.class); - intent.putExtras(mAutoCredentialViewModel.createCredentialIntentExtra()); - intent.putExtras(mViewModel.getNextActivityBaseIntentExtras()); - mNextActivityLauncher.launch(intent); + startEnrollingFragment(); } } } + private void onEnrollingDone(boolean isDone) { + if (!isDone) { + return; + } + final FingerprintEnrollProgressViewModel progressViewModel = + mViewModelProvider.get(FingerprintEnrollProgressViewModel.class); + progressViewModel.clearProgressLiveData(); + + startFinishActivity(); + } + @Override protected void onPause() { super.onPause(); mViewModel.checkFinishActivityDuringOnPause(isFinishing(), isChangingConfigurations()); } + @Override + public void onBackPressed() { + final FragmentManager manager = getSupportFragmentManager(); + final String[] tags = new String[] {ENROLLING_UDFPS_TAG, ENROLLING_SFPS_TAG, + ENROLLING_RFPS_TAG }; + for (String tag: tags) { + final Fragment fragment = manager.findFragmentByTag(tag); + if (fragment != null) { + mViewModelProvider.get(FingerprintEnrollEnrollingViewModel.class).onBackPressed(); + break; + } + } + super.onBackPressed(); + } + @Override protected void onApplyThemeResource(Resources.Theme theme, @StyleRes int resid, boolean first) { theme.applyStyle(R.style.SetupWizardPartnerResource, true); diff --git a/src/com/android/settings/biometrics2/ui/view/GlifLayoutHelper.java b/src/com/android/settings/biometrics2/ui/view/GlifLayoutHelper.java index 814f57994df..6af4928fc2a 100644 --- a/src/com/android/settings/biometrics2/ui/view/GlifLayoutHelper.java +++ b/src/com/android/settings/biometrics2/ui/view/GlifLayoutHelper.java @@ -68,15 +68,13 @@ public class GlifLayoutHelper { } } - /** - * Sets description resId to GlifLayout - */ - public void setDescriptionText(int resId) { - CharSequence previousDescription = mGlifLayout.getDescriptionText(); - CharSequence description = mActivity.getString(resId); - // Prevent a11y for re-reading the same string - if (!TextUtils.equals(previousDescription, description)) { - mGlifLayout.setDescriptionText(resId); - } + @NonNull + public Activity getActivity() { + return mActivity; + } + + @NonNull + public GlifLayout getGlifLayout() { + return mGlifLayout; } } diff --git a/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollEnrollingViewModel.java b/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollEnrollingViewModel.java index 058b50b95f2..2877c9acf7c 100644 --- a/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollEnrollingViewModel.java +++ b/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollEnrollingViewModel.java @@ -16,26 +16,32 @@ package com.android.settings.biometrics2.ui.viewmodel; +import android.annotation.IntDef; import android.app.Application; +import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; +import android.os.Bundle; import android.os.VibrationAttributes; import android.os.VibrationEffect; import android.util.Log; import android.view.accessibility.AccessibilityManager; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.lifecycle.AndroidViewModel; -import androidx.lifecycle.DefaultLifecycleObserver; +import androidx.lifecycle.LiveData; import androidx.lifecycle.MutableLiveData; import com.android.settings.biometrics2.data.repository.AccessibilityRepository; import com.android.settings.biometrics2.data.repository.FingerprintRepository; import com.android.settings.biometrics2.data.repository.VibratorRepository; -import com.android.settings.biometrics2.ui.model.EnrollmentRequest; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; /** * ViewModel explaining the fingerprint enrolling page */ -public class FingerprintEnrollEnrollingViewModel extends AndroidViewModel - implements DefaultLifecycleObserver { +public class FingerprintEnrollEnrollingViewModel extends AndroidViewModel { private static final String TAG = FingerprintEnrollEnrollingViewModel.class.getSimpleName(); private static final boolean DEBUG = false; @@ -45,34 +51,117 @@ public class FingerprintEnrollEnrollingViewModel extends AndroidViewModel private static final VibrationAttributes FINGERPRINT_ENROLLING_SONFICATION_ATTRIBUTES = VibrationAttributes.createForUsage(VibrationAttributes.USAGE_ACCESSIBILITY); - //Enrolling skip + /** + * Enrolling skipped + */ public static final int FINGERPRINT_ENROLL_ENROLLING_ACTION_SKIP = 0; - //Icon touch dialog show - public static final int FINGERPRINT_ENROLL_ENROLLING_ACTION_SHOW_DIALOG = 0; + /** + * Enrolling finished + */ + public static final int FINGERPRINT_ENROLL_ENROLLING_ACTION_DONE = 1; - //Icon touch dialog dismiss - public static final int FINGERPRINT_ENROLL_ENROLLING_ACTION_DISMISS_DIALOG = 1; + /** + * Icon touch dialog show + */ + public static final int FINGERPRINT_ENROLL_ENROLLING_ACTION_SHOW_DIALOG = 2; + /** + * Icon touch dialog dismiss + */ + public static final int FINGERPRINT_ENROLL_ENROLLING_ACTION_DISMISS_DIALOG = 3; + + @IntDef(prefix = { "FINGERPRINT_ENROLL_ENROLLING_ACTION_" }, value = { + FINGERPRINT_ENROLL_ENROLLING_ACTION_SKIP, + FINGERPRINT_ENROLL_ENROLLING_ACTION_DONE, + FINGERPRINT_ENROLL_ENROLLING_ACTION_SHOW_DIALOG, + FINGERPRINT_ENROLL_ENROLLING_ACTION_DISMISS_DIALOG + }) + @Retention(RetentionPolicy.SOURCE) + public @interface FingerprintEnrollEnrollingAction {} + + /** + * Enrolling skipped + */ + public static final int FINGERPRINT_ERROR_DIALOG_ACTION_SET_RESULT_FINISH = 0; + + /** + * Enrolling finished + */ + public static final int FINGERPRINT_ERROR_DIALOG_ACTION_SET_RESULT_TIMEOUT = 1; + + /** + * Icon touch dialog show + */ + public static final int FINGERPRINT_ERROR_DIALOG_ACTION_RESTART = 2; + + @IntDef(prefix = { "FINGERPRINT_ERROR_DIALOG_ACTION_" }, value = { + FINGERPRINT_ERROR_DIALOG_ACTION_SET_RESULT_FINISH, + FINGERPRINT_ERROR_DIALOG_ACTION_SET_RESULT_TIMEOUT, + FINGERPRINT_ERROR_DIALOG_ACTION_RESTART + }) + @Retention(RetentionPolicy.SOURCE) + public @interface FingerprintErrorDialogAction {} + + + private final int mUserId; private final FingerprintRepository mFingerprintRepository; private final AccessibilityRepository mAccessibilityRepository; private final VibratorRepository mVibratorRepository; - private EnrollmentRequest mEnrollmentRequest = null; + private final MutableLiveData mBackPressedLiveData = new MutableLiveData<>(false); private final MutableLiveData mEnrollingLiveData = new MutableLiveData<>(); private final MutableLiveData mIconTouchDialogLiveData = new MutableLiveData<>(); - + private final MutableLiveData mErrorDialogLiveData = new MutableLiveData<>(); + private final MutableLiveData mErrorDialogActionLiveData = new MutableLiveData<>(); public FingerprintEnrollEnrollingViewModel(Application application, + int userId, FingerprintRepository fingerprintRepository, AccessibilityRepository accessibilityRepository, VibratorRepository vibratorRepository) { super(application); + mUserId = userId; mFingerprintRepository = fingerprintRepository; mAccessibilityRepository = accessibilityRepository; mVibratorRepository = vibratorRepository; } + /** + * Notifies activity to show error dialog + */ + public void showErrorDialog(@NonNull ErrorDialogData errorDialogData) { + mErrorDialogLiveData.postValue(errorDialogData); + } + + public LiveData getErrorDialogLiveData() { + return mErrorDialogLiveData; + } + + /** + * Saves new user dialog action to mErrorDialogActionLiveData + */ + public void onErrorDialogAction(@FingerprintErrorDialogAction int action) { + if (DEBUG) { + Log.d(TAG, "onErrorDialogAction(" + action + ")"); + } + mErrorDialogActionLiveData.postValue(action); + } + + /** + * Clears back press data + */ + public void clearBackPressedData() { + mBackPressedLiveData.setValue(false); + } + + /** + * User trigger back pressed + */ + public void onBackPressed() { + mBackPressedLiveData.postValue(true); + } + /** * User clicks skip button */ @@ -84,6 +173,17 @@ public class FingerprintEnrollEnrollingViewModel extends AndroidViewModel mEnrollingLiveData.postValue(action); } + /** + * Is enrolling finished + */ + public void onEnrollingDone() { + final int action = FINGERPRINT_ENROLL_ENROLLING_ACTION_SKIP; + if (DEBUG) { + Log.d(TAG, "onEnrollingDone, post action " + action); + } + mEnrollingLiveData.postValue(action); + } + /** * Icon touch dialog show */ @@ -120,20 +220,6 @@ public class FingerprintEnrollEnrollingViewModel extends AndroidViewModel return mFingerprintRepository.getEnrollStageCount(); } - /** - * The first sensor type is UDFPS sensor or not - */ - public boolean canAssumeUdfps() { - return mFingerprintRepository.canAssumeUdfps(); - } - - /** - * The first sensor type is SFPS sensor or not - */ - public boolean canAssumeSfps() { - return mFingerprintRepository.canAssumeSfps(); - } - /** * Requests interruption of the accessibility feedback from all accessibility services. */ @@ -154,8 +240,80 @@ public class FingerprintEnrollEnrollingViewModel extends AndroidViewModel * Like {@link #vibrate(VibrationEffect, VibrationAttributes)}, but allows the * caller to specify the vibration is owned by someone else and set a reason for vibration. */ - public void vibrateError(int uid, String opPkg, String reason) { - mVibratorRepository.vibrate(uid, opPkg, VIBRATE_EFFECT_ERROR, reason, - FINGERPRINT_ENROLLING_SONFICATION_ATTRIBUTES); + public void vibrateError(String reason) { + mVibratorRepository.vibrate(mUserId, getApplication().getOpPackageName(), + VIBRATE_EFFECT_ERROR, reason, FINGERPRINT_ENROLLING_SONFICATION_ATTRIBUTES); + } + + /** + * Gets the first FingerprintSensorPropertiesInternal from FingerprintManager + */ + @Nullable + public FingerprintSensorPropertiesInternal getFirstFingerprintSensorPropertiesInternal() { + return mFingerprintRepository.getFirstFingerprintSensorPropertiesInternal(); + } + + /** + * The first sensor type is UDFPS sensor or not + */ + public boolean canAssumeUdfps() { + return mFingerprintRepository.canAssumeUdfps(); + } + + /** + * Saves current state to outState + */ + public void onSaveInstanceState(@NonNull Bundle outState) { + // TODO +// mRestoring = true; +// mIsCanceled = savedInstanceState.getBoolean(KEY_STATE_CANCELED, false); +// mPreviousRotation = savedInstanceState.getInt(KEY_STATE_PREVIOUS_ROTATION, +// getDisplay().getRotation()); +// mIsOrientationChanged = mPreviousRotation != getDisplay().getRotation(); + } + + /** + * Restores saved state from previous savedInstanceState + */ + public void restoreSavedState(@Nullable Bundle savedInstanceState) { + if (savedInstanceState == null) { + return; + } + // TODO +// outState.putBoolean(KEY_STATE_CANCELED, mIsCanceled); +// outState.putInt(KEY_STATE_PREVIOUS_ROTATION, mPreviousRotation); + } + + /** + * Data for passing to FingerprintEnrollEnrollingErrorDialog + */ + public static class ErrorDialogData { + @NonNull private final CharSequence mErrMsg; + @NonNull private final CharSequence mErrTitle; + @NonNull private final int mErrMsgId; + + public ErrorDialogData(@NonNull CharSequence errMsg, @NonNull CharSequence errTitle, + int errMsgId) { + mErrMsg = errMsg; + mErrTitle = errTitle; + mErrMsgId = errMsgId; + } + + public CharSequence getErrMsg() { + return mErrMsg; + } + + public CharSequence getErrTitle() { + return mErrTitle; + } + + public int getErrMsgId() { + return mErrMsgId; + } + + @Override + public String toString() { + return ErrorDialogData.class.getSimpleName() + "{id:" + mErrMsgId + "}"; + } } } diff --git a/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollProgressViewModel.java b/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollProgressViewModel.java index 532e2cce1d7..2cb94ec799b 100644 --- a/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollProgressViewModel.java +++ b/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollProgressViewModel.java @@ -55,17 +55,17 @@ public class FingerprintEnrollProgressViewModel extends AndroidViewModel { new MutableLiveData<>(); private final MutableLiveData mErrorMessageLiveData = new MutableLiveData<>(); - private final MutableLiveData mAcquireLiveData = new MutableLiveData<>(); private final MutableLiveData mPointerDownLiveData = new MutableLiveData<>(); private final MutableLiveData mPointerUpLiveData = new MutableLiveData<>(); + private final MutableLiveData mDoneLiveData = new MutableLiveData<>(false); private byte[] mToken = null; private final int mUserId; private final FingerprintUpdater mFingerprintUpdater; private final MessageDisplayController mMessageDisplayController; - private EnrollmentHelper mEnrollmentHelper; + @Nullable private CancellationSignal mCancellationSignal = null; private final EnrollmentCallback mEnrollmentCallback = new EnrollmentCallback() { @Override @@ -78,7 +78,11 @@ public class FingerprintEnrollProgressViewModel extends AndroidViewModel { + ", post progress as " + progress); } mProgressLiveData.postValue(progress); - // TODO set enrolling to false when remaining is 0 during implementing b/260957933 + + final Boolean done = remaining == 0; + if (!done.equals(mDoneLiveData.getValue())) { + mDoneLiveData.postValue(done); + } } @Override @@ -136,7 +140,10 @@ public class FingerprintEnrollProgressViewModel extends AndroidViewModel { * clear progress */ public void clearProgressLiveData() { + mDoneLiveData.setValue(false); mProgressLiveData.setValue(new EnrollmentProgress(INITIAL_STEPS, INITIAL_REMAINING)); + mHelpMessageLiveData.setValue(null); + mErrorMessageLiveData.setValue(null); } public LiveData getProgressLiveData() { @@ -151,18 +158,21 @@ public class FingerprintEnrollProgressViewModel extends AndroidViewModel { return mErrorMessageLiveData; } - public MutableLiveData getAcquireLiveData() { + public LiveData getAcquireLiveData() { return mAcquireLiveData; } - public MutableLiveData getPointerDownLiveData() { + public LiveData getPointerDownLiveData() { return mPointerDownLiveData; } - public MutableLiveData getPointerUpLiveData() { + public LiveData getPointerUpLiveData() { return mPointerUpLiveData; } + public LiveData getDoneLiveData() { + return mDoneLiveData; + } /** * Starts enrollment and return latest isEnrolling() result @@ -172,16 +182,18 @@ public class FingerprintEnrollProgressViewModel extends AndroidViewModel { Log.e(TAG, "Null hardware auth token for enroll"); return false; } - if (isEnrolling()) { + if (mCancellationSignal != null) { Log.w(TAG, "Enrolling has started, shall not start again"); return true; } + if (DEBUG) { + Log.e(TAG, "startEnrollment(" + reason + ")"); + } - mEnrollmentHelper = new EnrollmentHelper( - mMessageDisplayController != null - ? mMessageDisplayController - : mEnrollmentCallback); - mEnrollmentHelper.startEnrollment(mFingerprintUpdater, mToken, mUserId, reason); + mCancellationSignal = new CancellationSignal(); + mFingerprintUpdater.enroll(mToken, mCancellationSignal, mUserId, + mMessageDisplayController != null ? mMessageDisplayController : mEnrollmentCallback, + reason); return true; } @@ -189,86 +201,22 @@ public class FingerprintEnrollProgressViewModel extends AndroidViewModel { * Cancels enrollment and return latest isEnrolling result */ public boolean cancelEnrollment() { - if (!isEnrolling() || mEnrollmentHelper == null) { - Log.e(TAG, "Fail to cancel enrollment, enrollmentController exist:" - + (mEnrollmentHelper != null)); + final CancellationSignal cancellationSignal = mCancellationSignal; + if (cancellationSignal == null) { + Log.e(TAG, "Fail to cancel enrollment, has cancelled or not start"); return false; } - mEnrollmentHelper.cancelEnrollment(); - mEnrollmentHelper = null; + mCancellationSignal = null; + cancellationSignal.cancel(); return true; } public boolean isEnrolling() { - return (mEnrollmentHelper != null); + return (mCancellationSignal != null); } private int getSteps() { return mProgressLiveData.getValue().getSteps(); } - - /** - * This class is used to stop latest message from onEnrollmentError() after user cancelled - * enrollment. This class will not forward message anymore after mCancellationSignal is sent. - */ - private static class EnrollmentHelper extends EnrollmentCallback { - - @NonNull private final EnrollmentCallback mEnrollmentCallback; - @Nullable private CancellationSignal mCancellationSignal = new CancellationSignal(); - - EnrollmentHelper(@NonNull EnrollmentCallback enrollmentCallback) { - mEnrollmentCallback = enrollmentCallback; - } - - @Override - public void onEnrollmentError(int errMsgId, CharSequence errString) { - if (mCancellationSignal == null) { - return; - } - mEnrollmentCallback.onEnrollmentError(errMsgId, errString); - } - - @Override - public void onEnrollmentHelp(int helpMsgId, CharSequence helpString) { - if (mCancellationSignal == null) { - return; - } - mEnrollmentCallback.onEnrollmentHelp(helpMsgId, helpString); - } - - @Override - public void onEnrollmentProgress(int remaining) { - if (mCancellationSignal == null) { - return; - } - mEnrollmentCallback.onEnrollmentProgress(remaining); - } - - /** - * Starts enrollment - */ - public boolean startEnrollment(@NonNull FingerprintUpdater fingerprintUpdater, - @NonNull byte[] token, int userId, @EnrollReason int reason) { - if (mCancellationSignal == null) { - // Not allow enrolling twice as same instance. Allocate a new instance for second - // enrollment. - return false; - } - fingerprintUpdater.enroll(token, mCancellationSignal, userId, this, reason); - return true; - } - - /** - * Cancels current enrollment - */ - public void cancelEnrollment() { - final CancellationSignal cancellationSignal = mCancellationSignal; - mCancellationSignal = null; - - if (cancellationSignal != null) { - cancellationSignal.cancel(); - } - } - } }