Refactor FingerprintEnrollFindSensor

Refactor FingerprintEnrollFindSensor to 3 pages for different sensor
types, and apply MVVM for them.

Bug: 259664912
Bug: 260957195
Bug: 260957816
Test: atest FingerprintRepositoryTest FingerprintEnrollmentActivityTest
      AutoCredentialViewModelTest FingerprintEnrollIntroViewModelTest

Change-Id: Iace790952567cac13e61e5175e90555d4da7dfe2
This commit is contained in:
Milton Wu
2022-11-18 13:33:02 +08:00
parent b8926bd868
commit 3be7385d90
24 changed files with 1706 additions and 180 deletions

View File

@@ -53,6 +53,14 @@ public class FingerprintRepository {
return prop != null && prop.isAnyUdfpsType(); return prop != null && prop.isAnyUdfpsType();
} }
/**
* The first sensor type is Side fps sensor or not
*/
public boolean canAssumeSfps() {
FingerprintSensorPropertiesInternal prop = getFirstFingerprintSensorPropertiesInternal();
return prop != null && prop.isAnySidefpsType();
}
/** /**
* Get max possible number of fingerprints for a user * Get max possible number of fingerprints for a user
*/ */
@@ -78,6 +86,7 @@ public class FingerprintRepository {
@Nullable @Nullable
private FingerprintSensorPropertiesInternal getFirstFingerprintSensorPropertiesInternal() { private FingerprintSensorPropertiesInternal getFirstFingerprintSensorPropertiesInternal() {
// TODO(b/264827022) use API addAuthenticatorsRegisteredCallback
final List<FingerprintSensorPropertiesInternal> props = final List<FingerprintSensorPropertiesInternal> props =
mFingerprintManager.getSensorPropertiesInternal(); mFingerprintManager.getSensorPropertiesInternal();
return props.size() > 0 ? props.get(0) : null; return props.size() > 0 ? props.get(0) : null;

View File

@@ -30,6 +30,8 @@ import com.android.settings.biometrics2.data.repository.FingerprintRepository;
*/ */
public class BiometricsRepositoryProviderImpl implements BiometricsRepositoryProvider { public class BiometricsRepositoryProviderImpl implements BiometricsRepositoryProvider {
private static volatile FingerprintRepository sFingerprintRepository;
/** /**
* Get FingerprintRepository * Get FingerprintRepository
*/ */
@@ -41,6 +43,13 @@ public class BiometricsRepositoryProviderImpl implements BiometricsRepositoryPro
if (fingerprintManager == null) { if (fingerprintManager == null) {
return null; return null;
} }
return new FingerprintRepository(fingerprintManager); if (sFingerprintRepository == null) {
synchronized (FingerprintRepository.class) {
if (sFingerprintRepository == null) {
sFingerprintRepository = new FingerprintRepository(fingerprintManager);
}
}
}
return sFingerprintRepository;
} }
} }

View File

@@ -27,12 +27,18 @@ import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory;
import androidx.lifecycle.viewmodel.CreationExtras; 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.biometrics2.data.repository.FingerprintRepository; import com.android.settings.biometrics2.data.repository.FingerprintRepository;
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.DeviceRotationViewModel;
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.FingerprintEnrollmentViewModel; import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollmentViewModel;
import com.android.settings.overlay.FeatureFactory; import com.android.settings.overlay.FeatureFactory;
import com.android.systemui.unfold.compat.ScreenSizeFoldProvider;
/** /**
* View model factory for biometric enrollment fragment * View model factory for biometric enrollment fragment
@@ -42,7 +48,7 @@ public class BiometricsViewModelFactory implements ViewModelProvider.Factory {
private static final String TAG = "BiometricsViewModelFact"; private static final String TAG = "BiometricsViewModelFact";
public static final CreationExtras.Key<ChallengeGenerator> CHALLENGE_GENERATOR = public static final CreationExtras.Key<ChallengeGenerator> CHALLENGE_GENERATOR =
new CreationExtras.Key<ChallengeGenerator>() {}; new CreationExtras.Key<>() {};
@NonNull @NonNull
@Override @Override
@@ -59,7 +65,22 @@ public class BiometricsViewModelFactory implements ViewModelProvider.Factory {
final BiometricsRepositoryProvider provider = FeatureFactory.getFactory(application) final BiometricsRepositoryProvider provider = FeatureFactory.getFactory(application)
.getBiometricsRepositoryProvider(); .getBiometricsRepositoryProvider();
if (modelClass.isAssignableFrom(FingerprintEnrollIntroViewModel.class)) { if (modelClass.isAssignableFrom(AutoCredentialViewModel.class)) {
final LockPatternUtils lockPatternUtils =
featureFactory.getSecurityFeatureProvider().getLockPatternUtils(application);
final ChallengeGenerator challengeGenerator = extras.get(CHALLENGE_GENERATOR);
if (challengeGenerator != null) {
return (T) new AutoCredentialViewModel(application, lockPatternUtils,
challengeGenerator);
}
} else if (modelClass.isAssignableFrom(DeviceFoldedViewModel.class)) {
return (T) new DeviceFoldedViewModel(new ScreenSizeFoldProvider(application),
application.getMainExecutor());
} else if (modelClass.isAssignableFrom(DeviceRotationViewModel.class)) {
return (T) new DeviceRotationViewModel(application);
} else if (modelClass.isAssignableFrom(FingerprintEnrollFindSensorViewModel.class)) {
return (T) new FingerprintEnrollFindSensorViewModel(application);
} else if (modelClass.isAssignableFrom(FingerprintEnrollIntroViewModel.class)) {
final FingerprintRepository repository = provider.getFingerprintRepository(application); final FingerprintRepository repository = provider.getFingerprintRepository(application);
if (repository != null) { if (repository != null) {
return (T) new FingerprintEnrollIntroViewModel(application, repository); return (T) new FingerprintEnrollIntroViewModel(application, repository);
@@ -70,14 +91,9 @@ public class BiometricsViewModelFactory implements ViewModelProvider.Factory {
return (T) new FingerprintEnrollmentViewModel(application, repository, return (T) new FingerprintEnrollmentViewModel(application, repository,
application.getSystemService(KeyguardManager.class)); application.getSystemService(KeyguardManager.class));
} }
} else if (modelClass.isAssignableFrom(AutoCredentialViewModel.class)) { } else if (modelClass.isAssignableFrom(FingerprintEnrollProgressViewModel.class)) {
final LockPatternUtils lockPatternUtils = return (T) new FingerprintEnrollProgressViewModel(application,
featureFactory.getSecurityFeatureProvider().getLockPatternUtils(application); new FingerprintUpdater(application));
final ChallengeGenerator challengeGenerator = extras.get(CHALLENGE_GENERATOR);
if (challengeGenerator != null) {
return (T) new AutoCredentialViewModel(application, lockPatternUtils,
challengeGenerator);
}
} }
return create(modelClass); return create(modelClass);
} }

View File

@@ -0,0 +1,52 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.biometrics2.ui.model;
/**
* Biometric Enrollment progress
*/
public final class EnrollmentProgress {
public static final int INITIAL_STEPS = -1;
public static final int INITIAL_REMAINING = 0;
private final int mSteps;
private final int mRemaining;
public EnrollmentProgress(int steps, int remaining) {
mSteps = steps;
mRemaining = remaining;
}
public int getSteps() {
return mSteps;
}
public int getRemaining() {
return mRemaining;
}
public boolean isInitialStep() {
return mSteps == INITIAL_STEPS;
}
@Override
public String toString() {
return getClass().getSimpleName() + "@" + Integer.toHexString(hashCode())
+ "{steps:" + mSteps + ", remaining:" + mRemaining + "}";
}
}

View File

@@ -0,0 +1,209 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.biometrics2.ui.view;
import static android.hardware.fingerprint.FingerprintManager.ENROLL_FIND_SENSOR;
import static android.view.View.OnClickListener;
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
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.fingerprint.FingerprintFindSensorAnimation;
import com.android.settings.biometrics2.ui.model.EnrollmentProgress;
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollFindSensorViewModel;
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollProgressViewModel;
import com.google.android.setupcompat.template.FooterBarMixin;
import com.google.android.setupcompat.template.FooterButton;
import com.google.android.setupdesign.GlifLayout;
/**
* Fragment explaining the side fingerprint sensor location for fingerprint enrollment.
* It interacts with ProgressViewModel, and FingerprintFindSensorAnimation.
* <pre>
| Has | UDFPS | SFPS | Other (Rear FPS) |
|---------------------|-------|------|------------------|
| Primary button | Yes | No | No |
| Illustration Lottie | Yes | Yes | No |
| Animation | No | No | Depend on layout |
| Progress ViewModel | No | Yes | Yes |
| Orientation detect | No | Yes | No |
| Foldable detect | No | Yes | No |
</pre>
*/
public class FingerprintEnrollFindRfpsFragment extends Fragment {
private static final boolean DEBUG = false;
private static final String TAG = "FingerprintEnrollFindRfpsFragment";
private FingerprintEnrollFindSensorViewModel mViewModel;
private FingerprintEnrollProgressViewModel mPorgressViewModel;
private View mView;
private GlifLayout mGlifLayout;
private FooterBarMixin mFooterBarMixin;
private final OnClickListener mOnSkipClickListener = (v) -> mViewModel.onSkipButtonClick();
@Nullable private FingerprintFindSensorAnimation mAnimation;
private final Observer<EnrollmentProgress> mProgressObserver = progress -> {
if (DEBUG) {
Log.d(TAG, "mProgressObserver(" + progress + ")");
}
if (progress != null && !progress.isInitialStep()) {
mViewModel.onStartButtonClick();
}
};
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
final Context context = inflater.getContext();
mView = inflater.inflate(R.layout.fingerprint_enroll_find_sensor, container, false);
mGlifLayout = mView.findViewById(R.id.setup_wizard_layout);
mFooterBarMixin = mGlifLayout.getMixin(FooterBarMixin.class);
mFooterBarMixin.setSecondaryButton(
new FooterButton.Builder(context)
.setText(R.string.security_settings_fingerprint_enroll_enrolling_skip)
.setButtonType(FooterButton.ButtonType.SKIP)
.setTheme(R.style.SudGlifButton_Secondary)
.build()
);
View animationView = mView.findViewById(R.id.fingerprint_sensor_location_animation);
if (animationView instanceof FingerprintFindSensorAnimation) {
mAnimation = (FingerprintFindSensorAnimation) animationView;
}
return mView;
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
final Activity activity = getActivity();
final GlifLayoutHelper glifLayoutHelper = new GlifLayoutHelper(activity, mGlifLayout);
glifLayoutHelper.setHeaderText(
R.string.security_settings_fingerprint_enroll_find_sensor_title);
glifLayoutHelper.setDescriptionText(
getText(R.string.security_settings_fingerprint_enroll_find_sensor_message));
mFooterBarMixin.getSecondaryButton().setOnClickListener(mOnSkipClickListener);
}
@Override
public void onStart() {
super.onStart();
if (DEBUG) {
Log.d(TAG, "onStart(), start looking for fingerprint, animation exist:"
+ (mAnimation != null));
}
startLookingForFingerprint();
}
@Override
public void onResume() {
if (mAnimation != null) {
if (DEBUG) {
Log.d(TAG, "onResume(), start animation");
}
mAnimation.startAnimation();
}
super.onResume();
}
@Override
public void onPause() {
if (mAnimation != null) {
if (DEBUG) {
Log.d(TAG, "onPause(), pause animation");
}
mAnimation.pauseAnimation();
}
super.onPause();
}
@Override
public void onStop() {
super.onStop();
if (DEBUG) {
Log.d(TAG, "onStop(), stop looking for fingerprint, animation exist:"
+ (mAnimation != null));
}
stopLookingForFingerprint();
}
private void startLookingForFingerprint() {
if (mPorgressViewModel.isEnrolling()) {
Log.d(TAG, "startLookingForFingerprint(), failed because isEnrolling is true before"
+ " starting");
return;
}
mPorgressViewModel.clearProgressLiveData();
mPorgressViewModel.getProgressLiveData().observe(this, mProgressObserver);
final boolean startResult = mPorgressViewModel.startEnrollment(ENROLL_FIND_SENSOR);
if (!startResult) {
Log.e(TAG, "startLookingForFingerprint(), failed to start enrollment");
}
}
private void stopLookingForFingerprint() {
if (!mPorgressViewModel.isEnrolling()) {
Log.d(TAG, "stopLookingForFingerprint(), failed because isEnrolling is false before"
+ " stopping");
return;
}
mPorgressViewModel.getProgressLiveData().removeObserver(mProgressObserver);
final boolean cancelResult = mPorgressViewModel.cancelEnrollment();
if (!cancelResult) {
Log.e(TAG, "stopLookingForFingerprint(), failed to cancel enrollment");
}
}
@Override
public void onDestroy() {
if (mAnimation != null) {
if (DEBUG) {
Log.d(TAG, "onDestroy(), stop animation");
}
mAnimation.stopAnimation();
}
super.onDestroy();
}
@Override
public void onAttach(@NonNull Context context) {
final FragmentActivity activity = getActivity();
final ViewModelProvider provider = new ViewModelProvider(activity);
mViewModel = provider.get(FingerprintEnrollFindSensorViewModel.class);
mPorgressViewModel = provider.get(FingerprintEnrollProgressViewModel.class);
super.onAttach(context);
}
}

