Refactor FingerprintEnrollEnrolling to fragment
Bug: b/260957933 Test: NA Change-Id: I8f704297a2a53ddf39734e0fefe258a123255341
This commit is contained in:
@@ -0,0 +1,47 @@
|
|||||||
|
/*
|
||||||
|
* 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.data.repository;
|
||||||
|
|
||||||
|
import android.view.accessibility.AccessibilityManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This repository is used to call all APIs in {@link AccessibilityManager}
|
||||||
|
*/
|
||||||
|
public class AccessibilityRepository {
|
||||||
|
|
||||||
|
private final AccessibilityManager mAccessibilityManager;
|
||||||
|
|
||||||
|
public AccessibilityRepository(AccessibilityManager accessibilityManager) {
|
||||||
|
mAccessibilityManager = accessibilityManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Requests interruption of the accessibility feedback from all accessibility services.
|
||||||
|
*/
|
||||||
|
public void interrupt() {
|
||||||
|
mAccessibilityManager.interrupt();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns if the {@link AccessibilityManager} is enabled.
|
||||||
|
*
|
||||||
|
* @return True if this {@link AccessibilityManager} is enabled, false otherwise.
|
||||||
|
*/
|
||||||
|
public boolean isEnabled() {
|
||||||
|
return mAccessibilityManager.isEnabled();
|
||||||
|
}
|
||||||
|
}
|
@@ -42,7 +42,8 @@ import java.util.List;
|
|||||||
public class FingerprintRepository {
|
public class FingerprintRepository {
|
||||||
|
|
||||||
private static final String TAG = "FingerprintRepository";
|
private static final String TAG = "FingerprintRepository";
|
||||||
@NonNull private final FingerprintManager mFingerprintManager;
|
@NonNull
|
||||||
|
private final FingerprintManager mFingerprintManager;
|
||||||
|
|
||||||
private List<FingerprintSensorPropertiesInternal> mSensorPropertiesCache;
|
private List<FingerprintSensorPropertiesInternal> mSensorPropertiesCache;
|
||||||
|
|
||||||
@@ -130,4 +131,18 @@ public class FingerprintRepository {
|
|||||||
return RestrictedLockUtilsInternal.checkIfKeyguardFeaturesDisabled(
|
return RestrictedLockUtilsInternal.checkIfKeyguardFeaturesDisabled(
|
||||||
context, DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT, userId) != null;
|
context, DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT, userId) != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get fingerprint enroll stage threshold
|
||||||
|
*/
|
||||||
|
public float getEnrollStageThreshold(int index) {
|
||||||
|
return mFingerprintManager.getEnrollStageThreshold(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get fingerprint enroll stage count
|
||||||
|
*/
|
||||||
|
public int getEnrollStageCount() {
|
||||||
|
return mFingerprintManager.getEnrollStageCount();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
* 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.data.repository;
|
||||||
|
|
||||||
|
import android.annotation.NonNull;
|
||||||
|
import android.os.VibrationAttributes;
|
||||||
|
import android.os.VibrationEffect;
|
||||||
|
import android.os.Vibrator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This repository is used to call all APIs in {@link Vibrator}
|
||||||
|
*/
|
||||||
|
public class VibratorRepository {
|
||||||
|
|
||||||
|
private final Vibrator mVibrator;
|
||||||
|
|
||||||
|
public VibratorRepository(Vibrator vibrator) {
|
||||||
|
mVibrator = vibrator;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 vibrate(int uid, String opPkg, @NonNull VibrationEffect vibe,
|
||||||
|
String reason, @NonNull VibrationAttributes attributes) {
|
||||||
|
mVibrator.vibrate(uid, opPkg, vibe, reason, attributes);
|
||||||
|
}
|
||||||
|
}
|
@@ -21,7 +21,9 @@ import android.app.Application;
|
|||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.android.settings.biometrics2.data.repository.AccessibilityRepository;
|
||||||
import com.android.settings.biometrics2.data.repository.FingerprintRepository;
|
import com.android.settings.biometrics2.data.repository.FingerprintRepository;
|
||||||
|
import com.android.settings.biometrics2.data.repository.VibratorRepository;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface for BiometricsRepositoryProvider
|
* Interface for BiometricsRepositoryProvider
|
||||||
@@ -33,4 +35,16 @@ public interface BiometricsRepositoryProvider {
|
|||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
FingerprintRepository getFingerprintRepository(@NonNull Application application);
|
FingerprintRepository getFingerprintRepository(@NonNull Application application);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get VibtatorRepository
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
VibratorRepository getVibratorRepository(@NonNull Application application);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get AccessibilityRepository
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
AccessibilityRepository getAccessibilityRepository(@NonNull Application application);
|
||||||
}
|
}
|
||||||
|
@@ -18,12 +18,16 @@ package com.android.settings.biometrics2.factory;
|
|||||||
|
|
||||||
import android.app.Application;
|
import android.app.Application;
|
||||||
import android.hardware.fingerprint.FingerprintManager;
|
import android.hardware.fingerprint.FingerprintManager;
|
||||||
|
import android.os.Vibrator;
|
||||||
|
import android.view.accessibility.AccessibilityManager;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import com.android.settings.Utils;
|
import com.android.settings.Utils;
|
||||||
|
import com.android.settings.biometrics2.data.repository.AccessibilityRepository;
|
||||||
import com.android.settings.biometrics2.data.repository.FingerprintRepository;
|
import com.android.settings.biometrics2.data.repository.FingerprintRepository;
|
||||||
|
import com.android.settings.biometrics2.data.repository.VibratorRepository;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implementation for BiometricsRepositoryProvider
|
* Implementation for BiometricsRepositoryProvider
|
||||||
@@ -31,6 +35,8 @@ import com.android.settings.biometrics2.data.repository.FingerprintRepository;
|
|||||||
public class BiometricsRepositoryProviderImpl implements BiometricsRepositoryProvider {
|
public class BiometricsRepositoryProviderImpl implements BiometricsRepositoryProvider {
|
||||||
|
|
||||||
private static volatile FingerprintRepository sFingerprintRepository;
|
private static volatile FingerprintRepository sFingerprintRepository;
|
||||||
|
private static volatile VibratorRepository sVibratorRepository;
|
||||||
|
private static volatile AccessibilityRepository sAccessibilityRepository;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get FingerprintRepository
|
* Get FingerprintRepository
|
||||||
@@ -52,4 +58,49 @@ public class BiometricsRepositoryProviderImpl implements BiometricsRepositoryPro
|
|||||||
}
|
}
|
||||||
return sFingerprintRepository;
|
return sFingerprintRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get VibratorRepository
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public VibratorRepository getVibratorRepository(@NonNull Application application) {
|
||||||
|
|
||||||
|
final Vibrator vibrator = application.getSystemService(Vibrator.class);
|
||||||
|
if (vibrator == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sVibratorRepository == null) {
|
||||||
|
synchronized (VibratorRepository.class) {
|
||||||
|
if (sVibratorRepository == null) {
|
||||||
|
sVibratorRepository = new VibratorRepository(vibrator);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sVibratorRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get AccessibilityRepository
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public AccessibilityRepository getAccessibilityRepository(@NonNull Application application) {
|
||||||
|
|
||||||
|
final AccessibilityManager accessibilityManager = application.getSystemService(
|
||||||
|
AccessibilityManager.class);
|
||||||
|
if (accessibilityManager == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sAccessibilityRepository == null) {
|
||||||
|
synchronized (AccessibilityRepository.class) {
|
||||||
|
if (sAccessibilityRepository == null) {
|
||||||
|
sAccessibilityRepository = new AccessibilityRepository(accessibilityManager);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sAccessibilityRepository;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -28,12 +28,15 @@ import androidx.lifecycle.viewmodel.CreationExtras;
|
|||||||
|
|
||||||
import com.android.internal.widget.LockPatternUtils;
|
import com.android.internal.widget.LockPatternUtils;
|
||||||
import com.android.settings.biometrics.fingerprint.FingerprintUpdater;
|
import com.android.settings.biometrics.fingerprint.FingerprintUpdater;
|
||||||
|
import com.android.settings.biometrics2.data.repository.AccessibilityRepository;
|
||||||
import com.android.settings.biometrics2.data.repository.FingerprintRepository;
|
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 com.android.settings.biometrics2.ui.model.EnrollmentRequest;
|
||||||
import com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel;
|
import com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel;
|
||||||
import com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel.ChallengeGenerator;
|
import com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel.ChallengeGenerator;
|
||||||
import com.android.settings.biometrics2.ui.viewmodel.DeviceFoldedViewModel;
|
import com.android.settings.biometrics2.ui.viewmodel.DeviceFoldedViewModel;
|
||||||
import com.android.settings.biometrics2.ui.viewmodel.DeviceRotationViewModel;
|
import com.android.settings.biometrics2.ui.viewmodel.DeviceRotationViewModel;
|
||||||
|
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollEnrollingViewModel;
|
||||||
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollFindSensorViewModel;
|
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollFindSensorViewModel;
|
||||||
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollIntroViewModel;
|
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollIntroViewModel;
|
||||||
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollProgressViewModel;
|
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollProgressViewModel;
|
||||||
@@ -109,6 +112,16 @@ public class BiometricsViewModelFactory implements ViewModelProvider.Factory {
|
|||||||
return (T) new FingerprintEnrollProgressViewModel(application,
|
return (T) new FingerprintEnrollProgressViewModel(application,
|
||||||
new FingerprintUpdater(application), userId);
|
new FingerprintUpdater(application), userId);
|
||||||
}
|
}
|
||||||
|
} else if (modelClass.isAssignableFrom(FingerprintEnrollEnrollingViewModel.class)) {
|
||||||
|
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,
|
||||||
|
accessibility, vibrator);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return create(modelClass);
|
return create(modelClass);
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,272 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2023 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.android.settings.biometrics2.ui.view;
|
||||||
|
|
||||||
|
import android.app.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.util.Log;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.MotionEvent;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.view.animation.AnimationUtils;
|
||||||
|
import android.view.animation.Interpolator;
|
||||||
|
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.ViewModelProvider;
|
||||||
|
|
||||||
|
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.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;
|
||||||
|
import com.google.android.setupcompat.template.FooterButton;
|
||||||
|
import com.google.android.setupdesign.GlifLayout;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fragment is used to handle enrolling process for rfps
|
||||||
|
*/
|
||||||
|
public class FingerprintEnrollEnrollingRfpsFragment extends Fragment {
|
||||||
|
|
||||||
|
private static final String TAG = FingerprintEnrollEnrollingRfpsFragment.class.getSimpleName();
|
||||||
|
|
||||||
|
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 FingerprintEnrollEnrollingViewModel mEnrollingViewModel;
|
||||||
|
private DeviceRotationViewModel mRotationViewModel;
|
||||||
|
private FingerprintEnrollProgressViewModel mProgressViewModel;
|
||||||
|
|
||||||
|
private Interpolator mFastOutSlowInInterpolator;
|
||||||
|
private Interpolator mLinearOutSlowInInterpolator;
|
||||||
|
private Interpolator mFastOutLinearInInterpolator;
|
||||||
|
private boolean mAnimationCancelled;
|
||||||
|
|
||||||
|
private View mView;
|
||||||
|
private ProgressBar mProgressBar;
|
||||||
|
private TextView mErrorText;
|
||||||
|
private FooterBarMixin mFooterBarMixin;
|
||||||
|
private AnimatedVectorDrawable mIconAnimationDrawable;
|
||||||
|
private AnimatedVectorDrawable mIconBackgroundBlinksDrawable;
|
||||||
|
|
||||||
|
private LottieAnimationView mIllustrationLottie;
|
||||||
|
private boolean mShouldShowLottie;
|
||||||
|
private boolean mIsAccessibilityEnabled;
|
||||||
|
|
||||||
|
private boolean mHaveShownSfpsNoAnimationLottie;
|
||||||
|
private boolean mHaveShownSfpsCenterLottie;
|
||||||
|
private boolean mHaveShownSfpsTipLottie;
|
||||||
|
private boolean mHaveShownSfpsLeftEdgeLottie;
|
||||||
|
private boolean mHaveShownSfpsRightEdgeLottie;
|
||||||
|
|
||||||
|
private final View.OnClickListener mOnSkipClickListener =
|
||||||
|
(v) -> mEnrollingViewModel.onSkipButtonClick();
|
||||||
|
|
||||||
|
private int mIconTouchCount;
|
||||||
|
|
||||||
|
@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);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
mIsAccessibilityEnabled = mEnrollingViewModel.isAccessibilityEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||||
|
Bundle savedInstanceState) {
|
||||||
|
mView = initRfpsLayout(inflater, container);
|
||||||
|
return mView;
|
||||||
|
}
|
||||||
|
|
||||||
|
private View initRfpsLayout(LayoutInflater inflater, ViewGroup container) {
|
||||||
|
final View containView = 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);
|
||||||
|
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));
|
||||||
|
|
||||||
|
mErrorText = containView.findViewById(R.id.error_text);
|
||||||
|
mProgressBar = containView.findViewById(R.id.fingerprint_progress_bar);
|
||||||
|
mFooterBarMixin = ((GlifLayout) containView).getMixin(FooterBarMixin.class);
|
||||||
|
mFooterBarMixin.setSecondaryButton(
|
||||||
|
new FooterButton.Builder(activity)
|
||||||
|
.setText(R.string.security_settings_fingerprint_enroll_enrolling_skip)
|
||||||
|
.setListener(mOnSkipClickListener)
|
||||||
|
.setButtonType(FooterButton.ButtonType.SKIP)
|
||||||
|
.setTheme(R.style.SudGlifButton_Secondary)
|
||||||
|
.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(
|
||||||
|
activity, android.R.interpolator.linear_out_slow_in);
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void startIconAnimation() {
|
||||||
|
if (mIconAnimationDrawable != null) {
|
||||||
|
mIconAnimationDrawable.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void stopIconAnimation() {
|
||||||
|
mAnimationCancelled = true;
|
||||||
|
if (mIconAnimationDrawable != null) {
|
||||||
|
mIconAnimationDrawable.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
showIconTouchDialog();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private final Animatable2.AnimationCallback mIconAnimationCallback =
|
||||||
|
new Animatable2.AnimationCallback() {
|
||||||
|
@Override
|
||||||
|
public void onAnimationEnd(Drawable d) {
|
||||||
|
if (mAnimationCancelled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start animation after it has ended.
|
||||||
|
mProgressBar.post(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
startIconAnimation();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
@@ -0,0 +1,410 @@
|
|||||||
|
/*
|
||||||
|
* 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.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.util.Log;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.MotionEvent;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.view.animation.AnimationUtils;
|
||||||
|
import android.view.animation.Interpolator;
|
||||||
|
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.ViewModelProvider;
|
||||||
|
|
||||||
|
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.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.airbnb.lottie.LottieCompositionFactory;
|
||||||
|
import com.google.android.setupcompat.template.FooterBarMixin;
|
||||||
|
import com.google.android.setupcompat.template.FooterButton;
|
||||||
|
import com.google.android.setupdesign.GlifLayout;
|
||||||
|
import com.google.android.setupdesign.template.DescriptionMixin;
|
||||||
|
import com.google.android.setupdesign.template.HeaderMixin;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fragment is used to handle enrolling process for sfps
|
||||||
|
*/
|
||||||
|
public class FingerprintEnrollEnrollingSfpsFragment extends Fragment {
|
||||||
|
|
||||||
|
private static final String TAG = FingerprintEnrollEnrollingSfpsFragment.class.getSimpleName();
|
||||||
|
|
||||||
|
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;
|
||||||
|
private static final int SFPS_STAGE_CENTER = 1;
|
||||||
|
private static final int SFPS_STAGE_FINGERTIP = 2;
|
||||||
|
private static final int SFPS_STAGE_LEFT_EDGE = 3;
|
||||||
|
private static final int SFPS_STAGE_RIGHT_EDGE = 4;
|
||||||
|
|
||||||
|
private FingerprintEnrollEnrollingViewModel mEnrollingViewModel;
|
||||||
|
private DeviceRotationViewModel mRotationViewModel;
|
||||||
|
private FingerprintEnrollProgressViewModel mProgressViewModel;
|
||||||
|
|
||||||
|
private Interpolator mFastOutSlowInInterpolator;
|
||||||
|
private Interpolator mLinearOutSlowInInterpolator;
|
||||||
|
private Interpolator mFastOutLinearInInterpolator;
|
||||||
|
private boolean mAnimationCancelled;
|
||||||
|
|
||||||
|
private View mView;
|
||||||
|
private ProgressBar mProgressBar;
|
||||||
|
private TextView mErrorText;
|
||||||
|
private FooterBarMixin mFooterBarMixin;
|
||||||
|
private AnimatedVectorDrawable mIconAnimationDrawable;
|
||||||
|
private AnimatedVectorDrawable mIconBackgroundBlinksDrawable;
|
||||||
|
|
||||||
|
private LottieAnimationView mIllustrationLottie;
|
||||||
|
private boolean mShouldShowLottie;
|
||||||
|
private boolean mIsAccessibilityEnabled;
|
||||||
|
|
||||||
|
private boolean mHaveShownSfpsNoAnimationLottie;
|
||||||
|
private boolean mHaveShownSfpsCenterLottie;
|
||||||
|
private boolean mHaveShownSfpsTipLottie;
|
||||||
|
private boolean mHaveShownSfpsLeftEdgeLottie;
|
||||||
|
private boolean mHaveShownSfpsRightEdgeLottie;
|
||||||
|
|
||||||
|
private final View.OnClickListener mOnSkipClickListener =
|
||||||
|
(v) -> mEnrollingViewModel.onSkipButtonClick();
|
||||||
|
|
||||||
|
private int mIconTouchCount;
|
||||||
|
|
||||||
|
@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);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
mIsAccessibilityEnabled = mEnrollingViewModel.isAccessibilityEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
@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);
|
||||||
|
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));
|
||||||
|
|
||||||
|
mErrorText = containView.findViewById(R.id.error_text);
|
||||||
|
mProgressBar = containView.findViewById(R.id.fingerprint_progress_bar);
|
||||||
|
mFooterBarMixin = ((GlifLayout) containView).getMixin(FooterBarMixin.class);
|
||||||
|
mFooterBarMixin.setSecondaryButton(
|
||||||
|
new FooterButton.Builder(activity)
|
||||||
|
.setText(R.string.security_settings_fingerprint_enroll_enrolling_skip)
|
||||||
|
.setListener(mOnSkipClickListener)
|
||||||
|
.setButtonType(FooterButton.ButtonType.SKIP)
|
||||||
|
.setTheme(R.style.SudGlifButton_Secondary)
|
||||||
|
.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(
|
||||||
|
activity, android.R.interpolator.linear_out_slow_in);
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateTitleAndDescription() {
|
||||||
|
|
||||||
|
final Activity activity = getActivity();
|
||||||
|
final GlifLayoutHelper glifLayoutHelper = new GlifLayoutHelper(activity,
|
||||||
|
(GlifLayout) mView);
|
||||||
|
|
||||||
|
if (mIsAccessibilityEnabled) {
|
||||||
|
mEnrollingViewModel.clearTalkback();
|
||||||
|
((GlifLayout) mView).getDescriptionTextView().setAccessibilityLiveRegion(
|
||||||
|
View.ACCESSIBILITY_LIVE_REGION_POLITE);
|
||||||
|
}
|
||||||
|
switch (getCurrentSfpsStage()) {
|
||||||
|
case SFPS_STAGE_NO_ANIMATION:
|
||||||
|
glifLayoutHelper.setHeaderText(
|
||||||
|
R.string.security_settings_fingerprint_enroll_repeat_title);
|
||||||
|
if (!mHaveShownSfpsNoAnimationLottie && mIllustrationLottie != null) {
|
||||||
|
mHaveShownSfpsNoAnimationLottie = true;
|
||||||
|
mIllustrationLottie.setContentDescription(
|
||||||
|
getString(
|
||||||
|
R.string.security_settings_sfps_animation_a11y_label,
|
||||||
|
0
|
||||||
|
)
|
||||||
|
);
|
||||||
|
configureEnrollmentStage(
|
||||||
|
getString(R.string.security_settings_sfps_enroll_start_message),
|
||||||
|
R.raw.sfps_lottie_no_animation
|
||||||
|
);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SFPS_STAGE_CENTER:
|
||||||
|
glifLayoutHelper.setHeaderText(
|
||||||
|
R.string.security_settings_sfps_enroll_finger_center_title);
|
||||||
|
if (!mHaveShownSfpsCenterLottie && mIllustrationLottie != null) {
|
||||||
|
mHaveShownSfpsCenterLottie = true;
|
||||||
|
configureEnrollmentStage(
|
||||||
|
getString(R.string.security_settings_sfps_enroll_start_message),
|
||||||
|
R.raw.sfps_lottie_pad_center
|
||||||
|
);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SFPS_STAGE_FINGERTIP:
|
||||||
|
glifLayoutHelper.setHeaderText(
|
||||||
|
R.string.security_settings_sfps_enroll_fingertip_title);
|
||||||
|
if (!mHaveShownSfpsTipLottie && mIllustrationLottie != null) {
|
||||||
|
mHaveShownSfpsTipLottie = true;
|
||||||
|
configureEnrollmentStage("", R.raw.sfps_lottie_tip);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SFPS_STAGE_LEFT_EDGE:
|
||||||
|
glifLayoutHelper.setHeaderText(
|
||||||
|
R.string.security_settings_sfps_enroll_left_edge_title);
|
||||||
|
if (!mHaveShownSfpsLeftEdgeLottie && mIllustrationLottie != null) {
|
||||||
|
mHaveShownSfpsLeftEdgeLottie = true;
|
||||||
|
configureEnrollmentStage("", R.raw.sfps_lottie_left_edge);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SFPS_STAGE_RIGHT_EDGE:
|
||||||
|
glifLayoutHelper.setHeaderText(
|
||||||
|
R.string.security_settings_sfps_enroll_right_edge_title);
|
||||||
|
if (!mHaveShownSfpsRightEdgeLottie && mIllustrationLottie != null) {
|
||||||
|
mHaveShownSfpsRightEdgeLottie = true;
|
||||||
|
configureEnrollmentStage("", R.raw.sfps_lottie_right_edge);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case STAGE_UNKNOWN:
|
||||||
|
default:
|
||||||
|
// Don't use BiometricEnrollBase#setHeaderText, since that invokes setTitle,
|
||||||
|
// which gets announced for a11y upon entering the page. For SFPS, we want to
|
||||||
|
// announce a different string for a11y upon entering the page.
|
||||||
|
glifLayoutHelper.setHeaderText(
|
||||||
|
R.string.security_settings_sfps_enroll_find_sensor_title);
|
||||||
|
glifLayoutHelper.setDescriptionText(
|
||||||
|
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);
|
||||||
|
break;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void maybeHideSfpsText(@android.annotation.NonNull Configuration newConfig) {
|
||||||
|
final HeaderMixin headerMixin = ((GlifLayout) mView).getMixin(HeaderMixin.class);
|
||||||
|
final DescriptionMixin descriptionMixin = ((GlifLayout) mView).getMixin(
|
||||||
|
DescriptionMixin.class);
|
||||||
|
final boolean isLandscape = newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE;
|
||||||
|
|
||||||
|
if (isLandscape) {
|
||||||
|
headerMixin.setAutoTextSizeEnabled(true);
|
||||||
|
headerMixin.getTextView().setMinLines(0);
|
||||||
|
headerMixin.getTextView().setMaxLines(10);
|
||||||
|
descriptionMixin.getTextView().setMinLines(0);
|
||||||
|
descriptionMixin.getTextView().setMaxLines(10);
|
||||||
|
} else {
|
||||||
|
headerMixin.setAutoTextSizeEnabled(false);
|
||||||
|
headerMixin.getTextView().setLines(4);
|
||||||
|
// hide the description
|
||||||
|
descriptionMixin.getTextView().setLines(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getCurrentSfpsStage() {
|
||||||
|
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 SFPS_STAGE_NO_ANIMATION;
|
||||||
|
} else if (progressSteps < getStageThresholdSteps(1)) {
|
||||||
|
return SFPS_STAGE_CENTER;
|
||||||
|
} else if (progressSteps < getStageThresholdSteps(2)) {
|
||||||
|
return SFPS_STAGE_FINGERTIP;
|
||||||
|
} else if (progressSteps < getStageThresholdSteps(3)) {
|
||||||
|
return SFPS_STAGE_LEFT_EDGE;
|
||||||
|
} else {
|
||||||
|
return SFPS_STAGE_RIGHT_EDGE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 updateOrientation(int orientation) {
|
||||||
|
mIllustrationLottie = mView.findViewById(R.id.illustration_lottie);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 startIconAnimation() {
|
||||||
|
if (mIconAnimationDrawable != null) {
|
||||||
|
mIconAnimationDrawable.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void stopIconAnimation() {
|
||||||
|
mAnimationCancelled = true;
|
||||||
|
if (mIconAnimationDrawable != null) {
|
||||||
|
mIconAnimationDrawable.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showIconTouchDialog() {
|
||||||
|
mIconTouchCount = 0;
|
||||||
|
//TODO EnrollingActivity should observe live data and add dialog fragment
|
||||||
|
mEnrollingViewModel.onIconTouchDialogShow();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void configureEnrollmentStage(CharSequence description, @RawRes int lottie) {
|
||||||
|
final GlifLayoutHelper glifLayoutHelper = new GlifLayoutHelper(getActivity(),
|
||||||
|
(GlifLayout) mView);
|
||||||
|
glifLayoutHelper.setDescriptionText(description);
|
||||||
|
LottieCompositionFactory.fromRawRes(getActivity(), lottie)
|
||||||
|
.addListener((c) -> {
|
||||||
|
mIllustrationLottie.setComposition(c);
|
||||||
|
mIllustrationLottie.setVisibility(View.VISIBLE);
|
||||||
|
mIllustrationLottie.playAnimation();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Runnable mShowDialogRunnable = new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
showIconTouchDialog();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private final Animatable2.AnimationCallback mIconAnimationCallback =
|
||||||
|
new Animatable2.AnimationCallback() {
|
||||||
|
@Override
|
||||||
|
public void onAnimationEnd(Drawable d) {
|
||||||
|
if (mAnimationCancelled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start animation after it has ended.
|
||||||
|
mProgressBar.post(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
startIconAnimation();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
@@ -0,0 +1,470 @@
|
|||||||
|
/*
|
||||||
|
* 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.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.ViewModelProvider;
|
||||||
|
|
||||||
|
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.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.airbnb.lottie.LottieCompositionFactory;
|
||||||
|
import com.google.android.setupcompat.template.FooterBarMixin;
|
||||||
|
import com.google.android.setupcompat.template.FooterButton;
|
||||||
|
import com.google.android.setupdesign.GlifLayout;
|
||||||
|
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 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 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 Interpolator mFastOutSlowInInterpolator;
|
||||||
|
private Interpolator mLinearOutSlowInInterpolator;
|
||||||
|
private Interpolator mFastOutLinearInInterpolator;
|
||||||
|
private boolean mAnimationCancelled;
|
||||||
|
|
||||||
|
private LottieAnimationView mIllustrationLottie;
|
||||||
|
private boolean mHaveShownUdfpsTipLottie;
|
||||||
|
private boolean mHaveShownUdfpsLeftEdgeLottie;
|
||||||
|
private boolean mHaveShownUdfpsRightEdgeLottie;
|
||||||
|
private boolean mHaveShownUdfpsCenterLottie;
|
||||||
|
private boolean mHaveShownUdfpsGuideLottie;
|
||||||
|
|
||||||
|
private View mView;
|
||||||
|
private ProgressBar mProgressBar;
|
||||||
|
private TextView mErrorText;
|
||||||
|
private FooterBarMixin mFooterBarMixin;
|
||||||
|
private AnimatedVectorDrawable mIconAnimationDrawable;
|
||||||
|
private AnimatedVectorDrawable mIconBackgroundBlinksDrawable;
|
||||||
|
|
||||||
|
private boolean mShouldShowLottie;
|
||||||
|
private boolean mIsAccessibilityEnabled;
|
||||||
|
|
||||||
|
private final View.OnClickListener mOnSkipClickListener =
|
||||||
|
(v) -> mEnrollingViewModel.onSkipButtonClick();
|
||||||
|
|
||||||
|
private int mIconTouchCount;
|
||||||
|
|
||||||
|
@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);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
mIsAccessibilityEnabled = mEnrollingViewModel.isAccessibilityEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||||
|
Bundle savedInstanceState) {
|
||||||
|
mView = initUdfpsLayout(inflater, container);
|
||||||
|
return mView;
|
||||||
|
}
|
||||||
|
|
||||||
|
private View initUdfpsLayout(LayoutInflater inflater, ViewGroup container) {
|
||||||
|
final View containView = 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 LinearLayout layoutContainer = containView.findViewById(
|
||||||
|
R.id.layout_container);
|
||||||
|
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);
|
||||||
|
updateTitleAndDescription();
|
||||||
|
|
||||||
|
mShouldShowLottie = shouldShowLottie();
|
||||||
|
boolean isLandscape = BiometricUtils.isReverseLandscape(activity)
|
||||||
|
|| BiometricUtils.isLandscape(activity);
|
||||||
|
updateOrientation((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.setSecondaryButton(
|
||||||
|
new FooterButton.Builder(activity)
|
||||||
|
.setText(R.string.security_settings_fingerprint_enroll_enrolling_skip)
|
||||||
|
.setListener(mOnSkipClickListener)
|
||||||
|
.setButtonType(FooterButton.ButtonType.SKIP)
|
||||||
|
.setTheme(R.style.SudGlifButton_Secondary)
|
||||||
|
.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(
|
||||||
|
activity, android.R.interpolator.linear_out_slow_in);
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateTitleAndDescription() {
|
||||||
|
|
||||||
|
final Activity activity = getActivity();
|
||||||
|
final GlifLayoutHelper glifLayoutHelper = new GlifLayoutHelper(activity,
|
||||||
|
(GlifLayout) 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);
|
||||||
|
} else if (!mHaveShownUdfpsCenterLottie && mIllustrationLottie != null) {
|
||||||
|
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:
|
||||||
|
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);
|
||||||
|
} else if (!mHaveShownUdfpsGuideLottie && mIllustrationLottie != null) {
|
||||||
|
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:
|
||||||
|
glifLayoutHelper.setHeaderText(
|
||||||
|
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:
|
||||||
|
glifLayoutHelper.setHeaderText(
|
||||||
|
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()) {
|
||||||
|
glifLayoutHelper.setDescriptionText(
|
||||||
|
R.string.security_settings_fingerprint_enroll_repeat_message);
|
||||||
|
} else {
|
||||||
|
glifLayoutHelper.setDescriptionText(
|
||||||
|
R.string.security_settings_udfps_enroll_edge_message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case STAGE_RIGHT_EDGE:
|
||||||
|
glifLayoutHelper.setHeaderText(
|
||||||
|
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()) {
|
||||||
|
glifLayoutHelper.setDescriptionText(
|
||||||
|
R.string.security_settings_fingerprint_enroll_repeat_message);
|
||||||
|
} else {
|
||||||
|
glifLayoutHelper.setDescriptionText(
|
||||||
|
R.string.security_settings_udfps_enroll_edge_message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case STAGE_UNKNOWN:
|
||||||
|
default:
|
||||||
|
// setHeaderText(R.string.security_settings_fingerprint_enroll_udfps_title);
|
||||||
|
// Don't use BiometricEnrollBase#setHeaderText, since that invokes setTitle,
|
||||||
|
// which gets announced for a11y upon entering the page. For UDFPS, we want to
|
||||||
|
// 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);
|
||||||
|
final CharSequence description = getString(
|
||||||
|
R.string.security_settings_udfps_enroll_a11y);
|
||||||
|
((GlifLayout) mView).getHeaderTextView().setContentDescription(description);
|
||||||
|
activity.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(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 startIconAnimation() {
|
||||||
|
if (mIconAnimationDrawable != null) {
|
||||||
|
mIconAnimationDrawable.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void stopIconAnimation() {
|
||||||
|
mAnimationCancelled = true;
|
||||||
|
if (mIconAnimationDrawable != null) {
|
||||||
|
mIconAnimationDrawable.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 showIconTouchDialog() {
|
||||||
|
mIconTouchCount = 0;
|
||||||
|
//TODO EnrollingActivity should observe live data and add dialog fragment
|
||||||
|
mEnrollingViewModel.onIconTouchDialogShow();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void configureEnrollmentStage(CharSequence description, @RawRes int lottie) {
|
||||||
|
final GlifLayoutHelper glifLayoutHelper = new GlifLayoutHelper(getActivity(),
|
||||||
|
(GlifLayout) mView);
|
||||||
|
glifLayoutHelper.setDescriptionText(description);
|
||||||
|
LottieCompositionFactory.fromRawRes(getActivity(), lottie)
|
||||||
|
.addListener((c) -> {
|
||||||
|
mIllustrationLottie.setComposition(c);
|
||||||
|
mIllustrationLottie.setVisibility(View.VISIBLE);
|
||||||
|
mIllustrationLottie.playAnimation();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Runnable mShowDialogRunnable = new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
showIconTouchDialog();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private final Animatable2.AnimationCallback mIconAnimationCallback =
|
||||||
|
new Animatable2.AnimationCallback() {
|
||||||
|
@Override
|
||||||
|
public void onAnimationEnd(Drawable d) {
|
||||||
|
if (mAnimationCancelled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start animation after it has ended.
|
||||||
|
mProgressBar.post(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
startIconAnimation();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
@@ -67,4 +67,16 @@ public class GlifLayoutHelper {
|
|||||||
mGlifLayout.setDescriptionText(description);
|
mGlifLayout.setDescriptionText(description);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,63 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2023 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.android.settings.biometrics2.ui.view;
|
||||||
|
|
||||||
|
import android.app.Dialog;
|
||||||
|
import android.app.settings.SettingsEnums;
|
||||||
|
import android.content.DialogInterface;
|
||||||
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
|
||||||
|
import com.android.settings.R;
|
||||||
|
import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Icon Touch dialog
|
||||||
|
*/
|
||||||
|
public class IconTouchDialog extends InstrumentedDialogFragment {
|
||||||
|
|
||||||
|
// private FingerprintEnrollEnrollingViewModel mViewModel;
|
||||||
|
//
|
||||||
|
// @Override
|
||||||
|
// public void onAttach(Context context) {
|
||||||
|
// mViewModel = new ViewModelProvider(getActivity()).get(
|
||||||
|
// FingerprintEnrollEnrollingViewModel.class);
|
||||||
|
// super.onAttach(context);
|
||||||
|
// }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||||
|
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity(),
|
||||||
|
R.style.Theme_AlertDialog);
|
||||||
|
builder.setTitle(R.string.security_settings_fingerprint_enroll_touch_dialog_title)
|
||||||
|
.setMessage(R.string.security_settings_fingerprint_enroll_touch_dialog_message)
|
||||||
|
.setPositiveButton(R.string.security_settings_fingerprint_enroll_dialog_ok,
|
||||||
|
new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
dialog.dismiss();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return builder.create();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getMetricsCategory() {
|
||||||
|
return SettingsEnums.DIALOG_FINGERPRINT_ICON_TOUCH;
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,161 @@
|
|||||||
|
/*
|
||||||
|
* 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.viewmodel;
|
||||||
|
|
||||||
|
import android.app.Application;
|
||||||
|
import android.os.VibrationAttributes;
|
||||||
|
import android.os.VibrationEffect;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.accessibility.AccessibilityManager;
|
||||||
|
|
||||||
|
import androidx.lifecycle.AndroidViewModel;
|
||||||
|
import androidx.lifecycle.DefaultLifecycleObserver;
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ViewModel explaining the fingerprint enrolling page
|
||||||
|
*/
|
||||||
|
public class FingerprintEnrollEnrollingViewModel extends AndroidViewModel
|
||||||
|
implements DefaultLifecycleObserver {
|
||||||
|
|
||||||
|
private static final String TAG = FingerprintEnrollEnrollingViewModel.class.getSimpleName();
|
||||||
|
private static final boolean DEBUG = false;
|
||||||
|
|
||||||
|
private static final VibrationEffect VIBRATE_EFFECT_ERROR =
|
||||||
|
VibrationEffect.createWaveform(new long[]{0, 5, 55, 60}, -1);
|
||||||
|
private static final VibrationAttributes FINGERPRINT_ENROLLING_SONFICATION_ATTRIBUTES =
|
||||||
|
VibrationAttributes.createForUsage(VibrationAttributes.USAGE_ACCESSIBILITY);
|
||||||
|
|
||||||
|
//Enrolling skip
|
||||||
|
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;
|
||||||
|
|
||||||
|
//Icon touch dialog dismiss
|
||||||
|
public static final int FINGERPRINT_ENROLL_ENROLLING_ACTION_DISMISS_DIALOG = 1;
|
||||||
|
|
||||||
|
private final FingerprintRepository mFingerprintRepository;
|
||||||
|
private final AccessibilityRepository mAccessibilityRepository;
|
||||||
|
private final VibratorRepository mVibratorRepository;
|
||||||
|
|
||||||
|
private EnrollmentRequest mEnrollmentRequest = null;
|
||||||
|
private final MutableLiveData<Integer> mEnrollingLiveData = new MutableLiveData<>();
|
||||||
|
private final MutableLiveData<Integer> mIconTouchDialogLiveData = new MutableLiveData<>();
|
||||||
|
|
||||||
|
|
||||||
|
public FingerprintEnrollEnrollingViewModel(Application application,
|
||||||
|
FingerprintRepository fingerprintRepository,
|
||||||
|
AccessibilityRepository accessibilityRepository,
|
||||||
|
VibratorRepository vibratorRepository) {
|
||||||
|
super(application);
|
||||||
|
mFingerprintRepository = fingerprintRepository;
|
||||||
|
mAccessibilityRepository = accessibilityRepository;
|
||||||
|
mVibratorRepository = vibratorRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User clicks skip button
|
||||||
|
*/
|
||||||
|
public void onSkipButtonClick() {
|
||||||
|
final int action = FINGERPRINT_ENROLL_ENROLLING_ACTION_SKIP;
|
||||||
|
if (DEBUG) {
|
||||||
|
Log.d(TAG, "onSkipButtonClick, post action " + action);
|
||||||
|
}
|
||||||
|
mEnrollingLiveData.postValue(action);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Icon touch dialog show
|
||||||
|
*/
|
||||||
|
public void onIconTouchDialogShow() {
|
||||||
|
final int action = FINGERPRINT_ENROLL_ENROLLING_ACTION_SHOW_DIALOG;
|
||||||
|
if (DEBUG) {
|
||||||
|
Log.d(TAG, "onIconTouchDialogShow, post action " + action);
|
||||||
|
}
|
||||||
|
mIconTouchDialogLiveData.postValue(action);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Icon touch dialog dismiss
|
||||||
|
*/
|
||||||
|
public void onIconTouchDialogDismiss() {
|
||||||
|
final int action = FINGERPRINT_ENROLL_ENROLLING_ACTION_DISMISS_DIALOG;
|
||||||
|
if (DEBUG) {
|
||||||
|
Log.d(TAG, "onIconTouchDialogDismiss, post action " + action);
|
||||||
|
}
|
||||||
|
mIconTouchDialogLiveData.postValue(action);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get enroll stage threshold
|
||||||
|
*/
|
||||||
|
public float getEnrollStageThreshold(int index) {
|
||||||
|
return mFingerprintRepository.getEnrollStageThreshold(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get enroll stage count
|
||||||
|
*/
|
||||||
|
public int getEnrollStageCount() {
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
public void clearTalkback() {
|
||||||
|
mAccessibilityRepository.interrupt();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns if the {@link AccessibilityManager} is enabled.
|
||||||
|
*
|
||||||
|
* @return True if this {@link AccessibilityManager} is enabled, false otherwise.
|
||||||
|
*/
|
||||||
|
public boolean isAccessibilityEnabled() {
|
||||||
|
return mAccessibilityRepository.isEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
}
|
@@ -56,6 +56,10 @@ public class FingerprintEnrollProgressViewModel extends AndroidViewModel {
|
|||||||
private final MutableLiveData<EnrollmentStatusMessage> mErrorMessageLiveData =
|
private final MutableLiveData<EnrollmentStatusMessage> mErrorMessageLiveData =
|
||||||
new MutableLiveData<>();
|
new MutableLiveData<>();
|
||||||
|
|
||||||
|
private final MutableLiveData<Boolean> mAcquireLiveData = new MutableLiveData<>();
|
||||||
|
private final MutableLiveData<Integer> mPointerDownLiveData = new MutableLiveData<>();
|
||||||
|
private final MutableLiveData<Integer> mPointerUpLiveData = new MutableLiveData<>();
|
||||||
|
|
||||||
private byte[] mToken = null;
|
private byte[] mToken = null;
|
||||||
private final int mUserId;
|
private final int mUserId;
|
||||||
|
|
||||||
@@ -86,6 +90,21 @@ public class FingerprintEnrollProgressViewModel extends AndroidViewModel {
|
|||||||
public void onEnrollmentError(int errMsgId, CharSequence errString) {
|
public void onEnrollmentError(int errMsgId, CharSequence errString) {
|
||||||
mErrorMessageLiveData.postValue(new EnrollmentStatusMessage(errMsgId, errString));
|
mErrorMessageLiveData.postValue(new EnrollmentStatusMessage(errMsgId, errString));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAcquired(boolean isAcquiredGood) {
|
||||||
|
mAcquireLiveData.postValue(isAcquiredGood);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPointerDown(int sensorId) {
|
||||||
|
mPointerDownLiveData.postValue(sensorId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPointerUp(int sensorId) {
|
||||||
|
mPointerUpLiveData.postValue(sensorId);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
public FingerprintEnrollProgressViewModel(@NonNull Application application,
|
public FingerprintEnrollProgressViewModel(@NonNull Application application,
|
||||||
@@ -132,6 +151,19 @@ public class FingerprintEnrollProgressViewModel extends AndroidViewModel {
|
|||||||
return mErrorMessageLiveData;
|
return mErrorMessageLiveData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public MutableLiveData<Boolean> getAcquireLiveData() {
|
||||||
|
return mAcquireLiveData;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MutableLiveData<Integer> getPointerDownLiveData() {
|
||||||
|
return mPointerDownLiveData;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MutableLiveData<Integer> getPointerUpLiveData() {
|
||||||
|
return mPointerUpLiveData;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Starts enrollment and return latest isEnrolling() result
|
* Starts enrollment and return latest isEnrolling() result
|
||||||
*/
|
*/
|
||||||
|
Reference in New Issue
Block a user