[BiometricsV2] Refactor EnrollingUdfpsFragment
Refactor FingerprintEnrollEnrollingUdfpsFragment to kotlin and add bindView() method for it Bug: 286198032 Test: atest FingerprintEnrollmentActivityTest Test: atest BiometricsMicrobenchmark:biometrics-enrollment-test Test: Manually test enrollment as Udfps device Change-Id: Ife1e14ffabc4716acc53b67f641ba9cf159319f2
This commit is contained in:
@@ -1,622 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.settings.biometrics2.ui.view;
|
||||
|
||||
import 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.hardware.fingerprint.FingerprintManager;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Surface;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.activity.OnBackPressedCallback;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
import androidx.lifecycle.Observer;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
|
||||
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.settings.biometrics2.ui.widget.UdfpsEnrollView;
|
||||
import com.android.settingslib.display.DisplayDensityUtils;
|
||||
|
||||
import com.airbnb.lottie.LottieAnimationView;
|
||||
import com.airbnb.lottie.LottieCompositionFactory;
|
||||
|
||||
/**
|
||||
* Fragment is used to handle enrolling process for udfps
|
||||
*/
|
||||
public class FingerprintEnrollEnrollingUdfpsFragment extends Fragment {
|
||||
|
||||
private static final String TAG = FingerprintEnrollEnrollingUdfpsFragment.class.getSimpleName();
|
||||
|
||||
private static final int PROGRESS_BAR_MAX = 10000;
|
||||
|
||||
private static final int STAGE_UNKNOWN = -1;
|
||||
private static final int STAGE_CENTER = 0;
|
||||
private static final int STAGE_GUIDED = 1;
|
||||
private static final int STAGE_FINGERTIP = 2;
|
||||
private static final int STAGE_LEFT_EDGE = 3;
|
||||
private static final int STAGE_RIGHT_EDGE = 4;
|
||||
|
||||
private FingerprintEnrollEnrollingViewModel mEnrollingViewModel;
|
||||
private DeviceRotationViewModel mRotationViewModel;
|
||||
private FingerprintEnrollProgressViewModel mProgressViewModel;
|
||||
|
||||
private LottieAnimationView mIllustrationLottie;
|
||||
private boolean mHaveShownUdfpsTipLottie;
|
||||
private boolean mHaveShownUdfpsLeftEdgeLottie;
|
||||
private boolean mHaveShownUdfpsRightEdgeLottie;
|
||||
private boolean mHaveShownUdfpsCenterLottie;
|
||||
private boolean mHaveShownUdfpsGuideLottie;
|
||||
|
||||
private TextView mTitleText;
|
||||
private TextView mSubTitleText;
|
||||
private UdfpsEnrollView mUdfpsEnrollView;
|
||||
private Button mSkipBtn;
|
||||
private ImageView mIcon;
|
||||
|
||||
private boolean mShouldShowLottie;
|
||||
private boolean mIsAccessibilityEnabled;
|
||||
|
||||
private int mRotation = -1;
|
||||
|
||||
private final View.OnClickListener mOnSkipClickListener = v -> {
|
||||
mEnrollingViewModel.setOnSkipPressed();
|
||||
cancelEnrollment();
|
||||
};
|
||||
|
||||
private final Observer<EnrollmentProgress> mProgressObserver = progress -> {
|
||||
if (progress != null) {
|
||||
onEnrollmentProgressChange(progress);
|
||||
}
|
||||
};
|
||||
private final Observer<EnrollmentStatusMessage> mHelpMessageObserver = helpMessage -> {
|
||||
if (helpMessage != null) {
|
||||
onEnrollmentHelp(helpMessage);
|
||||
}
|
||||
};
|
||||
private final Observer<EnrollmentStatusMessage> mErrorMessageObserver = errorMessage -> {
|
||||
if (errorMessage != null) {
|
||||
onEnrollmentError(errorMessage);
|
||||
}
|
||||
};
|
||||
private final Observer<Boolean> mAcquireObserver = isAcquiredGood -> {
|
||||
if (isAcquiredGood != null) {
|
||||
onAcquired(isAcquiredGood);
|
||||
}
|
||||
};
|
||||
private final Observer<Integer> mPointerDownObserver = sensorId -> {
|
||||
if (sensorId != null) {
|
||||
onPointerDown(sensorId);
|
||||
}
|
||||
};
|
||||
private final Observer<Integer> mPointerUpObserver = sensorId -> {
|
||||
if (sensorId != null) {
|
||||
onPointerUp(sensorId);
|
||||
}
|
||||
};
|
||||
|
||||
private final Observer<Integer> mRotationObserver = rotation -> {
|
||||
if (rotation != null) {
|
||||
onRotationChanged(rotation);
|
||||
}
|
||||
};
|
||||
|
||||
private final OnBackPressedCallback mOnBackPressedCallback = new OnBackPressedCallback(true) {
|
||||
@Override
|
||||
public void handleOnBackPressed() {
|
||||
setEnabled(false);
|
||||
mEnrollingViewModel.setOnBackPressed();
|
||||
cancelEnrollment();
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public void onAttach(@NonNull Context context) {
|
||||
final FragmentActivity activity = getActivity();
|
||||
final ViewModelProvider provider = new ViewModelProvider(activity);
|
||||
mEnrollingViewModel = provider.get(FingerprintEnrollEnrollingViewModel.class);
|
||||
mRotationViewModel = provider.get(DeviceRotationViewModel.class);
|
||||
mProgressViewModel = provider.get(FingerprintEnrollProgressViewModel.class);
|
||||
super.onAttach(context);
|
||||
activity.getOnBackPressedDispatcher().addCallback(mOnBackPressedCallback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetach() {
|
||||
mOnBackPressedCallback.setEnabled(false);
|
||||
super.onDetach();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
mIsAccessibilityEnabled = mEnrollingViewModel.isAccessibilityEnabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
final RelativeLayout containView = (RelativeLayout) inflater.inflate(
|
||||
R.layout.udfps_enroll_enrolling_v2, container, false);
|
||||
|
||||
final Activity activity = getActivity();
|
||||
mIcon = containView.findViewById(R.id.sud_layout_icon);
|
||||
mTitleText = containView.findViewById(R.id.suc_layout_title);
|
||||
mSubTitleText = containView.findViewById(R.id.sud_layout_subtitle);
|
||||
mSkipBtn = containView.findViewById(R.id.skip_btn);
|
||||
mSkipBtn.setOnClickListener(mOnSkipClickListener);
|
||||
mUdfpsEnrollView = containView.findViewById(R.id.udfps_animation_view);
|
||||
mUdfpsEnrollView.setSensorProperties(
|
||||
mEnrollingViewModel.getFirstFingerprintSensorPropertiesInternal());
|
||||
mShouldShowLottie = shouldShowLottie();
|
||||
final boolean isLandscape = BiometricUtils.isReverseLandscape(activity)
|
||||
|| BiometricUtils.isLandscape(activity);
|
||||
updateOrientation(containView, (isLandscape
|
||||
? Configuration.ORIENTATION_LANDSCAPE : Configuration.ORIENTATION_PORTRAIT));
|
||||
|
||||
mRotation = mRotationViewModel.getLiveData().getValue();
|
||||
configLayout(mRotation);
|
||||
return containView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
startEnrollment();
|
||||
updateProgress(false /* animate */, mProgressViewModel.getProgressLiveData().getValue());
|
||||
final EnrollmentStatusMessage msg = mProgressViewModel.getHelpMessageLiveData().getValue();
|
||||
if (msg != null) {
|
||||
onEnrollmentHelp(msg);
|
||||
} else {
|
||||
updateTitleAndDescription();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
mRotationViewModel.getLiveData().observe(this, mRotationObserver);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
mRotationViewModel.getLiveData().removeObserver(mRotationObserver);
|
||||
super.onPause();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
removeEnrollmentObservers();
|
||||
if (!getActivity().isChangingConfigurations() && mProgressViewModel.isEnrolling()) {
|
||||
mProgressViewModel.cancelEnrollment();
|
||||
}
|
||||
super.onStop();
|
||||
}
|
||||
|
||||
private void removeEnrollmentObservers() {
|
||||
preRemoveEnrollmentObservers();
|
||||
mProgressViewModel.getErrorMessageLiveData().removeObserver(mErrorMessageObserver);
|
||||
}
|
||||
|
||||
private void preRemoveEnrollmentObservers() {
|
||||
mProgressViewModel.getProgressLiveData().removeObserver(mProgressObserver);
|
||||
mProgressViewModel.getHelpMessageLiveData().removeObserver(mHelpMessageObserver);
|
||||
mProgressViewModel.getAcquireLiveData().removeObserver(mAcquireObserver);
|
||||
mProgressViewModel.getPointerDownLiveData().removeObserver(mPointerDownObserver);
|
||||
mProgressViewModel.getPointerUpLiveData().removeObserver(mPointerUpObserver);
|
||||
}
|
||||
|
||||
private void cancelEnrollment() {
|
||||
preRemoveEnrollmentObservers();
|
||||
mProgressViewModel.cancelEnrollment();
|
||||
}
|
||||
|
||||
private void startEnrollment() {
|
||||
final boolean startResult = mProgressViewModel.startEnrollment(ENROLL_ENROLL);
|
||||
if (!startResult) {
|
||||
Log.e(TAG, "startEnrollment(), failed");
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
private void updateProgress(boolean animate, @NonNull EnrollmentProgress enrollmentProgress) {
|
||||
if (!mProgressViewModel.isEnrolling()) {
|
||||
Log.d(TAG, "Enrollment not started yet");
|
||||
return;
|
||||
}
|
||||
|
||||
final int progress = getProgress(enrollmentProgress);
|
||||
|
||||
if (mProgressViewModel.getProgressLiveData().getValue().getSteps() != -1) {
|
||||
mUdfpsEnrollView.onEnrollmentProgress(enrollmentProgress.getRemaining(),
|
||||
enrollmentProgress.getSteps());
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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() {
|
||||
switch (getCurrentStage()) {
|
||||
case STAGE_CENTER:
|
||||
mTitleText.setText(R.string.security_settings_fingerprint_enroll_repeat_title);
|
||||
if (mIsAccessibilityEnabled || mIllustrationLottie == null) {
|
||||
mSubTitleText.setText(R.string.security_settings_udfps_enroll_start_message);
|
||||
} else if (!mHaveShownUdfpsCenterLottie) {
|
||||
mHaveShownUdfpsCenterLottie = true;
|
||||
// Note: Update string reference when differentiate in between udfps & sfps
|
||||
mIllustrationLottie.setContentDescription(
|
||||
getString(R.string.security_settings_sfps_enroll_finger_center_title)
|
||||
);
|
||||
configureEnrollmentStage(R.raw.udfps_center_hint_lottie);
|
||||
}
|
||||
break;
|
||||
|
||||
case STAGE_GUIDED:
|
||||
mTitleText.setText(R.string.security_settings_fingerprint_enroll_repeat_title);
|
||||
if (mIsAccessibilityEnabled || mIllustrationLottie == null) {
|
||||
mSubTitleText.setText(
|
||||
R.string.security_settings_udfps_enroll_repeat_a11y_message);
|
||||
} else if (!mHaveShownUdfpsGuideLottie) {
|
||||
mHaveShownUdfpsGuideLottie = true;
|
||||
mIllustrationLottie.setContentDescription(
|
||||
getString(R.string.security_settings_fingerprint_enroll_repeat_message)
|
||||
);
|
||||
// TODO(b/228100413) Could customize guided lottie animation
|
||||
configureEnrollmentStage(R.raw.udfps_center_hint_lottie);
|
||||
}
|
||||
break;
|
||||
case STAGE_FINGERTIP:
|
||||
mTitleText.setText(R.string.security_settings_udfps_enroll_fingertip_title);
|
||||
if (!mHaveShownUdfpsTipLottie && mIllustrationLottie != null) {
|
||||
mHaveShownUdfpsTipLottie = true;
|
||||
mIllustrationLottie.setContentDescription(
|
||||
getString(R.string.security_settings_udfps_tip_fingerprint_help)
|
||||
);
|
||||
configureEnrollmentStage(R.raw.udfps_tip_hint_lottie);
|
||||
}
|
||||
break;
|
||||
case STAGE_LEFT_EDGE:
|
||||
mTitleText.setText(R.string.security_settings_udfps_enroll_left_edge_title);
|
||||
if (!mHaveShownUdfpsLeftEdgeLottie && mIllustrationLottie != null) {
|
||||
mHaveShownUdfpsLeftEdgeLottie = true;
|
||||
mIllustrationLottie.setContentDescription(
|
||||
getString(R.string.security_settings_udfps_side_fingerprint_help)
|
||||
);
|
||||
configureEnrollmentStage(R.raw.udfps_left_edge_hint_lottie);
|
||||
} else if (mIllustrationLottie == null) {
|
||||
if (isStageHalfCompleted()) {
|
||||
mSubTitleText.setText(
|
||||
R.string.security_settings_fingerprint_enroll_repeat_message);
|
||||
} else {
|
||||
mSubTitleText.setText(R.string.security_settings_udfps_enroll_edge_message);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case STAGE_RIGHT_EDGE:
|
||||
mTitleText.setText(R.string.security_settings_udfps_enroll_right_edge_title);
|
||||
if (!mHaveShownUdfpsRightEdgeLottie && mIllustrationLottie != null) {
|
||||
mHaveShownUdfpsRightEdgeLottie = true;
|
||||
mIllustrationLottie.setContentDescription(
|
||||
getString(R.string.security_settings_udfps_side_fingerprint_help)
|
||||
);
|
||||
configureEnrollmentStage(R.raw.udfps_right_edge_hint_lottie);
|
||||
|
||||
} else if (mIllustrationLottie == null) {
|
||||
if (isStageHalfCompleted()) {
|
||||
mSubTitleText.setText(
|
||||
R.string.security_settings_fingerprint_enroll_repeat_message);
|
||||
} else {
|
||||
mSubTitleText.setText(R.string.security_settings_udfps_enroll_edge_message);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case STAGE_UNKNOWN:
|
||||
default:
|
||||
mTitleText.setText(R.string.security_settings_fingerprint_enroll_udfps_title);
|
||||
mSubTitleText.setText(R.string.security_settings_udfps_enroll_start_message);
|
||||
final CharSequence description = getString(
|
||||
R.string.security_settings_udfps_enroll_a11y);
|
||||
getActivity().setTitle(description);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
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 void updateOrientation(@NonNull RelativeLayout content, int orientation) {
|
||||
switch (orientation) {
|
||||
case Configuration.ORIENTATION_LANDSCAPE: {
|
||||
mIllustrationLottie = null;
|
||||
break;
|
||||
}
|
||||
case Configuration.ORIENTATION_PORTRAIT: {
|
||||
if (mShouldShowLottie) {
|
||||
mIllustrationLottie = content.findViewById(R.id.illustration_lottie);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
Log.e(TAG, "Error unhandled configuration change");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private int getCurrentStage() {
|
||||
EnrollmentProgress progressLiveData = mProgressViewModel.getProgressLiveData().getValue();
|
||||
|
||||
if (progressLiveData == null || progressLiveData.getSteps() == -1) {
|
||||
return STAGE_UNKNOWN;
|
||||
}
|
||||
|
||||
final int progressSteps = progressLiveData.getSteps() - progressLiveData.getRemaining();
|
||||
if (progressSteps < getStageThresholdSteps(0)) {
|
||||
return STAGE_CENTER;
|
||||
} else if (progressSteps < getStageThresholdSteps(1)) {
|
||||
return STAGE_GUIDED;
|
||||
} else if (progressSteps < getStageThresholdSteps(2)) {
|
||||
return STAGE_FINGERTIP;
|
||||
} else if (progressSteps < getStageThresholdSteps(3)) {
|
||||
return STAGE_LEFT_EDGE;
|
||||
} else {
|
||||
return STAGE_RIGHT_EDGE;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isStageHalfCompleted() {
|
||||
EnrollmentProgress progressLiveData = mProgressViewModel.getProgressLiveData().getValue();
|
||||
if (progressLiveData == null || progressLiveData.getSteps() == -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final int progressSteps = progressLiveData.getSteps() - progressLiveData.getRemaining();
|
||||
int prevThresholdSteps = 0;
|
||||
for (int i = 0; i < mEnrollingViewModel.getEnrollStageCount(); i++) {
|
||||
final int thresholdSteps = getStageThresholdSteps(i);
|
||||
if (progressSteps >= prevThresholdSteps && progressSteps < thresholdSteps) {
|
||||
final int adjustedProgress = progressSteps - prevThresholdSteps;
|
||||
final int adjustedThreshold = thresholdSteps - prevThresholdSteps;
|
||||
return adjustedProgress >= adjustedThreshold / 2;
|
||||
}
|
||||
prevThresholdSteps = thresholdSteps;
|
||||
}
|
||||
|
||||
// After last enrollment step.
|
||||
return true;
|
||||
}
|
||||
|
||||
private int getStageThresholdSteps(int index) {
|
||||
|
||||
EnrollmentProgress progressLiveData = mProgressViewModel.getProgressLiveData().getValue();
|
||||
|
||||
if (progressLiveData == null || progressLiveData.getSteps() == -1) {
|
||||
Log.w(TAG, "getStageThresholdSteps: Enrollment not started yet");
|
||||
return 1;
|
||||
}
|
||||
return Math.round(progressLiveData.getSteps()
|
||||
* mEnrollingViewModel.getEnrollStageThreshold(index));
|
||||
}
|
||||
|
||||
private void configureEnrollmentStage(@RawRes int lottie) {
|
||||
mSubTitleText.setText("");
|
||||
LottieCompositionFactory.fromRawRes(getActivity(), lottie)
|
||||
.addListener((c) -> {
|
||||
mIllustrationLottie.setComposition(c);
|
||||
mIllustrationLottie.setVisibility(View.VISIBLE);
|
||||
mIllustrationLottie.playAnimation();
|
||||
});
|
||||
}
|
||||
|
||||
private void onEnrollmentProgressChange(@NonNull EnrollmentProgress progress) {
|
||||
updateProgress(true /* animate */, progress);
|
||||
|
||||
updateTitleAndDescription();
|
||||
|
||||
if (mIsAccessibilityEnabled) {
|
||||
final int steps = progress.getSteps();
|
||||
final int remaining = progress.getRemaining();
|
||||
final int percent = (int) (((float) (steps - remaining) / (float) steps) * 100);
|
||||
CharSequence announcement = getActivity().getString(
|
||||
R.string.security_settings_udfps_enroll_progress_a11y_message, percent);
|
||||
mEnrollingViewModel.sendAccessibilityEvent(announcement);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void onEnrollmentHelp(@NonNull EnrollmentStatusMessage helpMessage) {
|
||||
final CharSequence helpStr = helpMessage.getStr();
|
||||
if (!TextUtils.isEmpty(helpStr)) {
|
||||
showError(helpStr);
|
||||
mUdfpsEnrollView.onEnrollmentHelp();
|
||||
}
|
||||
}
|
||||
private void onEnrollmentError(@NonNull EnrollmentStatusMessage errorMessage) {
|
||||
removeEnrollmentObservers();
|
||||
|
||||
if (mEnrollingViewModel.getOnBackPressed()
|
||||
&& errorMessage.getMsgId() == FingerprintManager.FINGERPRINT_ERROR_CANCELED) {
|
||||
mEnrollingViewModel.onCancelledDueToOnBackPressed();
|
||||
} else if (mEnrollingViewModel.getOnSkipPressed()
|
||||
&& errorMessage.getMsgId() == FingerprintManager.FINGERPRINT_ERROR_CANCELED) {
|
||||
mEnrollingViewModel.onCancelledDueToOnSkipPressed();
|
||||
} else {
|
||||
final int errMsgId = errorMessage.getMsgId();
|
||||
mEnrollingViewModel.showErrorDialog(
|
||||
new FingerprintEnrollEnrollingViewModel.ErrorDialogData(
|
||||
getString(FingerprintErrorDialog.getErrorMessage(errMsgId)),
|
||||
getString(FingerprintErrorDialog.getErrorTitle(errMsgId)),
|
||||
errMsgId
|
||||
));
|
||||
mProgressViewModel.cancelEnrollment();
|
||||
}
|
||||
}
|
||||
|
||||
private void onAcquired(boolean isAcquiredGood) {
|
||||
if (mUdfpsEnrollView != null) {
|
||||
mUdfpsEnrollView.onAcquired(isAcquiredGood);
|
||||
}
|
||||
}
|
||||
|
||||
private void onPointerDown(int sensorId) {
|
||||
if (mUdfpsEnrollView != null) {
|
||||
mUdfpsEnrollView.onPointerDown(sensorId);
|
||||
}
|
||||
}
|
||||
|
||||
private void onPointerUp(int sensorId) {
|
||||
if (mUdfpsEnrollView != null) {
|
||||
mUdfpsEnrollView.onPointerUp(sensorId);
|
||||
}
|
||||
}
|
||||
|
||||
private void showError(CharSequence error) {
|
||||
mTitleText.setText(error);
|
||||
mTitleText.setContentDescription(error);
|
||||
mSubTitleText.setContentDescription("");
|
||||
}
|
||||
|
||||
private void onRotationChanged(int newRotation) {
|
||||
if( (newRotation +2) % 4 == mRotation) {
|
||||
mRotation = newRotation;
|
||||
configLayout(newRotation);
|
||||
}
|
||||
}
|
||||
|
||||
private void configLayout(int newRotation) {
|
||||
final Activity activity = getActivity();
|
||||
if (newRotation == Surface.ROTATION_270) {
|
||||
RelativeLayout.LayoutParams iconLP = new RelativeLayout.LayoutParams(-2, -2);
|
||||
iconLP.addRule(RelativeLayout.ALIGN_PARENT_TOP);
|
||||
iconLP.addRule(RelativeLayout.END_OF, R.id.udfps_animation_view);
|
||||
iconLP.topMargin = (int) convertDpToPixel(76.64f, activity);
|
||||
iconLP.leftMargin = (int) convertDpToPixel(151.54f, activity);
|
||||
mIcon.setLayoutParams(iconLP);
|
||||
|
||||
RelativeLayout.LayoutParams titleLP = new RelativeLayout.LayoutParams(-1, -2);
|
||||
titleLP.addRule(RelativeLayout.ALIGN_PARENT_TOP);
|
||||
titleLP.addRule(RelativeLayout.END_OF, R.id.udfps_animation_view);
|
||||
titleLP.topMargin = (int) convertDpToPixel(138f, activity);
|
||||
titleLP.leftMargin = (int) convertDpToPixel(144f, activity);
|
||||
mTitleText.setLayoutParams(titleLP);
|
||||
|
||||
RelativeLayout.LayoutParams subtitleLP = new RelativeLayout.LayoutParams(-1, -2);
|
||||
subtitleLP.addRule(RelativeLayout.ALIGN_PARENT_TOP);
|
||||
subtitleLP.addRule(RelativeLayout.END_OF, R.id.udfps_animation_view);
|
||||
subtitleLP.topMargin = (int) convertDpToPixel(198f, activity);
|
||||
subtitleLP.leftMargin = (int) convertDpToPixel(144f, activity);
|
||||
mSubTitleText.setLayoutParams(subtitleLP);
|
||||
} else if (newRotation == Surface.ROTATION_90) {
|
||||
DisplayMetrics metrics = activity.getResources().getDisplayMetrics();
|
||||
RelativeLayout.LayoutParams iconLP = new RelativeLayout.LayoutParams(-2, -2);
|
||||
iconLP.addRule(RelativeLayout.ALIGN_PARENT_TOP);
|
||||
iconLP.addRule(RelativeLayout.ALIGN_PARENT_START);
|
||||
iconLP.topMargin = (int) convertDpToPixel(76.64f, activity);
|
||||
iconLP.leftMargin = (int) convertDpToPixel(71.99f, activity);
|
||||
mIcon.setLayoutParams(iconLP);
|
||||
|
||||
RelativeLayout.LayoutParams titleLP = new RelativeLayout.LayoutParams(
|
||||
metrics.widthPixels / 2, -2);
|
||||
titleLP.addRule(RelativeLayout.ALIGN_PARENT_TOP);
|
||||
titleLP.addRule(RelativeLayout.ALIGN_PARENT_START, R.id.udfps_animation_view);
|
||||
titleLP.topMargin = (int) convertDpToPixel(138f, activity);
|
||||
titleLP.leftMargin = (int) convertDpToPixel(66f, activity);
|
||||
mTitleText.setLayoutParams(titleLP);
|
||||
|
||||
RelativeLayout.LayoutParams subtitleLP = new RelativeLayout.LayoutParams(
|
||||
metrics.widthPixels / 2, -2);
|
||||
subtitleLP.addRule(RelativeLayout.ALIGN_PARENT_TOP);
|
||||
subtitleLP.addRule(RelativeLayout.ALIGN_PARENT_START);
|
||||
subtitleLP.topMargin = (int) convertDpToPixel(198f, activity);
|
||||
subtitleLP.leftMargin = (int) convertDpToPixel(66f, activity);
|
||||
mSubTitleText.setLayoutParams(subtitleLP);
|
||||
}
|
||||
|
||||
if (newRotation == Surface.ROTATION_90 || newRotation == Surface.ROTATION_270) {
|
||||
RelativeLayout.LayoutParams skipBtnLP =
|
||||
(RelativeLayout.LayoutParams) mSkipBtn.getLayoutParams();
|
||||
skipBtnLP.topMargin = (int) convertDpToPixel(26f, activity);
|
||||
skipBtnLP.leftMargin = (int) convertDpToPixel(54f, activity);
|
||||
mSkipBtn.requestLayout();
|
||||
}
|
||||
}
|
||||
|
||||
private float convertDpToPixel(float dp, Context context) {
|
||||
return dp * getDensity(context);
|
||||
}
|
||||
|
||||
private float getDensity(Context context) {
|
||||
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
|
||||
return metrics.density;
|
||||
}
|
||||
|
||||
// Give the user a chance to see progress completed before jumping to the next stage.
|
||||
private final Runnable mDelayedFinishRunnable = () -> mEnrollingViewModel.onEnrollingDone();
|
||||
}
|
@@ -0,0 +1,619 @@
|
||||
/*
|
||||
* Copyright (C) 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.settings.biometrics2.ui.view
|
||||
|
||||
import android.annotation.RawRes
|
||||
import android.content.Context
|
||||
import android.hardware.fingerprint.FingerprintManager.ENROLL_ENROLL
|
||||
import android.hardware.fingerprint.FingerprintManager.FINGERPRINT_ERROR_CANCELED
|
||||
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.Surface
|
||||
import android.view.Surface.ROTATION_270
|
||||
import android.view.Surface.ROTATION_90
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.Button
|
||||
import android.widget.ImageView
|
||||
import android.widget.RelativeLayout
|
||||
import android.widget.TextView
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import com.airbnb.lottie.LottieAnimationView
|
||||
import com.airbnb.lottie.LottieComposition
|
||||
import com.airbnb.lottie.LottieCompositionFactory
|
||||
import com.android.settings.R
|
||||
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.settings.biometrics2.ui.widget.UdfpsEnrollView
|
||||
import com.android.settingslib.display.DisplayDensityUtils
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
/**
|
||||
* Fragment is used to handle enrolling process for udfps
|
||||
*/
|
||||
class FingerprintEnrollEnrollingUdfpsFragment : Fragment() {
|
||||
|
||||
private var _enrollingViewModel: FingerprintEnrollEnrollingViewModel? = null
|
||||
private val enrollingViewModel: FingerprintEnrollEnrollingViewModel
|
||||
get() = _enrollingViewModel!!
|
||||
|
||||
private var _rotationViewModel: DeviceRotationViewModel? = null
|
||||
private val rotationViewModel: DeviceRotationViewModel
|
||||
get() = _rotationViewModel!!
|
||||
|
||||
private var _progressViewModel: FingerprintEnrollProgressViewModel? = null
|
||||
private val progressViewModel: FingerprintEnrollProgressViewModel
|
||||
get() = _progressViewModel!!
|
||||
|
||||
private var illustrationLottie: LottieAnimationView? = null
|
||||
|
||||
private var haveShownTipLottie = false
|
||||
private var haveShownLeftEdgeLottie = false
|
||||
private var haveShownRightEdgeLottie = false
|
||||
private var haveShownCenterLottie = false
|
||||
private var haveShownGuideLottie = false
|
||||
|
||||
private var enrollingUdfpsView: RelativeLayout? = null
|
||||
|
||||
private val titleText: TextView
|
||||
get() = enrollingUdfpsView!!.findViewById<TextView>(R.id.suc_layout_title)!!
|
||||
|
||||
private val subTitleText: TextView
|
||||
get() = enrollingUdfpsView!!.findViewById<TextView>(R.id.sud_layout_subtitle)!!
|
||||
|
||||
private val udfpsEnrollView: UdfpsEnrollView
|
||||
get() = enrollingUdfpsView!!.findViewById<UdfpsEnrollView>(R.id.udfps_animation_view)!!
|
||||
|
||||
private val skipBtn: Button
|
||||
get() = enrollingUdfpsView!!.findViewById<Button>(R.id.skip_btn)!!
|
||||
|
||||
private val icon: ImageView
|
||||
get() = enrollingUdfpsView!!.findViewById<ImageView>(R.id.sud_layout_icon)!!
|
||||
|
||||
private val shouldShowLottie: Boolean
|
||||
get() {
|
||||
val displayDensity = DisplayDensityUtils(requireContext())
|
||||
val currentDensityIndex: Int = displayDensity.currentIndexForDefaultDisplay
|
||||
val currentDensity: Int =
|
||||
displayDensity.defaultDisplayDensityValues[currentDensityIndex]
|
||||
val defaultDensity: Int = displayDensity.defaultDensityForDefaultDisplay
|
||||
return defaultDensity == currentDensity
|
||||
}
|
||||
|
||||
private val isAccessibilityEnabled
|
||||
get() = enrollingViewModel.isAccessibilityEnabled
|
||||
|
||||
private var rotation = -1
|
||||
|
||||
private val onSkipClickListener = View.OnClickListener { _: View? ->
|
||||
enrollingViewModel.setOnSkipPressed()
|
||||
cancelEnrollment()
|
||||
}
|
||||
|
||||
private val progressObserver: Observer<EnrollmentProgress> =
|
||||
Observer<EnrollmentProgress> { progress: EnrollmentProgress? ->
|
||||
progress?.let { onEnrollmentProgressChange(it) }
|
||||
}
|
||||
|
||||
private val helpMessageObserver: Observer<EnrollmentStatusMessage> =
|
||||
Observer<EnrollmentStatusMessage> { helpMessage: EnrollmentStatusMessage? ->
|
||||
helpMessage?.let { onEnrollmentHelp(it) }
|
||||
}
|
||||
private val errorMessageObserver: Observer<EnrollmentStatusMessage> =
|
||||
Observer<EnrollmentStatusMessage> { errorMessage: EnrollmentStatusMessage? ->
|
||||
errorMessage?.let { onEnrollmentError(it) }
|
||||
}
|
||||
|
||||
private val acquireObserver =
|
||||
Observer { isAcquiredGood: Boolean? -> isAcquiredGood?.let { onAcquired(it) } }
|
||||
|
||||
private val pointerDownObserver =
|
||||
Observer { sensorId: Int? -> sensorId?.let { onPointerDown(it) } }
|
||||
|
||||
private val pointerUpObserver =
|
||||
Observer { sensorId: Int? -> sensorId?.let { onPointerUp(it) } }
|
||||
|
||||
private val rotationObserver =
|
||||
Observer { rotation: Int? -> rotation?.let { onRotationChanged(it) } }
|
||||
|
||||
private val onBackPressedCallback: OnBackPressedCallback =
|
||||
object : OnBackPressedCallback(true) {
|
||||
override fun handleOnBackPressed() {
|
||||
isEnabled = false
|
||||
enrollingViewModel.setOnBackPressed()
|
||||
cancelEnrollment()
|
||||
}
|
||||
}
|
||||
|
||||
// Give the user a chance to see progress completed before jumping to the next stage.
|
||||
private val delayedFinishRunnable = Runnable { enrollingViewModel.onEnrollingDone() }
|
||||
|
||||
override fun onAttach(context: Context) {
|
||||
ViewModelProvider(requireActivity()).let { provider ->
|
||||
_enrollingViewModel = provider[FingerprintEnrollEnrollingViewModel::class.java]
|
||||
_rotationViewModel = provider[DeviceRotationViewModel::class.java]
|
||||
_progressViewModel = provider[FingerprintEnrollProgressViewModel::class.java]
|
||||
}
|
||||
super.onAttach(context)
|
||||
requireActivity().onBackPressedDispatcher.addCallback(onBackPressedCallback)
|
||||
}
|
||||
|
||||
override fun onDetach() {
|
||||
onBackPressedCallback.isEnabled = false
|
||||
super.onDetach()
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater, container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View = (inflater.inflate(
|
||||
R.layout.udfps_enroll_enrolling_v2, container, false
|
||||
) as RelativeLayout).also {
|
||||
enrollingUdfpsView = it
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
rotation = rotationViewModel.liveData.value!!
|
||||
updateIllustrationLottie(rotation)
|
||||
|
||||
requireActivity().bindFingerprintEnrollEnrollingUdfpsView(
|
||||
view = enrollingUdfpsView!!,
|
||||
sensorProperties = enrollingViewModel.firstFingerprintSensorPropertiesInternal!!,
|
||||
rotation = rotation,
|
||||
onSkipClickListener = onSkipClickListener,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
startEnrollment()
|
||||
updateProgress(false /* animate */, progressViewModel.progressLiveData.value!!)
|
||||
val msg: EnrollmentStatusMessage? = progressViewModel.helpMessageLiveData.value
|
||||
if (msg != null) {
|
||||
onEnrollmentHelp(msg)
|
||||
} else {
|
||||
updateTitleAndDescription()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
rotationViewModel.liveData.observe(this, rotationObserver)
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
rotationViewModel.liveData.removeObserver(rotationObserver)
|
||||
super.onPause()
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
removeEnrollmentObservers()
|
||||
if (!activity!!.isChangingConfigurations && progressViewModel.isEnrolling) {
|
||||
progressViewModel.cancelEnrollment()
|
||||
}
|
||||
super.onStop()
|
||||
}
|
||||
|
||||
private fun removeEnrollmentObservers() {
|
||||
preRemoveEnrollmentObservers()
|
||||
progressViewModel.errorMessageLiveData.removeObserver(errorMessageObserver)
|
||||
}
|
||||
|
||||
private fun preRemoveEnrollmentObservers() {
|
||||
progressViewModel.progressLiveData.removeObserver(progressObserver)
|
||||
progressViewModel.helpMessageLiveData.removeObserver(helpMessageObserver)
|
||||
progressViewModel.acquireLiveData.removeObserver(acquireObserver)
|
||||
progressViewModel.pointerDownLiveData.removeObserver(pointerDownObserver)
|
||||
progressViewModel.pointerUpLiveData.removeObserver(pointerUpObserver)
|
||||
}
|
||||
|
||||
private fun cancelEnrollment() {
|
||||
preRemoveEnrollmentObservers()
|
||||
progressViewModel.cancelEnrollment()
|
||||
}
|
||||
|
||||
private fun startEnrollment() {
|
||||
val startResult: Boolean =
|
||||
progressViewModel.startEnrollment(ENROLL_ENROLL)
|
||||
if (!startResult) {
|
||||
Log.e(TAG, "startEnrollment(), failed")
|
||||
}
|
||||
progressViewModel.progressLiveData.observe(this, progressObserver)
|
||||
progressViewModel.helpMessageLiveData.observe(this, helpMessageObserver)
|
||||
progressViewModel.errorMessageLiveData.observe(this, errorMessageObserver)
|
||||
progressViewModel.acquireLiveData.observe(this, acquireObserver)
|
||||
progressViewModel.pointerDownLiveData.observe(this, pointerDownObserver)
|
||||
progressViewModel.pointerUpLiveData.observe(this, pointerUpObserver)
|
||||
}
|
||||
|
||||
private fun updateProgress(animate: Boolean, enrollmentProgress: EnrollmentProgress) {
|
||||
if (!progressViewModel.isEnrolling) {
|
||||
Log.d(TAG, "Enrollment not started yet")
|
||||
return
|
||||
}
|
||||
val progress = getProgress(enrollmentProgress)
|
||||
if (progressViewModel.progressLiveData.value!!.steps != -1) {
|
||||
udfpsEnrollView.onEnrollmentProgress(
|
||||
enrollmentProgress.remaining,
|
||||
enrollmentProgress.steps
|
||||
)
|
||||
}
|
||||
if (animate) {
|
||||
animateProgress(progress)
|
||||
} else if (progress >= PROGRESS_BAR_MAX) {
|
||||
delayedFinishRunnable.run()
|
||||
}
|
||||
}
|
||||
|
||||
private fun getProgress(progress: EnrollmentProgress): Int {
|
||||
if (progress.steps == -1) {
|
||||
return 0
|
||||
}
|
||||
val displayProgress = 0.coerceAtLeast(progress.steps + 1 - progress.remaining)
|
||||
return PROGRESS_BAR_MAX * displayProgress / (progress.steps + 1)
|
||||
}
|
||||
|
||||
private fun animateProgress(progress: Int) {
|
||||
// UDFPS animations are owned by SystemUI
|
||||
if (progress >= PROGRESS_BAR_MAX) {
|
||||
// Wait for any animations in SysUI to finish, then proceed to next page
|
||||
activity!!.mainThreadHandler.postDelayed(delayedFinishRunnable, 400L)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateTitleAndDescription() {
|
||||
when (currentStage) {
|
||||
STAGE_CENTER -> {
|
||||
titleText.setText(R.string.security_settings_fingerprint_enroll_repeat_title)
|
||||
if (isAccessibilityEnabled || illustrationLottie == null) {
|
||||
subTitleText.setText(R.string.security_settings_udfps_enroll_start_message)
|
||||
} else if (!haveShownCenterLottie) {
|
||||
haveShownCenterLottie = true
|
||||
// Note: Update string reference when differentiate in between udfps & sfps
|
||||
illustrationLottie!!.contentDescription = getString(R.string.security_settings_sfps_enroll_finger_center_title)
|
||||
configureEnrollmentStage(R.raw.udfps_center_hint_lottie)
|
||||
}
|
||||
}
|
||||
|
||||
STAGE_GUIDED -> {
|
||||
titleText.setText(R.string.security_settings_fingerprint_enroll_repeat_title)
|
||||
if (isAccessibilityEnabled || illustrationLottie == null) {
|
||||
subTitleText.setText(
|
||||
R.string.security_settings_udfps_enroll_repeat_a11y_message
|
||||
)
|
||||
} else if (!haveShownGuideLottie) {
|
||||
haveShownGuideLottie = true
|
||||
illustrationLottie!!.contentDescription =
|
||||
getString(R.string.security_settings_fingerprint_enroll_repeat_message)
|
||||
// TODO(b/228100413) Could customize guided lottie animation
|
||||
configureEnrollmentStage(R.raw.udfps_center_hint_lottie)
|
||||
}
|
||||
}
|
||||
|
||||
STAGE_FINGERTIP -> {
|
||||
titleText.setText(R.string.security_settings_udfps_enroll_fingertip_title)
|
||||
if (!haveShownTipLottie && illustrationLottie != null) {
|
||||
haveShownTipLottie = true
|
||||
illustrationLottie!!.contentDescription =
|
||||
getString(R.string.security_settings_udfps_tip_fingerprint_help)
|
||||
configureEnrollmentStage(R.raw.udfps_tip_hint_lottie)
|
||||
}
|
||||
}
|
||||
|
||||
STAGE_LEFT_EDGE -> {
|
||||
titleText.setText(R.string.security_settings_udfps_enroll_left_edge_title)
|
||||
if (!haveShownLeftEdgeLottie && illustrationLottie != null) {
|
||||
haveShownLeftEdgeLottie = true
|
||||
illustrationLottie!!.contentDescription =
|
||||
getString(R.string.security_settings_udfps_side_fingerprint_help)
|
||||
configureEnrollmentStage(R.raw.udfps_left_edge_hint_lottie)
|
||||
} else if (illustrationLottie == null) {
|
||||
if (isStageHalfCompleted) {
|
||||
subTitleText.setText(
|
||||
R.string.security_settings_fingerprint_enroll_repeat_message
|
||||
)
|
||||
} else {
|
||||
subTitleText.setText(R.string.security_settings_udfps_enroll_edge_message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
STAGE_RIGHT_EDGE -> {
|
||||
titleText.setText(R.string.security_settings_udfps_enroll_right_edge_title)
|
||||
if (!haveShownRightEdgeLottie && illustrationLottie != null) {
|
||||
haveShownRightEdgeLottie = true
|
||||
illustrationLottie!!.contentDescription =
|
||||
getString(R.string.security_settings_udfps_side_fingerprint_help)
|
||||
configureEnrollmentStage(R.raw.udfps_right_edge_hint_lottie)
|
||||
} else if (illustrationLottie == null) {
|
||||
if (isStageHalfCompleted) {
|
||||
subTitleText.setText(
|
||||
R.string.security_settings_fingerprint_enroll_repeat_message
|
||||
)
|
||||
} else {
|
||||
subTitleText.setText(R.string.security_settings_udfps_enroll_edge_message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
STAGE_UNKNOWN -> {
|
||||
titleText.setText(R.string.security_settings_fingerprint_enroll_udfps_title)
|
||||
subTitleText.setText(R.string.security_settings_udfps_enroll_start_message)
|
||||
val description: CharSequence = getString(
|
||||
R.string.security_settings_udfps_enroll_a11y
|
||||
)
|
||||
requireActivity().title = description
|
||||
}
|
||||
|
||||
else -> {
|
||||
titleText.setText(R.string.security_settings_fingerprint_enroll_udfps_title)
|
||||
subTitleText.setText(R.string.security_settings_udfps_enroll_start_message)
|
||||
val description: CharSequence = getString(
|
||||
R.string.security_settings_udfps_enroll_a11y
|
||||
)
|
||||
requireActivity().title = description
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateIllustrationLottie(@Surface.Rotation rotation: Int) {
|
||||
if (rotation == ROTATION_90 || rotation == ROTATION_270) {
|
||||
illustrationLottie = null
|
||||
} else if (shouldShowLottie) {
|
||||
illustrationLottie =
|
||||
enrollingUdfpsView!!.findViewById<LottieAnimationView>(R.id.illustration_lottie)
|
||||
}
|
||||
}
|
||||
|
||||
private val currentStage: Int
|
||||
get() {
|
||||
val progress = progressViewModel.progressLiveData.value!!
|
||||
if (progress.steps == -1) {
|
||||
return STAGE_UNKNOWN
|
||||
}
|
||||
val progressSteps: Int = progress.steps - progress.remaining
|
||||
return if (progressSteps < getStageThresholdSteps(0)) {
|
||||
STAGE_CENTER
|
||||
} else if (progressSteps < getStageThresholdSteps(1)) {
|
||||
STAGE_GUIDED
|
||||
} else if (progressSteps < getStageThresholdSteps(2)) {
|
||||
STAGE_FINGERTIP
|
||||
} else if (progressSteps < getStageThresholdSteps(3)) {
|
||||
STAGE_LEFT_EDGE
|
||||
} else {
|
||||
STAGE_RIGHT_EDGE
|
||||
}
|
||||
}
|
||||
|
||||
private val isStageHalfCompleted: Boolean
|
||||
get() {
|
||||
val progress: EnrollmentProgress = progressViewModel.progressLiveData.value!!
|
||||
if (progress.steps == -1) {
|
||||
return false
|
||||
}
|
||||
val progressSteps: Int = progress.steps - progress.remaining
|
||||
var prevThresholdSteps = 0
|
||||
for (i in 0 until enrollingViewModel.getEnrollStageCount()) {
|
||||
val thresholdSteps = getStageThresholdSteps(i)
|
||||
if (progressSteps in prevThresholdSteps until thresholdSteps) {
|
||||
val adjustedProgress = progressSteps - prevThresholdSteps
|
||||
val adjustedThreshold = thresholdSteps - prevThresholdSteps
|
||||
return adjustedProgress >= adjustedThreshold / 2
|
||||
}
|
||||
prevThresholdSteps = thresholdSteps
|
||||
}
|
||||
|
||||
// After last enrollment step.
|
||||
return true
|
||||
}
|
||||
|
||||
private fun getStageThresholdSteps(index: Int): Int {
|
||||
val progress: EnrollmentProgress = progressViewModel.progressLiveData.value!!
|
||||
if (progress.steps == -1) {
|
||||
Log.w(TAG, "getStageThresholdSteps: Enrollment not started yet")
|
||||
return 1
|
||||
}
|
||||
return (progress.steps * enrollingViewModel.getEnrollStageThreshold(index)).roundToInt()
|
||||
}
|
||||
|
||||
private fun configureEnrollmentStage(@RawRes lottie: Int) {
|
||||
subTitleText.text = ""
|
||||
LottieCompositionFactory.fromRawRes(activity, lottie)
|
||||
.addListener { c: LottieComposition ->
|
||||
illustrationLottie?.let {
|
||||
it.setComposition(c)
|
||||
it.visibility = View.VISIBLE
|
||||
it.playAnimation()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun onEnrollmentProgressChange(progress: EnrollmentProgress) {
|
||||
updateProgress(true /* animate */, progress)
|
||||
updateTitleAndDescription()
|
||||
if (isAccessibilityEnabled) {
|
||||
val steps: Int = progress.steps
|
||||
val remaining: Int = progress.remaining
|
||||
val percent = ((steps - remaining).toFloat() / steps.toFloat() * 100).toInt()
|
||||
val announcement: CharSequence = activity!!.getString(
|
||||
R.string.security_settings_udfps_enroll_progress_a11y_message, percent
|
||||
)
|
||||
enrollingViewModel.sendAccessibilityEvent(announcement)
|
||||
}
|
||||
}
|
||||
|
||||
private fun onEnrollmentHelp(helpMessage: EnrollmentStatusMessage) {
|
||||
val helpStr: CharSequence = helpMessage.str
|
||||
if (helpStr.isNotEmpty()) {
|
||||
showError(helpStr)
|
||||
udfpsEnrollView.onEnrollmentHelp()
|
||||
}
|
||||
}
|
||||
|
||||
private fun onEnrollmentError(errorMessage: EnrollmentStatusMessage) {
|
||||
removeEnrollmentObservers()
|
||||
if (enrollingViewModel.onBackPressed
|
||||
&& errorMessage.msgId == FINGERPRINT_ERROR_CANCELED
|
||||
) {
|
||||
enrollingViewModel.onCancelledDueToOnBackPressed()
|
||||
} else if (enrollingViewModel.onSkipPressed
|
||||
&& errorMessage.msgId == FINGERPRINT_ERROR_CANCELED
|
||||
) {
|
||||
enrollingViewModel.onCancelledDueToOnSkipPressed()
|
||||
} else {
|
||||
val errMsgId: Int = errorMessage.msgId
|
||||
enrollingViewModel.showErrorDialog(
|
||||
FingerprintEnrollEnrollingViewModel.ErrorDialogData(
|
||||
getString(FingerprintErrorDialog.getErrorMessage(errMsgId)),
|
||||
getString(FingerprintErrorDialog.getErrorTitle(errMsgId)),
|
||||
errMsgId
|
||||
)
|
||||
)
|
||||
progressViewModel.cancelEnrollment()
|
||||
}
|
||||
}
|
||||
|
||||
private fun onAcquired(isAcquiredGood: Boolean) {
|
||||
udfpsEnrollView.onAcquired(isAcquiredGood)
|
||||
}
|
||||
|
||||
private fun onPointerDown(sensorId: Int) {
|
||||
udfpsEnrollView.onPointerDown(sensorId)
|
||||
}
|
||||
|
||||
private fun onPointerUp(sensorId: Int) {
|
||||
udfpsEnrollView.onPointerUp(sensorId)
|
||||
}
|
||||
|
||||
private fun showError(error: CharSequence) {
|
||||
titleText.text = error
|
||||
titleText.contentDescription = error
|
||||
subTitleText.contentDescription = ""
|
||||
}
|
||||
|
||||
private fun onRotationChanged(newRotation: Int) {
|
||||
if ((newRotation + 2) % 4 == rotation) {
|
||||
rotation = newRotation
|
||||
requireContext().configLayout(newRotation, titleText, subTitleText, icon, skipBtn)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val TAG = "FingerprintEnrollEnrollingUdfpsFragment"
|
||||
private const val PROGRESS_BAR_MAX = 10000
|
||||
private const val STAGE_UNKNOWN = -1
|
||||
private const val STAGE_CENTER = 0
|
||||
private const val STAGE_GUIDED = 1
|
||||
private const val STAGE_FINGERTIP = 2
|
||||
private const val STAGE_LEFT_EDGE = 3
|
||||
private const val STAGE_RIGHT_EDGE = 4
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun FragmentActivity.bindFingerprintEnrollEnrollingUdfpsView(
|
||||
view: RelativeLayout,
|
||||
sensorProperties: FingerprintSensorPropertiesInternal,
|
||||
@Surface.Rotation rotation: Int,
|
||||
onSkipClickListener: View.OnClickListener
|
||||
) {
|
||||
view.findViewById<UdfpsEnrollView>(R.id.udfps_animation_view)!!.setSensorProperties(
|
||||
sensorProperties
|
||||
)
|
||||
|
||||
val titleText = view.findViewById<TextView>(R.id.suc_layout_title)!!
|
||||
val subTitleText = view.findViewById<TextView>(R.id.sud_layout_subtitle)!!
|
||||
val icon = view.findViewById<ImageView>(R.id.sud_layout_icon)!!
|
||||
val skipBtn = view.findViewById<Button>(R.id.skip_btn)!!.also {
|
||||
it.setOnClickListener(onSkipClickListener)
|
||||
}
|
||||
configLayout(rotation, titleText, subTitleText, icon, skipBtn)
|
||||
}
|
||||
|
||||
private fun Context.configLayout(
|
||||
@Surface.Rotation newRotation: Int,
|
||||
titleText: TextView,
|
||||
subTitleText: TextView,
|
||||
icon: ImageView,
|
||||
skipBtn: Button
|
||||
) {
|
||||
if (newRotation == ROTATION_270) {
|
||||
val iconLP = RelativeLayout.LayoutParams(-2, -2)
|
||||
iconLP.addRule(RelativeLayout.ALIGN_PARENT_TOP)
|
||||
iconLP.addRule(RelativeLayout.END_OF, R.id.udfps_animation_view)
|
||||
iconLP.topMargin = convertDpToPixel(76.64f)
|
||||
iconLP.leftMargin = convertDpToPixel(151.54f)
|
||||
icon.layoutParams = iconLP
|
||||
val titleLP = RelativeLayout.LayoutParams(-1, -2)
|
||||
titleLP.addRule(RelativeLayout.ALIGN_PARENT_TOP)
|
||||
titleLP.addRule(RelativeLayout.END_OF, R.id.udfps_animation_view)
|
||||
titleLP.topMargin = convertDpToPixel(138f)
|
||||
titleLP.leftMargin = convertDpToPixel(144f)
|
||||
titleText.layoutParams = titleLP
|
||||
val subtitleLP = RelativeLayout.LayoutParams(-1, -2)
|
||||
subtitleLP.addRule(RelativeLayout.ALIGN_PARENT_TOP)
|
||||
subtitleLP.addRule(RelativeLayout.END_OF, R.id.udfps_animation_view)
|
||||
subtitleLP.topMargin = convertDpToPixel(198f)
|
||||
subtitleLP.leftMargin = convertDpToPixel(144f)
|
||||
subTitleText.layoutParams = subtitleLP
|
||||
} else if (newRotation == ROTATION_90) {
|
||||
val metrics = resources.displayMetrics
|
||||
val iconLP = RelativeLayout.LayoutParams(-2, -2)
|
||||
iconLP.addRule(RelativeLayout.ALIGN_PARENT_TOP)
|
||||
iconLP.addRule(RelativeLayout.ALIGN_PARENT_START)
|
||||
iconLP.topMargin = convertDpToPixel(76.64f)
|
||||
iconLP.leftMargin = convertDpToPixel(71.99f)
|
||||
icon.layoutParams = iconLP
|
||||
val titleLP = RelativeLayout.LayoutParams(
|
||||
metrics.widthPixels / 2, -2
|
||||
)
|
||||
titleLP.addRule(RelativeLayout.ALIGN_PARENT_TOP)
|
||||
titleLP.addRule(RelativeLayout.ALIGN_PARENT_START, R.id.udfps_animation_view)
|
||||
titleLP.topMargin = convertDpToPixel(138f)
|
||||
titleLP.leftMargin = convertDpToPixel(66f)
|
||||
titleText.layoutParams = titleLP
|
||||
val subtitleLP = RelativeLayout.LayoutParams(
|
||||
metrics.widthPixels / 2, -2
|
||||
)
|
||||
subtitleLP.addRule(RelativeLayout.ALIGN_PARENT_TOP)
|
||||
subtitleLP.addRule(RelativeLayout.ALIGN_PARENT_START)
|
||||
subtitleLP.topMargin = convertDpToPixel(198f)
|
||||
subtitleLP.leftMargin = convertDpToPixel(66f)
|
||||
subTitleText.layoutParams = subtitleLP
|
||||
}
|
||||
if (newRotation == ROTATION_90 || newRotation == ROTATION_270) {
|
||||
val skipBtnLP = skipBtn.layoutParams as RelativeLayout.LayoutParams
|
||||
skipBtnLP.topMargin = convertDpToPixel(26f)
|
||||
skipBtnLP.leftMargin = convertDpToPixel(54f)
|
||||
skipBtn.requestLayout()
|
||||
}
|
||||
}
|
||||
|
||||
fun Context.convertDpToPixel(dp: Float): Int {
|
||||
return (dp * resources.displayMetrics.density).toInt()
|
||||
}
|
Reference in New Issue
Block a user