View File

@@ -0,0 +1,251 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.biometrics2.ui.view;
import static android.hardware.fingerprint.FingerprintManager.ENROLL_FIND_SENSOR;
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Surface;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RawRes;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider;
import com.android.settings.R;
import com.android.settings.biometrics2.ui.model.EnrollmentProgress;
import com.android.settings.biometrics2.ui.viewmodel.DeviceFoldedViewModel;
import com.android.settings.biometrics2.ui.viewmodel.DeviceRotationViewModel;
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollFindSensorViewModel;
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollProgressViewModel;
import com.android.settingslib.widget.LottieColorUtils;
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 explaining the side fingerprint sensor location for fingerprint enrollment.
* It interacts with ProgressViewModel, FoldCallback (for different lottie), and
* LottieAnimationView.
* <pre>
| Has | UDFPS | SFPS | Other (Rear FPS) |
|---------------------|-------|------|------------------|
| Primary button | Yes | No | No |
| Illustration Lottie | Yes | Yes | No |
| Animation | No | No | Depend on layout |
| Progress ViewModel | No | Yes | Yes |
| Orientation detect | No | Yes | No |
| Foldable detect | No | Yes | No |
</pre>
*/
public class FingerprintEnrollFindSfpsFragment extends Fragment {
private static final boolean DEBUG = false;
private static final String TAG = "FingerprintEnrollFindSfpsFragment";
private FingerprintEnrollFindSensorViewModel mViewModel;
private FingerprintEnrollProgressViewModel mProgressViewModel;
private DeviceRotationViewModel mRotationViewModel;
private DeviceFoldedViewModel mFoldedViewModel;
private final Observer<Integer> mRotationObserver = rotation -> {
if (DEBUG) {
Log.d(TAG, "rotationObserver " + rotation);
}
if (rotation == null) {
return;
}
onRotationChanged(rotation);
};
@Surface.Rotation private int mAnimationRotation = -1;
private View mView;
private GlifLayout mGlifLayout;
private FooterBarMixin mFooterBarMixin;
private final OnClickListener mOnSkipClickListener = (v) -> mViewModel.onSkipButtonClick();
private LottieAnimationView mIllustrationLottie;
private final Observer<EnrollmentProgress> mProgressObserver = progress -> {
if (DEBUG) {
Log.d(TAG, "mProgressObserver(" + progress + ")");
}
if (progress != null && !progress.isInitialStep()) {
mViewModel.onStartButtonClick();
}
};
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
final Context context = inflater.getContext();
mView = inflater.inflate(R.layout.sfps_enroll_find_sensor_layout, container, false);
mGlifLayout = mView.findViewById(R.id.setup_wizard_layout);
mIllustrationLottie = mView.findViewById(R.id.illustration_lottie);
mFooterBarMixin = mGlifLayout.getMixin(FooterBarMixin.class);
mFooterBarMixin.setSecondaryButton(
new FooterButton.Builder(context)
.setText(R.string.security_settings_fingerprint_enroll_enrolling_skip)
.setButtonType(FooterButton.ButtonType.SKIP)
.setTheme(R.style.SudGlifButton_Secondary)
.build()
);
return mView;
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
final Activity activity = getActivity();
final GlifLayoutHelper glifLayoutHelper = new GlifLayoutHelper(activity, mGlifLayout);
glifLayoutHelper.setHeaderText(R.string.security_settings_sfps_enroll_find_sensor_title);
glifLayoutHelper.setDescriptionText(
getText(R.string.security_settings_sfps_enroll_find_sensor_message));
mFooterBarMixin.getSecondaryButton().setOnClickListener(mOnSkipClickListener);
}
@Override
public void onStart() {
super.onStart();
if (DEBUG) {
Log.d(TAG, "onStart(), start looking for fingerprint");
}
startLookingForFingerprint();
}
@Override
public void onResume() {
super.onResume();
final LiveData<Integer> rotationLiveData = mRotationViewModel.getLiveData();
playLottieAnimation(rotationLiveData.getValue());
rotationLiveData.observe(this, mRotationObserver);
}
@Override
public void onPause() {
mRotationViewModel.getLiveData().removeObserver(mRotationObserver);
super.onPause();
}
@Override
public void onStop() {
super.onStop();
if (DEBUG) {
Log.d(TAG, "onStop(), stop looking for fingerprint");
}
stopLookingForFingerprint();
}
private void startLookingForFingerprint() {
if (mProgressViewModel.isEnrolling()) {
Log.d(TAG, "startLookingForFingerprint(), failed because isEnrolling is true before"
+ " starting");
return;
}
mProgressViewModel.clearProgressLiveData();
mProgressViewModel.getProgressLiveData().observe(this, mProgressObserver);
final boolean startResult = mProgressViewModel.startEnrollment(ENROLL_FIND_SENSOR);
if (!startResult) {
Log.e(TAG, "startLookingForFingerprint(), failed to start enrollment");
}
}
private void stopLookingForFingerprint() {
if (!mProgressViewModel.isEnrolling()) {
Log.d(TAG, "stopLookingForFingerprint(), failed because isEnrolling is false before"
+ " stopping");
return;
}
mProgressViewModel.getProgressLiveData().removeObserver(mProgressObserver);
final boolean cancelResult = mProgressViewModel.cancelEnrollment();
if (!cancelResult) {
Log.e(TAG, "stopLookingForFingerprint(), failed to cancel enrollment");
}
}
private void onRotationChanged(@Surface.Rotation int newRotation) {
if (DEBUG) {
Log.d(TAG, "onRotationChanged() from " + mAnimationRotation + " to " + newRotation);
}
if ((newRotation + 2) % 4 == mAnimationRotation) {
playLottieAnimation(newRotation);
}
// Fragment will be re-created if it's changed between landscape and portrait, so no need to
// handle other cases.
}
private void playLottieAnimation(@Surface.Rotation int rotation) {
@RawRes final int animationRawRes = getSfpsLottieAnimationRawRes(rotation);
if (DEBUG) {
Log.d(TAG, "play lottie animation " + animationRawRes
+ ", previous rotation:" + mAnimationRotation + ", new rotation:" + rotation);
}
mAnimationRotation = rotation;
mIllustrationLottie.setAnimation(animationRawRes);
LottieColorUtils.applyDynamicColors(getActivity(), mIllustrationLottie);
mIllustrationLottie.setVisibility(View.VISIBLE);
mIllustrationLottie.playAnimation();
}
@RawRes
private int getSfpsLottieAnimationRawRes(@Surface.Rotation int rotation) {
final boolean isFolded = !Boolean.FALSE.equals(mFoldedViewModel.getLiveData().getValue());
switch (rotation) {
case Surface.ROTATION_90:
return isFolded ? R.raw.fingerprint_edu_lottie_folded_top_left
: R.raw.fingerprint_edu_lottie_portrait_top_left;
case Surface.ROTATION_180 :
return isFolded ? R.raw.fingerprint_edu_lottie_folded_bottom_left
: R.raw.fingerprint_edu_lottie_landscape_bottom_left;
case Surface.ROTATION_270 :
return isFolded ? R.raw.fingerprint_edu_lottie_folded_bottom_right
: R.raw.fingerprint_edu_lottie_portrait_bottom_right;
default :
return isFolded ? R.raw.fingerprint_edu_lottie_folded_top_right
: R.raw.fingerprint_edu_lottie_landscape_top_right;
}
}
@Override
public void onAttach(@NonNull Context context) {
final FragmentActivity activity = getActivity();
final ViewModelProvider provider = new ViewModelProvider(activity);
mViewModel = provider.get(FingerprintEnrollFindSensorViewModel.class);
mProgressViewModel = provider.get(FingerprintEnrollProgressViewModel.class);
mRotationViewModel = provider.get(DeviceRotationViewModel.class);
mFoldedViewModel = provider.get(DeviceFoldedViewModel.class);
super.onAttach(context);
}
}

View File

@@ -0,0 +1,115 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.biometrics2.ui.view;
import static android.view.View.OnClickListener;
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import com.android.settings.R;
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollFindSensorViewModel;
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 explaining the under-display fingerprint sensor location for fingerprint enrollment.
* It interacts with Primary button, and LottieAnimationView.
* <pre>
| Has | UDFPS | SFPS | Other (Rear FPS) |
|---------------------|-------|------|------------------|
| Primary button | Yes | No | No |
| Illustration Lottie | Yes | Yes | No |
| Animation | No | No | Depend on layout |
| Progress ViewModel | No | Yes | Yes |
| Orientation detect | No | Yes | No |
| Foldable detect | No | Yes | No |
</pre>
*/
public class FingerprintEnrollFindUdfpsFragment extends Fragment {
private FingerprintEnrollFindSensorViewModel mViewModel;
private View mView;
private GlifLayout mGlifLayout;
private FooterBarMixin mFooterBarMixin;
private final OnClickListener mOnSkipClickListener = (v) -> mViewModel.onSkipButtonClick();
private final OnClickListener mOnStartClickListener = (v) -> mViewModel.onStartButtonClick();
private LottieAnimationView mIllustrationLottie;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
final Context context = inflater.getContext();
mView = inflater.inflate(R.layout.udfps_enroll_find_sensor_layout, container, false);
mGlifLayout = mView.findViewById(R.id.setup_wizard_layout);
mIllustrationLottie = mView.findViewById(R.id.illustration_lottie);
mFooterBarMixin = mGlifLayout.getMixin(FooterBarMixin.class);
mFooterBarMixin.setSecondaryButton(
new FooterButton.Builder(context)
.setText(R.string.security_settings_fingerprint_enroll_enrolling_skip)
.setButtonType(FooterButton.ButtonType.SKIP)
.setTheme(R.style.SudGlifButton_Secondary)
.build()
);
mFooterBarMixin.setPrimaryButton(
new FooterButton.Builder(context)
.setText(R.string.security_settings_udfps_enroll_find_sensor_start_button)
.setButtonType(FooterButton.ButtonType.NEXT)
.setTheme(R.style.SudGlifButton_Primary)
.build()
);
return mView;
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
final Activity activity = getActivity();
final GlifLayoutHelper glifLayoutHelper = new GlifLayoutHelper(activity, mGlifLayout);
glifLayoutHelper.setHeaderText(R.string.security_settings_udfps_enroll_find_sensor_title);
glifLayoutHelper.setDescriptionText(
getText(R.string.security_settings_udfps_enroll_find_sensor_message));
mFooterBarMixin.getSecondaryButton().setOnClickListener(mOnSkipClickListener);
mFooterBarMixin.getPrimaryButton().setOnClickListener(mOnStartClickListener);
mIllustrationLottie.setOnClickListener(mOnStartClickListener);
if (mViewModel.isAccessibilityEnabled()) {
mIllustrationLottie.setAnimation(R.raw.udfps_edu_a11y_lottie);
}
}
@Override
public void onAttach(@NonNull Context context) {
mViewModel = new ViewModelProvider(getActivity()).get(
FingerprintEnrollFindSensorViewModel.class);
super.onAttach(context);
}
}

View File

@@ -24,18 +24,17 @@ import static com.android.settings.biometrics2.ui.model.FingerprintEnrollIntroSt
import static com.google.android.setupdesign.util.DynamicColorPalette.ColorType.ACCENT; import static com.google.android.setupdesign.util.DynamicColorPalette.ColorType.ACCENT;
import android.app.Activity;
import android.app.admin.DevicePolicyManager; import android.app.admin.DevicePolicyManager;
import android.content.Context; import android.content.Context;
import android.graphics.PorterDuff; import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter; import android.graphics.PorterDuffColorFilter;
import android.os.Bundle; import android.os.Bundle;
import android.text.Html; import android.text.Html;
import android.text.TextUtils;
import android.text.method.LinkMovementMethod; import android.text.method.LinkMovementMethod;
import android.util.Log; import android.util.Log;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
@@ -69,6 +68,9 @@ public class FingerprintEnrollIntroFragment extends Fragment {
private View mView = null; private View mView = null;
private FooterButton mPrimaryFooterButton = null; private FooterButton mPrimaryFooterButton = null;
private FooterButton mSecondaryFooterButton = null; private FooterButton mSecondaryFooterButton = null;
private final OnClickListener mOnNextClickListener = (v) -> mViewModel.onNextButtonClick();
private final OnClickListener mOnSkipOrCancelClickListener =
(v) -> mViewModel.onSkipOrCancelButtonClick();
private ImageView mIconShield = null; private ImageView mIconShield = null;
private TextView mFooterMessage6 = null; private TextView mFooterMessage6 = null;
@Nullable private PorterDuffColorFilter mIconColorFilter; @Nullable private PorterDuffColorFilter mIconColorFilter;
@@ -150,8 +152,8 @@ public class FingerprintEnrollIntroFragment extends Fragment {
final Context context = view.getContext(); final Context context = view.getContext();
mPrimaryFooterButton.setOnClickListener(mViewModel::onNextButtonClick); mPrimaryFooterButton.setOnClickListener(mOnNextClickListener);
mSecondaryFooterButton.setOnClickListener(mViewModel::onSkipOrCancelButtonClick); mSecondaryFooterButton.setOnClickListener(mOnSkipOrCancelClickListener);
if (mViewModel.canAssumeUdfps()) { if (mViewModel.canAssumeUdfps()) {
mFooterMessage6.setVisibility(View.VISIBLE); mFooterMessage6.setVisibility(View.VISIBLE);
@@ -165,15 +167,15 @@ public class FingerprintEnrollIntroFragment extends Fragment {
? R.string.security_settings_fingerprint_enroll_introduction_cancel ? R.string.security_settings_fingerprint_enroll_introduction_cancel
: R.string.security_settings_fingerprint_enroll_introduction_no_thanks); : R.string.security_settings_fingerprint_enroll_introduction_no_thanks);
final GlifLayoutHelper glifLayoutHelper = new GlifLayoutHelper(getActivity(), getLayout());
if (mViewModel.isBiometricUnlockDisabledByAdmin() if (mViewModel.isBiometricUnlockDisabledByAdmin()
&& !mViewModel.isParentalConsentRequired()) { && !mViewModel.isParentalConsentRequired()) {
setHeaderText( glifLayoutHelper.setHeaderText(
getActivity(),
R.string.security_settings_fingerprint_enroll_introduction_title_unlock_disabled R.string.security_settings_fingerprint_enroll_introduction_title_unlock_disabled
); );
getLayout().setDescriptionText(getDescriptionDisabledByAdmin(context)); glifLayoutHelper.setDescriptionText(getDescriptionDisabledByAdmin(context));
} else { } else {
setHeaderText(getActivity(), glifLayoutHelper.setHeaderText(
R.string.security_settings_fingerprint_enroll_introduction_title); R.string.security_settings_fingerprint_enroll_introduction_title);
} }
observePageStatusLiveDataIfNeed(); observePageStatusLiveDataIfNeed();
@@ -192,7 +194,7 @@ public class FingerprintEnrollIntroFragment extends Fragment {
final RequireScrollMixin requireScrollMixin = getLayout() final RequireScrollMixin requireScrollMixin = getLayout()
.getMixin(RequireScrollMixin.class); .getMixin(RequireScrollMixin.class);
requireScrollMixin.requireScrollWithButton(getActivity(), mPrimaryFooterButton, requireScrollMixin.requireScrollWithButton(getActivity(), mPrimaryFooterButton,
getMoreButtonTextRes(), mViewModel::onNextButtonClick); getMoreButtonTextRes(), mOnNextClickListener);
// Always set true to setHasScrolledToBottom() before registering listener through // Always set true to setHasScrolledToBottom() before registering listener through
// setOnRequireScrollStateChangedListener(), because listener will not be called if first // setOnRequireScrollStateChangedListener(), because listener will not be called if first
@@ -253,21 +255,6 @@ public class FingerprintEnrollIntroFragment extends Fragment {
} }
} }
private void setHeaderText(@NonNull Activity activity, int resId) {
TextView layoutTitle = getLayout().getHeaderTextView();
CharSequence previousTitle = layoutTitle.getText();
CharSequence title = activity.getText(resId);
if (previousTitle != title) {
if (!TextUtils.isEmpty(previousTitle)) {
layoutTitle.setAccessibilityLiveRegion(View.ACCESSIBILITY_LIVE_REGION_POLITE);
}
getLayout().setHeaderText(title);
getLayout().getHeaderTextView().setContentDescription(title);
activity.setTitle(title);
}
getLayout().getHeaderTextView().setContentDescription(activity.getText(resId));
}
void updateFooterButtons(@NonNull FingerprintEnrollIntroStatus status) { void updateFooterButtons(@NonNull FingerprintEnrollIntroStatus status) {
@StringRes final int scrollToBottomPrimaryResId = @StringRes final int scrollToBottomPrimaryResId =
status.getEnrollableStatus() == FINGERPRINT_ENROLLABLE_OK status.getEnrollableStatus() == FINGERPRINT_ENROLLABLE_OK

View File

@@ -16,6 +16,7 @@
package com.android.settings.biometrics2.ui.view; package com.android.settings.biometrics2.ui.view;
import static androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult;
import static androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY; import static androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY;
import static com.android.settings.biometrics2.factory.BiometricsViewModelFactory.CHALLENGE_GENERATOR; import static com.android.settings.biometrics2.factory.BiometricsViewModelFactory.CHALLENGE_GENERATOR;
@@ -23,24 +24,34 @@ import static com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewMo
import static com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel.CREDENTIAL_FAIL_NEED_TO_CONFIRM_LOCK; import static com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel.CREDENTIAL_FAIL_NEED_TO_CONFIRM_LOCK;
import static com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel.CREDENTIAL_IS_GENERATING_CHALLENGE; import static com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel.CREDENTIAL_IS_GENERATING_CHALLENGE;
import static com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel.CREDENTIAL_VALID; import static com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel.CREDENTIAL_VALID;
import static com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollFindSensorViewModel.FINGERPRINT_ENROLL_FIND_SENSOR_ACTION_DIALOG;
import static com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollFindSensorViewModel.FINGERPRINT_ENROLL_FIND_SENSOR_ACTION_SKIP;
import static com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollFindSensorViewModel.FINGERPRINT_ENROLL_FIND_SENSOR_ACTION_START;
import static com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollFindSensorViewModel.FingerprintEnrollFindSensorAction;
import static com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollIntroViewModel.FINGERPRINT_ENROLL_INTRO_ACTION_CONTINUE_ENROLL; import static com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollIntroViewModel.FINGERPRINT_ENROLL_INTRO_ACTION_CONTINUE_ENROLL;
import static com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollIntroViewModel.FINGERPRINT_ENROLL_INTRO_ACTION_DONE_AND_FINISH; import static com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollIntroViewModel.FINGERPRINT_ENROLL_INTRO_ACTION_DONE_AND_FINISH;
import static com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollIntroViewModel.FINGERPRINT_ENROLL_INTRO_ACTION_SKIP_OR_CANCEL; import static com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollIntroViewModel.FINGERPRINT_ENROLL_INTRO_ACTION_SKIP_OR_CANCEL;
import static com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollIntroViewModel.FingerprintEnrollIntroAction;
import android.annotation.StyleRes;
import android.app.Application; import android.app.Application;
import android.content.Intent; import android.content.Intent;
import android.content.res.ColorStateList; import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.graphics.Color; import android.graphics.Color;
import android.os.Bundle; import android.os.Bundle;
import android.util.Log; import android.util.Log;
import androidx.activity.result.ActivityResult; import androidx.activity.result.ActivityResult;
import androidx.activity.result.ActivityResultCallback;
import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.ColorInt; import androidx.annotation.ColorInt;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity; import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentManager;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider; import androidx.lifecycle.ViewModelProvider;
import androidx.lifecycle.viewmodel.CreationExtras; import androidx.lifecycle.viewmodel.CreationExtras;
import androidx.lifecycle.viewmodel.MutableCreationExtras; import androidx.lifecycle.viewmodel.MutableCreationExtras;
@@ -48,14 +59,16 @@ import androidx.lifecycle.viewmodel.MutableCreationExtras;
import com.android.settings.R; import com.android.settings.R;
import com.android.settings.Utils; import com.android.settings.Utils;
import com.android.settings.biometrics.BiometricEnrollBase; import com.android.settings.biometrics.BiometricEnrollBase;
import com.android.settings.biometrics.fingerprint.FingerprintEnrollFindSensor; import com.android.settings.biometrics.fingerprint.FingerprintEnrollEnrolling;
import com.android.settings.biometrics.fingerprint.SetupFingerprintEnrollFindSensor; import com.android.settings.biometrics.fingerprint.SetupFingerprintEnrollEnrolling;
import com.android.settings.biometrics2.data.repository.FingerprintRepository; import com.android.settings.biometrics2.data.repository.FingerprintRepository;
import com.android.settings.biometrics2.factory.BiometricsViewModelFactory; import com.android.settings.biometrics2.factory.BiometricsViewModelFactory;
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.FingerprintChallengeGenerator; import com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel.FingerprintChallengeGenerator;
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.FingerprintEnrollmentViewModel; import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollmentViewModel;
import com.android.settings.overlay.FeatureFactory; import com.android.settings.overlay.FeatureFactory;
@@ -66,37 +79,57 @@ import com.google.android.setupdesign.util.ThemeHelper;
*/ */
public class FingerprintEnrollmentActivity extends FragmentActivity { public class FingerprintEnrollmentActivity extends FragmentActivity {
private static final boolean DEBUG = false;
private static final String TAG = "FingerprintEnrollmentActivity"; private static final String TAG = "FingerprintEnrollmentActivity";
private static final String INTRO_TAG = "enroll-intro";
private static final String FIND_UDFPS_TAG = "enroll-find-udfps";
private static final String FIND_SFPS_TAG = "enroll-find-sfps";
private static final String FIND_RFPS_TAG = "enroll-find-rfps";
private static final String SKIP_SETUP_FIND_FPS_DIALOG_TAG = "skip-setup-dialog";
protected static final int LAUNCH_CONFIRM_LOCK_ACTIVITY = 1; protected static final int LAUNCH_CONFIRM_LOCK_ACTIVITY = 1;
private ViewModelProvider mViewModelProvider;
private FingerprintEnrollmentViewModel mViewModel; private FingerprintEnrollmentViewModel mViewModel;
private AutoCredentialViewModel mAutoCredentialViewModel; private AutoCredentialViewModel mAutoCredentialViewModel;
private ActivityResultLauncher<Intent> mNextActivityLauncher; private final Observer<Integer> mIntroActionObserver = action -> {
private ActivityResultLauncher<Intent> mChooseLockLauncher; if (DEBUG) {
Log.d(TAG, "mIntroActionObserver(" + action + ")");
}
if (action != null) {
onIntroAction(action);
}
};
private final Observer<Integer> mFindSensorActionObserver = action -> {
if (DEBUG) {
Log.d(TAG, "mFindSensorActionObserver(" + action + ")");
}
if (action != null) {
onFindSensorAction(action);
}
};
private final ActivityResultCallback<ActivityResult> mNextActivityResultCallback =
result -> mViewModel.onContinueEnrollActivityResult(result,
mAutoCredentialViewModel.getUserId());
private final ActivityResultLauncher<Intent> mNextActivityLauncher =
registerForActivityResult(new StartActivityForResult(), mNextActivityResultCallback);
private final ActivityResultCallback<ActivityResult> mChooseLockResultCallback =
result -> onChooseOrConfirmLockResult(true /* isChooseLock */, result);
private final ActivityResultLauncher<Intent> mChooseLockLauncher =
registerForActivityResult(new StartActivityForResult(), mChooseLockResultCallback);
@Override @Override
protected void onCreate(@Nullable Bundle savedInstanceState) { protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
mNextActivityLauncher = registerForActivityResult( mViewModelProvider = new ViewModelProvider(this);
new ActivityResultContracts.StartActivityForResult(),
(it) -> mViewModel.onContinueEnrollActivityResult(
it,
mAutoCredentialViewModel.getUserId())
);
mChooseLockLauncher = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
(it) -> onChooseOrConfirmLockResult(true, it)
);
ViewModelProvider viewModelProvider = new ViewModelProvider(this); mViewModel = mViewModelProvider.get(FingerprintEnrollmentViewModel.class);
mViewModel = viewModelProvider.get(FingerprintEnrollmentViewModel.class);
mViewModel.setRequest(new EnrollmentRequest(getIntent(), getApplicationContext())); mViewModel.setRequest(new EnrollmentRequest(getIntent(), getApplicationContext()));
mViewModel.setSavedInstanceState(savedInstanceState); mViewModel.setSavedInstanceState(savedInstanceState);
mAutoCredentialViewModel = viewModelProvider.get(AutoCredentialViewModel.class); mAutoCredentialViewModel = mViewModelProvider.get(AutoCredentialViewModel.class);
mAutoCredentialViewModel.setCredentialModel(savedInstanceState, getIntent()); mAutoCredentialViewModel.setCredentialModel(savedInstanceState, getIntent());
// Theme // Theme
@@ -106,20 +139,37 @@ public class FingerprintEnrollmentActivity extends FragmentActivity {
// fragment // fragment
setContentView(R.layout.biometric_enrollment_container); setContentView(R.layout.biometric_enrollment_container);
final FingerprintEnrollIntroViewModel introViewModel =
viewModelProvider.get(FingerprintEnrollIntroViewModel.class);
introViewModel.setEnrollmentRequest(mViewModel.getRequest());
introViewModel.setUserId(mAutoCredentialViewModel.getUserId());
if (DEBUG) {
Log.e(TAG, "onCreate() has savedInstance:" + (savedInstanceState != null));
}
if (savedInstanceState == null) { if (savedInstanceState == null) {
checkCredential(); checkCredential();
startIntroFragment();
final String tag = "FingerprintEnrollIntroFragment"; } else {
getSupportFragmentManager().beginTransaction() final FragmentManager manager = getSupportFragmentManager();
.setReorderingAllowed(true) String[] tags = new String[] {
.add(R.id.fragment_container_view, FingerprintEnrollIntroFragment.class, null, FIND_UDFPS_TAG,
tag) FIND_SFPS_TAG,
.commit(); FIND_RFPS_TAG,
INTRO_TAG
};
for (String tag: tags) {
final Fragment fragment = manager.findFragmentByTag(tag);
if (fragment == null) {
continue;
}
if (DEBUG) {
Log.e(TAG, "onCreate() currentFragment:" + tag);
}
if (tag.equals(INTRO_TAG)) {
attachIntroViewModel();
} else { // FIND_UDFPS_TAG, FIND_SFPS_TAG, FIND_RFPS_TAG
attachFindSensorViewModel();
attachIntroViewModel();
}
break;
}
} }
// observe LiveData // observe LiveData
@@ -128,11 +178,80 @@ public class FingerprintEnrollmentActivity extends FragmentActivity {
mAutoCredentialViewModel.getGenerateChallengeFailedLiveData().observe(this, mAutoCredentialViewModel.getGenerateChallengeFailedLiveData().observe(this,
this::onGenerateChallengeFailed); this::onGenerateChallengeFailed);
}
private void startIntroFragment() {
attachIntroViewModel();
getSupportFragmentManager().beginTransaction()
.setReorderingAllowed(true)
.replace(R.id.fragment_container_view, FingerprintEnrollIntroFragment.class, null,
INTRO_TAG)
.commit();
}
private void attachIntroViewModel() {
final FingerprintEnrollIntroViewModel introViewModel =
mViewModelProvider.get(FingerprintEnrollIntroViewModel.class);
introViewModel.setEnrollmentRequest(mViewModel.getRequest());
introViewModel.setUserId(mAutoCredentialViewModel.getUserId());
// Clear ActionLiveData in FragmentViewModel to prevent getting previous action during // Clear ActionLiveData in FragmentViewModel to prevent getting previous action during
// recreate, like press 'I agree' then press 'back' in FingerprintEnrollFindSensor activity. // recreate, like press 'Agree' then press 'back' in FingerprintEnrollFindSensor activity.
introViewModel.clearActionLiveData(); introViewModel.clearActionLiveData();
introViewModel.getActionLiveData().observe(this, this::observeIntroAction); introViewModel.getActionLiveData().observe(this, mIntroActionObserver);
}
// We need to make sure token is valid before entering find sensor page
private void startFindSensorFragment() {
attachFindSensorViewModel();
if (mViewModel.canAssumeUdfps()) {
// UDFPS does not need to start real fingerprint enrolling during finding sensor
startFindFpsFragmentWithProgressViewModel(FingerprintEnrollFindUdfpsFragment.class,
FIND_UDFPS_TAG, false /* initProgressViewModel */);
} else if (mViewModel.canAssumeSfps()) {
startFindFpsFragmentWithProgressViewModel(FingerprintEnrollFindSfpsFragment.class,
FIND_SFPS_TAG, true /* initProgressViewModel */);
} else {
startFindFpsFragmentWithProgressViewModel(FingerprintEnrollFindRfpsFragment.class,
FIND_RFPS_TAG, true /* initProgressViewModel */);
}
}
private void startFindFpsFragmentWithProgressViewModel(
@NonNull Class<? extends Fragment> findFpsClass, @NonNull String tag,
boolean initProgressViewModel) {
if (initProgressViewModel) {
final FingerprintEnrollProgressViewModel progressViewModel =
mViewModelProvider.get(FingerprintEnrollProgressViewModel.class);
progressViewModel.setUserId(mAutoCredentialViewModel.getUserId());
progressViewModel.setToken(mAutoCredentialViewModel.getToken());
}
final FingerprintEnrollFindSensorViewModel findSensorViewModel =
mViewModelProvider.get(FingerprintEnrollFindSensorViewModel.class);
findSensorViewModel.setIsSuw(mViewModel.getRequest().isSuw());
getSupportFragmentManager().beginTransaction()
.setReorderingAllowed(true)
.setCustomAnimations(R.anim.sud_slide_next_in, R.anim.sud_slide_next_out,
R.anim.sud_slide_back_in, R.anim.sud_slide_back_out)
.replace(R.id.fragment_container_view, findFpsClass, null, tag)
.addToBackStack(tag)
.commit();
}
private void attachFindSensorViewModel() {
final FingerprintEnrollFindSensorViewModel findSensorViewModel =
mViewModelProvider.get(FingerprintEnrollFindSensorViewModel.class);
// Clear ActionLiveData in FragmentViewModel to prevent getting previous action during
// recreate, like press 'Start' then press 'back' in FingerprintEnrollEnrolling activity.
findSensorViewModel.clearActionLiveData();
findSensorViewModel.getActionLiveData().observe(this, mFindSensorActionObserver);
}
private void startSkipSetupFindFpsDialog() {
new SkipSetupFindFpsDialog().show(getSupportFragmentManager(),
SKIP_SETUP_FIND_FPS_DIALOG_TAG);
} }
private void onGenerateChallengeFailed(@NonNull Boolean ignoredBoolean) { private void onGenerateChallengeFailed(@NonNull Boolean ignoredBoolean) {
@@ -217,10 +336,7 @@ public class FingerprintEnrollmentActivity extends FragmentActivity {
} }
} }
private void observeIntroAction(@Nullable Integer action) { private void onIntroAction(@FingerprintEnrollIntroAction int action) {
if (action == null) {
return;
}
switch (action) { switch (action) {
case FINGERPRINT_ENROLL_INTRO_ACTION_DONE_AND_FINISH: { case FINGERPRINT_ENROLL_INTRO_ACTION_DONE_AND_FINISH: {
onSetActivityResult( onSetActivityResult(
@@ -233,13 +349,30 @@ public class FingerprintEnrollmentActivity extends FragmentActivity {
return; return;
} }
case FINGERPRINT_ENROLL_INTRO_ACTION_CONTINUE_ENROLL: { case FINGERPRINT_ENROLL_INTRO_ACTION_CONTINUE_ENROLL: {
startFindSensorFragment();
}
}
}
private void onFindSensorAction(@FingerprintEnrollFindSensorAction int action) {
switch (action) {
case FINGERPRINT_ENROLL_FIND_SENSOR_ACTION_SKIP: {
onSetActivityResult(
new ActivityResult(BiometricEnrollBase.RESULT_SKIP, null));
return;
}
case FINGERPRINT_ENROLL_FIND_SENSOR_ACTION_DIALOG: {
startSkipSetupFindFpsDialog();
return;
}
case FINGERPRINT_ENROLL_FIND_SENSOR_ACTION_START: {
final boolean isSuw = mViewModel.getRequest().isSuw(); final boolean isSuw = mViewModel.getRequest().isSuw();
if (!mViewModel.isWaitingActivityResult().compareAndSet(false, true)) { if (!mViewModel.isWaitingActivityResult().compareAndSet(false, true)) {
Log.w(TAG, "startNext, isSuw:" + isSuw + ", fail to set isWaiting flag"); Log.w(TAG, "startNext, isSuw:" + isSuw + ", fail to set isWaiting flag");
} }
final Intent intent = new Intent(this, isSuw Intent intent = new Intent(this, isSuw
? SetupFingerprintEnrollFindSensor.class ? SetupFingerprintEnrollEnrolling.class
: FingerprintEnrollFindSensor.class); : FingerprintEnrollEnrolling.class);
intent.putExtras(mAutoCredentialViewModel.createCredentialIntentExtra()); intent.putExtras(mAutoCredentialViewModel.createCredentialIntentExtra());
intent.putExtras(mViewModel.getNextActivityBaseIntentExtras()); intent.putExtras(mViewModel.getNextActivityBaseIntentExtras());
mNextActivityLauncher.launch(intent); mNextActivityLauncher.launch(intent);
@@ -253,6 +386,12 @@ public class FingerprintEnrollmentActivity extends FragmentActivity {
mViewModel.checkFinishActivityDuringOnPause(isFinishing(), isChangingConfigurations()); mViewModel.checkFinishActivityDuringOnPause(isFinishing(), isChangingConfigurations());
} }
@Override
protected void onApplyThemeResource(Resources.Theme theme, @StyleRes int resid, boolean first) {
theme.applyStyle(R.style.SetupWizardPartnerResource, true);
super.onApplyThemeResource(theme, resid, first);
}
@Override @Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
if (requestCode == LAUNCH_CONFIRM_LOCK_ACTIVITY) { if (requestCode == LAUNCH_CONFIRM_LOCK_ACTIVITY) {

View File

@@ -0,0 +1,70 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.biometrics2.ui.view;
import android.app.Activity;
import android.text.TextUtils;
import android.view.View;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.StringRes;
import com.google.android.setupdesign.GlifLayout;
/**
* Utils class for GlifLayout
*/
public class GlifLayoutHelper {
@NonNull private final Activity mActivity;
@NonNull private final GlifLayout mGlifLayout;
public GlifLayoutHelper(@NonNull Activity activity, @NonNull GlifLayout glifLayout) {
mActivity = activity;
mGlifLayout = glifLayout;
}
/**
* Sets header text to GlifLayout
*/
public void setHeaderText(@StringRes int textResId) {
TextView layoutTitle = mGlifLayout.getHeaderTextView();
CharSequence previousTitle = layoutTitle.getText();
CharSequence title = mActivity.getText(textResId);
if (previousTitle != title) {
if (!TextUtils.isEmpty(previousTitle)) {
layoutTitle.setAccessibilityLiveRegion(View.ACCESSIBILITY_LIVE_REGION_POLITE);
}
mGlifLayout.setHeaderText(title);
mGlifLayout.getHeaderTextView().setContentDescription(title);
mActivity.setTitle(title);
}
mGlifLayout.getHeaderTextView().setContentDescription(title);
}
/**
* Sets description text to GlifLayout
*/
public void setDescriptionText(CharSequence description) {
CharSequence previousDescription = mGlifLayout.getDescriptionText();
// Prevent a11y for re-reading the same string
if (!TextUtils.equals(previousDescription, description)) {
mGlifLayout.setDescriptionText(description);
}
}
}

View File

@@ -0,0 +1,71 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.biometrics2.ui.view;
import static android.content.DialogInterface.OnClickListener;
import android.app.Dialog;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.lifecycle.ViewModelProvider;
import com.android.settings.R;
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollFindSensorViewModel;
import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
/**
* Skip dialog which shows when user clicks "Do it later" button in FingerprintFindSensor page.
*/
public class SkipSetupFindFpsDialog extends InstrumentedDialogFragment {
private FingerprintEnrollFindSensorViewModel mViewModel;
private final OnClickListener mOnSkipClickListener =
(d, w) -> mViewModel.onSkipDialogButtonClick();
@Override
public int getMetricsCategory() {
return SettingsEnums.DIALOG_FINGERPRINT_SKIP_SETUP;
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
return onCreateDialogBuilder().create();
}
/**
* Returns builder for this dialog
*/
@NonNull
private AlertDialog.Builder onCreateDialogBuilder() {
return new AlertDialog.Builder(getActivity(), R.style.Theme_AlertDialog)
.setTitle(R.string.setup_fingerprint_enroll_skip_title)
.setPositiveButton(R.string.skip_anyway_button_label, mOnSkipClickListener)
.setNegativeButton(R.string.go_back_button_label, null)
.setMessage(R.string.setup_fingerprint_enroll_skip_after_adding_lock_text);
}
@Override
public void onAttach(Context context) {
mViewModel = new ViewModelProvider(getActivity()).get(
FingerprintEnrollFindSensorViewModel.class);
super.onAttach(context);
}
}

View File

@@ -253,7 +253,11 @@ public class AutoCredentialViewModel extends AndroidViewModel {
if (isUnspecifiedPassword()) { if (isUnspecifiedPassword()) {
return CREDENTIAL_FAIL_NEED_TO_CHOOSE_LOCK; return CREDENTIAL_FAIL_NEED_TO_CHOOSE_LOCK;
} else if (mCredentialModel.isValidGkPwHandle()) { } else if (mCredentialModel.isValidGkPwHandle()) {
generateChallenge(mCredentialModel.getGkPwHandle()); final long gkPwHandle = mCredentialModel.getGkPwHandle();
mCredentialModel.clearGkPwHandle();
// GkPwHandle is got through caller activity, we shall not revoke it after
// generateChallenge(). Let caller activity to make decision.
generateChallenge(gkPwHandle, false /* revokeGkPwHandle */);
mIsGeneratingChallengeDuringCheckingCredential = true; mIsGeneratingChallengeDuringCheckingCredential = true;
return CREDENTIAL_IS_GENERATING_CHALLENGE; return CREDENTIAL_IS_GENERATING_CHALLENGE;
} else { } else {
@@ -261,7 +265,7 @@ public class AutoCredentialViewModel extends AndroidViewModel {
} }
} }
private void generateChallenge(long gkPwHandle) { private void generateChallenge(long gkPwHandle, boolean revokeGkPwHandle) {
mChallengeGenerator.setCallback((sensorId, userId, challenge) -> { mChallengeGenerator.setCallback((sensorId, userId, challenge) -> {
try { try {
final byte[] newToken = requestGatekeeperHat(gkPwHandle, challenge, userId); final byte[] newToken = requestGatekeeperHat(gkPwHandle, challenge, userId);
@@ -274,11 +278,13 @@ public class AutoCredentialViewModel extends AndroidViewModel {
return; return;
} }
if (revokeGkPwHandle) {
mLockPatternUtils.removeGatekeeperPasswordHandle(gkPwHandle); mLockPatternUtils.removeGatekeeperPasswordHandle(gkPwHandle);
mCredentialModel.clearGkPwHandle(); }
if (DEBUG) { if (DEBUG) {
Log.d(TAG, "generateChallenge " + mCredentialModel); Log.d(TAG, "generateChallenge(), model:" + mCredentialModel
+ ", revokeGkPwHandle:" + revokeGkPwHandle);
} }
// Check credential again // Check credential again
@@ -314,7 +320,9 @@ public class AutoCredentialViewModel extends AndroidViewModel {
if (data != null) { if (data != null) {
final long gkPwHandle = result.getData().getLongExtra( final long gkPwHandle = result.getData().getLongExtra(
EXTRA_KEY_GK_PW_HANDLE, INVALID_GK_PW_HANDLE); EXTRA_KEY_GK_PW_HANDLE, INVALID_GK_PW_HANDLE);
generateChallenge(gkPwHandle); // Revoke self requested GkPwHandle because it shall only used once inside this
// activity lifecycle.
generateChallenge(gkPwHandle, true /* revokeGkPwHandle */);
return true; return true;
} }
} }
@@ -328,6 +336,14 @@ public class AutoCredentialViewModel extends AndroidViewModel {
return mCredentialModel.getUserId(); return mCredentialModel.getUserId();
} }
/**
* Get userId for this credential
*/
@Nullable
public byte[] getToken() {
return mCredentialModel.getToken();
}
@Nullable @Nullable
private byte[] requestGatekeeperHat(long gkPwHandle, long challenge, int userId) private byte[] requestGatekeeperHat(long gkPwHandle, long challenge, int userId)
throws IllegalStateException { throws IllegalStateException {

View File

@@ -0,0 +1,66 @@
/*
* 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.util.Log;
import androidx.annotation.NonNull;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
import com.android.systemui.unfold.compat.ScreenSizeFoldProvider;
import com.android.systemui.unfold.updates.FoldProvider;
import java.util.concurrent.Executor;
/**
* ViewModel explaining the fingerprint sensor location for fingerprint enrollment.
*/
public class DeviceFoldedViewModel extends ViewModel {
private static final String TAG = "DeviceFoldedViewModel";
@NonNull private final MutableLiveData<Boolean> mLiveData =
new MutableLiveData<>(null);
private final ScreenSizeFoldProvider mScreenSizeFoldProvider;
private final FoldProvider.FoldCallback mIsFoldedCallback = isFolded -> {
Log.d(TAG, "onFoldUpdated= " + isFolded);
mLiveData.postValue(isFolded);
};
public DeviceFoldedViewModel(@NonNull ScreenSizeFoldProvider screenSizeFoldProvider,
@NonNull Executor executor) {
super();
mScreenSizeFoldProvider = screenSizeFoldProvider;
mScreenSizeFoldProvider.registerCallback(mIsFoldedCallback, executor);
}
/**
* Returns FoldedLiveData
*/
public LiveData<Boolean> getLiveData() {
return mLiveData;
}
@Override
protected void onCleared() {
mScreenSizeFoldProvider.unregisterCallback(mIsFoldedCallback);
super.onCleared();
}
}

View File

@@ -0,0 +1,92 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.biometrics2.ui.viewmodel;
import static android.hardware.display.DisplayManager.DisplayListener;
import android.app.Application;
import android.hardware.display.DisplayManager;
import android.util.Log;
import android.view.DisplayInfo;
import android.view.Surface;
import androidx.annotation.NonNull;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
/**
* ViewModel explaining the fingerprint sensor location for fingerprint enrollment.
*/
public class DeviceRotationViewModel extends AndroidViewModel {
private static final boolean DEBUG = false;
private static final String TAG = "RotationViewModel";
private final DisplayManager mDisplayManager;
@NonNull private final DisplayInfo mDisplayInfo = new DisplayInfo();
private final DisplayListener mDisplayListener = new DisplayListener() {
@Override
public void onDisplayAdded(int displayId) {
}
@Override
public void onDisplayRemoved(int displayId) {
}
@Override
public void onDisplayChanged(int displayId) {
final int rotation = getRotation();
if (DEBUG) {
Log.d(TAG, "onDisplayChanged(" + displayId + "), rotation:" + rotation);
}
mLiveData.postValue(rotation);
}
};
@NonNull private final MutableLiveData<Integer> mLiveData =
new MutableLiveData<>(getRotation());
public DeviceRotationViewModel(@NonNull Application application) {
super(application);
mDisplayManager = application.getSystemService(DisplayManager.class);
mDisplayManager.registerDisplayListener(mDisplayListener,
application.getMainThreadHandler());
}
/**
* Returns current rotation
*/
@Surface.Rotation
private int getRotation() {
getApplication().getDisplay().getDisplayInfo(mDisplayInfo);
return mDisplayInfo.rotation;
}
/**
* Returns RotationLiveData
*/
public LiveData<Integer> getLiveData() {
return mLiveData;
}
@Override
protected void onCleared() {
mDisplayManager.unregisterDisplayListener(mDisplayListener);
super.onCleared();
}
}

View File

@@ -0,0 +1,135 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.biometrics2.ui.viewmodel;
import android.annotation.IntDef;
import android.app.Application;
import android.util.Log;
import android.view.accessibility.AccessibilityManager;
import androidx.annotation.NonNull;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* ViewModel explaining the fingerprint sensor location for fingerprint enrollment.
*/
public class FingerprintEnrollFindSensorViewModel extends AndroidViewModel {
private static final boolean DEBUG = false;
private static final String TAG = "FingerprintEnrollFindSensorViewModel";
/**
* User clicks 'Skip' button on this page in Settings
*/
public static final int FINGERPRINT_ENROLL_FIND_SENSOR_ACTION_SKIP = 0;
/**
* User clicks 'Skip' button on this page in SetupWizard flow
*/
public static final int FINGERPRINT_ENROLL_FIND_SENSOR_ACTION_DIALOG = 1;
/**
* User clicks 'Start' button on this page
*/
public static final int FINGERPRINT_ENROLL_FIND_SENSOR_ACTION_START = 2;
@IntDef(prefix = { "FINGERPRINT_ENROLL_FIND_SENSOR_ACTION_" }, value = {
FINGERPRINT_ENROLL_FIND_SENSOR_ACTION_SKIP,
FINGERPRINT_ENROLL_FIND_SENSOR_ACTION_DIALOG,
FINGERPRINT_ENROLL_FIND_SENSOR_ACTION_START
})
@Retention(RetentionPolicy.SOURCE)
public @interface FingerprintEnrollFindSensorAction {}
private final AccessibilityManager mAccessibilityManager;
private boolean mIsSuw = false;
@NonNull private final MutableLiveData<Integer> mActionLiveData = new MutableLiveData<>();
public FingerprintEnrollFindSensorViewModel(@NonNull Application application) {
super(application);
mAccessibilityManager = application.getSystemService(AccessibilityManager.class);
}
/**
* Sets isSetupWizard or not
*/
public void setIsSuw(boolean isSuw) {
mIsSuw = isSuw;
}
/**
* Returns action live data that user chooses
*/
public LiveData<Integer> getActionLiveData() {
return mActionLiveData;
}
/**
* Clear ActionLiveData to prevent get obsolete data
*/
public void clearActionLiveData() {
mActionLiveData.setValue(null);
}
/**
* User clicks skip button on dialog
*/
public void onSkipDialogButtonClick() {
final int action = FINGERPRINT_ENROLL_FIND_SENSOR_ACTION_SKIP;
if (DEBUG) {
Log.d(TAG, "onSkipDialogButtonClick, post " + action);
}
mActionLiveData.postValue(action);
}
/**
* User clicks skip button
*/
public void onSkipButtonClick() {
final int action = mIsSuw
? FINGERPRINT_ENROLL_FIND_SENSOR_ACTION_DIALOG
: FINGERPRINT_ENROLL_FIND_SENSOR_ACTION_SKIP;
if (DEBUG) {
Log.d(TAG, "onSkipButtonClick, post action " + action);
}
mActionLiveData.postValue(action);
}
/**
* User clicks start button
*/
public void onStartButtonClick() {
final int action = FINGERPRINT_ENROLL_FIND_SENSOR_ACTION_START;
if (DEBUG) {
Log.d(TAG, "onStartButtonClick, post action " + action);
}
mActionLiveData.postValue(action);
}
/**
* Returns the info about accessibility is enabled or not
*/
public boolean isAccessibilityEnabled() {
return mAccessibilityManager.isEnabled();
}
}

View File

@@ -24,7 +24,6 @@ import android.annotation.IntDef;
import android.app.Application; import android.app.Application;
import android.os.UserHandle; import android.os.UserHandle;
import android.util.Log; import android.util.Log;
import android.view.View;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.lifecycle.AndroidViewModel; import androidx.lifecycle.AndroidViewModel;
@@ -189,7 +188,7 @@ public class FingerprintEnrollIntroViewModel extends AndroidViewModel
/** /**
* User clicks next button * User clicks next button
*/ */
public void onNextButtonClick(View ignoredView) { public void onNextButtonClick() {
final Integer status = mEnrollableStatusLiveData.getValue(); final Integer status = mEnrollableStatusLiveData.getValue();
switch (status != null ? status : ENROLLABLE_STATUS_DEFAULT) { switch (status != null ? status : ENROLLABLE_STATUS_DEFAULT) {
case FINGERPRINT_ENROLLABLE_ERROR_REACH_MAX: case FINGERPRINT_ENROLLABLE_ERROR_REACH_MAX:
@@ -206,7 +205,7 @@ public class FingerprintEnrollIntroViewModel extends AndroidViewModel
/** /**
* User clicks skip/cancel button * User clicks skip/cancel button
*/ */
public void onSkipOrCancelButtonClick(View ignoredView) { public void onSkipOrCancelButtonClick() {
mActionLiveData.postValue(FINGERPRINT_ENROLL_INTRO_ACTION_SKIP_OR_CANCEL); mActionLiveData.postValue(FINGERPRINT_ENROLL_INTRO_ACTION_SKIP_OR_CANCEL);
} }

View File

@@ -0,0 +1,231 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.biometrics2.ui.viewmodel;
import static com.android.settings.biometrics2.ui.model.EnrollmentProgress.INITIAL_REMAINING;
import static com.android.settings.biometrics2.ui.model.EnrollmentProgress.INITIAL_STEPS;
import android.app.Application;
import android.content.res.Resources;
import android.hardware.fingerprint.FingerprintManager.EnrollReason;
import android.hardware.fingerprint.FingerprintManager.EnrollmentCallback;
import android.os.CancellationSignal;
import android.os.SystemClock;
import android.os.UserHandle;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import com.android.settings.R;
import com.android.settings.biometrics.fingerprint.FingerprintUpdater;
import com.android.settings.biometrics.fingerprint.MessageDisplayController;
import com.android.settings.biometrics2.ui.model.EnrollmentProgress;
/**
* Progress ViewModel handles the state around biometric enrollment. It manages the state of
* enrollment throughout the activity lifecycle so the app can continue after an event like
* rotation.
*/
public class FingerprintEnrollProgressViewModel extends AndroidViewModel {
private static final boolean DEBUG = false;
private static final String TAG = "FingerprintEnrollProgressViewModel";
private final MutableLiveData<EnrollmentProgress> mProgressLiveData = new MutableLiveData<>(
new EnrollmentProgress(INITIAL_STEPS, INITIAL_REMAINING));
private byte[] mToken = null;
private int mUserId = UserHandle.myUserId();
private final FingerprintUpdater mFingerprintUpdater;
private final MessageDisplayController mMessageDisplayController;
private EnrollmentHelper mEnrollmentHelper;
private final EnrollmentCallback mEnrollmentCallback = new EnrollmentCallback() {
@Override
public void onEnrollmentProgress(int remaining) {
final int currentSteps = getSteps();
final EnrollmentProgress progress = new EnrollmentProgress(
currentSteps == INITIAL_STEPS ? remaining : getSteps(), remaining);
if (DEBUG) {
Log.d(TAG, "onEnrollmentProgress(" + remaining + "), steps: " + currentSteps
+ ", post progress as " + progress);
}
mProgressLiveData.postValue(progress);
// TODO set enrolling to false when remaining is 0 during implementing b/260957933
}
@Override
public void onEnrollmentHelp(int helpMsgId, CharSequence helpString) {
// TODO add LiveData for help message during implementing b/260957933
}
@Override
public void onEnrollmentError(int errMsgId, CharSequence errString) {
// TODO add LiveData for error message during implementing b/260957933
}
};
public FingerprintEnrollProgressViewModel(@NonNull Application application,
@NonNull FingerprintUpdater fingerprintUpdater) {
super(application);
mFingerprintUpdater = fingerprintUpdater;
final Resources res = application.getResources();
mMessageDisplayController =
res.getBoolean(R.bool.enrollment_message_display_controller_flag)
? new MessageDisplayController(
application.getMainThreadHandler(),
mEnrollmentCallback,
SystemClock.elapsedRealtimeClock(),
res.getInteger(R.integer.enrollment_help_minimum_time_display),
res.getInteger(R.integer.enrollment_progress_minimum_time_display),
res.getBoolean(R.bool.enrollment_progress_priority_over_help),
res.getBoolean(R.bool.enrollment_prioritize_acquire_messages),
res.getInteger(R.integer.enrollment_collect_time)) : null;
}
public void setToken(byte[] token) {
mToken = token;
}
public void setUserId(int userId) {
mUserId = userId;
}
/**
* clear progress
*/
public void clearProgressLiveData() {
mProgressLiveData.setValue(new EnrollmentProgress(INITIAL_STEPS, INITIAL_REMAINING));
}
public LiveData<EnrollmentProgress> getProgressLiveData() {
return mProgressLiveData;
}
/**
* Starts enrollment and return latest isEnrolling() result
*/
public boolean startEnrollment(@EnrollReason int reason) {
if (mToken == null) {
Log.e(TAG, "Null hardware auth token for enroll");
return false;
}
if (isEnrolling()) {
Log.w(TAG, "Enrolling has started, shall not start again");
return true;
}
mEnrollmentHelper = new EnrollmentHelper(
mMessageDisplayController != null
? mMessageDisplayController
: mEnrollmentCallback);
mEnrollmentHelper.startEnrollment(mFingerprintUpdater, mToken, mUserId, reason);
return true;
}
/**
* Cancels enrollment and return latest isEnrolling result
*/
public boolean cancelEnrollment() {
if (!isEnrolling() || mEnrollmentHelper == null) {
Log.e(TAG, "Fail to cancel enrollment, enrollmentController exist:"
+ (mEnrollmentHelper != null));
return false;
}
mEnrollmentHelper.cancelEnrollment();
mEnrollmentHelper = null;
return true;
}
public boolean isEnrolling() {
return (mEnrollmentHelper != null);
}
private int getSteps() {
return mProgressLiveData.getValue().getSteps();
}
/**
* This class is used to stop latest message from onEnrollmentError() after user cancelled
* enrollment. This class will not forward message anymore after mCancellationSignal is sent.
*/
private static class EnrollmentHelper extends EnrollmentCallback {
@NonNull private final EnrollmentCallback mEnrollmentCallback;
@Nullable private CancellationSignal mCancellationSignal = new CancellationSignal();
EnrollmentHelper(@NonNull EnrollmentCallback enrollmentCallback) {
mEnrollmentCallback = enrollmentCallback;
}
@Override
public void onEnrollmentError(int errMsgId, CharSequence errString) {
if (mCancellationSignal == null) {
return;
}
mEnrollmentCallback.onEnrollmentError(errMsgId, errString);
}
@Override
public void onEnrollmentHelp(int helpMsgId, CharSequence helpString) {
if (mCancellationSignal == null) {
return;
}
mEnrollmentCallback.onEnrollmentHelp(helpMsgId, helpString);
}
@Override
public void onEnrollmentProgress(int remaining) {
if (mCancellationSignal == null) {
return;
}
mEnrollmentCallback.onEnrollmentProgress(remaining);
}
/**
* Starts enrollment
*/
public boolean startEnrollment(@NonNull FingerprintUpdater fingerprintUpdater,
@NonNull byte[] token, int userId, @EnrollReason int reason) {
if (mCancellationSignal == null) {
// Not allow enrolling twice as same instance. Allocate a new instance for second
// enrollment.
return false;
}
fingerprintUpdater.enroll(token, mCancellationSignal, userId, this, reason);
return true;
}
/**
* Cancels current enrollment
*/
public void cancelEnrollment() {
final CancellationSignal cancellationSignal = mCancellationSignal;
mCancellationSignal = null;
if (cancellationSignal != null) {
cancellationSignal.cancel();
}
}
}
}

View File

@@ -124,8 +124,6 @@ public class FingerprintEnrollmentViewModel extends AndroidViewModel implements
} }
} }
private boolean isKeyguardSecure() { private boolean isKeyguardSecure() {
return mKeyguardManager != null && mKeyguardManager.isKeyguardSecure(); return mKeyguardManager != null && mKeyguardManager.isKeyguardSecure();
} }
@@ -182,4 +180,18 @@ public class FingerprintEnrollmentViewModel extends AndroidViewModel implements
public void onSaveInstanceState(@NonNull Bundle outState) { public void onSaveInstanceState(@NonNull Bundle outState) {
outState.putBoolean(SAVED_STATE_IS_WAITING_ACTIVITY_RESULT, mIsWaitingActivityResult.get()); outState.putBoolean(SAVED_STATE_IS_WAITING_ACTIVITY_RESULT, mIsWaitingActivityResult.get());
} }
/**
* The first sensor type is UDFPS sensor or not
*/
public boolean canAssumeUdfps() {
return mFingerprintRepository.canAssumeUdfps();
}
/**
* The first sensor type is side fps sensor or not
*/
public boolean canAssumeSfps() {
return mFingerprintRepository.canAssumeSfps();
}
} }

View File

@@ -58,26 +58,30 @@ public class FingerprintEnrollmentActivityTest {
} }
@Test @Test
public void lunchWithoutCredential() { public void launchWithoutCredential() {
launchFingerprintEnrollActivity(true); launchFingerprintEnrollActivity(true);
Assert.assertNotNull(mDevice.wait(Until.hasObject( Assert.assertNotNull(mDevice.wait(Until.hasObject(
By.text("Choose your backup screen lock method")), IDLE_TIMEOUT)); By.text("Choose your backup screen lock method")), IDLE_TIMEOUT));
} }
@Test @Test
public void lunchWithCredential() { public void launchWithCredential() {
LockScreenUtil.setLockscreen(LockScreenUtil.LockscreenType.PIN, "1234", true); LockScreenUtil.setLockscreen(LockScreenUtil.LockscreenType.PIN, "1234", true);
launchFingerprintEnrollActivity(true); launchFingerprintEnrollActivity(true);
Assert.assertNotNull(mDevice.wait(Until.hasObject( for (long i = 0; i < IDLE_TIMEOUT; i += 100L) {
By.text("More")), IDLE_TIMEOUT)); if (mDevice.wait(Until.hasObject(By.text("More")), 50L) != null) {
break;
} else if (mDevice.wait(Until.hasObject(By.text("I agree")), 50L) != null) {
break;
}
}
//click more btn twice and the introduction should stay in the last page //click more btn at most twice and the introduction should stay in the last page
UiObject2 moreBtn = mDevice.findObject(By.text("More")); UiObject2 moreBtn;
moreBtn.click(); for (int i = 0; i < 2 && (moreBtn = mDevice.findObject(By.text("More"))) != null; ++i) {
Assert.assertNotNull(mDevice.wait(Until.hasObject(
By.text("More")), IDLE_TIMEOUT));
moreBtn = mDevice.findObject(By.text("More"));
moreBtn.click(); moreBtn.click();
mDevice.wait(Until.hasObject(By.text("More")), IDLE_TIMEOUT);
}
Assert.assertNotNull(mDevice.wait(Until.hasObject( Assert.assertNotNull(mDevice.wait(Until.hasObject(
By.text("I agree")), IDLE_TIMEOUT)); By.text("I agree")), IDLE_TIMEOUT));
@@ -114,5 +118,4 @@ public class FingerprintEnrollmentActivityTest {
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK); intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.startActivity(intent); mContext.startActivity(intent);
} }
} }

View File

@@ -23,16 +23,17 @@ import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_UDFP
import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_UDFPS_ULTRASONIC; import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_UDFPS_ULTRASONIC;
import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_UNKNOWN; import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_UNKNOWN;
import static com.android.settings.biometrics2.util.FingerprintManagerUtil.setupFingerprintEnrolledFingerprints;
import static com.android.settings.biometrics2.util.FingerprintManagerUtil.setupFingerprintFirstSensor;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import android.content.Context; import android.content.Context;
import android.content.res.Resources; import android.content.res.Resources;
import android.hardware.biometrics.SensorProperties;
import android.hardware.fingerprint.Fingerprint;
import android.hardware.fingerprint.FingerprintManager; import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorProperties;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.test.core.app.ApplicationProvider; import androidx.test.core.app.ApplicationProvider;
@@ -48,6 +49,8 @@ import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule; import org.mockito.junit.MockitoRule;
import java.util.ArrayList;
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
public class FingerprintRepositoryTest { public class FingerprintRepositoryTest {
@@ -66,31 +69,49 @@ public class FingerprintRepositoryTest {
} }
@Test @Test
public void testCanAssumeSensorType() { public void testCanAssumeSensorType_forUnknownSensor() {
setupFingerprintFirstSensor(mFingerprintManager, TYPE_UNKNOWN, 1); setupFingerprintFirstSensor(mFingerprintManager, TYPE_UNKNOWN, 1);
assertThat(mFingerprintRepository.canAssumeUdfps()).isFalse(); assertThat(mFingerprintRepository.canAssumeUdfps()).isFalse();
assertThat(mFingerprintRepository.canAssumeSfps()).isFalse();
}
@Test
public void testCanAssumeSensorType_forRearSensor() {
setupFingerprintFirstSensor(mFingerprintManager, TYPE_REAR, 1); setupFingerprintFirstSensor(mFingerprintManager, TYPE_REAR, 1);
assertThat(mFingerprintRepository.canAssumeUdfps()).isFalse(); assertThat(mFingerprintRepository.canAssumeUdfps()).isFalse();
assertThat(mFingerprintRepository.canAssumeSfps()).isFalse();
}
@Test
public void testCanAssumeSensorType_forUdfpsUltrasonicSensor() {
setupFingerprintFirstSensor(mFingerprintManager, TYPE_UDFPS_ULTRASONIC, 1); setupFingerprintFirstSensor(mFingerprintManager, TYPE_UDFPS_ULTRASONIC, 1);
assertThat(mFingerprintRepository.canAssumeUdfps()).isTrue(); assertThat(mFingerprintRepository.canAssumeUdfps()).isTrue();
assertThat(mFingerprintRepository.canAssumeSfps()).isFalse();
}
@Test
public void testCanAssumeSensorType_forUdfpsOpticalSensor() {
setupFingerprintFirstSensor(mFingerprintManager, TYPE_UDFPS_OPTICAL, 1); setupFingerprintFirstSensor(mFingerprintManager, TYPE_UDFPS_OPTICAL, 1);
assertThat(mFingerprintRepository.canAssumeUdfps()).isTrue(); assertThat(mFingerprintRepository.canAssumeUdfps()).isTrue();
assertThat(mFingerprintRepository.canAssumeSfps()).isFalse();
}
@Test
public void testCanAssumeSensorType_forPowerButtonSensor() {
setupFingerprintFirstSensor(mFingerprintManager, TYPE_POWER_BUTTON, 1); setupFingerprintFirstSensor(mFingerprintManager, TYPE_POWER_BUTTON, 1);
assertThat(mFingerprintRepository.canAssumeUdfps()).isFalse(); assertThat(mFingerprintRepository.canAssumeUdfps()).isFalse();
assertThat(mFingerprintRepository.canAssumeSfps()).isTrue();
}
@Test
public void testCanAssumeSensorType_forHomeButtonSensor() {
setupFingerprintFirstSensor(mFingerprintManager, TYPE_HOME_BUTTON, 1); setupFingerprintFirstSensor(mFingerprintManager, TYPE_HOME_BUTTON, 1);
assertThat(mFingerprintRepository.canAssumeUdfps()).isFalse(); assertThat(mFingerprintRepository.canAssumeUdfps()).isFalse();
assertThat(mFingerprintRepository.canAssumeSfps()).isFalse();
} }
@Test @Test
public void testGetMaxFingerprints() { public void testGetMaxFingerprints() {
setupFingerprintFirstSensor(mFingerprintManager, TYPE_UNKNOWN, 44);
assertThat(mFingerprintRepository.getMaxFingerprints()).isEqualTo(44);
setupFingerprintFirstSensor(mFingerprintManager, TYPE_UNKNOWN, 999); setupFingerprintFirstSensor(mFingerprintManager, TYPE_UNKNOWN, 999);
assertThat(mFingerprintRepository.getMaxFingerprints()).isEqualTo(999); assertThat(mFingerprintRepository.getMaxFingerprints()).isEqualTo(999);
} }
@@ -122,4 +143,31 @@ public class FingerprintRepositoryTest {
"suw_max_fingerprints_enrollable"); "suw_max_fingerprints_enrollable");
when(mockedResources.getInteger(resId)).thenReturn(numOfFp); when(mockedResources.getInteger(resId)).thenReturn(numOfFp);
} }
public static void setupFingerprintFirstSensor(
@NonNull FingerprintManager mockedFingerprintManager,
@FingerprintSensorProperties.SensorType int sensorType,
int maxEnrollmentsPerUser) {
final ArrayList<FingerprintSensorPropertiesInternal> props = new ArrayList<>();
props.add(new FingerprintSensorPropertiesInternal(
0 /* sensorId */,
SensorProperties.STRENGTH_STRONG,
maxEnrollmentsPerUser,
new ArrayList<>() /* componentInfo */,
sensorType,
true /* resetLockoutRequiresHardwareAuthToken */));
when(mockedFingerprintManager.getSensorPropertiesInternal()).thenReturn(props);
}
public static void setupFingerprintEnrolledFingerprints(
@NonNull FingerprintManager mockedFingerprintManager,
int userId,
int enrolledFingerprints) {
final ArrayList<Fingerprint> ret = new ArrayList<>();
for (int i = 0; i < enrolledFingerprints; ++i) {
ret.add(new Fingerprint("name", 0, 0, 0L));
}
when(mockedFingerprintManager.getEnrolledFingerprints(userId)).thenReturn(ret);
}
} }

View File

@@ -45,6 +45,7 @@ import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_G
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage; import static com.google.common.truth.Truth.assertWithMessage;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import android.annotation.NonNull; import android.annotation.NonNull;
@@ -71,6 +72,8 @@ import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule; import org.mockito.junit.MockitoRule;
import java.util.concurrent.atomic.AtomicBoolean;
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
public class AutoCredentialViewModelTest { public class AutoCredentialViewModelTest {
@@ -299,13 +302,18 @@ public class AutoCredentialViewModelTest {
when(mLockPatternUtils.verifyGatekeeperPasswordHandle(gkPwHandle, newChallenge, userId)) when(mLockPatternUtils.verifyGatekeeperPasswordHandle(gkPwHandle, newChallenge, userId))
.thenReturn(newGoodCredential(gkPwHandle, new byte[] { 1 })); .thenReturn(newGoodCredential(gkPwHandle, new byte[] { 1 }));
final AtomicBoolean hasCalledRemoveGkPwHandle = new AtomicBoolean();
doAnswer(invocation -> {
hasCalledRemoveGkPwHandle.set(true);
return null;
}).when(mLockPatternUtils).removeGatekeeperPasswordHandle(gkPwHandle);
// Run credential check // Run credential check
@CredentialAction final int action = mViewModel.checkCredential(); @CredentialAction final int action = mViewModel.checkCredential();
// Check viewModel behavior // Check viewModel behavior
assertThat(action).isEqualTo(CREDENTIAL_IS_GENERATING_CHALLENGE); assertThat(action).isEqualTo(CREDENTIAL_IS_GENERATING_CHALLENGE);
assertThat(mViewModel.getGenerateChallengeFailedLiveData().getValue()).isNull(); assertThat(mViewModel.getGenerateChallengeFailedLiveData().getValue()).isNull();
assertThat(mChallengeGenerator.mCallbackRunCount).isEqualTo(1);
// Check data inside CredentialModel // Check data inside CredentialModel
final Bundle extras = mViewModel.createCredentialIntentExtra(); final Bundle extras = mViewModel.createCredentialIntentExtra();
@@ -314,6 +322,8 @@ public class AutoCredentialViewModelTest {
assertThat(extras.getByteArray(EXTRA_KEY_CHALLENGE_TOKEN)).isNotNull(); assertThat(extras.getByteArray(EXTRA_KEY_CHALLENGE_TOKEN)).isNotNull();
assertThat(extras.getLong(EXTRA_KEY_GK_PW_HANDLE)).isEqualTo(INVALID_GK_PW_HANDLE); assertThat(extras.getLong(EXTRA_KEY_GK_PW_HANDLE)).isEqualTo(INVALID_GK_PW_HANDLE);
assertThat(extras.getLong(EXTRA_KEY_CHALLENGE)).isNotEqualTo(INVALID_CHALLENGE); assertThat(extras.getLong(EXTRA_KEY_CHALLENGE)).isNotEqualTo(INVALID_CHALLENGE);
assertThat(mChallengeGenerator.mCallbackRunCount).isEqualTo(1);
assertThat(hasCalledRemoveGkPwHandle.get()).isFalse();
// Check createGeneratingChallengeExtras() // Check createGeneratingChallengeExtras()
final Bundle generatingChallengeExtras = mViewModel.createGeneratingChallengeExtras(); final Bundle generatingChallengeExtras = mViewModel.createGeneratingChallengeExtras();
@@ -511,6 +521,12 @@ public class AutoCredentialViewModelTest {
when(mLockPatternUtils.verifyGatekeeperPasswordHandle(gkPwHandle, newChallenge, userId)) when(mLockPatternUtils.verifyGatekeeperPasswordHandle(gkPwHandle, newChallenge, userId))
.thenReturn(newGoodCredential(gkPwHandle, new byte[] { 1 })); .thenReturn(newGoodCredential(gkPwHandle, new byte[] { 1 }));
final AtomicBoolean hasCalledRemoveGkPwHandle = new AtomicBoolean();
doAnswer(invocation -> {
hasCalledRemoveGkPwHandle.set(true);
return null;
}).when(mLockPatternUtils).removeGatekeeperPasswordHandle(gkPwHandle);
// Run checkNewCredentialFromActivityResult() // Run checkNewCredentialFromActivityResult()
final Intent intent = new Intent().putExtra(EXTRA_KEY_GK_PW_HANDLE, gkPwHandle); final Intent intent = new Intent().putExtra(EXTRA_KEY_GK_PW_HANDLE, gkPwHandle);
final boolean ret = mViewModel.checkNewCredentialFromActivityResult(true, final boolean ret = mViewModel.checkNewCredentialFromActivityResult(true,
@@ -524,6 +540,7 @@ public class AutoCredentialViewModelTest {
assertThat(extras.getByteArray(EXTRA_KEY_CHALLENGE_TOKEN)).isNotNull(); assertThat(extras.getByteArray(EXTRA_KEY_CHALLENGE_TOKEN)).isNotNull();
assertThat(extras.getLong(EXTRA_KEY_GK_PW_HANDLE)).isEqualTo(INVALID_GK_PW_HANDLE); assertThat(extras.getLong(EXTRA_KEY_GK_PW_HANDLE)).isEqualTo(INVALID_GK_PW_HANDLE);
assertThat(mChallengeGenerator.mCallbackRunCount).isEqualTo(1); assertThat(mChallengeGenerator.mCallbackRunCount).isEqualTo(1);
assertThat(hasCalledRemoveGkPwHandle.get()).isTrue();
} }
@Test @Test
@@ -541,6 +558,12 @@ public class AutoCredentialViewModelTest {
when(mLockPatternUtils.verifyGatekeeperPasswordHandle(gkPwHandle, newChallenge, userId)) when(mLockPatternUtils.verifyGatekeeperPasswordHandle(gkPwHandle, newChallenge, userId))
.thenReturn(newGoodCredential(gkPwHandle, new byte[] { 1 })); .thenReturn(newGoodCredential(gkPwHandle, new byte[] { 1 }));
final AtomicBoolean hasCalledRemoveGkPwHandle = new AtomicBoolean();
doAnswer(invocation -> {
hasCalledRemoveGkPwHandle.set(true);
return null;
}).when(mLockPatternUtils).removeGatekeeperPasswordHandle(gkPwHandle);
// Run checkNewCredentialFromActivityResult() // Run checkNewCredentialFromActivityResult()
final Intent intent = new Intent().putExtra(EXTRA_KEY_GK_PW_HANDLE, gkPwHandle); final Intent intent = new Intent().putExtra(EXTRA_KEY_GK_PW_HANDLE, gkPwHandle);
final boolean ret = mViewModel.checkNewCredentialFromActivityResult(false, final boolean ret = mViewModel.checkNewCredentialFromActivityResult(false,
@@ -554,6 +577,7 @@ public class AutoCredentialViewModelTest {
assertThat(extras.getByteArray(EXTRA_KEY_CHALLENGE_TOKEN)).isNotNull(); assertThat(extras.getByteArray(EXTRA_KEY_CHALLENGE_TOKEN)).isNotNull();
assertThat(extras.getLong(EXTRA_KEY_GK_PW_HANDLE)).isEqualTo(INVALID_GK_PW_HANDLE); assertThat(extras.getLong(EXTRA_KEY_GK_PW_HANDLE)).isEqualTo(INVALID_GK_PW_HANDLE);
assertThat(mChallengeGenerator.mCallbackRunCount).isEqualTo(1); assertThat(mChallengeGenerator.mCallbackRunCount).isEqualTo(1);
assertThat(hasCalledRemoveGkPwHandle.get()).isTrue();
} }
public static class TestChallengeGenerator implements ChallengeGenerator { public static class TestChallengeGenerator implements ChallengeGenerator {

View File

@@ -20,6 +20,8 @@ import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_REAR
import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_UDFPS_OPTICAL; import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_UDFPS_OPTICAL;
import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_UDFPS_ULTRASONIC; import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_UDFPS_ULTRASONIC;
import static com.android.settings.biometrics2.data.repository.FingerprintRepositoryTest.setupFingerprintEnrolledFingerprints;
import static com.android.settings.biometrics2.data.repository.FingerprintRepositoryTest.setupFingerprintFirstSensor;
import static com.android.settings.biometrics2.data.repository.FingerprintRepositoryTest.setupSuwMaxFingerprintsEnrollable; import static com.android.settings.biometrics2.data.repository.FingerprintRepositoryTest.setupSuwMaxFingerprintsEnrollable;
import static com.android.settings.biometrics2.ui.model.FingerprintEnrollIntroStatus.FINGERPRINT_ENROLLABLE_ERROR_REACH_MAX; import static com.android.settings.biometrics2.ui.model.FingerprintEnrollIntroStatus.FINGERPRINT_ENROLLABLE_ERROR_REACH_MAX;
import static com.android.settings.biometrics2.ui.model.FingerprintEnrollIntroStatus.FINGERPRINT_ENROLLABLE_OK; import static com.android.settings.biometrics2.ui.model.FingerprintEnrollIntroStatus.FINGERPRINT_ENROLLABLE_OK;
@@ -32,8 +34,6 @@ import static com.android.settings.biometrics2.util.EnrollmentRequestUtil.newIsS
import static com.android.settings.biometrics2.util.EnrollmentRequestUtil.newIsSuwPortalRequest; import static com.android.settings.biometrics2.util.EnrollmentRequestUtil.newIsSuwPortalRequest;
import static com.android.settings.biometrics2.util.EnrollmentRequestUtil.newIsSuwRequest; import static com.android.settings.biometrics2.util.EnrollmentRequestUtil.newIsSuwRequest;
import static com.android.settings.biometrics2.util.EnrollmentRequestUtil.newIsSuwSuggestedActionFlowRequest; import static com.android.settings.biometrics2.util.EnrollmentRequestUtil.newIsSuwSuggestedActionFlowRequest;
import static com.android.settings.biometrics2.util.FingerprintManagerUtil.setupFingerprintEnrolledFingerprints;
import static com.android.settings.biometrics2.util.FingerprintManagerUtil.setupFingerprintFirstSensor;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
@@ -134,26 +134,47 @@ public class FingerprintEnrollIntroViewModelTest {
} }
@Test @Test
public void testOnStartToUpdateEnrollableStatus_isNotSuw() { public void testOnStartToUpdateEnrollableStatusOk_isNotSuw() {
testOnStartToUpdateEnrollableStatus(newAllFalseRequest(mApplication)); testOnStartToUpdateEnrollableStatusOk(newAllFalseRequest(mApplication));
} }
@Test @Test
public void testOnStartToUpdateEnrollableStatus_isSuwDeferred() { public void testOnStartToUpdateEnrollableStatusReachMax_isNotSuw() {
testOnStartToUpdateEnrollableStatus(newIsSuwDeferredRequest(mApplication)); testOnStartToUpdateEnrollableStatusReachMax(newAllFalseRequest(mApplication));
} }
@Test @Test
public void testOnStartToUpdateEnrollableStatus_isSuwPortal() { public void testOnStartToUpdateEnrollableStatusOk_isSuwDeferred() {
testOnStartToUpdateEnrollableStatus(newIsSuwPortalRequest(mApplication)); testOnStartToUpdateEnrollableStatusOk(newIsSuwDeferredRequest(mApplication));
} }
@Test @Test
public void testOnStartToUpdateEnrollableStatus_isSuwSuggestedActionFlow() { public void testOnStartToUpdateEnrollableStatusReachMax_isSuwDeferred() {
testOnStartToUpdateEnrollableStatus(newIsSuwSuggestedActionFlowRequest(mApplication)); testOnStartToUpdateEnrollableStatusReachMax(newIsSuwDeferredRequest(mApplication));
} }
private void testOnStartToUpdateEnrollableStatus(@NonNull EnrollmentRequest request) { @Test
public void testOnStartToUpdateEnrollableStatusOk_isSuwPortal() {
testOnStartToUpdateEnrollableStatusOk(newIsSuwPortalRequest(mApplication));
}
@Test
public void testOnStartToUpdateEnrollableStatusReachMax_isSuwPortal() {
testOnStartToUpdateEnrollableStatusReachMax(newIsSuwPortalRequest(mApplication));
}
@Test
public void testOnStartToUpdateEnrollableStatusOk_isSuwSuggestedActionFlow() {
testOnStartToUpdateEnrollableStatusOk(newIsSuwSuggestedActionFlowRequest(mApplication));
}
@Test
public void testOnStartToUpdateEnrollableStatusReachMax_isSuwSuggestedActionFlow() {
testOnStartToUpdateEnrollableStatusReachMax(
newIsSuwSuggestedActionFlowRequest(mApplication));
}
private void testOnStartToUpdateEnrollableStatusOk(@NonNull EnrollmentRequest request) {
final int userId = 45; final int userId = 45;
mViewModel.setUserId(userId); mViewModel.setUserId(userId);
mViewModel.setEnrollmentRequest(request); mViewModel.setEnrollmentRequest(request);
@@ -163,19 +184,28 @@ public class FingerprintEnrollIntroViewModelTest {
mViewModel.onStart(mLifecycleOwner); mViewModel.onStart(mLifecycleOwner);
FingerprintEnrollIntroStatus status = mViewModel.getPageStatusLiveData().getValue(); FingerprintEnrollIntroStatus status = mViewModel.getPageStatusLiveData().getValue();
assertThat(status.getEnrollableStatus()).isEqualTo(FINGERPRINT_ENROLLABLE_OK); assertThat(status.getEnrollableStatus()).isEqualTo(FINGERPRINT_ENROLLABLE_OK);
}
private void testOnStartToUpdateEnrollableStatusReachMax(@NonNull EnrollmentRequest request) {
final int userId = 45;
mViewModel.setUserId(userId);
mViewModel.setEnrollmentRequest(request);
setupFingerprintEnrolledFingerprints(mFingerprintManager, userId, 5); setupFingerprintEnrolledFingerprints(mFingerprintManager, userId, 5);
setupFingerprintFirstSensor(mFingerprintManager, TYPE_UDFPS_OPTICAL, 5); setupFingerprintFirstSensor(mFingerprintManager, TYPE_UDFPS_OPTICAL, 5);
mViewModel.onStart(mLifecycleOwner); mViewModel.onStart(mLifecycleOwner);
status = mViewModel.getPageStatusLiveData().getValue(); FingerprintEnrollIntroStatus status = mViewModel.getPageStatusLiveData().getValue();
assertThat(status.getEnrollableStatus()).isEqualTo(FINGERPRINT_ENROLLABLE_ERROR_REACH_MAX); assertThat(status.getEnrollableStatus()).isEqualTo(FINGERPRINT_ENROLLABLE_ERROR_REACH_MAX);
} }
@Test @Test
public void textCanAssumeUdfps() { public void textCanAssumeUdfps_forUdfpsUltrasonicSensor() {
setupFingerprintFirstSensor(mFingerprintManager, TYPE_UDFPS_ULTRASONIC, 1); setupFingerprintFirstSensor(mFingerprintManager, TYPE_UDFPS_ULTRASONIC, 1);
assertThat(mViewModel.canAssumeUdfps()).isEqualTo(true); assertThat(mViewModel.canAssumeUdfps()).isEqualTo(true);
}
@Test
public void textCanAssumeUdfps_forRearSensor() {
setupFingerprintFirstSensor(mFingerprintManager, TYPE_REAR, 1); setupFingerprintFirstSensor(mFingerprintManager, TYPE_REAR, 1);
assertThat(mViewModel.canAssumeUdfps()).isEqualTo(false); assertThat(mViewModel.canAssumeUdfps()).isEqualTo(false);
} }
@@ -238,7 +268,7 @@ public class FingerprintEnrollIntroViewModelTest {
assertThat(status.getEnrollableStatus()).isEqualTo(FINGERPRINT_ENROLLABLE_OK); assertThat(status.getEnrollableStatus()).isEqualTo(FINGERPRINT_ENROLLABLE_OK);
// Perform click on `next` // Perform click on `next`
mViewModel.onNextButtonClick(null); mViewModel.onNextButtonClick();
assertThat(mViewModel.getActionLiveData().getValue()) assertThat(mViewModel.getActionLiveData().getValue())
.isEqualTo(FINGERPRINT_ENROLL_INTRO_ACTION_CONTINUE_ENROLL); .isEqualTo(FINGERPRINT_ENROLL_INTRO_ACTION_CONTINUE_ENROLL);
@@ -258,7 +288,7 @@ public class FingerprintEnrollIntroViewModelTest {
assertThat(status.getEnrollableStatus()).isEqualTo(FINGERPRINT_ENROLLABLE_ERROR_REACH_MAX); assertThat(status.getEnrollableStatus()).isEqualTo(FINGERPRINT_ENROLLABLE_ERROR_REACH_MAX);
// Perform click on `next` // Perform click on `next`
mViewModel.onNextButtonClick(null); mViewModel.onNextButtonClick();
assertThat(mViewModel.getActionLiveData().getValue()) assertThat(mViewModel.getActionLiveData().getValue())
.isEqualTo(FINGERPRINT_ENROLL_INTRO_ACTION_DONE_AND_FINISH); .isEqualTo(FINGERPRINT_ENROLL_INTRO_ACTION_DONE_AND_FINISH);
@@ -266,7 +296,7 @@ public class FingerprintEnrollIntroViewModelTest {
@Test @Test
public void testOnSkipOrCancelButtonClick() { public void testOnSkipOrCancelButtonClick() {
mViewModel.onSkipOrCancelButtonClick(null); mViewModel.onSkipOrCancelButtonClick();
assertThat(mViewModel.getActionLiveData().getValue()) assertThat(mViewModel.getActionLiveData().getValue())
.isEqualTo(FINGERPRINT_ENROLL_INTRO_ACTION_SKIP_OR_CANCEL); .isEqualTo(FINGERPRINT_ENROLL_INTRO_ACTION_SKIP_OR_CANCEL);

View File

@@ -20,10 +20,10 @@ import static com.android.settings.biometrics.BiometricEnrollBase.RESULT_FINISHE
import static com.android.settings.biometrics.BiometricEnrollBase.RESULT_SKIP; import static com.android.settings.biometrics.BiometricEnrollBase.RESULT_SKIP;
import static com.android.settings.biometrics.BiometricEnrollBase.RESULT_TIMEOUT; import static com.android.settings.biometrics.BiometricEnrollBase.RESULT_TIMEOUT;
import static com.android.settings.biometrics.fingerprint.SetupFingerprintEnrollIntroduction.EXTRA_FINGERPRINT_ENROLLED_COUNT; import static com.android.settings.biometrics.fingerprint.SetupFingerprintEnrollIntroduction.EXTRA_FINGERPRINT_ENROLLED_COUNT;
import static com.android.settings.biometrics2.data.repository.FingerprintRepositoryTest.setupFingerprintEnrolledFingerprints;
import static com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollmentViewModel.SAVED_STATE_IS_WAITING_ACTIVITY_RESULT; import static com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollmentViewModel.SAVED_STATE_IS_WAITING_ACTIVITY_RESULT;
import static com.android.settings.biometrics2.util.EnrollmentRequestUtil.newAllFalseRequest; import static com.android.settings.biometrics2.util.EnrollmentRequestUtil.newAllFalseRequest;
import static com.android.settings.biometrics2.util.EnrollmentRequestUtil.newIsSuwRequest; import static com.android.settings.biometrics2.util.EnrollmentRequestUtil.newIsSuwRequest;
import static com.android.settings.biometrics2.util.FingerprintManagerUtil.setupFingerprintEnrolledFingerprints;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;

View File

@@ -1,58 +0,0 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.biometrics2.util;
import static org.mockito.Mockito.when;
import android.hardware.biometrics.SensorProperties;
import android.hardware.fingerprint.Fingerprint;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorProperties;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import androidx.annotation.NonNull;
import java.util.ArrayList;
public class FingerprintManagerUtil {
public static void setupFingerprintFirstSensor(
@NonNull FingerprintManager mockedFingerprintManager,
@FingerprintSensorProperties.SensorType int sensorType,
int maxEnrollmentsPerUser) {
final ArrayList<FingerprintSensorPropertiesInternal> props = new ArrayList<>();
props.add(new FingerprintSensorPropertiesInternal(
0 /* sensorId */,
SensorProperties.STRENGTH_STRONG,
maxEnrollmentsPerUser,
new ArrayList<>() /* componentInfo */,
sensorType,
true /* resetLockoutRequiresHardwareAuthToken */));
when(mockedFingerprintManager.getSensorPropertiesInternal()).thenReturn(props);
}
public static void setupFingerprintEnrolledFingerprints(
@NonNull FingerprintManager mockedFingerprintManager,
int userId,
int enrolledFingerprints) {
final ArrayList<Fingerprint> ret = new ArrayList<>();
for (int i = 0; i < enrolledFingerprints; ++i) {
ret.add(new Fingerprint("name", 0, 0, 0L));
}
when(mockedFingerprintManager.getEnrolledFingerprints(userId)).thenReturn(ret);
}
}